共计 5875 个字符,预计需要花费 15 分钟才能阅读完成。
重写引擎的草渲染的原因
1. 效果图
2. 为什么决定重写引擎的草渲染
我的项目立项较早引擎版本为 Unity 5.6,这里探讨 Terrain 自带草的问题,以及我一一对应的解决方案。以下的问题不是每个都必须重写渲染机制能力解决,但次要的几个需要就决定重写了。
- 法线问题,不带 Mesh 法线,用的是地表法线作为渲染法线(因为 Mesh 或 Billboard 都会用插片来做,不传法线贴图,面数限度下不可能正确表白草叶子的实在法线方向,这种模式能够根本保障光照不发黑,地表根本处于能够被阳光找到的半球)。
- 动静 Mesh 问题,个别选 Collect Detail Patches 运行时 CPU 动态创建一堆 Mesh,即有顶点数下限,又会引起卡顿(引擎源码批改成异步能够缩小卡顿但不缩小计算量会发热,PC 发热则让一些机器 CPU 降频)。
- Hi- Z 低效问题,动态创建一堆 Mesh。无奈最高水平剔除每个不渲染的草,不能最大施展 Hi- Z 剔除性能。
- PBR 材质问题,因为没有 Smooth 贴图无奈实现 PBR 渲染,导致材质成果十分差(这里独自传图也是能够实现)。
- 淡入淡出问题,因为不反对淡入淡出,所以 120 米外草的隐没与呈现无奈做到 PUBG 那样的抖动半透明过渡。
- LOD 问题,自带的草不反对 LOD,改引擎也不好实现,因为合并 Mesh 渲染的,所以无奈逐棵切换 LOD。
- Shadowmask 问题,提早渲染下,草会获取地形的 Shadowmask 进行计算到 G -buffer 的 RT4,这里能够省略优化。
- Shadowmap 问题,草无奈投递暗影。
- AO 问题,SSAO 诚然能够减少一点草的 AO,但那个精度体现的成果不显著且成果不好。
- 草的成长方向问题,植被尽管是应该反重力成长,但游戏中的草一片表白的是一小堆草,当这些草很矮时,心愿草的成长方向按地表方向成长,相似苔藓。这样做能够无效防止底部与地形切出一条直线的问题,看起来更贴合高空。
- Mipmap 问题,草在远处,惯例的 Mipmap 计算会导致剔除过多而提前隐没。应该应用间隔场贴图。
- PreZ 效率问题,PreZ 阶段只须要透明度信息,采样因 Albedo 精度须要而创立贴图性能不是最省,须要独立的 Alpha 图。
解决方案
1. 法线计划
下图中上半局部是 Unity 法线策略,导致画面较平,下半局部是半球 Mesh 法线。树和草常常会用半球法线(原理不再赘述),间接用 Mesh 法线须要较粗疏的建模或法线贴图并实现双面渲染的带简略透射的光照逻辑(即法线取反再算一次亮度)。因为我是采样 GPU Instance 绘制,所以顶点自带了法线,如果要沿用 Terrain 的渲染草形式,能够把草顶点组成 Buffer 传 Shader,而后依据 VertexID 来取。
近处须要法线是 Mesh 法线,远处须要用地形法线作为草的法线,这样能够让草与底部在远处更融为一体,所以须要对 Normal 做过渡。
2. Mesh 与 Hi- Z 计划
Mesh 采纳 ComputeShader+GPU Instance 绘制,每帧给 ComputeShader 传入四周 7 ×7 虚构网格草的数据(一个格子 40 米,这样能够做到 120 米都有草),只传发生变化的格子,一帧传一个格子。ComputeShader 每帧对这些小范畴的草进行间隔裁剪、LOD 判断、视锥裁剪与 Hi- Z 裁剪。实现最大水平剔除。这里具体做法有之前文章介绍。
《Compute Shader 进阶利用:联合 Hi-Z 剔除海量草渲染》
3. PBR 材质计划
因为草金属度都为 0,所以只有传 Smooth 贴图,把它放在 Albedo 的 a 通道,因为它总是和 Albedo 同一个 Pass 被拜访。而且只有 Alpha>0 像素的 Smooth 数据才有意义,而草个别用 Alphatest,所以须要的透明度只有不是 0 即可。所以能够压缩在同一个 a 通道,前面提到 a 会独自存储。传了 Smooth 因为提早渲染是对立的前期 Lighting,所以这里不须要写光照,只须要在 dot(normal,lightDir)<0 的中央加一点自发光模仿背光面亮度防止发黑。
下图中上半局部是 Unity 的草对立 Smooth,下半局部是 Smooth 贴图在 a 通道,成果比照不是特地显著。这里能够去看结尾美术调的 PBR 效果图。
4. 淡入淡出计划
仔细观察其余游戏成果,发现用抖动半透明(Dithering Alphatest)能够很好地实现草的呈现与隐没成果,而 Unity 自带的 Alphatest 也会因为间隔越远裁剪面积比就越大。扣除边缘比例减少来做的隐没,就相似缩放,成果必定不如挖一堆小孔(Dithering)好,Dithering 的变动更平均,然而在性能上小小取胜,倡议草的可视间隔在 100 米内的采纳 Dithering,反之太远了看不清可用 Unity 这套。但有一种成果与性能均衡做法能够对 Clip 参数做个依据间隔变动的函数。比方:
clip(a - lerp(_cutout, 1, smoothstep(hzbGrassDistance - 5, hzbGrassDistance, disCmr))); 代 https://blog.uwa4d.com/admin/write-post.php#wmd-preview 替简略 clip(a-_cutout).
比照成果:
这是引擎间接 Clip 成果
会呈现一条显著切线
这是模拟 PUBG 罗马之子等抖动半透明成果
进行时候可看到纱窗效应
这是性能更好利用间隔动静计算 Clip 值的后果
但通明区域先隐没,经常是顶部先隐没,所以成果不如抖动半透明
抖动半透明淡入淡出算法
5. Shadowmask 计划
这个版本的 GPU Instance 是不带 Shadowmask 的,草也是不参加烘焙的,因为它的 Lightmap、Shadowmask 和贴着的地表十分统一,间接取对应地表的这 2 个图采样即可。然而这一步能够简化算法,不做任何采样。提早渲染的 Shadowmask 逻辑是这样的,烘焙的对象会采样 Shadowmask 图,并记录屏幕空间的 Shadowmask 到 G -buffer 的 RT4,示意本人是否在暗影里,动静对象如果没 Lightprobe,会绘制红色示意本人不在任何暗影里(因为没通过烘焙提前计算好,零碎不晓得其在不在暗影里,所以一律疏忽暗影),但如果存在 Lightprobe,那么会依据左近的 Probe 是否在暗影里来插值计算。同样写到 RT4。如果在我的项目中用到 Bakey 烘焙,那么对于是否让 Probe 烘焙遮挡信息是可选的,若不勾上就会失落 Mask 数据。
接下来用案例解释:
下面这张是简略烘焙的场景,带了 Lightmap、Shadowmask 和 Lightprobe。它们是怎么表白本人在暗影里的呢?
1. 渲染完底板、墙壁和小方块这 3 个动态物体,它们会读取烘焙的 Shadowmask 来写到 RT4 如下图,咱们只有一个带 Shadowmask 烘焙的光所以只看红色通道。这里表白很分明,彩色为暗影内区域。
2. 渲染小球,把它想成一堆草贴地摆放(下图),能够看到是青色(这里只探讨红色通道),截图可知是 0,这套就是 Unity 对草的 Shadowmask 计算逻辑。
3. 兴许你会发现一个问题:草可不可以不去写 RT4 呢?让地表彩色的区域仍然放弃彩色就好,最初屏幕空间计算光照时,反正都是要用平行光计算光照前获取这个像素的 Mask 值当作 Atten 用,当参加 Shadowmask 烘焙的只有一个平行光的时候,青色和彩色就齐全是一个后果。或者说,不须要晓得草绘制哪些区域,如果绘制了,取草背地(绝大部分都是地表)的 Mask 作为本人是否在暗影里的判断。这样对于海量的草即便少了这个采样仍然有肉眼可见的晋升的。
4. 那么这样省略有没有问题?当然有。Unity 不会白白多计算一次的,它须要思考齐全正确性,所以性能不敢省,咱们本人的我的项目能够依据理论状况取舍。这里有 1 个小问题,当咱们从侧面看上坡上草的时候,草的背景是天空或远方就有问题了。这时候它的前面间接用的 Mask 不再是地表了,会让明明在暗影里的草显得和阳光下一样亮。如下图红框内的球体局部,如果取前面的红色,示意不在暗影里,如果本人采样计算并写入 RT4,就是青色(红色为 0)示意在暗影里。然而 Shadowmask 产生在实时暗影外,少说也有 70 到 120 米,所以那时候你了解为侧面的草的透光也曾经十分不显著了,须要看我的项目接受程度。
5. 如果你承受了这点瑕疵,防止了 Shadowmask 的采样开销,那么要如何不写 RT4 呢?frag/vert 模式的 Shader 很简略,把 out float4 RT4 的写入间接正文掉即可。如果是 surface shader 则更简略,#pragmasurface surfaceFunction lightModel noshadowmask 即可。
6. 开展聊一聊 Shadowmask,Unity 的 Shadowmask 有个很乏味的特点,它是一个超级宅男,能够轻易地让 3 年、5 年、7 年、10 年 Unity 程序、主程序、甚至 TA 都认不全它。先说什么叫 Mask,个别渲染里可能间接查图就晓得本人是不是某个属性,或是多少这种属性的叫 Mask,所以 Shadowmask 表白是不是在暗影里。而 Shadowmap 不是记录是否在暗影里,而是记录相机空间深度,失去深度不是表白是否在暗影里所以并不算 Mask。
对于 Unity 5.6,我所晓得的至多有 4 种 Shadowmask,有学习脱漏的同学能够依据这个查问具体内容,补上常识空缺。
- 烘焙出动态物体 Shadowmask 贴图
- 烘焙出给动静物体用的,利用 Lightprobe 记录 Shadowmask 插值信息
- 平行光的 Shadowmap 独自转换的 Screenspaceshadowmask
- G-buffer 中 RT4 全屏动静动态都参加计算的 Shadowmask
以下是反对 Shadowmask 前后比照:
没有 Shadowmask 反对的草在实时暗影间隔外成果
实现 Shadowmask 反对的草在实时暗影间隔外的成果
6. Shadowmap 与 Contact Shadow 计划
因为用了 GPU Instance 来绘制草,这里比较简单,能够让 Graphics.DrawMeshInstancedIndirect 分 3 次调用:
- 30 米内的 Instance,选 CastShadow 为 true 的参数,提交 MeshLOD0。
- 30 米外 LOD Distance 内的 Instance,选 CastShadow 为 false 的参数,提交 MeshLOD0。
- LOD Distance 外的 Instance,选 CastShadow 为 false 的参数,提交 MeshLOD1。
以下是开启暗影与否的比照图,个别做个暗影浓度越远越衰减为好,衰减形式能够是 ShadowCast pass 里 Clip 值的变动。
Unity 引擎不反对投递暗影的成果
本人实现 Shadowmap 后成果
Shadowmap 是用来实现对地表的投影,但因为原理与精度的限度,用来体现细小叶片的自暗影成果并不好,这方面可采纳 ContactShadow 补救。而对于 ContactShadwo 可参考:
《超过育碧品质的接触暗影(contact shadow)》
Shadowmap+ContactShadow 成果
7. AO 计划
草的 AO 计划很少被提及,后面提到 SSAO/HBAO+ 不能精密表白好草和四周地表的 AO 及其它色彩变动。长草的土与不长草的土,每天受到的日照并不同,所以它们的色彩或轻微植被状况也不同。而这不适宜做在地表贴图上,因为同一种地表有局部长草、有局部不长草。所以为了这个辨别,同时为了模仿光线进入草堆后各种衰减的模仿,单个阳光对植被自身的衰减能够用植被的半球法线简略模仿,但这份衰减也应该让地表变暗。这个计算就往往被抛弃了。想了一种简略的做法,就是离线统计草的密度,依据不同密度生成一张地表 AO 图。相似另一张 Shadowmask,然而不同于 Shadowmask,它在实时暗影范畴内也有成果。为了显著点,把参数调夸大后成果如下图:
依据草密度高度换算成光线衰减水平的图并在 PS 里加个高斯含糊
https://www.youku.com/video/X…
8. 草的成长方向计划
草的成长方向后面提到须要依据植被来独自设计,是偏差重力反方向;还是偏差地表方向。个别瘦高的(少数单子叶动物)适宜反重力方向生成,低矮的或根茎面积大的(多为双子叶动物)适宜贴地表成长。用叉乘来计算:
重力权重为 1,植被 up 方向与引擎默认雷同
重力权重为 0,采纳地表方向为植被 up 方向
9. Mipmap 与 PreZ 计划
Unity 的 Alphatest 的 Mipmap 可能为了降噪默认有个问题越远剔除越多,导致 SpeedTree 官网给的资源都不得不对每个 LOD 设置不同材质球,就为了独自设置不同间隔的 Cutout 的数值。这里有我晚期提到的一些解决办法《解决 AlphaTest 远处隐没问题》。
然而对于草,这样却有个意外播种,即会有简略版的淡入淡出,然而为了更好是变动成果,最好还是本人管制应该的 LOD 或本人计算动静的 Clip 值。淡入淡出里,提到后者的做法这里不反复了。
PreZ 是什么能够简略了解为强制版显式的 Early Z。在引擎革新过程中,咱们会强制渲染一遍深度,而后在绘制 Opaque 的对象时,采纳 ZTest Equal 来绘制 G-buffer,所以 Clip 的过程只产生在 PreZ 的 Pass。这个 Pass 只须要透明度不关注色彩。所以咱们把透明度独自存一张图,为什么独自进去能进步性能呢?因为咱们能够让这张图尺寸比 Albedo 小。为什么放大却不会重大失真呢?因为我会用半透明的有向间隔场 SDF 数据代替半透明数据来寄存,如果理解间隔场会晓得它是更准确的表白,不是它原始数据准确而是对插值更准确,所以 Mipmaps 采样时成果更好,且容许用更小的图。
额定小技巧
根部的透明度,人为成心擦除一些,好像断了一些。这样视觉错觉会让玩家脑部草的前后关系,否则都在一个面上很僵硬。
这是美术制作给到我的草,根部齐平很难看
这是我手动擦除了些根部产生的立体感
当贴近朝上看的时候须要缓缓降落一点,防止浮空地位穿帮
以上是整个我的项目的开发过程中积攒到的对于草的总结,心愿对其他人有局部启发。
这是侑虎科技第 1229 篇文章,感激作者偶然不帅供稿。欢送转发分享,未经作者受权请勿转载。如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ 群:793972859)
作者主页:https://www.zhihu.com/people/…
再次感激偶然不帅的分享,如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ 群:793972859)