Drawing a Triangle
Now that you can prepare the Direct3D engine and create a device on which to draw, the next step is to create some data and draw it. To draw something, you need to supply the coordinates of the points you will use to Direct3D. You do that by creating a buffer to hold the coordinate data and then passing the buffer to Direct3D.
sample program, which is available for download, is very similar to the previous example. It differs from program d3dCreateDevice
in three ways.
First, its InitializeGraphics
method calls the CreateVertexBuffer
method to create the data buffer.
Second, the program's Render
method calls SetupMatrices
to create the world, view, and projection transformations that Direct3D should use to display the graphics. The previous program didn't need to do this because it didn't draw anything, but program d3dDrawTriangle
does draw something and needs to define these transformations.
Finally, the program's Render
method calls methods to tell Direct3D where the data is and how to draw it:
The following code shows how the program defines its data.
' Data variables.
Private Const NUM_TRIANGLES As Integer = 1
Private Const NUM_POINTS As Integer = 3 * NUM_TRIANGLES
' The vertex buffer that holds drawing data.
Private m_VertexBuffer As VertexBuffer = Nothing
' Initialize the graphics device. Return True if successful.
Public Function InitializeGraphics() As Boolean
... Create a device as before ...
' Turn off D3D lighting because
' we set the vertex colors explicitly.
m_Device.RenderState.Lighting = False
' Create the vertex data.
' We succeeded.
The code starts with module-level constants that define the number of triangles that it will draw and the number of points those triangles will need. It also defines a module-level VertexBuffer object.
function creates a Direct3D device as before. It then sets the device's Lighting
property to False
to indicate that this program does not use a lighting model. Instead the data explicitly defines colors for the triangle's corners. InitializeGraphics
to create the data and then returns as before.
The following code shows how the CreateVertexBuffer
method creates the buffer:
' Create a vertex buffer for the device.
Public Sub CreateVertexBuffer()
' Create a buffer for 3 vertexes.
m_VertexBuffer = New VertexBuffer( _
NUM_POINTS, m_Device, 0, _
' Lock the vertex buffer.
' Lock returns an array of positionColored objects.
Dim vertices As CustomVertex.PositionColored() = _
CType(m_VertexBuffer.Lock(0, 0), _
' Make the vertexes.
vertices(0).X = 1
vertices(0).Y = -1
vertices(0).Z = 0
vertices(0).Color = Color.Red.ToArgb
vertices(1).X = -1
vertices(1).Y = -1
vertices(1).Z = 0
vertices(1).Color = Color.Blue.ToArgb
vertices(2).X = 0
vertices(2).Y = 1
vertices(2).Z = 0
vertices(2).Color = Color.Green.ToArgb
The code first creates a VertexBuffer object, passing its constructor a type indicating that the buffer should contain PositionColored objects. That type of object contains X, Y, and Z coordinates for each point, plus a color value. The method also passes the constructor the number of points it wants the buffer to hold.
then locks the buffer to gain access to the objects it contains and fills in the coordinates and colors for the triangle's three corners. The method finishes by unlocking the buffer.
In this program, the Render
method performs the transformations and displays the image defined by the vertices. Here's the code:
Public Sub Render()
' Clear the back buffer.
m_Device.Clear(ClearFlags.Target, Color.Black, 1, 0)
' Make a scene.
' Draw stuff here...
' Setup the world, view, and projection matrices.
' Set the device's data stream source (the vertex buffer).
m_Device.SetStreamSource(0, m_VertexBuffer, 0)
' Tell the device the format of the vertices.
m_Device.VertexFormat = CustomVertex.PositionColored.Format
' Draw the primitives in the data stream.
' End the scene and display.
The method clears the device and begins a scene as the previous example did. It then calls the SetupMatrices
method (described shortly) to define the world, view, and projection transformations.
then calls the device's SetStreamSource
method to tell Direct3D where the data is and sets its VertexFormat
property to tell Direct3D the type of data in the buffer.
Next the method calls DrawPrimitives
to make Direct3D draw the triangle. The TriangleList
parameter indicates that the buffer contains points that define independent triangles. Direct3D looks through the buffer and uses each triple of points to define a triangle.
method finishes as the previous example's version did—by ending the scene and displaying the result.
The final new piece to this program is the SetupMatrices
method shown in the following code:
' Setup the world, view, and projection matrices.
Private Sub SetupMatrices()
' World Matrix:
' Just the identity.
m_Device.Transform.World = Matrix.Identity()
' View Matrix:
' This is defined by giving:
' An eye point (0, 0, -3)
' A point to look at (0, 0, 0)
' An "up" direction <0, 1, 0>
m_Device.Transform.View = Matrix.LookAtLH( _
New Vector3(0, 0, -3), _
New Vector3(0, 0, 0), _
New Vector3(0, 1, 0))
' Projection Matrix:
' Perspective transformation defined by:
' Field of view Pi / 4
' Aspect ratio 1
' Near clipping plane Z = 1
' Far clipping plane Z = 100
m_Device.Transform.Projection = _
Matrix.PerspectiveFovLH(Math.PI / 4, 1, 1, 100)
sets the world transformation to the identity matrix. This transformation leaves all its data unchanged so the objects in three-dimensional space are not transformed before Direct3D applies the other transformations.
The method calls the Matrix class's LookAtLH
function to define the viewing transformation. The "LH" at the end of the method's name indicates that it's designed for use in a left-handed coordinate system. This method builds a transformation that is defined by a camera position, a point toward which the camera should be pointed, and an "up" vector that determines the camera's roll (imagine holding the camera sideways or upside down).
In this program, the camera is at position (0, 0, -3). If you recall the earlier discussion of Direct3D's left-handed coordinate system, the Z-axis normally points into the computer's screen so the point value refers to a point moved out of the screen toward where you are sitting relative to the origin (0, 0, 0). If you make the Z coordinate smaller (for example, -10), the camera moves further away from the origin, so objects look smaller. If you make the Z coordinate smaller (for example, -2), then the camera moves closer to the origin—and objects there look bigger.
The call to LookAtLH
makes the camera look toward the origin and points the camera so "up" is the direction <0, 1, 0>. That direction points toward the positive Y-axis, which is the vertical axis in the Direct3D coordinate system.
|Figure 3. Colorful but Static: The d3dDrawTriangle program displays a colored triangle.|
calls the Matrix class's PerspectiveFovLH
method to build the projection transformation. This builds a projection transformation based on a field of view for a left-handed coordinate system. The parameters to this function give the angle of view in the Y direction in radians, the aspect ratio (the width-to-height ratio), and the distances to near and far clipping planes that determine whether an object should be drawn.
When you put all this together, you get the triangle shown in Figure 3
. If you look at the code in the CreateVertexBuffer
method, you can match up the coordinates of the points in the buffer with those in Figure 3
and get a better feel for how the coordinate system works. The first point is red and in the lower-right corner at (1, -1, 0), the second point is blue and in the lower-left corner at (-1, -1, 0), and the third point is green and at the top at (0, 1, 0).
|Figure 4. On the Move: The d3dRotatingTriangle program uses a world transformation to rotate its triangle before displaying it.|
uses exactly the same code as program d3dDrawTriangle except that its SetupMatrices
method uses the following code to define the world transformation. The code uses Environment.TickCount
to see how many milliseconds have passed since the computer last booted. It multiplies that number by 2 * Pi and divides by 2,000 to get the number of radians that it should rotate the data. This makes the data rotate one complete revolution every two seconds:
' World Matrix:
' Rotate the object around the Y axis by
' 2 * Pi radians per 2000 ticks (2 seconds).
Const TICKS_PER_REV As Integer = 2000
Dim angle As Double = Environment.TickCount * _
(2 * Math.PI) / TICKS_PER_REV
m_Device.Transform.World = _
shows the result.