1. 项目概述从基础纹理到高级纹理的跃迁如果你已经跟着《Unity Shader入门精要》一路学过来或者自己捣鼓过一些基础的漫反射、高光模型那么恭喜你你已经跨过了Shader编程最基础的门槛。但当你翻到第10章“高级纹理二”时可能会感觉画风突变。这里不再是简单地用一张图片去贴模型表面而是开始玩一些“花活”用一张纹理控制渲染流程用另一张纹理存储数学运算的结果甚至用纹理来“欺骗”眼睛实现远超模型本身细节的视觉效果。这正是Shader从“实现功能”到“创造艺术”的关键一步。简单来说这一章的核心是教你如何将纹理从“贴图”这个单一角色解放为Shader管线中的“多功能数据载体”。我们不再仅仅关心纹理的颜色RGB通道而是深度挖掘其每一个通道R, G, B, A所蕴含的潜力以及如何利用不同类型的纹理如法线贴图、遮罩纹理、渐变纹理来协同工作实现复杂且高效的渲染效果。无论是想让你的角色皮肤有次表面散射的透光感还是想让金属武器在不同部位呈现出磨损与高光的差异亦或是用一张低模配合法线贴图渲染出高模的细节都离不开对这些高级纹理技术的掌握。接下来我们就抛开书本上略显抽象的描述直接进入实战拆解每一个技术点背后的设计思路、实现细节以及那些只有踩过坑才知道的注意事项。2. 核心纹理类型深度解析与应用场景在基础阶段一张.jpg或.png可能就够了。但在高级渲染中我们需要根据数据用途精心选择和制作特定类型的纹理。理解它们的本质差异是正确使用的第一步。2.1 法线贴图细节的“魔术师”法线贴图恐怕是游戏开发中最著名的高级纹理了。它的原理不是改变模型颜色而是改变模型表面法线的方向。法线方向决定了光线如何反射从而在视觉上创造出凹凸不平的细节而模型的几何形状实际上并没有改变。核心原理与制作一张法线贴图本质上是一张RGB图像。它的每个像素的(R, G, B)值不再代表颜色而是代表一个三维向量即法线向量。通常我们使用切线空间来存储这个向量。在切线空间中R通道对应法线向量的X分量切线方向通常为右。G通道对应法线向量的Y分量副切线方向通常为上。B通道对应法线向量的Z分量法线方向通常为前。由于法线是单位向量其Z分量通常为正且较大所以法线贴图整体会呈现一种蓝紫色调。制作法线贴图通常有两种主流方式高模烘焙这是最专业、效果最好的方法。使用ZBrush、Mudbox等软件雕刻一个超高精度的模型高模然后将其细节信息主要是法线信息烘焙到一个低精度模型低模的UV展开图上。常用工具有xNormal、Marmoset Toolbag或Substance Painter的烘焙功能。图像转换通过Photoshop的滤镜或专用工具如CrazyBump、NVIDIA Texture Tools将一张普通灰度图高度图转换为法线贴图。这种方法适合处理已有纹理的细节如砖墙、布料褶皱等。在Shader中的关键实现 在Shader中采样法线贴图后需要将采样的RGB值从[0,1]映射回[-1,1]的范围并归一化。// 在片元着色器中 fixed4 packedNormal tex2D(_BumpMap, i.uv); fixed3 tangentNormal; // UnpackNormal 是UnityCG.cginc中内置的函数负责解码 tangentNormal UnpackNormal(packedNormal); tangentNormal.xy * _BumpScale; // _BumpScale用于控制凹凸强度 tangentNormal.z sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));之后需要将切线空间下的法线向量tangentNormal通过顶点着色器传递下来的切线空间变换矩阵通常由TANGENT_SPACE_ROTATION宏计算得到转换到世界空间再用于后续的光照计算。注意Unity导入法线贴图时在纹理导入设置中务必勾选“Bump Map”类型并选择正确的“Texture Type”如Normal map。Unity会根据平台自动对纹理进行压缩如DXT5nm、BC5这种压缩格式能更高效地存储法线数据。如果勾选了“Create from Grayscale”则是从高度图生成法线贴图适用于快速原型制作。2.2 遮罩纹理性能与效果的平衡大师遮罩纹理的精髓在于“控制”。它允许你用一张纹理的不同通道来精确控制同一材质上不同属性的强度或开关。这比使用多个独立的浮点参数要灵活和强大得多。典型应用场景高光遮罩一张纹理的R通道存储高光强度系数。例如一个角色皮肤材质在鼻子、额头等出油区域高光强度应该更强而在脸颊等其他部位则较弱。你可以手绘一张灰度图白色区域代表强高光黑色代表无高光。在计算高光时将最终的高光强度乘以这个遮罩值。fixed specularMask tex2D(_SpecMask, i.uv).r; fixed3 specular _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(normal, halfDir)), _Gloss) * specularMask;细节纹理混合使用遮罩的G通道来控制第二张细节纹理如污渍、划痕与基础纹理的混合程度。这比简单的lerp混合更加区域化。自发光区域控制使用B通道来定义哪些区域会发光比如机甲的能量槽、魔法符文。纹理集Texture Atlas边界软化在将多个小纹理合并到一张大图纹理集时接缝处容易出现硬边。可以在Alpha通道存储一个边缘羽化的遮罩在采样时进行混合使过渡自然。实操心得通道规划在制作遮罩纹理时例如在Substance Painter或Photoshop中要有清晰的通道规划文档。通常R、G、B、A四个通道可以分别对应高光强度、粗糙度、自发光、透明度等。保持团队内约定一致至关重要。精度选择遮罩纹理对颜色的绝对精度要求不高但对灰阶过渡的平滑性有要求。通常使用RGB24或带压缩的格式即可无需RGBA32这样的高精度格式以节省内存和带宽。与Shader Graph结合在Shader Graph中使用“Sample Texture 2D”节点采样遮罩纹理后用“Split”节点将其RGBA四个通道拆分开然后分别连接到不同属性的输入上逻辑非常清晰直观。2.3 渐变纹理非真实渲染与风格化的利器渐变纹理的核心思想是“查表”。它打破了对光照模型数学计算的依赖通过一个一维或二维的纹理直接建立输入值如法线-光夹角、视角夹角到输出颜色或灰度的映射关系。这为实现卡通渲染、风格化着色提供了极大的自由度。一维渐变纹理Ramp Texture 这是最常用的形式通常是一张细长的水平纹理。其UV坐标的U轴横轴对应某个计算出的标量值如兰伯特漫反射项dot(normal, lightDir)V轴可以固定为0.5或用来做其他变化。// 计算兰伯特项 fixed diff saturate(dot(worldNormal, worldLightDir)); // 将diff从[0,1]映射为纹理UV的横坐标 fixed2 rampUV fixed2(diff, 0.5); fixed3 rampColor tex2D(_RampTex, rampUV).rgb; // 用渐变颜色代替传统的漫反射计算 fixed3 diffuse _LightColor0.rgb * rampColor;通过精心设计渐变纹理的颜色条你可以轻松实现硬边卡通色块、多级阴影、冷暖色调阴影等效果。二维渐变纹理 其UV的两个轴可以分别对应两个不同的参数。例如U轴对应N·LV轴对应N·V法线与视角方向的点积。这样可以创建一个二维查找表实现更复杂的、随视角变化而变化的风格化效果比如某些日式动漫中角色头发的高光随视角移动的效果。注意事项纹理过滤模式渐变纹理通常需要Clamp包裹模式防止在边界diff为0或1时采样到纹理外。过滤模式使用Bilinear即可Trilinear或Anisotropic通常没有必要。纹理尺寸一维渐变纹理不需要很高的高度但宽度决定了颜色过渡的精度。通常256x16或512x32的尺寸就足够了。过大的尺寸是一种浪费。动态修改有时我们需要在运行时根据游戏状态如角色血量、能量动态改变渐变纹理。这可以通过脚本修改纹理的像素数据或者准备多张渐变纹理进行切换来实现。动态修改时需注意性能开销。3. 多纹理混合策略与性能优化掌握了单种纹理的用法后真正的挑战在于如何让它们协同工作并且高效地工作。无节制的纹理采样和复杂混合是GPU性能的主要杀手之一。3.1 混合策略从线性插值到高度图混合线性插值Lerp最基本的混合方式使用一个权重值在纹理A和纹理B之间过渡。这个权重可以来自顶点颜色、另一张遮罩纹理或者基于世界坐标、高度的计算。fixed3 texColorA tex2D(_MainTex, uv1); fixed3 texColorB tex2D(_DetailTex, uv2); fixed blendFactor tex2D(_BlendMask, uv).r; // 从遮罩纹理获取混合因子 fixed3 finalColor lerp(texColorA, texColorB, blendFactor);高度图混合常用于地形渲染实现更自然的纹理过渡。原理是同时使用两张纹理及其对应的高度图灰度图越白表示越高。在像素处比较两张高度图的值让更高的纹理更多地显示出来。同时可以在过渡区域引入一些噪声来打破僵硬的边界。细节纹理叠加细节纹理Detail Map通常是一张高频率、可平铺的小纹理如布纹、皮革细纹。它不以覆盖基础纹理为目的而是通过叠加通常是Multiply或Overlay混合模式的方式为表面增加微观细节。为了不随模型拉伸而变形细节纹理通常使用独立的、基于模型世界空间或切线空间的UV并设置较高的平铺率。3.2 性能优化实战指南在移动平台或需要绘制大量物体的场景中Shader的优化至关重要。纹理压缩与格式选择法线贴图使用平台特定的压缩格式。在AndroidASTC或iOSPVRTC上选择对应的法线贴图压缩格式。在PC上DXT5nm是标准。遮罩/渐变纹理如果只有单通道信息如灰度遮罩可以考虑使用Alpha8格式。如果RGBA四个通道都用则根据精度需求选择RGBA32无压缩高质量或DXT5/BC3有压缩有质量损失。基础颜色纹理根据是否有Alpha通道选择DXT1无Alpha或DXT5有Alpha。对于移动端ASTC是当前平衡质量和性能的最佳选择可以根据纹理内容选择不同的块尺寸如4x4, 6x6, 8x8。减少纹理采样次数纹理集Texture Atlas将多个小纹理合并到一张大纹理中。这可以将多次采样合并为一次采样并通过共享边缘像素减少纹理过滤时的缓存缺失。Unity的Sprite Atlas和很多地形系统都采用此原理。通道打包将多张单通道的灰度图如粗糙度图、金属度图、AO图打包到一张纹理的R、G、B通道中。这样原本需要三次采样才能获得的数据现在一次采样就能全部获取。这是PBR物理渲染工作流中的标准做法例如金属度存储在R通道粗糙度存储在G通道AO存储在B通道。fixed4 metallicRoughAO tex2D(_MetallicGlossMap, i.uv); float metallic metallicRoughAO.r; float roughness metallicRoughAO.g; float ao metallicRoughAO.b;Mipmap与各向异性过滤Mipmap务必为所有用于3D渲染的纹理生成Mipmap。这能显著减少远处物体因纹理采样不足而产生的锯齿和闪烁摩尔纹。在纹理导入设置中勾选“Generate Mip Maps”。各向异性过滤对于地面、墙面等可能被极度锐角视角观察的表面开启各向异性过滤在Quality Settings中设置可以改善远处纹理的清晰度。但这会带来一定的性能开销需根据目标平台权衡。Shader LOD与变体剔除Shader LOD为你的Shader编写不同复杂度的版本并设置LOD值。当摄像机距离物体超过一定距离时Unity会自动切换到更简单的Shader变体从而节省计算资源。变体管理使用#pragma multi_compile或shader_feature生成的关键词变体如果过多会导致Shader编译时间剧增和包体膨胀。定期使用Unity的Shader Variant Collection工具收集并剔除项目中未使用的变体。4. 实战构建一个多纹理PBR材质让我们综合运用以上知识构建一个支持遮罩控制的PBR材质Shader。这个材质将包含基础颜色纹理法线纹理一张打包了金属度R、粗糙度G、环境光遮蔽B的纹理一张单独的高光遮罩纹理R通道用于在某些区域增强或减弱高光。Shader属性定义Properties { _Color (Color Tint, Color) (1,1,1,1) _MainTex (Albedo (RGB), 2D) white {} _BumpMap (Normal Map, 2D) bump {} _BumpScale (Bump Scale, Float) 1.0 _MRAMap (Metal(R) Rough(G) AO(B), 2D) white {} // 打包纹理 _SpecMask (Specular Mask (R), 2D) white {} _SpecIntensity (Specular Intensity, Range(0, 2)) 1.0 _Gloss (Glossiness, Range(8, 256)) 20 }片元着色器核心逻辑fixed4 frag(v2f i) : SV_Target { // 1. 采样基础纹理 fixed4 albedo tex2D(_MainTex, i.uv) * _Color; // 2. 采样并解包法线纹理 fixed4 packedNormal tex2D(_BumpMap, i.uv); fixed3 tangentNormal UnpackNormal(packedNormal); tangentNormal.xy * _BumpScale; tangentNormal.z sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); // 转换到世界空间 float3 worldNormal normalize(mul(tangentNormal, i.TtoW)); // 3. 采样打包纹理解包各属性 fixed4 mra tex2D(_MRAMap, i.uv); float metallic mra.r; float roughness mra.g; // 注意通常存储的是粗糙度但某些工作流存储的是光滑度 float ao mra.b; // 4. 采样高光遮罩 float specMask tex2D(_SpecMask, i.uv).r; // 5. 计算光照简化版实际PBR应使用BRDF方程 float3 lightDir normalize(_WorldSpaceLightPos0.xyz); float3 viewDir normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); float3 halfDir normalize(lightDir viewDir); // 漫反射 (Lambert) float NdotL saturate(dot(worldNormal, lightDir)); float3 diffuse _LightColor0.rgb * albedo.rgb * NdotL; // 高光 (Blinn-Phong此处仅为示例真实PBR更复杂) float NdotH saturate(dot(worldNormal, halfDir)); float specularTerm pow(NdotH, _Gloss); // 应用金属度金属材质没有漫反射高光颜色接近本色非金属则有漫反射高光为白色。 float3 specularColor lerp(fixed3(0.04, 0.04, 0.04), albedo.rgb, metallic); float3 specular _LightColor0.rgb * specularColor * specularTerm * _SpecIntensity * specMask; // 6. 组合最终颜色并应用AO float3 finalColor (diffuse * (1 - metallic) specular) * ao; return fixed4(finalColor, albedo.a); }这个示例将金属度、粗糙度、AO的采样从三次合并为一次并通过遮罩纹理灵活控制了高光表现。在实际的PBR管线中漫反射和高光计算应使用更复杂的BRDF模型如Cook-Torrance但多纹理混合与控制的框架是一致的。5. 常见问题排查与进阶技巧即使理解了原理在实际操作中仍会遇到各种问题。下面是一些典型问题的排查思路和进阶技巧。5.1 法线贴图“看起来不对”问题模型表面凹凸方向错误或者光照下出现奇怪的暗斑。排查切线空间一致性确保模型导出时和Unity导入时的切线空间计算一致。在3D软件中导出时通常选择“切线空间”法线贴图。在Unity中检查模型导入设置里的“Tangents”选项对于人形角色或标准网格通常使用“Calculate”即可。纹理导入设置确认法线贴图的“Texture Type”设置为“Normal map”这样Unity才会正确解码。如果手动设置为“Default”则需要自己在Shader中使用正确的解码函数对于DXT5nm格式需使用UnpackNormal。双面材质如果模型是双面渲染的如树叶背面采样的法线方向会出错。需要在Shader中根据VFACE语义判断正面还是背面并对法线进行相应处理。缩放系数BumpScale_BumpScale值过大如超过2可能导致法线Z分量计算出现负数产生错误。通常保持在[0, 2]范围内。5.2 遮罩纹理混合边缘生硬问题使用遮罩纹理混合两种材质时边界处像剪刀裁剪一样不自然。解决模糊遮罩在Photoshop或Substance Painter中制作遮罩时对边缘进行适当的羽化或模糊。一个像素的渐变过渡就能在屏幕上带来平滑的混合效果。在Shader中平滑采样遮罩后可以使用smoothstep函数对混合因子进行重映射以创建一个更平滑的过渡。float rawMask tex2D(_Mask, uv).r; float smoothMask smoothstep(_Threshold - _Feather, _Threshold _Feather, rawMask);其中_Threshold是阈值_Feather是羽化范围。使用高度图混合如前所述采用基于高度的混合算法能根据两张纹理的高度信息自动计算出更自然的过渡区域。5.3 渐变纹理出现色带问题使用渐变纹理渲染卡通角色时阴影部分不是平滑过渡而是出现一圈一圈的色带。原因这通常是由于渐变纹理本身颜色阶数过少或者纹理过滤Filtering设置不当在颜色跳跃处进行线性插值导致的。解决增加渐变纹理颜色阶数在制作渐变纹理时确保在颜色过渡区域有足够多的颜色采样点避免从一种颜色直接跳到另一种颜色。检查纹理过滤模式确保渐变纹理的过滤模式不是“Point (no filter)”。应使用“Bilinear”或“Trilinear”让GPU在像素之间进行插值平滑色阶。在Shader中抖动Dithering一种高级技巧是在计算UV时加入微小的、基于屏幕位置的噪声蓝噪声效果最好可以打散色带在视觉上形成平滑过渡。这对于移动端减少纹理尺寸尤其有用。5.4 性能热点定位与优化当你发现游戏帧率下降怀疑是Shader复杂度过高时使用Unity Frame Debugger逐帧查看每个Draw Call的耗时定位是哪个材质或Shader造成了瓶颈。使用Unity Profiler的GPU模块查看每个渲染阶段的GPU耗时确认瓶颈是在顶点处理、片元着色还是纹理采样。简化Shader复杂度如果片元着色器是瓶颈检查是否可以减少纹理采样次数通过打包、图集。检查复杂的数学运算如pow,sin,cos是否可以被简化或查找表LUT替代。考虑是否可以使用更简单的光照模型如去掉高光计算。合批Batching失败确保使用相同材质和纹理的物体满足静态合批或动态合批的条件。纹理不同是导致合批失败的最常见原因之一。此时纹理图集是解决方案。掌握高级纹理意味着你掌握了为游戏世界注入丰富细节和独特风格的钥匙。它要求你不仅是程序员还要对美术资源制作流程、性能优化有深入的理解。从理解每种纹理的数据本质开始到设计高效的混合策略再到最后针对目标平台进行精准优化每一步都充满了权衡与创造。我个人的体会是最好的学习方式就是动手为一个简单的模型尝试添加法线贴图用遮罩纹理控制一个特效的显现用渐变纹理实现一个卡通Shader。在不断的试错和迭代中这些概念才会从书本上的知识真正变成你Shader工具箱里得心应手的武器。