- 法線紋理一般是為了讓模型有視覺上的凹凸效果,但是模型實(shí)際不是具有凹凸面的,只是在計(jì)算光照時(shí),讓模型不同位置根據(jù)法線值的不同,得到凹凸感的光照效果。法線紋理中保存的可能是模型空間中的法線值,這種法線紋理是五顏六色的,因?yàn)樗姆ň€值在(-1,1)之間,經(jīng)過映射后得到(0,1)之間的值,顏色是多種的。而另一種法線紋理保存的是模型中每個(gè)頂點(diǎn)的切線空間中法線擾動(dòng)方向,又因?yàn)槟P椭写蟛糠猪旤c(diǎn)法線不需要擾動(dòng),所以轉(zhuǎn)換到切線空間中法線方向仍為(0,0,1),變換后為(0.5,0.5,1),因此這種法線紋理大都為偏藍(lán)色。
- 在計(jì)算光照時(shí)有兩種選擇,一種是在切線空間中計(jì)算,即在vertex shader中將光照計(jì)算需要的光照方向、視線方向、模型頂點(diǎn)世界坐標(biāo)以及模型頂點(diǎn)中保存的法線方向變換到切線空間中,然后在fragment shader中對(duì)法線紋理BumpTex進(jìn)行采樣,得到法線紋理中法線normal,再根據(jù)經(jīng)典光照模型計(jì)算出ambient,diffuse和specular。
- 在unity shader中使用法線紋理有一些要注意的地方,如法線紋理導(dǎo)入到unity中如果被設(shè)置為normalMap格式時(shí),應(yīng)該使用的解壓函數(shù),計(jì)算binormal時(shí)要確定叉乘后的方向等,以及求float3x3的模型->切線空間變換矩陣等。在實(shí)例中給予注釋說明。
- 使用基于切線空間的法線紋理的計(jì)算復(fù)雜點(diǎn)在于光照計(jì)算上,因?yàn)槲覀冃枰亚芯€空間的法線變換到世界空間(或者翻過來把世界空間中的光線視點(diǎn)等變換到切線空間),而求這些變換矩陣時(shí)就要仔細(xì)小心(也算對(duì)矩陣變換計(jì)算的一個(gè)自我檢驗(yàn))。
- 在tangentSpace中計(jì)算光照實(shí)例
Shader "FFD/BumpMap/NormalMapTangentSpace"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BumpTex ("BumpMap",2D) = "bump" {}
_BumpScale ("BumpScale",Range(-1,1)) = 0
_Gloss ("GLoss",range(1,30)) = 20
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "lighting.cginc"
#include "autolight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 vTangent : TANGENT;
float3 vNormal : NORMAL;
};
struct v2f
{
float4 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
// float3 tangentNormal : TEXCOORD1;
float3 tangentView : TEXCOORD2;
float3 tangentLight : TEXCOORD3;
};
sampler2D _MainTex;
sampler2D _BumpTex;
float4 _MainTex_ST;
float4 _BumpTex_ST;
float _BumpScale;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.uv,_BumpTex);
float3x3 objToTangent;
float3 bitNormal = normalize(cross(v.vNormal,v.vTangent.xyz)*v.vTangent.w);
float3 vNormal = normalize(v.vNormal);
float3 vTangent = normalize(v.vTangent);
//計(jì)算object->tangent轉(zhuǎn)換矩陣,構(gòu)建一個(gè)3x3的矩陣,因?yàn)樽儞Q的是向量,沒必要用4x4的矩陣。
//這個(gè)矩陣使用的是三個(gè)向量分布為矩陣的每一行(考慮 world->camera )
objToTangent = float3x3(vTangent.xyz,bitNormal,vNormal);
//計(jì)算object空間光線方向或者用內(nèi)置函數(shù)計(jì)算
float3 objLight = mul((float3x3)unity_WorldToObject,_WorldSpaceLightPos0.xyz);
// float3 objLight = ObjSpaceLightDir(v.vertex);
//計(jì)算object空間view方向
float3 objView = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos.xyz,1))-v.vertex.xyz;
o.tangentLight = mul(objToTangent,objLight);
o.tangentView = mul(objToTangent,objView);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
float3 tangentLight = normalize(i.tangentLight);
float3 tangentView = normalize(i.tangentView);
//如果沒有在unity中對(duì)紋理設(shè)置為normalTexture,那么就要手動(dòng)解析切線空間中的法線
fixed4 colBump = tex2D(_BumpTex,i.uv.zw);
float3 tangentNormal;
//此時(shí)沒有被壓縮,所以tangentNormal.xyz = colBump*2-1;
//tangentNormal = UnpackNormal(colBump);//如果設(shè)置了就用內(nèi)置函數(shù)。
tangentNormal.xy = (colBump.xy*2-1.0) * _BumpScale;
tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
//計(jì)算光照
tangentNormal = normalize(tangentNormal);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.xyz * saturate(dot(tangentLight,tangentNormal));
float3 H = normalize(tangentView + tangentLight);
float3 specular=_LightColor0.xyz * pow(saturate(dot(tangentNormal,H)),_Gloss);
col = fixed4(col.xyz*(ambient+diffuse+specular),1);
return col;
}
ENDCG
}
}
FallBack "Specular"
}
- 在切線空間計(jì)算光照有一個(gè)問題就是當(dāng)物體在不統(tǒng)一縮放情況下會(huì)產(chǎn)生光照錯(cuò)誤的情況,因此unity5.x內(nèi)置的shader中都將光照計(jì)算放到世界空間中進(jìn)行了。世界空間法線紋理光照計(jì)算如下例,注意的地方是因?yàn)橐獙⑶芯€空間中的法線變換到世界空間,就要在vert shader中將變換矩陣求出來,因?yàn)関ert->frag數(shù)據(jù)傳遞最多是float4類型的向量,所以定義3個(gè)float3的向量來組成tangent->world轉(zhuǎn)換矩陣(也可以充分利用寄存器空間,在已用三個(gè)寄存器w分量中存儲(chǔ)worldPos的三個(gè)分量)。
Shader "FFD/BumpMap/NormalMapWorldSpace"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BumpTex ("BumpTex",2D) = "bump"{}
_Gloss("Gloss",range(1,30)) = 1
_BumpScale ("BumpScale",range(-1,1)) =0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "lighting.cginc"
#include "autolight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 tangent :TANGENT;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 uv : TEXCOORD0;
float4 worldVertexPos : TEXCOORD1;
float3 TtoW0 : TEXCOORD2;
float3 TtoW1 : TEXCOORD3;
float3 TtoW2 : NORMAL;
};
sampler2D _MainTex;
sampler2D _BumpTex;
float4 _MainTex_ST;
float4 _BumpTex_ST;
float _Gloss;
float _BumpScale;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.uv,_BumpTex);
o.worldVertexPos = mul(unity_ObjectToWorld,v.vertex);
float3 worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
float3 worldTangent = mul(unity_ObjectToWorld,v.tangent.xyz);
float3 worldBitNormal = cross(worldNormal,worldTangent)*v.tangent.w;
o.TtoW0 = float3(worldTangent.x,worldBitNormal.x,worldNormal.x);
o.TtoW1 = float3(worldTangent.y,worldBitNormal.y,worldNormal.y);
o.TtoW2 = float3(worldTangent.z,worldBitNormal.z,worldNormal.z);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv.xy);
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
float3 worldView = normalize(_WorldSpaceCameraPos.xyz-i.worldVertexPos.xyz);
//對(duì)bump紋理采樣得到tangent空間下的normal;
float4 colBump = tex2D(_BumpTex,i.uv.zw);
//如果將法線圖設(shè)置為normalMap,就用這個(gè)函數(shù),如果沒設(shè)置,用此函數(shù)會(huì)得到錯(cuò)誤的結(jié)果
float3 normal = UnpackNormal(colBump);
normal.xy = normal.xy*_BumpScale;
normal.z = sqrt(1.0-dot(normal.xy,normal.xy));
//構(gòu)建Tangent->World變換矩陣,或者直接相乘
float3x3 TtoW = float3x3(i.TtoW0,i.TtoW1,i.TtoW2);
float3 worldNormal = normalize(mul(TtoW,normal));
//計(jì)算光照
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.xyz * saturate(dot(worldNormal,worldLight));
float3 H = normalize(worldView+worldLight);
fixed3 specular = _LightColor0.xyz * pow(saturate(dot(H,worldNormal)),_Gloss);
col = col*fixed4(ambient+diffuse+specular,1);
return col;
}
ENDCG
}
}
FallBack "Specular"
}