It's free to join Gamasutra!|Have a question? Want to know who runs this site? Here you go.|Targeting the game development market with your product or service? Get info on advertising here.||For altering your contact information or changing email subscription preferences.
Registered members can log in here.Back to the home page.

Search articles, jobs, buyers guide, and more.

By Wolfgang Engel
[Author's Bio]

Gamasutra
April 18, 2003

Introduction

Diffuse Lighting

Self-Shadowing Term

Printer Friendly Version
   

 

Change Login/Pwd
Post A Job
Post A Project
Post Resume
Post An Event
Post A Contractor
Post A Product
Write An Article
Get In Art Gallery
Submit News

 


 


Latest Letters to the Editor:
Perpetual Layoffs by Alexander Brandon [09.21.2007]

Casual friendliness in MMO's by Colby Poulson [09.20.2007]

Scrum deals and 'What is Scrum?' by Tom Plunket [08.29.2007]


[Submit Letter]

[View All...]
  



Upcoming Events:
CoGames 2008: The 1st International Workshop on Collaborative Games
Irvine, United States
05.19.08

Vancouver International Games Summit
Vancouver, Canada
05.21.08

Game Institute Challenge #7
, United States
05.30.08

DevStation
, United Kingdom
06.10.08

Interfaces Conference
Troy, United States
06.14.08

[Submit Event]
[View All...]

 


[Enter Forums...]

Note: Discussion forums for Gamasutra are hosted by the IGDA, which is free to join.
 

 

 


Features

Implementing Lighting Models With HLSL

Diffuse Lighting

In a diffuse lighting model (or "positional lighting model") the location of the light is considered. Another characteristic of diffuse lighting is that reflections are independent of the observer's position. Therefore the surface of an object in a diffuse lighting model reflects equally well in all directions. This is why diffuse lighting is commonly used to simulate matte surfaces (another more advanced lighting model to simulate matte surfaces was developed by Oren-Nayar [Fosner] [Valient]).


Figure 4. Diffuse lighting.

The diffuse lighting model following Lambert's law is described with the help of two vectors (read more in [RTR] and [Savchenko]). The light vector L describes the position of light and the normal vector N describes the normal of a vertex of the object:


Figure 5. Light and normal vector.

The diffuse reflection has its peak (cos alpha = = 1) when L and N are aligned; in other words, when the surface is perpendicular to the light beam. The diffuse reflection diminishes for smaller angles. Therefore light intensity is directly proportional to cos a.

To implement the diffuse reflection in an efficient way, a property of the dot product of two n-dimensional vectors is used:

N.L = ||N|| * ||L|| * cos alpha

If the light and normal vectors are of unit length (normalized), this leads to

N.L = cos alpha

When N.L is equal to cos alpha , the diffuse lighting component can be described with the dot product of N and L. This diffuse lighting component is usually added to the ambient lighting component like this:

I = Aintensity * Acolor + Dintensity * Dcolor * N.L + Specular

The example uses the following simplified version:

I = A + D * N.L + Specular

The following source code shows the HLSL vertex and pixel shaders:


float4x4 matWorldViewProj;
float4x4 matWorld;
float4 vecLightDir;

struct VS_OUTPUT
{
    float4 Pos : POSITION;
    float3 Light : TEXCOORD0;
    float3 Norm : TEXCOORD1;
};

VS_OUTPUT VS(float4 Pos : POSITION, float3 Normal : NORMAL)
{
    VS_OUTPUT Out = (VS_OUTPUT)0;
    Out.Pos = mul(Pos, matWorldViewProj); // transform Position
    Out.Light = vecLightDir; // output light vector
    Out.Norm = normalize(mul(Normal, matWorld)); // transform       Normal and normalize it
    return Out;
}

float4 PS(float3 Light: TEXCOORD0, float3 Norm : TEXCOORD1) : COLOR
{
    float4 diffuse = { 1.0f, 0.0f, 0.0f, 1.0f};
    float4 ambient = {0.1, 0.0, 0.0, 1.0};
    return ambient + diffuse * saturate(dot(Light, Norm));
}

Compared to the previous example, the vertex shader gets additionally a vertex normal as input data. The semantic NORMAL shows the compiler how to bind the data to the vertex shader registers. The world-view-projection matrix, the world matrix and the light vector are provided to the vertex shader via the constants matWorldViewProj, matWorld, vecLightDir. All these constants are provided by the application to the vertex shader.

The vertex shader VS outputs additionally the N and L vectors in the variables Light and Norm here. Both vectors are normalized in the vertex shader with the intrinsic function normalize(). This function returns the normalized vector v = v/length(v). If the length of v is 0, the result is indefinite.

The normal vector is transformed by beeing multiplied with the world matrix (read more on transformation of normals in [RTR2]). This is done with the function mul(a, b), which performs a matrix multiplication between a and b. If a is a vector, it is treated as a row vector. If b is a vector, it is treated as a column vector. The inner dimensions acolumns and brows must be equal. The result has the dimension arows * bcolumns. In this example mul() gets the position vector as the first parameter --therefore it is treated as a row vector -- and the transformation matrix, consisting of 16 floating-point values (float4x4), as the second parameter. The following figure shows the row vector and the matrix:


Figure 6. Column-major matrix multiply.

The whole lighting formula consisting of an ambient and a diffuse component is implemented in the return statement. The diffuse and ambient constant values were defined in the pixel shader to make the source code easier to understand. In a real-world application these values might be loaded from the 3D model file.

Specular Lighting

Whereas diffuse lighting considers the location of the light vector and ambient lighting does not consider the location of the light or the viewer at all, specular lighting considers the location of the viewer. Specular lighting is used to simulate smooth, shiny and/or polished surfaces.


Figure 7. Phong lighting

In the specular lighting model developed by Bui Tong Phong [Foley], two vectors are used to calculate the specular component: the viewer vector V that describes the direction of the viewer (in other words the camera), and the reflection vector R that describes the direction of the reflections from the light vector.


Figure 8. Vectors for specular lighting.

The angle between V and R is ß. The more V is aligned with R, the brighter the specular light should be. Therefore cos ß can be used to describe the specular reflection. Additionally to characterize shiny properties an exponentn is used.


Figure 9. Specular power value.

Therefore the specular reflection can be described with

cos (ß)n

In an implementation of a specular lighting model, a property of the dot product can be used as follows:

R.V = ||R|| * ||V|| * cos ß

If both vectors are of unit length, R.V can replace cos ß. Therefore the specular reflection can be described with

(R.V)n

It is quite common to calculate the reflection vector with the following formula (read more in [Foley2]):

R = 2 * (N.L) * N - L

The whole Phong specular lighting formula now looks like this:

I = Aintensity * Acolor + Dintensity * Dcolor * N.L + Sintensity * Scolor * (R.V)n

Baking some values together and using white as the specular color leads to (find a more advanced implementation of specular lighting together with cube map shadow mapping at [Persson] [Valient]):

I = A + D * N.L + (R.V)n

The following source shows the implementation of the Phong specular lighting model:


float4x4 matWorldViewProj;
float4x4 matWorld;
float4 vecLightDir;
float4 vecEye;

struct VS_OUTPUT
{
    float4 Pos : POSITION;
    float3 Light : TEXCOORD0;
    float3 Norm : TEXCOORD1;
    float3 View : TEXCOORD2;
};

VS_OUTPUT VS(float4 Pos : POSITION, float3 Normal : NORMAL)
{
    VS_OUTPUT Out = (VS_OUTPUT)0;
    Out.Pos = mul(Pos, matWorldViewProj); // transform Position
    Out.Light = vecLightDir;              // L
    float3 PosWorld = normalize(mul(Pos, matWorld));
    Out.View = vecEye - PosWorld;         // V
    Out.Norm = mul(Normal, matWorld);     // N

return Out;
}

float4 PS(float3 Light: TEXCOORD0, float3 Norm : TEXCOORD1,
        float3 View : TEXCOORD2) : COLOR
{
    float4 diffuse = { 1.0f, 0.0f, 0.0f, 1.0f};
    float4 ambient = { 0.1f, 0.0f, 0.0f, 1.0f};

    float3 Normal = normalize(Norm);
    float3 LightDir = normalize(Light);
    float3 ViewDir = normalize(View);
    float4 diff = saturate(dot(Normal, LightDir)); // diffuse component

    // R = 2 * (N.L) * N - L
    float3 Reflect = normalize(2 * diff * Normal - LightDir);
    float4 specular = pow(saturate(dot(Reflect, ViewDir)), 8); // R.V^n

    // I = Acolor + Dcolor * N.L + (R.V)n
    return ambient + diffuse * diff + specular;
}

Like the previous example, the vertex shader input values are the position values and a normal vector. Additionally, the vertex shader gets as constants the matWorldViewProj matrix, the matWorld matrix, the position of the eye in vecEye and the light vector in vecLightDir from the application.

In the vertex shader, the viewer vector V is calculated by subtracting the vertex position in world space from the eye position. The vertex shader outputs the position, the light, normal and viewer vector. The vectors are also the input values of the pixel shader.

All three vectors are normalized with normalize() in the pixel shader, whereas in the previous example vectors were normalized in the vertex shader. Normalizing vectors in the pixel shader is quite expensive, because every pixel shader version has a restricted number of assembly instruction slots available for use by the output of the HLSL compiler. If more instructions slots are used than available, the compiler will display an error message like the following:

error X4532: cannot map expression to pixel shader instruction set

The number of instruction slots available in a specific Direct3D pixel shader version usually corresponds to the number of instruction slots available in graphics cards. The high-level language compiler can not choose a suitable shader version on its own, this has to be done by the programmer. If your game will targets something other than the least common denominator of graphics cards on the target market, several shader versions must be provided.

Here is a list of all vertex and pixel shader versions supported by DirectX 9.

Version  Inst. Slots Constant Count
====== ======== ===========
vs_1_1 128 at least 96 cap'd (4)
vs_2_0 256 cap'd (4)
vs_2_x 256 cap'd (4)
vs_2_sw unlimited 8192
vs_3_0 cap'd (1) cap'd (4)
ps_1_1 - ps_1_3 12 8
ps_1_4 28 (in two phases) 8
ps_2_0 96 32
ps_2_x cap'd (2) 32
ps_3_0 cap'd (3) 224
ps_3_sw unlimited 8192

(1) D3DCAPS9.MaxVertexShader30InstructionSlots
(2) D3DCAPS9.D3DPSHADERCAPS2_0.NumInstructionSlots
(3) D3DCAPS9.MaxPixelShader30InstructionSlots
(4) D3DCAPS9.MaxVertexShaderConst

The capability bits above have to be checked to obtain the maximum number of instructions supported by a specific card. This can be done in the DirectX Caps viewer or via the D3DCAPS9 structure in the application. Checking the availability of specific shader versions is usually done while the application starts up. The application can then choose the proper version from a list of already prepared shaders or the application can put together a shader consisting of already prepared shader fragments with the help of the shader fragment linker.

Note that the examples shown here require mostly pixel shader version 2.0 or higher. With some tweaking, some of the effects might be implemented with a lower shader version by trading off some image quality, but that's out of the scope of this article.

The pixel shader example above calculates the diffuse reflection in the source code line below the lines that are used to normalize the input vectors. The function saturate() is used here to clamp all values to the range 0..1. The reflection vector is retrieved by re-using the result from the diffuse reflection calculation. To get the specular power value, the function pow() is used. It is declared as pow(x, y) and returns xy. This function is only available in pixel shaders >= ps_2_0. Getting a smooth specular power value in pixel shader versions lower than ps_2_0 is quite a challenge (read more at [Beaudoin/Guardado], [Halpin]).

The last line that starts with the return statement corresponds to the implementation of the lighting formula shown above.

______________________________________________________


join | contact us | advertise | write | my profile
news | features | companies | jobs | resumes | education | product guide | projects | store



Copyright © 2003 CMP Media LLC

privacy policy
| terms of service