Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Introduction to 3D Game Programming with DirectX.9.0 - F. D. Luna

.pdf
Скачиваний:
240
Добавлен:
24.05.2014
Размер:
6.94 Mб
Скачать

294 Chapter 17

lighting algorithm that can be implemented in a vertex shader. We are no longer limited to Direct3D’s fixed lighting algorithm. Furthermore, the ability to manipulate a vertex’s position has a variety of applications as well, such as cloth simulation, point size manipulation for particle systems, and vertex blending/morphing. In addition, our vertex data structures are more flexible and can contain much more data in the programmable pipeline than they could in the fixed function pipeline.

Vertex shaders are still a relatively new feature, and many cards do not support them, especially the newer vertex shader versions that were released with DirectX 9. You can test the vertex shader version

that your graphics card supports by checking the VertexShader-

Version member of the D3DCAPS9 structure and the macro

 

 

 

 

Y

D3DVS_VERSION. The following code snippet illustrates this:

 

 

 

L

// If the device’s supported version is less than version 2.0

if( caps.VertexShaderVersion < D3DVSFVERSION(2, 0) )

 

// Then vertex shader version 2.0 is not supported on this

 

// device.

 

M

 

We see that the two parameters of D3DVS_VERSION take the major

 

 

A

 

and minor version number, respectively. Currently, the D3DXCompile-

ShaderFromFileEfunction supports vertex shader versions 1.1 and

2.0.

T

 

 

 

 

 

Objectives

To learn how we define the components of our vertex structure in the programmable pipeline

To learn about the different usages for vertex components

To learn how to create, set, and destroy a vertex shader

To learn how to implement a cartoon rendering effect using a vertex shader

17.1Vertex Declarations

Thus far, we have been using a flexible vertex format (FVF) to describe the components of our vertex structure. However, in the programmable pipe, our vertex data can contain much more data than can be expressed with an FVF. Therefore, we usually use the more descriptive and powerful vertex declaration.

Team-Fly®

Introduction to Vertex Shaders 295

Note: We can still use an FVF with the programmable pipeline if the format of our vertex can be described by it. However, this is for convenience only, as the FVF will be converted to a vertex declaration internally.

17.1.1 Describing a Vertex Declaration

We describe a vertex declaration as an array of D3DVERTEXELEMENT9 structures. Each element in the D3DVERTEXELEMENT9 array describes one component of the vertex. So if your vertex structure has three components (e.g., position, normal, color), then the corresponding vertex declaration will be described by an array of three D3DVERTEXELEMENT9 structures. The D3DVERTEXELEMENT9 structure is defined as:

typedef struct _D3DVERTEXELEMENT9 { BYTE Stream;

BYTE Offset;

BYTE Type;

BYTE Method;

BYTE Usage;

BYTE UsageIndex;

} D3DVERTEXELEMENT9;

Stream—Specifies the stream with which the vertex component is associated

Offset—The offset, in bytes, to the start of the vertex component relative to the vertex structure of which it is a member. For example, if the vertex structure is:

struct Vertex

{

D3DXVECTOR3 pos; D3DXVECTOR3 normal;

};

 

...The offset of the component pos is 0 since it’s the first compo-

 

nent. The offset of the component normal is 12 because

 

sizeof(pos) == 12. In other words, the component normal

 

starts at byte 12 relative to Vertex.

 

Type—Specifies the data type. This can be any member of the

 

D3DDECLTYPE enumerated type; see the documentation for a com-

 

plete list. Some commonly used types are:

 

 

D3DDECLTYPE_FLOAT1—A floating-point scalar

V

 

I

D3DDECLTYPE_FLOAT2—A 2D floating-point vector

rt

D3DDECLTYPE_FLOAT3—A 3D floating-point vector

Pa

D3DDECLTYPE_FLOAT4—A 4D floating-point vector

 

 

296Chapter 17

D3DDECLTYPE_D3DCOLOR—A D3DCOLOR type that is expanded to the RGBA floating-point color vector (r g b a), with each component normalized to the interval [0, 1].

Method—Specifies the tessellation method. We consider this parameter advanced, and thus we use the default method, which is specified by the identifier D3DDECLMETHOD_DEFAULT.

Usage—Specifies the planned use for the vertex component. For example, is it going to be a position vector, normal vector, texture coordinate, etc.? Valid usage identifiers are of the D3DDECLUSAGE enumerated type:

typedef enum _D3DDECLUSAGE {

 

 

D3DDECLUSAGE_POSITION

= 0,

// Position.

D3DDECLUSAGE_BLENDWEIGHTS

= 1,

// Blending weights.

D3DDECLUSAGE_BLENDINDICES

= 2,

// Blending indices.

D3DDECLUSAGE_NORMAL

= 3,

// Normal vector.

D3DDECLUSAGE_PSIZE

= 4,

// Vertex point size.

D3DDECLUSAGE_TEXCOORD

= 5,

// Texture coordinates.

D3DDECLUSAGE_TANGENT

= 6,

// Tangent vector.

D3DDECLUSAGE_BINORMAL

= 7,

// Binormal vector.

D3DDECLUSAGE_TESSFACTOR

= 8,

// Tessellation factor.

D3DDECLUSAGE_POSITIONT

= 9,

// Transformed position.

D3DDECLUSAGE_COLOR

= 10,

// Color.

D3DDECLUSAGE_FOG

= 11,

// Fog blend value.

D3DDECLUSAGE_DEPTH

= 12,

// Depth value.

D3DDECLUSAGE_SAMPLE

= 13

// Sampler data.

} D3DDECLUSAGE;

 

 

The D3DDECLUSAGE_PSIZE type is used to specify a vertex point size. This is used for point sprites so that we can control the size on a per vertex basis. A vertex declaration with a D3DDECLUSAGE_ POSITIONT member implies that the vertex has already been transformed, which instructs the graphics card to not send this vertex through the vertex processing stages (transformation and lighting).

Note: A few of these usage types are not covered in this book, such as BLENDWEIGHTS, BLENDINDICES, TANGENT, BINORMAL, and

TESSFACTOR.

UsageIndex—Used to identify multiple vertex components of the same usage. The usage index is an integer in the interval [0, 15]. For example, suppose that we have three vertex components of the usage D3DDECLUSAGE_NORMAL. We would specify a usage index of 0 for the first, a usage index of 1 for the second, and a usage index of 2 for the third. In this way we can identify each particular normal by its usage index.

Introduction to Vertex Shaders 297

Example vertex declaration description: Suppose the vertex format we want to describe consists of a position vector and three normal vectors. The vertex declaration would be specified as:

D3DVERTEXELEMENT9 decl[] =

{

{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ POSITION, 0},

{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ NORMAL, 0},

{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ NORMAL, 1},

{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ NORMAL, 2},

D3DDECL_END() };

The D3DDECL_END macro is used to initialize the last vertex element in the D3DVERTEXELEMENT9 array. Also, observe the usage index labels for the normal vectors.

17.1.2 Creating a Vertex Declaration

Once you have described a vertex declaration as a D3DVERTEXELEMENT9 array, we can obtain a pointer to an IDirect3DVertexDeclaration9 interface using the method:

HRESULT IDirect3DDevice9::CreateVertexDeclaration(

CONST D3DVERTEXELEMENT9* pVertexElements,

IDirect3DVertexDeclaration9** ppDecl

);

pVertexElements—Array of D3DVERTEXELEMENT9 structures describing the vertex declaration we want created

ppDecl—Used to return a pointer to the created IDirect3DVertexDeclaration9 interface

Example call, where decl is a D3DVERTEXELEMENT9 array:

IDirect3DVertexDeclaration9* _decl = 0;

hr = _device->CreateVertexDeclaration(decl, &_decl);

17.1.3 Enabling a Vertex Declaration

Recall that flexible vertex formats are a convenience feature and internally get converted to vertex declarations. Thus, when using vertex declarations directly, we no longer call:

Device->SetFVF( fvf );

We instead call:

P a r t I V

Device->SetVertexDeclaration( _decl );

298 Chapter 17

where _decl is a pointer to an IDirect3DVertexDeclaration9 interface.

17.2 Vertex Data Usages

Consider the vertex declaration:

D3DVERTEXELEMENT9 decl[] =

{

{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ POSITION, 0},

{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ NORMAL, 0},

{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ NORMAL, 1},

{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_ NORMAL, 2},

D3DDECL_END() };

We need a way to define a map from the elements of the vertex declaration to the data members of the vertex shader’s input structure. We define this map in the input structure by specifying a semantic

(: usage-type[usage-index]) for each data member. The semantic identifies an element in the vertex declaration by its usage type and usage index. The vertex element identified by a data member’s semantic is the element that gets mapped to that data member. For example, an input structure for the previous vertex declaration is:

struct VS_INPUT

 

{

 

vector position

: POSITION;

vector normal

: NORMAL0;

vector faceNormal1

: NORMAL1;

vector faceNormal2 : NORMAL2;

};

Note: If we leave off the usage index, it implies usage index zero. For example, POSITION is the same thing as POSITION0.

Here element 0 in decl, identified by usage POSITION and usage index 0, is mapped to position. Element 1 in decl, identified by usage NORMAL and usage index 0, is mapped to normal. Element 2 in decl, identified by usage NORMAL and usage index 1, is mapped to faceNormal1. Element 3 in decl, identified by usage NORMAL and usage index 2, is mapped to faceNormal2.

Introduction to Vertex Shaders 299

The supported vertex shader input usages are:

POSITION[n]—Position

BLENDWEIGHTS[n]—Blend weights

BLENDINDICES[n]—Blend indices

NORMAL[n]—Normal vector

PSIZE[n]—Vertex point size

DIFFUSE[n]—Diffuse color

SPECULAR[n]—Specular color

TEXCOORD[n]—Texture coordinates

TANGENT[n]—Tangent vector

BINORMAL[n]—Binormal vector

TESSFACTOR[n]—Tessellation factor

Where n is an optional integer in the range [0, 15].

Note: Again, a few of these usage types are not covered in this book, such as BLENDWEIGHTS, TANGENT, BINORMAL, BLENDINDICES, and TESSFACTOR.

In addition, for the output structure, we must specify what each member is to be used for. For example, should the data member be treated as a position vector, color, texture coordinate, etc.? The graphics card has no idea, unless you explicitly tell it. This is also done with the semantic syntax:

struct VS_OUTPUT

 

 

{

 

 

vector position

: POSITION;

 

vector diffuse

: COLOR0;

 

vector specular

: COLOR1;

 

};

 

 

The supported vertex shader output usages are:

 

POSITION—Position

 

PSIZE—Vertex point size

 

FOG—Fog blend value

 

COLOR[n]—Vertex color. Observe that multiple vertex colors can

IV

be output, and these colors are blended together to produce the

 

final color.

 

rt

TEXCOORD[n]—Vertex texture coordinates. Observe that multiple

Pa

 

texture coordinates can be output.

where n is an optional integer in the interval [0, 15].

300Chapter 17

17.3Steps to Using a Vertex Shader

The following list outlines the steps necessary to create and use a vertex shader.

1.Write and compile the vertex shader.

2.Create an IDirect3DVertexShader9 interface to represent the vertex shader based on the compiled shader code.

3.Enable the vertex shader with the IDirect3DDevice9:: SetVertexShader method.

Of course, we have to destroy the vertex shader when we are done with it. The next subsections go into these steps in more detail.

17.3.1 Writing and Compiling a Vertex Shader

First, we must write a vertex shader program. In this book we write our shaders in HLSL. Once the shader code is written, we compile the shader using the D3DXCompileShaderFromFile function, as described in section 16.2.2. Recall that this function returns a pointer to an ID3DXBuffer that contains the compiled shader code.

17.3.2 Creating a Vertex Shader

Once we have the compiled shader code, we can obtain a pointer to an IDirect3DVertexShader9 interface, which represents a vertex shader, using the following method:

HRESULT IDirect3DDevice9::CreateVertexShader(

const DWORD *pFunction,

IDirect3DVertexShader9** ppShader

);

pFunction—Pointer to compiled shader code

ppShader—Returns a pointer to an IDirect3DVertexShader9 interface

For example, suppose the variable shader is an ID3DXBuffer that contains the compiled shader code. Then to obtain an IDirect3DVertexShader9 interface, we would write:

IDirect3DVertexShader9* ToonShader = 0; hr = Device->CreateVertexShader(

(DWORD*)shader->GetBufferPointer(), &ToonShader);

Introduction to Vertex Shaders 301

Note: To reiterate, the D3DXCompileShaderFromFile is the function that would return the compiled shader code (shader).

17.3.3 Setting a Vertex Shader

After we have obtained a pointer to an IDirect3DVertexShader9 interface that represents our vertex shader, we can enable it using the following method:

HRESULT IDirect3DDevice9::SetVertexShader(

IDirect3DVertexShader9* pShader

);

The method takes a single parameter where we pass a pointer to the vertex shader that we wish to enable. To enable the shader we created in section 17.3.2, we would write:

Device->SetVertexShader(ToonShader);

17.3.4 Destroying a Vertex Shader

As with all Direct3D interfaces, to clean them up we must call their Release method when we are finished with them. Continuing to use the vertex shader we created in section 17.3.2, we have:

d3d::Release<IDirect3DVertexShader9*>(ToonShader);

17.4 Sample Application: Diffuse Lighting

As a warm-up to creating and using vertex shaders, we write a vertex shader that does standard diffuse lighting per vertex with a directional (parallel) light source. As a recap, diffuse lighting calculates the amount of light that a vertex receives based on the angle between the vertex normal and the light vector (which points in the direction of the light source). The smaller the angle, the more light the vertex receives, and the larger the angle, the less light the vertex receives. If the angle is greater than or equal to 90 degrees, the vertex receives no light. Refer back to section 13.4.1 for a more complete description of the diffuse lighting algorithm.

We begin by examining the vertex shader code.

//File: diffuse.txt

//Desc: Vertex shader that does diffuse lighting.

//

//Global variables we use to hold the view matrix, projection matrix,

//ambient material, diffuse material, and the light vector that

//describes the direction to the light source. These variables are

//initialized from the application.

P a r t I V

//

302 Chapter 17

matrix ViewMatrix; matrix ViewProjMatrix;

vector AmbientMtrl; vector DiffuseMtrl;

vector LightDirection;

//

//Global variables used to hold the ambient light intensity (ambient

//light the light source emits) and the diffuse light

//intensity (diffuse light the light source emits). These

//variables are initialized here in the shader.

//

vector DiffuseLightIntensity = {0.0f, 0.0f, 1.0f, 1.0f}; vector AmbientLightIntensity = {0.0f, 0.0f, 0.2f, 1.0f};

//

// Input and Output structures.

//

struct VS_INPUT

{

vector position : POSITION; vector normal : NORMAL;

};

struct VS_OUTPUT

{

vector position : POSITION; vector diffuse : COLOR;

};

//

// Main

//

VS_OUTPUT Main(VS_INPUT input)

{

// zero out all members of the output instance. VS_OUTPUT output = (VS_OUTPUT)0;

//

//Transform position to homogeneous clip space

//and store in the output.position member.

//

output.position = mul(input.position, ViewProjMatrix);

//

//Transform lights and normals to view space. Set w

//components to zero since we're transforming vectors

//here and not points.

//

LightDirection.w = 0.0f;

Introduction to Vertex Shaders 303

input.normal.w

= 0.0f;

LightDirection

= mul(LightDirection, ViewMatrix);

input.normal

= mul(input.normal, ViewMatrix);

//

// Compute cosine of the angle between light and normal.

//

float s = dot(LightDirection, input.normal);

//

//Recall that if the angle between the surface and light

//is greater than 90 degrees the surface receives no light.

//Thus, if the angle is greater than 90 degrees we set

//s to zero so that the surface will not be lit.

//

if( s < 0.0f ) s = 0.0f;

//

//Ambient light reflected is computed by performing a

//component-wise multiplication with the ambient material

//vector and the ambient light intensity vector.

//

//Diffuse light reflected is computed by performing a

//component-wise multiplication with the diffuse material

//vector and the diffuse light intensity vector. Further

//we scale each component by the shading scalar s, which

//shades the color based on how much light the vertex received

//from the light source.

//

//The sum of both the ambient and diffuse components give

//us our final vertex color.

//

output.diffuse = (AmbientMtrl * AmbientLightIntensity) +

(s * (DiffuseLightIntensity * DiffuseMtrl));

return output;

}

Now that we have looked at the actual vertex shader code, let’s shift gears and look at the application code. The application has the following relevant global variables:

IDirect3DVertexShader9* DiffuseShader = 0;

ID3DXConstantTable* DiffuseConstTable = 0;

ID3DXMesh* Teapot

= 0;

D3DXHANDLE ViewMatrixHandle

= 0;

D3DXHANDLE ViewProjMatrixHandle = 0;

D3DXHANDLE AmbientMtrlHandle

= 0;

D3DXHANDLE DiffuseMtrlHandle

= 0;

D3DXHANDLE LightDirHandle

= 0;

P a r t I V

D3DXMATRIX Proj;