I will dedicate this post to matrices. If you missed this mathematical discipline thinking it was probably useless: bad luck, in the world of 3D rendering this topic is essential! 🙂
A simple explanation (basically) is that each matrix is defined as an array of numbers (4x4 in our case) which will simplify our life for applying transformations to the cube mesh (rotation, translation and scaling). As I do not claim to give you an algebra class (that my notions are actually quite limited), I suggest you to visit the resources section at the end of this article (stolen on a previous post from my colleague David Rousset ^^):
Before depressing, the good news is that Direct3D manages much of the inherent complexity of the matrices, the important point to understand is that the coordinates of our object (vertices in first) will be transformed in the following way.
In the first place, we will apply transformations on the object (model) to position it appropriately in the scene.
Then, we'll look at this object from a certain point of view. Imagine a camera that fits over our cube. We will apply the appropriate transformations to see the object in the new coordinates system.
And finally the projection. It is nice to be in a 3D world, but our screen is a flat surface where each pixel is represented by 2 values (x and y). Projection will help us to switch from the 3D world to the 2D surface.
These transformations are defined by different matrices: model, view and projection. Our initial data (stored in the Vertex Buffer) will be transformed by these matrices to obtain the final coordinated of our pixel on the screen.
This is largely the role of the Vertex Shader (yet another technical term ^^) to perform this operation. How does it do? It will simply multiply the starting coordinates with each matrix model, view and projection, job completed!
Before talking about the Vertex Shader, a small additional speech about the Constant Buffer, an awesome program for you dear reader. 🙂
The graphics card is our friend, it works quickly because this hardware has everything available to work. Its memory is dedicated and we already allocated some information on this area with our previous buffers (Vertex Buffer and Index Buffer).
The same goes for the matrices, we will store them in a Constant Buffer. It is a memory space that Vertex Shader will be able to consume to perform the projection calculations. With this last piece, our Vertex Shader is finally ready to work on our data!
Ok, now that we have introduced the concepts, some code.
1. Constant buffer declaration
a. In SceneRenderer.cpp, we will start by declaring the structure of our Constant Buffer. This structure simply consists of our three 4x4 matrices: model, view and projection.
m_constantBuffer: this is a pointer to our buffer data (as well as m_indexBuffer and m_vertexBuffer), directly accessible by the graphics card (and hence accessible by our Vertex Shader).
m_constantBufferData: our matrices will be first stored in this variable (as cubeVertices and cubeIndices). In short, our three transformation matrices are initialized in this variable and we send the result to m_constantBuffer.
2. Constant buffer initialization
Initialization is performed in SceneRenderer::CreateDeviceResources, we proceed in a similar way to Index/Vertex Buffer with a call to ID3D11Device::CreateBuffer. The only difference is that the input data is empty at the moment (but we will fix this soon).
CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
3. Matrices initialization: model, view et projection
a. model matrix
We begin by addressing the matrix model. In this example, I want to rotate the cube: we're going to apply a rotation matrix (on the Y axis), and update the rotation angle for each new frame.
This job is performed in SceneRenderer::Update method with a call to XMatrixRotationY: it simply takes an angle argument (in radians). This type of method exists for all transformations, by the way matrix calculation become a black box that you can use easily.
void SceneRenderer::Update(float timeTotal, float timeDelta)
(void) timeDelta; // Unused parameter.
XMStoreFloat4x4(&m_constantBufferData.model, XMMatrixTranspose(XMMatrixRotationY(timeTotal * XM_PIDIV4)));
b. view matrix
The view matrix sets the scene in a new coordinate systems, where object is targeted by a camera. To define a camera, we need three vectors:
eye defines the position of the camera
at defines the position of the focal point (the direction of the targeted object)
up defines the up direction of the camera
Then XMatrixLookAtRH method allows us to reposition the scene with these three new coordinates.
XMVECTOR eye = XMVectorSet(0.0f, 0.7f, 1.5f, 0.0f);
XMVECTOR at = XMVectorSet(0.0f, -0.1f, 0.0f, 0.0f);
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMStoreFloat4x4(&m_constantBufferData.view, XMMatrixTranspose(XMMatrixLookAtRH(eye, at, up)));
c. projection matrix
FovAngleY defines the top-down field-of-view angle in radians. This angle is 70 degrees in our case, we just need to convert this value in radian 🙂
- AspectRatio defines the aspect ratio of the view space (we devide x by y)
- NearZ is the distance to the near clipping plane, all graphics elements before this plane are not displayed.
- FarZ is the distance to the far clipping plane, all graphics elements after this plane are not displayed.
As drawing is probably more meaningful, it looks like this:
And voila, we have our third matrix !
float aspectRatio = m_windowBounds.Width / m_windowBounds.Height;
float fovAngleY = 70.0f * XM_PI / 180.0f; // convert in radian
if (aspectRatio < 1.0f)
fovAngleY /= aspectRatio;
fovAngleY, // field-of-view
aspectRatio, // aspect ratio
0.01f, // near clipping plane
100.0f // far clipping plane
- Transforms (Direct3D 9)
- World, View and Projection Matrix Unveiled
- Cameras on OpenGL ES 2.x – The ModelViewProjection Matrix
- Tutorial 3 : Matrices
- A brief introduction to 3D
- OpenGL Transformation