52880.fb2 DirectX 8 Programming Tutorial - читать онлайн бесплатно полную версию книги . Страница 11

DirectX 8 Programming Tutorial - читать онлайн бесплатно полную версию книги . Страница 11

DirectX Tutorial 11: 2D in 3D

Introduction

In this tutorial we will add some 2D elements to a 3D scene. This will be useful for adding things like energy bars, timers, radars and so on. We will also look at how to do texture transparency so that your 2D elements can appear non-rectangular. You can download the full source code by clicking the "Download Source" link above.

Adding Text

The first and easiest thing to do is add some 2D text to your scene. For this, I have created a simple wrapper class called CFont. The code for this class is shown below.

CFont::CFont(LPDIRECT3DDEVICE8 pD3DDevice, LPSTR pFontFace, int nHeight, bool fBold, bool fItalic, bool fUnderlined) {

 HFONT hFont;

 m_pD3DDevice = pD3DDevice;

 int nWeight = FW_NORMAL;

 DWORD dwItalic = 0;

 DWORD dwUnderlined = 0;

 if (fBold) {

  nWeight = FW_BOLD;

 }

 if (fItalic) {

  dwItalic = 1;

 }

 if (fUnderlined) {

  dwUnderlined = 1;

 }

 hFont = CreateFont(nHeight, 0, 0, 0, nWeight, dwItalic, dwUnderlined, 0, ANSI_CHARSET, 0, 0, 0, 0, pFontFace);

 D3DXCreateFont(m_pD3DDevice, hFont, &m_pFont);

 LogInfo("<li>Font loaded OK");

}

CFont::~CFont() {

 SafeRelease(m_pFont);

 LogInfo("<li>Font destroyed OK");

}

void CFont::DrawText(LPSTR pText, int x, int y, D3DCOLOR rgbFontColour) {

 RECT Rect;

 Rect.left = x;

 Rect.top = y;

 Rect.right = 0;

 Rect.bottom = 0;

 m_pFont->Begin();

 m_pFont->DrawTextA(pText, –1, &Rect, DT_CALCRECT, 0); //Calculate the size of the rect needed

 m_pFont->DrawTextA(pText, –1, &Rect, DT_LEFT, rgbFontColour); //Draw the text

 m_pFont->End();

}

In the constructor of CFont, we use the CreateFont function to get a handle to a new font (HFONT). Now, to use this font in your DirectX application you need to create a font object for the device. To do this, we use the D3DXCreateFont function, passing in the device pointer and font handle. We then store the resulting font pointer (LPD3DXFONT) as a member variable of the CFont class.

In the destructor we simply release the stored font object.

The final function, DrawText, renders the text. We pass in the text to render, the x and y position of the upper left corner (in screen coordinates) and the colour of the text. We use two calls to the DrawTextA method. The first calculates the dimensions of the bounding rectangle of the text and the second draws the text inside the rectangle. The text is left aligned inside the rectangle.

To use the CFont class we use the following code to create a CFont pointer:

m_pFont = new CFont(m_pD3DDevice, "Verdana", 12, false, false, false);

Once we have a pointer to the CFont object we can start drawing text. So, we have a new CGame method called RenderText which is called from inside the main Render method.

void CGame::RenderText() {

 //Draw some text at the top of the screen showing stats

 char buffer[255];

 DWORD dwDuration = (timeGetTime() – m_dwStartTime) / 1000;

 if (dwDuration > 0) {

  sprintf(buffer, "Duration: %d seconds. Frames: %d. FPS: %d.", dwDuration, m_dwFrames, (m_dwFrames / dwDuration));

 } else {

  sprintf(buffer, "Calculating…");

 }

 m_pFont->DrawText(buffer, 0, 0, D3DCOLOR_XRGB(255, 255, 255));

}

So for every frame, the RenderText method is called. RenderText simply displays the length of time that the application has been running (in seconds), the number of frames so far and the average FPS count.

Steps for 2D in 3D Rendering

Now, to create 2D graphics is really pretty simple. All we need to do, is the following steps for each frame:

· Setup camera for 3D as usual

· Enable the z-buffer and lighting

· Render 3D objects as usual

· Setup camera for 2D

· Disable the z-buffer and lighting

· Render 2D objects

2D Camera

To setup the camera for 2D objects we need to change the projection matrix from a perspective one used in 3D to an orthogonal projection. When using a perspective projection, objects that are further away from the camera are smaller than objects that are closer to the camera. When using an orthogonal projection, an object will be the same size no matter how far it is from the camera. For example, when you use your 3D modelling program, you normally get three orthogonal projections (top, side and front) and a perspective projection (3D). The code for setting the camera for 2D is shown below:

void CGame::Setup2DCamera() {

 D3DXMATRIX matOrtho;

 D3DXMATRIX matIdentity;

 //Setup the orthogonal projection matrix and the default world/view matrix

 D3DXMatrixOrthoLH(&matOrtho, (float)m_nScreenWidth, (float)m_nScreenHeight, 0.0f, 1.0f);

 D3DXMatrixIdentity(&matIdentity);

 m_pD3DDevice->SetTransform(D3DTS_PROJECTION, &matOrtho);

 m_pD3DDevice->SetTransform(D3DTS_WORLD, &matIdentity);

 m_pD3DDevice->SetTransform(D3DTS_VIEW, &matIdentity);

 //Make sure that the z-buffer and lighting are disabled

 m_pD3DDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);

 m_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

}

In addition to changing the projection matrix, we also set the world and view matrices to an identity matrix and disable the z-buffer and lighting. We disable the z-buffer so that everything that is rendered from then on will appear on top of objects that have already been rendered (3D objects).

2D Objects

A 2D object is really two textured triangles in a triangle strip that form a rectangle. The z value for each of the vertices is the same so that the 2D object faces the camera flat. I have created a class called CPanel which we will use as our 2D object.

CPanel is very similar to the objects we have already created. It has a vertex buffer which contains four vertices (one for each corner of the panel), and the panel is centred about the origin. The code for CPanel is shown below.

CPanel::CPanel(LPDIRECT3DDEVICE8 pD3DDevice, int nWidth, int nHeight, int nScreenWidth, int nScreenHeight, DWORD dwColour) {

 m_pD3DDevice = pD3DDevice;

 m_pVertexBuffer = NULL;

 m_pTexture = NULL;

 m_nWidth = nWidth;

 m_nHeight = nHeight;

 m_nScreenWidth = nScreenWidth;

 m_nScreenHeight = nScreenHeight;

 m_dwColour = dwColour;

 //Initialize Vertex Buffer

 if (CreateVertexBuffer()) {

  if (UpdateVertices()) {

   LogInfo("<li>Panel created OK");

   return;

  }

 }

 LogError("<li>Panel failed to create");

}

CPanel::~CPanel() {

 SafeRelease(m_pTexture);

 SafeRelease(m_pVertexBuffer);

 LogInfo("<li>Panel destroyed OK");

}

bool CPanel::CreateVertexBuffer() {

 //Create the vertex buffer from our device.

 if (FAILED(m_pD3DDevice->CreateVertexBuffer(4 * sizeof(PANEL_CUSTOMVERTEX), 0, PANEL_D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pVertexBuffer))) {

  LogError("<li>CPanel: Unable to create vertex buffer.");

  return false;

 }

 return true;

}

bool CPanel::UpdateVertices() {

 PANEL_CUSTOMVERTEX* pVertices = NULL;

 m_pVertexBuffer->Lock(0, 4 * sizeof(PANEL_CUSTOMVERTEX), (BYTE**)&pVertices, 0);

 if (m_dwColour == –1) {

  //No colour was set, so default to white

  m_dwColour = D3DCOLOR_XRGB(255, 255, 255);

 }

 //Set all the vertices to selected colour

 pVertices[0].colour = m_dwColour;

 pVertices[1].colour = m_dwColour;

 pVertices[2].colour = m_dwColour;

 pVertices[3].colour = m_dwColour;

 //Set the positions of the vertices

 pVertices[0].x = –(m_nWidth) / 2.0f;

 pVertices[0].y = –(m_nHeight) / 2.0f;

 pVertices[1].x = –(m_nWidth) / 2.0f;

 pVertices[1].y = m_nHeight / 2.0f;

 pVertices[2].x = (m_nWidth) / 2.0f;

 pVertices[2].y = –(m_nHeight) / 2.0f;

 pVertices[3].x = (m_nWidth) / 2.0f;

 pVertices[3].y = m_nHeight / 2.0f;

 pVertices[0].z = 1.0f;

 pVertices[1].z = 1.0f;

 pVertices[2].z = 1.0f;

 pVertices[3].z = 1.0f;

 //Set the texture coordinates of the vertices

 pVertices[0].u = 0.0f;

 pVertices[0].v = 1.0f;

 pVertices[1].u = 0.0f;

 pVertices[1].v = 0.0f;

 pVertices[2].u = 1.0f;

 pVertices[2].v = 1.0f;

 pVertices[3].u = 1.0f;

 pVertices[3].v = 0.0f;

 m_pVertexBuffer->Unlock();

 return true;

}

bool CPanel::SetTexture(const char *szTextureFilePath, DWORD dwKeyColour) {

 if (FAILED(D3DXCreateTextureFromFileEx(m_pD3DDevice, szTextureFilePath, 0, 0, 0, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, dwKeyColour, NULL, NULL, &m_pTexture))) {

  return false;

 }

 return true;

}

DWORD CPanel::Render() {

 m_pD3DDevice->SetStreamSource(0, m_pVertexBuffer, sizeof(PANEL_CUSTOMVERTEX));

 m_pD3DDevice->SetVertexShader(PANEL_D3DFVF_CUSTOMVERTEX);

 if (m_pTexture != NULL) {

  m_pD3DDevice->SetTexture(0, m_pTexture);

  m_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);

 } else {

  m_pD3DDevice->SetTexture(0, NULL);

 }

 m_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

 return 2; //Return the number of polygons rendered

}

void CPanel::MoveTo(int x, int y) {

 //x and y specify the top left corner of the panel in screen coordinates

 D3DXMATRIX matMove;

 x –= (m_nScreenWidth / 2) – (m_nWidth / 2);

 y –= (m_nScreenHeight / 2) – (m_nHeight / 2);

 D3DXMatrixTranslation(&matMove, (float)x, –(float)y, 0.0f);

 m_pD3DDevice->SetTransform(D3DTS_WORLD, &matMove);

}

The only slight changes from usual are that the SetTexture and Render methods have changed to enable texture transparencies (we will look at this in a moment).

There is also a new function called MoveTo which will move the panel from the centre of the screen, to any position you specify. It takes two parameters x and y which are screen coordinates for the top left corner of the panel. We then use the normal matrix translation functions to move the panel. We can also rotate the panel by using the normal rotation matrices.

Texture Transparency

The first thing to do is to enable alpha blending so that we can use transparent textures. We do this with a few calls to SetRenderState, the code to enable alpha blending is shown below:

//Enable alpha blending so we can use transparent textures

m_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);

//Set how the texture should be blended (use alpha)

m_pD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);

m_pD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

There are two further changes to make. The first is the way we load the textures. We need to use the D3DXCreateTextureFromFileEx function, this will enable us to specify a key colour. This means that any pixel in the texture that is the same colour as the key colour will be made transparent. Fig 11.1, 11.2 and 11.3 below show the three textures that I have used for this tutorial. I have specified that the black pixels should be transparent.

D3DXCreateTextureFromFileEx(m_pD3DDevice, szTextureFilePath, 0, 0, 0, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, dwKeyColour, NULL, NULL, &m_pTexture);

Fig 11.1

Fig 11.2

Fig 11.3

The second change is how the texture should be rendered. The SetTextureStageState function has been changed in the Render method of CPanel to render the texture using transparencies.

m_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);

From the last tutorial we have three rotating spaceships. We've added some 2D elements to the scene, each with some transparency in the texture. The final scene when rendered will look something like the screenshot below:

Summary

Now, we have completed the basics to rendering graphics in DirectX. There is lots more advanced topics in DirectX Graphics which we will cover in future tutorials. In the next tutorial we will see how to add some user interaction with the keyboard and mouse using DirectInput, vital to any game.