一.Unity的紋理概念
紋理最基礎(chǔ)的目的:用一張圖片控制模型的外觀(guān)搞乏。使用紋理映射(texture mapping)技術(shù)慢哈,我們可以把一張圖“粘”在模型表面匣砖,逐紋素(texel)(名字是為了和逐像素進(jìn)行區(qū)分)的控制模型的顏色布朦。
建模軟件中利用紋理展開(kāi)技術(shù)把紋理映射坐標(biāo)存儲(chǔ)在每個(gè)頂點(diǎn)上。如下圖
UV坐標(biāo)會(huì)被歸一化到[0, 1]范圍內(nèi)。
OpenGL和DirectX在二維紋理空間的坐標(biāo)有差異暂筝。OpenGL原點(diǎn)位于左下角箩言,DirectX位于左上角,Unity默認(rèn)OpenGL模式焕襟。
二.單張紋理.FilterMode屬性
A.單張紋理應(yīng)用
通常使用一張紋理來(lái)代替物體的漫反射顏色
紋理名_ST表示聲明某個(gè)紋理的屬性陨收,name##_ST.xy 存儲(chǔ)縮放值,name##_ST.zw存儲(chǔ)偏移值鸵赖。
結(jié)合之前的光照模型的單張紋理應(yīng)用:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
Shader "Single Texture" {
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;
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 = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
// Or just call the built-in function
// o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
// Use the texture to sample the diffuse color
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
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"
}
要點(diǎn)1:tex2D函數(shù)表示對(duì)紋理進(jìn)行采樣务漩,param1是需要被采樣的紋理,param2是float2類(lèi)型的紋理坐標(biāo)它褪,返回計(jì)算得到的紋素值饵骨。
要點(diǎn)2:TRASFORM_TEX實(shí)在UnityCG.cginc中定義的:
//Transform 2D UV by scale/bias property
//param1是頂點(diǎn)的紋理坐標(biāo)
//param2是紋理的名稱(chēng)
#define TRANSFORM_TEX(tex,name)(tex.xy * name##_ST.xy + name##_ST.zw)
效果如下圖:
B.紋理的屬性
為了闡釋紋理的屬性,編寫(xiě)一個(gè)更為純粹的紋理屬性應(yīng)用shader
Shader "TextureProperties" {
Properties {
_MainTex ("Main Tex", 2D) = "white" {}
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.position = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed4 c = tex2D(_MainTex, i.uv);
return fixed4(c.rgb, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
用這個(gè)shader生成一個(gè)紋理圖如下:
1.Wrap Mode屬性
決定紋理坐標(biāo)超過(guò)[0,1]范圍后使用哪種平鋪方式列赎。
a.Repeat——重復(fù)紋理采樣宏悦。
這種模式下镐确,如果紋理的坐標(biāo)超過(guò)了1包吝,那么整數(shù)部分將會(huì)被舍棄,而直接使用小數(shù)部分進(jìn)行采用源葫。
b.Clamp模式時(shí)诗越,超過(guò)的部分將會(huì)截取到邊界值,從而形成一個(gè)條形結(jié)構(gòu)息堂。如下圖:
2.FilterMode屬性
決定了當(dāng)紋理由于變換而產(chǎn)生拉伸時(shí)會(huì)使用哪種濾波模式嚷狞。
三種模式:Point模式块促、Bilinear濾波、Trilinear濾波
a.Point模式使用了最近鄰濾波床未,在放大或縮小時(shí)竭翠,它的采樣像素?cái)?shù)目通常只有一個(gè),因此圖像看起來(lái)有種像素風(fēng)格的效果薇搁。
b.Bilinear濾波采用了線(xiàn)性濾波斋扰,對(duì)于每個(gè)目標(biāo)像素,它會(huì)找4個(gè)鄰近的像素啃洋,然后對(duì)他們進(jìn)行線(xiàn)性插值混合后得到最終的像素传货,因此圖像看起來(lái)像被模糊了。
c.Trilinear濾波幾乎和Bilinear是一樣的宏娄,只是Trilinear還會(huì)在多級(jí)漸遠(yuǎn)紋理(mipmapping)之間進(jìn)行混合问裕。
效果如下:
三.凹凸映射
目的:使用一張紋理來(lái)修改模型表面的發(fā)現(xiàn)。
方法:1.高度映射 : 使用高度紋理來(lái)模擬表面位移,后得到修改后的法線(xiàn)值孵坚。
2.法線(xiàn)映射:使用一張法線(xiàn)紋理直接存儲(chǔ)表面法線(xiàn)粮宛。
1.法線(xiàn)映射
法線(xiàn)紋理中存儲(chǔ)的就是表面的法線(xiàn)方向,由于法線(xiàn)的方向的分量范圍在[-1,1]卖宠,而像素的分量范圍為[0,1]窟勃,因此我們需要做一個(gè)映射:
pixel =0.5*normal + 0.5
實(shí)際使用:normal = pixel * 2 - 1
模型空間法線(xiàn)紋理:對(duì)于模型自帶的表面法線(xiàn),修改后存儲(chǔ)在此紋理中逗堵。
切線(xiàn)空間法線(xiàn)紋理:對(duì)于模型的每個(gè)定點(diǎn)都有一個(gè)屬于自己的切線(xiàn)空間秉氧,這個(gè)切線(xiàn)空間的原點(diǎn)就是該定點(diǎn)的本身,而z軸是定點(diǎn)的法線(xiàn)方向蜒秤,x軸是切線(xiàn)方向汁咏,而y軸有法線(xiàn)和切線(xiàn)的叉積得到,也被稱(chēng)為是副切線(xiàn)或副法線(xiàn)作媚。
左邊的模型空間法線(xiàn)之所以是五顏六色的漂问,因?yàn)樗蟹ň€(xiàn)所在坐標(biāo)空間是在模型空間,x/y/z區(qū)間都在0到1之間女揭,所以能顯示彩色蚤假。
切線(xiàn)空間下的法線(xiàn)紋理幾乎是淺藍(lán)色,因?yàn)榘赏茫總€(gè)法線(xiàn)方向所在的坐標(biāo)空間是不一樣的磷仰,即是表面各點(diǎn)各自的切線(xiàn)空間。這種法線(xiàn)紋理其實(shí)就是存儲(chǔ)了每個(gè)點(diǎn)在各自的切線(xiàn)空間中的法線(xiàn)擾動(dòng)方向境蔼。
2.實(shí)踐代碼
由于法線(xiàn)紋理存儲(chǔ)的法線(xiàn)是切線(xiàn)空間下的方向灶平,有兩種選擇:
1.在切線(xiàn)空間下計(jì)算光照伺通,需要把光照方向、視角方向變換到切線(xiàn)空間逢享。
2.都在世界空間進(jìn)行光照計(jì)算罐监。
下面是選擇世界空間下計(jì)算光照模型的代碼。
Shader "NormalMapWorldSpace" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_BumpMap ("BumpMap",2D) = "white"{}
_BumpScale("BumpScale",Range(-2.0,2.0)) = 1.0
_Specular("Specular",Color)=(1,1,1,1)
_Diffuse("Diffuse",Color)=(1,1,1,1)
_Gloss("Gloss",Range(8,256)) = 40
}
SubShader {
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex; float4 _MainTex_ST;
sampler2D _BumpMap; float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
fixed4 _Diffuse;
float _Gloss;
struct a2v
{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
float4 tangent:TANGENT;
};
struct v2f
{
float4 pos:SV_POSITION;
float4 TtoW0:TEXCOORD0;
float4 TtoW1:TEXCOORD1;
float4 TtoW2:TEXCOORD2;
float4 uv:TEXCOORD3;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 worldPos = mul(_Object2World,v.vertex).xyz;
float3 worldNormal = mul(v.normal,(float3x3)_World2Object);
//這里要怎樣計(jì)算瞒爬?有兩種方式
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
//float3 worldTangent = mul((float3x3)_Object2World,v.tagent);
//這里要怎樣計(jì)算笑诅?
//叉積求得
float3 worldBionormal = cross(worldNormal,worldTangent) * v.tangent.w;
//計(jì)算了世界空間下頂點(diǎn)切線(xiàn)、副切線(xiàn)和法線(xiàn)的表示疮鲫,并把它們按列擺放得到從切線(xiàn)空間到世界空間的變換矩陣
//把該矩陣的每一行分別存放在TtoW0吆你、TtoW1、TtoW2中
//最后把世界空間下頂點(diǎn)位置x俊犯、y妇多、z分量分別存儲(chǔ)在這些變量的w分量中
o.TtoW0 = float4(worldTangent.x,worldBionormal.x,worldNormal.x,worldPos.x);
o.TtoW1 = float4(worldTangent.y,worldBionormal.y,worldNormal.y,worldPos.y);
o.TtoW2 = float4(worldTangent.z,worldBionormal.z,worldNormal.z,worldPos.z);
//o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpMap);
return o;
}
fixed4 frag(v2f i):SV_Target
{
//i.uv.xy
fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);
float3 worldViewDir =normalize( UnityWorldSpaceViewDir(worldPos) );
float3 worldLightDir =normalize( UnityWorldSpaceLightDir(worldPos) );
//不需要這個(gè)值了,所有法線(xiàn)信息都是用bumpMap中的Q嘞馈U咦妗!包括漫反射的計(jì)算绢彤!
float3 worldNormal = normalize( float3(i.TtoW0.z,i.TtoW1.z,i.TtoW2.z) );
//UnpackNormal對(duì)法線(xiàn)紋理進(jìn)行采樣和解碼(需要把法線(xiàn)紋理標(biāo)識(shí)成Normal map)
fixed3 bump = UnpackNormal(tex2D(_BumpMap,i.uv.zw));
//倘若_BumpScale為0七问,那么bump=float3(0,0,1)就相當(dāng)于入射表面沒(méi)有任何擾動(dòng)
bump.xy *= _BumpScale;
//需要保證經(jīng)過(guò)處理的法線(xiàn)仍然是歸一化的單位向量
//dot計(jì)算時(shí),對(duì)應(yīng)的分量相乘然后相加茫舶,dot(bump.xy,bump.xy)=x*x + y*y
bump.z = sqrt(1.0- saturate( dot(bump.xy,bump.xy) ));
//將法線(xiàn)從切線(xiàn)空間轉(zhuǎn)換到世界空間械巡,這里是在模擬bump左乘【切線(xiàn)空間到世界空間的轉(zhuǎn)換矩陣】
//我潛意識(shí)中兩種種錯(cuò)誤的寫(xiě)法
//bump = normalize(float3(bump.x * i.TtoW0.xyz,bump.y*i.TtoW1.xyz,bump.z*i.TtoW2.xyz));
//bump = normalize(float3(i.TtoW0.xyz * bump,i.TtoW1.xyz * bump,
//正確寫(xiě)法,原來(lái),單個(gè)向量默認(rèn)相當(dāng)于列向量
bump = normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));
//計(jì)算漫發(fā)射時(shí)同樣適用BumpMap中的法線(xiàn)信息饶氏!
fixed3 diffuse = _LightColor0.rgb * albedo* saturate(dot(bump,worldLightDir));
float3 halfDir = normalize(worldViewDir + worldLightDir);
//一定千萬(wàn)要注意讥耗,前面廢了好大的勁,就是為了在這一步計(jì)算高光時(shí)能用上U钇簟9懦獭!
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(bump,halfDir)),_Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
基本思路:
由于我們只能在片元著色器中獲得bumpMap的紋素信息喊崖,所以我們需要在片元著色器中使用切線(xiàn)空間到世界空間的轉(zhuǎn)換矩陣挣磨。首先在頂點(diǎn)著色器中獲得切線(xiàn)空間各個(gè)坐標(biāo)軸在世界空間中的表示,副切線(xiàn)副通過(guò)轉(zhuǎn)換好的worldNormal與worldTangent的叉積獲得荤懂,注意方向靠v.tangent的w分量來(lái)確定茁裙。在v2f結(jié)構(gòu)體聲明了TtoW0、TtoW1势誊、TtoW2三個(gè)紋理寄存器呜达,用來(lái)分別將存放轉(zhuǎn)換矩陣的每一行谣蠢。由于需要在片元著色器用到worldPos來(lái)計(jì)算worldViewDir以及worldLightDir粟耻,所以需要向片元著色器傳遞該信息查近,傳遞方式為將該變量的三個(gè)分量分別存放到TtoW0、TtoW1挤忙、TtoW2的w分量中霜威。
效果如下圖
四.漸變紋理
之前計(jì)算漫反射光照,都是使用表面法線(xiàn)和光照方向的點(diǎn)積結(jié)果與材質(zhì)的反射率相乘册烈。漸變紋理是使用一張漸變紋理來(lái)控制漫反射光照戈泼。
Shader "Ramp Texture" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_RampTex ("Ramp 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 _RampTex;
float4 _RampTex_ST;
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 = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, 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.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Use the texture to sample the diffuse color
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"
}
利用半蘭伯特構(gòu)建float2變量,以此對(duì)一張漸變紋理進(jìn)行采樣赏僧。漫反射的顏色靠此方式獲得大猛。
效果圖:
五.遮罩紋理
使用遮罩紋理的流程:通過(guò)采樣得到的遮罩紋理的紋素值,然后使用其中某個(gè)(或某幾個(gè))通道的值(如texel.r)來(lái)與某種表面屬性進(jìn)行相乘淀零,這樣挽绩,當(dāng)該通道的值為0時(shí),可以保護(hù)表面不受該屬性的影響驾中。
遮罩紋理的使用是對(duì)高光效果進(jìn)行遮罩唉堪,使用同一組uv坐標(biāo),對(duì)同一位置處的遮罩紋理進(jìn)行采樣肩民,這里只使用了r分量唠亚。
世界空間下高光遮罩紋理實(shí)現(xiàn):
Shader "MaskTexWorld" {
Properties {
_Color ("ColorTint", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Gloss ("Gloss", Range(8,256)) = 20
_BumpMap("BumpMap",2D) = "white"{}
_BumpScale("BumpScale",Range(-1,1)) = 1
_Specular("Specular",Color) = (1,1,1,1)
_SpecularMask("SpecularMask",2D) = "white"{}//高光反射遮罩紋理
_SpecularScale("SpecularScale",float) = 1.0//控制遮罩影響度的系數(shù)
}
SubShader {
Pass
{
Tags{"LgihtMode" = "ForwarBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#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;
//注意因?yàn)槲覀円褂胻angent的w分量,所以這里的類(lèi)型是float4
float4 texcoord:TEXCOORD0;
float4 tangent:TANGENT;
};
struct v2f
{
float4 pos:SV_POSITION;
float4 TtoW0:TEXCOORD0;
float4 TtoW1:TEXCOORD1;
float4 TtoW2:TEXCOORD2;
float2 uv:TEXCOORD3;
};
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
float3 worldNormal = normalize( UnityObjectToWorldNormal(v.normal) );
float3 worldTangent = normalize( UnityObjectToWorldDir(v.tangent.xyz) );
float3 worldBionormal = cross(worldNormal,worldTangent)*v.tangent.w;
float3 worldPos = mul(_Object2World,v.vertex);
o.TtoW0 = float4(worldTangent.x,worldBionormal.x,worldNormal.x,worldPos.x);
o.TtoW1 = float4(worldTangent.y,worldBionormal.y,worldNormal.y,worldPos.y);
o.TtoW2 = float4(worldTangent.z,worldBionormal.z,worldNormal.z,worldPos.z);
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
return o;
}
fixed4 frag(v2f i):SV_Target
{
float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);
float3 worldLightDir =normalize(UnityWorldSpaceLightDir(worldPos));
float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float3 bump = UnpackNormal(tex2D(_BumpMap,i.uv));
bump.xy *= _BumpScale;
bump.z = sqrt(1-saturate(dot(bump.xy,bump.xy)));
float3 worldNormal = normalize( float3(dot(bump,i.TtoW0.xyz),dot(bump,i.TtoW1.xyz),dot(bump,i.TtoW2.xyz)) );
fixed3 albedo = tex2D(_MainTex,i.uv).rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal,worldLightDir));
//通過(guò)uv對(duì)MaskTex對(duì)應(yīng)位置進(jìn)行采樣
//所謂的 specularMask 實(shí)際上只利用采樣紋理的一個(gè)分量通道持痰,這里僅僅利用r
fixed specularMask = tex2D(_SpecularMask,i.uv).r * _SpecularScale;
fixed3 halfDir = normalize(worldLightDir + worldViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal,halfDir)),_Gloss) * specularMask;
return fixed4(ambient + diffuse + specular,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
效果如下