一、首章
好景不长那次写了《用动态散射Shader进一步增强镜头高帅富》 后,很多开发人员已经开始试著用它来图形河面,但效用都不尽人意。
这原因在于,河面除散射,除了很多技术细节须要考量。
在此之后,也有很多开发人员提及过河面图形的市场需求,也有很多开发人员撷取了很多有关河面图形的 Shader,但更多分散在米老鼠著色路径。
河面图形在3D工程项目中的市场需求是十分大的,即便极区河面占了约 70.8%,极难躲避河面效用。
上周发动机项目组的 youyou 元老也撷取了两个河面图形效用,包涵 正方形散射、FSR、SSAO、TAA 等众多动态图形控制技术。
但该 DEMO 如前所述 Cocos Creator 延后图形管道,对工程项目和电子设备明确要求较低,因此金狮子专门预备了那个分立的河面效用撷取,期望能对我们略有协助。
二、河面图形业务流程
河面图形控制技术十分多,相同七段的商品,对河面的明确要求相同。
毛暗星云的 《喜剧效果地下水图形控制技术归纳》 这首诗中,透过对很多3A巨作的河面图形展开预测,列举了十分多的控制技术关键点,有兴趣的好友能读到。
河面图形控制技术从单纯到繁杂来次序,能分成下列四类:
正方形著色 三角形动画电影 液体演示本文实现的是 如前所述正方形著色 的河面效用,虽然它并非高端效用,但却是大部分 3D 工程项目中采用的计划。
如前所述正方形著色的河面图形主要涉及下列几个部分:
散射透射潮差效用滨江柔边动态天空盒法线图与光照岸边浪花 由于时间关系,法线图与光照 与 岸边浪花 暂未实现。
标准的图形业务流程如下所示:
能看出,如果要实现所有效用,至少须要绘制场景 4 次。
由于这里的深度图只是和透射搭配使用,8 位精度足够用了,我们能考量借用透射图中的 Alpha 通道来存储深度信息。
优化后的业务流程图如下:
三、散射贴图图形
金狮子写的另一首诗 《用动态散射Shader进一步增强镜头高帅富》 中已经完整地剖析了动态散射相关原理,在此就不再敷述,有须要了解的读者可去金狮子的媒体号上查阅。
这里主要讲一讲本 DEMO 中的实现步骤。
步骤1、 使用代码新建两个 RenderTexture。
步骤2、 创建两个节点,添加摄像机组件,并将 clearFlags、clearColor、visibility 属性与主摄像机同步。
步骤3、 设置散射摄像机的图形优先级,确保比主摄像机先图形。
步骤4、 将新创建的 RenderTexture 赋值给此摄像机的 targetTexture 属性。
以上步骤的代码在 WaterPlane.ts 中,如下图所示:
步骤5、 在 lateUpdate 中同步主摄像机参数。
步骤6、 在 lateUpdate 中根据动态散射原理,动态计算摄像机有关主摄像机的镜像位置和旋转。
最终,图形得到的 RenderTexture 如下:
金狮小贴士:
所有物体的材质,须要加入自定义裁剪面,裁剪掉河面下列的部分。
能明显看到,上图中绿色物体的倒影,河面下列的部分是被裁剪掉了的。
四、透射贴图图形
透射图形的原理十分单纯:
图形水正方形下列的部分到 RenderTexture在河面图形阶段使用噪声图展开扰动,以演示出河面透射效用透射图形的业务流程与散射图形大致相同,只有两个细小的差别:
用于透射图形的摄像机所有参数均与主摄像保持一致即可透射图形阶段,物体被裁剪掉的是河面以上的部分下面我们来看看,本 DEMO 中有关透射的实现步骤。
步骤1、 使用代码新建两个 RenderTexture。
步骤2、 创建两个节点,添加摄像机组件,并将 clearFlags、clearColor、visibility 属性与主摄像机同步。
步骤3、 设置散射摄像机的图形优先级,确保比主摄像机先图形。
步骤4、 将新创建的 RenderTexture 赋值给此摄像机的 targetTexture 属性。
以上步骤的代码在 WaterPlane.ts 中,如下图所示:
金狮小贴士: 注意红色线框部分,本 DEMO 中透射贴图的 Alpha 通道用于标记深度信息,因此须要确保 Alpha 通道的值为 255。
步骤5、 在 lateUpdate 中同步主摄像机参数、位置、旋转等信息。
最终,图形得到的 RenderTexture 如下:
五、水面图形
河面图形主要利用了投影纹理控制技术,将三角形的投影坐标转化为UV,对透射和散射贴图展开采样。
由于使用了透射贴图,我们的河面材质不须要开启 Alpha 混合。
透射图形
步骤1、 根据投影坐标计算出屏幕UV。如下所示:
vec2 screenUV = v_screenPos.xy / v_screenPos.w * 0.5 + 0.5;
步骤2、 采样透射贴图,能得到如下图形效用:
左边为正常图形效用,右边为标记了透射内容的效用
步骤3、 使用噪声图对透射展开扰动,可得到如下效用:
散射图形
步骤1、 与透射图形一样,根据投影坐标计算出屏幕UV。
步骤2、 采样散射贴图,能得到如下图形效用:
步骤3、 使用噪声图对散射展开扰动,可得到如下效用:
菲涅尔混合
菲涅尔的计算公式从玉兔的边缘光教程已经开始,到动态散射等场合,已经出现过很多次了。下面是核心代码:
透射能视为地下水本色,利用菲涅尔因子与散射内容混合,即可实现一个带透射和散射的地下水效用。
伪代码如下:
finalColor = mix(refractionColor,reflectionColor,fresnel)
最终能得到如下显示效用:
完整代码代码请查看工程项目中的 effect-water.effect 文件。
六、潮差效用
从上面的动画电影中能看出,虽然透射和散射效用都有了。但画风有些奇怪,完全没有河面的感觉。
这是河面没有深浅效用导致的。
度信息,并根据深度信息实现潮差效用。
从上图中,我们能清晰地看到,靠近岸边的海水的颜色比远处海水的颜色透明得多。
产生这种现象的主要原因,就是 如前所述视线路径的地下水厚度 相同。
什么叫如前所述视线方向的地下水厚度,请看下图:
我们通常说的地下潮差度,是指在忽略视线因素的情况,河面到水底的高度差。
在不追究技术细节的情况下,我们能单纯地使用高度差来作为水的深度。
一种可能的伪代码如下:
depth = clamp((g_waterLevel – v_position.y) * depthScale,0.0,1.0);
其中 depthScale 是我们的深度缩放因子,能用来调节比例尺问题,以及地下水能见度线性衰减速率。
而如前所述视线路径的地下水厚度,是指视线路径与水正方形和水底交点的距离差。 即图中 点 P1 到 点 P2 的距离。
下面我们来推导一下,使用 如前所述视线路径地下水的厚度 来作为深度因子的公式。
很多好友第一反应是解直线方程,但用空间向量的特性来求解会更容易。
为方便对照理解,再贴一次上面的图:
设观察路径为 viewDir,厚度为 depth 则有:
P1 + viewDir * depth = P2
分拆为分量运算可得:
P1.x + viewDir.x * depth = P2.x
P1.y + viewDir.y * depth = P2.y
P1.z + viewDir.z * depth = P2.z
可推导出:
depth = (P2.y – P1.y) / viewDir.y
由此可得如下计算公式:
vec3 viewDir = normalize(v_position.xyz – cc_cameraPos.xyz);
float depth = (v_position.y – g_waterLevel) / viewDir.y
depth = clamp(depth * depthScale,0.0,1.0);
比起直接使用地下潮差度来说,多了一次求 viewDir 单位向量的运算,以及一次除以 viewDir.y 运算。
在非极端情况下,多出的这一点纯逻辑运算在 GPU 上是能忽略不计的,能放心使用。
将上述公式添加到图形对象的 Shader 中,并在透射图形阶段启用,将结果存入 Alpha 通道即可。
工程项目中的 Shader 代码如下图所示:
最终得到的深度信息如下:
深度混合
有了上面的深度信息,我们只须要在计算出透射颜色后,再用深度信息与水底颜色混合即可。Shader 代码如下图所示:
由于地下水的可见度是非线性的,因此对 diffDepth 使用了 pow 函数,那个 power 参数默认是 2.0。
最终能得到如下效用:
六、滨江柔边
当我们把摄像机拉近,观察河面与物体交接处的时候,能明显看到一条清晰的边界。
这条边界在散射越强的时候越明显,使我们的河面效用大打折扣。
好在我们已经有了深度信息,能根据深度来判断出哪里靠近岸边,并修改菲涅尔因子,使散射越靠近岸边的时候越弱即可。
核心代码如下:
最终能实现在全散射的情况下,河面与岸边依然平滑过渡。效用如下图所示:
再来一张远视角的图:
七、动态天空盒
为了进一步增强氛围感,DEMO 中使用了动态天空盒。
这是两个特别单纯的高效的动态天空盒计划,仅使用了两个双层纹理混合的半球模型,调节两张纹理的水平路径流动速度即可。
八、有关DEMO
所有效用参数均可调节,如下图所示:
配套视频: