一个复杂的3D模型可能由成千上万个三角形组成,而2D图像基本都是由两个三角形组成的四边形组成。如何绘制一个四边形呢:
在图中,我们用四个顶点组成了一个正方形,这四个顶点分别是v0,v1,v2,v3。为了正确描述这个正方形,我们需要根据这四个顶点创建两个三角形△V0V1V2 ,和 △V0V2V3,而这两个三角形的顶点数据,会依次保存在顶点缓冲区中。我们使用VS2019创建一个新的项目“D3D_03_Quad”,将上面绘制三角形的代码全部复制过来。因为我们之间已经做好函数封装,因此在这里,我们只需要修改initScene函数和renderScene函数即可。
// 定义初始化场景函数
void initScene(HWND hwnd, HINSTANCE hInstance) {
// 四边形顶点数组,数组元素012构成一个三角形,数组元素345构成另一个三角形
// 图元类型必须是 D3DPT_TRIANGLELIST,表示离散性顶点组成三角形
// 相对于 D3DPT_TRIANGLESTRIP 类型,多出了V0和V2两个顶点
//
// V1 *********** V2
// * * *
// * * *
// * * *
// * * *
// V0 *********** V3
//
D3D_DATA_VERTEX vertexArray[] =
{
// 红色三角形V0V1V2,左下角顺时针
{ 300.0f, 400.0f, 0.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0) },
{ 300.0f, 200.0f, 0.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0) },
{ 500.0f, 200.0f, 0.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0) },
// 蓝色三角形V0V2V3,左下角顺时针
{ 300.0f, 400.0f, 0.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255) },
{ 500.0f, 200.0f, 0.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255) },
{ 500.0f, 400.0f, 0.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255) },
};
// 创建顶点缓冲区对象
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();
}
我们发现,绘制一个三角形和四边形的区别在于,顶点数组数据不一样,其他都一样。这里需要大家注意的就是这个四边形是由那两种方向的三角形组成的,这个东西是固定的。我们不能够使用其他方向的三角形来绘制出同样的四边形。
// 定义渲染场景函数
void renderScene(HWND hwnd) {
D3DDevice->SetStreamSource(
0, // 表示顶点缓存连接的数据流,默认0即可
D3DVertexBuffer, // 表示顶点缓存数据
0, // 表示数据流中偏移量,默认0即可
sizeof(D3D_DATA_VERTEX)); // 表示每个顶点结构的大小
D3DDevice->SetFVF(D3D_FVF_VERTEX); // 设置灵活顶点格式
D3DDevice->DrawPrimitive(
D3DPT_TRIANGLELIST, // 表示要绘制的图元类型,D3DPT_TRIANGLELIST表示三角形列表
0, // 指定顶点缓存读取顶点数据的起始索引
2); // 绘制三角形图元数量(两个三角形组成一个四边形)
}
我们发现,绘制一个三角形和四边形几乎相同,区别仅在于DrawPrimitive函数的最后一个参数,三角形的个数是不一样的。一个三角形就是1,两个三角形就是2。运行效果如下:
三角形是Direct3D中绘制图形的基本单元,当我们绘制图形的时候,可能会使用大量的三角形组合来完成。上文中我们使用两个三角形来绘制一个四边形,共计使用了6个顶点。但是现实中一个四边形也只有4个顶点,也就是说我们多用了2个顶点。为了解决这个问题,Direct3D加入了索引缓冲区来解决这个问题。一般情况下,顶点缓冲区和索引缓冲区都是搭配使用的。顶点缓冲区保存了物体模型的所有顶点数据,这些顶点是唯一的。而索引缓冲区则是保存了按照三角形构造物体的顶点在顶点缓冲区中的索引值,通过索引查找对应的顶点,来绘制整个图形。例如我们绘制一个四边形,顶点缓冲区只保存4个顶点数据,而索引缓冲区则是保存了6个顶点索引值。这样一来,绘制一个四边形需要的数据就少很多了,性能也提升很多。关于如何使用顶点缓冲区和索引缓冲区绘制一个四边形,我们不在详细介绍,大家可以自行下载阅读这个部分。这个项目个名称是“D3D_03_Index”。
我们上面绘制的三角形和四边形的顶点类型是D3DFVF_XYZRHW,也就是说它的顶点的坐标单位就是像素。但是,如果我们使用D3DFVF_XYZ的话,它就不是像素单位了。在DirectX的3D空间中的单位是一个虚拟单位,它不可能与现实世界的单位(CM或M,甚至像素)来做对比,但是可以将这个单位理解成现实世界的某一个单位。举个例子,我绘制一个边长为100的四边形,那么这个四边形到底有多大呢?它在屏幕上显示多大呢?DirectX的单位是虚拟的,但是屏幕的像素单位是真实存在的,例如1366*768。假如一张图片的尺寸就是1366*768的话,那么打开这个图片的话,它会正好覆盖整个屏幕。假如一个3D模型的尺寸同样也是1366*768的话,那么这个模型在屏幕上会显示会占满整个屏幕吗?答案是,也不是。这个取决于透视摄像机与3D模型之间的距离。在3D场景中,距离透视摄像机近的物体大,反之就小。这里的大小就是屏幕像素的大小。也就是说,透视摄像机距离模型近的话,它在屏幕上呈现的就会大一些,反之,就会小一些。因为DirectX里面的单位与屏幕的像素单位是受摄像机影响的,它不是一个固定的转换关系。也就是说,我们制作一个10大小的模型和一个100大小的模型没有任何的区别,因为我们可以调整摄像机的距离,让两个模型在同一屏幕上显示相同的像素大小。单位存在的另一个意义在于,在制作多个模型的时候,相互之间要有一个大小的参考。例如,一个人物模型的高度为180,一颗大树模型的高度就不应该小于180。因为这样有悖于我们的认知常理,一棵大树比人还低。
本课程的所有代码案例下载地址:
备注:这是我们游戏开发系列教程的第二个课程,这个课程主要使用C++语言和DirectX来讲解游戏开发中的一些基础理论知识。学习目标主要依理解理论知识为主,附带的C++代码能够看懂且运行成功即可,不要求我们使用DirectX来开发游戏。课程中如果有一些错误的地方,请大家留言指正,感激不尽!