返回
Featured image of post 黑暗之潮

黑暗之潮

URP

为什么使用URP

  • 适合移动平台的PBR渲染管线
  • 非侵入式修改即可实现管线自定义
  • 有全部C#源码,渲染过程基本能全掌控
  • 源码结构清晰,组织合理,扩展和自定义容易
  • 比Builtin管线性能更好

为什么需要自定义管线

  • 透明物体荣誉出现渲染错误
  • 再Builtin管线中只能通过修改不稳定的Renderqueue来规避
  • 在引用新的Shader后,容易再次造成渲染错误
  • 在Builtin中一些效果只能通过ShaderPass实现,打断合批

在Builtin中渲染流程如下图:

渲染所需的Pass数量 = 物体数 x Shader的Pass数量

更好的Pass处理方式:

渲染所需的Pass数量 = Shader的Pass数量

  • Builtin管线为了兼容性,会在渲染中添加Blit操作且无法关闭

全屏Blit操作对于移动平台来说开销较大,而在确定的渲染管线中,可以明确知道Blit是否必须,不少情况下可以省去

  • 需要相对容易地实现一些项目特有的效果

URP的渲染管线

RenderObject的活用

  • URP内值的一个自定义的RenderPass的工具
  • 无需添加和编写一行代码
  • 可以明确指定某一个layer在何时渲染
  • 通过RenderFeature界面可排序
  • 可以重载摄像机属性和深度等渲染状态

RenderObject在《黑暗之潮》中的运用

  • 明确确定的地表透贴物体的渲染时机
  • 辅助其他自定义的RenderPass
  • 实现可对透明物体生效的单PassColorTexture

自定义RenderFeature/RenderPass

  • 可以被插入到任意指定时间点执行的自定义渲染操作
  • 拥有更强的控制能力
  • 可以手动调用Command Buffer底层接口
  • 可以控制切换RT时RenderBuffer的LoadStore操作

平面阴影

  • 游戏中大多数地形均为平地
  • 阴影质量高
  • 无需额外渲染Shadowmap
  • 用Render Feature可非常容易实现

自定义Renderer

  • URP内值了Forward和2D两个Renderer
  • 可以自行通过添加Renderer类实现扩展
  • 可以直接使用URP已经实现的各种Pass,自行进行编排
  • 《黑暗之潮》在ForwardRenderer基础上进行了自定义
  • 后效中不可避免会进行一次全屏Blit操作
  • 默认情况会在渲染UI后,使用FinalBlit Pass,将结果复制到FrameBuffer
  • 可以利用后效的Blit操作直接将结果复制到FrameBuffer,并直接在FrameBuffer上进行UI绘制,省一次Blit基础上还能实现3D场景与UI使用不同的分辨率渲染

《黑暗之潮》最终的渲染管线

URP的性能优势

  • 单Pass实时光照
  • 单Pass Color Texture替代GrabPass
  • 可根据实际情况去掉不必要的Blit操作
  • SRP Batcher
    • Dynamic Batching要求苛刻,且CPU开销大
    • Static Batching对动态物体无效,且内存占用巨大,且对LOD不友好
    • Instancing仅对Mesh和Material均一致的情况生效
    • Draw Call开销最大的是其中的SetPassCall
    • 通过Constant Buffer保存PerMaterial/PerDraw数据,实现Shader变种级别的合批

DOTS技术栈

关于DOTS

  • DOTS分为3个组件:ECS、JobSystem、Burst
  • 三个组件可以相互独立使用,并非必须捆绑使用
  • Job System无需配合ECS使用,各种需要并行计算的需求都可使用
  • Burst同样无需配合ECS使用,也并不需要跟并行计算捆绑使用,计算密集的同步方法也可使用
  • 使用ECS不代表整个项目必须全部用ECS来写,可根据项目需求将ECS和传统的OOP组合使用

自己总结下,为什么使用DOTS的情况下还能热更,因为大多数情况下,DOTS代码都是写在Model层,作为静态方法被调用

使用ECS渲染大量怪物

问题

  • 一组怪物通常有几名精英配合1\2种大量存在的爪牙
  • SkinMeshRenderer无法合批,且动画更新开销较高
  • GaneObject.Instantiate开销较大,瞬间刷一批怪只能依靠分帧

使用ECS

  • 利用ECS制作了一套基于GPU蒙皮的Instancing渲染系统

  • 实现将角色动作烘焙到一张贴图上,然后再GPU中进行蒙皮操作

  • 利用JobSystem+Burst实现视锥剔除和动画系统更新

  • 传统OOP游戏逻辑控制ECS的Entity,ECS部分仅提供渲染和动作接口,各取所长

性能优势

  • Drawcall数直接下降到怪物种类的数量
  • 一帧实例上千个怪物,在低端机上耗时也不足1毫秒
  • 借由Burst的力量,上千个怪物的视锥剔除和动画更新部分的耗时在低端机上都可忽略不计
  • 至此,能流畅支持多少个怪物完全取决与GPU本身的渲染性能,CPU端耗时忽略不计,不会出现卡顿

使用Job System实现怪物击飞

问题

  • 由于怪物数量多,可能会出现在一帧中同时击飞大量怪的情况
  • 直接使用Unity的Ragdoll会对低端机造成大量负担
  • 简化成播放预制动作,主要关注飞行轨迹的方案
  • 需要能跟场景产生正确的交互结果

使用Job System

  • 通过Job并行计算所有单位的飞行轨迹和动作
  • 使用Unity提供的多线程Raycast方法进行射线检测
  • 非ECS对象最后再通过一个单独的Job同步GameObject的结果位置

使用Burst加速射线技能特效的计算

问题

  • 射线技能需要同时跟场景和其它单位进行碰撞计算
  • 不光玩家控制的角色,其他怪物也可能会用,因此可能会出现同时有很多射线的情况
  • 角色实现射线时可随意变更方向
  • 需要每帧都重新计算射线所能碰到的位置

使用Burst

  • 需要将射线检测的CPU占用最小化
  • Burst非常善于处理计算密集型的需求
  • 新的数学库语法和数据类型都趋近于Shader,写起来特别方便
  • 经过Burst编译后,相关计算性能可以成百倍提升
  • 使用Job.Run接口可实现同步调用

性能对比

工作流的简化和改善

简化角色Prefab的制作

问题

  • 以往需要美术负责将新角色资源导入Unity,并按照规范创建材质球和Prefab
  • 采用PBR流程后,创建材质球和Prefab的复杂度大幅度上升,尤其是ECS单位,动画还需额外烘焙
  • 大量重复且复杂的手动操作非常耗时且容易出错

使用AssetGraph

  • AssetGraph是一个节点式自动化资源导入流程工具
  • 通过自定义节点可以完全根据项目需求定制资源导入流程
  • 整个复杂的Prefab创建过程均可一键完成
  • 美术只需将FBX和贴图文件按照要求放入指定目录即可
  • 也可通过自定义流程生成动画状态机

场景导出优化

问题

  • 根据不同的情况,会需要正确的渲染选项,以达到最佳的渲染性能
  • 具体的试着策略会根据技术团队的Profiling评估,进行细致调整,调整过程不应造成美术反复工作量
  • 根据不同的情况,会需要设置正确的渲染选项,以达到最佳的渲染性能
  • 具体的设置策略会根据技术团队的Profiling评估,进行细致调整,调整过程不应该造成美术反复工作量
  • 为了提升切换场景的加载速度,需要对场景进行切块

解决流程

问题

  • 使用ECS是否是不需要对象池

    ECS实例化过程是内存复制,所以不需要对象池

  • 烘培动画是否使用了插件

    Unity有开源一个GUP烘培的插件

  • 场景分割

    采用一个分簇的算法,将相近的物体进行合并

Licensed under CC BY-NC-SA 4.0
0