Writing a Vertex Shader


Before you write your first vertex shader program, you need something to render other than the blank screen that currently is displayed after you click one of the new game type buttons. Your first object rendered with the programmable pipeline might as well be the go-kart because that will be the focus of the game. Add the following variables to your class now:

 // Kart mesh private Mesh kartMesh = null; private Texture[] kartTextures = null; 

You've been using meshes and textures throughout this book, so this code shouldn't be that surprising. You might have noticed that the materials you normally create when you load the mesh are nonexistent, though. The materials are used by the fixed-function pipeline to determine how to blend the colors between the light and the textures, and they are not necessary for the programmable shaders you will be writing this chapter.

You also need a place to create these objects. You don't want to create them at startup because the application might just quit right away; instead, you create them when the user clicks one of the new game buttons to start playing the game. Find the OnNewGame method in your class and add the following code to load the kart mesh:

 ExtendedMaterial[] materials = null; // Create the kart mesh and textures kartMesh = Mesh.FromFile(MediaPath + "kart.x", MeshFlags.Managed,     sampleFramework.Device, out materials); if ( (materials != null) && (materials.Length > 0) ) {     // Create the textures     kartTextures = new Texture[materials.Length];     for(int i = 0; i < materials.Length; i++)     {         kartTextures[i] = ResourceCache.GetGlobalInstance().CreateTextureFromFile(             sampleFramework.Device,  MediaPath +             gameUI.GetComboBox(PickKartControl).GetSelectedData() as string);     } } 

Loading the mesh happens the same way as always here, with the only exception being the material loading code. Three things stand out:

  • The material structures themselves are ignored.

  • The resource cache is used to load the textures.

  • The texture filenames are also ignored, and the combo box data (which stored the texture filename you would be using) is used instead.

With the mesh and textures loaded now, you're ready to start writing your shader. Open the KartRacers.fx file and replace the code with that in Listing 20.2.

Listing 20.2. The Empty Shader File
 //----------------------------------------------------------------------------- // Global variables //----------------------------------------------------------------------------- float4x4 worldMatrix : WORLD;  // World matrix for object float4x4 worldViewProjection : WORLDVIEWPROJECTION; // World * View * Projection matrix float4 Transform(in float4 pos : POSITION, out float4 Position :                  POSITION) : COLOR0 {     // Transform position     Position = mul(pos, worldViewProjection);     return 1.0f; } //----------------------------------------------------------------------------- // Techniques //----------------------------------------------------------------------------- technique RenderScene {     pass P0     {         VertexShader = compile vs_1_1 Transform();         PixelShader  = NULL;     } } 

You'll notice that one of the variables (appTime) has been removed, a function has been added, and new things have been added to the P0 pass. The variable was removed because it isn't required for this application, but you should take a closer look at this new function.

It takes a single parameter in, a float4 called pos. This is the position of the vertex being computed currently. Notice that the second parameter is an out parameter, also of type float4. This parameter will be the transformed vertex after the shader executes. Last, notice that the function itself also returns a float4, which will be the final color of the vertex.

The only thing this shader does is transform the vertices from model space. In the fixed-function pipeline, this transformation happens by setting the three TRansform variables on the device, namely, World, View, and Projection. Because you're not using the fixed-function pipeline, you are required to do this transformation yourself, by multiplying each vertex by the combined world/view/projection matrices. Notice that you are required to use the mul intrinsic: the combined matrix is a float4x4, but the vertex is only a float4. The result is stored in the out parameter specified in the function prototype.

You might also be wondering what the things after the colon (:)mean, such as pos : position. They are called semantics, and they are simply extra information for you to use to clarify what types of data the variables hold. In the preceding case, it means that the pos variable holds position data.

Declaring Variables in HLSL and Intrinsic Types

You will notice that we are using some intrinsic types that don't exist in the C or C# languages, namely, float4 and float4x4. The scalar types that HLSL supports are

bool

true or false

int

A 32-bit signed integer

half

A 16-bit floating-point value

float

A 32-bit floating-point value

double

A 64-bit floating-point value


A variable of one of these types will behave just as it would in the C# language, a single variable of that type. However, adding a single integer value to the end of one of these scalar types will declare a vector type. These vector types can be used like a vector or like an array. For example, look at a declaration similar to one we used:

 float4 pos; 

This declares a Vector4 to hold a position. If you want a 2D vector instead, you could declare it as

 float2 someVector; 

You can access the members of this variable as an array; such as

 pos[0] = 2.0f; 

You can also access this type much as a vector in your C# code, such as

 pos.x = 2.0f; 

You can access one or more of the vector components in this way. This technique is called swizzling. You may use the vector components of xyzw as well as the color components of rgba; however, you may not mix and match them in the same swizzle. For example, these lines are valid (even if they don't do anything):

 pos.xz = 0.0f; pos.rg += pos.xz; 

However, this line is not:

 pos.xg = 0.0f; 

The xg swizzle isn't valid because it's mixing both the vector and color components.

Variables can also have modifiers much as they do in C and C#. You can declare constants much as you would normally:

 const float someConstant = 3.0f; 

You can share variables among different programs:

 shared float someVariable = 1.0f; 

See the DirectX SDK documentation if you want more information on the HLSL language.

You might notice that the function here will always return a color of 1.0f (which happens to be pure white). Although this isn't the desired final effect, it will help you get started enough to see something onscreen.

The changes to the technique and pass are actually minor. These two lines are simply telling the effects system that you are going to be using a vertex shader (the 1.1 model) and that the TRansform method will be the shader in use, but you will not be using a pixel shader at all. The effects system handles the rest.

Now you need to add some code to get the mesh rendered. Find the OnFrameRender method, look for the if statement determining the current game state, and add the else clause in Listing 20.3 to it.

Listing 20.3. Rendering Your Effect
 else if (currentState == GameState.Gameplay) {     // Update the effect's variables. Instead of using strings, it would     // be more efficient to cache a handle to the parameter by calling     // Effect.GetParameter     effect.SetValue("worldViewProjection", camera.WorldMatrix *         camera.ViewMatrix * camera.ProjectionMatrix);     effect.SetValue("worldMatrix", camera.WorldMatrix);     effect.Technique = "RenderScene";     int passes = effect.Begin(0);     for (int pass = 0; pass < passes; pass++)     {         effect.BeginPass(pass);         for (int i = 0; i < kartTextures.Length; i++)         {             kartMesh.DrawSubset(i);         }         effect.EndPass();     }     effect.End(); } 

Make sure to remove the calls to SetValue outside of the if statements because they're no longer required (and will throw a runtime exception because you removed that variable). What's going on in this code snippet? Obviously, the first thing you do is set the values for the variables declared in your shader file, just as the code did before. But what do you need to do then?

First, you tell the effects system which technique you want to be running; in this case, you want to run the RenderScene technique (of course, because it's the only one there). With the technique set, you want to then render some objects using it, so you call the Begin method, which returns the number of passes that this technique has. Although you happen to know that this technique only has one pass in it currently, it's still best to start the loop in this way so if you ever want to add passes to your techniques later, you could without any code changes.

Once inside the loop, you call the BeginPass method, passing in the index of the pass you're currently on. This is Direct3D's cue to ensure that all shader data and state are set up correctly on the device so the device is ready to render your data with the appropriate shader. You should do all the drawing you plan on doing in between the BeginPass and EndPass calls. You'll notice that this code snippet does exactly that by calling the DrawSubset member of the mesh for each texture in the list. After all the passes are finished, you call the End method on the effect to let the effects system know you've completed your work.

Construction Cue

You probably noticed you haven't used your texture yet (such as through a call to SetTexture). I address textures later in this chapter during the pixel shader portion. Although the PixelShader member is set to null, the mesh is rendered without any shading from lighting. In this case, it is pure white because that's the color you returned in the vertex shader.


Running the application now and choosing a new game option will just show you a big blob of white (because the default camera is too close). Find the OnResetDevice method and add this line to the end of it:

 camera.SetRadius(30.0f, 30.0f, 200.0f); 

This code zooms the camera out some and allows you to see the model. You can drag using the mouse buttons to rotate the model around (and the wheel to zoom in and out). See Figure 20.1 for an example of the program up to this point.

Figure 20.1. Your first shader output.




Beginning 3D Game Programming
Beginning 3D Game Programming
ISBN: 0672326612
EAN: 2147483647
Year: 2003
Pages: 191
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net