LOD原理及相关实现方式

目的:

模型当前的面数过多,在游戏上跑,性能会有较大问题

方法:

方法一:美术制作各种级别的模型     

优点:模型精细度可控     

缺点:素材可能变大并且美术需要消耗大量人力制作

方法二:程序自动支持各种级别的模型   

优点:不需要美术消耗资源制作,时间大量节省     

缺点:精细度不一定能完美解决,可能每个模型需要调整不同的减面系数

LOD是Level Of Details的缩写即多层次细节 LOD就是为了支持当物体远离观察者或者物体的重要程度不同,位置不同,速度不同或者视角相关的参数不同需要减少渲染3D模型的复杂度

Mesh Decimation技术

第一种为顶点聚类(vertex clustering)

第二种为增量式简化(Increment decimation)。

顶点聚类(vertex clustering)

顶点聚类原理大致分为以下几步:

1. 生成聚类(Cluster Generation)

2.计算表现因子(Computing a representative)

3.生成网格(Mesh Generation)

4.改变拓扑结构(Topology Changes)

边坍缩(edge collapse)

边坍缩(edge collapse)是实现增量式简化(Increment decimation)的一种思路,使用三角边坍缩的方法来进行网格简化,将两个顶点合并成一个顶点,如图所示。

Automatic LOD效果(分别是lod0,lod1,lod2的效果)

实现代码

GetWorldVertices(gameObject)会遍历模型上所有的顶点,对于SkinnedMesh会带入骨骼的旋转和权重值进行计算,返回物体所有的顶点在世界坐标系的位置。

meshUniqueVertices.BuildData(m_meshOriginal, aVerticesWorld)会重新创建一组Mesh顶点数据和一组三角形面片数据,包含本地坐标系位置和世界坐标系位置,因为Unity的Mesh数据结构里面,所有的顶点保存在一个顶点列表里面,三角形面片列表只会拥有顶点的索引,所以对于位置相同的顶点,三角形面片列表会拥有顶点列表里面相同的索引。

AddVertices(m_meshUniqueVertices.ListVertices, m_meshUniqueVertices.ListVerticesWorld, m_meshUniqueVertices.ListBoneWeights)会使用重新创建的顶点数据列表创建对应的Vertex对象数组,并保存到m_listVertices列表里面。

Vertex对象记录了一个顶点在网格简化过程中所需要的相关信息,如坐标位置,相邻的Vertex顶点,相邻的三角形对象,边收缩代价(Edge Collapse Cost),坍缩目标点,优化排序的顶点等等

优化顶点排序

二分优化排序,把大的值尽量放在左边,他并不能完全保证排序顺序(实际上只排了一半),但起码大的排在前面的概率会大

而且不需要0(n)以上来排序,只需要0(logn)

HeapValue是获取具体的边坍塌代价

void HeapSortUp(int k)
      {
        int k2;
        while (HeapValue(k) < HeapValue((k2 = (k - 1) / 2)))
        {
          Vertex tmp = m_listHeap[k];
          m_listHeap[k] = m_listHeap[k2];
          m_listHeap[k].m_nHeapSpot = k;
          m_listHeap[k2] = tmp;
          m_listHeap[k2].m_nHeapSpot = k2;
          k = k2;
        }
      }

创建对应的Triangle对象数组

AddFaceListSubMesh(nSubMesh, m_meshUniqueVertices.SubmeshesFaceList[nSubMesh].m_listIndices, anIndices, av2Mapping)会使用重新创建的三角形面片列表创建对应的Triangle对象数组,并保存到m_aListTriangles列表里面。主要是获取每个三角面所包含的三个顶点,并计算法线(用于计算曲率)。同时,将该三角面加入顶点的三角面列表,并将每个顶点加入另外两个顶点的邻居列表中去。Triangle对象包含了Vertex对象的引用列表,三角形面法线信息用于计算坍缩代价,UV信息等。

坍塌计算

ComputeAllEdgeCollapseCosts(strProgressDisplayObjectName, gameObject.transform, aRelevanceSpheres, progress)会遍历网格中的所有Vertex顶点,对于每一个Vertex顶点,计算所有相邻顶点的边坍缩代价(Edge Collapse Cost),保存边坍缩代价最小的值和坍缩目标点(Collapse Vertex),并且把计算后的Vertex顶点添加到堆里面,按照边坍缩代价进行从小到大排序

一条边是否要坍塌

它的边长与曲率值的乘积

-通过顶点间的向量之差的叉乘得到法线方向。

-比较两个面的法线的点积得到坍塌边uv的曲率值

-然后用两点距离×点乘的值得到坍塌的代价

坍缩代价

-边的长度,在模型简化过程中,小的细节应该优先被移除,所以边长短的应该优先被移除

-点周围的曲率变化,理论上曲率变化越小的顶点,所处的区域越平坦,应该优先被移除

边坍缩处理阶段

循环地从堆里弹出边坍缩代价最小的顶点,并且调用Collapse(Vertex u, Vertex v, bool bRecompute, Transform transform, RelevanceSphere[] aRelevanceSpheres)对顶点进行边坍缩处理

具体方式是获取坍缩代价最小的顶点u及其坍缩目标顶点v

遍历u的三角面列表

如果包含v就删除该三角面,否则将u替换为v

其中替换方案:把老顶点的临点赋值给新临点,删除老顶点的临点和老顶点

然后重新计算u的所有邻居点的坍缩代价和坍缩目标并更新堆的顺序

直到所有顶点全部处理完成

顶点间坍塌顺序的区别

参考资料

https://zhuanlan.zhihu.com/p/51944864?utm_source=qq&utm_medium=social&utm_oi=827612570348310528

unity运行时执行

三种模式 Screen Coverage:

-对象在屏幕中的大小决定

-Camera Distance:摄像机远近决定

-Just Change:强制切换LOD

CameraDistance

先判定相机距离

-Vector3 v3WorldCenter = transform.TransformPoint(m_localCenter.x, m_localCenter.y, m_localCenter.z);

-//计算自身坐标空间到摄像机的距离

-fDistanceToCamera = Vector3.Distance(v3WorldCenter, currentCamera.transform.position);

判断距离与每个lod切换条件

-if (fDistanceToCamera < m_listLODLevels[nLODLevel + 1].m_fMaxCameraDistance)

Screen Coverage

先判定对象占据屏幕的大小(特别注意的也需要看子对象是否比父级对象占据的上下左右最大)

Vector3 v3World = i == 0 ? GetComponent<Renderer>().bounds.min : GetComponent<Renderer>().bounds.max;
Vector3 v3View  = mtxView.inverse.MultiplyPoint(v3World);
if (v3View.x < fMinX) fMinX = v3View.x;
if (v3View.y < fMinY) fMinY = v3View.y;
if (v3View.z < fMinZ) fMinZ = v3View.z;
if (v3View.x > fMaxX) fMaxX = v3View.x;
if (v3View.y > fMaxY) fMaxY = v3View.y;
if (v3View.z > fMaxZ) fMaxZ = v3View.z;

float fViewSurfaceArea = (fMaxX - fMinX) * (fMaxY - fMinY);

判断大小与每个lod切换条件

-if (fScreenCoverage > m_listLODLevels[nLODLevel + 1].m_fScreenCoverage)

unity编译时流程

创建对象的n个mesh(现在n=3)

把相关依赖的mono附在对象上(AutoMaticLOD,AutoLODAllMesh)

创建mesh对象的减面百分比,距离,大小,最终存储在创建的asset文件下(包括对象的子对象)

附上相关的依赖关系和mesh到每个对象上 打ab

lod切换等级计算方式

ScreenCoverage:
float oneminust = (float)(percent / 100.0f);
data.m_fScreenCoverage = Mathf.Pow(oneminust, 3.0f);

CameraDistance
i == 0 ? 0.0f : nLevel * 100.0f;
 

未来尝试方向

减面需要符合真实美感:

-有合并过的面选择性平滑过度

-指定的部位或面不进行autolod

猜你喜欢

转载自blog.csdn.net/llsansun/article/details/99719416
LOD