补线代之余抽空把第四章上色学了,之所以说之余,是因为这一章内容确实不怎么多,不过为了巩固知识,便结合刚学的上色又做了一个小程序。
首先进行回顾,这一章学到的一共有四点:
一、Direct3D中颜色用RGB(Red、Green、Blue)三元组表示,用两种结构来保存
- D3DCOLOR,即unsigned long,共32位,分成4个8位项,分别保存Alpha(这玩意的作用会在第七章学到)、红、绿、蓝,均在0x00~0xff之间取值(就是0~255)
- 通过结构体来保存(D3DXCOLOR和D3DCOLORVALUE),用float r、float g、float b,float a四个float数据成员来替代第一种结构的存储方式。其中,每个数据成员的亮度区间均为0~1。另外,由于D3DXCOLOR包含了一系列的运算符重载和构造函数,而D3DCOLORVALUE只包含4个数据成员,所以通常使用前者而非后者。
二、颜色可通过D3DCOLOR_ARGB宏和D3DCOLOR_XRGB宏来取值,其中后者只是前者将Alpha设置为0xff后的简化版:
D3DCOLOR brightRed = D3DCOLOR_ARGB(255, 255, 0, 0);D3DXCOLOR brightRed = D3DCOLOR_XRGB(255, 0, 0); #define D3DCOLOR_XRGB(r, g, b) D3DCOLOR_ARGB(0xff, r, g, b)
三、为了添加颜色,需要在顶点结构体中添加相应的数据成员,而FVF也要改变相应设置:
struct ColorVertex { ColorVertex(){} ColorVertex(float x, float y, float z, D3DCOLOR c) { _x = x; _y = y; _z = z; _color = c; } float _x, _y, _z; D3DCOLOR _color; static const DWORD FVF; }; const DWORD ColorVertex::FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;
四、到龙书第四章为止,给出的着色方式有两种,分别为平面找色和Gouraud着色(也称平滑着色)。前者将每个图元的每个像素一致赋予第一个顶点做指定的颜色,后者则不会忽略另外两个顶点的颜色,而是会在各种颜色之间进行平滑的过渡,设置方式分别为:
Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT); // 平面着色 Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); // 平滑着色
接下来便是详细解释自己完成的小程序,这玩意由两个旋转的蓝色水晶(想不出其它名字了......)组成,每个水晶由两个底面重合的四棱锥构成,采用默认的面填充方式,着色模式分别为平面着色和平滑着色。
首先需要指明的是,这段程序要在龙书提供的d3dUtility.h头文件中添加下列全局颜色常量:
namespace d3d{ . . . const D3DXCOLOR WHITE (D3DCOLOR_XRGB(255, 255, 255)); const D3DXCOLOR BLACK (D3DCOLOR_XRGB( 0, 0, 0)); const D3DXCOLOR RED (D3DCOLOR_XRGB(255, 0, 0)); const D3DXCOLOR GREEN (D3DCOLOR_XRGB( 0, 255, 0)); const D3DXCOLOR BLUE (D3DCOLOR_XRGB( 0, 0, 255)); const D3DXCOLOR YELLOW (D3DCOLOR_XRGB(255, 255, 0)); const D3DXCOLOR CYAN (D3DCOLOR_XRGB( 0, 255, 255)); const D3DXCOLOR MAGENTA(D3DCOLOR_XRGB(255, 0, 255)); }
然后开始主要的源代码,依旧是从全局变量Device、屏幕高度Height和屏幕宽度Width的定义开始:
/* * File: crystal.cpp * * Author: EnoWang * * Desc: 绘制了两个水晶,每个水晶由两个底面重合的蓝色四棱锥构成, * 分别采用平面着色模式和平滑着色模式。 * */ #include "d3dUtility.h" IDirect3DDevice9* Device; const int Width = 640; const int Height = 480;
之后定义全局变量WorldMatrix,即世界变换矩阵,在这个简单的程序中,它的作用是在世界坐标系中设置某个水晶的中心点坐标。
下一步是顶点缓存和索引缓存的定义:
// 世界变换矩阵 D3DXMATRIX WorldMatrix; IDirect3DVertexBuffer9* verb = 0; IDirect3DIndexBuffer9 * indb = 0;
接下来开始定义顶点结构体:
// 创建顶点结构体 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;
正如之前所说,因为这次需要在绘制的图形上添加颜色,所以结构体的构成和FVF也发生了变化。
首先,相比上一个程序,结构体中多出了一个D3DCOLOR类型的颜色数据成员。同时书中强调,这里无法使用D3DCOLORVALUE结构,原因是,Direct3D希望用一个32位的值来描述顶点的颜色,而非一个结构体。
而相应的,作为顶点格式的标记,FVF的值也需要做出改变,在这里,将单纯描述空间的D3DFVF_XYZ修改成了同时描述空间和颜色的D3DFVF_XYZ | D3DFVF_DIFFUSE。
现在开始框架函数的定义,由于相比前一个程序的改变并非十分明显,所以依旧分为创建顶点缓存和索引缓存、锁定并对缓存写入数据、设置摄像机、实施投影变换和设置渲染模式五个步骤,首先进行第一步:
// 框架函数 bool Setup() { // 创建顶点缓存和索引缓存 Device->CreateVertexBuffer( 6 * sizeof(Vertex), // 每个水晶包含6个顶点 D3DUSAGE_WRITEONLY, Vertex::FVF, D3DPOOL_MANAGED, &verb, 0); Device->CreateIndexBuffer( 24 * sizeof(WORD), // 每个水晶包含八个三角形面 D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &indb, 0);
接下来锁定并向缓存中写入数据:
// 将数据写入缓存 Vertex* vertics; verb->Lock(0, 0, (void**)&vertics, 0); vertics[0] = Vertex(-1.0f, 0.0f,-1.0f, d3d::BLUE); vertics[1] = Vertex( 1.0f, 0.0f,-1.0f, d3d::BLUE); vertics[2] = Vertex( 1.0f, 0.0f, 1.0f, d3d::BLUE); vertics[3] = Vertex(-1.0f, 0.0f, 1.0f, d3d::BLUE); vertics[4] = Vertex( 0.0f, 2.0f, 0.0f, d3d::WHITE); vertics[5] = Vertex( 0.0f,-2.0f, 0.0f, d3d::WHITE); verb->Unlock(); WORD* Indics; indb->Lock(0, 0, (void**)&Indics, 0); Indics[0] = 0; Indics[1] = 1; Indics[2] = 4; Indics[3] = 0; Indics[4] = 1; Indics[5] = 5; Indics[6] = 1; Indics[7] = 2; Indics[8] = 4; Indics[9] = 1; Indics[10] = 2; Indics[11] = 5; Indics[12] = 2; Indics[13] = 3; Indics[14] = 4; Indics[15] = 2; Indics[16] = 3; Indics[17] = 5; Indics[18] = 3; Indics[19] = 0; Indics[20] = 4; Indics[21] = 3; Indics[22] = 0; Indics[23] = 5; indb->Unlock();
虽然我需要的是两个水晶,以便进行对比,但考虑到它们只有着色模式不同,所以只需要在每一帧中,于不同的两个世界坐标将水晶各绘制一遍即可。
之后设置摄影机、投影矩阵和渲染模式:
// 设置摄影机位置 D3DXVECTOR3 position(0.0f, 0.0f, -4.0f); D3DXVECTOR3 target(0.0f, 0.0f, 0.0f); D3DXVECTOR3 up(0.0f, 1.0f, 0.0f); D3DXMATRIX Camera; D3DXMatrixLookAtLH(&Camera, &position, &target, &up); Device->SetTransform(D3DTS_VIEW, &Camera); // 设置投影矩阵 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; }
首先,由于这次程序所采用的多边形填充模式是面填充,D3DFILL_SOILD——默认的填充模式,所以也不需要像前一个程序一样进行强调,省略即可。相应的,如果将FILL_MODE修改为D3DFILL_WIREFRAME或D3DFILL_POINT,则会生成着色后的线框或顶点。
其次,考虑到已经给物体本身设置了颜色,所以就不需要光照给与颜色了,因此便将D3DRS_LIGHTING设置为false。
void Cleanup() { d3d::Release<IDirect3DVertexBuffer9*>(verb); d3d::Release<IDirect3DIndexBuffer9 *>(indb); }
接下来是绘制函数的定义,首先设置旋转角度:
bool Display(float timeDelta) { if (Device) { D3DXMATRIX Rx, Ry; D3DXMatrixRotationX(&Rx, 0.5f); static float y = 0.0f; D3DXMatrixRotationY(&Ry, y); y += 2 * timeDelta; if (y > 6.28f) y = 0.0f;
之后开始绘制图像,这一次,我既想改变物体的世界坐标,又想改变物体的角度,并让物体产生旋转的效果,而改变绘制状态的SetTransform函数只能接受一个矩阵。
首先尝试着用了两次SetTransform,但是编译后的程序在运行后,窗口中的图像只有后一个SetTransform的效果。之后又思索半响,便参考之前Rx,Ry两个矩阵的相乘,把WorldMatrix也放入了矩阵相乘的队列里,居然成功的让结果产生了既旋转也改变世界坐标的效果。
于是便感觉自己大致明白了矩阵相乘在D3D中的一部分用法(当然,数学原理是什么我依旧不太明白,继续补线代吧......)
// 开始绘制 Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, d3d::WHITE, 1.0f, 0); Device->BeginScene(); // 将图形设置信息写入数据流 Device->SetStreamSource(0, verb, 0, sizeof(Vertex)); Device->SetIndices(indb); Device->SetFVF(Vertex::FVF); // 第一个水晶,采用平滑着色(Gouraud着色)模式 // 设置世界坐标系中水晶的中心点坐标 D3DXMatrixTranslation(&WorldMatrix, -2.0f, 0.0f, 0.0f); D3DXMATRIX Res = Rx * Ry * WorldMatrix; Device->SetTransform(D3DTS_WORLD, &Res); Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); // 设置为平滑着色 Device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 6, 0, 8); // 第二个水晶,采用平面着色模式 D3DXMatrixTranslation(&WorldMatrix, 2.0f, 0.0f, 0.0f); // 前一个水晶沿顺时针方向旋转,这一个水晶沿逆时针 Res = Rx * Ry * WorldMatrix; Device->SetTransform(D3DTS_WORLD, &Res); Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT); // 设置为平面着色 Device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 6, 0, 8); Device->EndScene(); // 结束绘制 Device->Present(0, 0, 0, 0); // 提交后台缓存 } return true; }
最后完成与上一个程序并无区别的回调函数和Windows主函数:
// 回调函数 LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_DESTROY: ::PostQuitMessage(0); break; // 按下Esc键退出 case WM_KEYDOWN: if (wParam == VK_ESCAPE) ::DestroyWindow(hwnd); break; } return ::DefWindowProc(hwnd, msg, wParam, lParam); } // Windows主函数 int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd) { if (!d3d::InitD3D(hinstance, Width, Height, true, D3DDEVTYPE_HAL, &Device)) { ::MessageBox(0, "InitD3D() - FAILED", 0, 0); return 0; } if (!Setup()) { ::MessageBox(0, "Setup() - FAILED", 0, 0); return 0; } d3d::EnterMsgLoop(Display); Cleanup(); Device->Release(); return 0; }
编译需要d3dUtility.h和d3dUtility.cpp,头文件的源码可以在这里下载:http://www.d3dcoder.net
然后图形在旋转过程中依旧有几片三角元在我眼前消失又出现,留待以后解决......
截图: