|
Features

Implementing Lighting Models With HLSL
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
"gamasutra-hlsl-lighting-examples.zip"
(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.
- 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
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;
};
VS_OUTPUT
VS( float4 Pos: POSITION )
{
VS_OUTPUT Out = (VS_OUTPUT) 0;
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:
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.
______________________________________________________
|