Chango的数学Shader世界(七)水波模拟-透明水面,菲涅尔(Fresnel)效应

目的:

解析,改进,批评一个国外免费透明水面Shader,进一步了解Shader背后的物理原理。

参考:

菲涅尔反射

分析:

我将原水面Shader一再简化,从中抽取最主要的部分,忽略细枝末节,并改掉缺陷,指出不足。

简化版:

1.造型

其中,顶点偏移,也就是波的造型,是用贴图制造的,一目了然:

就是一张高度图经过4方向扰动,乘以波高度。然后它再乘上顶点法线在世界座标系上的Z轴高度,这个莫名奇妙的操作会使得Normal朝上的顶点的波高更加高,而偏的更加低。我认为编者脑子里也不清楚,所以我修改了,直接乘以(0,0,1)。如果你的高度图只有b通道,这个(0,0,1)都不用乘。

2.Fresnel_Function

再观原材质图,你一定会注意到我们的主角,一个叫Fresnel_Function的节点。

UE4这样起名字,也真是大言不惭,一会我们会说到。

这个节点,涉及到一个现象,菲涅尔效应。国外一般不称为效应,搜索Fresnel equations

现象倒是很简单:

对着玻璃看,越斜着看,它反射越多,越不透明,越像镜子;

正着看,反射越少,就越透明。

我们在生活中常常见到过这种现象:

(我怀疑夏天远方道路反光,也是这个原因)

总之,这是个在各个材质上都很普遍发生的现象,包括水面。

如果说我们之前的不透明水面本质就是镜子,那我们这次的透明水面本质就是玻璃。

真正的Fresnel方程比较复杂,可脱胎于麦克斯韦电磁学方程

对于透明物体:

然而UE4的Fresnel_Function节点的方程并不根据实际。它利用视线与平面越垂直,反射越少这一点,进行了粗略模拟。这个方程也是图形学中常见的:

F=(1-(V\cdot N))^{power}

其中V向量是像素点到相机位置的向量,也就是光照模型中常见的View向量,与法线N起点一致。power用来控制效果,显然由于1-V·N在[0,1]间,power越大,那么越垂直于N的V的F才越大。在下图中,也就是当power越来越大,黑色会扩大,而白环会减少,慢慢收缩到最边缘那些法线十分垂直于视线的顶点上(白色为1,黑色为0):

显然这个函数可以用来做边缘光等其他特效。

那么,我们回过头来看透明水波材质。它的Fresnel节点的power赋予了5,十分接近边缘:

注意:

以下简称Fresnel_Fuction(...)为Fresnel(power)。

以下称V垂直于N的情况为“垂视”

3.金属度(Metallic)

原材质给金属度设置的是0。可以理解,因为金属材质不发生菲涅尔效应,而且金属度越高,透明金属材质越丢失BaseColor。但金属度太低,水的透明和反光也就低了一个层次。看下金属度为1的水面,按需求和风格调整即可:

(油腻的反光又出现了)

4.反光度(Specular)

反光直接给1,没什么好说的。给0就失去了波光粼粼的效果。

5.粗糙度(Roughness)

回看透明水波材质。它的粗糙度被设置为:

Lerp(0.1,0,Fresnel(5))

也就是越垂视的地方粗糙度越为0,完全光滑;其他地方为0.1,也很光滑,但较边缘差了点。

对于对面,从直觉上来说是这样,而且Roughness影响反射光计算,越光滑反射越多,符合菲涅尔效应。

实际效果上对比不明显。

6.不透明度(Opacity)

Lerp(0.5,1,Fresnel(5))

越垂视,越不透明。也符合菲涅尔效应。

7.折射率(Refraction)

Lerp(1.1,1.3,Fresnel(5))

在介质密度不同的表明,光发生折射。从低密度到高密度,折射角小于入射角。

回忆初高中物理,折射率定律。国外更一般叫成斯涅尔定律(Snell's Law)

\frac{n2}{n1}=\frac{sin(\theta _{1})}{sin(\theta _{2})}

由于真空密度最低,或者说真空光速最快,所以所有的材质折射率都大于1。

从真空射入某材质:

\frac{n_{out}}{n_{vacuum}=1}=\frac{sin(\theta _{in})}{sin(\theta _{out})}=c/v

透明水波Shader认为越垂视,折射率越高,这是对的。首先直觉上,越弯曲的玻璃,折射越厉害。

其次,你可以认为空气与水折射率不变,当入射角变大,那么折射率一定要变大。

或者你可以认为折射率=c/v,在垂视的地方,光路更长,相当于v更小,所以折射率更大了。

8.风格化法线

你可能会发现我们这个水波像是三角透明玻璃组成的,十分风格化。放大之后是这样的:

原因是这个透明水波Shader的法线。

图中用了一个函数称为Shine_func,打开后发现实现十分简单,效果也和迪厅反光球一样:

关键是DDX和DDY是什么?

这个蛋疼的起名让我一开始以为是二阶导数,但明显不对。经过搜索我发现它就是屏幕空间x方向偏导数和y方向偏导数。

问题在于为什么结果它是这样的,而不是我们之前那么顺滑的连续函数。

以DDX为例,就是检查当前像素点(Pixel)在屏幕水平方向上离得最近的左右两个顶点(Vertex),计算从左顶点到右顶点的空间向量,再归一化。

我将这个结果赋予BaseColor,这样看的准一点。

当屏幕向右是世界系y方向时,其值偏向为(0,0,1)。因为屏幕上的顶点从左到右,顶点向y轴方向变化(注意左下角世界座标系的方向):

当我大致面朝(1,1,0)方向:

注意到方块红面由于在往屏幕右方向走时,顶点世界位置x增大(导数大于0),y不变(导数为0),而z变小(导数为负,负数clamp后为0),其ddx为(1,0,0)

方块上面由于在往屏幕右方向走时,顶点的x,y方向都有增加且一样,ddx为(1,1,0)

当我的屏幕右方向更接近x轴时,方块黄面开始变红:

尽管我们的相机位置一直在变,同一个顶点的DDX和DDY也在变,但其都保持在切平面上,叉乘出来也一定是法线方向。

这里可以看出UE4材质系统的2个缺点

1.明明与真正的WP无关,但却必须引用这个节点,给读者以误导

2.程序执行顺序对用户不清楚。这里的WP明显是已经经过WPOffset以后的WP了。WPOffset中的WP节点和这里的WP节点明明一样,出来的值却不一样,若不是我们知晓渲染管线流程,就会被搞得怀疑人生。

9.问题一:法线

上面看出来了,叉乘出来的法线应该属于世界座标系,但我们的材质默认是切线空间法线。原材质连这个都没注意到,所以我说编者脑子不清楚

你可能记得,材质有个选项可以修改。但因为一个蛋疼的原因,我们不得不手动计算,之后会讲到。我们需要手动将世界座标系的法线转到切线座标系。

(上图从左到右分别是原法线,编者原材质水波法线,和我修改后的法线。我猜由于透明水波既没什么阴影,水波的折射是否正确也很难有人看出来,所以这个问题没人注意。)

所幸UE4有节点轻松解决座标系转换:

10.问题二:折射

 

看到没有,球下方边缘有很奇怪的折射。

本来是水面折射,怎么会和球有关?既然有“屏幕空间反射”,那么这个折射是不是也是基于屏幕空间算的,然后出的问题呢?

在材质detail中搜索Refraction,我们发现引擎提供2种算法,默认算法是这样的:

简单地来说它警告你不要用于像水面一样的大面积折射面。关于为什么会有这个bug它也没说细说,只说这个是物理模拟。

我选择第二种:

 

它说这个折射是个假模拟,值给1就是没折射,2就是offset为1的折射。这个offset是啥意思它也没说清楚。还告诉我们必须用切线空间法线,这就是为什么我们之前要手动转换。

蛋疼吗?蛋疼。UE4辣鸡吗?辣鸡。

我更改"折射"为:

Lerp(1.1,1.9,Fresnel(5))

因为这个假折射率要比较大,折射效果才明显。没办法,谁叫真折射有Bug呢?咱目前这种情况,就是我写本系列教程的缘由,了解原理,运用原理,避免这种情况。因为弱势,所以被牵着鼻子走,不知道在干什么。很不幸,我因为辣鸡,又碰上了这种情况,成为了一个"能用就行"的乞讨者。

以后有机会尝试改写引擎代码,改写这个折射。

结语:

本篇解析,改进,批评一个国外免费透明水面Shader,对简单的物理和材质系统进行初步了解。

由于能力的不足,在折射问题上最终又成为了一个被拦在原理和实现之外的人。之后提升自己的能力,改变这一状况。

水波模拟系列到此告一段落。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章