Archive for the ‘DirectX Tutorials’ Category

Consider XNA

Monday, June 2nd, 2008

For anyone still visiting these tutorials in hopes of learning more about the Managed DirectX Framework and creating games with it, consider the newer and replacement version known as the XNA Framework. Tutorials for an engine just like this (and I think a little bit better) can be found in the XNA Tutorials section.

Take a look

Giving ourselves some Objects for Play

Monday, February 6th, 2006

One thing that I have decided is missing from our HMMesh class is a way to generate simple default meshes that come with DirectX like the torus and teapot (instead of adding lines to our class everytime we want to test a shader with a round object like I had been doing.) The changes neccessary for this function are very minimal, and so this will more than likely be the shortest tutorial of the whole series, but we will probably use a lot of these objects when testing our physics later.

Step 1: Adding a Default Object Constructor

To let ourselves pick from the 5 included default meshes, I decided to simply duplicate the constructor in HMMesh and take an integer instead of a string for the first parameter. Here is the new code:

private int myType = -1; public HMMesh(int meshType, Vector3 pos, Vector3 rot, Vector3 scl, Device myDevice) { myType = meshType; myPosition = pos; myRotation = rot; myScaling = scl; ReloadResources(myDevice); myTimer = new FrameworkTimer(); myTimer.Start(); } // At the top of the old constructor myType = -1;

Now that we have our constructor set up, we will need to modify the ReloadResources() function to implement our new default object behavior. This is as simple as adding an if else with a switch inside. The top of ReloadResources now looks like this:

ExtendedMaterial[] mtrls = null; if(myType != -1) { switch(myType) { case 0: myMesh = Mesh.Box(myDevice, 1, 1, 1); break; case 1: myMesh = Mesh.Cylinder(myDevice, 1, 1, 3, 32, 1); break; case 2: myMesh = Mesh.Sphere(myDevice, 1, 32, 32); break; case 3: myMesh = Mesh.Teapot(myDevice); break; case 4: myMesh = Mesh.Torus(myDevice, 0.25f, 0.5f, 32, 32); break; default: myMesh = Mesh.Teapot(myDevice); break; } myMaterials = new Material[1]; myTextures = new Texture[1]; } else { myMesh = Mesh.FromFile(myPath, MeshFlags.Managed, myDevice, out mtrls); }

There you have it. A very simple way to add quite a few new objects to the world. Now I just have to think of a use for them!

Basic Shader Implementation

Wednesday, January 4th, 2006

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:

float4×4 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.

Making our Skybox act like a Skybox

Wednesday, October 19th, 2005

Now that we have a fully working camera (at least one we can use to move around and look at things) I have noticed that our skybox isn’t exactly acting like a real skybox should. A real skybox would follow the camera around so that it was always in the background of the scene being looked at, and it would also be much larger. These two changes are actually quite easy to implement.

Step 1: Making Our Skybox Bigger

For starters we can change the value in the skybox class for the size to be a much larger number since it has only been at 10 so far. Instead of changing the value of all of the vertices in the skybox class, we can more easily change the scaling value that is inherited from the HMObject class. This can be accomplished by adding the following code in the demo class:

sky1.SetScaling(new Vector3(100, 100, 100));

Step 2: Rendering at the Correct Position

Now, similar to the changes that we had to make to the Mesh class to take the transformation vectors into account, we must now add the functionality to do so to not only the skybox class, but to the VertexGroup class that it uses to render each of its sides. Here are the changes we have to make to the code to get all these transformations passed to their correct places and rendering correctly:

// In the HMObject class public virtual void SetPosition(Vector3 newPosition) { myPosition = newPosition; } public virtual void SetRotation(Vector3 newRotation) { myRotation = newRotation; } public virtual void SetScaling(Vector3 newScaling) { myScaling = newScaling; } // In the HMSkybox class public override void SetScaling(Vector3 newScaling) { foreach(HMVertexGroup vg in faces) { vg.SetScaling(newScaling); } } // In the HMVertexGroup class public override void Render(Device myDevice) { myDevice.Transform.World = Matrix.Scaling(myScaling) * Matrix.RotationYawPitchRoll(myRotation.Y, myRotation.X, myRotation.Z) * Matrix.Translation(myPosition); myDevice.VertexFormat = CustomVertex.PositionTextured.Format; myDevice.SetTexture(0, myTexture); myDevice.SetStreamSource(0, vb, 0); myDevice.Indices = ib; myDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, verts.Length, 0, inds.Length / 3); }

Step 3: Following the Camera Around

We will also need to make our skybox follow the camera around so it is always in the background and seems to be much farther away than it truly is. This is accomplished by changing the translation value for the skybox in the rendering function to the current position of the camera. To do this we will need to pass our camera into the Render function along with the device.

We will have to change the Render function for all of our object types and the call in our Scene class, but this is not that much trouble. Once we have gotten our camera passed on from the scene to each of our objects’ rendering functions, we can simply add a call to the skybox’s VertexGroup.SetPosition prior to each render so that it will adjust the boxes position when needed each frame. Here is what the code in HMSkybox looks like:

public override void Render(HMCamera myCamera, Device myDevice) { foreach(HMVertexGroup vg in faces) { vg.SetPosition(myCamera.Position); vg.Render(myCamera, myDevice); } }

Now our skybox is acting as it should, and if we need to change its size to accomodate a much larger world we can easily do so by changing one scaling value on the main skybox and it will automatically be adjusted for each of its parts and rendered correctly.

Picking Objects from 3D Space

Tuesday, October 18th, 2005

Now that we have the ability to put objects in space and move around them, it would also be nice to be able to choose which object we are focussed on. One method of doing this would be to click on the object on the screen and have the camera refocus itself around that object. This method of choosing an object from the screen with the mouse is called picking. We will implement it in a 3 step process.

Step 1: Setting up the Framework for Picking

The first thing we need to do is have some input from the mouse to play with and see if an object in our scene was clicked on so we will make some general functions for each of the 3 steps we are going to take. The first part of picking is simply getting the mouse clicks and sending them on to our scene. This code will go in the demo code and should look something like this:

// In the Main method demo1.MouseDown += new MouseEventHandler(demo1_MouseDown); // New function below Main static void demo1_MouseDown(object sender, MouseEventArgs e) { demo1.MyScene.Pick(e, demo1.MyCamera, demo1.MyDevice); }

Step 2: Getting the Scene to Pick all of our Objects

Logically this leads us to writing the next part of the picking function, converting the 2D point into a 3D ray by unprojecting it using an inverse matrix we will create by taking a few settings from our camera and video card. Here is the logic and code to accomplish what turns out to be a slightly lengthy group of actions:

public bool Pick(MouseEventArgs mouseEvent, HMCamera myCamera, Device myDevice) { // Get most of our placeholders for information ready Point mouseLoc = mouseEvent.Location; bool pickFound = false; IntersectInformation intInf; Vector3 rayStart, rayDirection; Matrix proj = myCamera.Projection;

Now we will convert the mouse click location into a 3D position in our world using the screen width and some simple algebra to move the center of reference to the middle of the screen instead of the top left corner where the original mouse point is taken from.

// Convert the mouse point into a 3D point Vector3 v; v.X = (((2.0f * mouseLoc.X) / myDevice.PresentationParameters.BackBufferWidth) – 1) / proj.M11; v.Y = -(((2.0f * mouseLoc.Y) / myDevice.PresentationParameters.BackBufferHeight) – 1) / proj.M22; v.Z = 1.0f;

Now, using the inverse of the matrix we normally use to project our 3D points, we will unproject our point to get a ray that shoots into the screen from the point we clicked and store it in two vectors, one for the ray’s initial point, and one for its direction

// Get the inverse of the composite view and world matrix Matrix m = myCamera.View * myCamera.World; m.Invert(); // Transform the screen space pick ray into 3D space rayDirection.X = v.X * m.M11 + v.Y * m.M21 + v.Z * m.M31; rayDirection.Y = v.X * m.M12 + v.Y * m.M22 + v.Z * m.M32; rayDirection.Z = v.X * m.M13 + v.Y * m.M23 + v.Z * m.M33; rayStart.X = m.M41; rayStart.Y = m.M42; rayStart.Z = m.M43;

Now comes the fun part. We must loop through all of the objects in our scene to find out which ones were clicked on (some may be behind others) and save the distances from the camera viewport so we know that the closer object was the one clicked on the screen, to store which object was clicked on, I created an instance variable at the top of our HMScene class: public HMObject activeObject;

This will allow us to access the object outside of the scene and manipulate some of our camera variables to change whenever a new object is clicked on. Here is what the code looks like for looping through our objects and saving the closest one:

float minDistance = float.MaxValue; for(int i = 0; i < WorldObjects.Count; i++) { HMObject o = (HMObject)WorldObjects[i]; if(o.Pick(rayStart, rayDirection, out intInf) && intInf.Dist < minDistance) { pickFound = true; activeObject = o; } } return pickFound; }

Now that we have our scene class looping through and calling our objects’ Pick method, we should probably write that method. For now we will just write the code for a mesh since a lot of the intersection math is handled by DirectX, but we will add other checks later so we can reuse our pick functionality to check things like GUI windows or other types of objects that might be added later on.

Step 3: Checking if an Object was Picked

The method for finding out whether an object was hit by the ray is much simpler to implement because DirectX does a lot of this for us. All we have to do is convert the ray into the local coordinates of the model we are checking (that is, unproject the ray using the model’s scaling, rotation, and translation matrices) and have the built in Mesh.Intersect function tell us whether we have hit home or not. Here is what this looks like:

public override bool Pick(Vector3 rayStart, Vector3 rayDirection, out IntersectInformation intInf) { // Convert ray to model space Matrix World = Matrix.Scaling(myScaling) * Matrix.RotationYawPitchRoll(myRotation.Y, myRotation.X, myRotation.Z) * Matrix.Translation(myPosition); Matrix inverseWorld = Matrix.Invert(World); Vector3 localStart = Vector3.TransformCoordinate(rayStart, inverseWorld); Vector3 localDirection = Vector3.TransformNormal(rayDirection, inverseWorld); localDirection.Normalize(); // Check for mesh intersection return myMesh.Intersect(localStart, localDirection, out intInf); }

That’s it! Now our engine will successfully test any mesh based objects that we load and report back with a true or false whether that object was clicked on or not. It will also set the clicked on object to be active in the scene so we can access it and play with other things (like camera focus) once we know what was clicked, which is how I will finish up this tutorial

Step 4: Putting our Picking to Good Use

Just so we can get an idea of how to put picking together with our current camera and see what we can do with just the few simple things we have learned and implemented so far, I decided to add a few meshes to the scene and allow the user to click a mesh and have the camera instantly focus on that mesh. To do this, we will only need to add a bit of code to our Demo class that will handle changing the camera’s radius so it is adjusted to the new object’s distance as well. We will also need to add a function in our camera to let us set a new focus point. Here is what the camera methods will look like:

public void SetTarget(Vector3 newTarget) { target = newTarget; } public void SetRadius(float newRadius){ radius = newRadius; } // Also remember to either make the location of HMObjects and the HMCamera public // or to make a property in each of these that will give you public access to them public Vector3 Position { get { return position; } }

The new code in the demo will look like this:

static void demo1_MouseDown(object sender, MouseEventArgs e) { if(demo1.MyScene.Pick(e, demo1.MyCamera, demo1.MyDevice)) { Vector3 objPosition = demo1.MyScene.activeObject.Position; Vector3 camPosition = demo1.MyCamera.Position; demo1.MyCamera.SetTarget(objPosition); demo1.MyCamera.SetRadius(Vector3.Length(objPosition – camPosition)); } } … HMMesh mesh1 = new HMMesh(“tiger.x”, new Vector3(0,0,0), new Vector3(), new Vector3(1, 1, 1), demo1.MyDevice); HMMesh mesh2 = new HMMesh(“tiger.x”, new Vector3(-5,0,5), new Vector3(), new Vector3(1, 1, 1), demo1.MyDevice); HMMesh mesh3 = new HMMesh(“tiger.x”, new Vector3(5,0,5), new Vector3(), new Vector3(1, 1, 1), demo1.MyDevice); demo1.MyScene.AddObject(mesh1); demo1.MyScene.AddObject(mesh2); demo1.MyScene.AddObject(mesh3);

Now if we run the demo we are able to click on any of our 3 tigers and have the camera fly around any of them. The motion of the camera is a bit choppy when changing targets, which is because of the way we update the camera’s position each frame. We will make this much smoother in the future when we change the camera’s motion with a scripting system that will be implemented in a later tutorial.

Converting the DirectX timer for easy use

Monday, October 17th, 2005

Almost every aspect of a video game uses a timer. Particle systems use them to calculate the motions and new positions of every one of the thousands of little sprites they contain, character animations user them to start and stop the motions and syncronize with other objects in the world. Camera scripts can use them to create video panning effects other cinemetography tricks. All in all a timer is probably one of the most important aspects of any game, and it is critical that th timer you use in your engine is versatile and easy to use. For this I have found it best to take the timer that comes with the DirectX SDK utllities and to simply convert it to be a class of its own so we can use as many instances of it as we need.

Step 1: Retreiving the code from dxmutmisc.cs

The first thing we need to do is find the file we are going to be taking our timer code from, it is ususally located in the managed samples that come with the SDK here: C:\Program Files\Microsoft DirectX 9.0 SDK (October 2005)\Samples\Managed\Common\dxmutmisc.cs. Open this file up and look around at all the wonderful utilities the people at Microsoft have provided us with by default. What we need is located between the #region Timer / #endregion. I am simply going to start by copying that section of code and pasting it into an empty HMUtility.cs file. The code will look like this:

#region Timer public class FrameworkTimer { #region Instance Data private static bool isUsingQPF; private static bool isTimerStopped; private static long ticksPerSecond; private static long stopTime; private static long lastElapsedTime; private static long baseTime; #endregion #region Creation private FrameworkTimer() { } // No creation /// <summary> /// Static creation routine /// </summary> static FrameworkTimer() { isTimerStopped = true; ticksPerSecond = 0; stopTime = 0; lastElapsedTime = 0; baseTime = 0; // Use QueryPerformanceFrequency to get frequency of the timer isUsingQPF = NativeMethods.QueryPerformanceFrequency(ref ticksPerSecond); } #endregion /// <summary> /// Resets the timer /// </summary> public static void Reset() { if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); baseTime = time; lastElapsedTime = time; stopTime = 0; isTimerStopped = false; } /// <summary> /// Starts the timer /// </summary> public static void Start() { if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); if(isTimerStopped) baseTime += (time – stopTime); stopTime = 0; lastElapsedTime = time; isTimerStopped = false; } /// <summary> /// Stop (or pause) the timer /// </summary> public static void Stop() { if(!isUsingQPF) return; // Nothing to do if(!isTimerStopped) { // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); stopTime = time; lastElapsedTime = time; isTimerStopped = true; } } /// <summary> /// Advance the timer a tenth of a second /// </summary> public static void Advance() { if(!isUsingQPF) return; // Nothing to do stopTime += ticksPerSecond / 10; } /// <summary> /// Get the absolute system time /// </summary> public static double GetAbsoluteTime() { if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); double absolueTime = time / (double)ticksPerSecond; return absolueTime; } /// <summary> /// Get the current time /// </summary> public static double GetTime() { if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); double appTime = (double)(time – baseTime) / (double)ticksPerSecond; return appTime; } /// <summary> /// get the time that elapsed between GetElapsedTime() calls /// </summary> public static double GetElapsedTime() { if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else NativeMethods.QueryPerformanceCounter(ref time); double elapsedTime = (double)(time – lastElapsedTime) / (double)ticksPerSecond; lastElapsedTime = time; return elapsedTime; } /// <summary> /// Returns true if timer stopped /// </summary> public static bool IsStopped { get { return isTimerStopped; } } } #endregion

Now this looks a bit messy, but there really isn’t much we will want to do to clean it up as almost all of the code is required for the timer functions to work correctly. One thing we do need to do however is retrieve the NativeMethods.QueryPerformance(Counter/Frequency) function so we can actually compile the timer. This is in the same file the timer was in in the NativeMethods/Windows API Calls section, and it looks like this:

[System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency); [System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceCounter(ref long PerformanceCount);

Now if we remove the word NativeMethods from in front of each of these calls the class will compile successfully. You could simply leave the code as it is now and be able to use the timer exactly the same way that the DirectX SDK demos and samples do, but I prefer to have an instanceable class so I can more easily keep track of which timer I am using. Converting this timer to a non-static one is simply a matter of removing all of the static declarations in front of the methods and instance variables. I am also going to change the comment structure a bit to save some space here in the tutorial, but feel free to leave the XML formatted comments as they are if you would rather. Here is what the final converted timer looks like:

using System.Runtime.InteropServices; namespace HMEngine { public class FrameworkTimer { private bool isUsingQPF; private bool isTimerStopped; private long ticksPerSecond; private long stopTime; private long lastElapsedTime; private long baseTime; public bool IsStopped { get { return isTimerStopped; } } // Returns true if timer stopped [System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency); [System.Security.SuppressUnmanagedCodeSecurity] // We won’t use this maliciously [DllImport("kernel32")] public static extern bool QueryPerformanceCounter(ref long PerformanceCount); public FrameworkTimer() { isTimerStopped = true; ticksPerSecond = 0; stopTime = 0; lastElapsedTime = 0; baseTime = 0; // Use QueryPerformanceFrequency to get frequency of the timer isUsingQPF = QueryPerformanceFrequency(ref ticksPerSecond); } public void Reset() { // Resets the timer if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); baseTime = time; lastElapsedTime = time; stopTime = 0; isTimerStopped = false; } public void Start() { // Starts the timer if(!isUsingQPF) return; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); if(isTimerStopped) baseTime += (time – stopTime); stopTime = 0; lastElapsedTime = time; isTimerStopped = false; } public void Stop() { // Stop (or pause) the timer if(!isUsingQPF) return; // Nothing to do if(!isTimerStopped) { // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); stopTime = time; lastElapsedTime = time; isTimerStopped = true; } } public void Advance() { // Advance the timer a tenth of a second if(!isUsingQPF) return; // Nothing to do stopTime += ticksPerSecond / 10; } public double GetAbsoluteTime() { // Get the absolute system time if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); double absolueTime = time / (double)ticksPerSecond; return absolueTime; } public double GetTime() { // Get the current time if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); double appTime = (double)(time – baseTime) / (double)ticksPerSecond; return appTime; } public double GetElapsedTime() { // Get time elapsed between GetElapsedTime() calls if(!isUsingQPF) return -1.0; // Nothing to do // Get either the current time or the stop time long time = 0; if(stopTime != 0) time = stopTime; else QueryPerformanceCounter(ref time); double elapsedTime = (double)(time – lastElapsedTime) / (double)ticksPerSecond; lastElapsedTime = time; return elapsedTime; } } }

A bit cleaner wouldn’t you say? Well, maybe not, but I sure think it is more readable this way. Anyway, now that we have a timer that we can use, why don’t we get to using it? I will put together a simple frames-per-second counter now just so you can see the timer in action.

Step 2: Using our Timer for FPS

The first thing we will need to do is create a new instance of our timer in the HMSystem code so we can use it to time the seconds that elapse between frames. This code is pretty simple, but I’ll show you where I put it anyway:

private Device myDevice; private HMCamera myCamera; private HMScene myScene; private FrameworkTimer myTimer; // Variables for FPS calculations/drawing private Microsoft.DirectX.Direct3D.Font frameFont; private int framesPassed; private float timeElapsed; private float fps; private int frameResetValue = 10; … if(InitializeGraphics()) { myCamera = new HMCamera(this.Width, this.Height); myScene = new HMScene(); myTimer = new FrameworkTimer(); myTimer.Start(); frameFont = new Microsoft.DirectX.Direct3D.Font( myDevice, new System.Drawing.Font(“Courier New”, 16, FontStyle.Bold) ); }

We are also going to need a function that acutally calculates this FPS value. There are many ways to do this, but I have found that one of the easiest and least buggy methods is to simply keep track of a number of frames and the amount of time that has passed since they have gone by and divide them. We also need to have the video card draw our FPS on the screen, so we will write a small function for that as well. Here are the two functions:

private void CalculateFPS() { framesPassed++; timeElapsed += (float)myTimer.GetElapsedTime(); if(framesPassed > frameResetValue) { fps = (float)framesPassed / (float)timeElapsed; framesPassed = 0; timeElapsed = 0; } } private void ShowFPS() { frameFont.DrawText(null, “FPS: “+fps.ToString(), new Point(0, 0), Color.White); }

Now we just need to add the calls to our new functions in the Render method and we will be able to see what speed our video card is rendering frames at. Here is what the last modification looks like:

private void Render(object sender, PaintEventArgs e) { SetDeviceCamera(); CalculateFPS(); myDevice.BeginScene(); myDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.CornflowerBlue, 1.0f, 0); myScene.Render(myDevice); ShowFPS(); myDevice.EndScene(); myDevice.Present(); this.Invalidate(); }

Finishing up our Camera

Friday, August 12th, 2005

I know you are all probably terribly angry with me for making this a separate tutorial, but honestly, I just haven’t had time to sit down and redo all of my math work again to get this half of it done until now. Hopefully, the quality and ease of use of this Camera in the future will make up for the long wait you all had to endure

Step 1: Brushing up on your trig

For starters, since this will be the tutorial that uses the most actual math out of almost everything I can think of that will go into this engine (besides particle effects and some more complicated shaders much later), I decided I would put together a bit of a section on trig for those of you who are new to it or a little rusty.

First of all (and forgive me if this seems very Jr. High but it is neccessary to know), all of the concepts of trigonometry start out with some basic ratios that refer to the lengths of the legs on triangles. For our purposes we will only need to use right triangles. These ratios are referred to as sine, cosine, and tangent, and are abbreviated sin, cos, tan respectively. Here is a picture explaining the ratios and which parts of the triangle they talk about.

Some simple trig ratios

On our right triangle we label each side (2 legs, and the hypotenuse) with the letters a,b, and c, and we label the angles opposite each side with its corrosponding greek letter alpha for a, beta for b, and gamma for c. Now, with these letters and symbols we can easily take our easy to remember formulas (SOH CAH TOA) and convert them into simple ratios to represent the values of sin, cos, and tan.

Using this knowledge it’s time to start designing some equations that we will use with our camera. Let’s start with the 3rd person equations and then we will move on tho add the 1st person ones later. I will try to put illustrations to this information, but it is slightly difficult to model 3D trig, especially when you aren’t the greatest artist in the world :)

Step 2: Putting together some 3rd person math

The first thing we must remember about a 3rd person camera is that the object we are looking at is stationary (relative to the camera position anyway) and that the camera simply rotates around it at some set distance (which will also be esaily changed later). Once we realize this, and we have a look at how a sphere is defined, we see that the cameras location around the 3d object can be specified by 3 simple pieces of information: the distance from the object (radius), the location on the circle going around the object (horizontal rotation), and the location on the circle going around in a perpendicular direction to the first one. Here is a picture that will hopefully show this a bit better.

3rd Person Camera Location

Once the camera has these three pieces of information, we can easily figure out all of the information we need to know to let the engine know what the current state of our camera is. A few things we need to mention here are that the target vector doesn’t need to change for our third person camera unless the object we are looking at moves, in which case we can change it, and that our up vector will only need to ever be (0,1,0) and (0,-1,0) depending on whether our camera is upside down or now (which is easy enough to figure out later.)

So all we have left to figure out is the camera’s position relative to our object which can then be translated by the objects own position in the 3D world to get the camera’s absolute position. To do this, we will need to use our simple trig formulas three times, once for each axis of positioning.

The first axis I started with to figure out these equations in the first place was the y-axis because some of the changes in position moving around that direction causes both the x and z values to be shortened a bit, and we can easily take that into account if we do the y rotation first. The equation for finding the y component of our camera’s position is found as follows: (refer to the image for more explaination)

sin(vRot) = y1/radius cos(vRot) = r1/radius After simple multiplication: y1 = radius*sin(vRot) r1 = radius*cos(vRot)

Now using the vertical rotation stored in our camera class and some simple trig, we have found the y1 component of our camera’s position and r1, which we will use as a new radius for the rest of our math since looking from the top down the camera will have moved closer to the center because of its movement vertically along the circle. All we have to do for the rest of the math is to use the same methods we did for finding the y1 component and get our final x1 and z1.

cos(hRot) = x1/newRadius sin(hRot) = z1/newRadius After simple multiplication: x1 = newRadius*cos(hRot) z1 = newRadius*sin(hRot)

Now if we plug these values into the position vector of our camera class and update the device, the scene will be drawn as if we were looking through this new position at the object in focus. Here is the code we will be adding to accomplish all of this

public void UpdatePosition() { // (radius * Math.Cos(vRotation)) is the temporary radius after the y component shift position.X = (float)(radius * Math.Cos(vRotation) * Math.Cos(hRotation)); position.Y = (float)(radius * Math.Sin(vRotation)); position.Z = (float)(radius * Math.Cos(vRotation) * Math.Sin(hRotation)); // Translate these coordinates by the target objects spacial location position += target; }

A lot shorter than you thought it would be isn’t it? Now that we have a way of converting rotation around an object to 3D position in space, we need a way to change that rotation, and the best way to accomplish this in my opinion is by adding some code to let the movement of the mouse control this. Add a simple rotate function to the camera that looks like this:

public void Rotate(float h, float v) { hRotation += h; vRotation += v; // We will do this after each type of camera movement UpdatePosition(); }

We also need a function to let us zoom in and out, which is actually just as simple as our rotation function and looks like this:

public void Zoom(float dist) { radius += dist; if(radius < .01f) radius = .01f; UpdatePosition(); }

Step 3: Putting it all to work

The very last thing to do to test out our new third person camera is to add a bit of input code to our Demo so the mouse movements will be sent to the camera and we can watch our code in action. Here are the changes that have to be added to the Demo class.

using System.Drawing; public class HMDemo { static Point lastMouseLoc = new Point(); public static void Main() { demo1.MouseMove += new MouseEventHandler(demo1_MouseMove); demo1.MouseWheel += new MouseEventHandler(demo1_MouseWheel); // Existing code } static void demo1_MouseMove(object sender, MouseEventArgs e) { Point delta = new Point(e.X – lastMouseLoc.X, e.Y – lastMouseLoc.Y); lastMouseLoc = new Point(e.X, e.Y); demo1.MyCamera.Rotate((float)delta.X * .05f, -(float)delta.Y * .05f); } static void demo1_MouseWheel(object sender, MouseEventArgs e) { demo1.MyCamera.Zoom(-e.Delta * .01f); } }

With this little snippet added in there you should be able to rotate the mouse around and watch the camera move. You may want to change the default values in the camerea class itself for the target (I made mine (0,0,0) and the radius (10)) just so you can get a better idea of how the camera moves so far.

If you put together and compiled everything we have so far you will notice that the camera seems to be only showing the tiger face up and when we go around where th camera should be upside-down it just switches it. This is because we aren’t also switching the up-vector when the angle of the camera is too great. Here are the lines we will add to UpdatePosition() to fix that:

// Keep all rotations between 0 and 2PI hRotation = hRotation > (float)Math.PI * 2 ? hRotation – (float)Math.PI * 2 : hRotation; hRotation = hRotation < 0 ? hRotation + (float)Math.PI * 2 : hRotation; vRotation = vRotation > (float)Math.PI * 2 ? vRotation – (float)Math.PI * 2 : vRotation; vRotation = vRotation < 0 ? vRotation + (float)Math.PI * 2 : vRotation; // Switch up-vector based on vertical rotation upVector = vRotation > Math.PI / 2 && vRotation < Math.PI / 2 * 3 ? new Vector3(0, -1, 0) : new Vector3(0, 1, 0);

That’s all we really need to create a great third person camera. You will probably notice a slight kick at the beginning of rotating with the mouse like we are, but that is because of our method of input and not the camera itself. Now we have a nice camera we can position and move around to get a better look at what we are playing with in our engine. We will add a lot of functions to this camera in the future, but for now we will just stick with this simple 3rd-Person rotating version.

The Beginnings of a Useful Camera

Sunday, July 31st, 2005

By now I am sure some of you are thinking, “Great, I can draw stuff, but how am I supposed to go around and look at it once it is drawn!?!” Maybe not with that much excitement, but I’m sure someone was thinking it. Anyway, the solution to this problem requires two things. A Camera class and an Input class that will let us tell the camera what to do. If you aren’t familiar with trigonometry and polar coordinates, then you should try to brush up on those concepts before attempting to learn much from this and the following tutorial. I wrote a camera class a while back that had an explanation of the math involved here.

I will be implementing this camera a bit differently since it will be both 1st and 3rd person, but the math involved is the same. If you think you know enough trig to get through this, or you have a new understanding of if after having read my article, then here we go!

Step 1: Implementing the Basic Camera Framework

Start out by creating a new class in your 3D engine project and name it something with the word Camera in it. Mine is going to be HMCamera. Since we are going to be using this camera to control all the aspects of our screen display, and possibly for some fun zoom effects or the like later, I figure we should store all of the variables that we used to initialize the camera in our System class earlier so this one class will give us full control over all of those aspects of our games. Here is the basic class with its member variables and a few useful properties:

public class HMCamera { private Vector3 position = new Vector3(0, 0, 0); // These are the most important values in private Vector3 target = new Vector3(0, 0, 1); // the camera class, and they are what private Vector3 upVector = new Vector3(0, 1, 0); // we make our final View matrix with private int screenWidth; private int screenHeight; // These values tell the camera how wide our view angle is and how close/far we can see private float nearClip = 1.0f; private float farClip = 100.0f; private float fov = (float)Math.PI / 4.0f; // We will use these in the math processes that control movement later private float hRotation = (float)Math.PI / 2.0f; private float vRotation = 0.0f; private float radius = 1.0f; // Public matrix properties that our device can use to set its own matrices before rendering public Matrix World { get { return Matrix.Identity; } } public Matrix View { get { return Matrix.LookAtLH(position, target, upVector); } } public Matrix Projection { get { return Matrix.PerspectiveFovLH( fov, (float)screenWidth / (float)screenHeight, nearClip, farClip ); } } public HMCamera(int width, int height) { screenWidth = width; screenHeight = height; } }

Now that we have a basic camera put into place we need to add the code in the main system part of our engine to use it. First we need to add our member variable for the camera and a public property so we can access it outside the main system, and add an initializer into the constructor of the System as well.

private HMCamera myCamera; public HMCamera MyCamera { get { return myCamera; } } // In the constructor if(InitializeGraphics()) { myScene = new HMScene(); myCamera = new HMCamera(this.Width, this.Height); myInput = new HMInput(this); }

The next thing we can do is delete the SetupCamera() function that created our matrices in the system class and replace it with a new one in the camera that looks like this:

private void SetDeviceCamera() { myDevice.Transform.World = myCamera.World; myDevice.Transform.View = myCamera.View; myDevice.Transform.Projection = myCamera.Projection; }

Now add a call to the SetDeviceCamera() function at the beginning of our system’s main Render function.

Rendering to the Screen, Simple Sprites

Saturday, June 11th, 2005

Now that we have the ability to startup and render with Direct3D let’s get to rendering something! I am going to keep these tutorial sets geared towards creating a full engine, so everything will be as object oriented as possible to make it easy to add to and use later. To start out, let’s create a file that will contain all of the code for objects our engine will render. I am calling mine HMObject.cs, but you can use whatever you want. Make sure you create this in the 3D Engine project and not the demo one

Step 1: Creating the basic object class

Start the file out by adding in the proper using and namespace code:

using System.Drawing;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace HMEngine { // Whatever your main namespace is }

Since we will be creating more than one kind of object, let’s create a class that holds all of the things each object will need to use so we can inherit from it later.

public class HMObject { protected Vector3 myPosition = new Vector3(0, 0, 0); // We will use these for protected Vector3 myRotation = new Vector3(0, 0, 0); // building our transformation protected Vector3 myScaling = new Vector3(1, 1, 1); // matrices in the camera later public virtual void ReloadResources(Device myDevice){} // This will be used for device resets public virtual void Render(Device myDevice){} // Basic render call for every object }

We will not be using those vectors until step 3, but I thought we should add them now since we will end up doing so later anyway.

Step 2: Rendering a simple sprite

The first useful object to start creating a game with is probably a sprite. Now I know sprites are only 2D and you are probably thinking “This is a 3D engine!! We don’t need sprites!”, but you would be surprised at what they might come in handy for later. That said I should inform you that DirectX already has a Sprite class that is very easy to use, but to cut down on our game coding time later, we will write a custom Sprite class to use in our games that will take care of many repetitive things for us. Here’s what my custom sprite class looks like:

public class HMSprite : HMObject { private Sprite mySprite; // Sprite object that does the rendering private Texture myTexture; // Texture object used for rendering private string myPath; // Path to the texture file so we can use on resets public HMSprite(string imagePath, Vector3 location, int width, int height, Device myDevice) { myPosition = location; // Keep location in inherited vector mySprite = new Sprite(myDevice); // Create a new sprite object myScaling = new Vector3(width, height, 1); // Keep size in inherited vector myPath = imagePath; // Store the texture’s path myTexture = TextureLoader.FromFile(myDevice, myPath); // Load our texture } // This will be called if the device resets and resources had to be acquired again public override void ReloadResources(Device myDevice) { myTexture = TextureLoader.FromFile(myDevice, myPath); } public override void Render(Device myDevice) { // Here we find the center of the texture dimensions Vector3 texCenter = new Vector3( myTexture.GetSurfaceLevel(0).Description.Width/2, myTexture.GetSurfaceLevel(0).Description.Height/2, 0 ); mySprite.Transform = Matrix.Transformation( new Vector3(), // Scaling Center, blank scales from the top corner new Quaternion(), // Scaling Rotation, blank to scale down and right (normally) // Scale ratio: newSize/texSize, since we stored half the size in texCenter we use texCenter*2 new Vector3(myScaling.X/(texCenter.X*2), myScaling.Y/(texCenter.Y*2),0), texCenter, // Rotation Center new Quaternion(), // Rotation amount new Vector3() // Translation amount (location) ); mySprite.Begin(SpriteFlags.AlphaBlend); // Start drawing with out sprite, and use Transparency // The parameters for the following .Draw call are: // myTexture – the Texture object to draw // new Vector3 – center to draw from, we are positioning from top left so use an empty vector // myLocation – the screen offset to draw at // Color.White – Color to modulate the texture with, this value keeps original colors mySprite.Draw(myTexture, new Vector3(), myPosition, Color.White.ToArgb()); mySprite.End(); // All done drawing } }

You will also need to add references to Microsoft.DirectX and Microsoft.DirectX.Direct3D to the demo project, and you need to add a reference to Miscrosoft.DirectX.Direct3DX as well since the Sprite class we are using is in that library.

In our sprite code, the first thing we did was create some storage spots for the sprites size, location, texture, and the actual sprite object DirectX will use to draw with. The constructor takes a path to the image we are using for our texture, the location in 3D space of the sprite (most of them will be at <X, Y, 0>), the width and height of the sprite we want to draw, and the device we will use to create the texture and sprite objects.

The path can be passed as relative or absolute in the filesystem. If you just want to pass something like “image1.jpg” to the constructor, then you will have to place the image file in the same directory that the .exe will be running from (in VC# this will be in the /bin folder of the Debug or Release sections of the demo application. You can use any standard image format you would like (.bmp, .gif, .jpg, .tga) and they can contain transparent sections as well.

Step 3: Adding a Simple Scene Graph

Now that we have an object to render, we need a good method to actually render it that we can extend for a more complicated use later. For this task we will create a very simple Scene class that we will add on to later when more complicated tasks come around (like frustum culling).

Start out by creating a new code file with the normal using and namespace statements and a using for System.Collections as well, and call it something similar to mine: HMScene. Here is our very, very simple scene class.

public class HMScene { ArrayList WorldObjects = new ArrayList(); public void AddObject(HMObject newObject) { WorldObjects.Add(newObject); } public void RemoveObject(HMObject oldObject) { WorldObjects.Remove(oldObject); } public void ReloadResources(Device myDevice) { foreach(HMObject o in WorldObjects) { o.ReloadResources(myDevice); } } public void Render(Device myDevice) { foreach(HMObject o in WorldObjects) { o.Render(myDevice); } } }

Ok, so we have objects and a scene, let’s add a scene object to our main system class and get it rendering. In the main system class with the rest of its instance variables (at the top) add a new scene variable and a way to access it from the outside:

private HMScene myScene = new HMScene(); public HMScene MyScene { get { return myScene; } } public Device MyDevice { get { return myDevice; } }

I also made the device available from outside the system. Now, add the call to render the scene into the main Render function between the Clear and Present calls, like this:

myDevice.BeginScene(); myScene.Render(myDevice); myDevice.EndScene();

That’s it for the engine code. Now all we have to do is add a sprite object to the scene in the demo code and watch it go. Here’s how you do that. In the Main() function of the demo add this:

HMSprite sprite1 = new HMSprite(“hmglogo.tga”, new Vector3(), 320, 240, demo1.MyDevice); demo1.MyScene.AddObject(sprite1);

Setting up your IDE

Wednesday, June 8th, 2005

The first thing we are going to learn how to do is get Visual C# .NET set up to create and run our engine demos in a manner that lets us keep the engine code separate from the demos themselves so we can be sure that our 3D engine’s file only contain code that is important to the engine itself, and not any of the many hundreds of uses it could eventually have.

The best method of accomplishing this, in my opinion, is to have multiple projects within Visual C# that contain the code for different sections. We could even have multiple projects for the actual game engine itself and have them compiled into different libraries for use later. I will keep the engine all together in one project for this tutorial series, but feel free to separate them how you see fit.

Step 1: Making the projects

Lets start by making two projects in Visual C#. One of these will contain the code or our engine and one will contain the demo code that will use the engine to create small and hopefully interesting apps that we can use to show o what we have created.

Start out by opening up Visual C# (VC# from now on so I don’t have to type so much) and selecting File-New-Project. For the first project we are going to create a Class Library, so click that option and name it whatever you would like to call the engine. I am using Hazy Mind 3D Engine for my name, but anything will work. Click ok, and then depending on how you have your VC# set up it may create a folder and save the solution to start out, or you may have to manually save it now.

Before we save the solution as is though, lets remove the default Class1.cs file that is created. Now, go to File-Save All (or Save Solution As) and a Save Project box will pop up. your porject’s name will appear in the Name: box and in the New Solution Name: box as well. I don’t mind having a soultion with the same name as one of its child projects, so this is how I will leave mine. Browse to the location you would like to save this solution and click save. I also have it create a directory for my solution just to keep things organized.

Now that we have our engine library created, we have to make a project that will use this library and run our demos. We will accomplish this by right-clicking on our solution name in the Soltion Explorer window and choosing Add-New Project… Click Windows Application and name it something appropriate. I will call mine Hazy Mind Demo. Once again (because I like to start from scratch) lets remove the default files that were created in this new project and save the solution.

Now there is only one thing left to do to get this setup ready to run our engine, and that is to choose the correct start up project to use when we are debugging later. To do this, right click on your Windows Application project and choose Set As Startup Project.

That’s it! Once you have this all set up you are ready to follow the rest of the tutorials. Good luck and happy learning.