0% found this document useful (0 votes)
308 views

DirectX Tutorial For C

The document provides an overview of a DirectX tutorial for C# that introduces programming DirectX using C#. It explains how to set up the development environment in C# and initialize a DirectX device, which provides direct access to the graphics hardware. The tutorial is divided into multiple chapters that will cover basic DirectX features such as drawing shapes, cameras, and lighting effects.

Uploaded by

xljubax
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
308 views

DirectX Tutorial For C

The document provides an overview of a DirectX tutorial for C# that introduces programming DirectX using C#. It explains how to set up the development environment in C# and initialize a DirectX device, which provides direct access to the graphics hardware. The tutorial is divided into multiple chapters that will cover basic DirectX features such as drawing shapes, cameras, and lighting effects.

Uploaded by

xljubax
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 21

DirectX Tutorial for C# overview

This part of the site focuses on programming DirectX using C#. This tutorial is aimed at people who haven't done any 3D programming so far and would like to see some results in the shortest possible time. To this end, C# is an ideal programming language. C# looks very much like Java, so anyone having some notions of Java should be able to start right away. Even more, this tutorial is written in such a way that anyone who has any programming experience should be able to understand and complete it. The C# tutorial gives you a general introduction to DirectX. It is divided in several chapters, which you can find listed below. In every chapter youll find a basic DirectX feature: Opening a window: setting up and using the Development Environment Linking to the device: Creating the most basic DirectX element, the device Drawing a triangle: defining points, displaying them using DirectX Camera: defining points in 3D space, defining camera position Rotation & translation: rotating and translating the scene Indices: removing redundant vertex information to decrease AGP/PCIX bandwidth Terrain/Landscape: using indices to display data read from a file Keyboard: read user input on the keyboard through DirectInput Importing bmp files: change your terrain from within Paint! Colored vertices: add simple color to you terrain DirectX light basics: lighting can be complex to fully understand it, a whole chapter Mesh creation: putting your buffers together into a powerful new format Mesh lighting: using the Mesh format to compute complex data needed for lighting

Opening your project window


Welcome to the first entry of the Managed DirectX 9 Tutorial. This tutorial is aimed at people who haven't done any 3D programming so far and would like to see some results in the shortest possible time. To this end, C# is an ideal programming language. C# is a relatively easy language, so anyone who has some basic programming experience should be able to start right away. Even more, this tutorial is written in such a way that anyone who has any programming experience should be able to understand and complete it. The required software to start writing your own DirectX 9 code : A C# programming environment. Popular is Visual Studio .NET, but you can also download the free evaluation version of Borland C# Builder. Recently, Microsoft has released a free version of Visual Studio, Visual Studio Express, which you can download here: (link) The .NET framework. (free) (link) And of course the DirectX9 SDK. (free) (link) With all this software installed, you can start a new C# Application. To do this in Visual Studio, click the File menu and select New. As project type, we of course need Visual C#. Now on the right, you can select Windows Application. Fill in the name, and hit the OK button! Now you should see a small empty form. To switch to the code, find Program.cs at the right of your screen. Right click on it and select View code. Next, you should add a reference to Microsoft.DirectX and to Microsoft.DirectX.Direct3D. This is how you do it: in Visual Studio, go to the Project menu, and select Add reference. In the list that comes up, pick Microsoft.DirectX and Microsoft.DirectX.Direct3D. Be sure NOT to select DirectX version 2.0. Also add the following lines to you using-block, so your program can use the referenced libraries: using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; Compiling this will already give you an empty form. Now we want to change the size and the title of it. To do this, simply find the InitializeComponent() method and change the size to (500,500) and the title

(= this.Text) to "DirectX Tutorial" or whatever you like. Next we are going to change the Main() method a bit: static void Main() { using (WinForm our_directx_form = new WinForm()) { Application.Run(our_directx_form); } } You are now ready to start programming with DirectX! Compiling this code should give you a small form.

After each chapter I will list the whole code so far, so this is what you should have by now : using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using Microsoft.DirectX; using Microsoft.DirectX.Direct3D; namespace DirectX_Tutorial { public class WinForm : System.Windows.Forms.Form

{ private Device device; private System.ComponentModel.Container components = null; public WinForm() { InitializeComponent(); } protected override void Dispose (bool disposing) { if (disposing) { if (components != null) { components.Dispose(); } } base.Dispose(disposing); } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.Size = new System.Drawing.Size(500,500); this.Text = "DirectX Tutorial"; } static void Main() { using (WinForm our_dx_form = new WinForm()) { Application.Run(our_dx_form); } } } }

Initializing the Device


In this chapter, we are going to create the device. In short, a device is a direct link to your graphical adapter. It is an object that gives you direct access to the piece of hardware inside your computer. To start, you have to declare the device as a variable within your WinForm-class. Just add this line as the first line in your class : private Device device; Now, we are going to create a method InitializeDevice() to activate the device. Here's the code, I'll explain it afterwards: public void InitializeDevice() { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Discard; device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams); }

The first line creates the Presentation Parameters, which we will need to tell the device how to behave. We will tell the device that we don't want a fullscreen application, and to discard the Swapeffect, so you write to the device immediately, and not to an extra back buffer that will be presented (= swapped) at runtime. Then, the device is created. The 0 selects the first graphical adapter in your PC. We want to render the graphics using the hardware. If you don't have a hardware card, you can use DeviceType.Reference, which supports all possible features, but is a lot slower. Next we bind 'this' window to the device and we for now we want all 'vertex processing' to happen on the CPU. More on vertices in the next chapter. Finally we pass our presentParams, and our device has been created! Of course, we have to call this method, so add this line to your Main method : our_dx_form.InitializeDevice(); When you try to compile your program now, youll probably get some errors like The type or namespace name 'DirectX' does not exist in the namespace. Make sure you add references to Microsoft.DirectX and Microsoft.Direct3D by clicking Project -> Add References to correct these errors. If you have multiple versions installed of these references, make sure you select references of the same version. If these 2 references arent included in your list, check if you have installed the full (free) Microsoft DirectX SDK. If theyre still missing, check out the first paragraphs of Linking to the Device in the C++ part of this tutorial. Running this code will still give you an empty form, but in the background the device has been initialized! Next we are going to overwrite the OnPaint method, so we can control what to draw on the screen. Add the following code below the InitializeDevice method: protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { device.Clear(ClearFlags.Target, Color.DarkSlateBlue , 1.0f, 0); device.Present(); } This method will be called every time something is drawn to the screen. The Clear method will fill the window with a solid color, darkslateblue in our case. The ClearFlags indicate what we actually want to clear, in our case the target window. To actually update our display, we have to Present the updates to the device. Running this code will give you a blueish window. For even more stunning results, read on!

Initializing the Device


In this chapter, we are going to create the device. In short, a device is a direct link to your graphical adapter. It is an object that gives you direct access to the piece of hardware inside your computer. To start, you have to declare the device as a variable within your WinForm-class. Just add this line as the first line in your class : private Device device; Now, we are going to create a method InitializeDevice() to activate the device. Here's the code, I'll explain it afterwards: public void InitializeDevice() { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Discard; device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams); } The first line creates the Presentation Parameters, which we will need to tell the device how to behave. We will tell the device that we don't want a fullscreen application, and to discard the Swapeffect, so you write to the device immediately, and not to an extra back buffer that will be presented (= swapped) at runtime. Then, the device is created. The 0 selects the first graphical adapter in your PC. We want to render the graphics using the hardware. If you don't have a hardware card, you can use DeviceType.Reference, which supports all possible features, but is a lot slower. Next we bind 'this' window to the device and we for now we want all 'vertex processing' to happen on the CPU. More on vertices in the next chapter. Finally we pass our presentParams, and our device has been created! Of course, we have to call this method, so add this line to your Main method : our_dx_form.InitializeDevice(); When you try to compile your program now, youll probably get some errors like The type or namespace name 'DirectX' does not exist in the namespace. Make sure you add references to Microsoft.DirectX and Microsoft.Direct3D by clicking Project -> Add References to correct these errors. If you have multiple versions installed of these references, make sure you select references of the same version. If these 2 references arent included in your list, check if you have installed the full (free) Microsoft DirectX SDK. If theyre still missing, check out the first paragraphs of Linking to the Device in the C++ part of this tutorial. Running this code will still give you an empty form, but in the background the device has been initialized! Next we are going to overwrite the OnPaint method, so we can control what to draw on the screen. Add the following code below the InitializeDevice method: protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { device.Clear(ClearFlags.Target, Color.DarkSlateBlue , 1.0f, 0);

device.Present(); } This method will be called every time something is drawn to the screen. The Clear method will fill the window with a solid color, darkslateblue in our case. The ClearFlags indicate what we actually want to clear, in our case the target window. To actually update our display, we have to Present the updates to the device. Running this code will give you a blueish window. For even more stunning results, read on!

Here is the complete code: using using using using using using using using System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data; Microsoft.DirectX; Microsoft.DirectX.Direct3D;

namespace DirectX_Tutorial { public class WinForm : System.Windows.Forms.Form { private Device device; private System.ComponentModel.Container components = null;

public WinForm() { InitializeComponent(); } public void InitializeDevice() { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Discard; device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams); } protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { device.Clear(ClearFlags.Target, Color.DarkSlateBlue , 1.0f, 0); device.Present(); } protected override void Dispose (bool disposing) { if (disposing) { if (components != null) { components.Dispose(); } } base.Dispose(disposing); } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.Size = new System.Drawing.Size(500,500); this.Text = "DirectX Tutotial"; } static void Main() { using (WinForm our_dx_form = new WinForm()) { our_dx_form.InitializeDevice(); Application.Run(our_dx_form); } } } }

Drawing your first Triangle


This chapter will cover the basics of drawing. First a few things you should know. Every object drawn by Direct3D is drawn using triangles. Surprisingly enough, a triangle is defined by 3 points. Every point is defined by a vector, specifying the X, Y and Z coordinates of the point. However, just knowing the coordinates of a point might not be enough. For example, you might want to define a color for the given point as well. This is where a vertex (pl. vertices) comes in: it is the list of properties of a given point, including the position, color and so on. DirectX has a construct that fits perfectly to hold our vertex information: the CustomVertex class. Just add the following code to the beginning of the OnPaint method : CustomVertex.TransformedColored[] vertices = new CustomVertex.TransformedColored[3]; vertices[0].Position = new Vector4(150f, 100f, 0f, 1f); vertices[0].Color = Color.Red.ToArgb(); vertices[1].Position = new Vector4(this.Width/2+100f, 100f, 0f, 1f); vertices[1].Color = Color.Green.ToArgb(); vertices[2].Position = new Vector4(250f, 300f, 0f, 1f); vertices[2].Color = Color.Yellow.ToArgb(); The first line creates an array to hold the information for 3 vertices. TransformedColored means the coordinates of the points will be screen coordinates and each of the points can have its own color. Next we fill in the position and information for 3 points. The'f' behind the numbers simply convert the integers to floats, the expected format. Don't pay attention to the 4th coordinate for now. All we have to do now is tell the device to paint the triangle! Right after the Clear call, add these lines: device.BeginScene(); device.EndScene(); The first line tells the device the were going to build the 'scene'. The scene is the whole world of objects the device has to display. The last line indicates the end of the scene definition. The following lines will define our scene (that currently only consists of 1 simple triangle), so add them between the BeginScene and EndScene calls: device.VertexFormat = CustomVertex.TransformedColored.Format; device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, vertices); First we tell the device what kind of vertex information to expect. The last line actually draws the triangle. The first argument indicates that a list of separate triangles is coming. If you would draw 4 triangles, you would need a vertex array of 12 vertices. Another possibility is to use a TriangleStrip, which can perform a lot faster, but is only useful to draw triangles that are connected to each other. More on the TriangleStrip in C# Series 3 : "Triangle Strip". That's all there is to it! Running this code will already give you a colorful triangle on a blue background. Feel free to experiment with the colors and the coordinates. There still is a problem you might encounter while resizing the window. Try adjusting the width of the window. Sometimes, Windows doesn't consider resizing a window important enough to repaint the

whole window. To solve the problem, simply add the following line to the bottom of your OnPaint method: this.Invalidate(); You should also add the following line to the constructor of your form: this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);

Much better. I've listed the total code below : using using using using using using using using System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data; Microsoft.DirectX; Microsoft.DirectX.Direct3D;

namespace DirectX_Tutorial { public class WinForm : System.Windows.Forms.Form { private Device device; private System.ComponentModel.Container components = null; public WinForm() { InitializeComponent(); this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); } public void InitializeDevice() { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Discard; device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams); } protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { CustomVertex.TransformedColored[] vertices = new CustomVertex.TransformedColored[3]; vertices[0].Position = new Vector4(150f, 100f, 0f, 1f); vertices[0].Color = Color.Red.ToArgb(); vertices[1].Position = new Vector4(this.Width/2+100f, 100f, vertices[1].Color = Color.Green.ToArgb(); vertices[2].Position = new Vector4(250f, 300f, 0f, 1f); vertices[2].Color = Color.Yellow.ToArgb(); 0); device.Clear(ClearFlags.Target, Color.DarkSlateBlue , 1.0f, device.BeginScene(); device.VertexFormat = CustomVertex.TransformedColored.Format; device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, vertices); device.EndScene(); device.Present(); this.Invalidate(); } protected override void Dispose (bool disposing) { if (disposing) { if (components != null) { components.Dispose(); } }

0f, 1f);

base.Dispose(disposing); } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.Size = new System.Drawing.Size(500,500); this.Text = "DirectX Tutorial"; } static void Main() { using (WinForm our_directx_form = new WinForm()) { our_directx_form.InitializeDevice(); Application.Run(our_directx_form); } }

} }

World Space coordinates and camera view


Last chapter we drew a triangle, using 'transformed' coordinates. These coordinates are already 'transformed' so you can directly specify their position on the screen. However, you will usually use the untransformed coordinates, the so called World space coordinates. These allow you to create a whole scene using simple 3D coordinates, and, most important, to position a camera and set its viewing point. So we'll start with redefining our triangle coordinates in world space. Replace the code in your OnPaint method with this code: CustomVertex.PositionColored[] vertices = new CustomVertex.PositionColored[3]; vertices[0].Position = new Vector3(0f, 0f, 0f); vertices[0].Color = Color.Red.ToArgb(); vertices[1].Position = new Vector3(10f, 0f, 0f); vertices[1].Color = Color.Green.ToArgb(); vertices[2].Position = new Vector3(5f, 10f, 0f); vertices[2].Color = Color.Yellow.ToArgb(); All you've done here is changed the format from pre-transformed coordinates to 'normal' coordinates. Also let the device know your format has changed : device.VertexFormat = CustomVertex.PositionColored.Format; Let's run this code. Very nice, your triangle has disappeared again. Why's that ? Easy, because you haven't told DirectX yet where to position the camera and where to look at! To position your camera, simply add the following code as the first lines in your OnPaint method : device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI/4, this.Width/this.Height, 1f, 50f);

device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,30), new Vector3(0,0,0), new Vector3(0,1,0)); The first line tells the device what and how the camera should look at the scene. The first parameter sets the view angle, 90 in our case. The we set the view aspect ratio, which is 1 in our case, but will be different if our window is a rectangle instead of a square. The last parameters define the view range. Any objects closer to the camera than 1f will not be shown. Any object farther than 50f won't be shown either. These distances are called the near and the far clipping planes, since all objects between these planes will be clipped (=not drawn). The second line actually positions the camera. The first parameter defines the position. We position it 30 units above our (0,0,0) point, the origin. The next parameter sets the target point the camera is looking at. We will be looking at our origin. At this point, we have defined the viewing axis of our camera, but we can still rotate our camera around this axe. So we still need to define which vector will be considered as 'up'. Now run the code again. This time, we see a triangle, but it's all black! This is because world space is a little more advanced than our previous chapter. Here we are also required to place some lights. However, these will be handled in a following chapter, thus here we will simply tell our device not to expect any lights. Add the following line underneath your camera definition and you'll see our colored triangle again: device.RenderState.Lighting = false; Now everything has been set to use world space coordinates. One thing you should notice: you'll see the green corner of the triangle on the LEFT side of the window, while you defined it on the POSITIVE x-axis. This is because DirectX uses left-handed coordinates!! So, if you would position your camera on the negative z-axis: device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,-30), new Vector3(0,0,0), new Vector3(0,1,0)); you would expect to see the green point in the right half of the window. Try to run this now. This might again not be exactly what you expected. Something very important has happened. DirectX only draws triangles that are facing the camera. DirectX defines that triangles facing the camera should be drawn clockwise relative to the camera. If you position the camera on the negative z-axis, the triangle will be defined counter-clockwise relative to the camera, and thus will not be drawn! One way to remove this problem is simply redefining the vertices clockwise (this time clockwise relative to our camera on the negative part of the Z axis) :

vertices[2].Position = new Vector3(0f, 0f, 0f); vertices[2].Color = Color.Red.ToArgb(); vertices[0].Position = new Vector3(5f, 10f, 0f); vertices[0].Color = Color.Yellow.ToArgb(); vertices[1].Position = new Vector3(10f, 0f, 0f); vertices[1].Color = Color.Green.ToArgb();

This will indeed draw the triangle with the green point to the right. The other way is to add the following line after you camera definition :

device.RenderState.CullMode = Cull.None;

This will simply draw all triangles, even those not facing the camera. You should note that this should never be done in a final product, because it slows down the drawing process, as all triangles will be drawn, even those not facing the camera! However, while designing, it is wise to turn off culling, so you'll always see everything you draw. Also, I chose the background color to be non-black, again because if your triangle might be wrongly defined and drawn black, you'ld still see it. So, while designing, you should turn culling off and set a non-black background color.

You can find some more information on culling in the Culling chapter of the C++ part of this tutorial, where I have made a small animation to visualize the culling process. Here's the complete code again : using using using using using using using System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data; Microsoft.DirectX;

using Microsoft.DirectX.Direct3D; namespace DirectX_Tutorial { public class WinForm : System.Windows.Forms.Form { private Device device; private System.ComponentModel.Container components = null; public WinForm() { InitializeComponent(); this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); } public void InitializeDevice() { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Discard; device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams); } protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI/4, this.Width/this.Height, 1f, 50f); device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,-30), new Vector3(0,0,0), new Vector3(0,1,0)); device.RenderState.Lighting = false; device.RenderState.CullMode = Cull.None; CustomVertex.PositionColored[] vertices = new CustomVertex.PositionColored[3]; vertices[0].Position = new Vector3(0f, 0f, 0f); vertices[0].Color = Color.Red.ToArgb(); vertices[1].Position = new Vector3(10f, 0f, 0f); vertices[1].Color = Color.Green.ToArgb(); vertices[2].Position = new Vector3(5f, 10f, 0f); vertices[2].Color = Color.Yellow.ToArgb(); device.Clear(ClearFlags.Target, Color.DarkSlateBlue , 1.0f, 0); device.BeginScene(); device.VertexFormat = CustomVertex.PositionColored.Format; device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, device.EndScene(); device.Present(); } this.Invalidate();

vertices);

protected override void Dispose (bool disposing)

{ if (disposing) { if (components != null) { components.Dispose(); } } base.Dispose(disposing); } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.Size = new System.Drawing.Size(500,500); this.Text = "DirectX Tutorial"; } static void Main() { using (WinForm our_directx_form = new WinForm()) { our_directx_form.InitializeDevice(); Application.Run(our_directx_form); } }

} }

Rotations and translations


This chapter will make your triangle spin around. Since we are using world space coordinates, this is very easy. Let's first add a variable 'angle' to our class to store the current rotation angle. Just add this one to your variables. private float angle = 0f; Now, increase this variable with 0.05f every time OnPaint is called. Add this to the end of the OnPaint class: angle += 0.05f; With our angle increasing automatically, all we have to do is to rotate the world coordinates. I hope you remember from your maths class this is done using transformation matrices ;) Luckily, all you have to do is specify the rotation axis and the rotation angle. All the rest is done by Direct3D! You should add the following line right before you actually draw the triangle, this is before the call to device.DrawUserPrimitives : device.Transform.World = Matrix.RotationZ(angle); When you run the application, you will see that your triangle is spinning around its (0,0,0) point as expected! But imagine we would like to spin it through the center. One possibility is to redefine the point so the (0,0,0) would be in the center of our triangle. The better solution would be to first move (=translate) the triangle a bit to the left and down, and then rotate it. To do this, simply first multiply

your World matrix with a translation matrix : device.Transform.World = Matrix.Translation(-5,10*1/3,0)*Matrix.RotationZ(angle); This will move the triangle so the (0,0,0) point is positioned in the gravity point of the triangle. Then our triangle is rotated around this point, giving us the desired result. Please note the order of transformations. Go ahead and place the translation AFTER the rotation. You will see a triangle rotation around one point, moved to the left and below. You can easily change the code to make the triangle rotate around the Y or Z axis. Make sure to try one of them, to get the first feeling of 3D. A bit more complex is the Matrix.RotateAxis, where you first specify your own custom rotation axis : device.Transform.World = Matrix.Translation(-5,10*1/3,0)*Matrix.RotationAxis(new Vector3(angle*4,angle*2,angle*3), angle); This will make our triangle spin around an every changing axe. Before starting next chapter, let's clear things up a bit. Put the camera positioning in a new method CameraPositioning() and the vertex declaration in VertexDeclaration(), as you can find in the code at the bottom of the page. Since both of them need to be declared only once, we'll only call them from our Main method, so the OnPaint method will go faster. Don't forget to make vertices a variable in your class by adjusting its definition and placing this line in the top of your class : private CustomVertex.PositionColored[] vertices; Here's the code: using using using using using using using using System; System.Drawing; System.Collections; System.ComponentModel; System.Windows.Forms; System.Data; Microsoft.DirectX; Microsoft.DirectX.Direct3D;

namespace DirectX_Tutorial { public class WinForm : System.Windows.Forms.Form { private Device device; private System.ComponentModel.Container components = null; private float angle = 0f; private CustomVertex.PositionColored[] vertices; public WinForm() { InitializeComponent(); this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); } public void InitializeDevice() { PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Discard; device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams); }

private void CameraPositioning() { device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI/4, this.Width/this.Height, 1f, 50f); device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,-30), new Vector3(0,0,0), new Vector3(0,1,0)); device.RenderState.Lighting = false; device.RenderState.CullMode = Cull.None; } private void VertexDeclaration() { vertices = new CustomVertex.PositionColored[3]; vertices[0].Position = new Vector3(0f, 0f, 0f); vertices[0].Color = Color.Red.ToArgb(); vertices[1].Position = new Vector3(10f, 0f, 0f); vertices[1].Color = Color.Green.ToArgb(); vertices[2].Position = new Vector3(5f, 10f, 0f); vertices[2].Color = Color.Yellow.ToArgb(); } protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { device.Clear(ClearFlags.Target, Color.DarkSlateBlue , 1.0f, 0); device.BeginScene(); device.VertexFormat = CustomVertex.PositionColored.Format; device.Transform.World = Matrix.Translation(-5,10*1/3,0)*Matrix.RotationAxis(new Vector3(angle*4,angle*2,angle*3), angle); device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, vertices); device.EndScene(); device.Present(); this.Invalidate(); angle += 0.05f; } protected override void Dispose (bool disposing) { if (disposing) { if (components != null) { components.Dispose(); } } base.Dispose(disposing); } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.Size = new System.Drawing.Size(500,500); this.Text = "DirectX Tutorial"; }

static void Main() { using (WinForm our_directx_form = new WinForm()) { our_directx_form.InitializeDevice(); our_directx_form.CameraPositioning(); our_directx_form.VertexDeclaration(); Application.Run(our_directx_form); } } } }

Recycling vertices using inidices


-- Small note: If your hardware is rather old, or if your pc isnt equipped with a real graphics card, its possible that running this chapter will give you an empty window. If this is the case, you can let your CPU do all the graphics operations for you. This will allow you to use all DirectX commands, but a CPU is a lot slower than a graphics card for this. You can switch to CPU processing by using this code: device = new Device(0, DeviceType.Reference, this, CreateFlags.SoftwareVertexProcessing, presentParams); Instead of this: device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams); -- End of note -The triangle was nice, but what about a lot of triangles ? You would need to specify 3 vertices for each triangle. Consider next example:

Only 4 out of 6 vertices are unique. So the other 2 are simply a waste of bandwidth to your graphics card! It would be better to define the 4 vertices in an array from 0 to 3, and to define triangle 1 as vertices 1,2 and 3 and triangle 2 as vertices 2,3 and 4. This way, the complex vertex data is not duplicated. This is exactly the idea behind IndexBuffers. Suppose we would like to draw these 2 triangles :

Normally we would have to define 6 vertices, now only 5. So change our VertexDeclaration method as follows: private void VertexDeclaration() { vb = new VertexBuffer(typeof(CustomVertex.PositionColored), 5, device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionColored.Format, Pool.Default); vertices = new CustomVertex.PositionColored[5]; vertices[0].Position = new Vector3(0f, 0f, 0f); vertices[0].Color = Color.White.ToArgb(); vertices[1].Position = new Vector3(5f, 0f, 0f); vertices[1].Color = Color.White.ToArgb(); vertices[2].Position = new Vector3(10f, 0f, 0f); vertices[2].Color = Color.White.ToArgb(); vertices[3].Position = new Vector3(5f, 5f, 0f); vertices[3].Color = Color.White.ToArgb(); vertices[4].Position = new Vector3(10f, 5f, 0f); vertices[4].Color = Color.White.ToArgb(); } vb.SetData(vertices, 0 ,LockFlags.None);

The middle part of this method is easy: we simply define our 5 needed vertices to draw the 2 triangles. However, the first and last lines are new. The first one creates a new VertexBuffer, which will later be needed to link our indices to. The first argument tells the buffer what vertex format to expect. Then the numbers of vertices, our device and some default settings. The last line links our vertex array to the vertex buffer. Of course, you first have to declare vb before this will compile : private VertexBuffer vb; You can already declare our array of indices we are going to fill next, togerther with its IndexBuffer, ib : private int[] indices; private IndexBuffer ib; Next, create this IndicesDeclaration method: private void IndicesDeclaration() { ib = new IndexBuffer(typeof(int), 6, device, Usage.WriteOnly, Pool.Default); indices = new int[6]; indices[0]=3; indices[1]=1; indices[2]=0;

indices[3]=4; indices[4]=2; indices[5]=1; ib.SetData(indices, 0, LockFlags.None); } As with our VertexDefinition method, the first line declares the buffer that will be used to draw triangles from. As you can see, we now will need 6 indices, since 1 triangle is defined by 3 indices. Next, our indices array is initiated. As you can see, vertex number 1 is used twice, this was our initial goal. In this case, the profit is rather small, but in real-life application (as you will see soon ;)this is the way to go. Also note that the triangles have been defined in a clockwise order again, so DirectX will see them as facing the camera. The last line attaches the array to the buffer. -- NOTE: Andy Beatty pointed to me this chapter wouldnt run on his PC. After some digging, he found this might be a limitation of his graphics card, which has a maximum indexbuffer size of 65.543 indices. Instead of using an index buffer consisting of integers (int), he used the type short, making this chapter run on his computer! Thanks Andy for this info! -Make sure to call this method from our Main method : our_directx_form.IndicesDeclaration(); All that's left for this chapter is to draw the triangles from our buffer! Make the following changes to your OnPaint method : device.BeginScene(); device.VertexFormat = CustomVertex.PositionColored.Format; device.SetStreamSource(0, vb, 0); device.Indices = ib; device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 5, 0, 2); device.EndScene(); You still have to indicate what format is to be expected. Then you set your sources, the VertexBuffer and the IndexBuffer. Finally, you call the DrawIndexedPrimitives method. We still offer a list of separate triangles. The first zero indicates at which index to start counting in your indexbuffer. Then you indicate the minimum amount of used indices. We give 0, which will bring no speed optimization. Then the amount of used vertices and the starting point in our vertexbuffer. Finally, we have to indicate how many primitives (=triangles) we want to be drawn. That's it! When you run the program, you'll see 2 white triangles next to each other. Try putting this line directly after your device creation : device.RenderState.FillMode = FillMode.WireFrame;

This will only draw the lines of our triangles, instead of solid triangles.

You might also like