(注:【D3D11游戏编程】学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~)

这次我们来学习几种常见的基本几何体的绘制方法,包含网格、球、圆柱等。很多复杂的几何图形都是由众多这些基本几何体组成的。而且,在水面渲染、地形渲染当中,都要使用到网格,因此掌握网格的基本生成方法很有必要。此外,有了这么多种几何体的绘制方法,我们在后面的程序中(学习导入3D模型之前)就可以绘制各种有趣图形了,而不是每次都是单调、乏味的立方体。。。

1. 程序框架新增内容

首先说下继上次实现的基本程序框架后,这次新增的内容。

当然,最大的更新显示是常见的几何体的绘制,包括立方体、网格、圆柱、球,实现了这些绘制算法,每次通过简单的一句调用,即可生成各种大小、不同的几何体了。

其次是鼠标的操作,为了对我们绘制的场景有更加直观的观察,我们从现在开始加入了鼠标的操作,通过鼠标可以方便地旋转场景、伸缩镜头来从各个角度、各个距离来观察绘制的场景。尽管这些功能依然很有限,但相比之前只能从一个角度观察场景,已经舒服多了。在后面我们会学习自己来实现一个第一人称的照相机,到时即可随意地在场景里面走动、观察了。

2. 基本几何体绘制方法

下面进入本文的重点,即这些几何体的绘制算法。

2.1 网格

网格应该是这些几何体当中最重要的一个了,其应用范围很广,包括水面、地形等。一般来讲,在生成一个网格之前,我们要指定该网格的宽和高(width、height),以及在宽、高上划分的格子数(m、n)。默认情况,也是绝大多数情况下,该网格位于x、z平面上,坐标系原点位于网格正中心。如下图所示:

图中宽为w,高为d。相应格子数为m、n。这样,每行顶点数为m+1,每列顶点数为n+1。格子宽、高分别为dx=width/m,dz=height/n。左上角顶点处坐标为(-0.5*width,0.5*height)。有了这些参数,整个网格上的顶点坐标就很容易生成了。我们可以通过一个二维的循环来实现,从左上角开始,向右、向下进行。

//每行顶点数、每列顶点数
UINT nVertsRow = m + 1;
UINT nVertsCol = n + 1;
//起始x、z坐标
float oX = -width * 0.5f;
float oZ = height * 0.5f;
//每一格纹理坐标变化
float dx = width / m;
float dz = height /n;
for(UINT i=0; i<nVertsCol; ++i)
{float tmpZ = oZ - dz * i;for(UINT j=0; j<nVertsRow; ++j){UINT index = nVertsRow * i + j;mesh.vertices[index].pos.x = oX + dx * j;mesh.vertices[index].pos.y = 0.f;mesh.vertices[index].pos.z = tmpZ;}
}

这样网格的顶点就生成了,下面是索引生成方法:

索引是为了组合成三角形而用的,因此我们的思路即逐个三角形进行。在网格中只存在一个个的矩形,我们需要把每个矩形拆成两个三角形。显然,沿对角线拆开即可。如下图所示:

我们依然用一个二维的循环,逐个遍历矩形(m*n个),如图为针对i行j列处的矩形,沿B、C拆成两个三角形。因此我们要构建的索引即针对ABC和BDC两个三角形。通过简单的数学计算,我们知道,对于 i 行 j 列(我们用[i,j]来表示)的矩形ABDC来说,四个顶点的坐标可以表示成:A[i,j]、B[i,j+1],C[i+1,j],D[i+1,j+1]。这样就可以算出各顶点在顶点缓存中所在的位置了:A(i * nVertsRow + j),B(i * nVertsRow + j + 1),C((i + 1) * nVertsRow + j),D((i + 1) * nVertsRow + j + 1)。这样就可以创建出三角形ABC和BDC的索引了,代码如下:

//总格子数量:m * n
//因此总索引数量: 6 * m * n
UINT nIndices = m * n * 6;
mesh.indices.resize(nIndices);
UINT tmp = 0;
for(UINT i=0; i<n; ++i)
{for(UINT j=0; j<m; ++j){mesh.indices[tmp] = i * nVertsRow + j;mesh.indices[tmp+1] = i * nVertsRow + j + 1;mesh.indices[tmp+2] = (i + 1) * nVertsRow + j;mesh.indices[tmp+3] = i * nVertsRow + j + 1;mesh.indices[tmp+4] = (i + 1) * nVertsRow + j + 1;mesh.indices[tmp+5] = (i + 1) * nVertsRow + j;tmp += 6;}
}

该代码中即逐个矩形进行遍历,每个矩形包括两个三角形,共六个索引值。

有了顶点和索引的集合,网格就生成了。当然每个顶点除了位置坐标外还可以包括其他信息,比如法线、切线(用于后面的Bump Mapping),纹理坐标等,这些信息的生成可以参考附带的源代码。

2.2 圆柱

圆柱的生成其实和网格有一定的相似,毕竟,把圆柱的柱面展开后,其实就是一个网格。因此它们的索引构建基本是一样的。为了构建一个圆柱,需要提供如下信息:圆柱的上口半径(topRadius),下口半径(bottomRadius),高度(height)。此外,为了指定圆柱的精细度,还需要指定两个参数,一个为没高度方向上平均划分的个数(stack),另一个为沿圆周方向等分的个数(slice)。如果还是不理解,可以看下图:

通过该图就可以直观地理解stack和slice的意义了。即stack为垂直方向上等分的个数,slice为在360度圆周上等分的个数。等分地越多,尤其是圆周上,其越接近圆形,即表面越光滑。

先来构建顶点。我们可以发现,把圆柱沿垂直方向等分后,圆柱可以看成是stack+1行的一系列点,每一行的点位于一定半径的圆周上。通过slice可以算出一行中每个点所在的角度theta,特定一行可以通过topRadius和bottomRadius插值算出其半径tmpRadius。这样顶点的位置就可以算出来了。

依然是二维的循环,外围循环为逐行遍历,内循环为一行的圆周上所有点的遍历。代码如下:

//从上到下每个stack半径变化量:dRadius
float dRadius = (bottomRadius - topRadius) / stack;
//每个stack高度:dHeight
float dHeight = height / stack;
//每个圆周上顶点数量:slice+1
int vertsPerRow = slice + 1;
//顶点行数:stack+1
int nRows = stack + 1;
//总顶点数
int nVerts = vertsPerRow * nRows;
//总索引数
int nIndices = slice * stack * 6;
mesh.vertices.resize(nVerts);
mesh.indices.resize(nIndices);
//顶部Y坐标
float topY = height * 0.5f;
for(int i=0; i<nRows; ++i)
{float tmpY = topY - dHeight * i;float tmpRadius = topRadius + i * dRadius;for(int j=0; j<vertsPerRow; ++j){float theta = XM_2PI * j / slice;int index = i * vertsPerRow + j;mesh.vertices[index].pos = XMFLOAT3(tmpRadius*cos(theta),tmpY,tmpRadius*sin(theta));}
}

下面是索引构建,由于与网格高度相似,直接放出代码:

UINT tmp(0);
for(int i=0; i<stack; ++i)
{for(int j=0; j<slice; ++j){mesh.indices[tmp] = i * vertsPerRow + j;mesh.indices[tmp+1] = (i + 1) * vertsPerRow + j + 1;mesh.indices[tmp+2] = (i + 1) * vertsPerRow + j;mesh.indices[tmp+3] = i * vertsPerRow + j;mesh.indices[tmp+4] = i * vertsPerRow + j + 1;mesh.indices[tmp+5] = (i + 1) * vertsPerRow + j + 1;tmp += 6;}
}

此外,我们发现该圆柱不包含顶部和底部的盖子。框架库中提供了添加顶部、底部盖子的函数。其实方法很简单,顶部和底部分别是slice个三角形而已,共享一个中心顶点。相关代码可以在源代码中进行参考。

2.3 球

绘制球体,基本参数只有一个半径。此外,与圆柱一样,为了指定其精细等级,也需要提供stack和slice两个参数,意义也相似。只是这里slice不是在垂直方向上的等分,而是从上极点沿球面到下极点的180度角进行等分。通过slice和stack可以得出顶点的球面坐标,因此可以算出其直角坐标。

球面顶点的生成与圆柱一样也分为两步(尤其与圆柱很类似,我只给出基本思路,可以通过研究代码来理解):

1. 不考虑上下两个极点,与圆柱计算方法类似,生成球面(与圆柱的柱面顶点计算一样)

2. 把两个极点及相应三角形添加进来,也可以想像成添加盖子(与圆柱添加盖子过程一样)

相关代码如下:

int vertsPerRow = slice + 1;
int nRows = stack - 1;for(int i=1; i<=nRows; ++i)
{float phy = XM_PI * i / stack;float tmpRadius = radius * sin(phy);for(int j=0; j<vertsPerRow; ++j){float theta = XM_2PI * j / slice;UINT index = (i-1)*vertsPerRow+j;float x = tmpRadius*cos(theta);float y = radius*cos(phy);float z = tmpRadius*sin(theta);//位置坐标mesh.vertices[index].pos = XMFLOAT3(x,y,z);}
}

2.4 立方体

最后一个,也是最简单的一个,即立方体。一个立方体只需要提供三维方向上的长度即可,即width(X方向)、height(Y方向)、depth(Z方向)。有一点与之前绘制彩色立方体时不一样的是,我们这里构建立方体用到24个顶点(每个面4个)。而之前彩色立方体只用到了8个顶点(每个顶点被3个面共享)。这是因为在后面学习过程中我们需要顶点的法线坐标,而一个顶点相对于其连接的3个面来说,法线完全不同,因此无法共享顶点。之前的例子由于只需要颜色信息,我们让其3个面在该顶点处共享了颜色值,因此只需要8个顶点即可。

索引创建与彩色立方体例子一样,共36个索引值(每个面包含两个三角形,共6个索引值)。

由于立方体构建十分容易,代码就不在这里列出了。

3. 本节的场景绘制

有了以上几种基本几何体的绘制方法,我们现在来关注本节当中的示例场景。在该场景中,我们放置了一个网格,作为地面;中心一个立方体,上面放置了一个圆球;对称的四个角落,分别摆放了四个圆柱,圆柱上分别放置一个圆球。场景截图如下:

该程序一方面是为了展示一下我们构建几何体的效果,更重要的是学习D3D11中如何让多个物体共享同一个顶点/索引缓冲区。

在该程序中,我们一共构建了四种几何体:网格、立方体、圆柱、球。这四种物体的顶点全部放置于同一个缓冲区中,索引也一样。这样在绘制相应的物体时,我们就需要在缓冲区中找到其对应的位置。在D3D11中,为了在顶点、索引缓冲区中找到一个物体对应的位置,我们使用三个参数:该物体在顶点缓冲区中的起始位置(VStart),索引缓冲区中的起始位置(IStart),以及索引总数(totalIndices)。

如下图所示:

在该图示例中,球、立方体、圆柱三种物品共享顶点缓冲区和索引缓冲区。在Global Vertex Buffer中,我们可以看到各自的起始位置(VStart)及顶点个数,在下面可以看到相应的索引起始位置(IStart)及索引个数。比如我们要绘制立方体,我们需要的三个参数即为:firstBoxVertexPos,firstBoxIndex和numBoxIndices。通过这三个参数来调用D3D11中绘制函数即可,绘制函数原型如下:

void DrawIndexed([in]  UINT IndexCount,[in]  UINT StartIndexLocation,[in]  INT BaseVertexLocation
);

IndexCount为相应物体索引个数,对应上面立方体的:numBoxIndices;

StartIndexLocation对应上面立方体的:firstBoxIndex;

BaseVertexLocation对应上面立方体的:firstBoxVertexPos。

因此,只要在构建顶点、索引缓冲区时记录下每个物体的顶点起始位置、索引起始位置、索引总数,即可在绘制时通过指定不同的变换矩阵、纹理等随意绘制它。

4. 最新框架代码+示例程序

本节完,以下最最新的框架代码及本节的示例程序:

操作方法:鼠标左键按下拖动旋转场景,右键按下拖动调整镜头的远近。

最新框架、示例程序代码

【D3D11游戏编程】学习笔记十一:基本几何体绘制相关推荐

  1. 【逐梦旅程Windows游戏编程学习笔记 ①】基本GDI绘图

    近半年各种忙碌,一直没写博客,现在得空学习记录一下,原书为<逐梦旅程 Windows游戏编程之从零开始>毛星云编著 实现样式与功能: 实现功能: 1,显示title:"致我们.. ...

  2. DirectX 11游戏编程学习笔记之1: 开场白

    本文由哈利_蜘蛛侠原创,转载请注明出处!有问题欢迎联系2024958085@qq.com 这是我之前的博客系列"DirectX9.0c游戏开发手记之'龙书'第二版学习笔记"的平行版 ...

  3. java2d游戏代码_JAVA游戏编程学习笔记(三)Java 2D游戏底层绘图框架

    前二篇记录了java如何绘制图形与动画,今天打算总结复习一下,把这些知识点集合起来,制作一个Java2D小游戏框架(暂且这么叫,好像挺牛逼似的!). Java AWT 下边提供一个 class Can ...

  4. 3D游戏编程学习笔记(七):模型与动画

    一.前言 本次3D游戏编程我们将设计一个智能巡逻兵游戏. 二.游戏基本内容及规定 游戏内容部分 创建一个地图和若干巡逻兵(使用动画): 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址.即每次 ...

  5. 3D游戏编程学习笔记(五):与游戏世界交互

    一.前言 本次3D游戏编程我们将设计一个简单打飞碟(Hit UFO)有游戏. 二.游戏基本内容及规定 游戏基本内容 游戏有 n 个 round,每个 round 都包括10 次 trial: 每个 t ...

  6. Windows游戏编程学习笔记

    1.文件类型: ipch文件夹和.sdf文件:这两个文件都是Visual Studio用来保存预编译的头文件和Intellisense用的.(可删除,对于工程开发没有影响) Debug文件夹:存放着编 ...

  7. Python微信打飞机游戏编程学习笔记01

    刚学习Python,看别人写的小游戏,照搬照学照写,纯手工手打,一步步,加深印象,加深学习 运行环境是: Python 3.7.1   pygame 1.9.4 微信很火的打飞机游戏拿了学习下 第一步 ...

  8. Python微信打飞机游戏编程学习笔记02

    继上一段的代码.继续完善中 此段代码主要 1.增加了主飞机的载入,并且是动态效果的主飞机 2.增加了主飞机的移动控制 终于有了游戏互动的感觉...继续加油 import pygame #导入pygam ...

  9. 【D3D11游戏编程】学习笔记十八:模板缓冲区的使用、镜子的实现

    (注:[D3D11游戏编程]学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~) 模板缓冲区(Stencil Buffe ...

最新文章

  1. c语言sprt的程序怎么用,sqrt函数在c语言中怎么用?
  2. java有没有求组合的函数_如何在Java 8中使用compose和andThen组合函数
  3. 描述java源程序构成_Java第二章Java程序设计
  4. LINUX文件系统介绍
  5. mysql+server+80_Windows Server 2019 IIS10.0+PHP(FastCGI)+MySQL环境搭建教程
  6. 【20120517】【早晨】
  7. 开关问题(模板+高斯消元)
  8. 执行`sudo apt-get install xx`命令时,报错问题解决
  9. 7 vsphere 分配许可_外企公司员工Office 365权限是否已分配
  10. iOS UISwitch控件
  11. python在线朗读-python朗读软件
  12. 切图教程,app切图命名总结
  13. 漫画 | 中间件到底是什么东西?
  14. python-cheatsheet,一款很全的Python小抄库
  15. python中[x for x in range(n)]列表推导式
  16. java单例模式构造器初始化_秒懂java单例模式,java私有构造器与一夫一妻制
  17. 如何激励你的内容团队产出更好的创意
  18. imba命令_Imba简介:兼容JavaScript的语言,可快速实现DOM更新
  19. Finder教程——了解 Mac 上的“访达”
  20. 飞越680pro+pixhack装机有感

热门文章

  1. You're Not Late! You're Not Early
  2. linux 查看硬盘报错_linux中挂载硬盘报错(you must specify the filesystem type)
  3. String[]数组初始化
  4. SQliteDatabase相关操作的工具类
  5. Python实战——ESIM 模型搭建(keras版)
  6. linux学习——linux中文件属主、属组是什么意思
  7. 未来蓝牙新方向之一【AoA室内定位】
  8. 安卓系统能运行 linux,重磅!安卓系统竟能运行PC软件,实测效果令人惊在当场!...
  9. php 照片上加水印字体——类库封装
  10. MyEclipse 2013优化技巧