Basic Shader Implementation

The next logical feature in my mind to start implementing into our 3D engine is a way to add lights to our 3D scenes. I have decided to do this using shaders partially because of their widespread availability and use, but also partially because I would rather only add one method of lighting our objects instead of programming an entire method based simply on DirectX built in functions and then later adding in a second type of lighting using shaders.

This tutorial will not cover the shader programming itself, but will only show the way that we will be sending our object and other video card data to the shaders from within the environment of our engine so far. Other shader programming tutorials will be added in the future to supplement this feature.

Step 1: Making Sure we have Shader Support

The first thing we should do when adding shaders to a game engine is make sure the user’s video card can support the use of shaders in rendering its vertices. We will do this by adding a few code sections to our DirectX initialization method in the HMSystem class. Here is what the new code looks like:

private bool InitializeGraphics() { try { // … Existing code // Check for hardware device shader support Caps hardware = Manager.GetDeviceCaps(0, DeviceType.Hardware); if((hardware.VertexShaderVersion >= new Version(2, 0)) && (hardware.PixelShaderVersion >= new Version(2, 0))) { // Default to using software processing CreateFlags flags = CreateFlags.SoftwareVertexProcessing; // But use hardware if it is available if(hardware.DeviceCaps.SupportsHardwareTransformAndLight) flags = CreateFlags.HardwareVertexProcessing; // and use pure hardware if we can if(hardware.DeviceCaps.SupportsPureDevice) flags |= CreateFlags.PureDevice; // now create the device with hardware shaders myDevice = new Device( 0, DeviceType.Hardware, this, flags, presentParams ); } else { // we have no hardware shader support so use reference device myDevice = new Device( 0, DeviceType.Reference, this, CreateFlags.SoftwareVertexProcessing, presentParams ); } } catch(DirectXException) { MessageBox.Show(“Unable to initialize Direct3D”); return false; } return true; }

Step 2: A Basic Shader class, and a Place for it

As is fairly common with how this engine has been put together so far, I am going to write a wrapper class and call it HMShader. For now this will only hold a string title, the filename for the shader an Effect object we can refernece when we are rendering the mesh. Here is what the simple shader class looks like:

public class HMShader { private string myTitle; private string myFile; private Effect myEffect; public string MyTitle { get { return myTitle; } } public Effect MyEffect { get { return myEffect; } } public HMShader(string title, string filename, Device myDevice) { myTitle = title; myFile = filename; myEffect = Effect.FromFile(myDevice, filename, null, null, ShaderFlags.None, null); } }

The best place I can think to store this shader object is in the HMScene class so it can be referenced by multiple meshes and only needs to be stored once. Here are the modifications to the HMScene and HMObject to accomodate this new functionality

// In the HMScene class private ArrayList ShaderObjects = new ArrayList(); public void AddShader(HMShader newShader) { ShaderObjects.Add(newShader); } public void RemoveShader(HMShader oldShader) { ShaderObjects.Remove(oldShader); } public void Render(HMCamera myCamera, Device myDevice) { foreach(HMObject o in WorldObjects) { HMShader currentShader = GetShader(o.MyShader); if(currentShader == null){ o.Render(myCamera, myDevice); } else { o.ShaderRender(currentShader, myCamera, myDevice); } } } private HMShader GetShader(string shaderTitle) { foreach(HMShader s in ShaderObjects) { if(s.MyTitle == shaderTitle) { return s; } } return null; } // In the HMObject class protected string myShader = “”; public string MyShader { get { return myShader; } } public void SetShader(string newShader) { myShader = newShader; } public virtual void ShaderRender(HMShader myShader, HMCamera myCamera, Device myDevice) { }

Step 3: Adding a Render Function for Meshes

Instead of simply overwriting the render functions that we already have, I thought I would write a separate function for rendering using shaders for each of our types of objects and calling that function from our other Render code depending on whether that object has had a shader set on it yet. Since all of the rest of that functionality has been added, all that is left is to actually set a shader in the Demo class and to write a ShaderRender function for one of our objects so we can see all this code in action.

For starters, I will just write the ShaderRender function for meshes so we can see how this will all fit together.

// In the HMMesh class public override void ShaderRender(HMShader myShader, HMCamera myCamera, Device myDevice) { // We pass this for use by the shader the same way we do in the normal Render function Matrix matWorld = Matrix.Scaling(myScaling) * Matrix.RotationYawPitchRoll(myRotation.Y, myRotation.X, myRotation.Z) * Matrix.Translation(myPosition); Matrix matView = myCamera.View; Matrix matProj = myCamera.Projection; Matrix WorldViewProj = matWorld * matView * matProj; myShader.MyEffect.SetValue(“WorldViewProj”, WorldViewProj); // Start the rendering process here int passes = myShader.MyEffect.Begin(0); for(int i = 0; i < passes; i++) { myShader.MyEffect.BeginPass(i); for(int m = 0; m < myMaterials.Length; m++) { myDevice.SetTexture(0, myTextures[m]); myMesh.DrawSubset(m); } myShader.MyEffect.EndPass(); } myShader.MyEffect.End(); }

Step 4: Displaying our Hard Work

Now, all that is left is to add an HMShader to the HMScene and tell our tiger mesh that it is going to use that shader. Here is a simple shader for rendering a textured mesh:

float4x4 WorldViewProj; struct VS_INPUT { float4 Position : POSITION0; float2 Texcoord : TEXCOORD0; }; struct VS_OUTPUT { float4 Position : POSITION0; float2 Texcoord : TEXCOORD0; }; VS_OUTPUT Transform(VS_INPUT Input){ VS_OUTPUT Output; Output.Position = mul(Input.Position, WorldViewProj); Output.Texcoord = Input.Texcoord; return Output; } sampler TextureSampler; struct PS_INPUT { float2 Texcoord : TEXCOORD0; }; float4 Texture(PS_INPUT Input) : COLOR0{ return tex2D(TextureSampler, Input.Texcoord); }; technique TransformTexture { pass P0 { VertexShader = compile vs_2_0 Transform(); PixelShader = compile ps_2_0 Texture(); } }

Save that code as “TransformTextured.fx” within the Demo/bin/Debug folder or somewhere else you can reference the file location from when we run the demo. In the Demo class we will need to add three lines of code, you can use them along with the three tigers that are already being rendered, or simply remove everything else (comment it out or delete it, I am simply removing everything else) and just use the code needed to render a single mesh with the shader. Here is the code for that

HMShader shader1 = new HMShader(“TransformTextured”, “TransformTextured.fx”, demo1.MyDevice); demo1.MyScene.AddShader(shader1); mesh1.SetShader(“TransformTextured”); // In case anyone was wondering here is what my SetupScene function looks like now static void SetupScene() { // I got a new skybox texture, so these files have changed, feel free to use any skybox you like string[] textures = { @”skybox\ft.jpg”, @”skybox\rt.jpg”, @”skybox\bk.jpg”, @”skybox\lt.jpg”, @”skybox\up.jpg”, @”skybox\dn.jpg” }; HMSkyBox sky1 = new HMSkyBox(textures, demo1.MyDevice); sky1.SetScaling(new Vector3(100, 100, 100)); HMMesh mesh1 = new HMMesh(“tiger.x”, new Vector3(0, 0, 0), new Vector3(), new Vector3(1, 1, 1), demo1.MyDevice); HMShader shader1 = new HMShader(“TransformTextured”, “TransformTextured.fx”, demo1.MyDevice); demo1.MyScene.AddShader(shader1); mesh1.SetShader(“TransformTextured”); demo1.MyScene.AddObject(sky1); demo1.MyScene.AddObject(mesh1); // We still include this line because the skybox is not being rendered with a shader or lights demo1.MyDevice.RenderState.Lighting = false; }

There you have it. A very simple way of loading in shaders and rendering your objects with them. This is an incredibly simple fremework, but it will allow us to add much more complex shader techniques later.

Leave a Reply

You must be logged in to post a comment.

Do NOT follow this link or you will be banned from the site!