时间:2025-09-07 15:49
人气:
作者:admin
法线贴图呈现蓝紫色调(尤其以蓝色为主)是由其存储原理、切线空间坐标系设计及颜色编码规则共同决定的。
【从UnityURP开始探索游戏渲染】专栏-直达
法线是单位向量,每个分量(X, Y, Z)的取值范围为 [-1, 1],分别代表切线空间中的方向:
图像颜色值范围是 [0, 1](对应0~255),因此需要进行转换:
RGB=(Normalxyz+1)/2
| 颜色表现 | 对应的法线方向 | 表面形态 |
|---|---|---|
| 深蓝色 (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)中定义:
UnpackNormal() 函数解码(内置管线见 UnityCG.cginc,URP管线UnpackNormalScale()见Packing.hlsl)。Universal Render Pipeline/LitNormalMapURP.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_END
struct 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;
};
ENDHLSL
Pass
{
Name "ForwardLit"
Tags { "LightMode"="UniversalForward" }
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
Varyings 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函数处理法线贴图数据,支持强度调节GetVertexPositionInputs等函数替代传统Shader写法法线贴图的蓝色基调本质是垂直方向向量(0,0,1)经归一化映射后的颜色表达,这种方法平衡了存储效率与光照计算需求,是3D渲染中模拟表面细节的核心技术,直观的颜色样式只是数据可视化的一种直观显示。
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,????)