《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]之間,對映:
利用法線紋理時需要反對映過程,即
用的是切線空間法線紋理。對於模型每個頂點擁有一個屬於自己的切線空間,切線的空間原點是該頂點本身,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通道