Point light VP -> Spot light VP: ?

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:


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


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#);


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


OUTPUT oPos = result.position; # position
OUTPUT oCol0 = result.color; # color
OUTPUT oTex0 = result.texcoord; # texture coord, unit 0


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;


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.

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].