首页 > 代码库 > DirectX学习笔记(五):利用平面着色和Gouraud着色模式绘制具有颜色的三角形

DirectX学习笔记(五):利用平面着色和Gouraud着色模式绘制具有颜色的三角形

 前言:


图元的颜色是由构成该图元的顶点的颜色所决定的,而物体是由图元索组成。那么如果我们要绘制一个具有颜色的三角形,我们就需要为三角形图元顶点附加颜色属性。


1.Direct3D中颜色的表示:

在Direct3D中,颜色用RGB来表示(这个大家都知道,咳咳。)通畅人为颜色可以分为红色(red)、绿色(green)、蓝色(bule)。这三个分量进行混合,以达到我们想要的颜色取得目的。RGB的数据可以用两种方式来进行存储一种是D3DCOLOR ,另一种是:DWORD。这是因为D3DCOLOR完全等同于DWORD(在MSDN中:

typedef DWORD D3DCOLOR;)关于Alpha分量,先不进行说明。因为这个现在用不到。

要指定每个颜色分量的值,并将其插入到D3DCOLOR中,需要使用D3DCOLOR_ARPG宏。这个宏帮我们实现了二进制的运算。

使用方式:

D3DCOLOR brightRed = D3DCOLOR_ARGB(255, 255, 0, 0);


另外介绍另外一种颜色数据的存储结构:D3DCOLORVALUE

typedef struct _D3DCOLORVALUE {
    float r;
    float g;
    float b;
    float a;
} D3DCOLORVALUE;

用单精度数来度量每个分量的亮度值,取值范围为0~1.

但是这种结构很少用到。因为D3DCOLORVALUE结构可以转换为D3DCOLOR,而且D3DCOLORD3DCOLORVALUE所包含的功能方法更为强大。


 2.着色:

这里先说一下什么是光栅化:简单理解,我们在现实生活中,如果绘制一个三角形,每条线是由无数个无穷小的点构成的。但是在计算机中,没有这个无穷小的处理方法,在屏幕上的三角形是由很多点像素构成,将连续的图像用离散的像素表示的过程就是光栅化。

在光栅化的过程中,需要对三角形进行着色。着色规定了如何利用顶点颜色来计算构成像素的颜色。在Direct3D中有两种着色模式:

第一种:平面着色:每个图元的像素都被一致的赋予该图元的第一个顶点的颜色。

第二种:Gouraud着色:土元种的各像素的颜色值由顶点的颜色经线性插值得到。

那么接下来我们就用这两种着色模式来绘制两个三角形:
要完成的操作:


1.定义顶点结构:

struct Vertex //顶点结构  
{

	Vertex(){}
	Vertex(float x, float y, float z, D3DCOLOR  color)
	{
		_x = x;  _y = y;  _z = z;
		_color = color;
	}
	float _x, _y, _z;
	D3DCOLOR _color;
	static const DWORD FVF;
};
//顶点格式:说明对应于顶点格式的顶点结构包含了位置属性和漫反射颜色属性
//要注意的是,灵活顶点格式标记的指定顺序必须与顶点结构中相应类型数据的顺序保持一致。
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;


2.创建顶点缓存,将顶点颜色等数据写入:

//创建顶点缓存
	Device->CreateVertexBuffer(3 * sizeof(Vertex), D3DUSAGE_WRITEONLY, Vertex::FVF, D3DPOOL_MANAGED, &Triangle, 0);

	//访问顶点缓存,将Cube顶点数据写入  
	Vertex* vertices;
	Triangle->Lock(0, 0, (void**)&vertices, 0);
	vertices[0] = Vertex(-1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0));
	vertices[1] = Vertex(0.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(0, 255, 0));
	vertices[2] = Vertex(1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB(0, 0, 255));

	Triangle->Unlock();


3.投影变换:

在当摄像机的位置和朝向任意时,进行一些操作会造成效率低下很是麻烦。为了简化运算,我们将摄像机变换到世界坐标系的原点,并旋转摄像机使其光轴与世界坐标系的z轴朝向一致。要特别注意的是,世界中的所有物体都会随之摄像机变换,以保证摄像机的视场恒定。
我们先使用D3DXMatrixLookAtLH(...)函数来获取一个取景变换矩阵,然后利用::SetTransform(..)方法设定:

	//投影变换  
	D3DXMATRIX proj;
	D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI * 0.5f, (float)Width / (float)Height, 1.0f, 1000.0f);
	Device->SetTransform(D3DTS_PROJECTION, &proj);



4. 设置绘制状态:

Device->SetRenderState(D3DRS_LIGHTING, true);  //默认状态下光照是启用的,这里将其禁用,因为我们没有使用到。

但是显示指定并无大碍

//设置绘制状态  
	Device->SetRenderState(D3DRS_LIGHTING, false);

5.显示:

在绘制中,我们要绘制两个三角形,那么设置三角形的位置信息由世界变换矩阵World来控制:

//绘制  
	Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);//屏幕清除,设为黑色背景
	Device->BeginScene();

	Device->SetFVF(Vertex::FVF); //设置灵活顶点格式
	
	//利用SetStreamSource设置顶点输入流的来源,将顶点缓存与数据流进行链接
	//实质是将几何体的信息传到绘制流水线中
	Device->SetStreamSource(0, Triangle, 0, sizeof(Vertex));

	// 利用平面着色模式绘制左边的三角形
	//设置位置
	D3DXMatrixTranslation(&WorldMatrix, -1.25f, 0.0f, 0.0f);
	Device->SetTransform(D3DTS_WORLD, &WorldMatrix);
	//绘制
	Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
	Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

	// 利用Grouaud模式绘制右边的三角形
	D3DXMatrixTranslation(&WorldMatrix, 1.25f, 0.0f, 0.0f);
	Device->SetTransform(D3DTS_WORLD, &WorldMatrix);
	//绘制
	Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
	Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

	Device->EndScene();
	Device->Present(0, 0, 0, 0);


完整代码:(可运行)

#include<d3d9.h>  
#include<d3dx9math.h>  
#include<windows.h>  

IDirect3DDevice9* Device = 0; // 一个C++对象,代表了我们用来显示3D图形的物理硬件设备  
IDirect3DVertexBuffer9* Triangle = 0;//顶点缓存  

D3DXMATRIX WorldMatrix;
const int Width = 640; //窗口的宽度  
const int Height = 480; //高度  


//-----------------------------顶点结构体----------------------------------  
struct Vertex //顶点结构  
{

	Vertex(){}
	Vertex(float x, float y, float z, D3DCOLOR  color)
	{
		_x = x;  _y = y;  _z = z;
		_color = color;
	}
	float _x, _y, _z;
	D3DCOLOR _color;
	static const DWORD FVF;
};
//顶点格式:说明对应于顶点格式的顶点结构包含了位置属性和漫反射颜色属性
//要注意的是,灵活顶点格式标记的指定顺序必须与顶点结构中相应类型数据的顺序保持一致。
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;



//------------------------------以下为窗口过程---------------------------------  
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)//窗口过程  
{
	switch (msg)
	{
	case WM_DESTROY://销毁  
		PostQuitMessage(0); //终止请求  
		break;
	}
	//调用缺省的窗口过程来为应用程序没有处理的任何窗口消息提供缺省的处理。    
	//该函数确保每一个消息得到处理    
	return ::DefWindowProc(hwnd, msg, wParam, lParam);
}


//----------------------------以下为初始化窗口信息---------------------------------  
bool InitWindow(HINSTANCE hInstance, HWND &hwnd, int width, int height)
{
	//定义窗口样式  
	WNDCLASS wc;
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(0, IDI_APPLICATION);
	wc.hCursor = LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = 0;
	wc.lpszClassName = "LszDX";

	//窗口注册  
	RegisterClass(&wc);

	//创建窗口   
	hwnd = ::CreateWindow("LszDX", "LszDX", WS_OVERLAPPEDWINDOW, 0, 0, width, height, 0, 0, hInstance, 0);

	//绘制更新窗口   
	ShowWindow(hwnd, SW_SHOW);
	UpdateWindow(hwnd);
	return true;
}


//----------------------------以下为初始化Direct3D----------------------------------------  
//注意函数的hwnd为传引用  
bool InitD3D(HINSTANCE &hInstance, HWND &hwnd, int width, int height, bool windowed, D3DDEVTYPE deviceType, IDirect3DDevice9** device)
{
	//获取IDirect3D9的指针  
	IDirect3D9* d3d9 = 0;
	d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

	//检验硬件顶点运算  
	D3DCAPS9 caps;
	d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps);

	int vp = 0;
	if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	//填充D3DPRESENT_PARAMETERS 结构  
	D3DPRESENT_PARAMETERS d3dpp;
	d3dpp.BackBufferWidth = width;
	d3dpp.BackBufferHeight = height;
	d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
	d3dpp.BackBufferCount = 1;
	d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
	d3dpp.MultiSampleQuality = 0;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.hDeviceWindow = hwnd;
	d3dpp.Windowed = windowed;
	d3dpp.EnableAutoDepthStencil = true;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	d3dpp.Flags = 0;
	d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

	//创建IDirect3DDevice9接口  
	d3d9->CreateDevice(D3DADAPTER_DEFAULT, deviceType, hwnd, vp, &d3dpp, device);
	d3d9->Release();
	return true;
}

//投影变换  
//设置绘制状态  
bool InitTr()
{
	//创建顶点缓存
	Device->CreateVertexBuffer(3 * sizeof(Vertex), D3DUSAGE_WRITEONLY, Vertex::FVF, D3DPOOL_MANAGED, &Triangle, 0);

	//访问顶点缓存,将Cube顶点数据写入  
	Vertex* vertices;
	Triangle->Lock(0, 0, (void**)&vertices, 0);
	vertices[0] = Vertex(-1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0));
	vertices[1] = Vertex(0.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(0, 255, 0));
	vertices[2] = Vertex(1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB(0, 0, 255));

	Triangle->Unlock();


	//投影变换  
	D3DXMATRIX proj;
	D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI * 0.5f, (float)Width / (float)Height, 1.0f, 1000.0f);
	Device->SetTransform(D3DTS_PROJECTION, &proj);

	//设置绘制状态  
	Device->SetRenderState(D3DRS_LIGHTING, false);
	return true;
}

//-----------------------以下为对Cube进行绘制-------------------------------------  
bool Display()
{
	MSG msg;
	ZeroMemory(&msg, sizeof(MSG));//用0来填充消息可类比为:memset()函数    
	while (msg.message != WM_QUIT) //退出   
	{
		//PeekMessage函数是以查看的方式从系统中获取消息    
		//并将该消息(如果存在)放于指定的结构    
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			//PM_REMOVE:PeekMessage处理后,消息从队列里除掉。  

			//TranslateMessage函数将虚拟键消息转换为字符消息。  
			//字符消息被寄送到调用线程的消息队列里,    
			//当下一次线程调用函数GetMessage或PeekMessage时被读出。    
			//TranslateMessage只能用于转换调用GetMessage或PeekMessage接收的消息。    

			TranslateMessage(&msg);

			//DispatchMessage函数  
			//该函数分发一个消息给窗口程序。    
			//通常消息从GetMessage函数获得。    
			//消息被分发到回调函数(过程函数),作用是消息传递给操作系统,    
			//然后操作系统去调用我们的回调函数,也就是说我们在窗体的过程函数中处理消息    
			DispatchMessage(&msg);
		}
		else
		{
			if (Device)
			{

				//绘制  
				Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);//屏幕清除,设为白色背景
				Device->BeginScene();

				Device->SetFVF(Vertex::FVF); //设置灵活顶点格式
				
				//利用SetStreamSource设置顶点输入流的来源,将顶点缓存与数据流进行链接
				//实质是将几何体的信息传到绘制流水线中
				Device->SetStreamSource(0, Triangle, 0, sizeof(Vertex));

				// 利用平面着色模式绘制左边的三角形
				//设置位置
				D3DXMatrixTranslation(&WorldMatrix, -1.25f, 0.0f, 0.0f);
				Device->SetTransform(D3DTS_WORLD, &WorldMatrix);
				//绘制
				Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
				Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

				// 利用Grouaud模式绘制右边的三角形
				D3DXMatrixTranslation(&WorldMatrix, 1.25f, 0.0f, 0.0f);
				Device->SetTransform(D3DTS_WORLD, &WorldMatrix);

				//绘制
				Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
				Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

				Device->EndScene();
				Device->Present(0, 0, 0, 0);
			}
		}
	}
	return true;
}


//-------------------------Main函数-----------------------------------------------  
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd)
{
	HWND hwnd = 0;

	//1.窗口创建  
	InitWindow(hinstance, hwnd, Width, Height);

	//2.初始化Direct3D  
	InitD3D(hinstance, hwnd, Width, Height, true, D3DDEVTYPE_HAL, &Device);

	//3.初始化cube信息  
	InitTr();

	//4.进行显示绘制  
	Display();

	//5.释放指针  
	Device->Release();
	return 0;
}

运行效果:

技术分享

DirectX学习笔记(五):利用平面着色和Gouraud着色模式绘制具有颜色的三角形