Unity Shader 噪声纹理从生成到实战:原理、采样流程与避坑指南

发布时间:2026/10/17 12:49:42
Unity Shader 噪声纹理从生成到实战:原理、采样流程与避坑指南 做 Unity Shader 这几年我一直觉得噪声是那种“用得最多的冷门话题”。说冷门是因为新手期大部分时间都在折腾光照、颜色、UV 动画这些一眼能看到变化的东西说最常用是因为一旦开始做特效、做材质抖动、扭曲、溶解、波纹几乎全都绕不开一张噪声贴图。甚至可以说手里有一张顺手的噪声图能顶掉一半效果逻辑。团队里现在问得最多的问题就是“你这个效果噪声怎么采的”“采完怎么用”。我最初也在这上面踩过不少坑——噪声贴图拉进 Shader 之后什么反应都没有、动态波纹抖得头晕、顶点随机偏移直接闪成雪花屏。这篇就把我从生成一张噪声纹理开始到最后让效果完整呈现在模型或 UI 上的流程、原理和避坑点都理一遍。希望对做 Shader、做特效的同学都能有点用。1. 噪声纹理的来源程序化生成比美术手绘更可靠先解释清楚噪声纹理到底是什么。简单说它就是一张灰度图黑白信息随机分布。普通贴图看名字就知道内容噪声贴图打开之后看到的则是类似电视雪花或者云层一样的图案。在 Shader 里我们通常不是去“看”它而是把它当作一组 0 到 1 之间的数值来源拿到这组数值后去驱动位移、透明度、颜色混合或者顶点位置。噪声纹理获取的方式一般有两种美术手绘或者素材库找现成的以及用脚本程序化生成。美术手绘的问题很实际分辨率一旦定死换一套 UI 尺寸或者模型比例就得重新出一张想把纹理做到四边无缝平铺难度不小手动调起来非常痛苦再加上手动调的灰度往往带颜色倾向放进 Shader 之后取哪个通道都不干净。程序化生成则完全绕开这些麻烦尺寸可以按需指定无缝平铺在算法层面解决灰度分布可以通过网格密度精确控制。这也就是为什么你在主流引擎里看到的默认噪声资源几乎全都是程序化生成的。1.1 程序化生成的核心思路程序化生成噪声的原理并不复杂把纹理划分成一个个小格子每个格子角上放一个随机亮度格子内部的像素则根据四个角的值做双线性插值。这样相邻格子之间亮度变化是连续的不会出现电视雪花那种一个像素一个颜色、毫无关联的跳变。为什么要做插值而不是让每个格子直接填一个随机色如果格子内部直接填同一个随机值画面会产生非常明显的马赛克感格子与格子之间的亮度是断裂的。插值之后过渡平滑看起来就像云层、地形或者水波这种噪声被称为 Value Noise。更进一步Perlin 噪声把角上的随机亮度换成随机方向向量用梯度方向来影响格内取值过渡会更自然。对 Shader 应用来说两者都能用Value 噪声计算量更小Perlin / Simplex 噪声视觉质感更好。1.2 一版可直接用起来的 C# 生成代码下面是一段生成无缝噪声纹理的 C# 代码基于 Value Noise 加双线性插值实现。注释我写得很足顺手复制到项目里就能用。using UnityEngine; public static class NoiseGenerator { public static Texture2D GenerateNoiseTexture(int width, int height, int gridSize, int seed) { Texture2D tex new Texture2D(width, height, TextureFormat.RGBA32, false); Random.State oldState Random.state; Random.InitState(seed); // 生成每个晶格角点的随机亮度 float[,] corner new float[gridSize 1, gridSize 1]; for (int y 0; y gridSize; y) { for (int x 0; x gridSize; x) { corner[x, y] Random.value; } } // 保证左右、上下边缘数值一致图片才能无缝平铺 for (int y 0; y gridSize; y) { corner[gridSize, y] corner[0, y]; corner[y, gridSize] corner[y, 0]; } corner[gridSize, gridSize] corner[0, 0]; for (int py 0; py height; py) { float fy (float)py / height * gridSize; int gy Mathf.FloorToInt(fy); float ty fy - gy; for (int px 0; px width; px) { float fx (float)px / width * gridSize; int gx Mathf.FloorToInt(fx); float tx fx - gx; float v00 corner[gx, gy]; float v10 corner[gx 1, gy]; float v01 corner[gx, gy 1]; float v11 corner[gx 1, gy 1]; float v0 Mathf.Lerp(v00, v10, tx); float v1 Mathf.Lerp(v01, v11, tx); float v Mathf.Lerp(v0, v1, ty); tex.SetPixel(px, py, new Color(v, v, v, 1f)); } } tex.Apply(); tex.wrapMode TextureWrapMode.Repeat; tex.filterMode FilterMode.Bilinear; Random.state oldState; return tex; } }这段代码里值得留意的是 seed 参数和 Random.state 的保存恢复。传入相同的 seed每次生成的纹理完全一致方便效果复现。而那段 state 的存取则是避免生成噪声时破坏全局随机序列不然会影响到关卡里其他随机逻辑属于比较隐蔽的细节。代码中的 gridSize 控制噪声颗粒的疏密gridSize 越小噪声块越大越大颗粒越细密。运行后把纹理保存成 PNG 或者直接放进工程你就可以得到一张四边无缝的灰度图。生产项目不一定非要自己写这套生成器很多开源库和资源商店都有更高质量的 Perlin / Simplex 实现但明白这套原理能帮你判断某个场景应该用哪种噪声。1.3 纹理导入之后的设置无论是运行时生成转成 Asset还是存成 PNG 再导入 Unity都需要检查三个关键设置Wrap Mode 设为 Repeat保证采样范围超过 0 到 1 时能循环取色Filter Mode 设为 Bilinear保证噪声数值平滑Format 建议用 RGBA32如果追求极致内存可以用 R8 格式不过要确认当前渲染平台支持单通道纹理。如果纹理只是在运行时生成给 Shader 用注意别放在 Update 里反复生成性能会很难看。比较好的做法是放到 Start 或静态构造方法里生成一次然后全局缓存引用。2. 噪声原理速通为什么不用随机数很多刚开始学 Shader 的人会问我只是想做一个随机抖动的效果为什么不能直接在 Shader 里写个 random当然可以但你会发现结果完全不是想要的因为“随机数”和“噪声”在视觉效果上差了整整一个维度。2.1 白噪声与连续噪声的区别白噪声的特点是每个像素独立随机相邻两个像素之间没有任何关联。Shader 里最经典的白噪声模拟是 hash 函数代码极其便宜float hash21(float2 p) { p frac(p * float2(123.34, 456.21)); p dot(p, p 45.32); return frac(p.x * p.y); }这个函数的输入是像素坐标返回一个 0 到 1 之间看起来毫无规律的数值。任何两个相邻像素拿到的结果都几乎不相关所以它适合做雪花、颗粒感、随机闪烁这类效果。但如果用它来驱动水面波纹或者布料抖动结果就是表面洒了一把跳跳糖每个点乱蹦完全不成形状。连续噪声恰恰相反。Value 噪声让亮度在空间上连续变化Perlin 噪声让坡度也连续变化这就给了视觉上一个很自然的“流动”和“起伏”的感受。判断噪声该用哪种只需回答一个问题效果需要空间连续性吗需要就选后者不需要就选前者。2.2 Perlin 噪声的直觉理解把 Perlin 噪声想象成一片山丘地形。普通 Value 噪声只控制每个网格点的高度山丘之间的斜坡可能是直上直下的Perlin 噪声则在每个网格点上也控制了坡度的方向斜坡本身就是平滑延续的。所以 Perlin 噪声做出来的云、大理石、木纹细节过渡明显更顺眼。Shader 里实际用的时候大部分情况并不需要区分 Value 还是 Perlin关键是别拿白噪声去冒充它。2.3 FBM 叠加出自然细节真实的物体表面很少只有一个频率的起伏。云层既有大块轮廓又有细腻纹理水面波浪大小波同时存在岩石表面更是一层细节叠一层细节。想让噪声效果更丰富就需要 FBM分形布朗运动叠加本质就是把同一张噪声在不同频率上采样再按越来越小的权重累加n 0.5 * n1 0.25 * n2 0.125 * n3 ...每一层频率翻倍、振幅减半低频负责整体轮廓高频负责表面细节。做波纹或者岩浆效果时叠加两层到三层就能看出明显差距。不过叠加要慎重。每一层噪声都是一次纹理采样手机上四层叠加很可能直接把帧率拉到不可接受。我的建议是移动端控制在两层以内而且第一层已经决定基本形状了后面只是润色多一层少一层都行。3. Shader 里采样噪声的通用流程噪声纹理拿到手接下来的问题就是如何在 Shader 中正确采样它。无论你之后是做抖动、扰动还是溶解核心步骤其实是同一套我把它拆成 uv 处理、时间驱动、采样位置三个环节。3.1 uv 缩放是第一步也是坑最多的地方第一次用噪声的人最容易遇到的现象噪声贴图明明接进了 Shader结果画面上啥也没有或者只有一个平平整整的灰块。原因几乎都是没做 uv 缩放。Unity 里模型默认采样纹理用的 uv 范围是 0 到 1。如果不做任何处理你就是把整张 512 的噪声图压缩到一个单位区间里不足以展示噪声本身的颗粒结构。正确的做法是先把 uv 乘一个倍数float noiseUv i.uv * _Tile; float n tex2D(_NoiseTex, noiseUv).r;_Tile 通常取 3 到 10 之间比较合适。3 左右是偏大的云块8 以上开始出现细碎颗粒。这个数字很影响最终观感调试时把它暴露出来用滑条实时拖比写死在代码里效率高得多。如果你采样的坐标要跟随主纹理的平铺参数Unity 提供了一个现成的宏float2 noiseUv TRANSFORM_TEX(i.uv, _MainTex);它会自动根据 _MainTex_ST 的缩放和平移处理 uv非常省事。噪声纹理如果有自己独立的平铺需求就把 _NoiseTex_ST 套上去。3.2 让噪声动起来_Time 的正确用法噪声静止不动能用的场景其实有限绝大多数特效都需要某种流动感。让噪声动起来的核心公式非常简单float2 flowUv uv * _Tile float2(_Time.y * _FlowSpeedX, _Time.y * _FlowSpeedY); float n tex2D(_NoiseTex, flowUv).r;意思就是在采样之前把 uv 整体加一个随时间递增的偏移量这样每次采样取到的纹理区域都不一样视觉上表现为噪声图案在表面匀速滑动。为什么不直接用 _Time.y因为 _Time.y 增长速度很快直接拿它加进 uv流动速度通常远超你想要的视觉效果。经验上速度值在 0.1 到 2 之间比较可控大于 3 就会出现明显的闪烁感。还有一点很多人会忽略让噪声纹理“整体流动”和让噪声“原地翻滚变形”是两回事。前者就是上面的坐标平移后者需要对噪声坐标再做一次噪声扰动。流体岩浆一类效果常用后一种但开销明显更大float2 p uv * _Tile; p float2( tex2D(_NoiseTex, p _Time.y * 0.1).r * 0.2, tex2D(_NoiseTex, p _Time.y * 0.15 10.0).r * 0.2 ); float n tex2D(_NoiseTex, p).r;这种写法等于把噪声采样次数翻倍我一般不建议在移动端角色和 UI 的主要材质里大面积使用。3.3 顶点着色器里的 tex2Dlod 细节顶点着色器里是不能直接用 tex2D 的必须用 tex2Dlod。常见的写法是float n tex2Dlod(_NoiseTex, float4(uv * _Tile, 0, 0)).r;注意最后一个通道传的是 0。这个 0 代表强制使用第 0 级 mipmap避免模型距离变化时不同顶点被采样到不同细节级别的纹理区域导致顶点之间数值断裂呈现随机闪光。这个坑我自己踩过后来排查了很久才意识到是 mipmap 层级不一致导致的。4. 四个实战玩法拆解原理说清楚了现在看实际操作。我把最常见的四种玩法拆开讲每种给出核心代码和对应的调参建议你可以直接照着抄抄完再按自己的效果微调。4.1 顶点抖动受击反馈与呼吸效果这类效果的核心是把噪声值转换成顶点沿法线方向的偏移量。模型表面会呈现自然的起伏收缩而不是整体平移的僵硬感。完整 Shader 如下Shader Custom/NoiseVertexPulse { Properties { _BaseColor (Base Color, Color) (1,1,1,1) _NoiseTex (Noise Texture, 2D) white {} _Tile (Tile, Float) 3.0 _Intensity (Intensity, Range(0, 0.5)) 0.1 _Speed (Speed, Range(0, 2)) 0.5 } SubShader { Tags { RenderTypeOpaque } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _NoiseTex; float _Tile; float _Intensity; float _Speed; fixed4 _BaseColor; v2f vert (appdata v) { v2f o; float2 noiseUv v.uv * _Tile; float3 offsetDir normalize(v.normal); float n tex2Dlod(_NoiseTex, float4(noiseUv, 0, 0)).r; v.vertex.xyz offsetDir * (n - 0.5) * _Intensity; o.vertex UnityObjectToClipPos(v.vertex); o.uv v.uv; return o; } fixed4 frag (v2f i) : SV_Target { return _BaseColor; } ENDCG } } }这段代码里值得解释的是 n - 0.5 这个操作。噪声纹理数值范围是 0 到 1如果不减 0.5所有顶点都会朝着法线方向向外顶整个模型会“膨胀”而不是“起伏”。减掉 0.5 之后数值落在 -0.5 到 0.5 之间一半顶点外扩一半内收起伏感就出来了。还要注意 _Intensity 的单位是模型本地空间长度所以这个值必须根据模型尺寸设置。如果一个角色模型高度是 2 米设为 0.05 可能只是轻微呼吸同一个值放在半径只有 0.1 的小道具上效果就会夸张到炸开。经验做法是先设为模型最大尺寸的 2% 到 5%再微调。如果想让效果有节奏感而不是持续波动可以在噪声结果上叠一个正弦波float pulse sin(_Time.y * _Speed) * 0.5 0.5; float n tex2Dlod(_NoiseTex, float4(noiseUv, 0, 0)).r; v.vertex.xyz offsetDir * (n - 0.5) * _Intensity * pulse;这样抖动会随正弦波忽大忽小像是受了攻击之后的那一瞬间鼓动做技能受击反馈非常好用。4.2 UV 扰动水波与玻璃扭曲UI 和特效里最常见的噪声用法是用噪声值去偏移另一张贴图的采样坐标。核心代码很短但用途极广fixed4 frag (v2f i) : SV_Target { // 1. 采样噪声得到扰动值 float2 noiseUv i.uv * _Tile _Time.y * _FlowSpeed; float n tex2D(_NoiseTex, noiseUv).r; n n - 0.5; // 2. 用噪声值扰动主贴图的采样坐标 float2 distortUv i.uv n * _Distort; fixed3 col tex2D(_MainTex, distortUv).rgb; return fixed4(col, 1); }这里要理解一个关键点为什么扰动要加在主贴图 uv 上而不是直接改颜色画面上看到的“扭曲感”本质是取色位置发生了偏移。同一张图片不同像素位置的内容被挪到了相邻位置视觉上才会出现波纹和水面折射的效果。直接改颜色只是改变了亮度色相不会产生位移感。这个玩法调参时要注意 _Distort 千万不能设太大。在常规 uv 尺度下偏移值超过 0.05 就会出现明显的花屏感超过 0.1 基本没法看。我一开始总想让扰动强一点结果每次调完就像玻璃碎裂后来才明白这种效果的视觉敏感度极高宁小勿大。Unity UI 里用这个 Shader 还有一个细节需要把 Shader 放到 UI 渲染队列并处理好 Stencil 缓冲否则 UI 的点击遮罩、圆角遮罩全都失效。如果是做模组里的贴图材质反而没有这个问题。4.3 阈值噪声溶解与手绘边缘第三种玩法是把噪声当“开关”来用而不是当数值来用。典型的场景是溶解效果物体按噪声图案从边缘开始消失而不是整整齐齐一刀切。float n tex2D(_NoiseTex, i.uv * _Tile).r; clip(n - _Cutoff);_Clip 从 0.0 往上推的过程中噪声值低于阈值的像素被裁剪掉物体就呈现自然破损消融的效果。这个写法的视觉质感完全取决于噪声纹理的选择。云状噪声适合柔和溶解点状白噪声适合颗粒飞散。如果想做更平滑的手绘风边缘可以把硬裁剪改成 smoothstep 做边缘软化float edge smoothstep(_Cutoff - _EdgeSoft, _Cutoff _EdgeSoft, n); clip(edge - 0.5);这样边缘不会出现像素级锯齿会有一种晕染开的笔触感。需要注意的是这种阈值裁剪在移动端会引入额外的 overdraw尤其用在半透明角色大面积表面时帧率掉得很快要留意场景里的使用面积。4.4 高度与分布噪声作为数值源最后别忘了噪声本质是 0 到 1 的数值集合它的用途远远不止贴图。把噪声值直接当作高度数据就能做地形轮廓当作概率分布就能往地面上撒草和石头撒得又自然又均匀。做这类需求时通常只需要一行代码float height tex2D(_NoiseTex, worldPos.xz * _Tile).r * _HeightScale;然后把这个 height 加到顶点 y 坐标上。注意采样 uv 用世界坐标时物体移动后噪声图案也会跟着固定像山体一样稳定不漂移。这一点和基于模型 uv 的采样很不一样做地形时优先考虑。撒点分布的逻辑也类似生成一堆候选位置用噪声值做筛选。噪声值大于阈值的位置保留小于阈值的位置丢掉。由于 Perlin 类噪声具有空间连续性保留下来的点会呈现成片分布的趋势而不是纯随机撒出来的均匀孤立点。视觉上自然很多。5. 踩坑实录与排查技巧把噪声从入门到能上手玩熟练我用了不少时间大部分时间都耗在排查各种“看起来不对劲”的 bug 上。这些坑单看文档很难提前预判我整理成几个高频问题方便你对号入座。5.1 噪声纹理整片发黑或者整片发灰这是新手最常见的问题没有之一。按这个顺序排查基本都能解决纹理面板 Wrap Mode 是不是 Repeat。设为 Clamp 时采样坐标一旦超出 0 到 1会自动钳制到边缘像素结果是画面里出现一大块均匀的灰或者黑。采样前有没有做 uv 缩放。直接用 0 到 1 的 uv 采样噪声特征会被完全抹平看起来就是一块均匀灰。顶点着色器里是否误用了 tex2D。顶点阶段要用 tex2Dlod编译错误有时候不会直接报出来而是渲染出奇怪结果。取通道是否正确。噪声纹理生成时常常把同一个灰度同时写入 RGB但如果你取的是 alpha 通道而 alpha 全部填了 1那画面就变纯白了。用 .r 通道最稳。5.2 动态噪声疯狂闪烁动态效果出现肉眼可见的闪烁原因通常有以下几类_Time.y 前面的乘数太大。采样坐标每帧变化过大时画面会跳变而不是流动。建议速度值控制在 2 以内结合自己的 Tile 值慢慢调。顶点着色器里采样噪声时 mipmap 层级不一致。解决办法是 tex2Dlod 强制指定第 0 层像 3.3 节写的那样。移动端浮点精度不足。如果项目挂的是 OpenGL ES 3.0 的老设备把 uv 相关的变量声明从 half 改成 float能解决很多莫名其妙的抖动。ASTC 纹理压缩带来的块状伪影。噪声这种高频内容压缩后很容易出现规则块状纹理尤其是采样坐标高速变化时。要求高的流体效果建议关掉压缩或者用更高精度的 R16 格式记录噪声。5.3 平铺重复感太强或者出现十字形接缝重复感强通常是 Tile 值太小。噪声纹理覆盖整体表面只出现一两次时图案的重复一眼就能被看出来。把 Tile 提到 4 以上重复感会明显下降如果你看到的是清晰的十字缝那基本就是纹理拼接边界没处理对。回到 1.2 节的生成代码重点检查边缘坐标是否做了数值统一四个 for 循环缺一个整张纹理就会裂开。5.4 计算噪声 vs 纹理噪声到底选哪种最后聊一个方向选择问题。很多人学会 hash 之后喜欢把所有噪声采样全部换成数学函数觉得这样省掉纹理带宽很划算。实际上要看场景需要高频细节的流体扰动、波纹扭曲一律用纹理噪声。纹理采样一次的性能开销远低于多层函数计算而且视觉效果更可控。需要世界坐标绝对对齐、多个物体联动时可以用世界坐标作为采样坐标但本质上还是纹理噪声。网格顶点数极少、对平滑连续性要求不高的效果比如随机摆放几个水晶柱、给灵魂粒子做无规律闪烁用 hash 数学方案确实更轻快。如果追求极致优化还有一个思路把噪声采样结果预计算到顶点色或静态顶点数据里。顶点数量有限的情况下运行期零采样开销几个平台的性能都过得去。写在最后的经验最初做项目时我对噪声的理解就是“一张黑白花图片”网上抄一段 Shader 试来试去效果好不好全凭运气。真正把“噪声 一组 0 到 1 的数值源”这个本质想明白之后做的效果才开始稳定可控。调整噪声效果时建议用低强度起步把 Tile 从 1 慢慢往上拖动观察画面变化。多数情况下单层纹理采样就够用不需要一上来就叠多层 FBM。希望这篇能帮你把噪声相关的弯路省掉。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询