第三章 DirectX 图形绘制(上)

在计算机的3D世界中,所有的物体模型都是通过多边形网格来逼近表示的,这些多边形可以是三角形,也可以是四边形。多边形网格是构成物体模型的基本单元。从存储的角度出发,三维模型本质是通过顶点来实现的。在Direct3D中,顶点缓冲区(Vertex Buffer)用来保存顶点数据的。顶点数据的存储位置可以在内存中,也可以在显卡的显存中。为了讲解清楚顶点缓冲区的使用方法,我们来看一个使用顶点缓冲区构建一个三角形的例子。首先我们使用VS2019创建一个名为“D3D_03_Triangle”的项目,然后创建“main.cpp”源码文件。同时,不要忘了在“链接器”下的“系统”项目中,将“子系统”的值由“控制台”改为“窗口”,还要添加DirectX的支持,也就是添加“包含目录”和“库目录”。虽然比较繁琐,但是这些修改都是必须的。首先,我们还是需要引入头文件,代码如下:

// 引入头文件
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <string>

// 引入依赖的库文件
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")

#define WINDOW_LEFT		200				// 窗口位置
#define WINDOW_TOP		100				// 窗口位置
#define WINDOW_WIDTH	800				// 窗口宽度
#define WINDOW_HEIGHT	600				// 窗口高度
#define WINDOW_TITLE	L"D3D游戏开发"	// 窗口标题
#define CLASS_NAME		L"D3D游戏开发"	// 窗口类名

// Direct3D设备指针对象
LPDIRECT3DDEVICE9 D3DDevice = NULL;

// 鼠标位置
int mx = 0, my = 0;

使用顶点缓冲区(Vertex Buffer)绘制三角形之前,我们需要先定义顶点的结构体,我们这里只定义顶点的坐标和颜色即可,然后我们在声明一个顶点缓存对象,代码如下:

// 定义FVF灵活顶点格式结构体
struct D3D_DATA_VERTEX { FLOAT x, y, z, rhw; DWORD color; };

// D3DFVF_XYZRHW 像素坐标(窗体左上角为坐标系原点,坐标单位也是像素),不需要投影变换
#define D3D_FVF_VERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

// 顶点缓冲区对象
LPDIRECT3DVERTEXBUFFER9 D3DVertexBuffer = NULL;

我们定义了一个结构体对象和一个顶点格式宏。这两个东西是对应关系。结构体中的x,y,z,rhw就对应了宏里面的D3DFVF_XYZRHW,而结构体中的color就对应了宏里面的D3DFVF_DIFFUSEDIFFUSE就是漫反射颜色,也就是物体的颜色。这里需要注意的是,我们定义的顶点类型为D3DFVF_XYZRHW,它的意思是说我们的顶点坐标就是窗体的2维坐标系。D3DFVF_XYZRHW(x, y, z, w)形式的齐次坐标,它的坐标值(x, y)是基于窗体坐标系而言的,坐标系的原点(0,0)位于左上角,且x向右为正,y向下为正。而其中z是象素深度,取值范围:0.01.0,离观察者最近的地方为0.0,观察范围内最远可见的地方为1.0w一般情况下只用1.0即可。

我们之前讲过,3D模型绘制在2D屏幕上,需要投影变换。而2D图形绘制在2D屏幕上的时候,因为维度相同,因此可以直接绘制,不需要投影变换。相对于D3DFVF_XYZRHW类型的顶点,另一种D3DFVF_XYZ的类型,它则是绘制3D图形,它就需要进行投影变换了。D3DFVF_XYZRHW通常用于做UI(用户界面),并且D3DFVF_XYZRHW是高洛德光照,而D3DFVF_XYZ默认的为无光照的。

上述顶点格式宏中我们只是简单的构建了顶点的坐标数据和颜色数据,其实我们还可以定义更多,比如法线向量和纹理坐标等等。需要注意的是,我们在书写灵活顶点格式的宏定义的时候需要遵守一个顺序原则,顺序就是优先级需要这样来分:

坐标位置>RHW值>混合权重值>法线向量>漫反射颜色值>镜面反射颜色值>纹理坐标

接下来,我们继续声明几个函数,用于完成不同的功能,代码如下:

// 声明游戏开始函数(初始化DirectX)
bool startGame(HWND hwnd, HINSTANCE hInstance);

// 声明游戏结束函数(释放对象)
void endGame();

// 声明游戏循环中处理用户输入函数
void update(int type, WPARAM wParam);

// 声明游戏循环中游戏界面渲染函数
void render(HWND hwnd);

// 声明窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

以上的函数,我们上一章节都使用过了,这里不在详细介绍。我们将上一章节中的wWinMain函数,WndProc函数,startGame函数全部复制过来。这里我们要使用DirectX绘制三角形了,不需要打印文字了,所以就不需要D3DFont对象了,大家可以删除或注释掉。本案例中,我们不需要对鼠标和键盘事件进行处理,因此update方法留空即可。

// 定义游戏循环中处理用户输入函数
void update(int type, WPARAM wParam) {};

接下来,我们就行复制render函数过来,因为我们不绘制文字了,因此绘制文字的代码删除或者注释掉即可,代码如下:

// 定义游戏循环中游戏界面渲染函数
void render(HWND hwnd) {

	// 第一步:清屏操作
	D3DDevice->Clear(0,NULL,D3DCLEAR_STENCIL | D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(255, 255, 255),1.0f,0);

	// 第二步:开始绘制
	D3DDevice->BeginScene();

	// 第三步:绘制三角形

	// 第四步:结束绘制
	D3DDevice->EndScene();

	// 第五步:显示翻转
	D3DDevice->Present(NULL,NULL,NULL,NULL);
}

最后我们在给出endGame函数代码,如下:

// 定义游戏结束函数(释放对象)
void endGame() {

	D3DVertexBuffer->Release();
	D3DVertexBuffer = NULL;
	D3DDevice->Release();
	D3DDevice = NULL;
}

此时,一个干干净净的DirectX程序就准备好了,大家可以运行一下,保证代码复制和改动都是正确的。因为很多代码都是重复固定的,为了不破坏这些固定的代码,我们重新定义两个新函数,专门用来初始化游戏数据和绘制游戏画面,如下:

// 声明初始化场景函数
void initScene(HWND hwnd, HINSTANCE hInstance);

// 声明渲染场景函数
void renderScene(HWND hwnd);

第一个函数initScene肯定是在DirectX初始化完毕后调用了,也就是之前我们实例化D3DFont对象的位置,就是在startGame函数的末尾,代码如下:

// 第五步:释放Direct3D接口对象
D3D9->Release();
D3D9 = NULL;

// 初始化场景
initScene(hwnd, hInstance);
return true;

第二个函数renderScene就是放在render函数的第三步,代码如下:

// 第二步:开始绘制
D3DDevice->BeginScene();

// 第三步:绘制场景
renderScene(hwnd);

// 第四步:结束绘制
D3DDevice->EndScene();

两个函数的声明和调用都完成了,接下来我们开始定义第一个函数initScene来绘制三角形。我们知道一个三角形由三个顶点构成,因此我们需要提供三个顶点的数据。至于这三个顶点的位置,我们就根据窗体坐标系来定义了。我们还定义了三个顶点的颜色为红色。也就是说,绘制出来的三角形应该是红色的。代码如下:

// 三角形顶点数组,窗体左上角是坐标原点,顶点从左下方顺时针开始
D3D_DATA_VERTEX vertexArray[] =
{
	{ 300.0f, 400.0f, 0.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0) },
	{ 400.0f, 200.0f, 0.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0) },
	{ 500.0f, 400.0f, 0.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0) },
};

// 创建顶点缓冲区对象
D3DDevice->CreateVertexBuffer(
	sizeof(vertexArray),	// 表示顶点数据的字节大小
	0,					    // 表示使用缓存的额外信息,默认0即可。
	D3D_FVF_VERTEX,	        // 表示灵活顶点格式
	D3DPOOL_DEFAULT,	    // 表示顶点缓存的存储位置,D3DPOOL_DEFAULT表示显卡的显存中
	&D3DVertexBuffer,		// 表示顶点缓存指针对象
	NULL);				    // 表示保留参数,在此设置为NULL

// 将三角形顶点数组写入顶点缓冲区对象
void* ptr;
D3DVertexBuffer->Lock(
	0,					    // 表示从什么位置开始加锁(存储区偏移量)
	sizeof(vertexArray),	// 表示要锁定的字节数,也就是加锁区域的大小
	(void**)&ptr,			// 表示被锁定的存储区的指针
	0);					    // 表示锁定方式,这里我们设置0即可
// 赋值顶点数据到缓冲区
memcpy(ptr, vertexArray, sizeof(vertexArray));
D3DVertexBuffer->Unlock();

这里需要注意的是,向顶点缓冲区对象中写入顶点数据的时候,需要加锁和解锁。这些DirectX API的参数基本上在注视中给到大家了,理解就可以了,不需要深入研究。我们这需要知道,使用三个顶点来绘制一个三角形,将顶点数据放入到顶点缓存对象中。接下来我们就来完成renderScene函数,绘制一个三角形了。代码如下:

D3DDevice->SetStreamSource(
	0,								// 表示顶点缓冲区连接的数据流,默认0即可
	D3DVertexBuffer,				// 表示顶点缓冲区数据
	0,								// 表示数据流中偏移量,默认0即可
	sizeof(D3D_DATA_VERTEX));		// 表示每个顶点结构的大小

D3DDevice->SetFVF(D3D_FVF_VERTEX);	// 设置灵活顶点格式

D3DDevice->DrawPrimitive(
	D3DPT_TRIANGLELIST,	        // 表示要绘制的图元类型,D3DPT_TRIANGLELIST表示三角形列表
	0,					        // 指定顶点缓存读取顶点数据的起始索引
	1);					        // 绘制三角形图元数量

绘制的过程主要由三个函数完成,SetStreamSource就是设置绘制的数据来源于我们的顶点缓存对象。SetFVF函数就是设置顶点格式。DrawPrimitive函数就是绘制三角形了。运行代码效果如下:

为什么要绘制一个三角形。因为在游戏引擎中,2D图像和3D模型都是由三角形组成的。

 本课程的所有代码案例下载地址:

workspace.zip

备注:这是我们游戏开发系列教程的第二个课程,这个课程主要使用C++语言和DirectX来讲解游戏开发中的一些基础理论知识。学习目标主要依理解理论知识为主,附带的C++代码能够看懂且运行成功即可,不要求我们使用DirectX来开发游戏。课程中如果有一些错误的地方,请大家留言指正,感激不尽!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咆哮的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值