尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

URP渲染管线中法线贴图的正确使用与实现详解

URP渲染管线中法线贴图的正确使用与实现详解 1. 项目概述为什么URP下的法线贴图值得单独拎出来讲如果你是从Unity内置渲染管线或者标准渲染管线Built-in/Standard转过来的开发者第一次在URPUniversal Render Pipeline里用法线贴图大概率会踩坑。最常见的情况就是你从资源商店下载了一个精美的模型拖进场景材质球上法线贴图也贴好了但模型表面看起来就是平的或者光照反射的方向完全不对感觉像是法线贴图“失效”了。这不是你的错觉也不是Bug而是URP为了追求更高的跨平台性能和更简洁的着色器架构在底层做了一些“约定”需要我们开发者去主动适配。法线贴图是提升模型细节、用低模模拟高模表面凹凸质感的核心技术。在URP中它的实现逻辑和标准管线有显著区别。标准管线里你可能只需要在材质上勾选“Bump Mapping”或者拖入一张法线贴图就完事了背后的切线空间转换、UnpackNormal等操作都被封装好了。但在URP里这套“黑盒”被打开了。URP的Lit Shader Graph或者手写Shader要求你显式地处理法线贴图的采样、解码以及从切线空间到世界空间的转换。这既是挑战也是机会——你获得了对渲染流程更精细的控制权但同时也必须理解每一步在做什么。这篇教程就是来解决这个“断层”的。我会从一张法线贴图资源开始带你完整走一遍在URP中让它正确生效的流程。无论你是使用URP自带的Lit材质球配合Shader Graph进行可视化连线还是直接编写URP兼容的HLSL代码核心原理都是一致的。我们会深入纹理导入设置、着色器中的采样解码、空间变换矩阵的构建这些细节并解释每一步背后的数学和性能考量。目标是让你不仅能在项目里用上法线贴图更能明白为什么这么做以及当效果不对时该从哪个环节去排查。2. 核心原理与管线差异解析2.1 法线贴图是什么从高度到方向首先我们得统一认知法线贴图Normal Map存储的不是颜色或高度而是法线向量。模型表面每个像素点都有一个垂直于该点切平面的向量称为法线。光照计算如漫反射、镜面反射的核心就是光线方向与法线方向的夹角。默认情况下一个低多边形模型顶点的法线是平滑插值的表面看起来光滑但缺乏细节。法线贴图的作用就是用一个RGB纹理来“篡改”这个插值后的法线方向。通常R通道对应法线向量的X分量切线方向G通道对应Y分量副切线/副法线方向B通道对应Z分量法线方向。由于法线是单位向量长度为1且Z分量通常为正所以一张未经压缩的法线贴图看起来是偏蓝紫色的因为0.5, 0.5, 1.0转换到RGB颜色空间就是淡蓝色。这里有个关键点纹理颜色值范围是[0, 1]而向量的分量范围是[-1, 1]。所以在存储时需要进行一次映射vector (color * 2) - 1。反过来从纹理中读取法线数据时就需要进行解码normal (texel * 2) - 1。这个解码操作就是URP中需要我们手动完成的第一步。2.2 URP与Built-in管线的核心差异在旧版Built-in管线中当你为Standard Shader指定一张法线贴图时Unity编辑器会自动帮你做很多事情纹理类型标记它会建议你将纹理类型设置为“Normal map”。平台压缩根据目标平台如PC用DXT5nm移动端用ASTC自动应用最适合法线向量的压缩格式。着色器黑盒在Shader内部一个简单的UnpackNormal(tex2D(_BumpMap, uv))调用就完成了采样、解码、以及可选的基于高度的细节混合等一系列操作。URP的设计哲学是“轻量”与“明确”。它移除了许多自动化的、但可能带来性能开销或不够灵活的封装分离的职责URP不假设你一定会用法线贴图。因此你需要自己在Shader中声明纹理属性、采样器并显式调用采样函数。明确的空间转换URP的很多光照函数如LightingPhysicallyBased期望输入的是世界空间法线。而法线贴图通常存储在切线空间。因此你必须自己构建从切线空间到世界空间的变换矩阵TBN矩阵并用它来转换法线。解码函数URP提供了UnpackNormal或UnpackNormalRGB等函数但它们的使用方式更“原始”你需要根据纹理的压缩格式来正确调用。这种差异带来的直接好处是性能可控和灵活性。你可以为不同的材质选择不同的法线压缩格式或者实现自定义的解包逻辑。但代价就是入门门槛提高了需要理解整个数据流。注意URP中有一个常见的误解是“法线贴图不工作”。其实90%的情况问题都出在以下两点一是纹理导入设置不对导致Shader读到了错误的数据二是忘记进行切线空间到世界空间的转换直接把解码后的切线空间法线丢给了光照计算。2.3 法线贴图的压缩格式与选择纹理导入设置是第一个拦路虎。在Project窗口选中你的法线贴图在Inspector面板中Texture Type必须设置为Normal map。这个设置至关重要它不仅仅是一个标签。当选择此类型时Unity会将纹理的sRGB颜色空间选项关闭。因为法线数据是线性数据不是颜色信息不需要进行伽马校正。提供一个Create from Grayscale的选项可以从高度图灰度图生成法线贴图。最重要的是它会根据你选择的平台应用特定的、针对法线向量优化的压缩格式。平台覆盖设置PC, Mac Linux Standalone默认并推荐使用BC5 (DX11)格式在Unity中显示为“BC5”或“DXT5nm”的变体。BC5格式专门用于存储两个通道的数据这里是X和Y能非常高精度地保存法线的水平方向Z分量通过计算z sqrt(1 - x² - y²)推导得出。这是桌面平台质量和性能的最佳平衡。Android/iOS推荐使用ASTC 4x4或ASTC 6x6块压缩。ASTC格式在移动端上对法线贴图的压缩质量很好。如果设备不支持ASTC回退到ETC2格式需要OpenGL ES 3.0或以上。切勿在移动端使用未经压缩的RGB格式这会极大增加内存带宽和功耗。WebGL通常使用DXT5对于支持DXT的浏览器或ETC2作为通用格式。如果你在这里设置错误比如将Texture Type留为Default那么Shader中即使解码代码正确读进来的向量值也是错的因为压缩算法可能破坏了向量数据的连续性。3. 在Shader Graph中实现法线贴图对于大多数不直接写Shader的开发者Shader Graph是URP中制作材质的主要工具。它的可视化连线方式让法线贴图的实现过程变得非常清晰。3.1 创建Lit Shader Graph与基础设置首先在Project窗口中右键 - Create - Shader Graph - URP - Lit Shader Graph。我将它命名为“URP_NormalMap_Example”。双击打开Shader Graph编辑器。默认的Master Stack主堆栈已经包含了URP Lit所需的绝大部分输入。我们重点关注两个节点Fragment Stage片段阶段中的Normal输入和Base Color输入。添加法线贴图属性在Blackboard黑板区域点击“”号添加一个属性。将属性类型设为Texture2D名称改为_NormalMap。在它的节点设置里将Reference设置为_NormalMap这是Shader中对应的变量名。这一步相当于在Shader代码中声明了sampler2D _NormalMap;。采样纹理从Blackboard中将_NormalMap属性拖到工作区会自动生成一个Sample Texture 2D节点与之连接。这个节点的UV输入我们通常连接一个UV节点选择模型的默认纹理坐标集通常是UV0。为了支持平铺和偏移更常见的做法是使用Tiling And Offset节点其UV输入连接UV节点其Tiling和Offset输入则连接两个新的Vector2属性_NormalMap_ST.xy和_NormalMap_ST.zw这样可以独立控制法线贴图的缩放和位移。3.2 解码与空间转换核心连线逻辑这是最关键的一步。从Sample Texture 2D节点输出的RGBA颜色需要经过解码和空间变换才能提供给Master Stack的Normal输入口。解码法线数据在Shader Graph中搜索Normal From Texture节点。这是一个功能强大的集成节点。将Sample Texture 2D节点的输出RGBA连接到它的Texture输入口。Space这里必须选择Tangent。这告诉节点输入的纹理数据是切线空间下的法线。默认设置保持Strength为1如果希望法线效果更强或更弱可以调整此值原理是扰动XY分量再重新归一化。这个节点内部帮我们完成了UnpackNormal和解码操作。它的输出就是一个切线空间下的法线向量Vector3。切线空间到世界空间的转换Master Stack的Normal输入口期望的是世界空间法线。因此我们需要将切线空间法线转换过去。搜索Transform节点。将它的In输入连接到Normal From Texture节点的输出。将Transform节点的From设置为Tangent SpaceTo设置为World Space。这个节点需要额外的信息来构建TBN矩阵。它自动从顶点阶段获取了必要的Tangent、Bitangent或叫副切线和Normal向量。这正是Shader Graph方便的地方它隐藏了构建矩阵的复杂代码。Transform节点的输出就是最终我们需要的世界空间法线。连接至Master Stack将Transform节点的输出直接连接到Master Stack上Fragment阶段的Normal输入口。至此一个最基本的、支持法线贴图的Lit Shader Graph就完成了。你可以将生成的Shader赋给一个材质球然后将法线贴图拖入材质球的_NormalMap属性槽中。在场景中你应该能看到模型表面根据法线贴图产生了正确的凹凸光照反应。3.3 常见问题与Shader Graph调试如果效果不对比如模型全黑、全亮或凹凸方向相反可以按以下步骤排查问题一模型看起来一片平坦像没加法线贴图。检查纹理导入设置这是最常见的原因。务必确认法线贴图的Texture Type是“Normal map”。可以临时将压缩格式Override为“None”看是否恢复正常以排除压缩问题。检查UV连接确保Sample Texture 2D节点的UV输入是正确的。可以连接一个Time节点做个简单的滚动动画看纹理是否在动以测试UV通路。检查Normal From Texture节点确认Space选项是“Tangent”而不是“Object”或“World”。问题二凹凸效果存在但光照方向完全错误该凹的地方凸起。检查法线贴图绿色(G)通道切线空间法线贴图的G通道控制着“上/下”方向。有时由于制作软件如3ds Max, Maya的坐标系Y-Up还是Z-Up与UnityY-Up不同会导致G通道需要反转。在Shader Graph中你可以在Normal From Texture节点后接一个Multiply节点乘以Vector3(1, -1, 1)来反转Y分量。更一劳永逸的方法是在纹理导入设置中勾选Bump Map区域下的Flip Green Channel翻转绿色通道。问题三模型接缝处出现难看的深色或浅色条纹。这是切线空间法线贴图的固有缺陷。当模型的切线在接缝处不连续时这是硬边的常见情况转换到世界空间的法线也会不连续导致光照断裂。解决方案对于硬边很多的模型如机械、建筑考虑使用对象空间法线贴图。但这种贴图不能旋转模型通用性差。在Shader Graph中如果使用对象空间法线贴图Normal From Texture节点的Space应选“Object”并且不需要经过Transform节点可以直接连接到Master Stack的Normal口因为Master Stack的Normal口默认期望世界空间而对象空间需要再乘上模型矩阵这里情况较特殊通常需要自定义处理。更实用的方案是在建模时尽量优化UV布局减少不必要的硬边或者接受一定程度的接缝瑕疵。Shader Graph提供了强大的预览功能。你可以右键点击任何节点选择“Create Preview Node”将该节点的输出可视化到一个球体或平面上这对于调试法线向量是否正确非常有用。4. 手写URP HLSL Shader实现对于需要极致性能控制或实现特殊效果如视差映射、曲面细分置换的情况手写Shader是必须的。我们来拆解一个支持法线贴图的基础URP Surface Shader结构。4.1 Shader结构与属性定义首先创建一个新的Unlit Shader文件然后修改为URP Lit的基本框架。核心在于包含正确的HLSL文件和处理TBN矩阵。Shader Custom/URP NormalMap Lit { Properties { _BaseMap (Base Texture, 2D) white {} _BaseColor (Base Color, Color) (1, 1, 1, 1) // 法线贴图属性 _BumpMap (Normal Map, 2D) bump {} // “bump”是内置的淡蓝色纹理 _BumpScale (Normal Scale, Float) 1.0 // 控制法线强度 _Smoothness (Smoothness, Range(0, 1)) 0.5 _Metallic (Metallic, Range(0, 1)) 0.0 } SubShader { Tags { RenderTypeOpaque RenderPipelineUniversalPipeline } Pass { Name ForwardLit Tags { LightModeUniversalForward } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile _ _MAIN_LIGHT_SHADOWS #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS #pragma multi_compile_fragment _ _SHADOWS_SOFT // 包含URP核心库 #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl // 定义与Properties块对应的变量 TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); TEXTURE2D(_BumpMap); SAMPLER(sampler_BumpMap); CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; // 包含纹理的平铺和偏移xy是Tiling, zw是Offset half4 _BaseColor; half _BumpScale; half _Smoothness; half _Metallic; CBUFFER_END // 顶点输入结构体必须包含切线信息 struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; // 切线.w分量有时会用来存储副切线方向 float2 texcoord : TEXCOORD0; }; // 顶点输出/片段输入结构体 struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1; // 为了构建TBN矩阵我们需要将法线、切线从物体空间变换到世界空间 half3 normalWS : TEXCOORD2; half3 tangentWS : TEXCOORD3; half3 bitangentWS : TEXCOORD4; };这里的关键是Attributes中包含了TANGENT语义以及Varyings中我们声明了tangentWS和bitangentWS。副切线Bitangent不需要从顶点输入它可以通过法线和切线的叉积计算得到bitangent cross(normal, tangent) * tangent.w。这里的tangent.w通常为1或-1用于处理手性镜像UV。4.2 顶点着色器构建TBN矩阵的基础顶点着色器的任务是将顶点位置变换到裁剪空间并计算世界空间下的法线、切线和副切线。Varyings vert(Attributes input) { Varyings output; // 顶点位置变换 VertexPositionInputs vertexInput GetVertexPositionInputs(input.positionOS.xyz); output.positionCS vertexInput.positionCS; output.positionWS vertexInput.positionWS; // 纹理坐标变换应用平铺和偏移 output.uv TRANSFORM_TEX(input.texcoord, _BaseMap); // 法线和切线从物体空间变换到世界空间 VertexNormalInputs normalInput GetVertexNormalInputs(input.normalOS, input.tangentOS); output.normalWS normalInput.normalWS; output.tangentWS normalInput.tangentWS; output.bitangentWS normalInput.bitangentWS; return output; }GetVertexNormalInputs是URP ShaderLibrary提供的一个非常方便的函数。它接收物体空间法线和切线带.w分量并返回世界空间下的法线、切线和副切线。这省去了我们手动乘以模型矩阵、处理旋转和缩放的麻烦。4.3 片段着色器采样、解码与光照计算片段着色器是核心在这里我们采样法线贴图构建TBN矩阵转换法线然后进行PBR光照计算。half4 frag(Varyings input) : SV_Target { // 1. 采样基础颜色 half4 baseColor SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; // 2. 采样并解码法线贴图 half4 packedNormal SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, input.uv); // 使用URP内置函数解码法线。它会根据纹理导入设置如DXT5nm进行正确的解码。 half3 tangentSpaceNormal UnpackNormalScale(packedNormal, _BumpScale); // UnpackNormalScale 已经处理了_BumpScale通过缩放xy重算z。 // 3. 构建切线空间到世界空间的旋转矩阵 (TBN矩阵) // 确保向量是正交且单位化的虽然GetVertexNormalInputs通常已经处理了但再归一化一次更安全 half3 normalWS normalize(input.normalWS); half3 tangentWS normalize(input.tangentWS); half3 bitangentWS normalize(input.bitangentWS); // 构建3x3矩阵。这里将TBN向量的xyz作为矩阵的行或列取决于乘法顺序关键是保持一致。 // 常见的乘法顺序是worldNormal mul(tangentSpaceNormal, float3x3(tangentWS, bitangentWS, normalWS)); // 但更清晰的方式是显式写出 float3x3 TBN float3x3(tangentWS, bitangentWS, normalWS); TBN transpose(TBN); // 转置矩阵因为我们构建时是行向量但mul函数默认是列优先。 // 4. 将切线空间法线转换到世界空间 half3 worldNormal normalize(mul(tangentSpaceNormal, TBN)); // 5. 准备表面数据用于URP光照函数 InputData inputData; ZERO_INITIALIZE(InputData, inputData); // 清空结构体 inputData.positionWS input.positionWS; inputData.normalWS worldNormal; // 使用我们计算出的世界空间法线 inputData.viewDirectionWS GetWorldSpaceNormalizeViewDir(input.positionWS); inputData.shadowCoord TransformWorldToShadowCoord(input.positionWS); // 阴影坐标 SurfaceData surfaceData; ZERO_INITIALIZE(SurfaceData, surfaceData); surfaceData.albedo baseColor.rgb; surfaceData.metallic _Metallic; surfaceData.smoothness _Smoothness; surfaceData.alpha baseColor.a; surfaceData.normalTS tangentSpaceNormal; // 一些高级效果可能需要这个 // 6. 调用URP主光照函数 half4 color UniversalFragmentPBR(inputData, surfaceData); return color; } ENDHLSL } } FallBack Universal Render Pipeline/Lit }这段代码的要点UnpackNormalScale这是URP中解码法线贴图的推荐函数。它自动处理了不同平台压缩格式如DXT5nm的解码并且通过_BumpScale参数可以方便地调整法线强度。TBN矩阵的构建与乘法顺序这是最容易出错的地方。切线空间法线(x, y, z)分别对应切线、副切线、法线方向。因此世界空间法线 x * tangentWS y * bitangentWS z * normalWS。用矩阵乘法表示就是worldNormal mul(tangentSpaceNormal, float3x3(tangentWS, bitangentWS, normalWS))。注意这里tangentSpaceNormal是一个行向量与由TBN基向量作为行组成的矩阵相乘。在HLSL中mul(行向量, 矩阵)正是这个含义。如果构建矩阵时把基向量作为列则需要转置矩阵或改变乘法顺序。光照函数UniversalFragmentPBR是URP内置的基于物理的渲染函数。我们只需要正确准备好InputData包含世界位置、世界法线、视线方向和SurfaceData包含反照率、金属度、粗糙度等它就会为我们处理所有直接光、间接光、阴影的计算非常方便。4.4 手写Shader的调试技巧手写Shader出错时调试不如Shader Graph直观。以下是一些实用技巧使用颜色输出调试在return color;之前可以临时用return half4(worldNormal * 0.5 0.5, 1.0);来可视化世界法线。正常的法线应该是一个彩色的、平滑变化的球体。如果出现大片纯色如纯蓝说明法线没有正确变化可能是TBN矩阵构建错误或法线贴图未正确采样。逐步简化如果效果不对先注释掉法线贴图部分直接使用input.normalWS作为光照法线看基础光照是否正确。然后逐步加入法线采样、解码、转换每一步都输出中间结果检查。检查变量名和语义确保Attributes和Varyings结构体中的语义: POSITION, : NORMAL, : TANGENT与模型导入时提供的数据匹配。模型必须有切线信息。注意空间一致性确保所有向量在参与计算前都在同一个空间通常是世界空间。GetVertexNormalInputs返回的是世界空间向量UnpackNormalScale返回的是切线空间向量这是正确的。5. 性能优化与高级应用当法线贴图正确工作后我们可以从性能和效果层面进行更深层次的优化和扩展。5.1 性能优化要点纹理压缩与Mipmaps如前所述为法线贴图选择正确的压缩格式是节省内存带宽的关键。同时确保开启Mipmaps。对于小型或中距离物体低Mip级别的法线贴图细节损失对视觉影响远小于颜色贴图但能显著提升缓存命中率。精度选择在移动端或VR项目中需要考虑精度。half类型16位浮点数对于法线向量和颜色计算通常足够并且比float更快。在片段着色器中将worldNormal、tangentSpaceNormal等变量声明为half3。分支优化避免在片段着色器中使用动态分支if语句来处理是否使用法线贴图。更好的做法是使用Shader变体。在Properties中定义一个_NORMAL_MAP开关然后在CGPROGRAM/HLSLPROGRAM中使用#pragma shader_feature _NORMAL_MAP。这样Unity会为启用和未启用法线贴图的情况分别编译两个版本的Shader运行时根据材质设置调用对应的版本没有分支开销。避免不必要的计算如果模型是静态的不旋转缩放且光照环境简单可以考虑在预处理时将法线贴图转换到世界空间或对象空间省去运行时每像素的矩阵乘法。但这会牺牲灵活性。5.2 与细节贴图Detail Normal Map混合为了在近距离观察时增加表面细节通常会使用第二张细节法线贴图。混合两张法线贴图需要特别小心因为直接线性混合两个法线向量会破坏其单位长度。正确做法是分别解码两张切线空间法线贴图normalA,normalB。将normalB的xy分量细节叠加到normalA的xy分量上。通常细节法线的强度_DetailBumpScale很小如0.1-0.3。half3 blendedNormal; blendedNormal.xy normalA.xy * _BumpScale normalB.xy * _DetailBumpScale; blendedNormal.z sqrt(1 - saturate(dot(blendedNormal.xy, blendedNormal.xy))); // 重新计算Z分量以保证单位长度使用混合后的blendedNormal进行后续的TBN变换。在Shader Graph中可以使用Blend Normals节点来完成这个操作它内部实现了上述的重归一化逻辑。5.3 视差映射Parallax Mapping与法线贴图结合视差映射通过高度图在采样时偏移UV坐标模拟简单的深度感与法线贴图结合能极大增强立体感。实现要点高度图采样需要一张灰度图作为高度图Height Map。UV偏移计算在片段着色器中根据视线方向在切线空间的分量viewDirTS和高度值计算UV偏移量。基础版本是视差偏移映射Parallax Offset Mappingfloat height SAMPLE_TEXTURE2D(_HeightMap, sampler_HeightMap, input.uv).r; float2 parallaxOffset viewDirTS.xy * (height * _ParallaxStrength) / viewDirTS.z; float2 parallaxUV input.uv - parallaxOffset; // 注意是减号因为高度凸起的地方应该看到更“后面”的纹理使用偏移后的UV用parallaxUV去采样法线贴图和颜色贴图。注意对于颜色贴图可能还需要进行多次步进采样Relief Parallax Mapping来获得更精确的结果但性能开销也更大。视差映射能显著提升地面、砖墙等具有明显深度变化的表面的质感是法线贴图技术的一个强力补充。5.4 常见问题排查速查表问题现象可能原因排查步骤模型表面无凹凸感像纯色1. 法线贴图纹理导入类型不是“Normal map”。2. Shader中法线贴图采样或属性连接错误。3. 法线向量未正确连接到光照计算。1. 检查纹理导入设置。2. 在Shader Graph中预览Normal From Texture节点输出在手写Shader中返回解码后的法线值可视化。3. 确认世界空间法线已传递给光照函数如InputData.normalWS。凹凸方向完全相反法线贴图的绿色(G)通道方向与Unity预期相反。在纹理导入设置中勾选“Flip Green Channel”或在Shader中将采样后的法线Y分量乘以-1。接缝处有黑色/白色条纹模型切线在UV接缝处不连续。1. 检查模型UV拆分避免不必要的硬边。2. 考虑使用对象空间法线贴图但限制多。3. 使用平滑组或顶点法线调整工具重新计算模型的切线。移动设备上效果模糊或闪烁1. 法线贴图压缩格式不合适如用了ETC1。2. Mipmaps导致细节丢失。3. 精度不足在低端机上使用float导致性能下降。1. 为Android/iOS覆盖设置为ASTC格式。2. 调整纹理的Max Size或Aniso Level。3. 在片段着色器中尽量使用half精度。特定角度下凹凸消失或变平法线转换矩阵TBN构建错误导致在某些视角下法线Z分量接近0。检查TBN矩阵的构建和乘法顺序。确保在片段着色器中对normalWS,tangentWS,bitangentWS进行了归一化normalize。性能开销过大1. 法线贴图分辨率过高。2. 使用了复杂的视差或曲面细分。3. Shader中存在大量每像素计算。1. 根据模型在屏幕上的大小合理降低法线贴图分辨率如从2K降到1K。2. 仅在需要的高视觉优先级物体上使用高级技术。3. 将部分计算移到顶点着色器如TBN矩阵计算但会损失精度。掌握这些排查思路能让你在遇到法线贴图相关问题时快速定位。归根结底理解数据流——从纹理导入、到采样解码、再到空间变换——是解决一切问题的钥匙。URP虽然要求我们多做一些显式的工作但这也让我们对渲染管线的控制力上了一个台阶为实现更复杂、更个性化的视觉效果打下了坚实的基础。
返回列表