Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
64 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

RacorX5

The only improvement of RacorX5 over RacorX4 is the addition of a point light source. The light can be moved via the <Home>, <End> and with the <Left>, <Right>, <Down> and <Up> arrow keys. As with the previous example, the specular light can be changed with <C> key.


Figure 15 - RacorX5 with Point Light

Point Light Source

A point light source has color and position within a scene, but no single direction. All light rays originate from one point and illuminate equally in all directions. The intensity of the rays will remain constant regardless of their distance from the point source unless a falloff value is explicitly stated. A point light is useful to simulate light bulb.

A spot light is a light source in which all light rays illuminate in the shape of a cone. The falloff, spread, and dropoff of a spotlight are adjustable.

Light Attenuation for Point Lights

Usually the energy from the point light that hits a surface diminishes as the distance increases. There were a few functions that were used to simulate the idea of attenuation in computer graphics. The simplest one might be:

funcAttenuation = 1 / dL

The inverse linear falloff doesn't look as good as the inverse squared falloff:

funcAttenuation = 1 / dL * dL

This is the attenuation equation, that might be used in conjunction with a directional light source. The problem with this function and a point light source is, that when a light is far away, then its intensity is too low, but when it is close, the intensity becomes large leading to visual artifacts.

To get a wider range of effects a decent attenuation equation is used:

funcAttenuation = 1 / A0 + A1 * dL + A2 * dL * dL

This is the same equation that is used by the Direct3D fixed-function pipeline to calculate the falloff of point lights. A0, A1 and A2 are known as the attenuation constants. You get a radial distance falloff with A1 = 0 and A2 > 0 and a linear falloff with A1 > 0 and A2 = 0. If the light is too close to the surface, then, attenuation becomes very large, making the light too bright. To avoid this set A0 to 1 or greater.

The attenuation at the maximum range of the light is not 0.0. To prevent lights from suddenly appearing when they are at the light range, an application can increase the light range. Or, the application can set up attenuation constants so that the attenuation factor is close to 0.0 at the light range. The attenuation value is multiplied by the red, green, and blue components of the light's color to scale the light's intensity as a factor of the distance light travels to a vertex.

To demonstrate the code used to implement this equation, the life-cycle of a vertex shader is tracked in the following paragraphs.

Compared to the previous examples, the same input register layout is used by RacorX5, but to feed the vertex shader with the attenuation data, additional constant registers has to be filled with data.

Setting the Vertex Shader Constants

The three attenuation values are set in RestoreDeviceObjects():

; in const.h:
#define LIGHT_ATTENUATION 17
...
// Attenuation
D3DXVECTOR4 Attenuation(1.0f, 0.0f, 0.0f, 0.0f);
m_pd3dDevice->SetVertexShaderConstant(LIGHT_ATTENUATION, &Attenuation.x, 1);

c17.x, c17.y and c17.z are holding the A0, A1 and A2 values. The world matrix and the point light position, that is changeable with the Left, Right, Up and Down Keys are set in FrameMove():

// set the world matrix
m_pd3dDevice->SetVertexShaderConstant(WORLD_MATRIX,&m_matWorld,4);
...
if(m_bKey[VK_LEFT]) { m_bKey[VK_RIGHT]=0;m_vLight.x -= 1.0f * m_fElapsedTime;}
if(m_bKey[VK_RIGHT]) { m_bKey[VK_LEFT]=0;m_vLight.x += 1.0f * m_fElapsedTime;}
if(m_bKey[VK_UP]) { m_bKey[VK_DOWN]=0; m_vLight.y += 1.0f * m_fElapsedTime;}
if(m_bKey[VK_DOWN]) { m_bKey[VK_UP]=0; m_vLight.y -= 1.0f * m_fElapsedTime;}
if(m_bKey[VK_END]) { m_bKey[VK_HOME]=0; m_vLight.z += 1.0f * m_fElapsedTime;}
if(m_bKey[VK_HOME]) { m_bKey[VK_END]=0; m_vLight.z -= 1.0f * m_fElapsedTime;}

// light direction
m_pd3dDevice->SetVertexShaderConstant(LIGHT_VECTOR, m_vLight,1);

The world matrix is used to calculate a vector that has its origin in the position of the vertex in world space and its end point at the position of the light in world space.

The Vertex Shader

The vertex shader handles a combined diffuse and specular reflection model and the point light source. The point light's position in world space and the coordinate of the vertex in world space is used to derive a vector for the direction of the light, and the distance that the light has traveled.

If you compare this vertex shader to the previous one, the only new elements are the calculation of a light vector and the calculation of the attenuation factor.

; diffuse and specular point light

#include "const.h"

vs.1.1
; transpose and transform to clip space
mul r0, v0.x, c[CLIP_MATRIX]
mad r0, v0.y, c[CLIP_MATRIX_1], r0
mad r0, v0.z, c[CLIP_MATRIX_2], r0
add oPos, c[CLIP_MATRIX_3], r0

; output texture coords
mov oT0, v7

; transform normal
dp3 r1.x, v3, c[INVERSE_WORLD_MATRIX]
dp3 r1.y, v3, c[INVERSE_WORLD_MATRIX_1]
dp3 r1.z, v3, c[INVERSE_WORLD_MATRIX_2]

; renormalize it
dp3 r1.w, r1, r1
rsq r1.w, r1.w
mul r1, r1, r1.w

; transpose and transform position to world space
mul r7, v0.x, c[WORLD_MATRIX]
mad r7, v0.y, c[WORLD_MATRIX_1], r7
mad r7, v0.z, c[WORLD_MATRIX_2], r7
add r7, c[WORLD_MATRIX_3], r7

; calculate vector from light position
; to vertex position in world space
add r10.xyz, c[LIGHT_VECTOR], -r7.xyz

; renormalize vector VERTEX_TO_LIGHT
; d = distance
dp3 r10.w, r10, r10  ; r10.w = d * d = (x*x) + (y*y) + (z*z)
rsq r11.w, r10.w     ; r11.w = 1/d = 1/||V|| = 1/sqrt(r7.w)
mul r11, r10, r11.w

dp3 r0.x, r1, r11 ; N dot L

; Get the attenuation
; Parameters for dst:
; src1 = (ignored, d * d, d * d, ignored)
; src2 = (ignored, 1/d, ignored, 1/d)
; r10.w = d * d
; r11.w = 1 / d
dst r4, r10.w, r11.w
; dest.x = 1
; dest.y = src0.y * src1.y
; dest.z = src0.z
; dest.w = src1.w
; r4(1, d * d * 1 / d, d * d, 1/d)

; c[LIGHT_ATTENUATION].x = a0
; c[LIGHT_ATTENUATION].y = a1
; c[LIGHT_ATTENUATION].z = a2
dp3 r7.x, r4, c[LIGHT_ATTENUATION] ; (a0 + a1*d + a2* (d * d))
rcp r7.x, r7.x             ; simplified:
                           ; if (src.w != 0.0 && src.w != 1.0)
                           ;   1 / (a0 + a1*d + a2* (d * d))

; compute normalized half vector H = L + V
add r2, c[EYE_VECTOR], r11 ; L + V

; renormalize H
dp3 r2.w, r2, r2
rsq r2.w, r2.w
mul r2, r2, r2.w

; N dot H
dp3 r0.y, r1, r2

; compute specular and clamp values (lit)
; r0.x - N dot L
; r0.y - N dot H
; r0.w - specular power n
mov r0.w, c[SPEC_POWER].y ; n must be between -128.0 and 128.0
lit r4, r0

; Scale the diffuse and specular intensity by the attenuation
mul r4, r4, r7.xxxx

; diffuse color * diffuse intensity (r4.y)
mul oD0, c[DIFFUSE_COLOR], r4.y

; specular color * specular intensity (r4.z)
mul oD1, c[SPEC_COLOR], r4.z

The lines including the renormalization of the normal should look quite familiar. Then the position of the vertex is transformed to world space, which is used to calculate the Light vector L in the following add instruction. L is renormalized in the following three instructions. It is important to note, that this renormalization is used at the same time to get the parameters for the dst instruction. The following dp3 instruction performs the well-known diffuse reflection.

In the next paragraph the dst, dp3 and rcp instructions calculate the attenuation factor with the formula already shown above:

funcAttenuation = 1 / A0 + A1 * dL + A2 * dL * dL

Therefore dst gets distance * distance (squared distance) and 1 / distance via r10.w and r11.w. This instruction performs then the following calculation:

r4.x = 1
r4.y = d = d * d * 1 /d
r4.z = d * d
r4.w = 1 / d

In the next dp3 instruction, the dot product between the first three components of r4 and the attenuation constants is calculated:

; c[LIGHT_ATTENUATION].x = a0
; c[LIGHT_ATTENUATION].y = a1
; c[LIGHT_ATTENUATION].z = a2
dp3 r7.x, r4, c[LIGHT_ATTENUATION] ; (a0 + a1*d + a2* (d * d))
rcp r7.x, r7.x ; 1 / (a0 + a1*d + a2* (d * d))

The rcp instruction performs the following calculation:

if(src.w == 1.0f)
{
  dest.x = dest.y = dest.z = dest.w = 1.0f;
}
else if(src.w == 0)
{
  dest.x = dest.y = dest.z = dest.w = PLUS_INFINITY();
}
else
{
  dest.x = dest.y = dest.z = m_dest.w = 1.0f/src.w;
}

Therefore the source value of src.w is divided through 1.0f if it is not 1.0f or 0.0f. This corresponds to the attenuation function postulated above.

The only thing that is left to get the point light effect is the component-wise multiplication of the attenuation factor with the diffuse and specular factors:

; Scale the diffuse and specular intensity by the attenuation
mul r4, r4, r7.xxxx

Summarize

This example showed the implementation of a point light with a falloff model as used in the Direct3D fixed function pipeline. The light uses a combined diffuse and a specular reflection model. To improve this example, an ambient light might be added and a number of point lights with different colors might be implemented.

What happens next?

This part of the introduction covered basic lighting models and very basic transformations with vertex shaders. To read more on advanced transformations see the chapter on character animation from David Gosselin [Gosselin] and the chapter on animated grass from [Isidoro/Card].

The next part of this introduction shows the fundamentals for using pixel shaders.

References

[Blinn] James F. Blinn and Martin E. Newell, "Texture and reflection in computer generated images", Communications of the ACM, 19: 542 - 546, 1976.

[Foley] James D. Foley, Andries van Dam, Steven K. Feiner, John F. Hughes, Computer Graphics - Principles and Practice, Second Edition, pp. 728 - 731, Addison Wesley, ISBN 0-201-84840-6.

[Gosselin] David Gosselin, "Character Animation with Direct3D Vertex Shaders", ShaderX, Wordware Inc., pp ?? - ??, 2002, ISBN 1-55622-041-3.

[Haines/Möller] Eric Haines, Thomas Möller, "Normal Transforms", http://www.gignews.com/realtime020100.htm.

[Hurley] Kenneth Hurley, "Photo Realistic Faces with Vertex and Pixel Shaders", ShaderX, Wordware Inc., pp ?? - ??, 2002, ISBN 1-55622-041-3.

[Isidoro/Card] John Isidoro and Drew Card, "Animated Grass with Pixel and Vertex Shaders", , ShaderX, Wordware Inc., pp ?? - ??, 2002, ISBN 1-55622-041-3.

[RTR] Thomas Möller, Eric Haines, "Real-Time Rendering", A K Peters, Ltd., p 71, 1999, ISBN 1-56881-101-2.

[Savchenko] Sergei Savchenko, "3D Graphics Programming Games and Beyond", SAMS, p. 266, 2000, ISBN 0-672-31929-2.

[Skinner] Michael Skinner, "Bézier Patches", http://www.gamedev.net/reference/articles/article1584.asp

[Taylor] Philip Taylor, Shader Optimization.- Techniques to write more efficient shaders, http://msdn.microsoft.com/directx

[Turkowski] Ken Turkowski, "Properties of Surface-Normal Transformations", in Andrew Glassner (editor), Graphics Gems, Academic Press, Inc., pp. 539-547, 1990 or on his web site at http:/www.worldserver.com/turk/computergraphics/index.html.

Acknowledgements

I'd like to recognize a couple of individuals that were involved in proof-reading and improving this paper (in alphabetical order):

  • David Callele (University of Saskatchewan)
  • Bart Sekura (People Can Fly)

© 2000-2002 Wolfgang Engel, Frankenthal, Germany




Contents
  RacorX
  RacorX2
  RacorX3
  RacorX4
  RacorX5

  Printable version
  Discuss this article

The Series
  Fundamentals of Vertex Shaders
  Programming Vertex Shaders
  Fundamentals of Pixel Shaders
  Programming Pixel Shaders
  Diffuse & Specular Lighting with Pixel Shaders