1. 程式人生 > >《Unity Shader入門精要》總結 #第七章 基礎紋理

《Unity Shader入門精要》總結 #第七章 基礎紋理

使用紋理對映可以將一張圖逐紋素地控制模型顏色。

紋理大小可變,但頂點UV座標範圍通常被歸一化道[0,1]範圍內

1、單張紋理

1.1 實踐

【補充一下第三章Properties語義塊支援的屬性型別,之前忘記寫自己還老分不清- -】

Properties{
    Name("display name", PropertyType) = DefaultValue
}
屬性型別 預設值的定義語法 例子
Int number _Int("Int", Int) = 2
Float number _Float("Float", Float) = 1.5
Range(min, max) number _Range("Range", Range(0.0, 5.0)) = 3.0
Color (number, number, number, number) _Color("Color", Color) = (1, 1, 1, 1)
Vector (number, number, number, number) _Vector("Vector", Vector) = (2, 3, 6, 1)
2D "defaulttexture" {} _2D("2D", 2D) = ""{}
Cube "defaulttexture" {} _Cube("Cube", Cube) = "white"{}
3D "defaulttexture" {} _3D("3D", 3D) = "black"{}
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter7/SingleTexture" {
	Properties{
		_Color("Color Tint", Color) = (1,1,1,1)		//控制物體整體色調
		_MainTex("Main Tex", 2D) = "white"{}
		_Specular("Specular", Color) = (1,1,1,1)
		_Gloss("Gloss", Range(8.0, 256)) = 20
	}
	SubShader{
		Pass{
			Tags{"LightMode" = "ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;	//紋理名_ST,宣告某個紋理的屬性,得到縮放和平移值
								//_MainTex_ST.xy儲存縮放值,即tilling,_MainTex_ST.zw存放偏移值即offset
			fixed4 _Specular;
			float _Gloss;

			struct a2v{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};

			struct v2f{
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};

			v2f vert(a2v v){
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				//o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

				return o;
			}

			fixed4 frag(v2f i) : SV_Target{
				fixed3 worldNormal = (i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;	//對紋理取樣,需要被取樣的紋理,float2型的紋理座標
																		//反射率albedo
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				return fixed4(ambient + diffuse + specular, 1.0);
			}

			ENDCG
		}
		
	}
	Fallback "Specular"
}

另外關於o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;這一句程式碼我自己本身卡了很久。因為我認為v.texcoord.xy * _MainTex_ST.xy是兩個float2相乘,如果將float2看成一個1*2的矩陣,則(1,2)*(1,2)的矩陣無法相乘。後來仔細想了一下,我認為可能是我的理解有誤,這裡的float2*float2的意義應該與dot函式是不同的,而*代表的應該是對應位相乘,即v.texcoord.x*_MainTex_ST.x,v.texcoord.y*_MainTex_ST.y,最終得到的還是float2值。

另外一提,_MainTex_ST.x代表的是面板裡的tilling值,_MainTex_ST.y代表的面板裡的是offset值

1.2 紋理的屬性

-Alpha from Grayscale,勾選後透明通道的值將由每個畫素的灰度值生成。

-Wrap Mode,當紋理座標超過[0,1]後如何被平鋪。兩種模式:Repeat,超過1後整數部分捨棄,對小數部分取樣(不斷重複);另一種是Clamp,紋理座標大於1則擷取到1,小於0擷取到0

想要得到則必須使用紋理屬性:_MainTex_ST

-Filter Mode,紋理由於變換產生拉伸時採用哪種濾波模式,提供:Point,Bilinear,Trilinear,濾波效果依次提升但消耗效能逐增大。

-多級漸遠紋理技術,物體遠離攝像機時採用較小紋理,但需要一定的空間用於儲存這些多級漸遠紋理多33%記憶體,空間換時間。

勾選Advanced,再勾選Generate Mip Maps

使用2的冪大小紋理

-Advanced,對於一些不需要使用很高精度的紋理(例如用於漫反射顏色的紋理),應儘量使用壓縮格式

2、凹凸對映

使用一張紋理修改模型表面的法線

兩種方法:高度紋理模擬表面位移,得到一個修改後的法線值,此方法被稱為高度對映;

                  使用法線紋理儲存表面法線,此方法被稱為法線對映;

2.1 高度紋理

利用高度圖(height map),儲存的是強度值intensity,顏色越前表明該位置表面越向外凸起。

優點:觀察直觀 缺點:計算複雜。

通常與法線對映一起使用,用於給出表面凹凸的額外資訊,通常使用法線對映修改光照

2.2 法線紋理

發現方向範圍在[-1,1]之間,畫素分量範圍在[0,1]之間,對映:

pixel=\tfrac{normal+1}{2}

利用法線紋理時需要反對映過程,即normal=pixel*2-1

用的是切線空間法線紋理。對於模型每個頂點擁有一個屬於自己的切線空間,切線的空間原點是該頂點本身,z軸是頂點的法線方向(n),x軸是頂點的切線方向(t),y軸是法線和切線叉積而得即副切線/副法線

模型空間下紋理五顏六色,每個點儲存的發現方向各異。而切線空間下幾乎全部為淺藍色,因為每個法線所在座標空間不一樣,。一個點的法線方向不變,則在切線空間中新的法線方向為z軸,(0,0,1),經過對映後為RGB(0.5,0.5,1)即淺藍色

模型空間儲存優點:

-實現簡單,直觀。想要得到效果比較好的法線對映要求紋理對映頁也連續

-紋理座標的縫合處和尖銳的邊角部分,可見的突變較少,可以提供平滑的便捷,而切線空間下的資訊依靠紋理座標的方向得到結果,可能會在邊緣處或尖銳部分造成更多可見的縫合跡象。

切線空間儲存優點:

-自由度高。非絕對法線資訊。

-可進行UV動畫。

-可重用法線紋理。

-可壓縮。儲存XY得到Z。

2.3 實踐

法線紋理儲存的切線方向,可以選擇在切線空間下進行光照計算,或是在世界空間下進行。第一種上優於第二種,因為可以在VS中就完成光照方向和視角方向的轉換;而第二種要先進行紋理取樣,變換過程在FS中進行。但第二種方法由於第一種,因為有時需要在世界空間下進行一些計算(Cubemap進行環境對映)

-在切線空間下計算

切線x軸、副切線y軸、法線z軸按列排列就是切線轉換矩陣

Shader "Unity Shaders Book/Chapter7/NormalMapTangentSpace" {
	Properties{
		_Color("Color Tint", Color) = (1.0,1.0,1.0,1.0)
		_MainTex("Main Tex", 2D) = "white"{}
		_Gloss("Gloss", Range(8.0, 256)) = 20
		_Specular("Specular", Color) = (1.0,1.0,1.0,1.0)
		_BumpMap("Normal Map", 2D) = "bump" {}
		_BumpScale("Bump Scale", Float) = 1.0 //控制凹凸程度
	}
	SubShader{
		Pass{
			Tags{"LightMode" = "ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc" 

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float4 _BumpMap_ST;
			fixed4 _Specular;
			float _Gloss;
			float _BumpScale;

			struct a2v{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
			};

			struct v2f{
				float4 pos : SV_POSITION;
				float4 uv : TEXCOORD0;
				float3 lightDir : TEXCOORD1;//注意TEXCOORD是float2/float4
				float3 viewDir : TEXCOORD2;
			};

			v2f vert(a2v v){
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
				//float3 bionormal = dot(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;	//w值決定叉乘的方向
				//float3x3 rotation = float3x3(v.tangent.xyz, bionormal, v.normal);
				TANGENT_SPACE_ROTATION;
				o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
				o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

				return o;
			}

			fixed4 frag(v2f i) : SV_Target{
				fixed3 tangentLightDir = normalize(i.lightDir);
				fixed3 tangentViewDir = normalize(i.viewDir);

				fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);//圖,紋理座標
				fixed3 tangentNormal;
				//if the texture is not marked as "Normal map"
				//tangentNormal.xy = (packedNormal.xy*2-1)*_BumpScale;
				//tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));//計算z分量
				//or marked as "Normal Map", use the built-in function
				tangentNormal = UnpackNormal(packedNormal);
				tangentNormal.xy *= _BumpScale;
				tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
				fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
				return fixed4(ambient + diffuse + specular, 1.0);

			}


			ENDCG
		}

	}
	FallBack "Specular"
}

tex2D函式來計算當前畫素的具體值(tex2D作用僅僅是通過一個二維uv座標在紋理上獲取該處值,根據紋理的型別不同,獲取的值的含義也不一樣,比如bump型別紋理上儲存的值代表的含義是該點的法向量,而普通紋理一般代表的是該點的顏色值),具體過程是根據對應uv座標(IN.uv_Bump)在bump型別紋理(_Bump)上得到該點儲存的值,注意此時得到的值是一個fixed4的值,但是法向值只需要fixed3型別,所以需要UnpackNormal進行轉換(Unity3d的標準法線解壓函式 — fixed3 UnpackNormal(fixed4 packednormal))

-在世界空間下計算

Shader "Unity Shaders Book/Chapter7/NormalMapWorldSpace" {
	Properties{
		_Color("Color Tint", Color) = (1.0,1.0,1.0,1.0)
		_MainTex("Main Tex", 2D) = "white"{}
		_Gloss("Gloss", Range(8.0, 256)) = 20
		_Specular("Specular", Color) = (1.0,1.0,1.0,1.0)
		_BumpMap("Normal Map", 2D) = "bump" {}
		_BumpScale("Bump Scale", Float) = 1.0 //控制凹凸程度
	}
	SubShader{
		Pass{
			Tags{"LightMode" = "ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc" 

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _BumpMap;
			float4 _BumpMap_ST;
			fixed4 _Specular;
			float _Gloss;
			float _BumpScale;

			struct a2v{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
			};

			struct v2f{
				float4 pos : SV_POSITION;
				float4 uv : TEXCOORD0;
				float4 TtoW0 : TEXCOORD1;
				float4 TtoW1 : TEXCOORD2;
				float4 TtoW2 : TEXCOORD3;
			};

			v2f vert(a2v v){
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
				//float3 bionormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;	//w值決定叉乘的方向
				//float3x3 rotation = float3x3(v.tangent.xyz, bionormal, v.normal);
				float3 worldPos = UnityObjectToClipPos(v.vertex).xyz;
				fixed3 worldNormal = UnityObjectToWorldNormal(v.vertex);
				fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
				fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

				o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
				o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
				o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

				return o;
			}

			fixed4 frag(v2f i) : SV_Target{
				float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
				fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

				fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
				bump.xy *= _BumpScale;
				bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
				bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));

				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
				fixed3 halfDir = normalize(lightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
				return fixed4(ambient + diffuse + specular, 1.0);

			}


			ENDCG
		}

	}
	FallBack "Specular"
}

2.4 Unity中的法線紋理型別

當把發現紋理的紋理型別標識成Normal Map時,可以利用內建函式UNpackNormal得到正確的法線方向。

選成Normal Map後有一個複選框是Create from Grayscale,是從高度圖生成法線紋理。勾選Create from Grayscale後,還有Bumpinss和Filtering,Bumpiness控制凹凸程度,Filtering決定使用哪種方式計算凹凸程度(Smooth和Sharp)

7.3 漸變紋理

卡通紋理

Shader "Unity Shaders Book/Chapter7/RampTexture" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_RampTex ("Ramp Tex", 2D) = "white" {}
		_Gloss ("Gloss", Range(8,256)) = 20
		_Specular("Specular", Color) = (1,1,1,1)
	}
	SubShader {
		Pass{
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _RampTex;
			float4 _RampTex_ST;
			float _Gloss;
			fixed4 _Specular;

			struct a2v{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};

			struct v2f{
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};

			v2f vert(a2v v){
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = UnityObjectToWorldNormal(v.vertex);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);//UnityObjectToWorldDir(v.vertex).xyz;////
				o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);

				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldNormal));
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
				fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb; //因為縱座標顏色不變化,用一樣的,實際上是一維紋理

				fixed3 diffuse = _LightColor0.rgb * diffuseColor;

				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
			
				return fixed4(ambient + diffuse + specular, 1.0);

			}



			ENDCG
		}
	}
	FallBack "Specular"
}

將Wrap Mode設定為Clamp模式,因為即使halfLambert的值在[0,1]之間,也依舊可能會有1.00001這樣的情況,此時取0.00001就會出現黑色情況。

4、遮罩紋理

4.1 實踐

逐畫素控制模型表面的高光反射

Shader "Unity Shaders Book/Chapter7/MaskTexture" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_Gloss ("Smoothness", Range(0,1)) = 0.5
		_BumpMap("Bump Map", 2D) = "bump"{}
		_BumpScale("Bump Scale", Float) = 1.0
		_Specular("Specular", Color) = (1,1,1,1)
		_SpecularMask("Specular Mask", 2D) = "white"{}
		_SpecularScale("Specular Scale", Float) = 1.0
	}
	SubShader {
		Pass{
			Tags{"LightMode" = "ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _Gloss;
			sampler2D _BumpMap;
			float _BumpScale;
			fixed4 _Specular;
			sampler2D _SpecularMask;
			float _SpecularScale;


			struct a2v{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
			};

			struct v2f{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
				float3 lightDir : TEXCOORD1;
				float3 viewDir : TEXCOORD2;
			};

			v2f vert(a2v v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

				TANGENT_SPACE_ROTATION;
				o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
				o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));

				return o;
			}

			fixed4 frag(v2f i) : SV_Target{
				fixed3 tangentLightDir = normalize(i.lightDir);
				fixed3 tangentViewDir = normalize(i.viewDir);

				fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
				tangentNormal.xy = tangentNormal.xy * _BumpScale;
				tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));

				fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
				fixed3 specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;

				return fixed4(ambient + diffuse + specular, 1.0f);

			}
		
			ENDCG
		}
	}
	FallBack "Specular"
}

通常高光反射儲存與R通道,邊緣光照強度儲存於G通道,高光反射的指數部分儲存在B通道,自發光強度儲存在A通道