Tutorial 2 – 2D Primitives
In this tutorial we’ll look at actually drawing some basic 2D shapes to the screen. Bear in mind that DirectX can take a bit of initialising so this tutorial will cover quite a bit of code. Once it’s all set up though it’s plain sailing, so stick with it!
All the shapes that you can think of either are, or can be broken down into, geometric primitives. For example a pyramid can be broken down into a few triangles. All the geometry that you render in DirectX is constructed of primitives and there are several available to you. Below is a list of the DirectX primitive types available and an example of how they can look with an example set of vertices:
(please note, there are some other slightly more advanced primitive types not included in this list which I plan on discussing in a future tutorial)
In some graphics APIs (such as OpenGL or DirectX 9) you can use a built in version of what is known as the “graphics pipeline“. This is basically a series of computations that take some input data (usually to do with primitives like the ones shown above) and converts them into a 2D raster image which can be shown on your screen. The main sections of the graphics pipeline that we’re interested in are:
- Vertex shader – Performs maths on individual vertices to, for example, transform them by their 3D coordinates or calculate their colour.
- Geometry shader – Converts the “point cloud” data of vertices into real primitives such as triangles.
- Pixel shader – Calculates the colour of each pixel making up the primitive e.g. if a pixel is between one red vertex and one blue vertex, what colour should it be?
D3DX10CreateEffectFromFile("./shader.fx", NULL, NULL, "fx_4_0", D3D10_SHADER_DEBUG, 0, pD3DDevice, NULL, NULL, &pShader, NULL, &hResult);
This function loads up the code stored in “shader.fx” and uses shader model version 4 to compile the shader into “pShader”.
In the shader file we define a “technique”. This is just a collection of shaders (vertex, geometry and pixel) grouped together and used to render an image. We want to be able to reference this technique in our DirectX code. To do this we can call:
In modern graphics APIs drawing is performed by creating a collection of 3-dimensional geometry and then mapping it into 2-dimensional space (drawing it on the screen). To do this we need to use projection matrices. There are 2 main types of projection matrix; In the real world, objects in the distance appear smaller than those closer to us. To simulate this we would use a perspective matrix. In some circumstances we want distant objects to appear exactly the same size as those close up e.g. CAD programs or if we are trying to draw in 2-dimensions anyway. For this kind of projection we would use an orthographic matrix.
Because we are drawing a 2 dimensional triangle we will be using an orthographic projection matrix in this application. DirectX extensions come to our aid again here and provide various functions for creating matrices. In this case we’ll be creating a right handed orthographic matrix using the following code:
D3DXMatrixOrthoRH(&projectionMat, 1.0f, 1.0f, -1.0f, 1.0f);
This creates a projection matrix that is one unit wide, one unit high, runs from the near clipping plane at -1.0, to the far clipping plane at 1.0 and stores it in “projectionMat”. Now we want to take this matrix and make our shader use it as it’s projection matrix. To do that we first get access to the shader’s projection matrix variable:
ID3D10EffectMatrixVariable *pShaderProjectionMat = pShader->GetVariableByName( "ProjectionMatrix")->AsMatrix()
and then assign our previously created ortho matrix to this shader variable:
For this basic 2D rendering we don’t need to do anything clever with the camera or even translate our vertices around the world, for this reason we set our World and View matrices to an identity matrix using the same principles as the projection matrix.
Creating The Geometry
In our code we have a function which generates an ID3D10Buffer object containing all the data we need to represent a coloured triangle. This function first creates an input layout which tells our shader how to interpret our vertex data. We are describing our vertex “Position” as 2 floats (DXGI_FORMAT_R32G32_FLOAT) followed by the vertex’s “Colour” which is 4 floats (DXGI_FORMAT_R32G32B32A32_FLOAT). We create an array of data that follows this format (2 float position, 4 float colour, 2 float position… etc.) and call “CreateBuffer” to convert this raw vertex data to a DirectX vertex buffer.
To draw the triangle defined by our vertex data we first tell DirectX that we want to use our vertex buffer by calling IASetVertexBuffers and passing in the vertex buffer. We then say what kind of primitives we’re attempting to draw by calling IASetPrimitiveTopology and passing in D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST. All that’s left to do is choose the shader technique we want to use and then call Draw to draw our 3 vertices as a triangle.
If all goes well you should see something like this:
This tutorial is quite lengthy but you can find the code for it (with plenty of comments) here: