Home Articles Tutorials Resources About

Diffuse Light Vertex Shader


By Pieter Germishuys, January 9 2006

Lights will be the subject at hand today. Lights can become very complicated. For the purpose of this tutorial we will be ignoring components like specularity and attenuation.

Fundamental knowledge of lights.

What is light?
"a form of energy called radiant energy that travels freely through space"
In simple terms, light is the absence of darkness.
Look at how light works, it's all around us (unless you are in a dark room without windows and no lighting). Look at how light attenuates (decreases) as it goes further away from the source.

The 3 light types
In graphics programming we simulate light. Direct3D has differentiated 3 light types known as point lights which resembles a light bulb which gives off light in all directions.a Spot light which resembles a flashlight. Then finally a directional light which you can think of as the sun which has a near infinite range and travels through a scene in the same direction. For more information on these lights refer to documentation.

Since we aren't using the fixed function pipeline our Device.Lights[] structures are ignored and we will have to do the calculations ourselves. This moves us to the next section. What do we need to calculate the vertex color?

What do we need to calculate lighting per vertex?
1) We will need a light position, a lighting equation
2) We need and object with normals. If you do not know what normals are. Please refer to tutorial 5 on my site for an explanation


As you can see, In figure 1 we have an object with some normals as example. For our example we are going to use a cube that was made in a modelling program such as 3dstudio max and converted to the DirectX .x file format for ease of use. The next figure shows a light that has been defined in a position. We do not worry about distance etc.. we are just concerned about the position for now since we are going to be doing simple point lights in this tutorial.

Note one important point. The 2 figures are in different spaces. Meaning that the object is defined in object space and so is it's normals. The light is defined in world space. To be able to calculate our light intensity we will have to bring the object's vertices and it's normals to world space. This done by transforming them with the world transformation matrix which will be covered in the shader.

The lighting equation
Light intensity = N?L
N : Normal of the vertex in this case
L : The Light vector
With diffuse lighting we calculate the amount of light a vertex recieves. This is based on the angle (theta). To bigger the angle the less light it receives and the smaller the angle is the more light it receives. Figure 3.0 shows the angle and how the light vector will hit a surface.

Getting the vertex normal and vertex position to world space
This is where the maths will come into play. Remember the space that we spoke about earlier in the series of tutorials?

To go from each of the spaces down the ladder you would multiply it with the previous set of transformation matrices.
Example. Object space to World space you will transform the vertices in object space by a world transformation matrix. From Object space to View space you will use a World * View matrix.


A simple diffuse light shader.

float4x4 worldViewProj : WORLDVIEWPROJ; //our world view projection matrix
float4x4 world :WORLD; //world matrix
float3 lightPos;

//application to vertex structure
struct a2v
{
????float4 position : POSITION0;
????float3 normal : NORMAL0;
????float2 texCoord : TEXCOORD0;
};

//vertex to pixel shader structure
struct v2p
{
????float4 position : POSITION0;
????float2 texCoord : TEXCOORD0;
????float4 color : COLOR0;
};

//VERTEX SHADER
void vs( in a2v IN, out v2p OUT)
{
????OUT.position = mul(IN.position, worldViewProj); //getting to position to object space
????float3 posWorld = mul(IN.position, world).xyz; //get the vertex to world space
????float3 normal = mul(IN.normal, world).xyz; //get the normal to world space
????float3 light = normalize(lightPos - posWorld); //calculating the light direction
????float lightIntensity = clamp(dot(normal, light), 0, 1); //calculate the light intensity and clamp it to a range
????float3 lightColor = float3(1.0f, 1.0f, 1.0f); //set the light color
????float4 color = float4(lightColor * lightIntensity, 1.0f); //calculate the color of the vertex
????color.rgb += 0.5f; //add an ambient color to the shader
????OUT.color = clamp(color, 0, 1); //set the color to the output color
????OUT.texCoord = IN.texCoord;
}

technique simple
{
????pass p0
????{
????????vertexshader = compile vs_1_1 vs();
????}
}

Running through the shader.
From our application we have set global variables such as the WorldViewProjection matrix to transform the vertices from object space to screen space.
The world transformation matrix to get a position of our vertices and normals to world space for the light calculations.
We add a Normal variable with a semantic to get the normal from the object from the application.
In our vs() method in our shader we are transforming the vertex from object space to screen space. posWorld gets the position of the vertex in world space.
Note: The swizzle operator that we use there to get the 3 components from the results. example:
float3 posWorld = mul(IN.position, world).xyz and color.rgb

Swizzle
"Swizzling refers to the ability to copy any source register component to any temporary register component. Swizzling does not affect the source register data. Before an instruction runs, the data in a source register is copied to a temporary register." For more information on swizzling refer to part of the documentation.

Continue looking at the shader
We then transform the normal from object space to world space too. In addition to that we get the light vector by subtracting the light position from the position in the world of the vertex. If you are unfamiliar with vector math. This is a great introduction to vector math. In addition we also normalize the light vector. We are only interested with the direction of the vector. Direction is a vector of unit length one. example: Light vector going left in a left handed coordinate system. (-1.0f, 0.0f, 0.0f)
As mentioned before. The dot product is then calculated between the normal and the light vector. For a good explanation on what dot product is refer to this article.

Note the keyword clamp there. We clamp the value that is returned after the dot product. The reason for this is that the value returned might be negative and we want a value between 1 and 0. So we clamp the returned value between that range. So this is how we calculate the light intensity.

Next up is the light color. The light color is a simple vector describing a white light. We then multiply the lightIntensity with the lightColor to get the overall illumination of the object. We also add an ambient value to the final color so that the object is visible even without light reflecting off of it.
Note: We clamp the final color since the color needs to be a unit vector. This means that the color as we know it (255, 255, 255) will be (1.0f, 1.0f, 1.0f) for white in both cases. So we clamp the color in the range 0, 1.

Just as a last side note, if the worldviewprojection transformation matrix has a scaling value the normals that are sent would not be normalized and the lighting equations will go all wrong. In DirectX there is a renderstate, NormalizeNormals that fixes this. If this is the problem you will have to normalize your normals in the shader.


This brings us to the end of another exciting tutorial. Next up we will start looking at per pixel processing in shaders.




Files for this tutorial

Filename Size
? hlsl4_vs2003.rar 176.5 KB
? hlsl4_vs2005.rar 202.3 KB
?
MDX info is an initiative by NetForge. All content is copyright ? 2005-2006 by its respective authors | About MDX info | Terms of Use |