天天看點

Direct-X學習筆記--紋理映射

一.介紹

之前學習了怎樣繪制物體,還畫了個DX自帶的茶壺,然而這個東東并不怎麼好看....離我們現實的物體簡直相隔千裡。隻能說像美術他們用來寫生的模型...那麼要怎麼樣才能讓我們的東西看起來更像真實的物體呢?這個就要用到今天學習的紋理映射技術了...

紋理映射(Texture Mapping),又稱紋理貼圖,是将紋理空間中的紋理像素映射到螢幕空間中的像素的過程。簡單來說,就是把一幅圖像貼到三維物體的表面上來增強真實感,可以和光照計算、圖像混合等技術結合起來形成許多非常漂亮的效果。總之,紋理映射就是我們用一幅2D圖檔貼在3D物體上,讓物體表面呈現為2D圖檔的樣子,使物體看起來更加真實。

我們一般把紋理映射所使用的2D圖像稱作紋理貼圖。Direct3D支援多種的紋理貼圖,比如有.bmp、.dds、.dib、.png以及.tga等等,為了提高程式使用紋理的效率,通常使用邊長為2的N次方幂的正方形圖檔。

介紹就這麼多,開始動手。

二.過程

建立紋理貼圖分為四個步驟:定點的定義,定點的通路,紋理的建立,紋理的啟用

1.頂點的定義:

在使用定點緩存或者索引緩存繪圖的時候,我們定義過定點,不過當時的定點是隻包括了坐标和頂點的顔色,在這裡,我們就不需要定義定點的顔色了,因為繪制物體的顔色會由紋理的顔色來确定。我們隻需要在定義坐标的時候,再添加一個紋理坐标(u,v)即可。

struct stVertex
{
	float _x, _y, _z;		//位置坐标
	float _u, _v;			//紋理坐标

	stVertex(float x, float y, float z, float u, float v) : _x(x), _y(y), _z(z), _u(u), _v(v){}
	stVertex(){}
};
           

定義靈活頂點的格式:

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX1)
           

這樣,我們的頂點格式就定義好啦!

2.頂點的通路:

與頂點緩沖繪圖一樣,定義過定點之後,需要給定點指派,這次我們指派坐标之後,還要給紋理坐标指派:例如:

vertex[0] = stVertex(-10.0f,  10.0f, -10.0f, 0.0f, 0.0f);
           

當然,其他的步驟與之前一樣,也需要鎖住緩沖區再操作,然後還要解鎖。

3.紋理的建立:

這裡就是我們的新内容啦,建立紋理一般使用這麼一個函數:D3DXCreateTextureFromFile

有三個參數,第一個為D3D裝置接口指針,第二個為紋理圖檔的路徑名,第三個為紋理的指針。

例如:

D3DXCreateTextureFromFile(g_pDevice, TEXT("texture.png"), &g_pTexture);
           

4.紋理的啟用:

建立好了紋理,我們就可以在繪制的時候使用啦,使用紋理很簡單,在繪制物體之前,使用SetTexture函數即可:

函數有兩個參數,第一個為紋理是哪一層,我們知道,一共可以建立8層紋理,是以這個參數的取值就為0-7啦。第二個就是我們的紋理指針啦。

g_pDevice->SetTexture(0, g_pTexture);
           

三.例子

下面是一個完整的例子: 我們通過索引緩存繪圖,繪制一個立方體:

沒有加紋理之前是介個樣子滴:

Direct-X學習筆記--紋理映射

然後我們使用一個磚牆的紋理圖檔,加上紋理之後效果棒棒哒:

Direct-X學習筆記--紋理映射

下面是紋理建立的完整的代碼:

// D3DDemo.cpp : 定義應用程式的入口點。
//

#include "stdafx.h"
#include "D3DDemo.h"


#define MAX_LOADSTRING 100

// 全局變量:
HINSTANCE hInst;								// 目前執行個體
TCHAR szTitle[MAX_LOADSTRING];					// 标題欄文本
TCHAR szWindowClass[MAX_LOADSTRING];			// 主視窗類名

// 此代碼子產品中包含的函數的前向聲明:
HWND                g_hWnd;
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);

//---------改造3D視窗需要的内容------------
LPDIRECT3D9 g_pD3D = NULL; //D3D接口指針
LPDIRECT3DDEVICE9 g_pDevice = NULL;//D3D裝置指針
LPDIRECT3DTEXTURE9 g_pTexture = NULL;//D3D紋理接口對象


//------------繪制圖形步驟1.定義靈活頂點格式
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX1)//坐标為經過變換的螢幕坐标,頂點的顔色

//------------繪制圖形步驟2.根據上面定義的頂點格式,建立一個頂點的結構體
struct stVertex
{
	float _x, _y, _z;		//位置坐标
	float _u, _v;			//紋理坐标

	stVertex(float x, float y, float z, float u, float v) : _x(x), _y(y), _z(z), _u(u), _v(v){}
	stVertex(){}
};

//----------繪制圖形步驟3.聲明一個頂點緩沖區指針&一個索引緩沖區指針
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;

//初始化頂點緩沖區
void initVB()
{
	//----------繪制圖形步驟4.定義一個結構體數組用來給每個頂點指派
	//數組中存儲目前程式中頂點的資料

	
	stVertex vertex[24];

	// 正面頂點資料
	vertex[0] = stVertex(-10.0f,  10.0f, -10.0f, 0.0f, 0.0f);
	vertex[1] = stVertex( 10.0f,  10.0f, -10.0f, 1.0f, 0.0f);
	vertex[2] = stVertex( 10.0f, -10.0f, -10.0f, 1.0f, 1.0f);
	vertex[3] = stVertex(-10.0f, -10.0f, -10.0f, 0.0f, 1.0f);

	// 背面頂點資料
	vertex[4] = stVertex( 10.0f,  10.0f, 10.0f, 0.0f, 0.0f);
	vertex[5] = stVertex(-10.0f,  10.0f, 10.0f, 1.0f, 0.0f);
	vertex[6] = stVertex(-10.0f, -10.0f, 10.0f, 1.0f, 1.0f);
	vertex[7] = stVertex( 10.0f, -10.0f, 10.0f, 0.0f, 1.0f);

	// 頂面頂點資料
	vertex[8]  = stVertex(-10.0f, 10.0f,  10.0f, 0.0f, 0.0f);
	vertex[9]  = stVertex( 10.0f, 10.0f,  10.0f, 1.0f, 0.0f);
	vertex[10] = stVertex( 10.0f, 10.0f, -10.0f, 1.0f, 1.0f);
	vertex[11] = stVertex(-10.0f, 10.0f, -10.0f, 0.0f, 1.0f);

	// 底面頂點資料
	vertex[12] = stVertex(-10.0f, -10.0f, -10.0f, 0.0f, 0.0f);
	vertex[13] = stVertex( 10.0f, -10.0f, -10.0f, 1.0f, 0.0f);
	vertex[14] = stVertex( 10.0f, -10.0f,  10.0f, 1.0f, 1.0f);
	vertex[15] = stVertex(-10.0f, -10.0f,  10.0f, 0.0f, 1.0f);

	// 左側面頂點資料
	vertex[16] = stVertex(-10.0f,  10.0f,  10.0f, 0.0f, 0.0f);
	vertex[17] = stVertex(-10.0f,  10.0f, -10.0f, 1.0f, 0.0f);
	vertex[18] = stVertex(-10.0f, -10.0f, -10.0f, 1.0f, 1.0f);
	vertex[19] = stVertex(-10.0f, -10.0f,  10.0f, 0.0f, 1.0f);

	// 右側面頂點資料
	vertex[20] = stVertex( 10.0f,  10.0f, -10.0f, 0.0f, 0.0f);
	vertex[21] = stVertex( 10.0f,  10.0f,  10.0f, 1.0f, 0.0f);
	vertex[22] = stVertex( 10.0f, -10.0f,  10.0f, 1.0f, 1.0f);
	vertex[23] = stVertex( 10.0f, -10.0f, -10.0f, 0.0f, 1.0f);

	//----------繪制圖形步驟5.為定點緩沖區配置設定記憶體,并将數組中的頂點值拷貝到頂點緩沖區中
	//通過裝置指針來建立頂點緩沖區,用來存儲頂點資料
	g_pDevice->CreateVertexBuffer(
		sizeof(vertex),					//頂點緩沖區大小
		D3DUSAGE_WRITEONLY,				//頂點緩沖區作用
		D3DFVF_CUSTOMVERTEX,			//通知系統頂點格式
		D3DPOOL_MANAGED,				//頂點緩沖區存儲位置,此處表示由系統處理
		&g_pVB,							//傳回頂點緩沖區指針
		NULL							//系統保留參數,NULL
		);

	void* pVertices = NULL;

	//鎖定頂點緩沖區,向其中拷貝資料
	g_pVB->Lock(
		0,								//鎖定的偏移量
		sizeof(vertex),					//鎖定的大小
		&pVertices,						//鎖定之後存儲空間
		0								//鎖定的辨別,0
		);

	//将數組中的内容拷貝到緩沖區中
	memcpy(pVertices, vertex, sizeof(vertex));

	//解鎖
	g_pVB->Unlock();

}

//初始化索引緩沖區
void initIB()
{
	//建立索引緩沖區
	g_pDevice->CreateIndexBuffer(
		48 * sizeof(WORD),      //緩沖區大小
		0,						//緩沖區屬性
		D3DFMT_INDEX16,			//索引大小,一般采用16位
		D3DPOOL_DEFAULT,		//存儲位置,預設為顯示卡緩存
		&g_pIB,					//索引緩沖區指針的指針
		NULL					//保留參數,NULL即可
		);

	WORD *pIndices = NULL;
	//鎖緩沖區
	g_pIB->Lock(0, 0, (void**)&pIndices, 0);
	//拷貝資料
	
	// 正面索引資料
	pIndices[0] = 0; pIndices[1] = 1; pIndices[2] = 2;
	pIndices[3] = 0; pIndices[4] = 2; pIndices[5] = 3;

	// 背面索引資料
	pIndices[6] = 4; pIndices[7]  = 5; pIndices[8]  = 6;
	pIndices[9] = 4; pIndices[10] = 6; pIndices[11] = 7;

	// 頂面索引資料
	pIndices[12] = 8; pIndices[13] =  9; pIndices[14] = 10;
	pIndices[15] = 8; pIndices[16] = 10; pIndices[17] = 11;

	// 底面索引資料
	pIndices[18] = 12; pIndices[19] = 13; pIndices[20] = 14;
	pIndices[21] = 12; pIndices[22] = 14; pIndices[23] = 15;

	// 左側面索引資料
	pIndices[24] = 16; pIndices[25] = 17; pIndices[26] = 18;
	pIndices[27] = 16; pIndices[28] = 18; pIndices[29] = 19;

	// 右側面索引資料
	pIndices[30] = 20; pIndices[31] = 21; pIndices[32] = 22;
	pIndices[33] = 20; pIndices[34] = 22; pIndices[35] = 23;


	//解鎖
	g_pIB->Unlock();
}


void onCreatD3D()
{
	g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if (!g_pD3D)
		return;

	//檢測硬體裝置能力的方法
	/*D3DCAPS9 caps;
	ZeroMemory(&caps, sizeof(caps));
	g_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);*/

	//獲得相關資訊,螢幕大小,像素點屬性
	D3DDISPLAYMODE d3ddm;
	ZeroMemory(&d3ddm, sizeof(d3ddm));

	g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);


	//設定全屏模式
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	/*d3dpp.Windowed = false;
	d3dpp.BackBufferWidth = d3ddm.Width;
	d3dpp.BackBufferHeight = d3ddm.Height;*/

	d3dpp.Windowed = true;
	d3dpp.BackBufferFormat = d3ddm.Format;
	d3dpp.BackBufferCount = 1;

	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;//交換後原緩沖區資料丢棄

	//是否開啟自動深度模闆緩沖
	d3dpp.EnableAutoDepthStencil = true;
	//目前自動深度模闆緩沖的格式
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;//每個像素點有16位的存儲空間,存儲離錄影機的距離
	

	g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pDevice);

	if (!g_pDevice)
		return;

	//設定渲染狀态,設定啟用深度值
	g_pDevice->SetRenderState(D3DRS_ZENABLE, true);

	//設定渲染狀态,關閉燈光
	g_pDevice->SetRenderState(D3DRS_LIGHTING, false);

	//設定渲染狀态,裁剪模式
	g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

	//g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE) ;

}

void createTexture()
{
	//通過檔案名建立紋理
	D3DXCreateTextureFromFile(g_pDevice, TEXT("texture.png"), &g_pTexture);
}

void onInit()
{
	//初始化D3D
	onCreatD3D();

	//初始化頂點緩沖區
	initVB();
	//初始化索引緩沖區
	initIB();
	//建立紋理
	createTexture();
}



void onDestroy()
{
	if (!g_pDevice)
		g_pDevice->Release();
	g_pDevice = NULL;
}

void onLogic(float fElapsedTime)
{
	
}

void Transform()
{
	//WorldTransform:世界變換
	D3DXMATRIXA16 matWorld;
	//生成繞Y軸旋轉矩陣,存儲于矩陣中
	D3DXMatrixRotationY(
		&matWorld,		        //輸出矩陣
		timeGetTime()/150.0f	//角度
		);
	g_pDevice->SetTransform(D3DTS_WORLD, &matWorld);


	//ViewTransform:取景變換

	D3DXVECTOR3 vEyePt(0.0f, 0.0f, -100.0f);		//錄影機世界坐标
	D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);	//觀察點世界坐标
	D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);		//錄影機的上向量,通常為(0.0f, 1.0f, 0.0f)
	D3DXMATRIXA16 matView;						//View變換的矩陣
	//根據上面的結果計算出矩陣,存入矩陣中
	D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);
	//進行取景變換
	g_pDevice->SetTransform(D3DTS_VIEW, &matView);

	//ProjectionTransform:投影變換

	D3DXMATRIXA16 matProj;					//投影變換矩陣
	//生成投影變換矩陣,存入上面的矩陣中
	D3DXMatrixPerspectiveFovLH(
		&matProj,	     //輸出結果矩陣
		D3DX_PI / 4,	 //視域角度,一般為PI/4
		1.0f,			 //顯示屏的長寬比
		1.0f,			 //視截體中近截面距離錄影機的位置
		100.0f			 //視截體中遠截面距離錄影機的位置
		);
	//進行投影變換
	g_pDevice->SetTransform(D3DTS_PROJECTION, &matProj);

	//ViewportTransform:視口變換
	D3DVIEWPORT9 vp = {
		0,			    //視口的左上角X坐标
		0,				//視口的左上角Y坐标
		800,			//視口的寬度
		500,			//視口的高度
		0,			    //深度緩存中的最小深度值
		1				//深度緩存中的最大深度值
	};
	g_pDevice->SetViewport(&vp);
}

void onRender(float fElasedTime)
{
	//前兩個參數是0和NULL時,清空整個遊戲視窗的内容(清的是背景)
	//第三個是清除的對象:前面表示清除顔色緩沖區,後面表示清除深度緩沖區,D3DCLEAR_STENCIL清空模闆緩沖區
	g_pDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,100,100), 1.0f, 0);

	g_pDevice->BeginScene();

	Transform();

	//----------繪制圖形步驟6.設定資料源,設定靈活頂點格式,繪制圖元

		//設定資料流來源
	g_pDevice->SetStreamSource(
		0,						//資料流管道号(0-15)
		g_pVB,					//資料來源
		0,						//資料流偏移量
		sizeof(stVertex)		//每個資料的位元組數大小
		);

		//通知系統資料格式,以便解析資料
	g_pDevice->SetFVF(D3DFVF_CUSTOMVERTEX);

	繪制圖元 
	//g_pDevice->DrawPrimitive(
	//	D3DPT_TRIANGLESTRIP,     //三角形列
	//	0,						//起始點編号
	//	15						//圖元數量
	//	);

	//設定索引緩存
	g_pDevice->SetIndices(g_pIB);

	g_pDevice->SetTexture(0, g_pTexture);

	//使用索引緩存繪制圖形
	g_pDevice->DrawIndexedPrimitive(
		D3DPT_TRIANGLELIST, //三角形列
		0,				    //頂點起點,從那個頂點開始做為索引
		0,					//最小索引值,通常為0
		24,					//索引頂點的個數
		0,					//起點索引,從第幾個索引處開始繪制圖元
		12					//圖元個數
		);

	g_pDevice->EndScene();


	g_pDevice->Present(NULL, NULL, NULL, NULL);
}


int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO: 在此放置代碼。
	MSG msg;
	HACCEL hAccelTable;

	// 初始化全局字元串
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_D3DDEMO, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// 執行應用程式初始化:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_D3DDEMO));

	

	ZeroMemory(&msg, sizeof(msg));
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			static DWORD dwTime = timeGetTime();
			DWORD dwCurrentTime = timeGetTime();
			DWORD dwElapsedTime = dwCurrentTime - dwTime;
			float fElapsedTime = dwElapsedTime * 0.001f;

			//------------渲染和邏輯部分代碼----------
			onLogic(fElapsedTime);
			onRender(fElapsedTime);
			//-----------------------------------------
			if (dwElapsedTime < 1000 / 60)
			{
				Sleep(1000/ 60 - dwElapsedTime);
			}
			dwTime = dwCurrentTime;
		}
	}

	onDestroy();
	return (int) msg.wParam;
}



//
//  函數: MyRegisterClass()
//
//  目的: 注冊視窗類。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_D3DDEMO));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_D3DDEMO);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   函數: InitInstance(HINSTANCE, int)
//
//   目的: 儲存執行個體句柄并建立主視窗
//
//   注釋:
//
//        在此函數中,我們在全局變量中儲存執行個體句柄并
//        建立和顯示主程式視窗。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{

   hInst = hInstance; // 将執行個體句柄存儲在全局變量中

   g_hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!g_hWnd)
   {
      return FALSE;
   }

 

   SetMenu(g_hWnd, NULL);
   ShowWindow(g_hWnd, nCmdShow);
   UpdateWindow(g_hWnd);

   onInit();

   return TRUE;
}

//
//  函數: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的: 處理主視窗的消息。
//
//  WM_COMMAND	- 處理應用程式菜單
//  WM_PAINT	- 繪制主視窗
//  WM_DESTROY	- 發送退出消息并傳回
//
//
LRESULT CALLBACK WndProc(HWND g_hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_KEYDOWN:
		if (wParam == VK_ESCAPE)
			PostQuitMessage(0);
		break;
	case WM_CLOSE:
		DestroyWindow(g_hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(g_hWnd, message, wParam, lParam);
	}
	return 0;
}