Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
64 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

RacorX4

RacorX4 has gotten a few additional features compared to RacorX3. First of all this example will not use a plain quad anymore, it uses instead a Bézier patch class, that shows a round surface with a texture attached to it. To simulate light reflections, a combined diffuse and specular reflection model is used.


Figure 13 - RacorX4

RacorX4 uses the trackball class provided with the Common files framework, to rotate and move the object. You can choose different specular colors with the <C> key and zoom in and out with the mouse wheel.

Vertex Shader Declaration

Compared to RacorX3, additionally the texture coordinates will be mapped to input register v7.

// shader decl
DWORD dwDecl[] =
{
  D3DVSD_STREAM(0),
  D3DVSD_REG(0, D3DVSDT_FLOAT3 ), // input register v0
  D3DVSD_REG(3, D3DVSDT_FLOAT3 ), // normal in input register v3
  D3DVSD_REG(7, D3DVSDT_FLOAT2), // tex coordinates
  D3DVSD_END()
};

The corresponding layout of the vertex buffer in BPatch.h looks like:

struct VERTICES {
  D3DXVECTOR3 vPosition;
  D3DXVECTOR3 vNormal;
  D3DXVECTOR2 uv;
};

// Declare custom FVF macro.
#define D3DFVF_VERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1)

The third flag used in the custom FVF macro indicates the usage of one texture coordinate pair. This macro is provided to CreateVertexBuffer(). The vertex shader gets the position values from the vertex buffer via v0, the normal values via v3 and the two texture coordinates via v7.

Setting the Vertex Shader Constants

The vertex shader constants are set in FrameMove() and RestoreDeviceObjects(). The file const.h holds the following defines:

#define CLIP_MATRIX 0
#define CLIP_MATRIX_1 1
#define CLIP_MATRIX_2 2
#define CLIP_MATRIX_3 3

#define INVERSE_WORLD_MATRIX 8
#define INVERSE_WORLD_MATRIX_1 9
#define INVERSE_WORLD_MATRIX_2 10

#define LIGHT_VECTOR 11
#define EYE_VECTOR 12
#define SPEC_POWER 13
#define SPEC_COLOR 14

In FrameMove() a clipping matrix, the inversed world matrix, an eye vector and a specular color are set:

// set the clip matrix
m_pd3dDevice->SetVertexShaderConstant(CLIP_MATRIX,
               &(m_matWorld * m_matView * m_matProj),4);

// set the world inverse matrix
D3DXMATRIX matWorldInverse;
D3DXMatrixInverse(&matWorldInverse, NULL, &m_matWorld);
m_pd3dDevice->SetVertexShaderConstant(INVERSE_WORLD_MATRIX, matWorldInverse,3);

// stuff for specular lighting
// set eye vector E
m_pd3dDevice->SetVertexShaderConstant(EYE_VECTOR, vEyePt,1);

// specular color
if(m_bKey['C'])
{
  m_bKey['C']=0;
  ++m_dwCurrentColor;
  if(m_dwCurrentColor >= 3)
    m_dwCurrentColor=0;
}
m_pd3dDevice->SetVertexShaderConstant(SPEC_COLOR, m_vLightColor[m_dwCurrentColor], 1);

Like in the previous example the concatenated world-, view- and projection matrix are set into c0 - c3, to get transposed in the vertex shader and the inverse 4x3 world matrix is send to vertex shader, to transform the normal.

The eye vector (EYE_VECTOR) that is used to build up the view matrix is stored in constant register c12. As shown below, this vector is helpful to build up the specular reflection model used in this upcoming examples.

The user can pick one of the following specular colors with the <C> key:

m_vLightColor[0]=D3DXVECTOR4(0.3f,0.1f,0.1f,1.0f);
m_vLightColor[1]=D3DXVECTOR4(0.1f,0.5f,0.1f,1.0f);
m_vLightColor[2]=D3DXVECTOR4(0.0f,0.1f,0.4f,1.0f);

In RestoreDeviceObjects() the specular power, the light vector and the diffuse color are set:

// specular power
m_pd3dDevice->SetVertexShaderConstant(SPEC_POWER, D3DXVECTOR4(0,10,25,50),1);

// light direction
D3DXVECTOR3 vLight(0,0,1);
m_pd3dDevice->SetVertexShaderConstant(LIGHT_VECTOR, vLight,1);

D3DXCOLOR matDiffuse(0.9f, 0.9f, 0.9f, 1.0f);
m_pd3dDevice->SetVertexShaderConstant(DIFFUSE_COLOR, &matDiffuse, 1);

As in the previous examples, the light is positioned at (0.0, 0.0, 1.0). There are four specular power values, from which one will be used in the vertex shader.

To optimize the usage of SetVertexShaderConstant(), the specular color could be set into the fourth component of the light vector, which only used three of its components.

The Vertex Shader

The vertex shader handles a combined diffuse and specular reflection model:

; diffuse and specular vertex lighting

#include "const.h"

vs.1.1
; transpose and transform to clip space
mul r0, v0.x, c[CLIP_MATRIX]
mad r0, v0.y, c[CLIP_MATRIX_1], r0
mad r0, v0.z, c[CLIP_MATRIX_2], r0
add oPos, c[CLIP_MATRIX_3], r0

; output texture coords
mov oT0, v7

; transform normal
dp3 r1.x, v3, c[INVERSE_WORLD_MATRIX]
dp3 r1.y, v3, c[INVERSE_WORLD_MATRIX_1]
dp3 r1.z, v3, c[INVERSE_WORLD_MATRIX_2]

; renormalize it
dp3 r1.w, r1, r1
rsq r1.w, r1.w
mul r1, r1, r1.w

; light vector L
; we need L towards the light, thus negate sign
mov r5, -c[LIGHT_VECTOR]

; N dot L
dp3 r0.x, r1, r5

; compute normalized half vector H = L + V
add r2, c[EYE_VECTOR], r5 ; L + V

; renormalize H
dp3 r2.w, r2, r2
rsq r2.w, r2.w
mul r2, r2, r2.w

; N dot H
dp3 r0.y, r1, r2

; compute specular and clamp values (lit)
; r0.x - N dot L
; r0.y - N dot H
; r0.w - specular power n
mov r0.w, c[SPEC_POWER].y ; n must be between -128.0 and 128.0
lit r4, r0

; diffuse color * diffuse intensity(r4.y)
mul oD0, c[DIFFUSE_COLOR], r4.y

; specular color * specular intensity(r4.z)
mul oD1, c[SPEC_COLOR], r4.z

Compared to the vertex shader in the previous example program, this shader maps the texture coordinates to texture stage 0 with the following instruction:

; output texture coords
mov oT0, v7

The corresponding texture stage states set for the shading of the pixels in the multitexturing unit will be shown below in the section named "Non-Shader specific code". The instructions that transform the normal and calculate the diffuse reflection were already discussed along with the previous example.

The real new functionality in this shader is the calculation of the specular reflection, which happens in the code lines starting with the add instruction.

Specular Reflection

Compared to the diffuse reflection model, the appearance of the reflection depends in the specular reflection model on the position of the viewer. When the direction of the viewing coincides, or nearly coincides, with the direction of specular reflection, a bright highlight is observed. This simulates the reflection of a light source by a smooth, shiny and polished surface.

To describe reflection from shiny surfaces an approximation is commonly used, which is called the Phong illumination model (not to be confused with Pong Shading), named after its creator Phong Bui Tong [Foley]. According to this model, a specular highlight is seen when the viewer is close to the direction of reflection. The intensity of light falls off sharply when the viewer moves away from the direction of the specular reflection.


Figure 14 - Vectors for Specular Reflection

A model describing this effect has to be aware of at least the location of the light source L, the location of the viewer V, and the orientation of the surface N. Additionally a vector R that describes the direction of the reflection might be useful. The half way vector H, that halfs the angle between L and V will be introduced below.

The original Phong formula approximates the falloff of the intensity. It looks like this:

kspecular cosn(ß)

where kspecular is a scalar coefficient showing the percentage of the incident light reflected. ß describes the angle between R and V. The exponent n characterizes the shiny properties of the surface and ranges from one to infinity. Objects that are matte require a small exponent, since they produce a large, dim, specular highlight with a gentle falloff. Shiny surfaces should have a sharp highlight that is modeled by a very large exponent, making the intensity falloff very steep.

Together with the diffuse reflection model shown above, the Phong illumination model can be expressed in the following way:

Ireflected = Idirected((N dot L) + kspecular cosn(ß))

cosn(ß) can be replaced by using the mechanism of the dot or scalar product of the unit vectors R and V:

Ireflected = Idirected((N dot L) + kspecular (R dot V)n)

This is the generally accepted phong reflection equation. As the angle between V and R decreases, the specularity will rise.

Because it is expensive to calculate the reflection vector R (mirror of light incidence around the surface normal), James F. Blinn [Blinn] introduced a way to do this using an imaginary vector H, which is defined as halfway between L and V. H is therefore:

H = (L + V) / 2

When H coincides with N, the direction of the reflection coincides with the viewing direction V and a specular hightlight is observed. So the original formula

(R dot V)n

is expanded to

(N dot ((L + V) / 2))n

or

(N dot H)n

The complete Blinn-Phong model formula looks like:

Ireflected = Idirected((N dot L) + kspecular (N dot H)n)

Now back to the relevant code piece that calculates the specular reflection:

; compute normalized half vector H = L + V
add r2, c[EYE_VECTOR], r5 ; L + V

; renormalize H
dp3 r2.w, r2, r2
rsq r2.w, r2.w
mul r2, r2, r2.w

; N dot H
dp3 r0.y, r1, r2

; compute specular and clamp values (lit)
; r0.x - N dot L
; r0.y - N dot H
; r0.w - specular power n
mov r0.w, c[SPEC_POWER].y ; n must be between -128.0 and 128.0
lit r4, r0

The add instruction adds the L and V vectors, but compared to the Blinn-Phong formula shown above, the divide through 2 is missing. This is compensated by altering the specular power value, which leads to good visual results. So this code snippet handles the following equation, which is very similar to the equation of the Blinn-Phong reflection formula:

(N dot (L+V))n

Although L + V is not equivalent to the half vector H, the term half vector H will be used throughout the upcoming examples. In the vertex shader source H is normalized in the following dp3, rsq and mul instructions.

The reduction of the original Blinn-Phong formula reduces the number of vertex shader instructions and there leads to a higher performance. This is a typical way of optimizing vertex shader code on a functional level [Taylor]. Although L + V is not equivalent to H, the term vector H is used throughout the example source.

One of the main instructions in this piece of code is the lit instruction. It handles a big amount of work in only one instruction. The following code fragment shows the operations performed by lit:

dest.x = 1;
dest.y = 0;
dest.z = 0;
dest.w = 1;

float power = src.w;
const float MAXPOWER = 127.9961f;
if (power < -MAXPOWER)
  power = -MAXPOWER; // Fits into 8.8 fixed point format
else if (power > MAXPOWER)
  power = -MAXPOWER; // Fits into 8.8 fixed point format

if (src.x > 0)
{
  dest.y = src.x;
  if (src.y > 0)
  {
    // Allowed approximation is EXP(power * LOG(src.y))
    dest.z = (float)(pow(src.y, power));
  }
}

lit returns the diffuse intensity in the y component of the destination register and the specular intensity in the z component of the destination register. If there is no diffuse light, there will be no specular light. So N dot L is only used to switch off the specular light in case there is no diffuse lighting.

A way to do specular lighting without the lit instruciton would be to raise the specular power with a few mul instructions like this:

mul r3, r3, r3 ; 2nd
mul r3, r3, r3 ; 4th
mul r3, r3, r3 ; 8th
mul r3, r3, r3 ; 16th

The last two instructions multiply the intensity with the diffuse and specular color components and move them into their respective output registers:

; diffuse color * diffuse intensity(r4.y)
mul oD0, c[DIFFUSE_COLOR], r4.y
; specular color * specular intensity(r4.z)
mul oD1, c[SPEC_COLOR], r4.z

Non-Shader specific Code

This example uses the multitexturing unit to shade pixels. The proper texture stage states are set in RestoreDeviceObjects():

// texture settings
m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE);
m_pd3dDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR);
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP);
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP);

This texture stage stages modulate the diffuse color with the texture color, disable texture stage 1 and switch on a bilinear filtering with the D3DTEXF_LINEAR flags for the D3DTSS_MINFILTER/D3DTSS_MAGFILTER texture stage states. The D3DTSS_MIPFILTER texture stage state indicates the texture filter to use between mipmap levels. By providing D3DTEXF_LINEAR, trilinear texture filtering, or mip map filtering is switched on. The clamp texture-addressing mode applies the texture only once on the polygon and then smears the color of the edge pixels of the texture. Furthermore it cut off all negative values to 0 by remaining the positive values unchanged.

We use a Bézier patch here to build up the surface used on source provided by ShaderX author Bart Sekura. A Bézier patch is the surface extension of the Bézier curve. Whereas a curve is a function of one variable and takes a sequence of control points, the patch is a function of two variables with an array of control points. A Bézier patch can be viewed as a continuous zet of Bézier curves. The Bézier patch is the most commonly used surface representation in the computer graphic (see [Skinner]). CD3DBPatch::Create() builds the Bézier patch in InitDeviceObjects() from nine control points and calculates the normals. CD3DBPatch::RestoreDeviceObjects() fills the vertex and index buffer with the data on which the m_pvVertices and m_pwIndices pointers refer.

Summarize

RacorX4 shows a specular reflection model, that is very similar to the Blinn-Phong reflection model. Additionally it shows the functionality behind the lit instruction and the basics of texture mapping with a vertex shader.





RacorX5

Contents
  RacorX
  RacorX2
  RacorX3
  RacorX4
  RacorX5

  Printable version
  Discuss this article

The Series
  Fundamentals of Vertex Shaders
  Programming Vertex Shaders
  Fundamentals of Pixel Shaders
  Programming Pixel Shaders
  Diffuse & Specular Lighting with Pixel Shaders