美文网首页
角色优化

角色优化

作者: 离原春草 | 来源:发表于2022-09-08 23:59 被阅读0次

本文将罗列工作中遇到的一些角色渲染优化的技术,这里的优化主要集中在性能的优化上面。

GPU Skinning

早期的动画蒙皮方案都是在CPU中完成的,主要步骤为:

  1. 骨骼变换:根据骨骼的父子关系&动画状态,计算出各个骨骼的变换矩阵,如果存在动画的blend,也是发生在这个阶段。
  2. 角色蒙皮:顶点数据根据其绑定的骨骼,分别进行变换,并将变换后的位置加权输出,即完成了角色动作的变化

这种方式的好处是在一帧之中,甚至多帧之中(如果角色动作保持不变的话),只需要一次蒙皮就能进行多次渲染使用,比如可以用于shadow pass与normal pass等,不过Unity提供的GPU蒙皮通过回写(Vertex Shader完成蒙皮,通过Geometry Shader的Steam Out功能,将蒙皮后的顶点数据写回到内存)也可以实现类似功能。

顺便提一句,GPU数据回写到内存中,在OpenGL中叫做Transform Feedback,不过这个特性在使用上是存在一些问题的,具体可以参考Transform feedback is terrible, so why are we doing it?,总体结论就是,在有Compute Shader的时候,可以考虑放弃这项特性。

根据上面的分析我们知道,对蒙皮结果进行回写在需要进行多遍蒙皮(比如某个角色需要经历多个pass,或者多个角色具有相同的动作)的时候有比较好的作用,且如今最合适的回写方法是通过Compute Shader,而这也是UE5的Skin Cache的基本原理[7]。

借用Compute Shader,甚至可以在未来考虑通过Async Compute在进行PostProcess的时候计算下一帧的蒙皮,从而进一步降低蒙皮消耗;此外,通过Skin Cache,还可以更方便实现动画的URO,比如通过降低蒙皮触发频率,可以做到每隔几帧更新一次,且动作效果是连贯的(如果没有回写,就会出现跳变回原始位置的情况)[7]。

不过回写也不是完美无缺的,其问题就在于会增加一份额外的Mesh数据的内存消耗[7]。

可以改进的地方在于,角色蒙皮时,各个顶点之间的变换是完全独立的,且这部分数目巨大,放在CPU上会存在较大的瓶颈,针对这一点,有两个解决方案:

  1. 将计算在CPU上改成并行完成,比如Unity上,就通过多线程+SIMD来提高速度
  2. 将计算放到GPU上完成,通过Vertex Shader的并行计算完成蒙皮

这里我们着重介绍第二种,这里将蒙皮计算放在GPU的VS中计算的方式就是GPU蒙皮。

GPU蒙皮的好处就是将蒙皮的压力从CPU移动到GPU,而众所周知,GPU的算力增长速度是远超CPU的,因此这种方式是符合趋势的。不好的地方在于每个角色都会触发一个Drawcall(如果一个角色上面有多个材质,就是多个Drawcall),即使多个角色之间是共享骨骼、动作甚至蒙皮的,也无法放到一个Drawcall中绘制。

Skeletal Instancing(实例化蒙皮)

实例化蒙皮方案是基于GPU蒙皮的改进方案,目标是解决GPU蒙皮单个Drawcall只能绘制一个角色的问题。

其做法类似于动态合批,不过这里合批的不是Mesh而是骨骼,这里有一些技术迭代:

  1. 早期的技术方案是,在CPU完成骨骼变换之后,将多个角色的骨骼变换数据合并到一起写入到一个InstanceBuffer(或者贴图,如果担心Buffer空间有限,不过访问速度慢于buffer)中,这样就能通过实例化渲染的方式,将Mesh相同、材质相同的多个角色通过一个Drawcall绘制出来

这种方案的不足在于:

  1. Buffer的空间是有限的,因此一个Drawcall可以绘制的角色数目也是有限的

  2. InstanceBuffer过大,更新消耗较高(包括计算与数据上传),这些负面影响会抵消一部分合批导致的优势

  3. 更新的版本是,在制作的时候,将骨骼的父子关系展开(不再有层级关系,方便GPU并行计算),渲染的时候,将一套骨骼数据(加多套对应于不同角色的动作数据,动作数据包括多个动作以及融合参数等)上传到GPU,通过CS完成骨骼的变换计算(可以做到1D融合,2D的计算复杂度过高,不可控,暂时不考虑),之后从CS直接唤起VS完成渲染(如果有必要的话,还可以将CS的数据取出,通过SkinCache方式供其他Pass使用)。

这个方案相对于前一个方案来说,有如下优势:

  1. 骨骼的计算从CPU转到GPU,通过并行进一步降低了时间消耗
  2. 不再需要上传庞大的InstanceBuffer数据,性能更好
    他的问题在于,融合是在GPU中计算,相对于CPU而言,融合效果会有损失。

这两个方案都存在的限制是,绘制的时候对Mesh&材质有约束,即希望角色Mesh相同(否则无法合批),材质可以合批(比如TextureArray)渲染。

Mesh&材质的约束可以考虑通过动态合批来解决,不过近景角色面数较多,材质也不见得能够合并到一起(如果不是同一母材质,且只有贴图存在区别),因此动态合批也并不是一个确定可用的方案。

[6]中给出了GPU蒙皮+实例化蒙皮的代码Demo,感兴趣的同学可以前往一观。

AnimToTexture(ATT)

AnimToTexture是UE的一个插件,在黑客帝国City Sample场景中,海量角色渲染就是采用的这套方案,其本质上是将角色(SkinnedMesh)当成StaticMesh来渲染,因此可以使用ISM/HISM的合批优化策略。

这个方案的原理可以参考[3]中的介绍,大概对这个文章的内容做一下摘录。

文章要解决的问题是要在写实渲染效果的基础上,实现实时帧率下对数千角色的支持(外加正常的场景、动态光、密集的特效),UE4并无对骨骼模型的实例化渲染支持,意味着角色默认消耗一个drawcall。

这里给出了两种实现思路:
1. 基于烘焙贴图的顶点动画

// Preprocess
for each Character
  for each LOD
    for each Animation
      for each Frame
        bake vertex position into texture

// Runtime - Vertex Shader
vert.pos = VertexAnimTexture.Sample(vert.UV, time)

大概翻译一下,就是对于每个角色的每一级LOD而言,对每个动作的每一帧,我们提前将各个顶点的位置数据烘焙到贴图中,在运行时,只需要对贴图进行采样就能完成动作的变化,本质上是一个顶点动画。

这种方案的问题在于,贴图消耗过高,渲染时显存占用大,借用文章中的数据,4个角色,19个动作,3级LOD,对应于228(1943)张贴图。好处在于在顶点Shader中不需要进行矩阵变换,计算消耗相对较低,如果只有一个角色,且用同一级LOD,那么这个方法会是一个很好的选择(比如在距离非常远的时候,我们只使用最低的一级LOD,且此时所有角色都退化成完全相同的模型&骨骼了,可以考虑这种方法)。

2. 基于烘焙的蒙皮动画

// Preprocess
for each Skeleton Set
  for each Animation
    for each Skeletal
      bake transform into texture

// Runtime - Vertex Shader
for each Skeletal Index
  vert.pos = blendfactors[index] * SkeletalAnimTex.Sample(index, time) * vert.localpos

大概翻译一下就是,贴图中不再存顶点的变换结果,而是存骨骼的变换矩阵(可以用更精简的方式表达),这样只要是同一套骨骼,那就只需要用同一套贴图即可,不需要考虑LOD、角色等的区别,显存占用更小。

最终结果是:用UE4实现了20w角色同场景,在NVidia 1080上全分辨率约0.5ms的角色渲染消耗。

Level of Detail(LOD)

LOD是一个广义的概念,不只是对应于面片数随着距离的减少,还可以用在其他方面,这里列举了可以用于角色性能优化的一些LOD:

  • 模型LOD,随着距离的增加而减少面数
  • 材质LOD,随着距离的增加,将多个材质合并成一个(类似于HLOD),同时降低shader的计算复杂度
  • 骨骼LOD,随着距离的减少与面数的减少,同步减少角色的骨骼,虽然减少骨骼在ATT中会需要新增一套动画烘焙贴图,但是
  • 更新频率LOD,降低动画更新频率,可以参考UE的Update Rate Optimization(URO)(【UE】性能优化策略集锦)或者UE的Anim Budgeter(可以理解为逻辑LOD与更新频率LOD的结合)
  • 逻辑LOD,降低逻辑计算复杂度,如[2]中提到的对于近景处角色会走插值来实现平滑移动,远景可能就直接SetPosition完事。

参考

[1]. UE5 CitySample的MassAI海量人群绘制
[2]. Large Numbers Of Entities With Mass In Unreal Engine 5
[3]. How To Populate Real-Time Worlds With Thousands Of Animated Characters
[4]. GPU Skinning 加速骨骼动画
[5]. chengkehan - GPUSkinning
[6]. GPU-Skinning-Demo - Github
[7]. What is the purpose of the GPU Skin Cache?

相关文章

  • 角色优化

    本文将罗列工作中遇到的一些角色渲染优化的技术,这里的优化主要集中在性能的优化上面。 GPU Skinning 早期...

  • Android 项目优化笔记(三):首页

    一、首页优化问题 那么首先回顾一下有关首页的优化建议: 角色切换方式 可以优化,如果更换的话可能需要 重新设计,且...

  • webpack优化

    项目背景:系统区分不同用户角色,针对不同角色生成不同的js文件(不同的js包含不同的路由设置)。 【优化结果】 打...

  • Hbase总结

    1.架构、角色(Hmaster、Hregionserver) 2.安装部署; 3.读写流程、API操作; 4.优化...

  • 流程优化的层级

    根据流程优化内容的复杂程度(所需要的资源多寡、涉及到的活动及活动关系的数量、参与的角色及部门等),可以将流程优化分...

  • 前端优化系列 - H5存储及优化

    摘要:数据存储在性能优化中扮演着极其重要的角色,H5相关的存储非常多,本文详细介绍各种存储的特点和相关优化实践。 ...

  • 性能优化项目的总结

    在性能优化项目中,我只是一个协助参与的角色,但也正好给了我从外部参看项目运作的机会,需要优化的系统已经是运行了6年...

  • U3D_03_26

    optimized gameobject 只能加白名单这个选项将提升动画角色的性能,推荐最终产品开启这个选项。优化...

  • Spark kyro Serialization

    序列化在分布式系统中扮演着重要的角色,优化Spark程序时,首当其冲的就是对序列化方式的优化。Spark为使用者提...

  • ElasticSearch 节点角色和优化措施

    ES的节点角色 Master Node. node.master 设置为true。可以参与竞选成为master节点...

网友评论

      本文标题:角色优化

      本文链接:https://www.haomeiwen.com/subject/larnnrtx.html