Direct3D-related part

The basic concepts of 3D graphics will be used in the native part of your game. As you can see, these concepts can be sometimes complex and difficult, as well as involve a lot of mathematical calculations, especially related to matrices. Therefore, creating the whole implementation of a 3D game from scratch will not be a trivial task. Fortunately, Direct3D may significantly simplify creation of 3D games and applications. Furthermore, the Windows Phone XAML and Direct3D App template automatically generates code that is related to the Direct3D setup. It also creates a simple example with a rotating cube. This section explains the role of some files from the native part.

Note

If you choose the Windows Phone Direct3D with XAML App template (instead of Windows Phone XAML and Direct3D App), the suitable code will also be automatically generated. However, it will be slightly different than the version described in this chapter.

Windows Phone Runtime Component

The native part of the project is named SpaceAim3DComp and is created as the Windows Phone Runtime Component. As mentioned in the previous chapters, this part prepares 3D graphics (using Direct3D) to display in the application page from the managed part. As stated at http://msdn.microsoft.com/library/jj207012, this:

"creates a Direct3D graphics device, which it uses to create a Texture that is shared with the XAML app"

The process of drawing graphics and showing them in the DrawingSurface control is presented in the following diagram:

Windows Phone Runtime Component

To start, Direct3D is used to draw elements directly to the texture, which is synchronized to prevent concurrent access to it by both the Direct3D and XAML parts. By using synchronization, only one of the parts can operate at a time. The last stage of the drawing process consists of graphics presentation in the DrawingSurface control, which is handled by the XAML engine.

Direct3DInterop class

The first class explained in this chapter is Direct3DInterop, whose code can be found in the Direct3DInterop.h and Direct3DInterop.cpp files. It is used to communicate between the native and the managed part of the application, as shown in the following figure:

Direct3DInterop class

The class is declared with the ref class keyword, thus it is a C++/CX class. It implements the IDrawingSurfaceManipulationHandler interface (from the Windows::Phone::Input::Interop namespace) and defines the SetManipulationHost method, which is used to prepare event handlers for touch input. It is important to understand the order in which particular event handlers are called. Whenever the user clicks on the screen, OnPointerPressed is called once. If the user stops clicking on the screen, OnPointerReleased is called. While clicking, the player can move a finger and then the OnPointerMoved method is called.

Tip

The Direct3DInterop class can be used for handling touch input.

The Direct3DInterop class also has a set of properties that allow to obtain data related to window bounds (WindowBounds) and resolutions (NativeResolution and RenderResolution). Apart from them, it contains a few private fields that represent, for example, a renderer that prepares graphics using Direct3D (m_renderer) and a timer (m_timer). The class has also two events, RequestAdditionalFrame and RecreateSynchronizedTexture. The first one indicates the necessity of rendering the content as soon as possible, and the other, the necessity of recreating the synchronized texture.

One of the methods is Connect, which is used to create a suitable renderer (CubeRenderer instance), initialize it (by calling the Initialize method), and pass values indicating window size and resolution. The Connect method is called after a successful creation of the Direct3D device and the texture. The Disconnect method can be understood as the opposite to Connect. In this example, it is used just to assign a null pointer (nullptr) to the m_renderer field, which indicates that the renderer is no longer used.

The PrepareResources method is called for each frame and can indicate whether the content needs to be rendered. It can be done by setting a suitable value of the contentDirty variable, whose pointer is passed as a parameter.

Two other methods are named GetTexture. One of them has three parameters, and the other does not require any of them. They also vary by a return type. The first one calls the Update and Render methods from the renderer class, fires the RequestAdditionalFrame event, and causes redrawing of the content. The second version of this method just returns a pointer to the synchronized texture.

Direct3DBase class

The Direct3DBase is a base class for the renderer and contains a set of virtual methods, including the CreateDeviceResources method. It creates the Direct3D device (the m_d3dDevice field) and the corresponding context (m_d3dContext).

Note

While programming using Direct3D, you may often use either the Direct3D device or the Direct3D device context. The first is represented by the ID3D11Device interface and performs crucial role for creating necessary resources. Regarding the other, it is specified by ID3D11DeviceContext and is used for rendering.

Another method is named CreateWindowSizeDependentResources and creates the resources that are dependent on a window size, including the texture as the render target buffer. It is prepared by the CreateTexture2D method and stored in the m_renderTarget field, which is a pointer to the D3D11Texture2D instance. The class also prepares the depth stencil view.

Among the other methods in the Direct3DBase class, the Initialize method is available. This method calls CreateDeviceResources and prepares resources required by Direct3D.

CubeRenderer class

The CubeRenderer.h and CubeRenderer.cpp files are related to the CubeRenderer class. This class derives from Direct3DBase and is used for rendering a rotating cube. You will significantly modify this file to present the game world with the planet and asteroids.

Fields

The CubeRenderer class has a set of fields. The first one, named m_loadingComplete, indicates whether the process of loading required resources is finished. A value is used to prevent drawing the cube until all resources have been loaded successfully.

Two other fields represent the vertex and index buffers (m_vertexBuffer and m_indexBuffer, respectively). Both are pointers to instances of the ID3D11Buffer structure. Their role is to store values that are later used during rendering. Data of each vertex are stored as an instance of the VertexPositionColor structure, while a single index is represented by an unsigned short number, as presented in the following figure:

Fields

The CubeRenderer class also contains the m_constantBuffer and m_constantBufferData fields. Both are related to the constant buffer, which is the third buffer resource type, along with the vertex and index ones. In the automatically generated template, the constant buffer supplies necessary data (model, view, and projection matrices) for the vertex shader. The m_constantBuffer field is a pointer to ID3D11Buffer, however m_constantBufferData is an instance of the ModelViewProjectionConstantBuffer structure that stores data for the constant buffer, as shown in the following figure:

Fields

Device resources

To render objects (such as the rotating cube), some resources should be loaded. The automatically generated CubeRenderer class has the CreateDeviceResources method. At the beginning, it loads the vertex shader and pixels shader asynchronously from the .cso files (compiled shader object). To prepare the vertex shader, the CreateVertexShader method is called. The result is stored in the m_vertexShader field, which is a pointer to an instance of the ID3D11VertexShader structure. Then, the CreateInputLayout method is called to specify the input data that are passed to the vertex shader program. The result is available by the m_inputLayout field that is a pointer to ID3D11InputLayout. In case of the pixel shader, its data are accessible by the m_pixelShader field, which is a a pointer to an instance of the ID3D11PixelShader structure. Then, the constant buffer is created using the CreateBuffer method.

When the vertex and pixel shaders are prepared, the application creates both the vertex and index buffers. To create the first one, vertex positions and colors are specified in an array and the CreateBuffer method is called with suitable parameters, shown as follows:

CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(cubeVertices),
  D3D11_BIND_VERTEX_BUFFER);
DX::ThrowIfFailed(m_d3dDevice->CreateBuffer(&vertexBufferDesc,
  &vertexBufferData, &m_vertexBuffer));

It is worth mentioning that the type of buffer is chosen according to the value passed in the constructor of CD3D11_BUFFER_DESC. In case of the vertex buffer, the D3D11_BIND_VERTEX_BUFFER enumeration value is used. However, to prepare the index buffer you should use D3D11_BIND_INDEX_BUFFER instead. Apart from the index buffer, a value of m_indexCount is set to a number of indices.

When the cube vertices and indices are prepared correctly, the m_loadingComplete variable is set to TRUE. This means that the mechanism has already loaded all required resources and the content can be rendered.

Resources dependent on the window size

Some resources are dependent on the window size and need to be recreated whenever the window size is changed. The CreateWindowSizeDependentResources method is called in such a case. Here, the aspect ratio is calculated by dividing the window width by its height. Another required value is the field of view (FOV). It indicates what angle is used to see the game world. In case of smaller values, a smaller area of the scene is visible. You can adjust the value of this parameter according to the game scenario.

When the aspect ratio and the field of view are available, the projection matrix is calculated, as shown in the following code:

XMStoreFloat4x4(&m_constantBufferData.projection,
  XMMatrixTranspose(XMMatrixPerspectiveFovRH(fovAngleY,
    aspectRatio, 0.01f, 100.0f)));

The XMMatrixPerspectiveFovRH method takes four parameters, namely field of view, aspect ratio, near z distance, and far z distance. The last two prevent from drawing objects that are too close (the NearZ parameter) or too far (FarZ) from the camera. By using the XMStoreFloat4x4 function, you store the calculated matrix into the projection field of the constant buffer data. The matrix is transposed by calling the XMMatrixTranspose method.

Tip

The perspective projection matrix can be created by calling the XMMatrixPerspectiveFovRH (XMMatrixPerspectiveFovLH) method. The orthographic projection matrix can be created by calling XMMatrixOrthographicRH (XMMatrixOrthographicLH).

Updating

The game should perform some operations while running and adjust the objects according to the current state. For instance, the rocket should be moved and the game should check whether a collision with any asteroid or the planet occurs. To achieve this, you can use the Update method that is called for each frame. This is a suitable place for the logic that updates the view or model matrices according to the movement of the object or the camera.

In case of the rounding cube example, the view matrix is not changeable and is calculated by the following code:

XMStoreFloat4x4(&m_constantBufferData.view,
  XMMatrixTranspose(XMMatrixLookAtRH(eye, at, up)));

The XMMatrixLookAtRH function takes three parameters, which represent the location of the camera (EyePosition), the location in the scene on which the camera should be focused (FocusPosition), and a vector indicating an upward direction (towards Y positive values, UpDirection parameter). After transposition, the matrix is stored in the view field of the constant buffer data.

You can also create the view matrix by providing the camera position, a vector indicating an upward direction, and a vector indicating the camera direction (instead of a location on which the camera should be focused). It can be achieved by the XMMatrixLookToRH or XMMatrixLookToLH methods, depending on the coordinate system.

Note

The view matrix can be created by the XMMatrixLookAtRH (XMMatrixLookAtLH) and XMMatrixLookToRH (XMMatrixLookToLH) methods. The first one specifies a location in the scene on which the camera is focused, while the other specifies the direction of the camera.

The second operation performed in the Update method, is rotating the cube, which is performed by the following code snippet:

XMStoreFloat4x4(&m_constantBufferData.model,
  XMMatrixTranspose(XMMatrixRotationY(timeTotal * XM_PIDIV4)));

The XMMatrixRotationY function takes only one parameter and returns a calculated matrix. In the example, a rotation is made according to the value indicating the total time of the game (timeTotal) multiplied by π/4 (XM_PIDIV4). The calculated rotation matrix is transposed and stored in the model field of the constant buffer data, as presented earlier.

Tip

Some mathematical constants are available as fields in Direct3D. Their names start with the XM prefix, followed by an underscore. Some sample names of constants are XM_1DIVPI (1/π), XM_PI (π), XM_2PI (2π), and XM_PIDIV2 (π/2).

A rotation can be performed by calling the XMMatrixRotationX, XMMatrixRotationY, and XMMatrixRotationZ functions (around x, y, and z-axis respectively by a given angle), XMMatrixRotationAxis (around a specified axis by a given angle), and XMMatrixRotationRollPitchYaw (around x, y, and z-axis by given angles).

Apart from the rotation, each mesh (a set of data such as vertices, representing an object) can be translated. It is performed by the XMMatrixTranslation or XMMatrixTranslationFromVector methods. Both allow to calculate a matrix, which moves the object in any direction.

Scaling is another transformation that involves calculating a matrix. You can use the XMMatrixScaling and XMMatrixScalingFromVector methods to perform this operation. Both make it possible to scale the same object differently along various axes.

Tip

The author strongly encourages the modification of the values of parameters related to matrices and transformations (translation, rotation, and scaling) on your own. This understanding is really important for 3D games development.

Rendering the scene

After execution of the CreateWindowSizeDependentResources and Update methods, proper values are inside the constant buffer data, however, nothing has been rendered yet. The CubeRenderer class contains the Render method that has code that makes it possible to render the simple rotating cube on the screen.

At the beginning, the render target view (using the texture) and depth stencil view are cleared. The second parameter of the ClearRenderTargetView method specifies the color that is used to fill the background. Later, the value of the m_loadingComplete variable is verified. If it is equal to FALSE, execution of the Render method is finished. Otherwise, the render target view is bound by calling the OMSetRenderTargets method. The next step consists of copying data prepared for the constant buffer (storing model, view, and projection matrices) to the constant buffer represented by the ID3D11Buffer.

The IASetVertexBuffers and IASetIndexBuffer methods prepare the vertex and index buffers before drawing. They are related to the input-assembly stage that is executed before the shaders are run.

The IASetPrimitiveTopology method specifies how indices and vertices data should be interpreted while drawing objects. One of the common possibilities is using them as a list of triangles (D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST enumeration value).Thus, vertices related to each of the three indices are taken to form a single triangle. The next three are used to create the second triangle. Other possibilities include an interpretation of data as points list or lines list (D3D11_PRIMITIVE_TOPOLOGY_POINTLIST and D3D11_PRIMITIVE_TOPOLOGY_LINELIST, respectively). By changing this setting, you can adjust the way how objects are rendered.

The IASetInputLayout method is also related to the input-assembler stage. It binds an object with information about an input for the vertex shader, that is, a layout of the vertex buffer.

The names of the next two methods called in Render, start with the VS prefix. This prefix indicates that they are related to the vertex shader and allow you to set it (VSSetShader), as well as set the constant buffer (VSSetConstantBuffer). The next method is named PSSetShader and simply sets the pixel shader.

At the end of the Render method, DrawIndexed is called. This indicates which indices from the index buffer should be used. Here, all indices are chosen.

Tip

There are many methods that can adjust the rendering. Their names often begin with a prefix that indicates their role (for example, IA is used in the input-assembly stage).

Shaders

As you can see, two shaders (the vertex and pixel ones) have already been loaded in the CreateDeviceResources method. The code of the first one is available in the SimpleVertexShader.hlsl file. Here, you can find information about a constant buffer, as well as input and output of the vertex shader. In this case, both input and output store the position and color of the vertex. To calculate the position, a few matrix multiplications are performed, the model matrix is multiplied by the view matrix, and the result is multiplied by the projection matrix. The color is copied without any changes.

The pixel shader is created in the SimplePixelShader.hlsl file. An input of the pixel shader is specified as a structure (PixelShaderInput) with information about a position and color. The shader returns the color exactly the same as passed in the input data.

GamePage.xaml.cs

You learned some basic information about rendering the content using Direct3D in Windows Phone 8 applications. Therefore, it can be beneficial to return for a moment to the code of the GamePage.xaml.cs file from the managed part. Now, it should be much easier to understand its content.

The most interesting part, from the perspective of the native game development, is the DrawingSurface_Loaded method. Here, a new instance of the Direct3DInterop class is created. Next steps consist of setting suitable window bounds (according to the width and height of the DrawingSurface control), as well as the native and render resolutions. Later, a content provider is set (using the SetContentProvider method), and an instance of the Direct3DInterop class receives access to manipulation events from the DrawingSurface control (by calling the SetManipulationHandler method).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.239.182