前言

总算是磕磕绊绊把场景渲染出来了,踩了好多坑,好记心不如烂笔头,把做练习过程中遇到的一些零零碎碎的问题和核心技术点记录一下。

另外Unity的Bug真多🙂,不报错的Bug才是最讨厌的Bug。

主要参考文章如下:

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

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

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

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

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

https://blog.csdn.net/puppet_master/article/details/80808486

https://blog.csdn.net/weixin_40301728/article/details/106024404

https://blog.csdn.net/qq_39471191/article/details/119110139

https://www.e-learn.cn/topic/3486254

    正文

#1 Enum

崩坏3的远景图用的是Clip通过透明通道来裁切。

近景图则是Src alpha + OneMinusSrcAlpha的混合模式。

另外混合要记得关闭剔除。

注意这个0,1,2的顺序和前置的文字说明不能变哦。

5代表SrcAlpha,10代表OneMinusSrcAlpha,2代表Less

#2 广告牌算法

崩3场景的树叶是通过广告牌算法+混合+关闭剔除得到。

第一种情况,广告牌的y轴对应法线,用下面的算法

第二种情况,当广告牌的z轴对应法线时,用下面的算法

#3 平面反射

崩3水体渲染就是 散焦贴图+法线贴图+平面反射贴图,然后我自己加了flow map和噪声贴图。

这里的重点是水面的反射,水面反射可以直接利用Unity的工具生成天空盒然后根据方向采样,但是有点捞,而且无法生成实时的反射,而且容易错位。

也可以用Box Projection来修正,但是还是不能做到实时反射,而且只适用于小空间。

这里我们采用大多数游戏的做法,用另一个摄像机来生成反射效果。

这里有些许复杂,需要一点前置知识:

平面表示-点法式:

P0为平面上点,N为法向量,P为任意一个点,只要满足上面的方程,那么则点在平面上。

然后点法式点乘展开后得到

反射的基本原理

在已知A(x,y,z)和法线N的情况下,利用下面的反射矩阵就可以得到A‘。

推导过程看这里:https://blog.csdn.net/puppet_master/article/details/80808486

了解了原理之后我们来实现具体效果。

平面反射效果实现

最重要的,我们需要一个相机,与当前正常相机关于平面对称,也就是说,我们把正常相机变换到这个对称的位置,然后将这个相机的渲染结果输出到一张RT上,就可以得到对称位置的图像了。

渲染物体需要通过MVP变换,物体首先通过M矩阵,从物体空间变换到世界空间,然后通过V矩阵,从世界空间变换到视空间,最后通过投影矩阵P变换到裁剪空间。

我们把R矩阵插在V之后,在一个物体在进行MV变换后,变到正常相机坐标系下,然后再进行一次反射变换,就相当于变换到了相对于平面对称的相机坐标系下,然后再进行正常的投影变换,就可以得到反射贴图了。

Built-in渲染管线的代码如下:

但是URP中是没有OnWillRenderObject这个函数了,所以URP中更新的代码如下:

https://zhuanlan.zhihu.com/p/493766119,看这篇文章吧,代码复制多了好卡。

Copy上面这段代码,然后把脚本放在水平面这个物体上就可以了。

然后我们使用反射贴图,我们在Shader中使用对应的变量即可。但是这里采样要用屏幕坐标作为uv坐标采样。

光采样倒影贴图的话可以得到类似的效果。

详细原理或者推导过程请看我给出的参考文章。

如果上面的代码出现持续不断的Warning。

那么把这里改成-1。

或者再添加一个RenderList。

#5 屏幕深度值采样

对于沙滩近岸水的颜色的变化,需要用到屏幕深度值和纹素深度值的差值。

URP和Built-in对于屏幕坐标的计算和深度值采样并不太一样。

URP中的屏幕空间采样如下:

_ProjectionParams.x表明是不是反向投射,其它含义可以在UnityInput.hlsl里查到。

总而言之就是,Unity在大部分情况下会自动帮我们翻转DirectX-like平台下的RenderTexture,但是部分情况不会,比如GrabPass和抗锯齿,这个时候需要我们自己翻转。

屏幕深度值采样的话需要在Camera Inspector面板开启Depth Texture,在渲染配置的文件Inspector面板同样也要开启。

然后在Shader中声明TEXTURE2D_X_FLOAT(_CameraDepthTexture)和SAMPLER(sampler_CameraDepthTexture)

UnityStereoTransformScreenSpaceTex(uv)是针对VR的单通道立体渲染,平常移动端的游戏用uv就可以了。

然后将屏幕深度值变为线性空间下的深度值,即视角空间的深度值:

#6 写入_CameraDepthTexture

URP和Built-in渲染管线不一样,Built-in想要写入深度图需要LightMode为ShadowCaster这个Pass,而URP则需要LightMode为DepthOnly这个Pass。

这里直接Copy Unlit中的源码就好。

把这个Pass Copy到Shader里,或者新增一个Material来渲染这个Pass,反正URP双Shader也不能SRP Batcher。

#7 物体与水的交互

我参考的是这位大佬的做法,原理就是利用额外的相机渲染出一张水波的深度图,然后叠加到水波的法线上。https://github.com/csdjk/LearnUnityShader/tree/master/Assets/Scenes/Water/Water_InteractionParticle

这里我用URP复原一下水波的做法。

如果要用VEG做粒子特效,把下面的代码转化为VEG Shader Graph就可以了。

水体渲染Shader中的核心代码是这几句。

如果出现RenderTexture中的位置和Game面板位置不对应的问题,那么调整一下Ripple Camera的Culling mask,再调回来,就可以了(Bug?)。

#8 特效消失

如果粒子特效消失了,那么大概率是被物体挡住了,又或者生成器不在摄像机范围内,所以整个粒子特效都被剔除了。

#9 VEG simulation space

https://blog.csdn.net/qq_39471191/article/details/119110139

如果想发射世界空间的粒子,那么需要修改simulation space,Particle system自带这个功能,VEG的话用这篇文章的第一个方法就i行,总而言之就是VFX Property binder绑定某个属性,然后第二个方法我这里无效呢。

在解决了茫茫多的问题之后,总算能正常做一个物体与水面的交互了。

#10 交界处的泡沫

原理就是根据深度差值在泡沫颜色和水体颜色之间差值。

然后再根据噪声图的采样进行扰动。

核心代码就是下面几句