Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.

Gamasutra: The Art & Business of Making Gamesspacer
Implementing Lighting Models With HLSL
arrowPress Releases
May 26, 2020
Games Press
View All     RSS

If you enjoy reading this site, you might also want to check out these UBM Tech sites:


Implementing Lighting Models With HLSL

April 18, 2003 Article Start Page 1 of 3 Next

With the introduction of the second generation of shader-capable hardware like the ATI Radeon 9500 (and more recent boards) and the Nvidia GeForce FX, several high-level languages for shader programming were released to the public. Prior to this hardware support, shader programming was performed exclusively with assembly-like syntax, that was difficult to read for a non-assembly programmer. The new C-like languages make learning shader programming faster and make the shader code easier to read.

The new shader languages -- Cg, HLSL and GLslang -- have similar syntax and provide similar functionality. Continuing Gamasutra's series on shader programming (which began with "Animation Using Cg" by Mark Kilgard and Randy Fernando), I will look at using the High Level Shader Language (HLSL) which comes with DirectX 9. However, the examples I provide should run without any changes in a Cg environment and with only minor changes in a GLslang environment. For further rapid prototyping of these shaders, ATI's RenderMonkey is highly recommended. The code and executables for all of the examples presented in this article can be downloaded in a single file called "" (17MB).

Getting a Jump Start with HLSL

The only requirement to start HLSL programming with the DirectX SDK is that the HLSL compiler is installed properly. This is done by setting the right directory path in the Tools>Option>Project>VC++ Directories of the Visual .NET IDE. Additionally the shader debugger provided for Visual .NET (not Visual C/C++ 6) should be installed from the SDK CD-ROM. This installation is optional and has to be explicitely chosen therefore. To be able to use the Visual Studio .NET shader debugger, the following steps are necessary:

  • Install the shader debugger. This is an option in the DirectX 9 SDK installation routine, which must be selected.
  • Select the "Use Debug Version of Direct3D" and "Enable Shader Debugger" check boxes in the configuration panel of the DirectX run-time.

    Figure 1. The Direct3D properties box. This checkbox can be found at Start>Control Panel>DirectX and there on the Direct3D tab.

  • Launch the application under Visual Studio .NET with Debug>Direct3D>Start D3D
  • Switch to the reference rasterizer
  • Set a breakpoint in Vertex Shader or Pixel Shader
  • Shader assembly can be viewed under Debug>Window>Disassembly
  • Render targets can be viewed under Debug>Direct3D>RenderTarget

Tasks of the Vertex and Pixel Shader

To estimate the capabilities of vertex and pixel shaders, it is helpful to look at their position in the Direct3D pipeline

Figure 2. The Direct3D pipeline.

The vertex shader, as the functional replacement of the legacy transform and lighting stage (T&L), is located after the tesselation stage and before the culling and clipping stages. The data for one vertex is provided by the tesselation stage to the vertex shader. This vertex might consist of data regarding its position, texture coordinates, vertex colors, normals and so on.

The vertex shader can not create or remove a vertex. Its minimal output value is the position data of one vertex. It can not affect subsequent stages of the Direct3D pipeline.

The pixel shader, as the functional replacement of the legacy multitexturing stage, is located after the rasterizer stage. It gets its data from the rasterizer, from the vertex shader, from the application and from texture maps. It is important to note here that texture maps can not only store color data, but all kinds of numbers that are useful to calculate algorithms in the pixel shader. Furthermore, the result of the pixel shader can be written into a render target and set in a second rendering pass as the input texture. This is called "render to render rarget". The output of the pixel shader consists usually of a float4 value. In a case where the pixel shader renders in several render targets at once, the output might be up to four float4 values (Multiple Render Targets; MRT).

With the help of the pixel shader, subsequent stages of the Direct3D pipeline can be "influenced" by choosing for example a specific alpha value and configuring the alpha test or alpha blending in a specific way.

Common Lighting Formulas Implemented with HLSL

To demonstrate the use of HLSL to create shaders, and to demonstrate the tasks of the vertex and pixel shaders, the following examples show the implementation of the common lighting formulas (for a more extensive introduction read [Mitchell/Peeper]).

Ambient Lighting

In an ambient lighting model, all light beams fall uniformly from all directions onto an object. A lighting model like this is also called a "global lighting model" (an example of a more advanced global lighting model is called "area lighting"). The ambient lighting component is usually described with the following formula:

I = Aintensity * Acolor

The intesity value describes the lighting intensity and the color value describes the color of the light. A more complex lighting formula with its ambient terms might look like this:

I = Aintensity * Acolor + Diffuse + Specular

The diffuse and specular component are placeholders here for the diffuse and specular lighting formulas that will be described in the upcoming examples. All high-level languages support shader instructions that are written similar to mathematical expressions like multiplication, addition and so on. Therefore the ambient component can be written in a high-level language as follows:

float4 Acolor = {1.0, 0.15, 0.15, 1.0};
float Aintensity = 0.5;
return Aintensity * Acolor;

A simplified implementation might look like this:

return float4 (0.5, 0.075, 0.075, 1.0);

It's common to pre-bake the intensity values and the color values into one value. This is done in the following example programs for simplicity and readability, otherwise the HLSL compiler would pre-bake the two constant values together in its output. The following source code shows a vertex and a pixel shader that displays ambient lighting:

float4x4 matWorldViewProj;

struct VS_OUTPUT
float4 Pos: POSITION;

Out.Pos = mul(Pos, matWorldViewProj); // transform Position
return Out;

float4 PS() : COLOR
return float4(0.5, 0.075, 0.075, 1.0);


The structure VS_OUTPUT at the beginning of the source describes the output values of the vertex shader. The vertex shader looks like a C function with the return value VS_OUTPUT and the input value in the variable Pos in brackets after its name VS.

The input and output values for the vertex shader use the semantic POSITION, which is identified by a colon (:) that precedes it. Semantics help the HLSL compiler to bind the right shader registers for the data. The semantic in the vertex shader input structure identifies the input data to the function as position data. The second semantic indentifies the vertex shader return value as position data that will be an input value to the pixel shader (this is the only obligatory output value of the vertex shader). There are several other semantics for the vertex shader input data, vertex shader output data, pixel shader input data and pixel shader output data. The following examples will show a few of them (consult the DirectX 9 documentation for more information).

Inside the brackets, the vertex shader transforms the vertex position with the matWorldViewProj matrix provided by the application as a constant and outputs the position values.

The pixel shader follows the same C-function-like approach as the vertex shader. Its return value is always float4 and this value is always treated as a color value by the compiler, because it is marked with the semantic COLOR. Unlike to the vertex shader, the pixel shader takes no explicit input value here (except the obligatory POSITION), because the brackets after its name PS are empty.

The names of the vertex and pixel shader are also the entry point for the high-level language compiler and must be provided in its command line.

The following screenshot shows the example program with an ambient lighting model:

Figure 3. Ambient lighting.

The knob of the top of the teapot is not visible, when it is turned towards the viewer. This is because all light beams are coming from all directions uniformly onto the object, and therefore the whole teapot gets exactly the same color.

Article Start Page 1 of 3 Next

Related Jobs

Moon Studios
Moon Studios — Remote, California, United States

Senior Character TD
innogames — Hamburg, Germany

(Senior) Java Developer
Stray Bombay Company
Stray Bombay Company — Seattle, Washington, United States

Senior Unreal Engineer
Square Enix Co., Ltd.
Square Enix Co., Ltd. — Tokyo, Japan

Experienced Game Developer

Loading Comments

loader image