首页 > 代码库 > 龙书学习笔记(二)

龙书学习笔记(二)

补线代之余抽空把第四章上色学了,之所以说之余,是因为这一章内容确实不怎么多,不过为了巩固知识,便结合刚学的上色又做了一个小程序。

首先进行回顾,这一章学到的一共有四点:

一、Direct3D中颜色用RGB(Red、Green、Blue)三元组表示,用两种结构来保存

  1. D3DCOLOR,即unsigned long,共32位,分成4个8位项,分别保存Alpha(这玩意的作用会在第七章学到)、红、绿、蓝,均在0x00~0xff之间取值(就是0~255)
  2. 通过结构体来保存(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

然后图形在旋转过程中依旧有几片三角元在我眼前消失又出现,留待以后解决......

截图:

龙书学习笔记(二)