GLSL Tutorial
Directional Light per Pixel
In this section we'll modify the previous shaders to compute the directional light per pixel. Basically we're going to split the work between the two shaders, so that some operations are done per pixel.
First lets take a look at the information we receive per vertex:
//我们可以从每个顶点接收三个数值
- normal //顶点法向量
- half vector //眼睛坐标和灯光的方向的和
- light direction //灯光的方向
We can also perform some computations combining the lights settings with the materials in the vertex shader, hence helping to split the load between the vertex and fragment shader.
The vertex shader could be:
varying vec4 diffuse,ambient; varying vec3 normal,lightDir,halfVector; void main() { /* first transform the normal into eye space and normalize the result */ normal = normalize(gl_NormalMatrix * gl_Normal); /* now normalize the light's direction. Note that according to the OpenGL specification, the light is stored in eye space. Also since we're talking about a directional light, the position field is actually direction */ lightDir = normalize(vec3(gl_LightSource[0].position)); //gl_LightSource[0].position存储在眼坐标 /* Normalize the halfVector to pass it to the fragment shader */ halfVector = normalize(gl_LightSource[0].halfVector.xyz);
//把halfVector传递给片元着色器 /* Compute the diffuse, ambient and globalAmbient terms */ diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse; ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient; ambient += gl_LightModel.ambient * gl_FrontMaterial.ambient;//全局环境光 gl_Position = ftransform(); }
Now for the fragment shader. The same varying variables have to be declared. We have to normalize again the normal. Note that there is no need to normalize again the light direction. This last vector is common to all vertices since we're talking about a directional light. The interpolation between two equal vectors yields the same vector, so there is no need to normalize again. Then we compute the dot product between the interpolated normalized normal and the light direction.
varying vec4 diffuse,ambient; varying vec3 normal,lightDir,halfVector; void main() { vec3 n,halfV; float NdotL,NdotHV; /* The ambient term will always be present */ vec4 color = ambient; /* a fragment shader can't write a varying variable, hence we need a new variable to store the normalized interpolated normal */ n = normalize(normal); /* compute the dot product between normal and ldir */ NdotL = max(dot(n,lightDir),0.0); .... }
If the dot product NdotL is greater than zero then we must compute the diffuse component, which is the diffuse setting we received from the vertex shader multiplied by the dot product. We must also compute the specular term. To compute the specular component we must first normalize the halfvector we received from the vertex shader, and also compute the dot product between the normalized halfvector and the normal.
... if (NdotL > 0.0) { color += diffuse * NdotL; halfV = normalize(halfVector); NdotHV = max(dot(n,halfV),0.0); color += gl_FrontMaterial.specular * gl_LightSource[0].specular * pow(NdotHV, gl_FrontMaterial.shininess); } gl_FragColor = color; }
The following images show the difference in terms of visual results between computing the lighting per vertex versus per pixel.
Per Vertex | Per Pixel |
A Shader Designer project containing the shaders for the directional light per pixel can be found in here