
1. 项目概述为什么选择Ampify Shader Editor来制作冰面Shader如果你在Unity里做过渲染相关的开发肯定对写Shader这件事又爱又恨。爱的是它能创造出独一无二的视觉效果恨的是那一行行抽象的代码和复杂的数学计算调试起来简直让人头大。尤其是像冰面这种需要模拟透光、折射、表面划痕和内部杂质的复杂材质纯手写代码的周期长、门槛高一个参数调不对效果就差之千里。这就是为什么我们需要可视化Shader编辑工具。Unity官方的Shader Graph固然不错但它生成的代码是黑盒调试时你面对的是编译后的HLSL块想深入看某个计算细节很难。而Ampify Shader EditorASE最大的优势就在这里它生成的是结构清晰、可读性极高的明文Cg/HLSL代码。这意味着你不仅能用节点拖拽快速搭建效果还能随时深入到生成的代码层去微调、优化甚至把某一段逻辑复制到你自己的手写Shader里。对于追求效果可控性和后期维护性的项目来说这个特性是决定性的。这次我们就用ASE从一张白纸开始完整地制作一个高质量、可复用的冰面Shader。这个Shader会包含冰的核心视觉特征基于物理的菲涅尔反射、具有深度感的次表面散射模拟冰内部的浑浊感、动态的平面裂纹、以及可调节的霜冻效果。最终我会把完整的节点图截图和生成的代码都贴出来你可以直接拿去用在你的雪地关卡、奇幻场景或者任何需要冰冷质感的地方。2. 核心视觉分析与技术方案设计制作一个可信的冰面不能只靠一个高光加透明。我们需要拆解真实冰的物理和视觉特性并映射到Shader的各个计算模块上。2.1 冰的物理与视觉特征拆解表面反射与菲涅尔效应冰是光滑的但不是镜子。它的反射率随观察角度变化——当你垂直看冰面时反射较弱能看到冰下内容当你掠射几乎平行看冰面时反射会非常强烈。这就是菲涅尔效应是冰材质真实感的基石。透光与次表面散射冰不是完全透明的玻璃。光线进入冰内部会发生多次散射和吸收使得冰看起来有一种内部的“浑浊感”或“乳白色”并且越厚的冰这种效果越明显颜色也会偏蓝或偏绿。这是模拟冰体积感的关键。表面瑕疵真实的冰面很少有绝对光滑的。它会有划痕、气泡、灰尘和霜。这些微观的、随机的瑕疵会打破反射的连续性增加细节和真实感。内部杂质与气泡冰内部往往冻结有空气泡或微小杂质它们会散射光线形成内部的光点或浑浊区域。2.2 基于ASE的模块化技术方案对应以上特征我们的Shader将设计成以下几个可独立调节的模块在ASE中用不同的节点组Custom Nodes或注释框来隔离基础表面模块负责处理漫反射、光滑度、金属度等PBR基础属性。对于冰金属度通常为0光滑度很高~0.8-0.95。菲涅尔反射增强模块利用菲涅尔公式Fresnel Node来动态控制反射强度并可能用它来混合一个额外的Cubemap反射或屏幕空间反射SSR。次表面散射模拟模块这是难点。我们不会实现真正的体积光追。一个高效且效果不错的方案是利用视角方向和法线点乘Dot Product来模拟光线在介质中传播的衰减并用一张噪声图来扰动散射的强度和颜色模拟内部的不均匀性。最终效果会叠加到最终的输出颜色上。表面细节模块使用至少两张不同尺度的噪声图Noise Texture进行混合。一张大尺度、低频率的噪声模拟整体的霜冻或磨砂感通过影响光滑度或Alpha一张小尺度、高频率的噪声模拟细微的划痕和凹凸通过法线贴图扰动。内部气泡模块使用另一张噪声图通过一个阈值Step或SmoothStep节点来生成随机分布的白点然后以Additive相加的方式混合到自发光Emission或透射Transmission通道模拟内部气泡对光线的散射。这种模块化设计的好处是美术同学可以直观地调整每个部分的强度、颜色和纹理而不必担心破坏其他部分。例如他可以把“霜冻”调强把“气泡”调弱快速得到从“纯净冰川”到“老旧窗冰”的不同变体。3. 环境准备与ASE核心节点解析在开始连接节点之前确保你的环境已经就绪。3.1 项目与ASE环境设置Unity版本与渲染管线ASE支持Built-in、URP和HDRP。本项目以最通用的Built-in管线为例原理相通。确保你已在Package Manager中安装了Amplify Shader Editor。建议使用较新的Unity LTS版本如2021.3或2022.3以获得最佳稳定性。创建Shader与材质在Project窗口右键 - Create - Amplify Shader - 选择一个基础模板例如“Legacy/Unlit”开始会更干净。将其命名为“ASE_IceShader”。然后创建一个新材质将Shader指定为刚才创建的。纹理资源准备我们需要几种噪声/噪波纹理。你可以在网上搜索“Seamless Noise Texture”或使用ASE自带的程序化噪波节点。建议准备T_Noise_LowFreq.png: 低频率噪声用于霜冻。T_Noise_HighFreq.png: 高频率噪声用于划痕。T_Noise_Bubbles.png: 另一种模式的噪声用于气泡。 如果没有现成纹理在ASE内使用“Noise Generator”节点也是完全可行的且无资源依赖。3.2 关键ASE节点深度解析ASE节点库庞大但制作冰面以下几个节点是核心中的核心必须理解其输入输出Fresnel菲涅尔节点这是创造冰面角度相关反射的关键。其核心输入是Normal法线和View Dir视角方向。输出是一个标量0-1表示菲涅尔强度。它通常与一个Power幂参数相连Power值越大菲涅尔效应越集中在边缘掠射角。我们会用这个输出来控制反射强度混合因子。注意在ASE中获取正确的View Dir视角方向需要小心。通常使用View Direction节点并确保其Mode设置为View相对于摄像机而不是World。法线则使用World Normal节点。Depth Fade深度淡出节点模拟次表面散射时用来创造基于模型厚度的效果。它计算当前像素与背后物体或背景在深度缓冲中的距离差值。输入是Alpha原始透明度输出是经过深度衰减后的Alpha。我们可以巧妙地用它来模拟光在冰内部随着深度增加而衰减的效果。Distance参数控制衰减的范围。值越小衰减越快。通常需要与Camera Depth节点联用获取场景深度图。Parallax Occlusion Mapping (POM) / Parallax视差节点这是一个高级选项用于在不增加几何体的情况下增加强烈的深度感。对于有厚度的冰层如冰柱、冰块可以用它来让表面的划痕和凹凸看起来真的有体积。它需要高度图Height Map作为输入。对性能有显著影响移动端项目慎用。Lerp线性插值节点你的最佳伙伴。几乎所有的混合操作都靠它。输入A输入B和一个混合因子T0-1。T为0时输出AT为1时输出B中间为线性混合。我们会用它来混合干净表面和霜冻表面、混合反射强度等。Append附加与Channel通道操作节点Shader中经常需要重组数据。例如你需要用R通道的噪声控制光滑度用G通道的噪声控制凹凸强度。这时就需要用Component Mask节点提取通道再用Append节点将多个标量重新组合成向量如RGB。理解这些节点就等于握住了构建Shader的乐高积木块。接下来我们开始搭建第一个核心模块。4. 分步构建冰面Shader节点网络让我们打开ASE从空白画布开始。建议为每个模块创建注释框右键 - Create Comment保持节点图整洁。4.1 模块一基础表面与菲涅尔反射创建属性Properties在ASE属性面板创建。_Color(Color): 冰的基础色调通常设为淡蓝色或白色。_Smoothness(Range 0-1): 基础光滑度设为0.9。_SpecColor(Color): 高光颜色保持默认或微蓝。_FresnelPower(Range 0.1, 10): 菲涅尔幂次控制边缘反射锐利度初始值3.0。_FresnelIntensity(Range 0, 5): 菲涅尔强度倍增初始值1.5。_ReflectionStrength(Range 0, 1): 整体反射强度初始值0.8。构建菲涅尔计算拉出World Normal节点和View Direction节点模式设为View。连接两者到一个Dot Product节点。但更简单的是直接用Fresnel节点将World Normal和View Direction连给它。将Fresnel节点的输出乘以_FresnelIntensity再通过一个Power节点指数为_FresnelPower进行非线性调整。最后用这个处理后的菲涅尔因子通过一个Lerp节点去混合一个弱反射值如0.3和_ReflectionStrength。这样视角越倾斜反射越强。连接至PBR主节点ASE的Master Node或URP中的PBR Master是最终输出口。将_Color连到Albedo。将计算出的最终反射强度一个0-1的值连到Smoothness。你也可以将基础_Smoothness与它相乘进行全局控制。Metallic保持为0。Normal暂时留空我们会在细节模块添加。至此你已经有了一个具有角度相关反射的基本冰面。可以拖拽材质球到场景中的一个球体上旋转摄像机视角看看边缘反射是否变化。4.2 模块二次表面散射SSS模拟这是让冰有“体积”和“内部发光”感的关键。原理简化真实SSS计算复杂。我们采用一个经典的“Wrap Lighting”变体。基本思想是让背光面法线与光方向点积为负的区域也能接收到一些光并且这部分光带有冰的颜色。节点实现获取光源方向使用Light Attenuation和Light Color节点可以获取主定向光信息。更通用的方法是使用World Space Light Dir节点。计算NdotLWorld Normal点乘Light Direction。模拟包裹使用公式(NdotL * 0.5 0.5)或(NdotL 1) * 0.5。这会将NdotL从[-1,1]映射到[0,1]让背光面也有基础亮度。添加颜色与强度将上一步的结果乘以一个_SSSColor如淡青色和_SSSIntensity参数。深度衰减可选但推荐使用Depth Fade节点。将上面计算出的SSS亮度作为输入A0作为输入B用Depth Fade的输出基于厚度作为混合因子T进行Lerp。这样薄的地方SSS效果弱厚的地方SSS效果强物理上更合理。输出将计算出的SSS颜色以Additive方式连接到Emission自发光通道。注意直接加Emission会导致过亮。更好的方法是将其与Albedo颜色在最终颜色输出前进行某种混合如Screen模式但这需要自定义光照模型。作为简易实现连接到Emission并控制好强度是一个快速有效的方案。4.3 模块三表面细节霜冻与划痕现在让冰面看起来不那么“完美”。霜冻效果影响光滑度与透明度采样低频率噪声纹理T_Noise_LowFreq。使用一个_FrostAmount参数Range 0-1通过Lerp节点用噪声的某个通道如R去扰动基础光滑度。例如Lerp(BaseSmoothness, BaseSmoothness * 0.3, noise.r * _FrostAmount)。这样噪声亮的地方光滑度降低看起来像结霜。同样可以用另一通道去扰动Alpha如果启用了透明度制造不均匀的半透明效果。划痕与凹凸细节影响法线采样高频率噪声纹理T_Noise_HighFreq。使用Normal From Height节点或Bump Map自定义函数节点将噪声图转换为法线贴图。这需要你连接噪声到Height输入并设置合适的Strength_ScratchStrength。将生成的划痕法线与从模型顶点读取的World Normal或Tangent Normal通过Blend Normals节点进行混合。将混合后的法线输出到Master Node的Normal输入口。4.4 模块四内部气泡模拟生成气泡遮罩采样气泡噪声纹理T_Noise_Bubbles。使用Step节点将噪声值与一个阈值_BubbleThreshold如0.7比较。大于阈值输出1白色代表气泡否则输出0黑色。为了气泡边缘更自然可以用SmoothStep代替Step并配合_BubbleSmoothness参数。应用气泡效果将上一步得到的气泡遮罩0或1乘以一个_BubbleBrightness参数如2.0得到气泡的亮度值。将这个亮度值以Additive方式连接到Master Node的Emission通道。这样气泡所在的位置就会微微发光模拟内部散射光点。进阶可以让气泡的颜色也受视角影响轻微菲涅尔或者让气泡的亮度随冰的厚度深度衰减变化效果会更细腻。将所有模块的输出正确连接到Master Node的相应端口后你的基础冰面Shader节点图就搭建完成了。接下来我们需要把它变得健壮、可调。5. 参数优化、性能考量与代码生成节点连好了但直接使用可能效果生硬或性能不佳。我们需要进行精细调整和优化。5.1 关键参数调节心得与技巧菲涅尔FresnelPower值小如1.5反射过渡柔和像湿润表面Power值大如5.0反射集中在非常边缘的区域像光滑的金属或干净玻璃。对于冰建议在2.5到4.0之间寻找平衡。Intensity不要超过2.0否则会像镀铬。次表面散射SSSSSSColor切忌纯白。使用淡淡的蓝色#A8D0E6或青色#88C1D0。SSSIntensity从0.1开始慢慢加直到在背光面能看到一抹温和的“内部发光”即可过强会像发光塑料。表面细节霜冻FrostAmount在0.1到0.3之间足以产生微妙变化。将噪声纹理的Tiling平铺值设大如10使其细节更细微。划痕ScratchStrength通常很小0.05-0.2因为冰的划痕很细微。使用两张不同缩放比例Scale的噪声进行混合Lerp可以产生更自然、无重复图案的细节。气泡BubbleThreshold决定了气泡的密度。阈值越高如0.85气泡越稀少、越大。BubbleBrightness建议在1.5-3.0之间并确保场景中有合理的环境光或反射否则在暗处会显得突兀。5.2 性能优化要点纹理采样优化我们用了至少三张噪声纹理。如果性能紧张可以尝试将低频率霜冻噪声和高频率划痕噪声打包到同一张纹理的RG两个通道中。这样一次采样就能获取两种数据。使用ASE的程序化Simple Noise或Gradient Noise节点代替纹理采样。程序化噪声无纹理读取开销但可能更耗ALU计算单元需在目标平台上测试。复杂节点取舍Depth Fade和POM视差节点需要访问深度纹理会带来额外的开销。如果目标平台是移动端考虑简化或移除这些效果或者提供开关Toggle属性让美术根据不同机型配置。生成代码检查ASE生成代码后务必点开生成的.shader文件看一眼。检查是否有冗余计算例如同样的WorldNormal被计算多次。虽然ASE优化得不错但手动将重复计算提取到变量也是一种好习惯。5.3 生成与解读最终Shader代码在ASE编辑器中点击顶部工具栏的“Generate Shader”按钮。ASE会在你原.ase文件同级目录下生成一个同名的.shader文件。打开这个.shader文件你会看到清晰的结构// ... 属性声明 (Properties) ... // ... 变量声明 (CBUFFER) ... // ... 顶点着色器输入结构 (VertexInput) ... // ... 片段着色器输入结构 (VertexOutput) ... // 顶点着色器 v2f vert (VertexInput v) { // ... 计算世界位置、法线、视角方向等 ... } // 片段着色器 fixed4 frag (VertexOutput i) : SV_Target { // 你的所有节点逻辑都被翻译成了下面的代码 float3 worldNormal normalize(i.worldNormal); float3 viewDir normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); // 菲涅尔计算 float fresnelNdotV dot(worldNormal, viewDir); float fresnelNode (1.0 - fresnelNdotV); float fresnelPower _FresnelPower; fresnelNode pow(fresnelNode, fresnelPower); float fresnelIntensity _FresnelIntensity; float finalFresnel saturate(fresnelNode * fresnelIntensity); // 噪声采样 float4 noiseLowFreq tex2D(_T_Noise_LowFreq, i.uv * _FrostTiling); // ... 更多计算 ... // 光滑度混合 float smoothness lerp(_Smoothness, _Smoothness * 0.3, noiseLowFreq.r * _FrostAmount); smoothness lerp(0.3, _ReflectionStrength, finalFresnel) * smoothness; // ... 次表面散射、气泡等计算 ... // 最终输出结构 SurfaceOutputStandard s; s.Albedo albedoColor.rgb; s.Normal finalNormal; s.Emission emissionColor; s.Metallic 0.0; s.Smoothness smoothness; s.Occlusion 1.0; s.Alpha finalAlpha; // 应用光照模型 fixed4 c UNITY_LIGHTMODEL_AMBIENT; // 环境光 #ifdef UNITY_PASS_FORWARDBASE c.rgb LightingStandard(s, ...); // 主光照明 #endif // ... 附加光处理 ... return c; }为什么看代码很重要假设你发现气泡效果在移动设备上太耗你可以直接在代码里注释掉气泡相关的计算行或者用一个#ifdef UNITY_MOBILE的编译指令来开关它。这种灵活性是黑盒工具无法提供的。你也可以将某段出色的计算逻辑比如那个菲涅尔混合反射的公式复制到你其他的手写Shader中复用。6. 常见问题、调试技巧与效果迭代即使节点连得再完美第一次运行时也难免遇到各种问题。这里记录一些我踩过的坑和解决方法。6.1 效果不匹配预期问题排查表问题现象可能原因排查与解决方法菲涅尔反射没有变化1. 法线或视角方向输入错误。2.Fresnel Power值过大或过小。3. 连接到了错误的混合环节。1. 检查World Normal和View Direction节点连接是否正确。可以先用一个简单的颜色输出测试这两个向量如将法线直接输出到Albedo。2. 将Power暂时设为1Intensity设为1看是否有基础变化。然后慢慢调整。3. 确保菲涅尔因子最终影响了Smoothness或反射贴图的混合权重。次表面散射SSS导致模型全亮SSSIntensity值过高或计算方式错误地影响了所有区域。1. 将SSSIntensity设为0看效果是否消失。2. 检查用于模拟包裹光照的公式。确保是(NdotL 1) * 0.5 * SSSColor而不是直接用NdotL。3. 尝试将SSS输出连接到Albedo进行叠加测试而不是Emission以排除光照计算错误。表面噪声纹理有可见重复图案纹理平铺Tiling值太小UV重复明显。1. 增大噪声纹理采样时的UV缩放倍数如从1改为10或20。2. 使用Triplanar Mapping节点替代普通的UV采样对于非平面物体如冰块、岩石能彻底解决重复问题但性能开销稍大。3. 混合两张不同缩放和偏移的噪声图。冰看起来像塑料或不透明1. 反射强度不足或过于均匀。2. 缺少透光感SSS。3. 颜色太纯、太饱和。1. 加强菲涅尔效应确保边缘反射明显强于正面。2. 适当增加SSSIntensity并给SSSColor一个非常淡的冷色调。3. 将基础_Color调成接近白色如#F0F8FF饱和度拉低。增加一点点环境光遮蔽AO或顶点颜色影响打破均匀性。在移动设备上帧率下降严重1. 使用了POM视差或Depth Fade等需要深度纹理的节点。2. 纹理采样次数过多。3. 片段着色器计算太复杂。1. 为移动平台创建简化版Shader关闭或简化POM和复杂的Depth Fade。2. 合并噪声纹理到同一张图的多个通道。3. 在ASE中使用Shader Variant功能或自定义Toggle属性让美术可以关闭某些高端特性。6.2 ASE使用中的独家技巧节点图整理大量使用注释框Comment和颜色标签。将不同模块的节点框起来并赋予不同颜色如菲涅尔用蓝色SSS用绿色。使用Reroute节点来避免连线交叉让逻辑更清晰。一个整洁的节点图能极大提升后期修改和排查的效率。利用“Preview”端口ASE中几乎所有节点都有一个预览小球或小图。在调试时不要只盯着最终输出。将中间计算步骤如菲涅尔因子、噪声采样结果、SSS亮度临时连接到Master Node的Emission或Albedo上可以直观地看到每一步的计算结果是否正确。参数驱动动画冰面可以是动态的你可以将Time节点连接到噪声纹理的UV偏移Offset上制造流动的霜雾或缓慢变化的气泡效果。将Time乘以一个很小的速度系数再连接到UV的Offset输入即可。保存材质预设在材质球 Inspector 窗口点击右上角的齿轮图标选择“Save as Material Preset”。这样你可以保存一套“纯净冰川”、“破碎浮冰”、“肮脏窗冰”的参数预设方便在不同物体上快速复用。最后将这个Shader应用到一个有复杂形状的模型上比如一个雕刻过的冰块或冰柱在不同光照角度和环境下观察。真正的考验在于它与场景的融合度。你可能需要根据场景的主光源颜色微调SSS的颜色或者根据场景的色调调整冰的基础色温。记住没有一个“绝对正确”的参数最好的效果永远是服务于整体画面氛围的。这个通过ASE构建的Shader其价值就在于它给了你快速迭代和精确调整的自由度从节点图到生成代码整个流程都在你的掌控之中。