存儲在每個(gè)頂點(diǎn)上沪曙。紋理映射坐標(biāo)定義了該頂點(diǎn)在紋理中對應(yīng)的 2D 坐標(biāo)邑蒋。通常姓蜂,這些坐標(biāo)使用一個(gè)二維變量(u, v)來表示,其中u 是橫向坐標(biāo)医吊,而v 是縱向坐標(biāo)钱慢。因此,紋理映射坐標(biāo)也被稱為UV 坐標(biāo)卿堂。
盡管紋理的大小可以是多種多樣的束莫,例如可以是256 x256 或者1028 x 1028,但頂點(diǎn)UV 坐標(biāo)的范圍通常都被歸一化到[0御吞,,1]范圍內(nèi)麦箍。需要注意的是,紋理采樣時(shí)使用的紋理坐標(biāo)不一定是在[0, 1]范圍內(nèi)。實(shí)際上敌完,這種不在[0, 1 ]范圍內(nèi)的紋理坐標(biāo)有時(shí)會非常有用属愤。與之關(guān)系緊密的是紋理的平鋪模式,它將決定渲染引擎在遇到不在[0, 1 ]范圍內(nèi)的紋理坐標(biāo)時(shí)如何進(jìn)行紋理來樣诀蓉。
還是直接上代碼吧
Shader "xxxx"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1)
_MainTex("Main Tex", 2D) = "white" {} // 聲明一個(gè)_MainTex紋理,
_Specular ("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(8.0, 256)) =20 //高光反射區(qū)域范圍大小
}
SubShader{
Pass{
Tags{ "LightMode" = "ForwardBase" } // 光照流水線的角色
CGPROGRAM //常規(guī) 操作聲明
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" //導(dǎo)入 內(nèi)置文件
fixed4 _Color; // 這些變量就是 對外屬性的 聲明暑脆,以便內(nèi)部使用
sampler2D _MainTex; // 其他變量和 光照的差不多渠啤,這個(gè)是圖片紋理,
//我們需要使用 紋理名_ST 的方式來聲明某個(gè)紋理的屬性添吗。其中沥曹, ST 是縮放( scale )
//和平移(translation ),_MainTex_ST.xy 存儲的是縮放值碟联,而
// _MainTex ST.zw 存儲的是偏移值妓美。這些值可以在材質(zhì)面板的紋理屬性中調(diào)節(jié)
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;
struct a2v{
float4 vertex : POSITION;
float3 normal :NORMAL;
float4 texcoord: TEXCOORD0; //存儲第一組紋理坐標(biāo)
};
struct v2f {
float4 pos: SV_POSITION;
float3 worldNormal :TEXCOORD0;
float3 worldPos:TEXCOORD1鲤孵;
float2 uv:TEXCOORD2壶栋; // 用這個(gè)值對紋理進(jìn)行采樣
}
v2f vert (a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(_Object2World, v.vertex).xyz;
o.uv = v.texcoord.xy * _MainTex_ST.xy +_MainTex_ST.zw;
//先xy對圖形縮放, 用zw 偏移量 相加 進(jìn)行偏移 得出最終的紋理坐標(biāo)
//有內(nèi)置的函數(shù)可以用
//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));
//用uv 偏移量來對紋理采樣普监, 和顏色相乘 作為材質(zhì)反射率 albedo
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
// 環(huán)境光和漫反射都用了 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);
}
FallBack "Specular"
}
}
}
上面代碼中的反射率 計(jì)算過程 有點(diǎn)意思
用頂點(diǎn)得出的uv值 對紋理采樣 用得到的rgb 顏色值和 屬性_Color乘積作為材質(zhì)的 反射率 albedo
就是說 顏色屬性影響到了折射率贵试。顏色乘積當(dāng)做反射率
TextureMode 兩種 repeat就是不斷重復(fù) 這個(gè)clamp 需要注意 是邊界值不斷重復(fù)琉兜,就邊界處顏色不斷填充。
有一種是Clamp 毙玻, 在這種模式下豌蟋,如果紋理坐標(biāo)大于1 ,那么將會截取到1桑滩,如果小于0 夺饲,那么將會截取到0.
但是這種表現(xiàn)的實(shí)現(xiàn)也是要依靠shader的計(jì)算
重要的就是 _MainTex_ST 的xy 和zw 值 上代碼 這幾個(gè)值可以在界面調(diào)整
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);
FilterMode 是濾波模式選項(xiàng) 3種
Point,Bilinear和Triliner施符。效果依次提升往声,性能也依次增大。內(nèi)存會增加戳吝。 一般point就夠用了浩销。
有些時(shí)候也用Bilinear模式。這里注意如果沒有開minmap Bilinear 和 Triliner 是一樣效果
MinMap 技術(shù) (多級漸遠(yuǎn)紋理) 除非移動3D場景有透視效果一般不啟用听哭,占內(nèi)存慢洋!
其實(shí)就是一個(gè)分級采樣,原圖生成小圖使用陆盘,近處大圖使用普筹。 一種空間換時(shí)間的方法。 多33%的內(nèi)存空間隘马。
FilterMode濾波實(shí)現(xiàn)細(xì)節(jié)
- Point 模式使用了最近鄰( nearest neighbor ) 濾波太防,在放大或縮小時(shí),它的采樣像素?cái)?shù)目通常只有一個(gè)酸员,因此圖像會看起來有種像素風(fēng)格的效果蜒车。
- 而Bilinear 濾波則使用了線性濾波,對于每個(gè)目標(biāo)像素幔嗦,它會找到4 個(gè)鄰近像素酿愧,然后對它們進(jìn)行線性插值混合后得到最終像素,因此圖像看起來像被模糊了邀泉。
- 而Trilinear 濾波幾乎是和Bilinear 一樣的嬉挡,只是Trilinear還會在多級漸遠(yuǎn)紋理之間進(jìn)行混合。如果一張紋理沒有使用多級漸遠(yuǎn)紋理技術(shù)汇恤,那么Trilinear 得到的結(jié)果是和Bilinear 就一樣的庞钢。
通常,我們會選擇Bilinear 濾波模式屁置。需要注意的是焊夸,有時(shí)我們不希望紋理看起來是模糊的仁连,例如對于一些類似棋盤的紋理蓝角,我們希望它就是像素風(fēng)的阱穗,這時(shí)我們可能會選擇Point 模式。
凹凸映射 (Bump mapping)
其實(shí)就是一個(gè)法線貼圖使鹅, 用紋理值改變模型表現(xiàn)的法線揪阶,讓看起來有立體感。實(shí)現(xiàn)有兩種方法
- 使用一張高度紋理(height map)來模擬表面位移(displacement)患朱,然后會得到一個(gè)修改后的法線值鲁僚。這也叫 高度映射(height mapping)
- 用法線紋理(normal map)來直接存儲法線,這個(gè)法線一般是第三方軟件直接導(dǎo)出的裁厅。 這種一般稱做 法線映射(normal mapping)冰沙。這個(gè)用的比較多!
高度紋理
高度圖中存儲的是 強(qiáng)度值 (intensity)执虹,它用于表示模型表面的海拔高度拓挥, 顏色越淺表明該位置越向外凸起,而顏色越深表明該位置越向里凹袋励。
好處:就是直觀侥啤,便于觀察,
缺點(diǎn):計(jì)算更加復(fù)雜茬故,實(shí)時(shí)計(jì)算 不夠直接得到法線盖灸,通過灰度值計(jì)算獲得。需要更多性能磺芭。
高度圖通常會和法線映射一起使用赁炎,用于給出表面凹凸的額外信息。也就是說钾腺,我們通常會使用法線映射來修改光照
法線紋理
法線紋理中存儲的就是表面的法線方向甘邀。由于法線方向的分量范圍在[-1, 1 ],而像素的分量范圍為[0, 1]垮庐,因此我們需要做一個(gè)映射松邪,通常使用的映射就是:
所以在shader中要使用的話 需要一次反映射的過程。公式就簡單了
由于方向是相對于坐標(biāo)空間來說的哨查,那么法線紋理中存儲的法線方向在哪個(gè)坐標(biāo)空間中呢逗抑?對于模型頂點(diǎn)自帶的法線,它們是定義在模型空間中的寒亥,因此一種直接的想法就是將修改后的模型空間中的表面法線存儲在一張紋理中邮府,這種紋理被稱為是模型空間的法線紋理( object-space normal map )。然而溉奕,在實(shí)際制作中褂傀,我們往往會采用另一種坐標(biāo)空間,即模型頂點(diǎn)的切線空間
( tangent space )來存儲法線加勤。對于模型的每個(gè)頂點(diǎn)仙辟,它都有一個(gè)屬于自己的切線空間同波,這個(gè)切線空間的原點(diǎn)就是該頂點(diǎn)本身,而z 軸是頂點(diǎn)的法線方向(n), x 軸是頂點(diǎn)的切線方向(t)叠国,而y 軸可由法線和切線叉積而得未檩,也被稱為是副切線( bitangent, b )或副法線,如圖7.12 所示粟焊。
這種紋理被稱為是切線空間的法線紋理(tangent-space normal map ) 冤狡。圖7.13 分別給出了模型空間和切線空間下的法線紋理(圖片來源: http://www.surlybird.com/tutorials/TangentSpace/)。
平時(shí)使用的都是切線空間的法線貼圖项棠。
切線空間下的法線紋理看起來幾乎全部是淺藍(lán)色的悲雳。這是因?yàn)椋總€(gè)法線方向所在的坐標(biāo)空間是不一樣的香追,即是表面每點(diǎn)各自的切線空間怜奖。這種法線紋理其實(shí)就是存儲了每個(gè)點(diǎn)在各自的切線空間中的法線擾動方向。也就是說翅阵,如果一個(gè)點(diǎn)的法線方向不變歪玲,那么在它的切線空間中, 新的法線方向就是z 軸方向掷匠,即值為(0, 0, 1 ) 滥崩, 經(jīng)過映射后存儲在紋理中就對應(yīng)了RGB(0.5, 0.5, 1 )淺藍(lán)色。而這個(gè)顏色就是法線紋理中大片的藍(lán)色讹语。這些藍(lán)色實(shí)際上說明頂點(diǎn)的大部分法線是和模型本身法線一樣的钙皮,不需要改變。
故意進(jìn)行這樣的區(qū)別是因?yàn)槲覀儾恢皇切枰ň€信息顽决,還需要后續(xù)的光照信息短条。用切線空間就需要把法線轉(zhuǎn)向世界空間中。這樣做的模型空間存儲法線優(yōu)點(diǎn)有:
- 實(shí)現(xiàn)簡單才菠,直觀茸时,我們不需要模型原始的法線和切線信息,計(jì)算少赋访,生成也非常簡單可都。而如果要生成切線空間下的法線紋理,由于模型的切線一般是和UV 方向相同蚓耽,因此想要得到效果比較好的法線映射就要求紋理映射也是連續(xù)的渠牲。
- 在紋理坐標(biāo)的縫合處和尖銳的邊角部分,可見的突變(縫隙)較少步悠,即可以提供平滑的邊界签杈。這是因?yàn)槟P涂臻g下的法線紋理存儲的是同一坐標(biāo)系下的法線信息, 因此在邊界處通過插值得到的法線可以平滑變換鼎兽。而切線空間下的法線紋理中的法線信息是依靠紋理坐標(biāo)的方向得到的結(jié)果答姥,可能會在邊緣處或尖銳的部分造成更多可見的縫合跡象
但是 切線空間的存儲法線 優(yōu)點(diǎn)更多
- 自由度很高铣除,模型空間有局限性,只能是創(chuàng)建時(shí)的模型踢涌,別的模型會錯(cuò)。切線空間是相對法線信息序宦,把紋理用到別的模型上也是可以正常使用的睁壁。
- 可以UV 動畫,如果用模型空間互捌,法線紋理完全錯(cuò)誤潘明。但是這種uv動畫卻經(jīng)常用到。
- 可以重復(fù)利用法線紋理秕噪,一張圖可以用到6個(gè)方塊的面钳降。 原因同上
- 可壓縮, 有用切線空間下的法線紋理中 法線的z方向總是正方向腌巾。我們僅需要存儲 XY方向遂填,推導(dǎo)得到z方向。 模型空間是強(qiáng)行綁定每個(gè)方向都不相同澈蝙,不能壓縮吓坚。
實(shí)踐
在對應(yīng)的光照計(jì)算時(shí),有兩種坐標(biāo)系可以選擇灯荧,一個(gè)是切線空間礁击,一個(gè)是世界空間。第一種性能優(yōu)于第一種但是有局限性逗载,很多時(shí)候我們還需要環(huán)境映射和采樣哆窿。
切線空間
基本思路:在片元著色器通過紋理采樣得到切線空間的法線,與切線空間下的視角方向厉斟,光照方向進(jìn)行計(jì)算挚躯,得到最終結(jié)果。 首先需要在頂點(diǎn)著色器中把視角方向從模型轉(zhuǎn)切線擦秽。
上代碼吧
Shader "xxx"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white" {}
_BumpMap("Normal Map", 2D) = "bump" {} //法線貼圖 bump是unity內(nèi)置的法線紋理
_BumpScale("Bump Scale", Float) =1.0 // 控制凹凸程度秧均。0 時(shí)法線對高照沒有任何影響
_Specular ("Specular", Color) = (1,1,1,1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader{
Pass{
Tags { "LightMode" = "ForwardBase"}
··· 這段省略
//聲明變量
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
struct a2v{
float4 vertex :POSITION;
float3 normal :NORMAL;
//TANGENT 語義來描述float4 類型的tangent 變量号涯,以告訴Unity 把頂點(diǎn)的切線方向填充到tangent 變量中,tangent 的類型是float4目胡,而非float3, 這是因?yàn)槲覀冃枰褂胻angent.w 分量來決定切線空間中的第三個(gè)坐標(biāo)軸一一副切線的方向性。
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
}
struct v2f{
float4 pos :SV_POSITION;
float4 UV: TEXCOORD0链快;
float3 lightDir :TEXCOORD1誉己;
float3 viewDir: TEXCOORD2;
}
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// uv 的偏移采樣 分別存儲了兩組紋理坐標(biāo)域蜗,(實(shí)際上巨双,紋理和法線通常使用同一組紋理坐標(biāo)噪猾。 減少插值寄存器的使用數(shù)目,只計(jì)算和存儲一個(gè)紋理坐標(biāo)筑累,反正算法也是一樣的)
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//模型空間下切線方向袱蜡。副切線方向和法線方向按行排列來得到從模型空間到切線空間的變換矩陣 rotation
//float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz)*v.tangent.w);
//float3*3 rotation = float3*3(v.tangent.xyz, binormal, v.normal);
//可以直接使用unity內(nèi)置變量 獲得變換矩陣,算法是一樣的
TANGENT_SPACE_ROTATION;
//最后求出切線空間的 光線方向和視角方向 用了兩個(gè)內(nèi)置函數(shù)
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyzx;
}
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;
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);
}
}
}
切線空間法線的變量用TANGENT 語義來描述float4 類型的tangent 變量坪蚁,以告訴Unity 把頂點(diǎn)的切線方向填充到tangent 變量中,tangent 的類型是float4,而非float3, 這是因?yàn)槲覀冃枰褂胻angent.w 分量來決定切線空間中的第三個(gè)坐標(biāo)軸一一副切線的方向性镜沽。
有點(diǎn)沒看懂 片元的算法
世界空間下的計(jì)算
在頂點(diǎn)著色器中計(jì)算從切線空間變換到世界空間的變換矩陣敏晤,變換矩陣由頂點(diǎn)的切線,副切線和法線在世界空間下表示得到缅茉。這種方法需要更多計(jì)算嘴脾,但是在Cubemap進(jìn)行環(huán)境映射情況下用這種方法
代碼就是只寫 需要修改的那部分了
struct v2f{
//包含了 從切線空間轉(zhuǎn)到 世界空間的變換矩陣
float4 pos :SV_POSITION;
float4 uv: TEXCOORD0;
float4 TtoW0: TEXCOORD1;
float4 TtoW1: TEXCOORD2;
float4 TtoW2: TEXCOORD3;
}
一個(gè)插值寄存器 最多只能存float4 大小的變量,矩陣這樣的變量蔬墩,按行拆成多個(gè)變盤再進(jìn)行存儲译打,TtoW0 到TtoW2就是存儲了從切線到世界空間的變換矩陣。方向矢量的變換只需要3*3大小的矩陣拇颅,所以矩陣用用了float3的存儲扶平。充分利用插值寄存器的存儲空間,W分量中蔬蕊,我們存儲世界空間下的頂點(diǎn)位置结澄。
//頂點(diǎn)著色器
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, 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 worldPos = mul(_Object2World, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
//副法線向量岸夯,副切線也是麻献、 和法線和切線平面垂直的向量
fixed3 worldBinormal = cross(worldNormal, worldTangent) *v.tangent.w;
// 分別存入 世界空間的變換矩陣、w存頂點(diǎn)位置
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); //w向量存了頂點(diǎn)位置
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//獲得在切線空間的法線向量
fixed3 bump = UnpackNormal(tex2D(_BumpMap猜扮,iuv.zw));
bump.xy *= _BumpScale; //根據(jù)高度紋理 變換
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);
}
使用內(nèi)置的UnpackNormal 函數(shù)對法線紋理進(jìn)行采樣和解碼(需要把法線紋理的格式標(biāo)識成Normal map )并使用_BumpScale 對其進(jìn)行縮放勉吻。最后,我們使用TtoW0 旅赢、TtoW1 和TtoW2存儲的變換矩陣把法線變換到世界空間下齿桃。這是通過使用點(diǎn)乘操作來實(shí)現(xiàn)矩陣的每一行和法線相乘來得到的。
在Unity 4.x 版本中煮盼,在不需要使用Cubemap 進(jìn)行環(huán)境映射的情況下短纵, 內(nèi)置的Unity Shader 使用的是切線空間來進(jìn)行法線映射和光照計(jì)算。而在Unity 5 .x 中僵控, 所有內(nèi)置的Unity Shader 都使用了世界空間來進(jìn)行光照計(jì)算香到。這也是為什么Unity 5.x 中表面著色器更容易報(bào)錯(cuò),因?yàn)樗鼈兪褂昧烁嗟牟逯导拇嫫鱽泶鎯ψ儞Q矩陣(還有一些額外的插值寄存器是用來輔助計(jì)算霧效的,
Unity中的法線紋理
圖片類型是NormalMap時(shí)悠就,可以用內(nèi)置的UnpackNormal來得到 正確的法線方向千绪。
UnpackNormal 函數(shù)實(shí)現(xiàn)
inline fixed3 UnpackNormalDXT5nm(fixed4 packednormal){
fixed3 normal;
normal.xy = packednormal.wy *2 -1;
normal.z = sqrt(1- saturate(dot(normal.xy, normal.xy)));
return normal;
}
inline fixed3 UnpackNormal(fixed4 packednormal){
#if defined(UNITY_NO_DXT5nm)
return packednormal.xyz *2 -1;
#else
return UnpackNormalDXT5nm(packednormal);
#endif
}
紋理類型設(shè)置成normal map 后,有一個(gè)復(fù)選框Create form Grayscale梗脾,荸型。 這個(gè)方法就是從高度圖中生產(chǎn)法線紋理的。高度圖本身是紀(jì)錄相對高度是一張灰度圖炸茧。白色表示相對更高瑞妇,黑色表示更低。這樣的紋理導(dǎo)入到unity之后宇立,要選擇NormalMap 也要勾選這個(gè)選項(xiàng)踪宠。這樣就可以等同法線紋理對待呢自赔。就是一張圖既有了法線圖也有了高度圖妈嘹。
還會有兩個(gè)選項(xiàng) ——Bumpniess 和Filtering。 Bumpiness 控制凹凸程度绍妨, Filtering決定使用哪種計(jì)算凹凸的程度润脸。 一種是smooth,生成的法線紋理比較平滑他去,另一種是Sharp毙驯,使用Sobel 濾波(一種邊緣檢測時(shí)使用的濾波器)來生成法線。濾波的實(shí)現(xiàn)非常簡單灾测,我們只需要在一個(gè)3x3的濾波器中計(jì)算x 和y 方向上的導(dǎo)數(shù)爆价,然后從中得到法線即可。
高度圖中的每個(gè)像素媳搪,考慮它與水平方向和豎直方向上的像素差铭段。把它們的差當(dāng)成該點(diǎn)對應(yīng)的法線在x和y方向上的位移。然后使用之前提到映射函數(shù)存儲成到法線紋理的r和g分量
漸變紋理
這種紋理 可以自由的控制漫反射秦爆,不同的漸變有不同的特性序愚。輪廓更明顯〉认蓿可以做類似卡通效果爸吮。
在左邊的圖中,我們使用一張從紫色調(diào)到淺黃色調(diào)的漸變紋理: 而中間的圖使用的漸變紋理則和《軍團(tuán)要塞2》中渲染人物使用的漸變紋理是類似的望门, 它們都是從黑色逐漸向淺灰色靠攏形娇,而且中間的分界線部分微微發(fā)紅, 這是因?yàn)楫嫾以诓瀹嬛型鶗陉幱疤幨褂眠@樣的色調(diào): 右側(cè)的漸變紋理則通常被用于卡迪風(fēng)格的渲染筹误,這種漸變紋理中的色調(diào)通常是突變的埂软, 即沒有平滑過渡,以此來模擬卡通中的陰影色塊。
Shader "xxxx"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1)
_RampTex("Ramp Tex", 2D) = "white" {} //這個(gè)是存儲漸變紋理
_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; //模型的第一組紋理坐標(biāo)存儲到該變量勘畔, 而v2f中的用于存儲紋理坐標(biāo)的uv所灸,便于在偏遠(yuǎn)著色i中使用該坐標(biāo)進(jìn)行紋理采樣
} ;
struct v2f{
float4 pos: SV_POSITION;
float3 worldNormal: TEXCOORD0;
float3 worldPos :TEXCOORD1;
float2 uv :TEXCOORD2; //為了執(zhí)行效率 所以 只用了texcoord 的xy值
}
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.wordlNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(_Object2World, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);
//內(nèi)置的TRANSFORM_TEX 宏來計(jì)算經(jīng)過平鋪和偏移后的紋理坐標(biāo)
return o;
}
fixed4 frag(v2f i): SV_Target{
fixed3 worldNormal = normalize (i. worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBENT.xyz;
//點(diǎn)積做一個(gè)縮放在加0.5的偏移這樣范圍就映射到【0,1】之間
fixed halfLambert = 0.5 *dot (worldNormal, worldLightDir) +0.5;
//halfLambert 構(gòu)建一個(gè)紋理坐標(biāo)炫七,用這個(gè)坐標(biāo)對_RampTex采樣
//由于 _RampTex 實(shí)際就是一個(gè)一維紋理(它在縱軸方向上顏色不變〉爬立,因此紋理坐標(biāo)的u 和v 方向我們都使用了halfLambert。然后万哪,把從漸變紋理采樣得到的顏色和材質(zhì)顏色 _Color 相乘 得到反射顏色
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);
}
}
}
}
texcoord 語義在a2v 中就是存儲紋理坐標(biāo)比較多侠驯。
texcoord0,texcoord1... 這樣是使用多重紋理貼圖, 也可以用來存儲其他動態(tài)計(jì)算信息奕巍。
texcoord 用float4應(yīng)該是TEXCOORD0寄存器用的32位的吟策,而后面XBOX360多的texcoord2-texcoord5用的是16位的寄存器,這跟硬件有關(guān)系的止,貼圖坐標(biāo)有1維檩坚、2維、3維的 使用float4和half4會更好诅福,
比如我們使用最多的就是2維的普通的紋理貼圖坐標(biāo)匾委,uv 分別存在texcoord.xy,texcoord.zw不使用氓润,當(dāng)然你也可以用texcoord.xyzw存儲兩個(gè)UV坐標(biāo)赂乐,那么讀取第二個(gè)UV坐標(biāo)時(shí),你必須先找到第一個(gè)的地址咖气,所以為了執(zhí)行效率浪費(fèi)點(diǎn)空間是值得的挨措。
片元著色_RampTex 采樣這段不是特別理解。
需要注意的是崩溪,我們需要把漸變紋理的Wrap Mode 設(shè)為Clamp 模式浅役,以防止對紋理進(jìn)行采樣時(shí)由于浮點(diǎn)數(shù)精度而造成的問題。圖7.19給出了Wrap Mode 分別為Repeat 和Clamp 模式的效果對比悯舟。
可以看出担租,左圖(使用Repeat 模式〉中在高光區(qū)域有一些黑點(diǎn)。這是由浮點(diǎn)精度造成的抵怎,當(dāng)我們使用
fixed2(haItLambert, haltLambert)對漸變紋理進(jìn)行采樣時(shí)奋救,雖然理論上halfLambert 的值在[0, 1 ]之間,但可能會奮1.000 01 這樣的值出現(xiàn)反惕。如果我們使用的是Repeat 模式尝艘,此時(shí)就會舍棄整數(shù)部分, 只保留小數(shù)部分姿染,得到的值就是0.000 01 背亥,對應(yīng)了漸變圖中最左邊的值秒际,即黑色。因此狡汉,就會出現(xiàn)圖中這樣在高光區(qū)域反而有黑點(diǎn)的情況娄徊。我們只需要把漸變紋理的Wrap Mode 設(shè)為Clamp 模式就可以解決這種問題。
遮罩紋理
遮罩紋理(mask texture) 遮罩可以對一下區(qū)域進(jìn)行保護(hù)盾戴,可以一些地方強(qiáng)烈寄锐,一些地方弱〖夥龋可以用遮罩紋理才控制光照橄仆。 另一種應(yīng)用是在制作地形混用多張圖片,例如草地衅斩,石頭盆顾,地面都可以用遮罩紋理控制混合這些紋理。
使用的流程:通過采樣得到遮罩紋理的紋素值畏梆,然后使用其中某個(gè)(或某幾個(gè))通道的值(texel.r)來與某種表面屬性進(jìn)行相乘您宪,這樣,當(dāng)該通道是 0 時(shí)具温,可以保護(hù)表面不受該屬性影響蚕涤。使用遮罩紋理可以讓美術(shù)人員更加精準(zhǔn)(像素級)控制模型表現(xiàn)的各種性質(zhì)筐赔。
Shader "Mask Texture"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1)
_MainTex("Main Tex ", 2D) = "white" {}
_BumpMap("Normap Map", 2D) = "bump" {}
_BumpScale("Bump Scale", Float) =1.0
_SpecularMask ("Specular Mask",2D) = "white" {}
_SpecularScale("Specular Scale", Float) =1.0
_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铣猩; // 定義Properties 中各屬性的變量
sampler2D _MainTex;
float4 _MainTex_ST; //主紋理,法線紋理(_BumpMap)和遮罩紋理共同使用的紋理變量_MainTex_ST
sampler2D _BumpMap;
float _BumpScale;
sampler2D _SpecularMask;
float _SpecularScale;
fixed4 _Specular;
float _Gloss;
//頂點(diǎn) 一般就是 位置茴丰, 法線达皿,后面的一般根據(jù)需要添加切線空間,紋理坐標(biāo)
struct a2v{
float4 vertex: POSITION;
float3 normal :NORMAL;
////切線空間的確定是通過存儲到模型里面的法線和切線確定的
//tangent.w是用來確定切線空間中坐標(biāo)軸的方向的
float4 tangent :TANGENT; //用切線空間 就需要這個(gè)值
float4 texcoord: TEXCOORD0; //存儲的紋理坐標(biāo)贿肩,有些地方可以計(jì)算或變化后傳遞給片元
}
struct v2f{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir: TEXCOORD2;
}
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//們對光照方向和視角方向進(jìn)行了坐標(biāo)空間的變換峦椰,把它們從模型空間變換到了切線空間中,以便在片元著色器中和法線進(jìn)行光照運(yùn)算:
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);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
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);
// Get the mask value
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
// Compute specular term with the specular mask
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;
return fixed4(ambient + diffuse + specular, 1.0);
}
}
}
}
環(huán)境光照和漫反射光照和之前使用過的代碼完全一樣汰规。在計(jì)算高光反射時(shí)汤功,我們首先對遮罩紋理 _SpecularMask 進(jìn)行采樣。由于本書使用的遮罩紋理中每個(gè)紋素的 rgb 分量其實(shí)都是一樣的溜哮,表明了該點(diǎn)對應(yīng)的高光反射強(qiáng)度滔金, 在這里我們選擇使用 r 分量來計(jì)算掩碼值。然后茂嗓,我們用得到的掩碼值和 _SpecularScale 相乘餐茵, 一起來控制高光反射的強(qiáng)度。
需要說明的是述吸,我們使用的這張遮罩紋理其實(shí)有很多空間被浪費(fèi)了一一它的 rgb 分量存儲的都是同一個(gè)值忿族。在實(shí)際的游戲制作中,我們往往會充分利用遮罩紋理中的每一個(gè)顏色通道來存儲不同的表面屬性,我們會在7.4.2 節(jié)中介紹這樣一個(gè)例子
其他遮罩紋理
在真實(shí)的游戲制作過程中道批,遮罩紋理已經(jīng)不止限于保護(hù)某些區(qū)域使它們免于某些修改错英,而是可以存儲任何我們希望逐像素控制的表面屬性。通常隆豹,我們會充分利用一張紋理的RGBA 四個(gè)通道走趋,用于存儲不同的屬性。例如噪伊,我們可以把高光反射的強(qiáng)度存儲在R 通道簿煌,把邊緣光照的強(qiáng)度存儲在G 通道,把高光反射的指數(shù)部分存儲在B 通道鉴吹,最后把自發(fā)光強(qiáng)度存儲在A 通道姨伟。
在游戲《DOTA 2》的開發(fā)中,開發(fā)人員為每個(gè)模型使用了4 張紋理:一張用于定義模型顏色豆励,一張用于定義表面法線夺荒,另外兩張則都是遮罩紋理。這樣良蒸,兩張遮罩紋理提供了共8 種額外的表面屬性技扼,這使得游戲中的人物材質(zhì)自由度很強(qiáng),可以支持很多高級的模型屬性嫩痰。讀者可以在他們的官網(wǎng)上找到關(guān)于《DOTA 2》的更加詳細(xì)的制作資料剿吻,包括游戲中的人物模型、紋理以及制作手冊等串纺。這是非常好的學(xué)習(xí)資料丽旅。