法線貼圖(Normal mapping)
在三維計算機圖形學中,是凸凹貼圖(Bump mapping)技術的一種應用,法線貼圖有時也稱爲“Dot3(仿立體)凸凹紋理貼圖”。凸凹與紋理貼圖通常是在現有的模型法線添加擾動不同,法線貼圖要完全更新法線。與凸凹貼圖類似的是,它也是用來在不增加多邊形的情況下在濃淡效果中添加細節。但是凸凹貼圖通常根據一個單獨的灰度圖像通道進行計算,而法線貼圖的數據源圖像通常是從更加細緻版本的物體得到的多通道圖像,即紅、綠、藍通道都是作爲一個單獨的顏色對待。(引用自維基百科)
凹凸貼圖是一種提高對象的外觀的真實度而不會增加幾何體的複雜性的有效方式.可以模擬表面的細節或者表面的不規則.
其實這項技術並沒有改變物體的表面,只是欺騙了光照計算.將不會出現在對象的輪廓邊緣上.通常適合模擬橘子上的皺紋,浮雕的徽標等等.
凹凸貼圖原理
N = T × normalize(dx/dv, dy/dv, dz/dv)
B = N × T
float4x4 matWorldViewProjection;
float4x4 matWorld;
float4 vViewPosition;
float3 vLight;
struct VS_INPUT
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
float3 Normal : NORMAL0;
float3 Binormal : BINORMAL0;
float3 Tangent : TANGENT0;
};
struct VS_OUTPUT
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
float3 Eye : TEXCOORD1;
float3 Light : TEXCOORD2;
};
VS_OUTPUT vs_main( VS_INPUT Input )
{
VS_OUTPUT Output;
Output.Position = mul( Input.Position, matWorldViewProjection );
Output.TexCoord = Input.TexCoord;
float3 world = mul (Input.Position, matWorld);
float3 eye = vViewPosition - world; //視線
//將光線和視線變換到正切空間,world矩陣爲單位陣所以不用先變換法線
Output.Eye.x = dot(Input.Tangent, eye);
Output.Eye.y = dot(Input.Binormal, eye);
Output.Eye.z = dot(Input.Normal, eye);
Output.Light.x = dot(Input.Tangent, vLight);
Output.Light.y = dot(Input.Binormal, vLight);
Output.Light.z = dot(Input.Normal, vLight);
return( Output );
}
同時我們看一下Pixe shader裏面的代碼
float4 AmbientColor;
float4 DiffuseColor;
float4 SpecularColor;
float SpecularIntensity;
float SpecularPow;
sampler2D TextureMap;
sampler2D NormalMap;
struct PS_INPUT
{
float2 TexCoord : TEXCOORD0;
float3 Eye : TEXCOORD1;
float3 Light : TEXCOORD2;
};
float4 ps_main( PS_INPUT Input ) : COLOR0
{
//貼圖顏色,環境光和漫反射光照亮這個顏色
float4 baseColor = tex2D(TextureMap, Input.TexCoord);
float3 light = normalize(Input.Light);
float3 eye = normalize(Input.Eye);
float3 normal = normalize(tex2D(NormalMap, Input.TexCoord).xyz * 2.0f - 1.0f);
float ndl = saturate(dot(light, normal));
//光線的反射方向r
float3 r = normalize(reflect(-light, normal));
float rdv = pow(saturate(dot(r, eye)), SpecularPow);
//光照方程
return AmbientColor * baseColor + DiffuseColor * baseColor * ndl + SpecularColor * SpecularIntensity * rdv;
}
這一句代碼比較難理解
float3 normal = normalize(tex2D(NormalMap, Input.TexCoord).xyz * 2.0f - 1.0f);
那種偏藍色的法線紋理其實就是存儲了在每個頂點各自的Tangent Space中,法線的擾動方向。也就是說,如果一個頂點的法線方向不變,那麼在它的Tangent Space中,新的normal值就是z軸方向,也就是說值爲(0, 0, 1)。但這並不是法線紋理中存儲的最終值,因爲一個向量每個維度的取值範圍在(-1, 1),而紋理每個通道的值範圍在(0, 1),因此我們需要做一個映射,即pixel = (normal + 1) / 2。
unity 中的NormalMap shader
// The Shader takes a color and a normal texture and uses them to do normal mapping with a surface shader.
Shader "Ellioman/NormalMapSurfaceShader"
{
// What variables do we want sent in to the shader?
Properties
{
_MainTex ("Main Texture", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
_NormalMapIntensity ("Normal Map Intensity", range(0, 10)) = 1
_Occlusion ("Occlusion", 2D) = "white" {}
_Specular ("Specular Map", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
}
CGPROGRAM
// Pragmas
#pragma surface surfaceShader StandardSpecular
// User Defined Variables
uniform sampler2D _MainTex;
uniform sampler2D _NormalMap;
uniform sampler2D _Occlusion;
uniform sampler2D _Specular;
uniform float _NormalMapIntensity;
// Base Input Structs
struct Input
{
float2 uv_MainTex;
float2 uv_NormalMap;
float2 uv_Occlusion;
float2 uv_Specular;
};
// The Surface Shader
void surfaceShader(Input IN, inout SurfaceOutputStandardSpecular o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
o.Occlusion = tex2D(_Occlusion, IN.uv_Occlusion).rgb;
o.Specular = tex2D(_Specular, IN.uv_Specular).rgb;
fixed3 n = UnpackNormal(tex2D(_NormalMap,IN.uv_NormalMap)).rgb;
n.xy*= _NormalMapIntensity;
//n.y *= _NormalMapIntensity;
o.Normal = normalize(n);
}
ENDCG
}
Fallback "Diffuse"
}