We need two more things before we can actually render a 3D object. The first, and most important one, is the set of shaders that we will use to define the look of the object and tell the GPU what to do.
A shader is a small program written in a custom language (in this case, the High Level Shader Language—HLSL) that describes how the different stages of the pipeline will operate to render to the screen. There are a few different types of shaders; however, we only really need to cover the vertex and pixel shaders before any 3D rendering can be done.
The first shader that we will look at is the vertex shader. This shader is responsible for taking the vertices from our buffer and making sure they are in the right place relative to the virtual camera in the world.
A common vertex shader looks like the following:
cbuffer WVP : register(b0) { matrix World; matrix View; matrix Projection; }; float3 main(float3 inputPos : POSITION) : POSITION { float4 pos = float4(inputPos, 1.0f); pos = mul(pos, World); pos = mul(pos, View); pos = mul(pos, Projection); return pos; }
HLSL has a C-like syntax that should make it easy to understand, so we'll focus on the new parts.
At the top you'll notice the
cbuffer
block. This is our shader representation of a constant buffer that we create in our game. In this case we provide three matrices, the world transform, the view transform, and the projection transform. We'll use these to take the vertex through to screen space, ready for the pixel shader.
Next you'll notice the definition of main, with some interesting additions to the parameters. The POSITION
part is the semantic for the variable. This tells the GPU where to use this variable in the overall pipeline. With this you can link the inputPos
variable to the position of the vertex. At the same time, we apply the POSITION
semantic to the function to tell the GPU that we want to use the float3
return value as the output POSITION
.
Pixel shaders represent the final step in the rendering pipeline that you can control. Once a vertex emerges from the vertex shader, the pipeline determines which pixels make up the surface of the triangle, and uses the pixel shader to draw each one, interpolating between the vertices as required to provide input to the pixel shader.
A very simple pixel shader that sets the entire model to a single color has the following code:
float4 main(float4 pos : POSITION) : SV_TARGET { return float4(1.0f, 0, 0, 1.0f); }
This is quite short; however, it illustrates what you need to provide to successfully render something. In particular, you need to set the SV_TARGET
semantic to tell the pipeline what color to put at that position. To do this, we need to return a float4
that describes the color using the red, green, blue, and alpha channels, in that order. Setting a value of 1.0f
on a channel indicates we want 100 percent of that color in the final mixed RGB value, and in this case we will see red in the shape of the model.
Pixel shaders really become powerful when they are used to add lighting and shading to objects. These effects can range from simple calculations (N dot L lighting calculation), to advanced global illumination (light bounces around the scene to realistically provide ambient lighting).
We now need to load these shaders into our program, compile them, and load them onto the GPU. One important thing to note here is that you cannot compile shaders once they have been submitted to the Windows Store, so you need to make sure you precompile the shaders and load them in (from the file or from within the code) as byte-code shaders when you want to submit to the store. During development you are allowed to use the compiler within your application if you want; however, once you are ready to submit you need to precompile the shaders.
There are a few ways to load in your code, and the Direct3D sample provides a helper method that reads all the data from a file. Once you have the data, though, you need to use the Direct3D device to create the shader.
Note
If you give your shader the .hlsl
file extension and add it to your project, Visual Studio 2012 will compile it for you and output an object file that you can load directly without worrying about a custom compile step.
To load in the shader you need to call the appropriate CreateXXShader method, where XX is the type of shader. To create the vertex shader, you can use the following method:
ID3D11VertexShader *shader; m_d3dDevice->CreateVertexShader( file->Data, file->Length, nullptr, &shader );
The first two parameters are standard: the unsigned char array that contains the bytes for the shader data and the length of the data. The third parameter provides the class linkage data. However, unless you're working with the advanced features in Shader Model 5 you won't need this; passing the nullptr
is fine. Finally you get the shader from the final parameter using a pointer to an ID3D11VertexShaderpointer
. Pixel shaders are similar; just change the name.