天天看點

DirectX3D遊戲開發三 繪制流水線

    歡迎來到EasyLiu的部落格 !

    參考:directxtutorial.com   DirectX.9.0.3D遊戲開發程式設計基礎

     In the last lesson you built a simple, flat triangle lit with simple diffuse lighting. This triangle was not 3D, it was flat. If you managed to change it, you found it was a 2D triangle drawn in screen coordinates. The triangle you made was pre-transformed, meaning you took no action to convert it from 3D coordinates to screen coordinates, but just gave Direct3D the screen coordinates.

   翻譯:在上一節課中我們建立了一個簡單的,具有簡單漫反射光的平面三角形。這個三角形不是3D的,它是平面的。如果你想改變它,你會發現它是一個平面坐标下的二維三角形。你畫的這個三角形是預先經過轉換的,意思是你并沒有采取任何措施而将它從三維坐标變換成了螢幕坐标,而是直接把三角形的螢幕坐标給了Direct3D進行繪制。

   In this lesson, you will learn to transform vertices, or convert them from 3D coordinates to screen coordinates. You will also learn to position the 3D camera, set the 3D "lens", and put your triangle into a 3D world with other 3D objects in it.

   翻譯:在這節課,我們将學習頂點的轉換或者三維坐标到螢幕坐标的轉換。我們也将學習放置3D攝像頭,設定3D“透鏡”,并且把你的三角形放入具有其它三維物體的世界中。

繪制流水線

    一個簡單的繪制流水線如下圖所示:需要經過坐标變換、背面消隐、光照、裁剪、投影、視口坐标系、光栅化等過程,這裡主要講一下局部坐标系到世界坐标系的轉換(世界變換)、世界坐标系到觀察坐标系的轉換(取景變換)、投影(投影變換)。

DirectX3D遊戲開發三 繪制流水線

   The Geometry Pipeline(幾何管道)

    Before we get into the actual transformation code, let's take a look at everything that happens to translate something from 3D coordinates to a flat image. There is a sequence of actions which must occur before an object appears on the screen properly. This sequence of actions is known as theGeometry Pipeline. It is called a pipeline because vertices are put through each step one at a time, and at each step along the "pipe", the vertex is rendered into its flat image form.

    When a model first starts out, it is generally centered on the origin, meaning the center of the object is at (0, 0, 0). This also means that all the objects in your game will be in the exact center of the world, all piled up on top of each other, which doesn't make a very excellent game. The first step is to sort out the new positions of all the models in relation to each other.This is called World Transformation.

    Once all the objects in the world have been sorted out and the vertices' positions have been altered to represent world coordinates, then we have to change the entire coordinate system to create a virtual camera. What this means is that we have to change the direction of each axis, in order to position everything in the most efficient way for the video card. This process is known as View Transformation.

    After the coordinate system has been rearanged, then it is time to convert all the 3D models into 2D images. In this step, the 3D coordinates are converted into screen coordinates. This process is known as Projection Transformation.

    上面介紹了世界轉換、視圖轉換(取景變換)以及投影變換的概念以及為什麼要進行這些變換,下圖代表了變換的過程:

DirectX3D遊戲開發三 繪制流水線

World Transformation(世界變換概述)

   World Transformation, in essence, changes coordinates from model space to world space. In other words, it places a model in a world at an exact point defined by coordinates. Following is an overhead demonstration of how this works:

DirectX3D遊戲開發三 繪制流水線

    This is known as translation. Translation refers to the movement of a vertex along a coordinate system axis. For example, the above diagram shows movement of a tree along the x and y axes. Of course, we could also translate the tree along the z-axis, but our ground here is flat, and having trees floating ten feet high just outside the local village tends to scare the tourists away (not to mention the players).

Of course, being able to move an object into world space is always useful, but it can get rather limiting if your model is always facing the same direction. A spaceship that can only face East is rather dull and does not elicit much thrill or adrenaline (or so I find).

And so another part of World Transformation is rotation(世界變換的另一部分是旋轉). Rotation is the process of spinning 3D objects along an axis. Like translation, it can be done along multiple axes simultaneously, allowing you to position your model as desired.

DirectX3D遊戲開發三 繪制流水線

Another important part of World Transformation is scaling(世界變換中另外一個重要的變換是縮放). Scaling is the action of making a 3D object larger or smaller. When an object is scaled, each vertex in the object is multiplied by a given number. These numbers can be different for each axis, resulting in various stretching effects.

DirectX3D遊戲開發三 繪制流水線

     位于局部坐标系中的物體通過世界變換的運算變換到世界坐标系中,該變換通常包括平移、旋轉以及比例運算。世界變換通過一個矩陣來表示,并由Direct3D通過SetTransform(D3DTS_WORLD, &matWorld)方法來加以應用,第二個參數表示所采用的世界變換矩陣。例如,我們想要一個立方體的中心位于世界坐标系中的的點(-3,2,6),讓一個球體的中心位于點(5,0,-2),可以這樣實作:

D3DXMATRIXA16 cubeWorldMatrix;
	D3DXMatrixTranslation(&cubeWorldMatrix, -3.0f, 2.0f, 6.0f);

	D3DXMATRIXA16 sphereWorldMatrix;
	D3DXMatrixTranslation(&sphereWorldMatrix, 5.0f, 0.0f, -2.0f);

	g_pd3dDevice->SetTransform(D3DTS_WORLD, &cubeWorldMatrix);
	//繪制立方體
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &sphereWorldMatrix);
	//繪制球體
           
DirectX3D遊戲開發三 繪制流水線

View Transformation(取景變換)

    在世界坐标中,幾何體和錄影機是相對世界坐标系定義的。但是當錄影機的位置和朝向任意時,投影變換以及其他類型的變換就略顯困難或者效率不高。為了簡化運算,我們将錄影機變換至世界坐标系的原點,并将其旋轉,使得攝像頭的光軸與世界坐标系的Z軸正方向一緻。同時,世界坐标系中的所有幾何體随着攝像頭一同進行變換,保證攝像頭的視場恒定,變換稱為取景變換,我們稱變換後的幾何體位于觀察坐标系中,如下圖所示:

DirectX3D遊戲開發三 繪制流水線
DirectX3D遊戲開發三 繪制流水線

Project Transformation(投影變換)   

DirectX3D遊戲開發三 繪制流水線

    投影變換定義了視域體,并負責将視域體中的幾何體投影到投影視窗中。投影矩陣比較複雜,可以使用如下函數: // Build a perspective projection matrix. (right-handed)

D3DXMATRIX* WINAPI D3DXMatrixPerspectiveFovRH( D3DXMATRIX *pOut, FLOAT fovy, FLOAT Aspect, FLOAT zn, FLOAT zf );

   其中pOut代表投影之後的矩陣,Aspect代表橫縱比(投影視窗中的幾何體最終會變換到螢幕顯示區,從方形(投影視窗)到矩形的顯示屏的的變換會導緻拉伸畸變,所謂縱橫比就是顯示屏縱橫兩維尺寸的比率,長用于校正由正方形到矩形的映射而引發的畸變,縱橫比=螢幕寬度/螢幕高度),其餘的三個參數的含義如下圖所示:fovy代表觀察範圍夾角,zn代表投影中心距離近平面的距離,zf代表投影中心距離遠平面的距離。

DirectX3D遊戲開發三 繪制流水線
DirectX3D遊戲開發三 繪制流水線

下面實作一個效果:使一個三角形繞Y軸一直旋轉。主要函數是自定義的一個函數SetupMatrices,用于設定世界、觀察、投影變換矩陣,代碼如下所示:

VOID SetupMatrices()
{
	// For our world matrix, we will just rotate the object about the y-axis.
	D3DXMATRIXA16 matWorld;

	// Set up the rotation matrix to generate 1 full rotation (2*PI radians) 
	// every 1000 ms. To avoid the loss of precision inherent in very high 
	// floating point numbers, the system time is modulated by the rotation 
	// period before conversion to a radian angle.
	UINT iTime = timeGetTime() % 1000;//擷取目前時間
	FLOAT fAngle = iTime * (2.0f * D3DX_PI) / 1000.0f;//1000ms旋轉360度
	D3DXMatrixRotationY(&matWorld, fAngle);//旋轉矩陣
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);//設定世界變換

	// Set up our view matrix. A view matrix can be defined given an eye point,
	// a point to lookat, and a direction for which way is up. Here, we set the
	// eye five units back along the z-axis and up three units, look at the
	// origin, and define "up" to be in the y-direction.
	D3DXVECTOR3 vEyePt(0.0f, 3.0f, -5.0f);//觀察點,攝像頭的位置
	D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);//朝原點看,代表攝像頭朝向
	D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);//垂直方向為y軸
	D3DXMATRIXA16 matView;
	D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);
	g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);

	// For the projection matrix, we set up a perspective transform (which
	// transforms geometry from 3D view space to 2D viewport space, with
	// a perspective divide making objects smaller in the distance). To build
	// a perpsective transform, we need the field of view (1/4 pi is common),
	// the aspect ratio, and the near and far clipping planes (which define at
	// what distances geometry should be no longer be rendered).
	D3DXMATRIXA16 matProj;
	D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4, 1.0f, 1.0f, 100.0f);
	g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);
}
           

同時在初始化裡面關閉背面消隐以及關閉光照:

// Turn off culling, so we see the front and back of the triangle
	g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

	// Turn off D3D lighting, since we are providing our own vertex colors
	g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);</span>
           

最後,Render()函數:

VOID Render()
{
	// Clear the backbuffer to a black color
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

	// Begin the scene
	if (SUCCEEDED(g_pd3dDevice->BeginScene()))
	{
		// Setup the world, view, and projection matrices
		SetupMatrices();

		// Render the vertex buffer contents
		g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
		g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
		g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 1);

		// End the scene
		g_pd3dDevice->EndScene();
	}

	// Present the backbuffer contents to the display
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
}
           

效果如下:由于是動态的,從圖檔看不出什麼效果。

DirectX3D遊戲開發三 繪制流水線

下面是main.c源代碼:

//-----------------------------------------------------------------------------
// File: Matrices.cpp
//
// Desc: Now that we know how to create a device and render some 2D vertices,
//       this tutorial goes the next step and renders 3D geometry. To deal with
//       3D geometry we need to introduce the use of 4x4 matrices to transform
//       the geometry with translations, rotations, scaling, and setting up our
//       camera.
//
//       Geometry is defined in model space. We can move it (translation),
//       rotate it (rotation), or stretch it (scaling) using a world transform.
//       The geometry is then said to be in world space. Next, we need to
//       position the camera, or eye point, somewhere to look at the geometry.
//       Another transform, via the view matrix, is used, to position and
//       rotate our view. With the geometry then in view space, our last
//       transform is the projection transform, which "projects" the 3D scene
//       into our 2D viewport.
//
//       Note that in this tutorial, we are introducing the use of D3DX, which
//       is a set of helper utilities for D3D. In this case, we are using some
//       of D3DX's useful matrix initialization functions. To use D3DX, simply
//       include <d3dx9.h> and link with d3dx9.lib.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
#pragma comment(lib,"winmm.lib") 


#include <Windows.h>
#include <mmsystem.h>
#include <d3dx9.h>
#pragma warning( disable : 4996 ) // disable deprecated warning 
#include <strsafe.h>
#pragma warning( default : 4996 )




//-----------------------------------------------------------------------------
// Global variables
//-----------------------------------------------------------------------------
LPDIRECT3D9             g_pD3D = NULL; // Used to create the D3DDevice
LPDIRECT3DDEVICE9       g_pd3dDevice = NULL; // Our rendering device
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices

// A structure for our custom vertex type
struct CUSTOMVERTEX
{
	FLOAT x, y, z;      // The untransformed, 3D position for the vertex
	DWORD color;        // The vertex color
};

// Our custom FVF, which describes our custom vertex structure
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)




//-----------------------------------------------------------------------------
// Name: InitD3D()
// Desc: Initializes Direct3D
//-----------------------------------------------------------------------------
HRESULT InitD3D(HWND hWnd)
{
	// Create the D3D object.
	if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)))
		return E_FAIL;

	// Set up the structure used to create the D3DDevice
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	d3dpp.Windowed = TRUE;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

	// Create the D3DDevice
	if (FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
		D3DCREATE_SOFTWARE_VERTEXPROCESSING,
		&d3dpp, &g_pd3dDevice)))
	{
		return E_FAIL;
	}

	// Turn off culling, so we see the front and back of the triangle
	g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

	// Turn off D3D lighting, since we are providing our own vertex colors
	g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: InitGeometry()
// Desc: Creates the scene geometry
//-----------------------------------------------------------------------------
HRESULT InitGeometry()
{
	// Initialize three vertices for rendering a triangle
	CUSTOMVERTEX g_Vertices[] =
	{
		{ -1.0f, -1.0f, 0.0f, 0xffff0000, },
		{ 1.0f, -1.0f, 0.0f, 0xff0000ff, },
		{ 0.0f, 1.0f, 0.0f, 0xffffffff, },
	};

	// Create the vertex buffer.
	if (FAILED(g_pd3dDevice->CreateVertexBuffer(3 * sizeof(CUSTOMVERTEX),
		0, D3DFVF_CUSTOMVERTEX,
		D3DPOOL_DEFAULT, &g_pVB, NULL)))
	{
		return E_FAIL;
	}

	// Fill the vertex buffer.
	VOID* pVertices;
	if (FAILED(g_pVB->Lock(0, sizeof(g_Vertices), (void**)&pVertices, 0)))
		return E_FAIL;
	memcpy(pVertices, g_Vertices, sizeof(g_Vertices));
	g_pVB->Unlock();

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: Cleanup()
// Desc: Releases all previously initialized objects
//-----------------------------------------------------------------------------
VOID Cleanup()
{
	if (g_pVB != NULL)
		g_pVB->Release();

	if (g_pd3dDevice != NULL)
		g_pd3dDevice->Release();

	if (g_pD3D != NULL)
		g_pD3D->Release();
}



//-----------------------------------------------------------------------------
// Name: SetupMatrices()
// Desc: Sets up the world, view, and projection transform matrices.
//-----------------------------------------------------------------------------
VOID SetupMatrices()
{
	// For our world matrix, we will just rotate the object about the y-axis.
	D3DXMATRIXA16 matWorld;

	// Set up the rotation matrix to generate 1 full rotation (2*PI radians) 
	// every 1000 ms. To avoid the loss of precision inherent in very high 
	// floating point numbers, the system time is modulated by the rotation 
	// period before conversion to a radian angle.
	UINT iTime = timeGetTime() % 1000;//擷取目前時間
	FLOAT fAngle = iTime * (2.0f * D3DX_PI) / 1000.0f;//1000ms旋轉360度
	D3DXMatrixRotationY(&matWorld, fAngle);//旋轉矩陣
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);//設定世界變換

	// Set up our view matrix. A view matrix can be defined given an eye point,
	// a point to lookat, and a direction for which way is up. Here, we set the
	// eye five units back along the z-axis and up three units, look at the
	// origin, and define "up" to be in the y-direction.
	D3DXVECTOR3 vEyePt(0.0f, 3.0f, -5.0f);//攝像頭位置
	D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);//攝像頭朝向
	D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);//垂直方向為Y軸
	D3DXMATRIXA16 matView;
	D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);
	g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);

	// For the projection matrix, we set up a perspective transform (which
	// transforms geometry from 3D view space to 2D viewport space, with
	// a perspective divide making objects smaller in the distance). To build
	// a perpsective transform, we need the field of view (1/4 pi is common),
	// the aspect ratio, and the near and far clipping planes (which define at
	// what distances geometry should be no longer be rendered).
	D3DXMATRIXA16 matProj;
	D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4, 1.0f, 1.0f, 100.0f);
	g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);
}



//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Draws the scene
//-----------------------------------------------------------------------------
VOID Render()
{
	// Clear the backbuffer to a black color
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

	// Begin the scene
	if (SUCCEEDED(g_pd3dDevice->BeginScene()))
	{
		// Setup the world, view, and projection matrices
		SetupMatrices();

		// Render the vertex buffer contents
		g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
		g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
		g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 1);

		// End the scene
		g_pd3dDevice->EndScene();
	}

	// Present the backbuffer contents to the display
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
}




//-----------------------------------------------------------------------------
// Name: MsgProc()
// Desc: The window's message handler
//-----------------------------------------------------------------------------
LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
	case WM_DESTROY:
		Cleanup();
		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hWnd, msg, wParam, lParam);
}




//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: The application's entry point
//-----------------------------------------------------------------------------
INT WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, INT)
{
	UNREFERENCED_PARAMETER(hInst);

	// Register the window class
	WNDCLASSEX wc =
	{
		sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
		GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
		L"D3D Tutorial", NULL
	};
	RegisterClassEx(&wc);

	// Create the application's window
	HWND hWnd = CreateWindow(L"D3D Tutorial", L"D3D Tutorial 03: Matrices",
		WS_OVERLAPPEDWINDOW, 100, 100, 256, 256,
		NULL, NULL, wc.hInstance, NULL);

	// Initialize Direct3D
	if (SUCCEEDED(InitD3D(hWnd)))
	{
		// Create the scene geometry
		if (SUCCEEDED(InitGeometry()))
		{
			// Show the window
			ShowWindow(hWnd, SW_SHOWDEFAULT);
			UpdateWindow(hWnd);

			// Enter the message loop
			MSG msg;
			ZeroMemory(&msg, sizeof(msg));
			while (msg.message != WM_QUIT)
			{
				if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
				else
					Render();
			}
		}
	}

	UnregisterClass(L"D3D Tutorial", wc.hInstance);
	return 0;
}


           

繼續閱讀