翻譯6 Unity Advance texture

1 Bump Map(凹凸貼圖):

NormalMap 法線紋理:比較經常使用算法

HeightMap 高度紋理(視差映射):手機平臺不經常使用,使用法線紋理替代。函數

Occlusion Map:細節紋理優化

Secondarspa

y Maps (Detail Maps) & Detail Mask:細節紋理code

Bump Map Type Describe
NormalMap                 法線圖,映射公式:normal=pixel*2-1,反映射:pixel=(normal+1)/2. 法線存儲既能夠在模型空間,也能夠在切線空間。//unity頂點輸入結構帶切線變量,通常存在切線空間更佳。
HeightMap 灰度圖(黑白紋理-強度值),顏色越淺該表面越向外凸起,顏色深越凹。視差映射技術,與Occlusion Map搭配使用體驗更佳,計算昂貴
Occlusion Map 灰度圖,表面細節更豐富。顏色值白色表示應該接收徹底間接照明的區域,黑色表示沒有間接照明。如裂縫或褶皺,實際上不會接收到太多的間接光,可與高度圖一塊兒使用。
Detail Maps 參考StandardShader:第二細節紋理,應用第二反照率圖和第二法線圖,在近距離觀察時有清晰的細節,好比毛孔、細小的裂縫等。計算昂貴


1.1 HeightMap:

高度圖爲了模擬平面的凹凸程度,將高度(黑白色)數據存儲在紋理中,因爲紋理數據是二維的,即u軸和v軸,那爲了獲得這些數據爲每一個片斷生成法向量,可分別在u軸和v軸上採樣。先從U軸計算:f(u)=h ,若是知道了斜率就能求得u軸上全部點的法向量,但斜率由h的變化程度高低決定。爲了近似獲得從一個點到下一個點的高度差:orm

image

圖八:斜率採樣示意圖,從f(0)到f(1)對象

這是對切向量的一個粗略的估算,它把整張紋理做爲線性的斜率。那爲了不這種粗略計算,能夠採樣兩個靠的更近點的。例如,從0到1/2,那這兩點的斜率=f(1/2)-f(0),同時f因子被縮小,須要乘以相應的倍數。2(f(1/2)−f(0))。擴展開來,能夠獲得以下的函數:δ值越小越精確,必須大於0小於1。blog

image=>imageci

圖九:有限差分get

那麼切向量就是[1, f’(u), 0]T,從切向量計算法向量[f’(u),1,0]T:

sampler2D _HeightMap;
float4 _HeightMap_TexelSize;//xy是紋素座標(uv),zw是整張紋理寬高
float2 delta = float2(_HeightMap_TexelSize.x, 0);//u軸
float h1 = tex2D(_HeightMap, i.uv);//模型uv在高度圖採樣
float h2 = tex2D(_HeightMap, i.uv + delta);//二次採樣

//第一步套用公式
//i.normal = float3(1, (h2 - h1) / delta.x, 0); 

//第二步優化,縮放向量並不改變方向,消除了除法操做
//i.normal = float3( delta.x, (h2 - h1), 0);

//第三步改變垂直方向,須要獲得法向量正向垂直於表面,那麼逆時針旋轉90度以翻轉x份量符號.//Y是擾動法向量的高低變化因子
i.normal = float3( h1 - h2, 1 , 0);

i.normal = normalize(i.normal);

有限差分只在一個方向近似求值,爲了更好近似能夠在兩個方向線性逼近:

image

圖十:中心差分

float2 delta = float2(_HeightMap_TexelSize.x * 0.5, 0);
float h1 = tex2D(_HeightMap, i.uv - delta);
float h2 = tex2D(_HeightMap, i.uv + delta);
i.normal = float3(h1 - h2, 1, 0);

那麼f’(u,v)計算f’(v)同理,切向量[0,f’(v),1]T,法向量是[0,1,-f’(v)]:

float2 du = float2(_HeightMap_TexelSize.x * 0.5, 0);
float u1 = tex2D(_HeightMap, i.uv - du);
float u2 = tex2D(_HeightMap, i.uv + du);
//float3 tu = float3(1, u2 - u1, 0);
float2 dv = float2(0, _HeightMap_TexelSize.y * 0.5);
float v1 = tex2D(_HeightMap, i.uv - dv);
float v2 = tex2D(_HeightMap, i.uv + dv);
//float3 tv = float3(0, v2 - v1, 1);
//i.normal = cross(tv, tu);//直接使用叉積求出垂直於u和v軸的法向量=>(0*(v2-v1)-(u2-u1)*1, 1*1-0*0, (u2-u1)*0-1*(v2-v1))=(u1-u2, 1, v1-v2)
i.normal = float3(u1 - u2, 1, v1 - v2);
i.normal = normalize(i.normal);

 image

1.2 Normal Map:

高度圖是每幀採樣實時計算法線,爲了不計算,採用預製法線紋理代替。

image

圖十一:Unity中使用高度圖

導入高度圖做爲法線貼圖預先計算法線紋理必須勾選Create from Grayscale,白色表示相對更高,黑色表示相對更低。

像素份量範圍是[0,1],而法線份量範圍[-1,1]。相互映射轉換公式爲:

pixel = (normal+1)/2;

normal = pixel · 2 – 1;

法線紋理呈現淡藍色,這是由於法向映射最多見的約定是將向上的方向存儲在Z份量中(垂直於表面外側),又因爲DXT5nm紋理壓縮格式的緣由,只存儲了X與Y份量捨棄了Z份量(Y份量存儲在G通道,X份量存儲在A通道,RB通道被捨棄)。經過推導法向量的單位向量可得Z份量:

|N| = |N|2 = Nx2 + Ny2 + Nz2 = 1;

Nz = 根號(1 -Nx2 - Ny2);


//第一種方法
// Unpack normal as DXT5nm (1, y, 1, x) or BC5 (x, y, 0, 1)
//dxt5壓縮對應的位置取wy
i.normal.xy = tex2D(_NormalMap, i.uv).wy * 2 - 1;
i.normal.xy *= _BumpScale;//計算Z以前縮放纔有效,平坦凹凸程度
i.normal.z = sqrt(1 - saturate(dot(i.normal.xy, i.normal.xy)));//dot模擬平方計算-((x,y)*(x,y))=-x方-y方
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);

//UnityStandardUtils.cginc包含了解碼法線函數,替代上面的方法
i.normal = UnpackScaleNormal(tex2D(_NormalMap, i.uv), _BumpScale);
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);


1.3 Detail Maps(Second Texture) 與 Detail Normals

第二細節紋理與MainTexture合併,簡要代碼以下:

//頂點uv座標映射到紋理uv
i.uv.xy  = TRANSFORM_TEX(v.uv, _MainTex);
i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex);
//計算第二紋理的影響
float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb;
albedo *= tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble;//顏色空間轉換

第二細節紋理的法線映射

i.normal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
i.normal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);


1.4 Blending Normals

方式一:(main.normal + details.normal) * 0.5; 簡單容易,但結果不是很好。主紋理和細節紋理都變得平坦。理想狀況下,當其中一個是平的,指望它不會影響到另外一個。

float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
i.normal = (mainNormal + detailNormal) * 0.5;
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);

方式二:用z份量作縮放因子求偏導函數,而後相加。[Mx, My, Mz]T = [Mx/Mz, My/Mz, 1]T 同理求得detail偏導函數,而後相加:[Mx/Mz + Dx/Dz, My/Mz + Dy/Dz, 1]T .效果很好,可是在合併陡峭時仍將失去細節。

float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
i.normal = float3(mainNormal.xy / mainNormal.z + detailNormal.xy / detailNormal.z, 1);
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);

方式三:白色調和,對上一步合併法線分別乘以MzDz,而後再去掉x和y的縮放因子誇大縮放,使陡峭更加明顯,同時平坦的法線,它不會影響到另外一個了。

float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
i.normal = float3(mainNormal.xy + detailNormal.xy, mainNormal.z * detailNormal.z);
//UnityStandardUtils包含了混合函數
//i.normal = BlendNormals(mainNormal, detailNormal);
i.normal = i.normal.xzy;
i.normal = normalize(i.normal);View Code


1.5 Tangent Space

切線空間的法線紋理:頂點爲原點,z軸爲法線方向,x軸爲切線方向,y軸爲垂直於xz的副切線方向。Unity導入模型計算切線默認使用了mikktspace(在頂點着色器計算),也能夠在片元着色器計算cross獲得副切線向量。

頂點下計算:

struct VertexData {
	float4 tangent : TANGENT;
};
struct Interpolators {
	float4 tangent : TEXCOORD2;
};

使用UnityCG中的UnityObjectToWorldDir在頂點程序中將切線轉換爲世界空間。 固然,這僅適用於切線的XYZ部分。 它的W份量須要不加修改地傳遞。

Interpolators MyVertexProgram (VertexData v) {
	Interpolators i;
	i.position = mul(UNITY_MATRIX_MVP, v.position);
	i.worldPos = mul(unity_ObjectToWorld, v.position);
	i.normal = UnityObjectToWorldNormal(v.normal);
	i.tangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
	i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
	i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex);
	ComputeVertexLightColor(i);
	return i;
}

如今咱們能夠將法線從切線空間轉換爲世界空間。

float3 binormal = cross(i.normal, i.tangent.xyz) * i.tangent.w;
i.normal = normalize(
	tangentSpaceNormal.x * i.tangent +
	tangentSpaceNormal.y * i.normal +
	tangentSpaceNormal.z * binormal
);

去掉顯式YZ交換,將其與空間轉換結合在一塊兒。


   
   
   
   
     //tangentSpaceNormal = tangentSpaceNormal.xzy; 
   	
float3 binormal = cross(i.normal, i.tangent.xyz) * i.tangent.w;
i.normal = normalize(
	tangentSpaceNormal.x * i.tangent +
	tangentSpaceNormal.y * binormal +
	tangentSpaceNormal.z * i.normal
);

在構造副法線時,還有一個額外的細節。假設一個對象的scale設置爲(- 1,1,1),這意味着它是鏡像的。在這種狀況下,咱們必須翻轉副法線,來正確地鏡像切線空間。事實上,當奇數維數爲負時,咱們必須這樣作。UnityShaderVariables經過定義float4 unity_WorldTransformParams變量來幫助咱們完成這個任務。當須要翻轉副法線時,它的第四個份量爲- 1,不然爲1。

float3 binormal = cross(i.normal, i.tangent.xyz) *(i.tangent.w * unity_WorldTransformParams.w);


轉換空間:

在世界空間下計算

fixed4 MyFrag(v2f v) : SV_TARGET{
	//...
	float3 tangentSpaceNormal= UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
	#if defined(BINORMAL_PER_FRAGMENT)
	    float3 binormal = cross(v.normal, v.tangent.xyz) * v.tangent.w;
	#else
	    float3 binormal = v.binormal;
	#endif
	//把切線空間轉到世界空間
	//tangentSpaceNormal * [v.tangent,binromal, v.normal]T
	v.normal = normalize(
	    tangentSpaceNormal.x * v.tangent +
	    tangentSpaceNormal.y * binormal +
	    tangentSpaceNormal.z * v.normal
	);
	//...
}

在切線空間計算

//計算副切線
float3 binormal = cross(normalize(i.normal), normalize(i.tangent.xyz)) * i.tangent.w;
//切線空間矩陣//行優先的填充
float3x3 t_matrix = float3x3(i.tangent.xyz, binormal, i.normal);
//把各類信息轉到切線空間下參與計算


1.6 副切線在哪算合適

在頂點計算沒必要計算叉乘函數,經過宏定義開啓。

struct Interpolators {
	float4 position : SV_POSITION;
	float4 uv : TEXCOORD0;
	float3 normal : TEXCOORD1;

	#if defined(BINORMAL_PER_FRAGMENT)
		float4 tangent : TEXCOORD2;
	#else
		float3 tangent : TEXCOORD2;
		float3 binormal : TEXCOORD3;
	#endif

	float3 worldPos : TEXCOORD4;

	#if defined(VERTEXLIGHT_ON)
		float3 vertexLightColor : TEXCOORD5;
	#endif
};

若是不肯定在哪裏計算比較好,能夠同時支持這兩種方法。假設定義了BINORMAL_PER_FRAGMENT,咱們逐像素計算每一個片斷的副法線。不然,逐頂點計算。在前一種狀況下,咱們保持咱們的float4 tangent變量 。在後者中,咱們須要兩個float3變量。

float3 CreateBinormal (float3 normal, float3 tangent, float binormalSign) {
	return cross(normal, tangent.xyz) *
		(binormalSign * unity_WorldTransformParams.w);
}

Interpolators MyVertexProgram (VertexData v) {
	Interpolators i;
	i.position = mul(UNITY_MATRIX_MVP, v.position);
	i.worldPos = mul(unity_ObjectToWorld, v.position);
	i.normal = UnityObjectToWorldNormal(v.normal);

	#if defined(BINORMAL_PER_FRAGMENT)
		i.tangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
	#else
		i.tangent = UnityObjectToWorldDir(v.tangent.xyz);
		i.binormal = CreateBinormal(i.normal, i.tangent, v.tangent.w);
	#endif

	i.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
	i.uv.zw = TRANSFORM_TEX(v.uv, _DetailTex);
	ComputeVertexLightColor(i);
	return i;
}

…

void InitializeFragmentNormal(inout Interpolators i) {
	float3 mainNormal =
		UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
	float3 detailNormal =
		UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
	float3 tangentSpaceNormal = BlendNormals(mainNormal, detailNormal);

	#if defined(BINORMAL_PER_FRAGMENT)
		float3 binormal = CreateBinormal(i.normal, i.tangent.xyz, i.tangent.w);
	#else
		float3 binormal = i.binormal;
	#endif

	i.normal = normalize(
		tangentSpaceNormal.x * i.tangent +
		tangentSpaceNormal.y * binormal +
		tangentSpaceNormal.z * i.normal
	);
}

要對全部Pass塊生效,須要使用CGINCLUDE … ENDCG包含

2 原文

支持原做者!

相關文章
相關標籤/搜索