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:
Women In Games Conference
Coventry, United Kingdom
09.10.08

3rd ACM International Conference on Digital Interactive Media in Entertainment and Arts - DIMEA 2008
Athens, Greece
09.10.08

GDC Austin
Austin, United States
09.15.08

Game Career Seminar
Austin, United States
09.17.08

Games Convention Asia 2008
, Singapore
09.18.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

Self-Shadowing Term

It's common to use a self-shadowing term together with a specular lighting model to receive a geometric self-shadowing effect. Such a term sets the light brightness to zero, if the light vector is obscured by geometry and allows a linear scaling of the light brightness. This helps to reduce pixel popping when using bump maps. The screenshot in Figure 10 shows an example of a self-shadowing term on the right and an example that does not use a self-shadowing term on the left.


Figure 10. Self-shadowing term. (Image on right is very faint.)

There are several ways to calculate this term. The following example uses (read more at [Frazier]):

S = saturate(4 * N.L)

This implementation just re-uses the N.L calculation to calculate the self-shadowing term. This leads to the following lighting formula:

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

The self-shadowing term reduces the diffuse and specular component of the lighting model to null, in case the diffuse component is null. In other words: the diffuse component is used to diminish the intensity of the specular component. This is shown in the following pixel shader:


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.1, 0.0, 0.0, 1.0};

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

  // compute self-shadowing term
  float shadow = saturate(4* diff);

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

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

Bump Mapping

As you are probably aware, bump mapping fakes the existance of geometry (grooves, nodges, bulges). The screenshot in Figure 11 shows an example of a program that gives the viewer the illusion that regions with mountains are higher than the water regions on earth.


Figure 11. Bump mapping.

Whereas a color map stores color values, a bump map is a graphics file (*.dds, *.tga etc.) that stores normals that are used instead of the vertex normals to calculate the lighting. These normals are stored in the most common bump mapping technique in what is called texture space or tangent space. The light vector is usually handled in object or world space, which leads to the problem, that the light vector has to be transformed into the same space as the normals in the bump map, to obtain proper results. This is done with the help of a texture or tangent space coordinate system.


Figure 12. Texture space coordinate system.

The easiest way to obtain such a texture space coordinate system is to use the D3DXComputeNormal() and D3DXComputeTangent() functions provided with the Direct3D utility library Direct3DX. Source code implementations of the functionality covered by these functions can be found in the NVMeshMender library, that can be downloaded from the Nvidia web site (developer.nvidia.com). Calculating a texture space coordinate system can work similar to the calculation of the vertex normal. For example, if a vertex shares three triangles, the face normal/face tangent of each triangle is calculated first, then these face normals of all three triangles are added together at the vertex that connects these triangles to form the vertex normal/vertex tangent. This example uses the vertex normal instead of a W vector for the texture space coordinate system and calculates the U vector with the help of the D3DXComputeTangent() function. The V vector is retrieved by calculating the cross product of the W and the U vector. This is done with the cross() function in the vertex shader:


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

VS_OUTPUT VS(float4 Pos : POSITION, float2 Tex : TEXCOORD, float3 Normal : NORMAL, float3 Tangent : TANGENT )
{
  VS_OUTPUT Out = (VS_OUTPUT)0;
  Out.Pos = mul(Pos, matWorldViewProj); // transform Position

  // compute the 3x3 tranform matrix
  // to transform from world space to tangent space
  float3x3 worldToTangentSpace;
  worldToTangentSpace[0] = mul(Tangent, matWorld);
  worldToTangentSpace[1] = mul(cross(Tangent, Normal), matWorld);
  worldToTangentSpace[2] = mul(Normal, matWorld);

  Out.Tex = Tex.xy;

  Out.Light.xyz = mul(worldToTangentSpace, vecLightDir); // L
  float3 PosWorld = normalize(mul(Pos, matWorld));
  float3 Viewer = vecEye - PosWorld; // V
  Out.View = mul(worldToTangentSpace, Viewer);

  return Out;
}

float4 PS(float2 Tex: TEXCOORD0, float3 Light : TEXCOORD1,
float3 View : TEXCOORD2) : COLOR
{
  float4 color = tex2D(ColorMapSampler, Tex); // fetch color map
  float3 bumpNormal = 2 * (tex2D(BumpMapSampler, Tex) - 0.5); // bump map

  float3 LightDir = normalize(Light); // L
  float3 ViewDir = normalize(View); // V

  float4 diff = saturate(dot(bumpNormal, LightDir)); // diffuse comp.

  float shadow = saturate(4 * diff); // compute self-shadowing term

  float3 Reflect = normalize(2 * diff * bumpNormal - LightDir); // R

  // gloss map in color.w restricts spec reflection
  float4 spec = min(pow(saturate(dot(Reflect, ViewDir)), 3), color.w);

  return 0.2 * color + shadow * (color * diff + spec);
}

The 3x3 matrix, consisting of the U, V and W (== N) vectors, is created in the vertex shader and is used there to transform L and V to texture space.

Compared to the previous example, the major differences in the pixel shader are the usage of a color map and a bump map, that are fetched with tex2D(). The function tex2D() is declared as tex2D(s, t), whereas s is a sampler object and t is a 2D texture coordinate. Please consult the DirectX 9 documentation for a lot of other texture sampler functions, like texCUBE(s, t), which fetches a cube map.

The normal from the bump map is used instead of the normal from the vertex throughout the whole pixel shader. It is fetched from the normal map by biasing (- 0.5) and scaling (* 2.0) its values. This has to be done, because the normal map was stored in a unsigned texture format with a value range of 0..1 to allow older hardware to operate correctly. Therefore the normals have to be expanded back to their signed range.

Compared to previous examples, this pixel shader restricts the region where a specular reflection might happen to the water regions of the shown earth model. This is done with the help of the min() function and a gloss map that is stored in the alpha values of the color map. min() is defined as min(a, b) and it selects the lesser of a and b.

In the return statement, the ambient term is replaced by an intensity-decreased color value from the color map. This way, if the self-shadowing term reduces the diffuse and specular lighting component, at least a very dark Earth is still visible.

Point Light

The last example adds a per-pixel point light to the previous example. Contrary to the parallel light beams of a directional light source, the light beams of a point light spread out from the position of the point light uniformerly in all directions.


Figure 13. Point light.


Using a point light with an attenuation map is extensively discussed in articles by Sim Dietrich [Dietrich2], by Dan Ginsburg/Dave Gosselin [Ginsburg/Gosselin], by Kelly Dempski [Dempski] and others.

Sim Dietrich shows in his article, that the following function delivers good enough results:

attenuation = 1 - d * d // d = distance

which stands for

attenuation = 1 - (x * x + y * y + z * z)

Dan Ginsburg/Dave Gosselin and Kelly Dempski divide the squared distance through a constant, which stands for the range of distance, in which the point light attenuation effect should happen:

attenuation = 1 - ((x/r)2 + (y/r)2 + (z/r)2)

The x/r, y/r and z/r values to the pixel shader via a TEXCOORDn channel and multiply them there with the help of the mul() function. The relevant source code to do this is:


VS_OUTPUT VS(float4 Pos : POSITION, float2 Tex : TEXCOORD,
float3 Normal : NORMAL, float3 Tangent : TANGENT )
{
...
  float LightRange = 1.0;

  // point light
  Out.Att = Light * LightRange;
...
}

float4 PS(float2 Tex: TEXCOORD0, float3 Light : TEXCOORD1,
float3 View : TEXCOORD2, float3 Att : TEXCOORD3) : COLOR
{
...
  // attenuation
  float4 Attenuation = mul(Att, Att);

  // colormap * (self-shadow-term * diffuse + ambient) + self-
    shadow-term   * // specular
  return (color * (shadow * diff + ambient) + shadow * spec) * (1     -Attenuation);
}

Decreasing the LightRange parameter increases the range of light, whereas increasing this value leads to a shorter light range. The attenuation value is multiplied with itself in the pixel shader because this is more efficient than using the exponent 2. In the last line of the pixel shader, the attenuation value is multiplied with the result of the lighting computation to decrease or increase light intensity depending on the distance of the light source.

Wrapping Up

This article had covered the syntax and some of the intrinsic functions of HLSL by showing how to implement some common lighting formulas. It introduced you to six working (and generally concise) code examples, getting you ready to generate your own shaders. To move on from here, I recommend lurking into the RenderMonkey examples on ATI's web site and playing around with the source code found there. An interactive RenderMonkey tutorial can be found on my website.

Furthermore, two books on shader programming (in the "ShaderX2" series) will be published by Wordware in August 2003. These books contain articles about shader programming by over 50 authors, and cover many aspects of shader programming (more info can be found at www.shaderx2.com). In a few weeks, Ron Fosner will publish an article on HLSL programming on Gamasutra that covers the use of RenderMonkey and Oren-Nayar Diffuse lighting. If you have any comments regarding this article, I appreciate any feedback -- send it to me at wolf@shaderx.com.

Acknowledgements

I have to thank the following people for proof-reading this text and sending me comments (in alphabetical order):

· Wessam Bahnassi
· Stefano Cristiano
· Ron Fosner
· Muhammad Haggag
· Dale LaForce
· William Liebenberg
· Vincent Prat
· Guillermo Rodríguez
· Mark Wang

This text is an excerpt from the following upcoming book:

Wolfgang F. Engel, Beginning Direct3D Game Programming with DirectX 9 Featuring Vertex and Pixel Shaders, May 2003, ISBN 1-93184-139-X

References

[Beaudoin/Guardado] Philippe Beaudoin, Juan Guardado, A Non-Integer Power Function on the Pixel Shader, http://www.gamasutra.com/features/20020801/beaudoin_01.htm (This feature is an excerpt from Direct3D ShaderX: Vertex and Pixel Shader Tips and Tricks, edited by Wolfgang Engel)

[Dempski] Kelly Dempski, Real-Time Rendering Tricks and Techniques in DirectX, Premier Press, Inc., pp 578 - 585, 2002, ISBN 1-931841-27-6

[Dietrich] Sim Dietrich, "Per-Pixel Lighting", Nvidia developer web-site.

[Dietrich2] Sim Dietrich, "Attenuation Maps", Game Programming Gems, Charles River Media Inc., pp 543 - 548, 2000, ISBN 1-58450-049-2

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

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

[Fosner] Ron Fosner, Real-Time Shader Programming, Morgan Kauffmann, pp 54 - 58, 2003, ISBN 1 - 55860-853-2

[Frazier] Ronald Frazier, "Advanced Real-Time Per-Pixel Lighting in OpenGL", http://www.ronfrazier.net/apparition/index.asp?appmain=research/ advanced_per_pixel_lighting.html

[Ginsburg/Gosselin] Dan Ginsburg/Dave Gosselin, "Dynamic Per-Pixel Lighting Techniques", Game Programming Gems 2, Charles River Media Inc., pp 452 - 462, 2001, ISBN 1-58450-054-9

[Halpin] Matthew Halpin, "Specular Bump mapping", ShaderX2 - Shader Tips & Tricks, Wordware Ltd. August 2003, ISBN ??

[Mitchell/Peeper] Jason L. Mitchell, Craig Peeper, "Introduction to the DirectX 9 High Level Shading Language", ShaderX2 - Shader Programming Introduction & Tutorials, Wordware Ltd., persumably August 2003, ISBN ??

[RTR] Thomas Möller, Eric Haines, Real-Time Rendering (Second Edition), A K Peters, Ltd., p 73, 2002, ISBN 1-56881-182-9.

[RTR2] Thomas Möller, Eric Haines, Real-Time Rendering (Second Edition), A K Peters, Ltd., pp 35 - 36, 2002, ISBN 1-56881-182-9.

[Persson] Emil Persson, "Fragment level Phong Illumination", ShaderX2 - Shader Tips & Tricks, Wordware Ltd. Autumn 2003,

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

[Valient] Michal Valient, "Advanced Lighting and Shading with DirectX 9", ShaderX2 - Shader Programming Introduction & Tutorials, Wordware Ltd., August 2003.

 

______________________________________________________

[back to] Introduction


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