zoukankan      html  css  js  c++  java
  • (转)【D3D11游戏编程】学习笔记十一:基本几何体绘制

    (注:【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)。有了这些参数,整个网格上的顶点坐标就很容易生成了。我们可以通过一个二维的循环来实现,从左上角开始,向右、向下进行。

    [cpp] view plain copy
    1. //每行顶点数、每列顶点数  
    2. UINT nVertsRow = m + 1;  
    3. UINT nVertsCol = n + 1;  
    4. //起始x、z坐标  
    5. float oX = -width * 0.5f;  
    6. float oZ = height * 0.5f;  
    7. //每一格纹理坐标变化  
    8. float dx = width / m;  
    9. float dz = height /n;  
    10. for(UINT i=0; i<nVertsCol; ++i)  
    11. {  
    12.     float tmpZ = oZ - dz * i;  
    13.     for(UINT j=0; j<nVertsRow; ++j)  
    14.     {  
    15.         UINT index = nVertsRow * i + j;  
    16.         mesh.vertices[index].pos.x = oX + dx * j;  
    17.         mesh.vertices[index].pos.y = 0.f;  
    18.         mesh.vertices[index].pos.z = tmpZ;  
    19.     }  
    20. }  

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

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

    我们依然用一个二维的循环,逐个遍历矩形(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的索引了,代码如下:

    [cpp] view plain copy
    1. //总格子数量:m * n  
    2. //因此总索引数量: 6 * m * n  
    3. UINT nIndices = m * n * 6;  
    4. mesh.indices.resize(nIndices);  
    5. UINT tmp = 0;  
    6. for(UINT i=0; i<n; ++i)  
    7. {  
    8.     for(UINT j=0; j<m; ++j)  
    9.     {  
    10.         mesh.indices[tmp] = i * nVertsRow + j;  
    11.         mesh.indices[tmp+1] = i * nVertsRow + j + 1;  
    12.         mesh.indices[tmp+2] = (i + 1) * nVertsRow + j;  
    13.         mesh.indices[tmp+3] = i * nVertsRow + j + 1;  
    14.         mesh.indices[tmp+4] = (i + 1) * nVertsRow + j + 1;  
    15.         mesh.indices[tmp+5] = (i + 1) * nVertsRow + j;  
    16.           
    17.         tmp += 6;  
    18.     }  
    19. }  

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

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

           2.2 圆柱

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

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

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

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

    [cpp] view plain copy
    1. //从上到下每个stack半径变化量:dRadius  
    2. float dRadius = (bottomRadius - topRadius) / stack;  
    3. //每个stack高度:dHeight  
    4. float dHeight = height / stack;  
    5. //每个圆周上顶点数量:slice+1  
    6. int vertsPerRow = slice + 1;  
    7. //顶点行数:stack+1  
    8. int nRows = stack + 1;  
    9. //总顶点数  
    10. int nVerts = vertsPerRow * nRows;  
    11. //总索引数  
    12. int nIndices = slice * stack * 6;  
    13. mesh.vertices.resize(nVerts);  
    14. mesh.indices.resize(nIndices);  
    15. //顶部Y坐标  
    16. float topY = height * 0.5f;  
    17. for(int i=0; i<nRows; ++i)  
    18. {  
    19.     float tmpY = topY - dHeight * i;  
    20.     float tmpRadius = topRadius + i * dRadius;  
    21.   
    22.     for(int j=0; j<vertsPerRow; ++j)  
    23.     {  
    24.         float theta = XM_2PI * j / slice;  
    25.         int index = i * vertsPerRow + j;  
    26.         mesh.vertices[index].pos = XMFLOAT3(tmpRadius*cos(theta),tmpY,tmpRadius*sin(theta));  
    27.     }  
    28. }  

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

    [cpp] view plain copy
    1. UINT tmp(0);  
    2. for(int i=0; i<stack; ++i)  
    3. {  
    4.     for(int j=0; j<slice; ++j)  
    5.     {  
    6.         mesh.indices[tmp] = i * vertsPerRow + j;  
    7.         mesh.indices[tmp+1] = (i + 1) * vertsPerRow + j + 1;  
    8.         mesh.indices[tmp+2] = (i + 1) * vertsPerRow + j;  
    9.         mesh.indices[tmp+3] = i * vertsPerRow + j;  
    10.         mesh.indices[tmp+4] = i * vertsPerRow + j + 1;  
    11.         mesh.indices[tmp+5] = (i + 1) * vertsPerRow + j + 1;  
    12.   
    13.         tmp += 6;  
    14.     }  
    15. }  

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

           2.3 球

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

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

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

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

           相关代码如下:

    [cpp] view plain copy
    1. int vertsPerRow = slice + 1;  
    2. int nRows = stack - 1;  
    3.   
    4. for(int i=1; i<=nRows; ++i)  
    5. {  
    6.     float phy = XM_PI * i / stack;  
    7.     float tmpRadius = radius * sin(phy);  
    8.     for(int j=0; j<vertsPerRow; ++j)  
    9.     {  
    10.         float theta = XM_2PI * j / slice;  
    11.         UINT index = (i-1)*vertsPerRow+j;  
    12.   
    13.         float x = tmpRadius*cos(theta);  
    14.         float y = radius*cos(phy);  
    15.         float z = tmpRadius*sin(theta);  
    16.         //位置坐标  
    17.         mesh.vertices[index].pos = XMFLOAT3(x,y,z);  
    18.     }  
    19. }  

           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中绘制函数即可,绘制函数原型如下:

    [cpp] view plain copy
    1. void DrawIndexed(  
    2.   [in]  UINT IndexCount,  
    3.   [in]  UINT StartIndexLocation,  
    4.   [in]  INT BaseVertexLocation  
    5. );  

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

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

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

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

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

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

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

           最新框架、示例程序代码

  • 相关阅读:
    什么是webview
    juqery.fn.extend和jquery.extend
    LeetCode
    5. Longest Palindromic Substring
    42. Trapping Rain Water
    11. Container With Most Water
    621. Task Scheduler
    49. Group Anagrams
    739. Daily Temperatures
    3. Longest Substring Without Repeating Characters
  • 原文地址:https://www.cnblogs.com/wodehao0808/p/6603901.html
Copyright © 2011-2022 走看看