CHAPTER 19
Projection Options
The projection of a scene is the method by which the scene is transformed from 3-D data points in RAM to a 2-D projection for display on the monitor. Desktop computers have 2-D monitors for displaying graphics; 3-D graphics are an illusion simulated by projecting a set of 3-D coordinates onto a 2-D surface (the computer monitor).
Perspective Projection
The projection matrix is defined in the CreateWindowSizeDependentResources method of the CubeRenderer.cpp file. The default definition is as follows.
float aspectRatio = m_windowBounds.Width / m_windowBounds.Height; float fovAngleY = 70.0f * XM_PI / 180.0f; // Note that the m_orientationTransform3D matrix is post-multiplied here // … // this transform should not be applied. XMStoreFloat4x4( &m_constantBufferData.projection, XMMatrixTranspose( XMMatrixMultiply( XMMatrixPerspectiveFovRH( fovAngleY,aspectRatio, 0.01f, 100.0f ), XMLoadFloat4x4(&m_orientationTransform3D) ))); |
XMMatrixPerspectiveFovRH
In the previous code we have defined a perspective field of view (FOV). The farther objects are away from the camera, the smaller they appear. Perspective is good for mimicking reality since our own visual system describes distance using a similar algorithm (coupled with binocular depth perception). The matrix used here is the FovRH (Right-Handed Field of View). A detailed discussion of right versus left handed coordinates is provided in the follow-up book Direct3D Succinctly.
Aspect Ratio
This parameter is the aspect ratio of the projection. This will usually be the same as the aspect ratio of the screen as we see here. This is the screen's width divided by its height. If this value is greater than the aspect ratio of the screen, objects will appear much taller when projected; if it is less than the screen, objects will appear squashed.
Field of View (FOV) Angle
This is the angle of the field of view. The parameter expects radians, so the 70 degrees in the definition of this variable is converted to radians by multiplying it by pi/180. This is the angle of visible objects from the camera. For humans it is around 180 degrees, but most of the sides of what we can see is very blurry. In projection matrices, it is conventional to use a value less than 180 degrees. The value 70 means the camera can see things 35 degrees left of its center and 35 degrees right.
Near Clipping Plane
It is conventional to clip objects too near the camera, because if they are rendered they block the rest of the scene. The closer an object is to the camera, the larger it will appear when rendered with perspective. The value given as a near clipping plane is the closest an object can be before it is no longer rendered. Here the value 0.01f is used to mean that anything closer to the camera than 0.01 units should be clipped or not rendered.
Far Clipping Plane
To save processing time, objects which are far from the camera may not need to be rendered. The value given for the far clipping plane is the farthest an object can be from the camera before it is no longer rendered. It is the viewing distance in units. It should be some value greater than the near clipping plane, otherwise the camera would not be able to see anything. For charting applications, the far clipping plane will usually be far enough that all the data is visible at all times.
Orthographic Projection
By default, the Direct3D template presents a cube rendered with perspective. Sometimes it is easier to understand data when it is not rendered in this way. After all, perspective is a distortion effect, and it may obscure the meaning of the data. An orthographic projection is the same as a perspective projection except that objects maintain their apparent size despite their distance from the camera. This looks a little unnatural, but it can be a very clear way to render data. Distant data points will not appear smaller than closer data points, and they can be compared more easily. Distant data is as clear as nearer data, whereas with a perspective projection, distant data is all squashed together approaching a point on the horizon line. See Figure 47 for a depiction of the difference between these projections.

Projection Options
To use an orthographic projection, replace the XMMatrixPerspectiveFovRH (in the CubeRenderer's CreateWindowSizeDependentResources method) with the following.
XMMatrixOrthographicRH( 25.0f, // Horizontal units visible 25.0f, // Vertical units visible 0.01f, // Distance to closest renderable point 500.0f), // Distance to farthest renderable point |
ViewWidth
This is number of units visible in the horizontal axis. If, for instance, you have rendered a 20 × 20 height map, you might set this value to 25.0f to make all the data points visible with an additional 2.5 units (half of 5.0) of extra padding on the side.
ViewHeight
This is the number of units visible in the vertical axis. For instance, to view a height map with data from 0.0f to 20.0f, you might set this value to 25.0f to allow all the data to be visible with a little extra for padding.
Near and Far Clipping Planes
These values have an identical meaning in an orthographic projection to the perspective projection. They represent the nearest and farthest points an object can be from the camera and still be rendered.
Direct3D Scatter Plot
As a final example of rendering data using Direct3D, we will examine rendering a collection of unconnected triangles (like the nodes in a scatter plot). The basic scatter plot we examined earlier was fairly good at rendering thousands of nodes, but when the count reaches tens or hundreds of thousands of nodes, this technique is no longer useful; it is simply too slow. The problem is the rendering loop. This loop is performed by the CPU. One node is drawn to the render target per iteration and this is a severe bottleneck. The CPU is running through the loop and relaying the drawing instructions to the GPU. Remember that Direct2D was an extra layer of abstraction on top of Direct3D. It is far more efficient to exclude the CPU and loop altogether, and render nodes in parallel using only the GPU.
Drawing many thousands of triangular nodes is extremely efficient in Direct3D. Instead of rendering the nodes as circles using a “for loop,” we could simply store them as a collection of triangles on the GPU and render them directly with Direct3D. The CPU does not need to perform any calculations once the nodes are loaded onto the graphics card. The performance gained by using the GPU to render triangles, instead of rendering circles with Direct2D, is vast. For instance, the original Direct2D scatter plot could render around 10,000 nodes comfortably on the machine I am writing with. This new method can easily render 1,000,000 at a steady 60 frames per second. This machine has only a moderately powerful GPU (nVidia GT 430) and it has no trouble. It becomes very choppy only at around 10,000,000 nodes.
For this, we will use a new Direct3D cube template. Open a new Direct3D application, and change the DeviceResources loading method to the following.
void CubeRenderer::CreateDeviceResources() { Direct3DBase::CreateDeviceResources(); auto loadVSTask = DX::ReadDataAsync("SimpleVertexShader.cso"); auto loadPSTask = DX::ReadDataAsync("SimplePixelShader.cso"); auto createVSTask = loadVSTask.then([this](Platform::Array<byte>^ fileData) { DX::ThrowIfFailed( m_d3dDevice->CreateVertexShader( fileData->Data,fileData->Length, nullptr, &m_vertexShader)); const D3D11_INPUT_ELEMENT_DESC vertexDesc[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; DX::ThrowIfFailed( m_d3dDevice->CreateInputLayout( vertexDesc,ARRAYSIZE(vertexDesc), fileData->Data,fileData->Length,&m_inputLayout )); }); auto createPSTask = loadPSTask.then([this](Platform::Array<byte>^ fileData) { DX::ThrowIfFailed( m_d3dDevice->CreatePixelShader( fileData->Data,fileData->Length, nullptr,&m_pixelShader )); CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER); DX::ThrowIfFailed( m_d3dDevice->CreateBuffer( &constantBufferDesc, nullptr, &m_constantBuffer) ); }); auto createCubeTask = (createPSTask && createVSTask).then([this] () { int count = 100; float x, y, r, g, b; m_CubeVertices = new VertexPositionColor[count * 3]; float sze = 0.01f; float z = 0.0f; for(int i = 0; i < count; i++){ x = (float(rand() % 5000)/5000.0f)-0.5f; y = (float(rand() % 5000)/5000.0f)-0.5f;
r = (float(rand() % 10)/10.0f); g = (float(rand() % 10)/10.0f); b = (float(rand() % 10)/10.0f); m_CubeVertices[(i*3)+0].pos = XMFLOAT3(x, y, z); m_CubeVertices[(i*3)+0].color = XMFLOAT3(r, g, b); m_CubeVertices[(i*3)+1].pos = XMFLOAT3(x-sze, y+sze, z); m_CubeVertices[(i*3)+1].color = XMFLOAT3(r, g, b); m_CubeVertices[(i*3)+2].pos = XMFLOAT3(x+sze, y+sze, z); m_CubeVertices[(i*3)+2].color = XMFLOAT3(r, g, b); z+= 0.0000001f; } D3D11_SUBRESOURCE_DATA vertexBufferData = {0}; vertexBufferData.pSysMem = m_CubeVertices; vertexBufferData.SysMemPitch = 0; vertexBufferData.SysMemSlicePitch = 0; CD3D11_BUFFER_DESC vertexBufferDesc( count*2*3*sizeof(XMFLOAT3), D3D11_BIND_VERTEX_BUFFER); DX::ThrowIfFailed( m_d3dDevice->CreateBuffer( &vertexBufferDesc,&vertexBufferData, &m_vertexBuffer) ); m_CubeIndices = new unsigned short[count * 3]; for(int i = 0; i < count * 3; i++){ m_CubeIndices[i] = i; } m_indexCount = count * 3; D3D11_SUBRESOURCE_DATA indexBufferData = {0}; indexBufferData.pSysMem = m_CubeIndices; indexBufferData.SysMemPitch = 0; indexBufferData.SysMemSlicePitch = 0; CD3D11_BUFFER_DESC indexBufferDesc(2*count * 3, D3D11_BIND_INDEX_BUFFER); DX::ThrowIfFailed( m_d3dDevice->CreateBuffer( &indexBufferDesc, &indexBufferData,&m_indexBuffer )); }); createCubeTask.then([this] () { m_loadingComplete = true; }); } |
Add the m_CubeVertices and m_CubeIndices arrays to the CubeRenderer.h file.
VertexPositionColor *m_CubeVertices; unsigned short *m_CubeIndices; |
Most of this code should be familiar. I have created a vertex buffer, an index buffer, and rendered the data. This is a demonstration of rendering 100 colored triangles. The number can be increased by changing the line that contains count = 100. You should find that you can increase the number of triangles far in excess of the number of triangles you could possibly plot with Direct2D geometry.
Tip: I have added a slowly incrementing z value for each of the triangles in the code sample. This was added so that no two triangles would lie exactly in the same position. If two triangles lie in exactly the same position, the GPU will sometimes render one first, and at other times it will render the other triangle first. This leads to an unpleasant flickering artifact. By creating each triangle on a slightly different z plane, we eliminate this flickering.
- 1800+ high-performance UI components.
- Includes popular controls such as Grid, Chart, Scheduler, and more.
- 24x5 unlimited support by developers.