龙书学习笔记(二)

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

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

一、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

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

截图:

时间: 2024-10-19 08:10:36

龙书学习笔记(二)的相关文章

龙书学习笔记(三)

在将第五章每个示例代码过了一遍之后,大致明白了光照这一章的内容,主要分为四点: 一.光照的类型分为三种,并且均通过结构D3DCOLORVALUE或D3DXCOLOR来表示光线的颜色 环境光(Ambient Light)经其它表面反射到达物体表面,并照亮整个场景,通常用做较低代价的粗略模拟. 漫射光(Diffuse Light)沿着特定的方向传播,到达某个表面后将沿着各个方向均匀反射,因此从各个方向观察物体表面亮度均相同. 镜面光(Specular Light)沿着特定的方向传播,到达一表面后将沿

Swift学习笔记(二)参数类型

关于参数类型,在以前的编程过程中,很多时间都忽视了形参与实参的区别.通过这两天的学习,算是捡回了漏掉的知识. 在swift中,参数有形参和实参之分,形参即只能在函数内部调用的参数,默认是不能修改的,如果想要修改就需要在参数前添加var声明. 但这样的声明过后,仍旧不会改变实参的值,这样就要用到inout了,传递给inout的参数类型必须是var类型的,不能是let类型或者字面类型,(字面类型是在swift中常提的一个术语,个人认为就是赋值语句,也不能修改)而且在传递过程中,要用传值符号"&

《SQL必知必会》学习笔记二)

<SQL必知必会>学习笔记(二) 咱们接着上一篇的内容继续.这一篇主要回顾子查询,联合查询,复制表这三类内容. 上一部分基本上都是简单的Select查询,即从单个数据库表中检索数据的单条语句,但是实际应用中的业务逻辑往往会非常复杂,所以会用到一些比较复杂的查询,如子查询,联合查询. 1.子查询 当一个查询是另一个查询的条件时,称为子查询.但是说到子查询又不的不说它与嵌套查询两者的区别,下面一张图来说明 下面再用一条sql语句来说明他们的关系. 其中在查询中又分为嵌套子查询和相关子查询,他们之间

spidering hacks 学习笔记(二)

看过去很乱,学习的记录东西而已,等我读完这本书,就把笔记给整理下!嘿嘿 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

angular学习笔记(二十四)-$http(2)-设置http请求头

1. angular默认的请求头: 其中,Accept 和 X-Requested-With是$http自带的默认配置 2. 修改默认请求头: (1) 全局修改(整个模块) 使用$httpProvider依赖 var myApp = angular.module('MyApp',[]); myApp.config(function($httpProvider){ console.log($httpProvider.defaults.headers.common) //修改/操作$httpProv

Caliburn.Micro学习笔记(二)----Actions

Caliburn.Micro学习笔记(二)----Actions 上一篇已经简单说了一下引导类和简单的控件绑定 我的上一个例子里的button自动匹配到ViewModel事件你一定感觉很好玩吧 今天说一下它的Actions,看一下Caliburn.Micro给我们提供了多强大的支持 我们还是从做例子开始 demo的源码下载在文章的最后 例子1.无参数方法调用 点击button把textBox输入的文本弹出来 如果textbox里没有文本button不可点,看一下效果图 看一下前台代码 <Stac

2. 蛤蟆Python脚本学习笔记二基本命令畅玩

2. 蛤蟆Python脚本学习笔记二基本命令畅玩 本篇名言:"成功源于发现细节,没有细节就没有机遇,留心细节意味着创造机遇.一件司空见惯的小事或许就可能是打开机遇宝库的钥匙!" 下班回家,咱先来看下一些常用的基本命令. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/48092873 1.  数字和表达式 看下图1一就能说明很多问题: 加法,整除,浮点除,取模,幂乘方等.是不是很直接也很粗暴. 关于上限,蛤蟆不太清楚

小猪的数据结构学习笔记(二)

小猪的数据结构学习笔记(二) 线性表中的顺序表 本节引言: 在上个章节中,我们对数据结构与算法的相关概念进行了了解,知道数据结构的 逻辑结构与物理结构的区别,算法的特性以及设计要求;还学了如何去衡量一个算法 的好坏,以及时间复杂度的计算!在本节中我们将接触第一个数据结构--线性表; 而线性表有两种表现形式,分别是顺序表和链表;学好这一章很重要,是学习后面的基石; 这一节我们会重点学习下顺序表,在这里给大家一个忠告,学编程切忌眼高手低,看懂不代表自己 写得出来,给出的实现代码,自己要理解思路,自己

JavaScript--基于对象的脚本语言学习笔记(二)

第二部分:DOM编程 1.文档象模型(DOM)提供了访问结构化文档的一种方式,很多语言自己的DOM解析器. DOM解析器就是完成结构化文档和DOM树之间的转换关系. DOM解析器解析结构化文档:将磁盘上的结构化文档转换成内存中的DOM树 从DOM树输出结构化文档:将内存中的DOM树转换成磁盘上的结构化文档 2.DOM模型扩展了HTML元素,为几乎所有的HTML元素都新增了innerHTML属性,该属性代表该元素的"内容",即返回的某个元素的开始标签.结束标签之间的字符串内容(不包含其它