前言

开放世界游戏中植被和物件的数量往往是巨大,  而传统组织大量植被渲染的方式是利用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.SetTextureStructuredBuffer<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

Unity实现GPU Cull渲染相关推荐

  1. echart 实例显示位置_技术分享:如何在Unity中使用实例化渲染?

    编者按 在日常开发中,通常说到优化.提高帧率时,总是会提到批量渲染.之前简单总结了静态合批(点此查看全文)以及动态合批(点此查看全文),这次作者将和大家聊聊实例化渲染. 作者:枸杞忧天 (本文内容由公 ...

  2. unity 可视化渲染管线_如何为高端可视化设置Unity的高清渲染管道

    unity 可视化渲染管线 Prior to Unite Copenhagen in September 2019, Unity collaborated with Lexus and its age ...

  3. Unity的GPU Instancing

    Unity的GPU Instancing GPU Instancing可以用来批量绘制大量相同几何结构相同材质的物体,以降低绘制所需的batches.要想在Unity中使用,首先需要至少在shader ...

  4. extreme rays_AMD Radeon Rays集成到Unity的GPU渐进式光照贴图中

    extreme rays As developer marketing manager at AMD, my mission is to enable developers to create gro ...

  5. Unity中的物体渲染顺序

    big seven 文章目录 前言 一.摄像机渲染 二.划分渲染队列 三.不透明物体的渲染 四.透明物体的渲染 五.UGUI元素的渲染 总结 前言 Unity中物体的渲染顺序 提示:以下是本篇文章正文 ...

  6. 如何正确选择集体渲染(云渲染)和gpu离线渲染

    在数字娱乐领域,渲染是制作高质量影像的关键步骤之一.随着技术的不断发展和应用的广泛普及,渲染方式也在不断演进.目前,集体渲染(云渲染)和GPU离线渲染是两种比较流行的渲染方式.那么,哪种方式会更快呢? ...

  7. ae怎么设置gpu渲染_AE/PR mac版如何开启GPU加速渲染?

    AE和PR是很多小伙伴比较熟悉的一款软件了,那么在使用 CC mac版的该软件的过程中如何开启GPU加速渲染呢?对此不清楚的小伙伴可以来看看小编为大家分享的方法啦~ 方法一: 在AE和PR的常规设置选 ...

  8. 腾讯云GPU服务器渲染型GN7配置性能详解

    腾讯云GPU服务器NVIDIA渲染型GN7vw在GN7基础上配置vDWS License服务器并安装GRID driver的渲染型实例,GPU采用NVIDIA Tesla T4,CPU基于2.5GHz ...

  9. Linux使用gpu渲染桌面,Firefox Nightly新版已经支持GPU网页渲染,Linux等全平台可用...

    火狐浏览器最新Firefox 63 Nightly版增加了WebRender工具,能让配置NVIDIA显卡的Windows 10桌面硬件自动开启GPU网页渲染功能,当然该版本也支持Linux.macO ...

最新文章

  1. RDKit | 基于RDKit输出分子结构图(Image)的方法
  2. 为什么 Redis 的吞吐量能这么高
  3. Linux下使用dmidecode查看服务器的详细的硬件配置
  4. CentOS系统环境下安装MongoDB
  5. 直播预约 | 揭秘微信支付背后的数据库技术
  6. 前端面试---Vue部分考点梳理
  7. WSS学习(一)---简单部署图
  8. php定时执行代码漏洞_在CTF比赛中发现的PHP远程代码执行0day漏洞
  9. (组合数学习题)递推关系一道经典题分析与解答
  10. ShardingSphere源码解析 初步准备
  11. mp3排序软件哪个好用_U盘加密软件_U盘防拷贝软件哪个好用?
  12. 使用Fastjson解析List对象时出现:{$ref:$.data[0].task.OBJECTS[0]}的问题原因及解决方法...
  13. vscode源码分析【一】从源码运行vscode
  14. Unity 两个UI(坐标)之间的连线(直线)。如连线题
  15. OneAPIConnect(一) 欧姆龙FINS协议实现源代码
  16. 计算机键盘汉字,统一码计算机汉字键盘输入法
  17. DSP-BIOS使用入门
  18. 零经验转型AI产品经理 —— 一些对跨行、跨职能转型者的建议
  19. 淘宝卖家缺货退款的常见问题解答
  20. 图灵社区 : 阅读 : (一)开始准备

热门文章

  1. html5文本框怎么透明度,话说半透明文本框!(不透明度可以任意调节,可以实现全透明)...
  2. 东华大学python题库_简单的中国MOOC大学列表提取 - Python
  3. 【转帖】追本朔源说COM
  4. AD19 PCB设计导入元件库、导出pdf、定义板子形状、生成元件库、铺铜基本操作总结
  5. 《数字化转型指数报告2021》发布,广东、上海、北京继续领跑全国数字化转型
  6. Detectron2 API 之 config | 十五
  7. 将Excle表格数据导入数据库
  8. 自定义View——双色球彩票选号界面,模仿网易彩票
  9. ArcMap安装与使用入门
  10. datagrip和idea查询数据库 ctrl+enter 查询快捷键设置