在Unity实现《Farcry5》 GPU地形

news/2024/11/19 11:05:38/文章来源:https://www.cnblogs.com/uwatech/p/18554438

【USparkle专栏】如果你深怀绝技,爱“搞点研究”,乐于分享也博采众长,我们期待你的加入,让智慧的火花碰撞交织,让知识的传递生生不息!


一、需求背景

开放大世界渲染中,地形的渲染占比较重,包括开发投入、表现效果及性能开销等。而地形Shader部分的性能优化已经做过多版了,但Mesh的部分还是老旧的Unity内置技术。这种CPU创建、分块、LOD、剔除、提交渲染等对于新设备,特别是高性能端游用户非常不友好。这是因为CPU这些年单核提升幅度放缓,引擎的渲染部分很难抽出大量并行的多线程,而高端GPU的算力隔代提升幅度更大。所以这类本身也适合GPU计算的部分,就有迁移的趋势了。《刺客信条》或《Farcry》系列都有这个趋势的分享。所以决定用GPUDriven技术重写这套地形。

主要参考来自《Farcry5》的一篇文章,但是它涉及流式加载等,感觉不够简单清晰。所以介绍下我自己的做法(85%一致),和他文章内没提到的细节处理。如果经验很丰富的开发者可以直接看这篇就足够了。

GDC Vault - Terrain Rendering in 'Far Cry 5'

因为技术细节比较多,所以先放下最终收益图,加点掌握这套的动力。

9700+3080设备下(1080p):


上图:Unity Terrain 下图:GPU Terrain

 

 


改造前任务分配

 

 


改造后任务分配

 

二、参数与名称

  • 精度为0.5米一个单位,为了方便都不说实际距离(米),都用单位:
  • 场景为8192x8192(4096x4096米)
  • 高度图为8192x8192 16位精度
  • 全图一个四叉树,不分多个四叉树(前面几级数量少浪费不了多少)
  • Tile:每512x512一个Tile,Tile是裁剪开始的等级,如果从四叉树根节点查询,层级太多性能不好
  • Sector:每128x128一个Sector,Sector是Lod的计算的划分尺度,也就是同一个Sector内部都是相同LOD等级
  • Patch:每16x16一个Patch,Patch是最小渲染单位,就是少于16单位后不再细分,逐个判断子节点,直接作为渲染元素

解释下为什么需要Patch:我一开始自己想的一套是用一个正方形4个顶点,来作为最小渲染单位的(DrawMeshInstancedIndirect API的Mesh)。这样可以剔除地最干净,但是这样缺点非常多,比如4个顶点一个格子,顶点比例为4,如果采用16x16,那么会有15x15格子,顶点比例接近1。这是因为同一个Mesh内的顶点可以复用,Mesh实例之间,顶点无法复用导致,另一个方面是如果四叉树需要细分到1x1,内存消耗非常大,大几百MB,而选16x16为最小节点,只要5MB,具体可选8x8或16x16,根据具体项目平衡这个值。

四叉树只记录每个单位的x、z,最小高度,最大高度和LODBias。因为ComputeShader传值最小用int,所以xz可以写成一个Index,但为了可读性,还是用x和z。又因为采用完整四叉树,所以每个子对象都可以根据自己位置算出来。如果不想计算这个也可以直接用C#算好。

三、四叉树数据

四叉树每个node有以下数据:coneDir和coneDot,是做背面裁剪用的,地形背面裁剪效果不大,离线计算比较慢所以这版不开始这功能。

 


node 数据

 

这是CPU端为了各自功能预计算方便的格式,会有部分冗余,真正存储到硬盘和给ComputeShader运行时计算的数据结构很小,而到了渲染时,渲染Shader需要的结构更小。

 


CS计算结构与Shader计算结构

 

最关键的是一个创建四叉树节点的Insert函数和一个分级迭代更新AABB和LODBias函数,LODBias标准用方差做但差别不大就选简单的DOT了。

 

 

 

 

因为在阴影存储的时候写过四叉树工具所以比较简单拿来直接用了,完整功能可以看这里:
Shader内实现四叉树压缩贴图

这个链接里提到的基本压缩方式,就是当4个Child内容非常接近时,可以删除4个Child,用Parent表示整个区域的数据。这样在实时计算LOD,准备细分子对象时,遇到空子对象就不需要细分了,无论数据量还是剔除次数都有更高性能,很适合有大量平地、海水底部大量平面的项目。

四、循环裁剪

 


循环裁剪示意图

 

这部分完全采用《Farcry5》文章的做法。C#每次Dispatch只裁剪一层四叉树,第一次是所有的Tile(Size=512),并判断Tile是否需要剔除(视锥、遮挡、距离等)。如果不需要剔除则计算LOD,看看自己是否需要细分为4个子节点(Size=256),如果不需要细分,那么进入FinalBuferr列表,如果需要细分,则进入TempB。然后C#交换设置下TempA和TempB与ComputeShader的绑定,调用下一次裁剪。这样ComputeShader就可以每次都把TempA当输入列表,TempB与Final当输出列表。这个循环的代码大概这样,每次交换TempA与TempB,但第一次用Tile那层。在DispatchCount这里,我用了每级最高可能值来做。我见过另一种做法,是利用实际TempA表长度来做,但是这样做性能并不好,因为这个数量不能回读CPU,而是用Indirect方式让GPU内部自己共享这个Count,这样NumThreads必须为[1,1,1],因为引擎会调用Count次,不会调用Count/64次(假如用NumThreads为[64,1,1]),这样会导致性能差些。

 

 

 


视锥裁剪

 

 


HIZ裁剪

 

五、高度图

高度图精度在大部分项目内是约定俗成的16bit,否则会不足或浪费,但是Unity 5.6没有提供这个类型,于是要找一个容量一样的来存,就是ARGB16(API里叫R4G4B4A4)。但是我们需要把一个高度float写入一个RGBA4通道的图,那么用Built-in的EncodeFloatRGBA是不行的,它是假设R8G8的格式下正确处理,所以需要自己封装2个函数为R4G4B4A4用。

 


高度float与R4G4B4A4的转换

 

 


ARGB16高度图存储方式

 

六、LOD计算

每个四叉树节点单独计算LOD是不行的,这是因为不同LOD衔接处要处理接缝。这需要获取相邻的LOD,而四叉树多次查询性能不足,所以需要更快地查找周围的方式 ,那自然就是把LOD写成贴图,然后根据位置对应到uv直接查询。

我先后尝试过2种实现方式性能差不多。

第一种是把所有Sector,也就是四叉树节点的Size为128的那一批,单独调用一个ComputeShader并写到RT,如下图:

 

 

 

 

但是这样会多一次Dispatch,而每次Dispatch都是有额外开销的。

所以我采用不单独计算LOD的方式,而是边裁剪边计算。因node的Size是从TileSize开始的(Tile作为第一批裁剪的节点),所以只要裁剪的size==sectorSize时,写入下LODRT即可。但是如果,还没到这个Size,比如Tile自己就被提前剔除了,这个LOD留空这么办,这里不需要做任何处理,因为如果一个方块区域被剔除不可见,那么所有与它相链接的顶点也都是不可见的,否则就是剔除错了。既然那些点不可见就不处理了, 但如果,没有被剔除而是LOD计算出来比较低,直接没降到Sector大小就进入Final作为渲染单位了呢? 这里就需要用for写入多个像素了,而比SectorSize更新Size的node 就不需要再自己计算LOD了,直接取LODRT图里自己所在Sector对应的LOD,因为一个Sector只能一个LOD。

 

 

LOD接缝问题
《Farcry5》文章里讲这个比较简单,把网格数量多的那个没对应顶点的中间点直接拉到有对应点的地方,然后高度用新位置的xz采样高度图高度,但是这样采样出来,还是会有接缝。因为若uv相同但LOD不同,采样的高度图会得到不同的高度,就出现接缝,这里需要把处于边线的点,取Max(蓝色块LOD,红色块LOD)。但即便如此,还是有一个问题,文章也没提到。

 

 

 

 

角点接缝

 

就是Mip0与2个Mip1衔接的边移动好了位置,并且也取了Max(0,1),用1来做高度图采样的LOD了,但是,因为两个Mip1也用了同样的逻辑与修正,导致它们有2个边采用了 Max(1,2)的2来作为LOD采样了,这些处理各自不相干涉,但在Mip0、Mip1、Mip2公共的那个点上,就出了问题。那个位置的点,有的用LOD1,有的用LOD2,就又对不上了。具体的解决办法有3个:

  1. 大家都采样高度图LOD0,牺牲点远处的缓存,反正这是vs内场景,顶点又不会太挨着所以缓存本来也没多少命中。
  2. 除了记录边界LOD,也记录对角LOD,取(自己,相邻,对角)最大值。
  3. 对高度图的Mipmap做特殊生成方式,然后采用子LOD结果与LOD0相同,效果与1一样只是多了缓存浪费了Mipmap内存。

七、关于HIZ剔除扩展部分

 


改造的HIZ裁剪

 

普通的视锥剔除HIZ剔除非常常见,这里不再重复讲。但是关于我自己对于常规算法的改造,是比较想分享的。不熟悉HIZ常规算法的需要先了解下,为了方便讲解,这里假设屏幕与HIZ Depth都是1024x1024的正方形。

假设现在一个地形Patch在屏幕上比较扁平(地形的特殊性导致大量投影到屏幕后扁平),比如18x68像素,根据普通算法需要找到128x128屏幕对于一个像素的Depth MipLevel,连续的判断2x2个,也就是256x256像素内都要遮挡才会剔除。这个太过保守了。扁平的AABB,最后去找正方形的深度像素比对,等于浪费了短边的信息,和68x68大小的AABB结果无差了。所以思路是避免扁平,避免的办法比较简单我想的是 在长的方向上切割成2个AABB,这里就是2个18x34的AABB了,这样来查询是分别寻找2x2个64x64,这样是查找2个128x128,比一个256x256少了一半,所以可以更多的剔除。这对于没有开发prez的项目非常有用,因为这种GPU剔除的渲染方式很难高性能做排序,如果重叠PS的开销巨大,无法有效利用EarlyZ,如果强行要用GPU排序,这里经过测试,推荐GitHub上的双调排序:

 


拆分扁平AABB 分别判断遮挡

 


这是侑虎科技第1713篇文章,感谢作者jackie 偶尔不帅供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者主页:https://www.zhihu.com/people/jackie-93-85-85

再次感谢jackie 偶尔不帅的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多表关联时过滤条件放在On和Where后面的区别

演示数据库:Mysql 创建表内连接放在On后面SELECT * FROM tb_singer t1 INNER JOIN tb_song t2 ON t1.id=t2.singer_id and t2.`name`=夜曲 WHERE t1.`name`=周杰伦;放在Where后面SELECT * FROM tb_singer t1 INNER JOIN tb_song t2 ON t1.id=t2.singer_id WHERE t1.`name`=周杰…

关于Nginx配置文件的一些知识点

图片中第一段是HTTP的配置,第二段是HTTPS配置。 listen:监听的端口号,可以自己定义。 server_name:自己定义的主机名字 root:本地资源文件的路径,做一个映射 autoindex:开不开启自动索引(文件夹一层一层的索引),一般开发环境下会打开,生产环境会关闭。 add_header:添加头…

Spring之@EnableAsync和@Async

@EnableAsync和@Async 目录@EnableAsync和@Async一、引入二、使用三、源码分析3.1、自动配置@EnableAsync的代理选择3.2、ProxyAsyncConfiguration的自动配置3.3、AsyncAnnotationBeanPostProcessor 初始化3.4、@Asyn注解实现异步的过程 一、引入 前面两个章节分析了@EventList…

你想了解的DDS协议解决方案在这里

作为OPEN联盟的活跃成员与AUTOSAR联盟的高级合作伙伴,经纬恒润致力于为国内外各大OEMs及供应商,提供全面覆盖TCP/IP、SOME/IP、DoIP、AVB、TSN、DDS、MQTT,以及10M、Multi-G等一系列关键技术领域的设计与测试咨询服务。 随着汽车电子电气架构快速演进,车企对车内网络…

VL4AD:让语义分割认识未知类别,无需额外数据和训练的OOD语义分割 | ECCV24

来源:晓飞的算法工程笔记 公众号,转载请注明出处论文: VL4AD: Vision-Language Models Improve Pixel-wise Anomaly Detection论文地址:https://arxiv.org/abs/2409.17330创新性提出VL4AD模型用于解决语义分割网络难以检测来自未知语义类别的异常的问题,避免额外的数据收集…

20222327 2024-2025-1 《网络与系统攻防技术》实验五实验报告

一、实验内容 网络攻击需要搜集的信息包括: 攻击对象的名称和域名;目标网络位置,如IP地址、DNS服务器、外部网络拓扑结构;现实世界中的对应物,如注册信息、电话号段、网络或安全管理员及联系方式、地理位置等;网络地图,包括活跃主机IP、操作系统类型、开放的端口与运行的…

小鸟科技携手纷享销客,共谱CRM国产化替代新篇章

小鸟科技(DigiBird)成立于2009年,是一家专注于全球专业视听领域,为客户提供数字化解决方案的国家高新技术企业。小鸟科技先后在北京、上海、广州、成都、西安、沈阳、济南、南京等多地设立分公司或服务网点,并拥有北京、郑州、南京三大研发团队,成功以自主品牌进入国际市…

LeetCode 2769[找出最大的可达成数字]

LeetCode 2769[找出最大的可达成数字]题目 链接 LeetCode 2769[找出最大的可达成数字] 详情实例提示题解 思路 每一步操作可同时操作 num 和 x ,可同时增加或者减少,若使 num 为最小值,每一步增加 1 个,同时 x 减少一个,则此时的 x 即为最大值 num 减少同时 x 增加,则操作…

王爽汇编笔记(第三版)

1. 测试环境 1.1 DosBox 简介:模拟dos环境的一个软件 下载地址:https://www.dosbox.com/download.php?main=1安装步骤:下一步...... 问题1: debug 不是内部或外部命令,也不是可运行的程序或批处理文件。 debug : 无法将“debug”项识别为 cmdlet、函数、脚本文件或可运行程…

VLC多媒体播放器 合并字幕srt文件和mp4文件 方法

转载自: 链接:https://blog.csdn.net/sdkdlwk/article/details/143867825 作者:sdkdlwk步骤: 1. 将视频和字幕放到同一个文件夹,并保证二者名字相同。 选择菜单"媒体"->"流"或者ctrl+s点"添加"选择视频文件,注意这里不要选择下面的使用…

聊聊springboot项目中使用jackson的一些小技巧

前言 在我们前后端联调时,很经常以json作为数据的交互格式,今天我们就来聊聊在开发springboot项目中,使用jackson进行数据渲染一些小技巧 场景一:枚举-JSON互转 在日常开发中我们为了避免过多的魔法值,使用枚举类来封装一些静态的状态代码。 但是在将这些枚举的意思正确而…