View Full Version : Point light VP -> Spot light VP: ?

01-27-2004, 05:42 AM
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:


# 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 + l*d + q*d^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;


Three questions arise:

1) 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....

2) 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?

3) 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.

01-27-2004, 11:43 AM
The spotlight attenuation uses two cosine terms, such that normalized vector from lit point to light source dot normalized light direction is compared to these terms. Something like the following:

dot > inner : attenuation = 1
dot > outer : attenuation = (dot-outer)/(inner-outer)
else : attenuation = 0

You can easily code this up in a few instructions, using MAD_SAT to multiply/add (for scaling/offsetting according to the range [inner,outer]) and saturate to [0,1].