UE4水的制作分析,涉及利用normal在屏幕空间的投影来计算折射,各种深度采样的计算,BoxProjection方法等等:
视频效果:
https://www.bilibili.com/video/BV1Ah411Z75j
创建一个材质函数OverWater来计算所有需要的采样
Function OverWater:
每个采样的作用逐步说明:
1,UvMaping :
创建材质函数WaterUV,UV选择是使用worldPosition.xy还是uv采样贴图(可以选择用uv通道0或者1).
Function WaterUV:
使用这个材质函数以不同的scale创建2套不同的UV,以上都是放在vertexshader里头计算的,再通过 customUV传到pixelshader比较省性能.
2,NormalSampling
创建材质函数UDNNormalBlend,SampleWave用来混合以之前算好的uv1,uv2对两张normal贴图的采样(使用UDN方法来混合),计算方法很简单把输入的normal还原到[-1,1]的范围,然后xy分别相加,再归一化得出合并后的结果,即:n = nomral*2 - 1, N=normalize(n1.rg+n2.rg,n1.b);当然也可以把两个材质函数写成一个,这边输出的height储存在normal中的alpha通道的normal高度信息,到后面做岸边水波纹宽度变化的时候可以用,这里填充都是1,没有根据normal做高度变化.然后把比较大的那个normal采样的高度信息单独输出,后面做顶点动画有用.
Function UDNNormalBlend:
Function SampleWave:
3,Depth:
创建材质函数Refraction用以计算基于屏幕uv空间的折射, 制作折射的方法是随意利用normal.xy作为屏幕UV.xy的扰动方向,更真实的做法是使用(float3(0,0,1) -normal)在viewSpace中xy平面上的投影矢量作为折射扰动方向(沿着平面朝向相反的放下如下第二张图),这个材质函数的输出就是扰动后的uv.下面逐步说明:
Function Refraction:
这个部分做了一个分支确定使用normal扰动还是,使用更加真实一点的扰动方向,下面是对真实扰动做法的图片说明:
图1可以看到n'位于n相对于float3(0,01)的对称位置,这样偏移采样更符合现实,图2显示了当摄像机(长方形是屏幕)垂直水面时相对其他角度n'在其上的xy平面的偏移量最小,这个也最符合现实情况,当我们对着水面观察时,水下沿着视线方向的目标几乎不会产生折射.
蓝图中左下方的节点的作用是获得一个离相机最近的地方是1无限远的是0的一个采样,读取自定义深度时,其深度已从DeviceZ转换为PixelDepth的单位,如果您手动采样自定义深度缓冲区,则需要使用ConvertToDeviceZ函数转换pixelDepth,也就是用Custom节点 return ConvertToDeviceZ(pixelDepth); 【DX定义是1为最近点的Z值】和之前的采样相乘这样我们就能获得一个近处扰动强远处扰动弱的一个结果,加min进行限制时因为[1,0]这样的深度会让近点的精度非常高,也就是不出多远感觉就不扰动了,衰减特别快,有了min限制之后可以让近点的一定范围内的水都有相同的扰动强度.
蓝图右边的部分使用step(PixelDetph,SceneDepth)(写在CustomNode中)对视图中不是水的部分进行剔除,然后使用SceneDepth - PixelDepth来获得一个深度的采样,也就是离水面越近的地方折射扰动越小,到水面就变成不扰动了,更加符合现实情况.然后把最终的扰动结果用saturate限制一下.
计算两种深度:fogDepth和waterDepth
以fogDepth = SceneDepth - PixelDepth 可以求出沿着cameraVector的视线深度.
waterDepth是距离水面的垂直深度,计算过程如下图,用简单的相似三角形可以得出深度的数值,这个采样在水面上是0.深度越深值越大.
4,FogColor:
创建材质函数ExponentialDensity:
Function ExponentialDensity:
这里做了一个静态开关切换exp和exp2,即1/e^d或者1/e^2d,e = 2.718, 然后用剔除深度小于0的部分.
5,BaseColor,Alpha:
在处理颜色之前我们需要先处理之前算好的waterDepth,让它数值上变得小一点,方便后面计算.
创建材质函数WaterColor,然后根据深度,深度的平方,采样插值颜色fogColor和scatterColor作为最终输出的颜色,并且处理waterDepth作为透明度输出,让岸边的可见度更高.
Function WaterColor:
BaseColor
Alpha
6,Foam:
这里的泡沫的做法比较简单,只是把之前的waterDepth减去normal.a中的高度信息,用Saturate做分割,用公开变量控制采样宽度,颜色,这样normal的高度信息和深度都能对泡沫的宽度产生影响让结果更加自然一些,然后把最终结果add到baseColor 中.
7,Translucent:
创建材质函数DistanceFade,距离比较远的像素,也就是length((CameraPoint-PixelPoint).xy)比较大的位置,处于节省性能考虑可以把计算放在vs中计算,这样远处的场景和水就能比较好的融合在一起.
Function DistanceFade:
深度比较小的地方能见度高,so,把之前算好的深度乘进来然后加上需要显示的foam部分作为半透明输出.
8,Sample CubeMap:
Sample CubeMap:
Fresnel计算:采样CubeMap有两种方式:1,直接拿ReflectionVector采样,简单把z轴abs了下让垂直上下的采样对称,但是结果当然不太准确.2,创建材质函数 BoxProjectionCubemap,定义采样Cube的大小位置(BoxProjectionMin,BoxProjectionMax),然后以非常低的性能损耗计算出更精确一些的反射采样.
Function BoxProjectionCubemap:
从上图可以看到直接用ReflectionVector采样的地板是不太准确的,用box Projected方法则更加真实
如图,S是CubeMap的采样点,Bmin/Bmax(需要延展到3维空间)决定了采样Cube的大小位置,C为摄像机方向,下面的方块是需要计算反射采样的对象.按照传统的方法反射矢量平移到S位置采样的是PA点,显然和实际情况不符,而我们需要的是反射矢量和BOX求交的像素位置.
以下是计算方法,有兴趣的可以画图解析解析.
[font =“'Courier New”] float3 dir = WrlPos-CamPos;
float3 rdir = reflect(dir,WrlTexNorm);
// BPCEM
float3 nrdir = normalize(rdir);
float3 rbmax =(EnvBoxMax-WrlPos)/ nrdir;
float3 rbmin =(EnvBoxMin-WrlPos)/ nrdir;
float3 rbminmax =(nrdir> 0.0f)?rbmax:rbmin;
float fa = min(min(rbminmax.x,rbminmax.y),rbminmax.z);
float3 Posonbox = WrlPos + nrdir * fa;
rdir = posonbox-EnvBoxPos; // PBCEM end
float3 env = texCUBE(envMap,rdir);
reference: https://www.gamedev.net/forums/topic/568829-box-projected-cubemap-environment-mapping/
https://zhuanlan.zhihu.com/p/128533024
创建材质函数Fresnel分别计算两种方式的菲涅尔(Schlick的方式更加舒服一些).
Function Fresnel:
然后把反射采样和菲涅尔相乘,留到下个Sun步骤和太阳光进行混合.
9,Sun:
创建一个材质函数DirectionSpecular,用来计算高光采样.这里做了一个分支,是使用Phong(dot(V,R))还是Blinn(N,H)计算高光采样.
Function DirectionSpecular:
1,太阳的光斑直接把DirectionSplecular的结果拿来做pow就好.
2,Pow反射向量的z分量来获得远处波光粼粼的感觉.
然后把这两种采样add在一起乘一个自定义颜色值在和之前算好的Sample CubeMap加在一起后add到Sun计算的结果当中去作为最终的Reflection项输出.
10,VertexAnimation:
利用worldPositon.xy加上time进行简单的三角函数处理成水波顶点动画,然后把之前NormalSamping中输出的BigHeightSamp映射到-1,1乘入之前的结果,让顶点动画更随机一些.
最后创建Material调用材质函数OverWater,蓝图如下:
Blend Mode需要设置成Alpha Composite,至于它和translucent有何区别,可以访问以下链接:
normal在使用之前需要做转换,因为材质球默认的normal空间是切线空间的.用fresnel来控制折射的权重,把之前BaseColor,Alpha中的半透明采样和translucent进行合并作为最终的opacity.
交流群:672571935
来源:oschina
链接:https://my.oschina.net/u/4419131/blog/4411449