资讯详情

资讯详情

Unity Shader金箔纸材质制作:法线UV扭曲实现碎闪高光

做材质的时候金箔纸这种需求大家应该都接过。我在某次项目里拿到一个礼盒包装的材质需求中间有一片烫金纸甲方要的感觉是灯光一转表面浮起一层细碎的、星星点点的金色闪烁。第一版我按通用金属材质调金属度给到0.9光滑度放在0.5出来的是一坨标准金色高光亮是亮但整个表面像一块金砖完全没有纸张该有的皱褶感。后来反复折腾才意识到金箔纸的质感核心不在金属参数而在法线方向的高频扰动——也就是标题里说的法线UV扭曲。这篇文章就把我在SurfaceShader里实现这个效果的完整过程写下来从原理、代码到调参和踩坑给想做出这种材质的朋友一条能直接复现的路径。1. 金箔纸效果的视觉本质为什么“调金属参数”这条路走不通先想清楚一件事金箔纸到底长什么样你把一片真金箔拿起来对着窗边光线慢慢转会看到金色的底子上面浮着一层又细又密的闪光点。每个亮点都不大几百个亮点挤在一起像细砂一样随角度扫过。真金箔是黄金经反复锤打、碾压形成的极薄箔片表面早就被锤得全是亚毫米级的皱褶和压痕根本不是平整光滑的金属面。问题在于Unity标准PBR提供的金属度和粗糙度两个滑杆调不出这种碎闪结构。金属度决定反射光是否带自身颜色粗糙度决定高光斑的统计宽度它们的组合只能产生一个统计意义上均匀的光斑分布。你把金属度拉到0.95、光滑度放在0.8得到的是一个大而亮、轮廓清晰的光斑像镜面金砖把光滑度降到0.1光斑变成均匀的哑光颗粒感像磨砂金喷漆。这两种都不是金箔纸。更深一层看Standard光照模型里的微表面分布函数是连续统计模型它假设表面微观法线方向服从某个连续分布然后算出整体反射光。这个假设对机器加工的金屬表面大体成立但对被锤打成皱巴巴的箔纸失效——金箔纸表面每一个皱褶都是一个特定朝向的微反射面高光不是连续光斑而是数千个独立的亮点叠加。所以结论很直接要实现金箔纸必须让采样到的法线方向在很小的空间尺度上发生明显变化。我们没法在金属参数里微调出这种变化只能直接去折腾法线贴图和UV坐标。2. 先搭一个能反射的SurfaceShader基座金属色与高光参数金箔纸本质是金属材质必须用带光照计算的Shader去做。我用Unity内置管线里的SurfaceShader一是写起来快二是不用自己拼BRDF。SurfaceShader是Unity对顶点片元Shader的一层封装你在surf函数里输出材质的颜色、金属度、光滑度、法线引擎自动生成前向渲染、延迟渲染需要的光照计算代码。金箔纸这种金属反射材质正好落在Unity Standard光照模型的能力范围内我们不需要手动实现GGX高光只要喂参数。基础版本先长这样Shader Custom/GoldFoilBase { Properties { _GoldColor (Gold Color, Color) (1, 0.78, 0.35, 1) _MainTex (Base Texture, 2D) white {} _Metallic (Metallic, Range(0, 1)) 0.9 _Smoothness (Smoothness, Range(0, 1)) 0.5 } SubShader { Tags { RenderTypeOpaque } LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 fixed4 _GoldColor; sampler2D _MainTex; half _Metallic; half _Smoothness; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c tex2D(_MainTex, IN.uv_MainTex) * _GoldColor; o.Albedo c.rgb; o.Metallic _Metallic; o.Smoothness _Smoothness; o.Alpha c.a; } ENDCG } FallBack Diffuse }把这个Shader挂到材质球上你会得到一块金色塑料板。所有需要关注的部分只有三处Properties里的参数、Input结构体里的UV字段、surf函数里的赋值逻辑。Input里的uv_MainTex是Unity按约定自动填的只要纹理属性名带_MainTex后缀Input里写float2 uv_MainTex就能拿到这张纹理由材质面板Tile和Offset决定的UV坐标。这里有个细节值得提醒#pragma target 3.0别省略。后面用到的UnpackScaleNormal依赖Shader Model 3.0以上默认的2.5有些老平台会报兼容性警告。现在这个基座的意义是先把金属底色和反射特性稳住下一步给它加入法线方向的细碎扰动。3. UV扭曲的核心机制让法线在亚毫米尺度“抖动”金箔纸效果的转折点在这里。我采用的方案是用一张噪声贴图去扰动主纹理和法线贴图的采样坐标让原本连续的表面在采样层面出现高频偏移。这种方式在视差、热扭曲、水波焦散里都很常见但用在金属纸张材质上特别出效果。为什么不直接在surf函数里给法线的xy分量加一个随机数因为那样得到的法线是纯无序的出来的碎闪会像白噪波一样糊成一片缺了纸张皱褶那种连续但紧密起伏的结构。而用UV扭曲噪声贴图相邻像素之间的偏移是平滑过渡的采样出来的法线会保留连贯的褶皱纹路。实现的核心代码是这样一段float2 noiseDir tex2D(_NoiseTex, IN.uv_NoiseTex).rg - 0.5; float2 uvOffset noiseDir * _Distortion; float2 uv IN.uv_MainTex uvOffset;_NoiseTex是一张灰度噪声贴图我取它的RG两个通道作为二维向量。一个关键操作是减去0.5纹理采样结果在0到1之间减去0.5后映射到-0.5到0.5这样偏移不是单向的既可以向左也可以向右不会导致整个UV整体漂移。接下来用扭曲后的UV去采样主贴图和法线贴图。完整版本如下Shader Custom/GoldFoilSurface { Properties { _GoldColor (Tint, Color) (1, 0.76, 0.25, 1) _MainTex (Albedo, 2D) white {} _BumpTex (Normal Map, 2D) bump {} _NoiseTex (Distortion Noise, 2D) gray {} _Metallic (Metallic, Range(0, 1)) 0.9 _Smoothness (Smoothness, Range(0, 1)) 0.5 _NoiseTiling (Noise Tiling, Float) 60 _Distortion (UV Distortion, Range(0, 0.05)) 0.008 _NormalScale (Normal Scale, Range(0, 2)) 0.6 _NormalNoise (Normal Noise, Range(0, 0.2)) 0.05 } SubShader { Tags { RenderTypeOpaque } LOD 200 CGPROGRAM #pragma surface surf Standard fullforwardshadows #pragma target 3.0 fixed4 _GoldColor; sampler2D _MainTex; sampler2D _BumpTex; sampler2D _NoiseTex; half _Metallic; half _Smoothness; float _NoiseTiling; half _Distortion; half _NormalScale; half _NormalNoise; struct Input { float2 uv_MainTex; float2 uv_BumpTex; float2 uv_NoiseTex; }; void surf (Input IN, inout SurfaceOutputStandard o) { half2 noiseDir tex2D(_NoiseTex, IN.uv_NoiseTex).rg - 0.5; float2 offset noiseDir * _Distortion; float2 uvMain IN.uv_MainTex offset; float2 uvBump IN.uv_BumpTex offset; fixed4 albedo tex2D(_MainTex, uvMain) * _GoldColor; o.Albedo albedo.rgb; float3 normal UnpackScaleNormal(tex2D(_BumpTex, uvBump), _NormalScale); normal.xy noiseDir * _NormalNoise; o.Normal normalize(normal); o.Metallic _Metallic; o.Smoothness _Smoothness; o.Alpha albedo.a; } ENDCG } FallBack Diffuse }这个Shader里有两个对法线的作用层次。第一层是UnpackScaleNormal(tex2D(_BumpTex, uvBump), _NormalScale)采样时UV已经经过扭曲法线贴图里预制的凹凸细节会随扭曲错位产生类似褶皱的连续突起。第二层是normal.xy noiseDir * _NormalNoise在扭曲基础上再叠一层细碎的高频扰动这层是金箔纸那种碎闪的点睛之笔没有它表面还是会显得偏软、偏顺。关于噪声贴图的选择我建议用fBm噪声也就是多层Perlin噪声叠加后的结果它可以同时保留低频起伏和高频细节。纯Perlin单层容易出大块斑纹纹理像云雾而不像金属皱褶。找不到合适的图也可以用在线生成工具生成灰度流水纹或者从比较均匀的丝绸细节贴图里取灰度。关键是这张图不能有太明显的方向倾向不然金箔纸上会出现一条条的拉丝感视觉上会偏向拉丝金属。_NoiseTiling参数在代码里没有直接乘以某个UNITY Matrix它其实是通过Shader的纹理面板设置的材质面板里点开_NoiseTex旁边的TilingX和Y都设置成你需要的重复次数。Input里的uv_NoiseTex会自动带上这个缩放。所以调参时直接改噪声贴图面板上的Tiling数值就好不用改代码。4. 从金色塑料到金箔纸参数配比与高频褶皱的调优实验代码写完只是第一步。把这套Shader挂到材质球上以后你大概率会面对一个既不金砖也不金箔的过渡态。这里分享一组我实测下来比较顺手的初始参数以及每个参数对最终效果的影响力。先看表参数控制内容数值越大推荐起点_Metallic反射光颜色占比金属感更强0.85-0.95_Smoothness高光锐利程度高光越聚拢0.4-0.6_NoiseTiling噪声皱褶的密度皱褶越密越小50-100_DistortionUV偏移强度褶皱起伏越明显0.005-0.02_NormalScale法线贴图基础强度表面凹凸越强0.5-0.8_NormalNoise法线高频碎闪碎亮点越多0.02-0.08我第一次拿这套Shader调参时先把_Distortion设成0.01_NoiseTiling设成60_NormalNoise放到0.05把其他参数都保持在基础值效果已经接近金箔纸了。真正动手调的过程建议按这个顺序走不要一次推所有滑杆第一步把_Smoothness锁在0.5_Metallic锁在0.9这个组合保证金色底子上有清晰的反射高光。如果这一步出来的高光面积太大、像塑料把光滑度降到0.4如果完全看不到高光就升到0.6。第二步把_NoiseTiling从60往两边试。Tiling设置到40时褶皱偏大光照下会有一块一块的明暗过渡比较像皮革拉到100以上时褶皱变成细密闪光像铝箔纸多过金箔。金箔纸我最终停在80左右。第三步调_Distortion。这个值非常敏感0.005已经看得出纹理轻微晃动0.01时表面有明显的起伏形变拉到0.03就会出现UV撕裂和法线花斑。我的经验是金箔纸控制在0.008-0.015之间超过0.02就得不偿失。第四步_NormalNoise从0开始加。它负责叠加一层极其细碎的法线扰动这层会让高光点碎开来产生一种细芒的感觉。金箔纸那层闪烁感主要来自于它。从0加到0.03基本就有效果到0.08已经非常闪不建议超过0.1否则整个表面会像祖母绿宝石带砂皮一样发花。灯光环境也需要留意。金箔纸最出效果的光是侧向主光加一盏暖色点光。主光方向跟纸面法线夹角太大时皱褶的凹陷完全落在阴影里金色会显得暗而脏夹角太小又只剩下镜面高光看不到碎闪。我一般把主光放在材质球上方偏侧45度左右再补一个低强度的环境光让皱褶暗部仍然透出一点金色。如果想快速对比版本差异可以把_NormalNoise、_Distortion分别做成两个Toggle通过MaterialPropertyBlock或者直接在材质面板调节。我在项目里做了四个预设金砖、磨砂金、银箔、金箔纸核心参数差异只在_NormalNoise和_Smoothness上4个参数组合就能覆盖大多数金属纸张材质。5. 开发中踩过的问题UV撕裂、法线花斑与移动端优化这套效果在我项目里前后出现过好几个翻车现场这里记录一下完整的排查链路免得你走同样的弯路。第一个问题是UV撕裂表现是材质表面出现一条条斜向的亮线或者暗线像纹理被硬生生扯断了。一开始我以为是法线贴图的问题换了好几张都没有改善。后来把_Distortion从0.01直接调到0.05复现裂缝越来越明显才意识到是UV偏移量过大采样坐标跨越了纹理的0-1边界GPU采样时发生了坐标回绕。定位方法很简单把surf函数里临时输出UV坐标本身o.Albedo float3(uvMain, 0);。如果能看到非连续跳变基本就是越界。解决方式有两个一是把_Distortion限制在合理范围比如0.015以下二是给噪声贴图设置Wrap Mode为Repeat而不是Clamp。Clamp模式下越界部分会掐死在边缘色值视觉上就是一条凝固的亮线特别容易被误判成纹理问题。第二个问题是法线花斑。效果看起来是表面出现大面积的蓝紫色块光照下闪烁不定。原因是我把_NoiseTiling拉到200以上同时_BumpTex用的是一张自带高频细节的法线贴图。UV扭曲后法线贴图的采样坐标跳过了过大的方向区间导致相邻像素法线突变超过90度Lighting模型把这种突变当成镜面高光来处理花斑就出来了。排查时我把_BumpTex临时删掉只用_NormalNoise做扰动花斑立刻消失说明问题出在法线贴图的高频部分。最终方案是把法线贴图换成一张低频细节更少的版本同时把_NoiseTiling降到100以内_NormalNoise维持在0.05左右。如果项目里只能用高频法线贴图那么把_Distortion再降一些也能缓解。第三类问题是移动端性能。金箔纸在电脑上看很爽打到一个中端安卓机上发热明显帧时间从8毫秒跳到20毫秒。我查了帧调试器耗电大户是材质里的三次纹理采样和SurfaceShader自动生成的一大堆变体。优化手段里最有效的一条是把噪声信息预烘焙到主贴图的Alpha通道。原本需要单独采样_NoiseTex现在只要在tex2D(_MainTex, uv)里取一次Alpha同样能拿到噪声值。代价是噪声图会跟着主贴图走同一套UV缩放少了一个独立控制_NoiseTiling的自由度。我的做法是提前在Photoshop里把噪声层压进主贴图的Alpha通道烘焙时把噪声缩放固定在一个全局值移动端直接用烘焙版本PC端保留完整Shader版本。这样两个平台的效果差异很小但移动端少了一张纹理采样帧时间降到12毫秒左右算是在画质和性能之间比较合理的折中。还有一个容易忽略的坑SurfaceShader里的#pragma target 3.0别删。默认的2.5在UnpackScaleNormal编译时会报错或者退化成低精度处理某些老安卓GPU上会出现法线方向错乱表面像被水泡过一样。把target提到3.0后这些问题大多能自动消失。6. 我自己的调参心路和一些可以继续玩的方向这次做金箔纸的过程中我最有体感的一点是别急着在金属度上面找差距。金箔纸的箔字决定了它的视觉重点在纸张那些细碎的高频皱褶上而不在是金还是铜的颜色上。把法线方向和UV扭曲这套东西弄好之后换一种颜色就是铝箔纸换一种高频噪声就是镭射卡纸这套方法论是可以直接复用的。后续自己玩的时候可以试试给_NormalNoise接一张遮罩贴图让碎闪只出现在纸面的特定区域配合手里的项目做磨损金箔效果。或者把_NoiseTex改成一个流动的UV动画函数让皱褶缓慢蠕动做一些特殊的包装宣传材质效果也很有意思。如果项目已经切到URP这套逻辑可以等价转换成Shader Graph里的UV扰动节点加上法线节点思路完全一致。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →