关于渲染:水体渲染

34次阅读

共计 4121 个字符,预计需要花费 11 分钟才能阅读完成。

对于水体渲染的一个简略 Demo,大部分资源以及实现都来自 Unity 官网我的项目 BoatAttack 以及 GPUI 插件。本文次要解说大抵实现思路,想具体理解的同学能够下载工程查看(800MB 左右)。

工程链接:
https://pan.baidu.com/share/i…
提取码:mrzz

须要的工具:Unity 2020,VS 2019

以下水体渲 GPU 实例化的演示:
https://www.youku.com/video/X…

水体次要实现的成果:

水体渲染成果 1

水体渲染成果 2

一、波浪

创立一个空物体,挂上脚本 ASE_Water.cs。

1. LOD
波浪应用到的资源在 Assets\ASE\Meshes\ASE_WaterMesh.fbx。

在 Unity 的 Scene 面板中的左上角,切换至网格渲染,该模型组如下所示,两头的网格顶点密集,边上的顶点稠密。

水面网格

将该模型组增加到 ASE_Water 的 Water Mesh 列表中。

而后咱们须要在 Update 中实时更改其地位,使其始终保持在摄像机朝向的下方。

更改水面地位

当初就失去了一个能够追随相机的水面了。

2. 顶点动画
当初的水面是一个立体上的网格,须要对顶点进行偏移能力模仿波浪。

代码局部在 Assets\ASE\Shaders\ASE_Water.shader 实现。

2.1 单个 Sin 函数
顶点高度函数:
Wi(x,y,t) = Aisin(Di(x,y)wi+tΦi)

Wi:批改后的顶点地位。输出为 x,y 坐标(水面顶点的 x,z)以及工夫 t,输入为变换后的 z 坐标(水面顶点的 y)
Ai:管制水的振幅
Di:波浪挪动的方向,与(x,y) 点乘失去某一方向上的重量
wi:频率,和波长互为倒数,管制水的波长
Φi:与 t 相乘代表波峰的挪动间隔,管制水的挪动速度

单个 Sin 函数生成波浪

法线函数:
首先定义顶点坐标函数:
P(x,y,t) = (x,y,Wi(x,y,t)),其中 Wi(x,y,t)即方才的顶点高度。

一个顶点的法线怎么求得?

求出 X 和 Y 方向上的偏导 B 和 T,再用 B * T 失去顶点的新法线 N。

对 X 偏导,失去 B:

同理,对 Y 偏导,失去 T:

B* T 失去 N:

其中:

// 单个 Sin 波浪
WaveStruct SinWave(half2 pos,float waveCountMulti, half amplitude, half angle, half wavelength)
{
    WaveStruct waveOut;
    float time = _Time.y;
    half w = 6.28318 / wavelength;
    half wSpeed = sqrt(9.8 * w);
    angle = radians(angle);
    half2 direction = half2(sin(angle), cos(angle));
    half dir = dot(direction, pos);        
    half calc = dir * w + time * wSpeed; // the wave calculation
    half cosCalc = cos(calc); 
    half sinCalc = sin(calc); 
    waveOut.position = 0;
    waveOut.position.y = amplitude * sinCalc*waveCountMulti;
    waveOut.normal = normalize(float3(
                            -w*direction.x*amplitude*sinCalc,
                            1,
                            -w*direction.y*amplitude*sinCalc
                        )) * waveCountMulti;
    return waveOut;
}

2.2 叠加 Sin 函数
顶点高度函数:

法线函数:
此时失去新的顶点坐标函数:

同理求得法线 N:

其中:

多个 Sin 叠加

2.3 Gerstner 叠加波浪
Sin 函数的变种,能够使得波谷更加平坦,波峰更平缓,更好地模仿波浪。
Sin 函数中的 P(x,y,t) 只会批改其 z 值,而 Gerstner 中还须要将 x,y 坐标向波峰地位挪动。

新的顶点坐标函数:

其中,x,y 别离向各自的偏导方向挪动,即波峰地位。
同理,求偏导失去 B 和 T,再用 B * T 失去顶点的新法线 N。

多个 Gerstner 函数叠加

到这里,波浪生成的局部就完结了。

二、折射

1. _CameraDepthTexture
该纹理由渲染管线在渲染完不通明物体之后生成,在 Renderer 中勾选 OpaqueTexture 即可。

2. UV 扰动
对_CameraDepthTexture 采样,不过须要增加肯定的 UV 扰动(扰动值是依据顶点法线算进去的)。

此时能够失去成果如下:

留神到黄色框内的谬误,在水面上的物体也进行了 UV 扰动,这并不应该,须要对其修改。在第 4 大节:修改中解说。

3. WaterDepth
在 Shader 中定义二维向量 WaterDepth,其中 x 存储了视角方向上的水的深度,y 存储了水的竖直深度。

WaterDepth 示意图

WaterDepth.x 对_CameraDepthTexture 采样,由采样失去的深度减去相机到水面顶点的间隔。

WaterDepth.x

WaterDepth.y 增加一个垂直水平面的相机,生成深度纹理。

WaterDepth.y

最显著的差异就在于视角深度会随着察看角度扭转,下面视角深度中的胶囊体凑近水面局部为红色,就是因为在这个角度观察,会失去较小的视角深度。

4. 修改
对_CameraDepthTexture 进行 UV 扰动时,咱们能够对 WaterDepth.x 进行同样的的扰动,失去如下成果:

WaterDepth.x(修改)

这时候,用扰动过后的 WaterDepth.x 判断该点是否在水平面以上。若是的话则勾销扰动,间接用 Screen UV 进行采样。

此时,就能失去一个较好的折射成果:

折射(修改)

能够看到,树叶以及在水面上方的方块、胶囊体都没有产生扰动了。

5. SSS 排汇
同时,随着水的 WaterDepth.x 变得越大,咱们能从水中察看到的折射局部会越少。在 Demo 里,自定义了一条排汇色带。当 WaterDepth.x 较小时,咱们可能透过水面看到水下的物体,对应了色带右边的红色;当 WaterDepth.x 较大时,咱们根本看不到水下的物体,随影了色带左边的彩色。

模仿水对光线排汇

模仿水对光线的排汇

将上述水对光线的排汇与折射局部相乘,即可失去整个折射局部的成果:

折射 +SSS(排汇)

三、反射

1. 翻转摄像机
水面反射物体的倒影,是将摄像机对于水平面做了一次翻转(地位、朝向都对于程度面对称)。对应到摄像机就是把红框内两个属性取反。

地位和朝向取反

上面试一试,将摄像机翻转后的成果:

此时若间接将反射纹理采样到程度面上,会呈现问题:

间接翻转摄像机

是因为咱们把摄像机进行翻转后,水下的物体也能被看到,跟着一起反射了。

2. 斜面裁剪
所以咱们须要对其进行一次斜面裁剪,将水面以下的物体给裁剪掉:

右边为惯例的裁剪面,左边为斜面裁剪

Unity 里能够很不便地实现斜面裁剪,调用 CameraSpacePlane(),设置好裁剪面即可。

设置裁剪面

当初看看斜面裁剪之后的水面:

反射(斜面裁剪)

3. UV 扰动
同样依据顶点法线增加扰动,不过在 Demo 里还有一张水的外表贴图:

外表贴图

能够通过该贴图使得法线扰动更加细节,后续的焦散成果也用到了该贴图。

左图依据顶点法线进行扰动,右图联合了顶点法线和外表贴图

四、菲涅尔项

当观察者和反射立体的夹角不同时,其反射和折射所占比例各不相同,菲涅尔效应即形容该景象。

通过 ViewDir(相机地位 – 水面顶点)和水面顶点法线相乘,能够失去夹角大小,而后进行幂运算可失去更好的成果。

菲涅尔项

能够看到,近处较暗,容易看到折射局部;远处较亮,容易看到反射局部。

将折射和反射依照菲涅尔项进行混合:

折射与反射混合

按菲涅尔项混合折射与反射

五、SSS 散射

在折射局部,曾经模仿了水对光的排汇,当初还须要模仿水对光线的散射(更好模拟出水体通透的成果)。散射局部的色带与排汇相同:

水对光线的散射,次要是模仿间接光照和环境光,当 WaterDepth 越大时,会有更多的光散射进去,而不是被排汇(排汇的局部是要从水下折射进去的光,散射是水上反射的光)。

模仿水对光线的散射

而后乘上环境光(环境光用的 Unity 提供的球谐采样)和间接光照:

SSS 散射

当初将 SSS 散射和之前的成果叠加:

折射 + 反射 + 菲涅尔 +SSS

六、高光反射

设置好水面的 BRDF,而后将顶点法线,ViewDir 和 LightDir 作为输出,即可失去高光反射局部:

高光反射

将其叠加到之前的成果上:

折射 + 反射 + 菲涅尔 +SSS+ 高光

七、浮沫

1. 深度采样
凑近岸边以及凑近物体的中央都须要产生浮沫,所以须要联合 WaterDepth 的 x 和 y 重量。凑近岸边的的 WaterDepth.y 个别比拟小,凑近两头悬浮物的 WaterDepth.x 个别比拟小。两者取反之后,再取最大值,即可失去下图所示的浮沫产生区域。

浮沫区域

还有在波峰的地位也能够增加一点浮沫,采纳了 frac(sin(x))用以产生随机数,使得波峰地位产生的浮沫具备肯定随机性(其实如同没有用)。

波峰浮沫区域

将浮沫纹理采样,联合浮沫产生的区域,即可失去下图所示的浮沫遮罩:

浮沫遮罩

2. 计算光照
Demo 里的浮沫自身没有色彩,须要通过浮沫遮罩计算光照失去:

不同光照对应了不同色彩的浮沫

通过浮沫遮罩将水面和浮沫进行混合:

折射 + 反射 + 菲涅尔 +SSS+ 高光 + 浮沫

最初能够加上一些后处理成果,调整场景中 Post-process Volume 的 Volume 组件即可。Bloom:

Bloom 后处理

参考链接:

lionheart:BoatAttack_水成果剖析 2_水的细节作色成果(折射,SSS,高光)

jaleco:Boat Attack 我的项目淡水技术解析

https://github.com/Unity-Techno


这是侑虎科技第 1272 篇文章,感激作者 Shawoxo 供稿。欢送转发分享,未经作者受权请勿转载。如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ 群:465082844)

作者主页:https://www.zhihu.com/people/…

再次感激 Shawoxo 的分享,如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ 群:465082844)

正文完
 0