Hi everyone!
After days of reading ARB_vertex_program spec, gl lighting spec and tons
of vertex program examples, I’ve written my own implementation of point
lighting. Full lighting equation, according to the red book, is:
Vertex Color = emission + globalAmbient +
sum(attenuation * [lightAmbient + (max{L.N, 0} * diffuse) +
(max{H.N, 0} ^ shininess) * specular]
Where: emission - is the material’s emissive color;
globalAmbient - is the material ambient color * global ambient brightness;
attenuation - is a term causing lights to become dimmer with distance;
lightAmbient - is the light’s ambient color * material ambient color;
diffuse - is the light’s diffuse color * the material’s diffuse color;
shininess - is the specular exponent, which tells us how shiny a surface is;
specular - is the light’s specular color * the material’s specular color;
L - is the normalized(unit length) vector from the vertex we are lighting
to the light;
N - is the unit length normal to our vertex;
H - is the normalized “half angle” vector, which points half way between
the light and the viewer relative to the vertex position.
Here goes the vp:
!!ARBvp1.0
#-------------------------------------------------------------------------#
Lighting Vertex Program - replacement for a fixed function single front
-face-illuminating (attenuated?) point light.
#-------------------------------------------------------------------------#
Parameters’ definition:
Matrices:
PARAM mv[4] = { state.matrix.modelview }; # modelview matrix
PARAM mvi[4] = { state.matrix.modelview.invtrans }; # inverse transpose of modelview
PARAM mvp[4] = { state.matrix.mvp }; # modelview-projection matrix
Lighting:
PARAM globalAmbient = state.lightmodel.ambient; # global ambient level
PARAM lightPos = state.light[1].position; # light position
PARAM lightAmbient = state.light[1].ambient; # light ambient color
PARAM lightDiffuse = state.light[1].diffuse; # light diffuse color
PARAM lightSpecular = state.light[1].specular; # light specular color
PARAM lightAttenuation = state.light[1].attenuation; # (const, linear, quadratic, #NA#);
Materials:
PARAM materialAmbient = state.material.ambient; # material ambient color
PARAM materialEmission = state.material.emission; # material emission color
PARAM materialDiffuse = state.material.diffuse; # material diffuse color
PARAM materialSpecular = state.material.specular; # material specular color
PARAM materialShineExp = state.material.shininess; # material shininess exponent (-128, 128)
Per vertex inputs:
ATTRIB iPos = vertex.position; # position
ATTRIB iNorm = vertex.normal; # normal
ATTRIB iCol0 = vertex.color; # primary color
ATTRIB iTex0 = vertex.texcoord; # texture coord, unit 0
Outputs:
OUTPUT oPos = result.position; # position
OUTPUT oCol0 = result.color; # color
OUTPUT oTex0 = result.texcoord; # texture coord, unit 0
Temporaries:
TEMP eyeVertex; # eye space vertex position
TEMP eyeNormal; # eye space normal position
TEMP vertToLight; # vertex-to-tight vector
TEMP lightAtnFinal; # light attenuation factor
TEMP lightFactors; # light factors, calculated by LIT
TEMP halfVector; # eye-vertex-to-light-vertex half-way vector
TEMP temp;
TEMP temp2;
TEMP ambient; # final ambient color
TEMP diffuse; # final diffuse color
TEMP specular; # final specular color
Transform vertex position to clip space:
DP4 oPos.x, mvp[0], iPos;
DP4 oPos.y, mvp[1], iPos;
DP4 oPos.z, mvp[2], iPos;
DP4 oPos.w, mvp[3], iPos;
Just pass on texture coordinates:
MOV oTex0, iTex0;
Transform eye position to eye space:
DP4 eyeVertex.x, mv[0], iPos;
DP4 eyeVertex.y, mv[1], iPos;
DP4 eyeVertex.z, mv[2], iPos;
DP4 eyeVertex.w, mv[3], iPos;
Transform normal to eye space:
DP4 eyeNormal.x, mvi[0], iNorm;
DP4 eyeNormal.y, mvi[1], iNorm;
DP4 eyeNormal.z, mvi[2], iNorm;
DP4 eyeNormal.w, mvi[3], iNorm;
Normalize the eye-space normal:
DP3 eyeNormal.w, eyeNormal, eyeNormal;
RSQ eyeNormal.w, eyeNormal.w;
MUL eyeNormal.xyz, eyeNormal.w, eyeNormal;
Calculate vector from vertex to light in eye-space:
ADD vertToLight, lightPos, -eyeVertex;
Compute light attenuation factor (?):
attenuation = 1/(c + ld + qd^2);
DP3 temp.yz, vertToLight, vertToLight;
RSQ temp2.yw, temp.y;
DST temp, temp, temp2;
DP4 lightAtnFinal, temp, lightAttenuation;
RCP lightAtnFinal, lightAtnFinal.w;
Normalize vertex to light vector:
DP3 vertToLight.w, vertToLight, vertToLight;
RSQ vertToLight.w, vertToLight.w;
MUL vertToLight.xyz, vertToLight.w, vertToLight;
Normalize eyeVertex to temp:
temp is then the normalized vertex->camera vector.
DP3 temp.w, eyeVertex, eyeVertex;
RSQ temp.w, temp.w;
MUL temp.xyz, temp.w, eyeVertex;
Calculate half-way vector:
SUB halfVector, vertToLight, temp;
Normalize halfVector:
DP3 halfVector.w, halfVector, halfVector;
RSQ halfVector.w, halfVector.w;
MUL halfVector.xyz, halfVector.w, halfVector;
Prepare to calculate lighting contributions.
Diffuse lighting:
DP3 lightFactors.x, eyeNormal, vertToLight;
Specular lighting:
DP3 lightFactors.y, eyeNormal, halfVector;
Specular exponent:
MOV lightFactors.w, materialShineExp.x;
Compute the lighting coefficients:
LIT lightFactors, lightFactors;
Compute final ambient:
MUL ambient, materialAmbient, lightAmbient;
Next line can be omitted; lightFactors.x is always 1.0 (see glspec.)
#MUL ambient, ambient, lightFactors.x;
Compute final diffuse:
MUL diffuse, materialDiffuse, lightDiffuse;
MUL diffuse, diffuse, lightFactors.y;
Compute final specular:
MUL specular, materialSpecular, lightSpecular;
MUL specular, specular, lightFactors.z;
So far, we have:
ambient = ambient_light_col * ambient_mat;
diffuse = (max{L.N, 0} * diffuse_light_col * diffuse_mat;
specular = (max{H.N, 0} ^ shininess) * specular_light_col * specular_mat;
Now we evaluate (in temp):
OutputVertexColor = emission + global_ambient_level*ambient_mat +
attenuation * [ambient + diffuse + specular];
ADD temp, ambient, diffuse;
ADD temp, temp, specular;
Note: if you are using this vp in a multipass system, the following code
is valid ONLY for a final pass light. Otherwise, the previous line must be:
MAD oCol0, temp, lightAtnFinal.w, specular;
and next two lines must be commented (#).
MAD temp, temp, lightAtnFinal.w, materialEmission;
MAD oCol0, globalAmbient, materialAmbient, temp;
Fill color alpha with diffuse alpha:
MOV oCol0.w, lightDiffuse.w;
END
Three questions arise:
-
Spot lights.
How to change this program, so spot lights can be computed?
I’ve made a directional lighting vp, which is VERY easy
(just rip out all the attenuation stuff, MOV ‘lightPos’ to
‘vertToLight’ instead of ‘lightPos-eyeVertex’, haflVector
is ‘state.light[1].half’). But while implementing spotlights
I quickly ran out of temporaries… -
Performance.
Even in the most simple (read: non-fill-rate-consuming-and-
non-vertex-processing-time-consuming) scenes, I’ve got 2x
fps fall when I switch from conventional lighting to this vp.
Is that ok? May be some optimizations? -
Attenuation.
In the above vp I’m calculating a “fair” attenuation, based
on GL lighting specification. But I still think that lighting
is not correct, or correct but not realistic - you can see it
if use vp. It simply doesn’t look like a distance attenuation,
even for per-vertex lighting.I wanted to implement per-pixel dist. att. with a single 3D
texture + register combiners + modification of the above vp.Any ideas?
With respect,
-Dmitry aka Freelancer.