3D数学之-三角形网格

本文详细介绍了3D图形中的三角形网格存储结构,包括索引三角网格和三角带网格,强调了索引三角网格的广泛应用。此外,探讨了额外的网格信息如纹理坐标、法向量和光照值。接着,讨论了三角网格的重要操作,如渲染、优化操作(如点焊接、面拆分)以及高级优化策略。最后,深入分析了索引三角网格的内存管理和性能提升技巧,包括删除退化三角形、整理材质链表等,并介绍了如何从文本文件构建和渲染三角网格数据结构。
摘要由CSDN通过智能技术生成
在3D物体中,所有的几何变换,几何检测,动画,渲染着色都要基于三角网格进行,所以三角网格的基本原理和优化策略都是是非常重要的。

一、三角网格的存储结构

三角形网格是为了模拟一个物体连续的体积表面,三角形网格具有表示简单和操作简单的特性而成为事实上表示物体平滑表面的标准,其它多边形网格都可以简化为三角形网格, 三角形需要表现三角形网格的顶点、边、面的信息。

1.索引三角网格

索引三角网格是三角网格的标准存储形式。
索引三角网格的一般定义:
struct Vertex
{
     // 顶点
    Vector3 p; 

   // UV纹理,用于渲染
    float u,v; 

   // 顶点法向量,用于计算光照,求取遇到问题可能需要面拆分
    Vector3 normal; 

  // 工具变量,用于对顶点操作的标记
    int mark; 
}

struct Triangle
{
    // 记录的是索引
    struct VertIndex
     {
         // 存储一个索引,比存储vector3少得多
        int index; 
      // 焊接和顶点拆分时候,顶点中的uv会失效,所以在三角形中也备份一份;且三角形上的拷贝直接从三角形取得UV坐标即可
        float u, v; 
     }

// 三角形的三个顶点    
VertIndex v[3];

// 面法向量
Vector3 normal;

// 三角形所属空间物体的那个部分
int part;

// 材质列表的索引
int material;

// 工具变量,用于对三角形操作的标记
int mark;
}

struct Material
{
     // 贴图纹理的名字
    char diffuseTextureName[256];
    // 标记,用于Material的操作标记
    int mark;
}

struct Part
{
    // 三角形所属物体中的部分的名字
    char name[256];
    // 标记
    int mark;
}

// 一个三角网格定义了一个物体平滑的空间表面,或者多个物体组合为一个物体的空间表面
class EditTriMesh
{
public:

private:
// 顶点分配的个数,顶点需要预分配一些空间给顶点的面拆分,点拆分使用,避免每次都new一个顶点出来
// 预分配的大小为: vAlloc =   vCount  * 4 / 3 + 10;
int vAlloc;
// 顶点个数
 int vCount;
// 顶点列表
Vertex *vList;

// 三角形分配的个数,三角形需要预分配一些空间给面拆分,点拆分使用,避免每次都new一个面结构体出来
// tAlloc =   tCount  * 4 / 3 + 10;
int tAlloc;
 int tCount;
Triangle *tList;

// 材质纹理的列表
int mCount;
Material *mList;

// 所属物体部分的列表
int pCount;
Part *pList;
}

这种简单的索引三角网格,并没有给出三角形的邻接信息,每次获取邻接信息都要遍历整个网格的三角形列表。另一种反映网格边邻接信息的存储方式是, 将三角形用边的列表来定义,顶点中维护一个共用该顶点的所有边的索引,这样通过定位顶点,可以在常数时间内找到和该点相关的边和三角形


2.三角带网格

三角带存储,因为顶点顺序就包含了三角形索引信息,所以不需要存储三角形的顶点索引,且提交的顶点数比顶点索引存储更少,但是只在一些平台上使用,例如PS平台上。 三角带是用t + 2s个顶点,存储了t个三角形,s是三角带的个数,所以尽量减少三角带数量,用退化三角形连接多个三角带是通用的做法,多个三角带存在主要是表现一个物体有时候不能用一个三角带表示完,用退化连接主要是建立多个三角带是需要较长时间的。因为三角带中的三角形提交顺序是一个顺时针交替一个逆时针的,所以因为绕序问题需要四个退化的三角形,或者在指明退化三角形不用渲染情况下用2个退化三角形。

3.索引三角网格较三角带网格使用广泛

索引三角网格,给图形卡提交的三角网格物体的顶点数,几乎一个三角形只需要提交一个顶点,和三角带网格一样,但是索引三角网格比较复杂,因为 三角形需要维护 顶点的索引信息,增删查改都比较复杂,而且需要比较多的空间,不过因为并不是所有的平台都支持三角网格,所以 索引三角网格是比较通用而普遍的定义三角网格的存储格式

4.三角网格的优化

索引三角网格和三角带网格都有显卡顶点缓存优化策略,可以使得三角网格提交的一个三角形少于一个顶点。
这个优化是显卡硬件层面的优化策略,工作原理是API传输给显卡先会缓存显卡中的顶点缓存,去哈希查询下如果显卡中存在该顶点那么"命中"不用发送告诉显卡使用显卡顶点缓存的xx位置的顶点即可, 如果没有那么“脱靶”传送顶点给显卡。 想要更好的使用显卡顶点缓存优化渲染,那么需要对三角网格优化排序下,使得共用顶点的三角形放置在相邻的位置,这些三角形连续发送时候就可以提高顶点的命中率。


二、三角网格存储的额外信息

纹理坐标用于渲染,顶点法向量用于光照计算,表面法向量用于相交检测或背面消隐。

1.纹理坐标

对于用纹理着色的多边形,每个顶点都要存放纹理坐标用这些坐标索引纹理图从而为相应像素着色, 一个三角形表面需要三个UV坐标指明纹理位置进行定位映射,顶点进行变换缩放时候纹理坐标不用进行变换, 真正渲染着色的时候需要对三角形中的每个像素进行纹理坐标插值算法求得该像素的纹理坐标,将纹理坐标的像素拷贝到空间点上。

2.表面和顶点法向量

表面法向量作用:
1)计算光照;
2)进行背面剔除;
3)模拟粒子在表面的“弹跳"效果; 
4)通过只考虑正面而加速碰撞检测。
表面法向量可能保存于三角形级或顶点级,或者两者都有。
对于三角形级法向量用三角形的边向量叉乘就可以了,假设三角形是顺时针列出的。
如果是顶点级的法向量(计算光照),可以求得共用该顶点的三角形,求取三角形的法向量(假设三角形是顺时针列举出来的),然后进行平均即可。但是会遇到"公告板"问题,共用一个顶点的三角形是两个相反的三角形;或者会遇到立方体边缘着色时候用Gouraud着色导致的边缘没有剧烈变化而模糊的问题; 这两种情况下都可以通过平面拆分来获得,使得他们不是共享该顶点的面,因此平均后就没有问题了,如果没有剧烈变化的平面也没有面拆分,会导致平面法向量接近的获得较多的”发言权“,但是因为三角表面本来就是一种近似,所以不会导致什么问题。 计算光照时候顶点的颜色可以直接计算出来,三角形中的其它点的颜色也需要插值算法来计算,这样的计算量消耗是巨大的,所以比较少使用实时动态计算光照。

3.光照值

光照值用于沿表面的插值,典型的方法是用Gouraud着色。有些时候,顶点处保存顶点法向量,用于渲染时候动态计算光照值。但另一些情况下我们需要自己指定光照值。

三、三角网格的重要操作

同拓扑的三角网格,也就是位置不同,大小不同,伸缩不同,但是网格形状是一致的网格。
有一种重要的网格就是封闭网格,物体表面的网格基本是封闭网格,导致网格不封闭的异常原因有:
1)孤立的顶点。
2)重复的顶点。

3)退化的三角形。
4)开放边,仅是一个三角形使用。
5)超过两个三角形共享的边。

6)重复面。
根据应用的不同,上面异常导致的问题,可能是大错误,或者是小错误,这个时候需要进行三角形网格纠正;或者无关紧要,那么不需要进行网格修复。

1.逐片操作:渲染提交和顶点变换

三角形网格的一系列基本操作都是逐三角形或逐点应用基本操作的结果,例如 渲染时需对逐个三角形进行提交;转换时如变换和缩放时需对逐个顶点进行操作。三角形渲染和顶点变换是对三角网格进行最多最广泛的操作。

2.基本优化操作:点焊接,面拆分

(1)点焊接
对于在误差范围内的点进行焊接,保留一个顶点,删除一个顶点。这样使得顶点减少了,那么需要存储的内存变少了;对三角网格的逐片操作(例如变换和渲染)速度会加快;且三角网格中几何相邻的边在逻辑上也是相邻的
点焊接的基本操作:
1)去掉三角网格中孤立的点。
2)当两个顶点均来自于细长的三角形,那么直接焊接可能会产生退化三角形,这个时候应该去掉整个细长的面。
3 ) 焊接时候,如果不是细长的三角形,那么只简单的去掉一个顶点(例如去掉高序数的或者去掉低序数的)。
点焊接不是网格削减,即不用大规模去掉三角形,而是尽量保持网格的外形和精度。
点焊接会导致一个问题,即 被焊接顶点中的顶点法向量,纹理坐标消失了,会导致不连续,因此这个时候权衡下使用三角形中保存的顶点法向量和纹理坐标,还是使用焊接到的顶点的法向量或纹理坐标即可。
点焊机算法是O(n^2)的,因为查找需要n搜索,调整三角形列表和顶点列表也是n搜索遍历。但是加以思考就可以找到O(n)算法效率,具体需要分析下已有的算法

(2)面拆分
面拆分的作用,在"公告板”中,或者立方体变化剧烈的情况下,为了正确的计算顶点法向量。或者其它类似情况下需要进行面拆分。

面拆分是点焊接相反的算法,使得顶点增加了,面增加了,使得拓扑间断了。不过这也正是我们想要的,就是逻辑间断的地方拓扑(几何)也是间断的。一般情况下面拆分了几何并没有间断位置还是那个位置,大小也是一样的大小,只是我们保持了两份数据,当做两个顶点来使用。
面拆分时候,产生新的顶点,它的顶点法向量,UV纹理坐标,可以来自于三角形中或者就是原来的顶点拷贝一份。

3.高级优化操作:边坍塌网格削减,点拆分LOD渐进式网格

(1)边坍塌网格削减
边坍塌是将边缩减为顶点的方法,与之对应的是顶点拆分。边坍塌使边上 的两个顶点变为一个,共享该边的两个三角形消失。
边坍塌常用于网格削减,边坍塌可以有效的减少顶点三角形的数量
这样使得摄像机远处的地面,建筑,物体,人物变得模糊。

网格削减是将顶点和三角形较多的网格简化为顶点和三角形较少的网格,并且要求网格外观和主要顶点尽量保持不变。使用 边坍塌就可以实现网格削减的方法,但是选择需要坍塌的边相对耗时(甚至需要进行离线计算), 但是看使用的启发算法和网格削减精度来决定,边坍塌本身的网格操作是很简单的。

边坍塌舍弃边上的两个顶点,产生新的顶点,它的顶点法向量,UV纹理坐标,可以来自于三角形中或者就是原来某个顶点的一份拷贝

(2)点拆分LOD渐进式网格
Hope的论文讲述了如何用 点拆分来反演边坍塌的过程,用此反演方法生成的网格称为渐进式(LOD)网格
大概是要先存放边坍塌过程中,选择的坍塌边信息,顶点信息。然后根据当前的网格形态和坍塌过程中的信息,通过相反的算法输入坍塌信息(从上到下,或者从下到上的顺序),将点拆分,逐步的构建反演的三角网格,来实现LOD网格。
这样使得摄像机近处的地面,建筑,物体,人物变得细腻而逼真。

点拆分产生新的顶点,它的顶点法向量,UV纹理坐标,可以来自于三角形中或者就是原来某个顶点的一份 拷贝,更准确的是来自于边坍塌过程中产生的顶点信息中。

四、索引三角网格代码分析


1.预分配内存和整理内存(非频繁申请和释放),提高索引三角网格的性能

预分配内存大小是: nAllocCount = int( nCount * 4 / 3 + 10 );
 vList = (Vertex *)::realloc(vList,   nAllocCount  * sizeof(*vList));

线性空间,对网格操作时候不是真正的删掉内存,而是挪动内存:
realloc函数的应用, memset函数,memcpy函数,memmove函数的应用。

// 没有真正释放内存,而是使用了memmove移动顶点位置
 memmove(&vList[vertexIndex], &vList[vertexIndex+1], (vCount - vertexIndex) * sizeof(*vList));

2.每次删除顶点或者材质索引或部分索引时候都要重新整理三角形列表

//---------------------------------------------------------------------------
// EditTriMesh::deleteVertex
//
// Deletes one vertex from the vertex list.  This will fixup vertex
// indices in the triangles, and also delete triangles that referenced
// that vertex
void EditTriMesh::deleteVertex(int vertexIndex) {
 // Check index.  Warn in debug build, don't crash release
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值