用Shader Graph创建风格化水体Shader | URP

这篇教程详细介绍了如何使用Unity的ShaderGraph创建一个风格化的水体着色器,包括计算水深、颜色与不透明度、折射、泡沫效果、光照交互以及波浪和浮力的模拟。教程通过多个步骤和节点图示展示了具体实现方法,适合对ShaderGraph感兴趣的开发者学习。

导语

这篇教程来自 Unity 社区 @alexanderameye ,将一步步介绍怎样用 Unity 的 Shader Graph 创建一款漂亮的风格化水体着色器。我们的目标不是制作物理上精准的水,而是实现一种 实时、可控且好看的 风格化水体。
着色器文件可以在文末找到,一起来学习吧~

1.最初准备

⚠ 本教程使用 Unity 2022.2.6f1 和 Universal RP 14.0.6 制作而成。
在开始教程前,我们必须先准备好以下几件事。

渲染设定

在 URP 管线配置文件里启用 Depth(深度)和 Opaque(不透明)纹理 。
图注:URP 渲染设定

着色器与材质

新建一个 Unlit(不受光) 的 Shader Graph,将材质设为 Unlit ,表面类型设为 Opaque 。
图注:着色器设定
接着用这个着色器创建一份材质。
图注:风格化水材质
选中新材质,在 Advanced Options(高级选项)里把 Render Queue(渲染队列)改为 Transparent(透明) 。
图注:材质设定

场景

场景方面,我用了 Artkovski 的 The Illustrated Nature ,不过你可以用别的场景。
在场景里,我新建了一个平面,把材质添加了上去。
我们接下来就开始吧!

2.求出水深 (Water Depth)

制作水着色器最重要的一步是 确定水的深度 (Water Depth) 。我们会用深度值来驱动许多其他效果,比如变色、透明、和泡沫。

相对摄像机深度

大部分水着色器教程基本都用以下这套节点来计算由浅(1,白色)到深(0,黑色)的渐变深度。
图注:相对于摄像机的渐变深度
在这套节点里, Scene Depth (Eye) 节点会返回摄像机与水下某个物体的距离(按世界的距离单位米计算)。你可以把它想象成一条从摄像机射向水的射线,它在第一次击中水面下某个物体时便会停止。这条射线所达到的距离就是节点返回的距离。
图注:场景深度(Scene Depth)-屏幕位置(Screen Position)=水深(Water Depth)
我们真正在意的不是摄像机到水底物体的距离,而是 水面到水底物体的距离 。为此,我们能用 Screen Position (Raw) 节点的alpha通道来获取摄像机和水面间的距离,然后两段距离相减来取得代表 水深的距离 。正如上方图表所示。
图注:深度相减
最后我们 Divide (除以)一个深度范围/距离控制参数, Saturate (限值)输出(将范围限为0到1),然后用一个 One Minus (一减)运算在河岸求得白色值,在水深处得出黑色值。
图注:深度除法运算
需要注意的是,这个深度值 不是水的垂直深度 。如果你看向水面,射线会从眼睛向水的某一点发射,而这些节点所求出的距离是射线命中水面到命中水底所穿过的距离。意味着 水面上同一点所返回的深度值将由你看向水面的角度决定 。
这种效果/瑕疵可在下方摄像机晃动的动图中看到。这一点在这块石头上尤为明显,水面上同一点的颜色会根据观测角的大小而变黑或变白。

世界空间深度

个人来说,我不喜欢上边做的深度效果。接收到的深度值会随摄像机的移动而改变,而我更喜欢独立于摄像机位置的固定深度值。
💡这只是个人喜好,你完全可以继续用相对于摄像机的深度运算。
为了“修”这种相对于摄像机的深度,我另找了一种在游戏世界里计算深度的方法。
图注:世界空间深度衰减
这套节点的水深测量方式就如同用量尺垂直地伸入水体,量出的是水面到水底的距离。即使摄像机再怎么动,计算出的深度值也不会发生改变。

Depth Fade子图表

为了保持整张表干净整洁,把刚刚创建的所有节点划到一张 Depth Fade 子图表里不失为一个好办法。
图注:深度衰减的子图表

3.颜色与不透明度

在这一节我们将用刚刚求出的深度值为水面上色。

浅水与深水

有了水的深度后,我们能用它来控制水的颜色和透明度。直接把 Depth Fade 子图表的输出(一个0到1之间的值)连到 Lerp 节点,形成浅水色到深水色的渐变。
图注:浅水与深水色
深浅水颜色的逐渐变化能带来不错的效果。

拓展:HSV线性渐变

正如Alan Zucconi在他 讨论颜色插值的文章 里所解释的,在HSV空间而不是RGB空间计算渐变可以改善水的颜色。要在Shader Graph里做到这点,我们可以用一个自定义节点将RGB颜色转换成HSV,完成插值后再重新转换成RGB。
这个自定义节点可以像这样接入前边的图表里。
图注:HSV颜色线性渐变
这样对于水的颜色来说,深水和浅水中间地带的色彩会显得更加鲜艳。

拓展:海报化

海报化是一种很酷又好做的效果。我们只需要添加 Posterize 节点,用一个属性来控制步骤数即可。
海报化技术在游戏 A Short Hike 里有很好的使用例子。下图里的很多条色带就是海报化的效果。

额外提示:使用渐变色

如果你想要进一步控制水的颜色,可以使用 渐变纹理 来控制颜色。我将下例的渐变色转换成了一张256x1的纹理,再用[0, 1]以内的深度值进行采样。之所以要将渐变色转换成一张纹理,是因为Shader Graph并不支持渐变色属性。
这样一来,你就能做出颜色的硬转换或软转换了。
下方是把渐变色转换成纹理的代码:
    
    
    
using UnityEngine ; # if UNITY_EDITOR using UnityEditor ; # endif using System . IO ; public static class GradientTextureMaker { public static int width = 128 ; public static int height = 4 ; // needs to be multiple of 4 for DXT1 format compression public static Texture2D CreateGradientTexture ( Material targetMaterial , Gradient gradient ) { Texture2D gradientTexture = new Texture2D ( width , height , TextureFormat . ARGB32 , false , false ) { name = "_gradient" , filterMode = FilterMode . Point , wrapMode = TextureWrapMode . Clamp } ; for ( int j = 0 ; j < height ; j ++ ) { for ( int i = 0 ; i < width ; i ++ ) gradientTexture . SetPixel ( i , j , gradient . Evaluate ( ( float ) i / ( float ) width ) ) ; } gradientTexture . Apply ( false ) ; gradientTexture = SaveAndGetTexture ( targetMaterial , gradientTexture ) ; return gradientTexture ; } private static Texture2D SaveAndGetTexture ( Material targetMaterial , Texture2D sourceTexture ) { string targetFolder = AssetDatabase . GetAssetPath ( targetMaterial ) ; targetFolder = targetFolder . Replace ( targetMaterial . name + ".mat" , string . Empty ) ; targetFolder += "Gradient Textures/" ; if ( ! Directory . Exists ( targetFolder ) ) { Directory . CreateDirectory ( targetFolder ) ;
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值