「Unity」记一次修改Mesh顶点数据的优化方案

优化方案

优化结果

先看优化前后的结果(左图为优化前,右图为优化后):
在这里插入图片描述 在这里插入图片描述

优化过程

实际上优化的关键点只在于——永远不要直接修改Mesh里面的顶点数据,除非你只修改一两个数据。

例如你需要修改Mesh每个顶点的uv,代码如下所示(方便起见,这里uv全部置为0):

for( int i = 0 ; i < mesh.uv.Length; i++ ){
    mesh.uv[i] = new Vector2();
}

从结果上看,上面的代码毫无问题,然而这是一段极其极其极其耗时的代码。(解释见原理)

你应该修改为如下所示的代码:

Vector2[] tempUV = mesh.uv;
for( int i = 0 ; i < mesh.uv.Length; i++ ){
    tempUV[i] = new Vector2();
}
mesh.uv = tempUV;

修改后的代码几乎不会耗费时间,而它的改动只有两处:

  • 先获取要修改的mesh的uv对象
  • 修改完以后,将其重新赋值给mesh.uv

原理

一般来说,Mesh在Unity存在有两份备份,一份在CPU中,另外一份在GPU中,Unity使用后者进行渲染(如果你使用过OpenGL或Directx3D,后者称为顶点缓存)。

  • 注:准确来说是在内存和GPU的显存中,这里仅为方便说明而说CPU和GPU。

而我们在C#代码中访问的是存在于CPU中的数据。前面说过,Unity使用GPU上的数据进行渲染,因此一旦我们修改了CPU上的数据,Unity就会马上更新整个Mesh的数据到GPU中,以保证数据一致,可以正常渲染

在这里插入图片描述

这个更新的过程非常缓慢,会阻塞CPU直到所有数据上传更新到GPU为止。


这时候返回来看上面的第一段代码,如下所示,增加了一点注释辅助说明:

for( int i = 0 ; i < mesh.uv.Length; i++ ){
    // 这里的每一次循环,都相当于对CPU的数据进行了修改
    mesh.uv[i] = new Vector2();
}

上面的代码,每次循环都修改了mesh.uv,也就意味着每次循环Unity背地里都会进行一次CPU上传GPU的操作,这就直接导致了这段代码运行非常缓慢。要命的是,随着mesh.uv.Length的增加,这段代码的运行时间也会随之增加。

而看第二段代码,如下所示,同样的增加了一点注释辅助说明:

Vector2[] tempUV = mesh.uv;
for( int i = 0 ; i < mesh.uv.Length; i++ ){
    tempUV[i] = new Vector2();
}
// 仅在这里修改了mesh的数据
mesh.uv = tempUV;

优化后的代码,仅仅在最后一行mesh.uv = tempUV处修改了CPU的数据,也就意味着这一段代码无论如何都只会触发一次CPU到GPU的更新,与顶点数量无关。

注意:你可能会注意到tempUVmesh.uv的引用,会认为循环中不也会间接修改mesh.uv吗?实际上mesh.uv返回的是一个新的数组,你对它的修改不会影响到mesh本身,也就不会触发更新了。

说在后面

这篇文章记录了笔者在做毕业设计过程中,在处理Mesh的顶点数据时所遇到严重时间消耗的问题,约有1800ms。由于这个功能对实时性要求非常高,实在不能承受如此高的时间消耗。然而研究了好久也没找到问题,而且这是一个非常关键的点,如果解决不了就凉了……

幸好最后经过推理和实验之后,成功将近1800ms的时间消耗降低为接近1ms。

由于这种优化方案是之前没有留意过的,因此写下这篇文章作为笔记。同时也将它分享出去,如果它能够帮助到你,那么这篇文章的另外一个目的也就达到了。

其他

  • 如果你不了解Unity中的Mesh,那么你可以查看笔者的另外一篇文章:从图形学认识Unity中的Mesh
  • 最后宣传一下最近新创建的公众号。我的想法是致力于分享实用的、有趣的干货,希望我能贯彻到底,也衷心希望它们能帮助到你!(目前还没有发布文章,近期应该会将博文转到公众号)
  • 原创不易,转载请注明出处;
    在这里插入图片描述
发布了4 篇原创文章 · 获赞 0 · 访问量 466

猜你喜欢

转载自blog.csdn.net/Arkish/article/details/104217801