Normal Mapping 法線貼圖

法線貼圖Normal mapping

三維計算機圖形學中,是凸凹貼圖(Bump mapping)技術的一種應用,法線貼圖有時也稱爲“Dot3(仿立體)凸凹紋理貼圖”。凸凹與紋理貼圖通常是在現有的模型法線添加擾動不同,法線貼圖要完全更新法線。與凸凹貼圖類似的是,它也是用來在不增加多邊形的情況下在濃淡效果中添加細節。但是凸凹貼圖通常根據一個單獨的灰度圖像通道進行計算,而法線貼圖的數據源圖像通常是從更加細緻版本的物體得到的多通道圖像,即紅、綠、藍通道都是作爲一個單獨的顏色對待。(引用自維基百科)

凹凸貼圖是一種提高對象的外觀的真實度而不會增加幾何體的複雜性的有效方式.可以模擬表面的細節或者表面的不規則.

其實這項技術並沒有改變物體的表面,只是欺騙了光照計算.將不會出現在對象的輪廓邊緣上.通常適合模擬橘子上的皺紋,浮雕的徽標等等.

凹凸貼圖原理

凹凸貼圖的關鍵在於我們需要每個片元位置有個有效的表面法線,還需要有一個光源查看方向矢量.如果我們能夠在片元着色器上面訪問這些值,那麼就可以在光源計算之前用程序擾動法線,以便產生凹凸的效果.在這種情況下,我們實際上是嘗試在所渲染的表面上創建凸起或者很小的小球.
通常眼睛座標是計算執行光照計算的最佳選擇,但是就性能上來說,對於每一個片元上執行一系列操作是相當高昂的.所以,這裏我們使用一種座標,它們使用的座標系是Tangent Space;
切空間的三條座標軸tangant軸(T)、bitangent軸(B)及法線軸(N)所組成的座標系,即切線空間(TBN).
其中三條座標軸的來源是:
T = normalize(dx/du, dy/du, dz/du)
N = T × normalize(dx/dv, dy/dv, dz/dv)
B = N × T

Vertex shader 代碼

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"
}




發佈了57 篇原創文章 · 獲贊 86 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章