【從UnityURP開始探索游戲渲染】專欄-直達
法線貼圖呈現藍紫色調(尤其以藍色為主)是由其?存儲原理、切線空間坐標系設計及顏色編碼規則共同決定的?。
核心原因:法線向量的存儲規則?
?法線向量的物理范圍?
法線是單位向量,每個分量(X, Y, Z)的取值范圍為 ?[-1, 1],分別代表切線空間中的方向:
- ?X(紅色通道):左右偏移(左為負,右為正)
- ?Y(綠色通道):上下偏移(下為負,上為正)
- ?Z(藍色通道):垂直表面的方向(指向外部為正)?。
?顏色空間的映射限制?
圖像顏色值范圍是 ?[0, 1](對應0~255),因此需要進行轉換:
RGB=(Normalxyz+1)/2
- ?默認法線方向?:當表面完全垂直(無傾斜)時,法線向量為 ?(0, 0, 1)。
- ?轉換結果?:
- R=20+1=0.5 (128)
- G=20+1=0.5 (128)
- B=21+1=1 (255)
- 最終顏色為 ?(128, 128, 255),即 ?藍紫色?(藍色占主導)?。
?現實模型的主導方向
- 大多數模型表面(如墻面、地面)以?垂直方向為主?(Z≈1),因此藍色通道值接近255,而XY通道接近128(中性灰),整體呈現藍色基調?。
?顏色變化的場景解釋?
?顏色表現? | ?對應的法線方向? | ?表面形態? |
---|---|---|
?深藍色 (0,0,1) | 完全垂直向外 | 平坦表面(如地板) |
?藍紫色 (0.5,0.5,1) | 輕微傾斜 | 緩坡、弧形表面 |
?青色/綠色 (低R,高G,中B) | 明顯上/下傾斜(Y≠0) | 邊緣、陡坡 |
?紅色/粉色 (高R,中G,中B)? | 明顯左/右傾斜(X≠0) | 側壁、凹凸邊緣 |
💎 ?示例?:墻面法線貼圖中,磚縫凹陷處因法線指向側方(X/Y增大),可能呈現紅綠色調,但整體仍以藍紫色為基底?。
? ?技術實現驗證?
?生成與解碼邏輯?
-
?生成法線貼圖?:通過公式?
color = (normal + 1) / 2
?將高模法線烘焙為貼圖?。 -
?Shader解碼?:在著色器中逆向計算還原法線向量:此步驟是光照計算的基礎?。
glsl vec3 normal = texture(normalMap, uv).rgb * 2.0 - 1.0; // [0,1] → [-1,1]
?切線空間的意義?
法線貼圖通常在? 切線空間(Tangent Space)中定義:
- 以頂點法線為Z軸,切線為X軸,副切線為Y軸構建坐標系。
- ?優勢?:無論模型如何旋轉,法線方向始終相對于表面本地坐標,確保凹凸效果穩定?。
常見誤區澄清?
- ?誤區1?:藍色是人為設定的美術風格。?真相?:藍色是數學映射的必然結果,由垂直方向(0,0,1)的編碼規則決定?。
- ?誤區2?:法線貼圖的顏色代表凹凸高度。?真相?:它存儲的是?方向?而非高度,凹凸感通過光照模擬實現?。
實際應用案例?
- ?Unity 工作流?:將法線貼圖拖入材質球的 ?Normal Map? 插槽,通過?
UnpackNormal()
?函數解碼(內置管線見?UnityCG.cginc
,URP管線UnpackNormalScale()
見Packing.hlsl)?。 - ?效果增強?:調整 ?Normal Scale? 參數控制凹凸強度(值>1增強凸起,<1弱化)?。
?URP中的法線貼圖
法線貼圖設置流程
- ?導入法線貼圖?
- 紋理類型設為"Default/Normal map"
- 壓縮格式推薦BC5(DXT5nm)或BC7
- 勾選"sRGB"選項確保正確色彩空間轉換
- ?創建URP材質?
- 使用Shader路徑:
Universal Render Pipeline/Lit
- 將法線貼圖拖拽到Normal Map插槽
- 調整Normal Scale參數(建議0.5-1.5)
- 使用Shader路徑:
完整Shader代碼實現
-
NormalMapURP.shader
Shader "Custom/URPNormalMap" {Properties{_BaseMap("Albedo", 2D) = "white" {}_BaseColor("Color", Color) = (1,1,1,1)_NormalMap("Normal Map", 2D) = "bump" {}_NormalScale("Normal Scale", Range(0,2)) = 1_Metallic("Metallic", Range(0,1)) = 0_Smoothness("Smoothness", Range(0,1)) = 0.5}SubShader{Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline"}HLSLINCLUDE#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"TEXTURE2D(_BaseMap);SAMPLER(sampler_BaseMap);TEXTURE2D(_NormalMap);SAMPLER(sampler_NormalMap);CBUFFER_START(UnityPerMaterial)float4 _BaseMap_ST;half4 _BaseColor;half _Metallic;half _Smoothness;half _NormalScale;CBUFFER_ENDstruct Attributes{float4 positionOS : POSITION;float3 normalOS : NORMAL;float4 tangentOS : TANGENT;float2 uv : TEXCOORD0;};struct Varyings{float4 positionCS : SV_POSITION;float2 uv : TEXCOORD0;float3 normalWS : TEXCOORD1;float4 tangentWS : TEXCOORD2;float3 positionWS : TEXCOORD3;};ENDHLSLPass{Name "ForwardLit"Tags { "LightMode"="UniversalForward" }HLSLPROGRAM#pragma vertex vert#pragma fragment fragVaryings vert(Attributes input){Varyings output;VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);output.positionCS = vertexInput.positionCS;output.positionWS = vertexInput.positionWS;output.uv = TRANSFORM_TEX(input.uv, _BaseMap);output.normalWS = normalInput.normalWS;output.tangentWS = float4(normalInput.tangentWS, input.tangentOS.w);return output;}half4 frag(Varyings input) : SV_Target{// 采樣基礎貼圖half4 baseColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;// 采樣和解壓法線貼圖half4 normalSample = SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, input.uv);half3 normalTS = UnpackNormalScale(normalSample, _NormalScale);// 構建TBN矩陣half3 bitangentWS = cross(input.normalWS, input.tangentWS.xyz) * input.tangentWS.w;half3x3 TBN = half3x3(input.tangentWS.xyz, bitangentWS, input.normalWS);half3 normalWS = TransformTangentToWorld(normalTS, TBN);// 光照計算Light mainLight = GetMainLight();half3 lightDir = normalize(mainLight.direction);half NdotL = saturate(dot(normalWS, lightDir));half3 diffuse = baseColor.rgb * NdotL * mainLight.color;// 高光計算half3 viewDir = normalize(_WorldSpaceCameraPos - input.positionWS);half3 halfVec = normalize(lightDir + viewDir);half NdotH = saturate(dot(normalWS, halfVec));half specular = pow(NdotH, _Smoothness * 256) * _Metallic;half3 finalColor = diffuse + specular * mainLight.color;return half4(finalColor, baseColor.a);}ENDHLSL}} }
關鍵實現說明
- ?法線解壓?:使用
UnpackNormalScale
函數處理法線貼圖數據,支持強度調節 - ?TBN矩陣?:通過切線、副切線和法線構建轉換矩陣,將切線空間法線轉到世界空間
- ?光照模型?:采用Blinn-Phong模型計算漫反射和高光
- ?URP適配?:使用URP特有的
GetVertexPositionInputs
等函數替代傳統Shader寫法
常見問題解決方案
- ?法線效果異常?:檢查切線空間計算是否正確,確保模型導入時勾選"Calculate Tangents"
- ?性能優化?:移動端可考慮在切線空間計算光照減少矩陣運算
- ?多光源支持?:需添加AdditionalLights Pass處理額外光源
總結?
法線貼圖的藍色基調本質是?垂直方向向量(0,0,1)經歸一化映射后的顏色表達?,這種方法平衡了存儲效率與光照計算需求,是3D渲染中模擬表面細節的核心技術?,直觀的顏色樣式只是數據可視化的一種直觀顯示。
【從UnityURP開始探索游戲渲染】專欄-直達
(歡迎點贊留言探討,更多人加入進來能更加完善這個探索的過程,🙏)