Computer graphics -- 2008-2009 -- info.uvt.ro/Laboratory 14

From Wikiversity

Quick links: front; laboratories agenda, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, JOGL template.


Brief introduction in Managed DirectX 9[edit]

Overview[edit]

In this lab we will discuss some general issues concerning with the creating of a simple DX scene and the draw of certain elements inside of it. The code will be written in C# as it closely resembles Java.

NOTE: This lab is only intended to provide a quick intro to Managed DX and comparison with OGL and must not be seen as a tutorial by itself.

Creating a simple Hello World application[edit]

Before proceeding we need to add references to the DirectX libraries which we are going to use. For this go to the Project menu, and select Add reference. In the list that comes up, pick Microsoft.DirectX and Microsoft.DirectX.Direct3D. Also add the following lines to you using-block, so your program can use the referenced libraries:


[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new 	HelloDirectX();
			frm.Show();

			Application.run(frm);
		}

		[...]
	}
}

Each Managed DX application basically requires:

  • an initialization method where linking to the Direct3D.Device object is accomplished (similar to the OGL init function)
  • a rendering method where the scene is being handled (similar with the OGL display function)
  • a method handling device resets. This event occurs for example when the window is being reshaped (similar with the OGL reshape function)

Additionally we could require some user interaction by using the mouse, the keyboard, the joystick etc.

Initialization of the DX device[edit]

Initialization of the DX device is usually done inside a custom built method which needs to be called before rendering the scene. The next fragment of code shows how we can achieve this:

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		public void Init()
		{
			// Do we support hardware vertex processing? If so, use it. 
			// If not, downgrade to software.
			Caps caps = Direct3D.Manager.GetDeviceCaps(Direct3D.Manager.Adapters.Default.Adapter,
					Direct3D.DeviceType.Hardware);
			CreateFlags flags;

			if (caps.DeviceCaps.SupportsHardwareTransformAndLight)
				flags = CreateFlags.HardwareVertexProcessing;
			else
				flags = CreateFlags.SoftwareVertexProcessing;

			// Everything checks out - create a simple, windowed device.
			PresentParameters d3dpp = new PresentParameters();

			d3dpp.BackBufferFormat = Format.Unknown;
			d3dpp.SwapEffect = SwapEffect.Discard;
			d3dpp.Windowed = true;
			d3dpp.EnableAutoDepthStencil = true;
			d3dpp.AutoDepthStencilFormat = DepthFormat.D16;
			d3dpp.PresentationInterval = PresentInterval.Immediate;

			device = new Direct3D.Device(0, Direct3D.DeviceType.Hardware, this, flags, 
					d3dpp);			
		}

		[...]
	}
}

Rendering[edit]

Rendering a DX scene requires overriding the OnPaint method:

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		public void Init()
		{
			[...]		
		}

		private void Render()
		{
			// Now we can clear just view-port's portion of the buffer to black
			device.Clear(ClearFlags.Target | ClearFlags.ZBuffer,
					Color.FromArgb(255, 0, 0, 0), 1.0f, 0);

			device.BeginScene();
				// Place your code here.
			device.EndScene();

			try
			{
				device.Present();
			}
			catch (DeviceLostException)
			{
				device.Reset(device.PresentationParameters);
			}
		}

		// We need to override this method so that we can control what is being drawn on the screen.
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			this.Render();
		}

		[...]
	}
}

Device resetting[edit]

The OnResetDevice event handler is a good place to create and initialize any Direct3D related objects, which may become invalid during a device reset:

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		public void Init()
		{
			[...]		
		}

		private void Render()
		{
			[...]		
		}

		// We need to override this method so that we can control what is being drawn on the screen.
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			this.Render();
		}

		// Event handler for managing the device reset instance.
		public void OnResetDevice(object sender, EventArgs e)
		{
			// Reinitialize the DX device.
			Direct3D.Device device = (Direct3D.Device)sender;

			// Reinitialize some parameters:
			device.RenderState.CullMode = Cull.None;
			device.RenderState.FillMode = FillMode.Solid;
			device.RenderState.Lighting = true;
		}
		[...]
	}
}

Projection and ModelView in Managed DirectX[edit]

As (J)OGL, DX also comes equipped with methods for dealing with setting up a projection, model (world) and view matrix. Both of their methods are quite similar:

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		public void Init()
		{
			[...]		
		}

		private void Render()
		{
			// Set the projection to be Perspective.
			device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI/4,
					this.Width/this.Height, 1f, 50f);
			// Orient the camera.
			device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,-30), new Vector3(0,0,0), 
					new Vector3(0,1,0));
			// Reset the world matrix to Identity.
			device.Transform.World = Matrix.Identity;
			[...]		
		}

		// We need to override this method so that we can control what is being drawn on the screen.
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			this.Render();
		}

		// Event handler for managing the device reset instance.
		public void OnResetDevice(object sender, EventArgs e)
		{
			[...]
		}
		[...]
	}
}

NOTE: The previous code created a perspective projection. Additionally one can create an orthographic one by using the Matrix.OrthoOffCenterLH, Matrix.OrthoLH methods or their RH (right hand) counterparts.

The model matrix can be manipulated in the same way as in (J)OGL by applying rotations, translations and scaling on the scene objects. The following fragment of code shows how we can achieve these:

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		public void Init()
		{
			[...]		
		}

		private void Render()
		{
			// Set the projection to be Perspective.
			device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI/4,
					this.Width/this.Height, 1f, 50f);
			// Orient the camera.
			device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,-30), new Vector3(0,0,0), 
					new Vector3(0,1,0));
			// Reset the world matrix to Identity.
			device.Transform.World = Matrix.Identity;

			// Create a new matrix which will store our transformations.
			Matrix customMatrix = Matrix.Identity;
			// Apply a translation to our matrix.
			Matrix translation = Matrix.Translation(10, 10, 10);
			// Apply a rotation on the OX axis.             
			Matrix rotation = Matrix.RotationX(45);
			// Apply scaling.                
			Matrix scaling = Matrix.Scaling(0.5f, 0.5f, 0.5f);
			// Build the composite matrix.
			customMatrix = translation * rotation * scaling;

			// Set the world matrix to be our new custom matrix.
			device.Transform.World = customMatrix;
			[...]		
		}

		// We need to override this method so that we can control what is being drawn on the screen.
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			this.Render();
		}

		// Event handler for managing the device reset instance.
		public void OnResetDevice(object sender, EventArgs e)
		{
			[...]
		}
		[...]
	}
}

Dealing with vertices[edit]

As (J)OGL, DX relies on vertices as the building blocks of the scene elements. In contrast to (J)OGL DX vertices can be defined in multiple ways: as transformed (screen coordinates), untransformed (world coordinates), with textures or with colors etc:

  • TransformedColored
  • TransformedTextured
  • PositionColored
  • PositionTextured
  • PositionNormalTextured
  • PositionNormalColored
  • etc.

The primitives which can be built from vertices are also similar with the (J)OGL ones:

  • PrimitiveType.LineList (GL_LINES)
  • PrimitiveType.LineStrip (GL_LINE_STRIP)
  • PrimitiveType.PointList (GL_POINTS)
  • PrimitiveType.TriangleFan (GL_TRIANGLE_FAN)
  • PrimitiveType.TriangleList (GL_TRIANGLES)
  • PrimitiveType.TriangleStrip (GL_TRIANGLE_STRIP)

The following code fragment show how we can build triangle from PositionColored vertices:

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		public void Init()
		{
			[...]		
		}

		private void Render()
		{
			[...]
			// Create a list of PositionColored vertices and define their position and color.
			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();

			// Now we can clear just view-port's portion of the buffer to black
			device.Clear(ClearFlags.Target | ClearFlags.ZBuffer,
					Color.FromArgb(255, 0, 0, 0), 1.0f, 0);

			device.BeginScene();
				// Let the device know your format has changed.
				device.VertexFormat = CustomVertex.PositionColored.Format;
				// Draw the triangle.
				device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, vertices);
			device.EndScene();
			[...]
		}

		// We need to override this method so that we can control what is being drawn on the screen.
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			this.Render();
		}

		// Event handler for managing the device reset instance.
		public void OnResetDevice(object sender, EventArgs e)
		{
			[...]
		}
		[...]
	}
}

Similar with OGL's display lists DX offers VertexBuffers. A vertex buffer can be created as follows:

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		public void Init()
		{
			[...]		
		}

		private void Render()
		{
			[...]
		}

		// We need to override this method so that we can control what is being drawn on the screen.
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			this.Render();
		}

		// Event handler for managing the device reset instance.
		public void OnResetDevice(object sender, EventArgs e)
		{
			[...]
		}

		private VertexBuffer vb = null;
		private CustomVertex.PositionColored[] vertices;

		private void CreateVertexBuffer(Direct3D.Device device, int noVertices)
		{
			vb = new VertexBuffer(typeof(CustomVertex.PositionColored),
				 noVertices, device, Usage.Dynamic | Usage.WriteOnly,
				 CustomVertex.PositionColored.Format, Pool.Default);
			vertices = new CustomVertex.PositionColored[noVertices];
			for (int i=0; i<noVertices; i++)
			{
				vertices[0].Position = new Vector3(i, i, i);
				vertices[0].Color = Color.Red.ToArgb();
			}
			vb.SetData(vertices, 0, LockFlags.None);
		}

		private void UseVertexBuffer(Direct3D.Device device, int noVertices)
		{
			device.SetStreamSource(0, vb, 0);
			device.VertexFormat = CustomVertex.PositionColored.Format;
			device.DrawUserPrimitives(PrimitiveType.LineList, noVertices/2 
				/*primitive count - 2 vertice per line implies a noVertices/2 primitives*/, 
				vertices);
		}
		[...]
	}
}

IMPORTANT: Lighting must be off if we want our primitives to be visible. If using lighting we need to specify normals to our vertices (use PositionNormalColored, PositionNormalTextured etc.).

Lights, Materials and Textures[edit]

The following fragment of code shows how we can add lighting, materials and textures to scene objects.

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		public void Init()
		{
			[...]
			this.SetUpMaterial(device);
			this.SetUpTexture(device);
			this.SetUpLights(device);
			[...]
		}

		private void Render()
		{
			[...]
			device.Transform.World = customMatrix;
			// Select material to be applied.
			device.Material = material;
			// Select texture to be applied.
			device.SetTexture(0, texture);

			// Draw object.
			[...]
		}

		// We need to override this method so that we can control what is being drawn on the screen.
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			this.Render();
		}

		// Event handler for managing the device reset instance.
		public void OnResetDevice(object sender, EventArgs e)
		{
			[...]
		}

		private void SetUpLights(Direct3D.Device device)
		{
			device.Lights[0].Type = LightType.Point; // LightType.Directional; LightType.Spot;
			// device.Lights[0].Ambient = Color.White;
			device.Lights[0].Specular = Color.White;
			device.Lights[0].Diffuse = Color.White;
			device.Lights[0].Position = new Vector3(x, y, z);
			// device.Lights[0].Direction = new Vector3(x, y, z);
			device.Lights[0].Attenuation1 = 0.001f;
			device.Lights[0].Range = 10000f;
			device.Lights[0].Enabled = true;
		}

                private Material material = null;

		private void SetUpMaterial(Direct3D.Device device, String fileName)
		{
			material = new Material();
			material.Ambient = Color.White;
			material.Diffuse = Color.White;
			material.Specular = Color.White;
		}

		private Texture texture = null;
		
		private void SetUpTexture(Direct3D.Device device)
		{
			texture = TextureLoader.FromFile(device, fileName);
		}
	}
}

Adding spheres to the scene[edit]

Spheres are some of the easiest 3D objects to draw in either OGL (using GLU) or DX. The following example shows how we can add a sphere to a scene and add a material and texture to it.

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		private Mesh myMesh = null;

		public void Init()
		{
			[...]
			this.SetUpMaterial(device);
			this.SetUpTexture(device);
			this.SetUpLights(device);
			// Create a sphere mesh.
			myMesh = Mesh.Sphere(device, 3.0f, 60, 20);
			[...]
		}

		private void Render()
		{
			[...]
			device.Transform.World = customMatrix;
			// Select material to be applied.
			device.Material = material;

			// Apply texture on the sphere.

			// Draw the sphere mesh.
			myMesh.DrawSubset(0);

			[...]
		}

		// We need to override this method so that we can control what is being drawn on the screen.
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			this.Render();
		}

		// Event handler for managing the device reset instance.
		public void OnResetDevice(object sender, EventArgs e)
		{
			[...]
		}
	}
}

Blending[edit]

Blending usually means mixing color components of several objects in the scene. DX achieves this as easily as (J)OGL:

[...]
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Direct3D = Microsoft.DirectX.Direct3D;

namespace FirstDirectX
{

	public class HelloDirectX : System.Windows.Forms.Form
	{
		private Direct3D.Device device = null;

		static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(true);
			
			HelloDirectX frm = new HelloDirectX();
			// Initialize the device.
			frm.Init();
			// Show the form.
			frm.Show();

			Application.run(frm);
		}

		private Mesh myMesh = null;

		public void Init()
		{
			[...]
			this.SetUpMaterial(device);
			this.SetUpTexture(device);
			this.SetUpLights(device);
			// Create a sphere mesh.
			myMesh = Mesh.Sphere(device, 3.0f, 60, 20);
			[...]
		}

		private void Render()
		{
			[...]
			device.Transform.World = customMatrix;
			// Select material to be applied.
			device.Material = material;

			// Enable alpha blending test.
			device.RenderState.AlphaBlendEnable = true;
			device.RenderState.AlphaTestEnable = true;
			device.RenderState.SourceBlend = Blend.SourceAlpha;
			device.RenderState.DestinationBlend = Blend.DestinationAlpha;

			device.RenderState.ReferenceAlpha = 0x08;
			device.RenderState.AlphaFunction = Compare.GreaterEqual;

			// Select texture to be applied.
			device.SetTexture(0, texture);
			// Draw the sphere mesh.
			myMesh.DrawSubset(0);

			// Disable alpha blending test.
			device.RenderState.AlphaBlendEnable = false;
			device.RenderState.AlphaTestEnable = false;

			[...]
		}

		// We need to override this method so that we can control what is being drawn on the screen.
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			this.Render();
		}

		// Event handler for managing the device reset instance.
		public void OnResetDevice(object sender, EventArgs e)
		{
			[...]
		}
	}
}

Other issues[edit]

The previous sections were meant to show how we can achieve in DX most of the things shown in the previous (J)OGL laboratories. However they were not presented in much detail.

However topics such as camera movement, particle engines, world boxes were omitted as they can be easily achieved with the already known issues plus some translation of OGL code to DX.

Links[edit]