Phong Shader: Fixed light specular component rotates with camera - webgl

I wrote a Phong shader for WebGL. My Scene supports camera rotation using Euler Angles. The light should be fixed in the scene. As soon as I rotate the camera the specular effects on objects move as well. Translation also seems to have strange effects on the specular component. I
(hopefully) made sure that calculations are performed in eye space. I first want to find out if my shaders are correct.
Here is my code:
Vertex Shader
struct Light {
vec3 position;
/* ... */
};
uniform Light u_light;
uniform mat4 u_modelViewProjMat;
uniform mat4 u_modelViewMat;
uniform mat4 u_viewMat;
uniform mat3 u_normalMat;
in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;
out vec2 v_texCoord;
out vec3 v_normal;
out vec3 f_position;
out vec3 v_lightPos;
void main() {
v_normal = u_normalMat * a_normal;
v_texCoord = a_texCoord;
f_position = vec3(u_modelViewMat * vec4(a_position, 1.0));
v_lightPos = vec3(u_viewMat * vec4(u_light.position, 1.0));
gl_Position = u_modelViewProjMat * vec4(a_position, 1.0);
}
Fragment Shader
struct Material {
sampler2D diffuse;
vec3 specular;
float shininess;
};
struct Light {
/* ... */
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Material u_material;
uniform Light u_light;
uniform vec3 u_eyePosition;
in vec2 v_texCoord;
in vec3 v_normal;
in vec3 f_position;
in vec3 v_lightPos;
out vec4 outColor;
void main() {
vec3 texture = vec3(texture(u_material.diffuse, v_texCoord));
// ambient
vec3 ambient = u_light.ambient * texture;
// diffuse
vec3 normal = normalize(v_normal);
vec3 lightDir = normalize(v_lightPos - f_position);
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = u_light.diffuse * diff * texture;
// specular
vec3 viewDir = normalize(u_eyePosition - f_position);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_material.shininess);
vec3 specular = u_light.specular * (spec * u_material.specular);
outColor = vec4((ambient + diffuse + specular), 1.0);
}

f_position is a camera space position, so to get the viewDir vector for your specular component you should only perform vec3 viewDir = normalize(-f_position); or work with world space coordinates

Related

Webgl - How to make Specular Light not change size

I am trying to implement specular lighting (thats coming from the front) but the light is always changing size in an unnatural way. How do I fix this?
I hardcoded viewerPos to test. I'm using a halfway vector "shortcut" so I have to calculate less things as explained here: https://webglfundamentals.org/webgl/lessons/webgl-3d-lighting-point.html
Video with my lighting implemented: https://streamable.com/j95bz7
// Vertex shader program
const vsSource = `
attribute vec4 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec2 aTextureCoord;
uniform mat4 uNormalMatrix;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform highp vec3 uViewPos;
varying highp vec2 vTextureCoord;
varying highp vec4 vNormal;
varying highp mat4 vModelViewMatrix;
varying highp vec3 vPos;
void main(void) {
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
vTextureCoord = aTextureCoord; //Textura
vModelViewMatrix = uModelViewMatrix;
vPos = (uModelViewMatrix * aVertexPosition).xyz;
vNormal = uNormalMatrix * vec4(aVertexNormal, 1.0);
}
`;
// Fragment shader program
const fsSource = `
varying highp vec2 vTextureCoord;
varying highp vec4 vNormal;
varying highp mat4 vModelViewMatrix;
varying highp vec3 vPos;
uniform sampler2D uSampler;
void main(void) {
// Apply lighting effect
highp vec4 texelColor = texture2D(uSampler, vTextureCoord);
//Luz Ambiente
highp vec3 ambientLight = 0.3 * vec3(1.0, 1.0, 1.0);
//Luz Difusa
highp vec3 directionalLightColor = vec3(1, 1, 1);
highp vec3 directionalVector = vec3(0.0, 0.0, 1.0);
highp float directional = max(dot(vNormal.xyz, normalize(directionalVector)), 0.0);
//Luz Especular
highp vec3 viewerPos = vec3(0, 0, -6); //NOTA: PASSAR PARA SHADERS, NAO DAR HARDCODE
highp vec3 surfaceToLightDirection = (-1.0 * directionalVector);
highp vec3 surfaceToViewDirection = (vPos - viewerPos);
highp vec3 halfVector = normalize(surfaceToLightDirection + surfaceToViewDirection);
highp float specular = max(dot(vNormal.xyz, halfVector), 0.0);
highp vec3 vLighting = ambientLight;// + (directionalLightColor * directional);
gl_FragColor = vec4(texelColor.rgb * vLighting + (specular * 0.5), texelColor.a);
}
`;

WebGL attribute is always null

I'm trying to make a graphic engine in WebGL, but I'm having lots of trouble.
One of this problems is about attributes being null or -1 when I call them with "gl.getUniformLocation".
My main problem with this right now is "aVertexTextureCoords", which is always -1.
I leave here my shaders, just in case they're the problem.
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec4 aVertexColor;
attribute vec2 aVertexTextureCoords;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform mat4 uNMatrix;
uniform float uAlpha;
const int NUM_LIGHTS = 4;
uniform vec4 uMaterialDiffuse;
uniform vec3 uLightPosition[NUM_LIGHTS];
//varyings
varying vec4 vColor;
varying vec2 vTextureCoord;
varying vec3 vNormal;
varying vec3 vLightRay[NUM_LIGHTS];
void main(void) {
//Transformed vertex position
vec4 vertex = uMVMatrix * vec4(aVertexPosition, 1.0);
//Transformed normal position
vNormal = vec3(uNMatrix * vec4(aVertexNormal, 1.0));
//Calculate light ray per each light
for(int i=0; i < NUM_LIGHTS; i++){
vec4 lightPosition = uMVMatrix * vec4(uLightPosition[i], 1.0);
vLightRay[i] = vertex.xyz - lightPosition.xyz;
}
//Final vertex position
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vTextureCoord = aVertexTextureCoords;
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
//object uniforms
uniform bool uWireframe;
uniform vec4 uMaterialAmbient;
uniform vec4 uMaterialDiffuse;
//Incluimos un uniform que asociara la textura a un uniform
uniform sampler2D uSampler;
//light uniforms
const int NUM_LIGHTS = 4;
uniform bool uLightSource;
uniform vec4 uLightAmbient;
uniform vec4 uLightDiffuse[NUM_LIGHTS];
uniform float uCutOff;
//varyings
varying vec2 vTextureCoord;
varying vec3 vNormal;
varying vec3 vLightRay[NUM_LIGHTS];
void main(void)
{
if(uWireframe || uLightSource){
gl_FragColor = uMaterialDiffuse;
}
else{
vec4 Ia = uLightAmbient * uMaterialAmbient; //Ambient component: one for all
vec4 finalColor = vec4(0.0,0.0,0.0,1.0); //Color that will be assigned to gl_FragColor
vec3 N = normalize(vNormal);
vec3 L = vec3(0.0);
float lambertTerm = 0.0;
for(int i = 0; i < NUM_LIGHTS; i++){ //For each light
L = normalize(vLightRay[i]); //Calculate reflexion
lambertTerm = dot(N, -L);
if (lambertTerm > uCutOff){
finalColor += uLightDiffuse[i] * uMaterialDiffuse *lambertTerm; //Add diffuse component, one per light
}
}
//Final color
finalColor += Ia;
finalColor.a = 1.0; //Add ambient component: one for all
gl_FragColor = finalColor * texture2D(uSampler, vTextureCoord); //The alpha value in this example will be 1.0
}
}
</script>
Can somebody tell me what I'm doing wrong?
Calling gl.getUniformLocation for a vertex attribute is wrong because it's an attribute, not a uniform. Replace the call with gl.getAttribLocation.

GLSL Shader Error "Constructor calls may not have precision"

GLSL Shader Error
ERROR: 0:1: '(' : syntax error: Constructor calls may not have precision
I'm seeing this error with Xcode 6 on an iOS 8 app based on GLPaint demo... (works fine in iOS7)
I also noticed they no longer use the "STRINGIFY" thing in version 1.13 of GLPaint demo.
.vsh
static const char* BaseVS = STRINGIFY
(
attribute highp vec4 inVertex;
uniform highp mat4 MVP;
uniform highp float pointSize;
uniform highp vec4 vertexColor;
uniform highp float brushRotation;
varying highp vec4 color;
void main()
{
gl_Position = MVP * inVertex;
gl_PointSize = pointSize;
color = vertexColor;
}
);
.fsh
static const char* BaseFS = STRINGIFY
(
uniform sampler2D texture;
uniform sampler2D normalMap;
uniform highp float brushRotation;
varying highp vec4 color;
varying highp vec3 normal;
varying highp vec3 lightDir;
varying highp vec3 eyeVec;
precision highp float;
void main (void)
{
highp float vRotation = (brushRotation/180.0)*3.14;;
highp float mid = 0.5;
highp vec2 rotated = vec2(cos(vRotation) * (gl_PointCoord.x - mid) + sin(vRotation) * (gl_PointCoord.y - mid) + mid,
cos(vRotation) * (gl_PointCoord.y - mid) - sin(vRotation) * (gl_PointCoord.x - mid) + mid);
highp vec4 rotatedTexture = texture2D( texture, rotated);
gl_FragColor = color * rotatedTexture;
}
);
The problem was in a method used for random generation. I removed the "high" before the vec2() construction. (Sigh)
highp float rand(highp vec2 co)
{
return fract(sin(dot(co.xy ,highp vec2(12.9898,78.233))) * 43758.5453);
}

iPad Opengl ES program works fine on simulator but not device

For the device, all of my shaders load fine except one. For this shader program I get "Fragment program failed to compile with current context state" error, followed by a similar error for the vertex shader when I make a call to glGetProgramInfoLog(...);
Vertex shader:
#version 100
uniform mat4 Projection;
uniform mat4 Modelview;
uniform mat4 Rotation;
uniform vec3 Translation;
uniform vec4 LightDirection;
uniform vec4 MaterialDiffuse;
uniform float MaterialShininess;
attribute vec3 position;
attribute vec3 normal;
varying vec4 color;
varying float specularCoefficient;
void main() {
vec3 _normal = normalize(mat3(Modelview[0].xyz, Modelview[1].xyz, Modelview[2].xyz)*normal);
// There is an easier way to do the above using typecast, but is apparently broken
float NdotL = dot(-_normal, normalize(vec3(LightDirection)));
if(NdotL < 0.0){
NdotL = 0.0;
}
color = NdotL * MaterialDiffuse;
float NdotO = dot(-_normal, vec3(0.0, 0.0, -1.0));
if(NdotO < 0.0){
NdotO = 0.0;
}
specularCoefficient = pow(NdotO, MaterialShininess);
vec3 p = position + Translation;
gl_Position = Projection*Modelview*vec4(p, 1.0);
}
Fragment shader:
#version 100
precision mediump float;
varying vec4 color;
varying float specularCoefficient;
uniform vec4 MaterialSpecular;
void main(){
gl_FragColor = vec4((color + specularCoefficient*MaterialSpecular).rgb, 1.0);
}
I am not sure what is going on, especially since I have a similar program that is exactly as above with the addition of texture coordinates. Also, I checked the compile status of each shader when I linked the programs using glGetShaderiv(theShader, GL_COMPILE_STATUS, &result) and they all checked out fine. Any ideas?
Changing the line
gl_FragColor = vec4((color + specularCoefficient*MaterialSpecular).rgb, 1.0);
in the fragment shader to
gl_FragColor = vec4((1.0*color + specularCoefficient*MaterialSpecular).rgb, 1.0);
fixes the problem. I suspect it has something to do with precision related to the varying variable color, for a reordering of the line to
gl_FragColor = vec4((MaterialSpecular + specularCoefficient*color).rgb, 1.0);
works as well.

Coloring with shaders

I have created a project on top of the XCode OpenGL ES template.
Is there a way to customize the shader color for each object?
Shader.vsh:
attribute vec4 position;
attribute vec3 normal;
varying lowp vec4 colorVarying;
uniform mat4 modelViewProjectionMatrix;
uniform mat3 normalMatrix;
void main()
{
vec3 eyeNormal = normalize(normalMatrix * normal);
vec3 lightPosition = vec3(1.0, 1.0, 1.0);
vec4 diffuseColor = vec4(1, 0.4, 1.0, 1.0);
float nDotVP = max(0.0, dot(eyeNormal, normalize(lightPosition)));
colorVarying = diffuseColor * nDotVP;
gl_Position = modelViewProjectionMatrix * position;
}
Shader.fsh:
varying lowp vec4 colorVarying;
void main()
{
gl_FragColor = colorVarying;
}
Do i need to create an extra attribute in Shader.vsh which parses the color to replace diffuse variable or how is this done?
I suggest using uniform variables for colors
// put this in Shader.vsh, before the main()
uniform vec4 diffuseColor;
and in the code just set the value:
glUseProgram(progID);
// object 1 (R, G, B, A is a color)
glUniform4f(glGetUniformLocation(progID, "diffuseColor"), R, G, B, A);
draw_object_1();
// object 2 (R, G, B, A is a color)
glUniform4f(glGetUniformLocation(progID, "diffuseColor"), R, G, B, A);
draw_object_2();
the same can be done with light position as well.

Resources