Unity实现GPU Cull渲染

news2024/11/23 15:03:34

前言

开放世界游戏中植被和物件的数量往往是巨大,  而传统组织大量植被渲染的方式是利用QuadTree/Octree/Kd-Tree等数据结构对植被Intance数据进行预先生成一个个Cluster,然后在运行时进行FrustumCull,收集可视的所有Cluster,最后进行DrawInstance.

这种方式往往存在两个缺点: 

[1]Cluster粒度和DrawInstance渲染的粒度冲突,也就是单个Cluster粒度很大时,很多位于视椎体外的Instance是浪费的绘制(VS/PS上的浪费)。而当Cluster粒度比较小时, DrawInstance渲染批次可能会明显上升。

[2]第二个缺点是完全依赖CPU的剔除, 从效率上很难做到单个Instance的高效剔除。

 对于上面两点,基于GPU剔除的方案可以很好解决这个问题.

CPU Cull + GPU Cull

CPU Cull(QuadTree)

以四叉树组织Instance数据


static class QuadTreeUtil
{
    public const int QUAD_TREE_NODE_MAX_NUM = 128;
}

[Serializable]
public struct InstanceData
{
    public Vector4 instance;
    public InstanceData(Vector3 inPos, int clusterId)
    {
        instance = new Vector4(inPos.x, inPos.y, inPos.z, (float)clusterId);
    }
}


[Serializable]
public class QuadTree
{
    [NonSerialized]
    public Vector3[] alllTreePos;
    
    [HideInInspector]
    public InstanceData[] instanceDatas;
    private int[] treeCullFlags;
    public TreeNode rootNode;
    public int leafId;

    public QuadTree(Vector3[] inAllTreePos, Vector2 regionMin, Vector2 regionMax)
    {
        List<int> indices = inAllTreePos.Select((item, index) => index).ToList();
        alllTreePos = inAllTreePos;
        leafId = 0;
        rootNode = new TreeNode(this, indices, regionMin, regionMax);
        BuildTreeClusterData();
    }
}

 默认下每个叶子节点最大数量为128个Instance

 Instance数据得记录所在的Cluster(leafNodeId)

所以CPU阶段的QuadTree主要是两个作用:

[1]收集InstanceDatas

 

[2]在CPU每帧进行QuadTree的粗粒度Frustum剔除,求出可见的leafNode Id集合。这里的leafNode Id集合用固定大小数组int[] 来表示, 1代表被剔除,0代表可见,用于后续的GPU剔除。

 

GPU Cull

GPU Cull是利用ComputeShader针对单个Instance进行的剔除,主要分为三步:

[1]基于CPU的粗粒度QuadTree剔除的结果,在GPU可以直接进行第一轮剔除

 [2]在GPU针对Instance进行 SphereFrustumCull 进行第二轮剔除

 [3]基于HZB(Hierarchical Z-Buffering 分层 Z 缓冲)的遮挡剔除, 就是根据当前物体的渲染深度和它的在上一帧渲染的HZB的深度作对比来判断物体是否被遮挡。

 

 HZB的原理和生成推荐 这篇博客 Compute Shader 进阶应用:结合Hi-Z 剔除海量草渲染 - 知乎

文章介绍的HZB生成方法质量比较不错,HZB各级Mip 像素偏移和丢失的现象较少。

最终GPU 剔除的ComputeShader如下:

// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain
#pragma enable_d3d11_debug_symbols
//#pragma use_dxc
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture

StructuredBuffer<float4> instanceDatas;
StructuredBuffer<int> treeNodeCullFlag; // 1 mean culled, 0 mean visible
RWStructuredBuffer<float3> posVisibleBuffer;
RWStructuredBuffer<int> bufferWithArgs;

int allCount;
float4 bounds; // center + radius
float4 frustumPlanes[6];
float3 cameraWorldDirection;
float4x4 worldToViewProject;
Texture2D<float4> hizTexture;
float hizMapSize;
int maxHizMapLevel;


float GetDistance(float3 pos, float4 plane)
{
    return plane.x * pos.x + plane.y * pos.y +  plane.z * pos.z + plane.w;
}

int IsOutOfFrustum(float3 pos, float radius, float4 frustum[6])
{
    for(int i = 0; i < 6; i++)
    {
        float distance = GetDistance(pos, frustum[i]);
        if(distance >= radius)
        {
            return 1;
        }
    }

    return 0;
}


float3 TransformToNdc(float3 worldPos)
{
    float4 worldAlignPos = float4(worldPos, 1.0);
    float4 ndc = mul(worldToViewProject, worldAlignPos);
    ndc.xyz /= ndc.w;
    ndc.xy = ndc.xy * 0.5 + 0.5;
    return ndc.xyz;
}

int GetHizMapIndex(float2 boundMin, float2  boundMax)
{
    float2 uv = (boundMax - boundMin) * hizMapSize;
    uint2 coord = ceil(log2(uv));
    uint index =  max(coord.x, coord.y);
    return min((int)index, maxHizMapLevel);
}

bool IsCullByHizMap(float3 pos, float boxWidth)
{
    float3 offsetPos = pos;
    float3 ndc1 = TransformToNdc(pos + float3(boxWidth, boxWidth, boxWidth));
    float3 ndc2 = TransformToNdc(pos + float3(boxWidth, -boxWidth, boxWidth));
    float3 ndc3 = TransformToNdc(pos + float3(boxWidth, boxWidth, -boxWidth));
    float3 ndc4 = TransformToNdc(pos + float3(boxWidth, -boxWidth, -boxWidth));
    float3 ndc5 = TransformToNdc(pos + float3(-boxWidth, boxWidth, boxWidth));
    float3 ndc6 = TransformToNdc(pos + float3(-boxWidth, -boxWidth, boxWidth));
    float3 ndc7 = TransformToNdc(pos + float3(-boxWidth, boxWidth, -boxWidth));
    float3 ndc8 = TransformToNdc(pos + float3(-boxWidth, -boxWidth, -boxWidth));

    float3 min0 = min(min(ndc1, ndc2), min(ndc3, ndc4));
    float3 min1 = min(min(ndc5, ndc6), min(ndc7, ndc8));
    float3 boundsMin = min(min0, min1);

    float3 max0 = max(max(ndc1, ndc2), max(ndc3, ndc4));
    float3 max1 = max(max(ndc5, ndc6), max(ndc7, ndc8));
    float3 boundsMax = max(max0, max1);
    uint mip = GetHizMapIndex(boundsMin, boundsMax);

    float currentHizMapWidth = hizMapSize / pow(2, mip);
    float2 uv0 = min(currentHizMapWidth - 1, floor(boundsMin.xy * currentHizMapWidth));
    float2 uv1 = min(currentHizMapWidth - 1, floor(boundsMax.xy * currentHizMapWidth));

    float d0 = hizTexture.mips[mip][uv0].r;
    float d1 = hizTexture.mips[mip][uv1].r;
    return boundsMax.z < d0 && boundsMax.z < d1;
}


[numthreads(16, 16,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    uint instanceId = id.x * 1500 + id.y;
    if(instanceId >= allCount)
        return;
    
    float4 instance = instanceDatas[instanceId];
    int clusterId = (int)instance.w;
    if(treeNodeCullFlag[clusterId] == 1)
        return;

    float3 worldPos = instance.xyz;
    float3 pos =  worldPos + bounds.xyz;
    if(IsOutOfFrustum(pos, bounds.w, frustumPlanes) == 0 && (!IsCullByHizMap(pos, bounds.w)))
    {
        int currentIndex;
        InterlockedAdd(bufferWithArgs[0], 1, currentIndex);
        posVisibleBuffer[currentIndex] = worldPos;
    }
}

Draw Instance Indirect 

利用GPU剔除得到的RWStructuredBuffer<float3> posVisibleBuffer 和 RWStructuredBuffer<int> bufferWithArgs 进行DrawInstanceIndirect。

Graphics.DrawMeshInstancedIndirect(treeMesh, 0, treeMaterial, drawIndirectBounds, bufferWithArgs, 0, null, ShadowCastingMode.Off, false);

Demo效果:

CPU:AMD 锐龙7950X

GPU:  RTX3060Ti

植被数量:200W

Cpu cull + GPU Cull: 2ms左右

Draw Instance Indirect :4.5ms左右

Unity GPU Cull

项目Git链接

https://github.com/2047241149/GPUFoliageCull

参考资料

[1]Compute Shader 进阶应用:结合Hi-Z 剔除海量草渲染 - 知乎

[2]https://www.cnblogs.com/mmc1206x/p/15542745

[3]Hierarchical-Z map based occlusion culling – RasterGrid

[4]Mobile hierarchical z buffer occlusion culling - 知乎

[5]Mobile GpuCulling for Unreal - 知乎

[6]Unity中使用ComputeShader做视锥剔除(View Frustum Culling) - 知乎

[7]LearnOpenGL - Frustum Culling

[8]视锥体剔除AABB和OBB包围盒的优化方法 - 知乎

[9]有没有较完善高效的视锥剔除算法? - 知乎

[10]AABB-Plane · 3DCollisions

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/490423.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Yolov1 源码讲解 loss.py

结构 1.lt rb我觉得不是很合适 正确来说是lb rt 因为比较出来的都是左下和右上坐标 比如前两个&#xff0c;都是max出来的 选两个box左下坐标中最大的&#xff0c; 后两个则是右上坐标中最小的 那也就形成了交集面积 但是代码中仍然是lt rb我也就直接这样说 而算出lt和r…

zynq基于XDMA实现PCIE X8视频采集HDMI输出 提供工程源码和QT上位机程序和技术支持

目录 1、前言2、我已有的PCIE方案3、基于zynq架构的PCIE4、总体设计思路和方案视频输入通路PCIE数据缓存通路视频输出通路 5、vivado工程详解6、SDK 工程详解7、驱动安装8、QT上位机软件9、上板调试验证10、福利&#xff1a;工程代码的获取 1、前言 PCIE&#xff08;PCI Expre…

二叉树的实现

二叉树 文章目录 二叉树背景二叉树的概念遍历方式代码实现 背景 数组存储方式的分析 优点&#xff1a;通过下标方式访问元素&#xff0c;速度快。对于有序数组&#xff0c;还可使用二分查找提高检索速度。 缺点&#xff1a;如果要检索具体某个值&#xff0c;或者插入值(按一…

linux中使用docker部署微服务

目录 一、制作jar包&#xff08;如果看一眼很简单&#xff0c;可以直接使用结尾的jar&#xff09; 1.首先创建一个微服务 demo2 2.启动微服务&#xff08;在DemoApplication上右键执行启动就行&#xff09; 注意&#xff1a;其他操作导致的 可能遇到的报错 3.修改端口 4.新…

ChatGPT的快速发展究竟给我们带来了什么?

&#x1f61a;一个不甘平凡的普通人&#xff0c;致力于为Golang社区和算法学习做出贡献&#xff0c;期待您的关注和认可&#xff0c;陪您一起学习打卡&#xff01;&#xff01;&#xff01;&#x1f618;&#x1f618;&#x1f618; &#x1f917;专栏&#xff1a;算法学习 &am…

java基础入门-05-【面向对象进阶(static继承)】

Java基础入门-05-【面向对象进阶&#xff08;static&继承&#xff09;】 13、面向对象进阶&#xff08;static&继承&#xff09;1.1 如何定义类1.2 如何通过类创建对象1.3 封装1.3.1 封装的步骤1.3.2 封装的步骤实现 1.4 构造方法1.4.1 构造方法的作用1.4.2 构造方法的…

Unity API详解——Random类

Random类是Unity中用于产生随机数的类&#xff0c;不可以实例化&#xff0c;只有静态属性和静态方法。本博客主要介绍了Random类的一些静态属性。 文章目录 一、Random类静态属性1、基本语法2、功能说明3、代码实现 二、rotationUniform属性1、基本语法2、功能说明1、规范化向量…

前沿探索,AI 在 API 开发测试中的应用

目录 一、引言二、AI 加持下的 API 设计1、NLP 在 API 设计中的应用2、DL 在 API 设计中的应用能力一&#xff1a;Apikit 如何利用 AI 生成最佳的 API 设计方案能力二&#xff1a; Apikit 如何利用 AI 提高 API 的可用性和易用性 三、AI 加持下的 API 开发能力三&#xff1a;Ap…

k8s二进制安装部署(详细)(3主2从)

目录 kubeadm 和二进制安装 k8s 适用场景分析 多 master 节点高可用架构图 集群环境准备 部署过程 修改主机内核参数&#xff08;所有节点&#xff09; 配置阿里云的repo源&#xff08;所有节点&#xff09; 配置国内安装 docker 和 containerd 的阿里云的 repo 源 配置…

比肩 ChatGPT,国内快速访问的强大 AI 工具 Claude

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;蚂蚁集团高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《EffectiveJava》独家解析》专栏作者。 热门文章推荐…

Gateway案例

官网:Spring Cloud Gateway 中文文档:Spring Cloud Gateway 2.1.0 中文官网文档 - 腾讯云开发者社区-腾讯云 一、网关介绍: 网关就是当前微服务的统一入口 通常在微服务项目中,只有网关项目是暴露在网络里的,其他服务一般都是在内网里, 用户访问网关,网关根据访问的路径,来进…

Tomcat安装步骤及详细配置教程(2022最新版)

网上的tomcat安装及配置教程一大堆&#xff0c;但是好多都过时了&#xff0c;根本不适用现在的版本&#xff0c;今天凯歌整理一篇Tomcat安装步骤及详细配置教程&#xff0c;2022年最新版~ Tomcat安装及配置教程主要分为四步&#xff1a; 步骤一&#xff1a;首先确认自己是否已…

ChatGPT登录操作扫盲级教程,附ChatGPT登录常见报错及处理技巧

文 / 韩彬&#xff08;微信公众号&#xff1a;量子论&#xff09; 有了帐号&#xff0c;我们自然可以使用ChatGPT尽情玩耍了。 知识扩展&#xff1a;ChatGPT是啥&#xff0c;以及注册的问题&#xff0c;可以看《ChatGPT常见问题手册&#xff0c;通俗易懂版&#xff0c;3分钟了解…

Windows微信聊天图片文件的解码和图片、视频占满电脑磁盘空间的清理

1 问题现象 我的Windows版微信最近老是提示“磁盘空间不足200MB&#xff0c;需及时清理磁盘”。 使用文件资源管理器查看我的电脑磁盘使用情况&#xff0c;发现C盘只剩下174MB空间可用。系统盘C盘空间耗尽已经严重影响电脑的使用。 2 问题分析 2.1 磁盘空间占用情况分析 由于…

【学习笔记】pandas提取excel数据形成三元组,采用neo4j数据库构建小型知识图谱

前言 代码来自github项目 neo4j-python-pandas-py2neo-v3&#xff0c;项目作者为Skyelbin。我记录一下运行该项目的一些过程文字以及遇到的问题和解决办法。 一、提取excel中的数据转换为DataFrame三元组格式 from dataToNeo4jClass.DataToNeo4jClass import DataToNeo4j imp…

实操带你使用Mybatis_plus(2)

文章目录 一、通用ServiceService CRUD 接口a> IServiceb>创建Service接口和实现类测试 二、常用注解1、TableName2、TableId雪花算法3、TableField4、TableLogic 一、通用Service Service CRUD 接口 通用 Service CRUD 封装IService 接口&#xff0c;进一步封装 CRUD …

选择无服务器:Babbel 的迁移故事

Babbel 是什么&#xff1f; Babbel 是一个完整的语言学习产品生态系统&#xff0c;囊括了世界上最畅销的语言学习应用程序。我们已售出超过 1000 万份订阅和超过 60,000 门涵盖 14 种语言的课程&#xff0c;创造了全球第一语言学习目的地。自 2007 年推出产品的第一天起&#…

vivid源码分析

vivid源码分析 文章目录 vivid源码分析如何编写V4L2驱动分析vivid.c的open,read,write,ioctl过程openreadioctlv4l2_ctrl_handler使用过程 如何编写V4L2驱动 分配/设置/注册v4l2_device.v4l2_device_register,v4l2_device(辅助作用&#xff0c;提供自旋锁&#xff0c;引用计数…

LeetCode单链表OJ题目做题思路分享

目录 移除链表元素链表的中间节点链表中倒数第K个节点合并两个有序链表 移除链表元素 链接: link 题目描述&#xff1a; 思路分享&#xff1a; 我们上个博客分享了第一种方法&#xff0c;下面我们分析第二种方法&#xff1a;思路就是将每一个不等于我们要删除的值的节点依次尾…

【硬件】嵌入式电子设计基础之产品实践

电子技术是一门实践性非常强的学科&#xff0c;学习电子元器件基础知识和设计技能&#xff0c;最终为的是把具备一定功能的电路板制作出来&#xff0c;解决科研、生产、生活中的实际问题。 本篇文章从实际的电子产品出发&#xff0c;让您能够初步体验电子产品的硬件设计过程&am…