GPU驱动的大规模静态物件渲染

44 篇文章 4 订阅
12 篇文章 0 订阅

        GPU Driven 的静态物件渲染,听起来很高级,其实具体操作很简单,基础就是直接调用 Graphics.DrawMeshInstancedIndirect 这个 Unity 内置接口就可以了。但我们项目对这个流程做了一些优化,使得支持的实体数量有大幅提升。

        这套系统主要也是公司的 TA 实现的,这里我也只简明扼要地介绍一下原理。

1、实现思路

        整个 GpuDriven 的实现简图如下图所示:

        CPU端收集好渲染所需要的数据之后,传给GPU进行剔除和整理,之后得到需要上屏的数据。之后通过异步回读,拿到各个类型是否需要渲染的结果,然后再调用 Graphics.DrawMeshInstancedIndirect 命令进行绘制。

        与传统流程相比,主要是增加了剔除和异步回读,使得 DrawCall 数量没有任何浪费,在保证渲染结果的同时,尽可能地减少了实际绘制的实体数量。

2、数据结构

        部分数据结构简单介绍如下:

2.1、实体数据:InstanceData

public struct InstanceData
{
    public int MeshType;//表示这个实体的类型
    public float3 Position;//世界坐标的位置
    public uint ScaleAndRoate;//缩放和旋转值,用位预算合并在一起(会降低精度)
    public float4 CustomData;//自定义数据,根据项目需求设置长度;
}

        这里为了性能考虑,对数据尽可能地做了压缩,例如旋转和缩放就整合成了一个unit,4个字节。自定义数据,可以理解为一些特殊 Shader 特殊效果用到的数据,例如描边颜色、顶点形变等。这个数据是最大的一块,不建议每帧都传给GPU,而是通过AOI来收集,在数据有变化的时候再传给 GPU 。

2.2、网格类型数据:MeshData

        网格类型记录每一个网格上屏需要的参数,包括网格、材质球等:

public class MeshData
{
    public int MeshType;//网格类型ID,需要顺序排列
    public Mesh mesh;//网格
    public Material material;//材质球
    //-----------------以下是一些自定义参数,可自行扩展------------------------
    public float2 lodDistance;//Lod参数
    public bool reciveShaodw;//阴影参数
}

        网格类型数据的改变频率就很低了,基本上可以全局共用一套(或者一个文明、国家用一套)。这里需要注意的是,数据存储时需要将 MeshData 存储为一个数组,且数组的 Index 和 MeshType 一一对应。

2.3、裁剪后的 Buffer 数据

        裁剪后的 Buffer 数据是写在 GPU 里的,除了标记哪些类型需要渲染外,其他数据都是不会异步回读到 CPU 的。这里的数据就随便写了,举个例子:

StructuredBuffer<InstanceData> _IndirectAllInstanceDatas;//所有Instance的几何信息以及自定义的数据
StructuredBuffer<uint> _IndirectInstanceTypeIndexStart;//下标为Meshid,val为起始下标
StructuredBuffer<uint> _IndirectInstanceTypeIndexCount;//下标为Meshid,val为渲染数量

        在回读 _IndirectInstanceTypeIndexCount 这个数据时,我们做了一个优化:由于CPU只需要知道每个类型是否需要渲染,所以是需要1位就能表示。我们将每8个类型合并成一个 int 返回,这样可以节省一点带宽。

3、关于裁剪

        裁剪我们用了视锥体剔除、遮挡剔除和 LOD 剔除,一般来讲这三个结合起来就基本可以剔除得很干净了。剔除顺序以及介绍如下:

  1. LOD 剔除:将距离相机距离大于 LOD 设置的直接剔除视为不渲染;
  2. 视锥体剔除:根据相机矩阵计算,剔除掉不在视野内的物体;
  3. 遮挡剔除:根据上一帧生成的深度图,对当前帧的实体进行遮挡剔除计算。
Game视图下与渲染并无异常
在Scene视图下查看的剔除效果,可以看到遮挡剔除和视锥体剔除都生效了

        在剔除的时候,用到了一些加速手段。例如使用 Cluster(或者叫Chunk)剔除加速优化:例如草,数量很多时,将其用莫顿码编每64个编成一个Chunk,然后整体做剔除。在视锥体剔除的时候,也可以先构建稀疏八叉树等等。(涉及的相关技术和参考文档我会放到最后)
        这一套整体下来,百万物体的剔除在编辑器上测试只有 0.163 ms (GTX 1050ti),非常高效。

        当然,我们实际实现的时候也遇到了不少问题,最常见就是单位闪烁的问题(主要原因是异步回读和深度图的数据并不是本帧的数据),后面也通过各种手段解决了。但这个技术路线本身原理是可行的。

参考文章

Graphics-DrawMeshInstancedIndirect - Unity 脚本 API

https://zhuanlan.zhihu.com/p/468542418

计算机图形学:使用体素锥体跟踪实现全局渲染_技术交流_牛客网

【Unity】相机视锥体剔除算法_unity视椎体剔除-CSDN博客

【Unity】LODGroup 计算公式_unity lod 计算viewdistance-CSDN博客

GPU Driven Occlusion Culling(Hiz)_hiz遮挡剔除-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值