轉(zhuǎn):http://www.cnblogs.com/polobymulberry/p/4316683.html
如果你是一個(gè)shader編程的新手淆游,并且你想學(xué)到下面這些酷炫的技術(shù),我覺(jué)得你可以看看這篇教程:
實(shí)現(xiàn)一個(gè)積雪效果的shader
創(chuàng)建一個(gè)具有凹凸紋理的shader
為每個(gè)像素修改其對(duì)應(yīng)紋理值
在表面著色器中修改模型的頂點(diǎn)數(shù)據(jù)
引論
這是我們系列教程的第二部分,我們將在此部分實(shí)現(xiàn)些有用的技術(shù)。在學(xué)習(xí)完第一部分的所有背景知識(shí)后逢渔,我們將利用所學(xué)的知識(shí)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的積雪效果的shader完箩。效果如下:
準(zhǔn)備工作
我們想做的其實(shí)很簡(jiǎn)單,簡(jiǎn)單介紹一下:
隨著Snow Level(表示積雪的程度排吴,該值越大,積雪越深)的增大懦鼠,我們將迎著下雪方向的巖石紋理區(qū)域都變成Snow Color(雪的顏色)
隨著Snow Level的增大钻哩,我們想將該巖石模型稍微變大一點(diǎn),尤其是雪的邊緣厚度要增加肛冶,這樣給人真正的“積”雪效果街氢。
步驟1 – Bumped Diffuse Shader
我們創(chuàng)建一個(gè)新的Diffuse shader(默認(rèn)新建的shader基本都是Diffuse shader),并向其中添加凹凸紋理效果(來(lái)自貓大的原話:法線貼圖是凸凹貼圖(Bump mapping)的一種常見(jiàn)應(yīng)用睦袖,簡(jiǎn)單說(shuō)就是在不增加模型多邊形數(shù)量的前提下珊肃,通過(guò)渲染暗部和亮部的不同顏色深度,來(lái)為原來(lái)的貼圖和模型增加視覺(jué)細(xì)節(jié)和真實(shí)效果。簡(jiǎn)單原理是在普通的貼圖的基礎(chǔ)上伦乔,再另外提供一張對(duì)應(yīng)原來(lái)貼圖的厉亏,可以表示渲染濃淡的貼圖。通過(guò)將這張附加的表示表面凸凹的貼圖的因素于實(shí)際的原貼圖進(jìn)行運(yùn)算后烈和,可以得到新的細(xì)節(jié)更加豐富富有立體感的渲染效果爱只。)。
Shader"Custom/SnowShader"{
Properties{
_MainTex("Base (RGB)",2D)="white"{}
//新的凹凸紋理貼圖
_Bump("Bump",2D)="bump"{}
}
SubShader{
Tags{"RenderType"="Opaque"}
LOD200
CGPROGRAM
#pragmasurface surfLambert
sampler2D_MainTex;
//必須添加一個(gè)與Properties代碼區(qū)中的同名的_Bump變量招刹,作為Properties中_Bump的引用恬试。
//具體緣由詳見(jiàn)教程第一部分。
sampler2D_Bump;
structInput{
float2 uv_MainTex;
//用來(lái)得到_Bump的uv坐標(biāo)
float2 uv_Bump;
};
voidsurf(InputIN,inoutSurfaceOutputo){
half4 c=tex2D(_MainTex,IN.uv_MainTex);
//從_Bump紋理中提取法向信息
o.Normal=UnpackNormal(tex2D(_Bump,IN.uv_Bump));
o.Albedo=c.rgb;
o.Alpha=c.a;
}
ENDCG
}
FallBack"Diffuse"
}
Unity已經(jīng)在新建的shader中幫我們自動(dòng)添加了很多代碼蔗喂,我們只要添加凹凸紋理相關(guān)的代碼即可忘渔。
在上述代碼,我們完成了一下內(nèi)容:
定義了默認(rèn)值為“bump”的2D類型的紋理_Bump缰儿,初始值為空畦粮。(默認(rèn)值為“bump”的2D類型紋理本質(zhì)就是凹凸紋理類型)
在CG代碼區(qū)域中(CGPROGRAM…ENDCG)中添加了與_Bump同名的sampler2D變量,該sampler2D _Bump做的事情就是再次聲明并鏈接了_Bump乖阵,使得接下來(lái)的CG代碼區(qū)域能夠使用這個(gè)變量宣赔。
在Input結(jié)構(gòu)體中添加一個(gè)變量float2 uv_Bump來(lái)獲得_Bump紋理的uv坐標(biāo)。
在surf函數(shù)中添加UnpackNormal函數(shù)來(lái)獲取對(duì)應(yīng)像素的法向值(因?yàn)樵搒urf函數(shù)時(shí)每個(gè)像素調(diào)用一次)瞪浸。注意我們先使用tex2D函數(shù)來(lái)計(jì)算當(dāng)前像素的具體值(tex2D作用僅僅是通過(guò)一個(gè)二維uv坐標(biāo)在紋理上獲取該處值儒将,根據(jù)紋理的類型不同,獲取的值的含義也不一樣对蒲,比如bump類型紋理上存儲(chǔ)的值代表的含義是該點(diǎn)的法向量钩蚊,而普通紋理一般代表的是該點(diǎn)的顏色值),具體過(guò)程是根據(jù)對(duì)應(yīng)uv坐標(biāo)(IN.uv_Bump)在bump類型紋理(_Bump)上得到該點(diǎn)存儲(chǔ)的值蹈矮,注意此時(shí)得到的值是一個(gè)fixed4的值砰逻,但是法向值只需要fixed3類型,所以需要UnpackNormal進(jìn)行轉(zhuǎn)換(Unity3d的標(biāo)準(zhǔn)法線解壓函數(shù) — fixed3 UnpackNormal(fixed4 packednormal))泛鸟。
經(jīng)過(guò)上述步驟蝠咆,我們得到了一個(gè)很普通的bumped shader。
我們將該shader應(yīng)用到我們的模型上北滥,并賦予相對(duì)應(yīng)的紋理貼圖刚操,具體代碼和資源請(qǐng)戳這里。
我在SnowRock文件夾下創(chuàng)建了名為SnowRock1的材質(zhì)和shader再芋,將我們的代碼粘貼到SnowRock1.shader中菊霜,并將該shader拖到SnowRock1材質(zhì)上。
隨后我們將Free_Rocks –> _models中的cliff拖到Scene面板中祝闻,再將SnowRock1材質(zhì)賦給該模型占卧。此時(shí)我們還看不出任何變化遗菠,因?yàn)槲覀冞€沒(méi)有給shader中Properties中那兩個(gè)紋理變量賦值联喘。
此時(shí)我們可以看到巖石效果的變化华蜒。
為了體現(xiàn)bump紋理的作用,我們將使用bump紋理和不使用bump紋理的巖石做下對(duì)比豁遭。
步驟2 – 在巖石上添加些雪
接下來(lái)我們要做的就是計(jì)算對(duì)應(yīng)像素的法向量是否與下雪的方向一致(如果一致叭喜,那么就將該像素置為雪的顏色,這個(gè)道理和平行光的原理很類似)蓖谢。
我們使用點(diǎn)乘(dot product)計(jì)算方向是否一致捂蕴。兩個(gè)單位向量之間的點(diǎn)乘等于這兩個(gè)向量之間夾角的余弦值。CG中自帶了一個(gè)dot函數(shù)為我們做兩向量的點(diǎn)乘計(jì)算闪幽。這樣的話啥辨,當(dāng)我們計(jì)算出的點(diǎn)乘結(jié)果為1,則說(shuō)明兩向量間余弦值為1盯腌,即兩者夾角為0溉知,說(shuō)明兩者方向一致,而結(jié)果為-1時(shí)腕够,說(shuō)明兩者方向相反级乍。所以我們不必算出最后的角度,僅僅通過(guò)點(diǎn)乘結(jié)果就可以判斷兩向量之間方向的關(guān)系帚湘。
單位向量是指大小為1的向量玫荣,也就是說(shuō)當(dāng)向量v滿足sqrt(v.x*v.x+v.y*v.y+v.z*v.z)=1時(shí),此向量為單位向量大诸。不要認(rèn)為單位向量就是(1,1,1)巴背А!W嗜帷焙贷!
如果要使用點(diǎn)乘的結(jié)果來(lái)表示兩向量之間的角度,首先要將兩個(gè)向量的長(zhǎng)度變?yōu)閱挝婚L(zhǎng)度建邓,也就是將兩個(gè)向量變到單位向量再計(jì)算點(diǎn)乘盈厘。
知道這些后,我們?cè)趕hader中定義以下屬性官边。
Properties{
_MainTex("Base (RGB)",2D)="white"{}
_Bump("Bump",2D)="bump"{}
_Snow("Snow Level",Range(0,1))=0
_SnowColor("Snow Color",Color)=(1.0,1.0,1.0,1.0)
_SnowDirection("Snow Direction",Vector)=(0,1,0)
_SnowDepth("Snow Depth",Range(0,0.3))=0.1
}
在上面代碼中定義了一下變量:
_Snow 表示覆蓋在巖石上雪的數(shù)量沸手,范圍從0~1.
_SnowColor 表示雪的顏色,默認(rèn)為白色注簿。
_SnowDirection注意F跫!诡渴!此處表示的其實(shí)是雪落下方向的反向捐晶,比如我們默認(rèn)雪是從天空直接落下來(lái)的菲语,理論上應(yīng)該為(0,-1,0),但是此處取得是它的反向惑灵,用的是(0,1,0)
_SnowDepth 我們將在步驟3中使用到該變量山上,它表示雪的厚度,范圍從0~0.3
根據(jù)第一部分所學(xué)的內(nèi)容英支,我們接著在下面的CGPROGRAM…ENDCG代碼中添加如下變量:
sampler2D_MainTex;
sampler2D_Bump;
float_Snow;
float4_SnowColor;
float4_SnowDirection;
float_SnowDepth;
現(xiàn)在佩憾,除了紋理以外,我們把其他屬性都當(dāng)做float類型進(jìn)行處理干花。
接著我們要更新我們shader中的Input結(jié)構(gòu)體妄帘。此處要注意,我們不能直接用每個(gè)像素的法向值池凄,因?yàn)榉ㄏ蛸N圖(normal map)為我們計(jì)算的每個(gè)像素法向值是在切空間的法向值抡驼,所以我們必須重新計(jì)算出每個(gè)像素在世界坐標(biāo)系下真正的法向值,以此來(lái)和SnowDirection進(jìn)行比較來(lái)決定此處是否覆蓋上雪的顏色肿仑。
得到世界坐標(biāo)系下的法向量還是有點(diǎn)麻煩的致盟,為此我還特意看了下官方文檔。因?yàn)槲覀兊谋砻嬷鲗?xiě)入了o.Normal柏副,所以按照第一部分的講解勾邦,我們需要使用INTERNAL_DATA來(lái)計(jì)算世界坐標(biāo)系下的法向量,此處我們使用了WorldNormalVector函數(shù)割择。
我們將這些數(shù)據(jù)變量放到Input結(jié)構(gòu)體中:
structInput{
float2 uv_MainTex;
float2 uv_Bump;
float3 worldNormal;
INTERNAL_DATA
};
最后我們完善surf函數(shù)眷篇,結(jié)束我們的shader程序。
voidsurf(InputIN,inoutSurfaceOutputo){
//該像素的真實(shí)顏色值
half4 c=tex2D(_MainTex,IN.uv_MainTex);
//從凹凸貼圖中得到該像素的法向量
o.Normal=UnpackNormal(tex2D(_Bump,IN.uv_Bump));
//得到世界坐標(biāo)系下的真正法向量(而非凹凸貼圖產(chǎn)生的法向量荔泳,要做一個(gè)切空間到世界坐標(biāo)系的轉(zhuǎn)化)和雪落
//下相反方向的點(diǎn)乘結(jié)果蕉饼,即兩者余弦值,并和_Snow(積雪程度)比較
if(dot(WorldNormalVector(IN,o.Normal),_SnowDirection.xyz)>lerp(1,-1,_Snow))
//此處我們可以看出_Snow參數(shù)只是一個(gè)插值項(xiàng)玛歌,當(dāng)上述夾角余弦值大于
//lerp(1,-1,_Snow)=1-2*_Snow時(shí)昧港,即表示此處積雪覆蓋,所以此值越大支子,
//積雪程度程度越大创肥。此時(shí)給覆蓋積雪的區(qū)域填充雪的顏色
o.Albedo=_SnowColor.rgb;
else
//否則使用物體原先顏色,表示未覆蓋積雪
o.Albedo=c.rgb;
o.Alpha=1;
}
現(xiàn)在你們肯定迫切想知道if語(yǔ)句中到底做了哪些事情值朋?
得到雪落下相反方向的向量和該點(diǎn)世界坐標(biāo)系下的法向量之間的點(diǎn)乘結(jié)果叹侄。此處注意使用的法向值是利用WorldNormalVector函數(shù)將切空間的法向值轉(zhuǎn)化到世界坐標(biāo)系下而得來(lái)的,另外提一點(diǎn)昨登,切空間的法向值其實(shí)就是所使用的凹凸貼圖法向值趾代。
經(jīng)過(guò)點(diǎn)乘之后,我們得到一個(gè)1到-1之間的值丰辣,越接近于1撒强,說(shuō)明該點(diǎn)法向與雪落下相反方向越一致禽捆,當(dāng)為-1時(shí),說(shuō)明兩者方向相反飘哨。
然后我們將點(diǎn)乘結(jié)果和一個(gè)插值結(jié)果進(jìn)行比較胚想,該插值使用了函數(shù)lerp(1,-1,_Snow)。實(shí)際上該函數(shù)結(jié)果為1+(-1-1)*_Snow=1-2*_Snow杖玲,所以當(dāng)_Snow為1時(shí)顿仇,函數(shù)結(jié)果為-1淘正,而當(dāng)_Snow為0時(shí)摆马,函數(shù)結(jié)果為1。注意為了符合正常的自然現(xiàn)象鸿吆,我們_Snow一般只取0~0.5囤采,因?yàn)榇笥?.5時(shí),插值的結(jié)果將小于0惩淳,會(huì)造成雪好像穿過(guò)了巖石蕉毯,落到了巖石的后面。這個(gè)道理和光照的道理一樣思犁,物體背面是見(jiàn)不到陽(yáng)光的代虾。
當(dāng)該點(diǎn)求得的點(diǎn)乘結(jié)果大于插值范圍,將顯示積雪顏色激蹲,反之顯示原先的紋理顏色棉磨。
下面使我們完整的積雪效果shader:
Shader"Custom/SnowShader2"{
Properties{
_MainTex("Base (RGB)",2D)="white"{}
_Bump("Bump",2D)="bump"{}
_Snow("Snow Level",Range(0,1))=0
_SnowColor("Snow Color",Color)=(1.0,1.0,1.0,1.0)
_SnowDirection("Snow Direction",Vector)=(0,1,0)
_SnowDepth("Snow Depth",Range(0,0.3))=0.1
}
SubShader{
Tags{"RenderType"="Opaque"}
LOD200
CGPROGRAM
#pragmasurface surfLambert
sampler2D_MainTex;
sampler2D_Bump;
float_Snow;
float4_SnowColor;
float4_SnowDirection;
float_SnowDepth;
structInput{
float2 uv_MainTex;
float2 uv_Bump;
float3 worldNormal;
INTERNAL_DATA
};
voidsurf(InputIN,inoutSurfaceOutputo){
//該像素的真實(shí)顏色值
half4 c=tex2D(_MainTex,IN.uv_MainTex);
//從凹凸貼圖中得到該像素的法向量
o.Normal=UnpackNormal(tex2D(_Bump,IN.uv_Bump));
//得到世界坐標(biāo)系下的真正法向量(而非凹凸貼圖產(chǎn)生的法向量)和雪落
//下相反方向的點(diǎn)乘結(jié)果,即兩者余弦值学辱,并和_Snow(積雪程度)比較
if(dot(WorldNormalVector(IN,o.Normal),_SnowDirection.xyz)>lerp(1,-1,_Snow))
//此處我們可以看出_Snow參數(shù)只是一個(gè)插值項(xiàng)乘瓤,當(dāng)上述夾角余弦值大于
//lerp(1,-1,_Snow)=1-2*_Snow時(shí),即表示此處積雪覆蓋策泣,所以此值越大衙傀,
//積雪程度程度越大。此時(shí)給覆蓋積雪的區(qū)域填充雪的顏色
o.Albedo=_SnowColor.rgb;
else
//否則使用物體原先顏色萨咕,表示未覆蓋積雪
o.Albedo=c.rgb;
o.Alpha=1;
}
ENDCG
}
FallBack"Diffuse"
}
使巖石隨積雪厚度變形
最后一步是將巖石模型沿著下雪的方向變厚统抬,以表示積雪的厚度。
為了達(dá)到此效果危队,我們需要修改模型的頂點(diǎn)數(shù)據(jù) - 這意味著我們要在表面著色器中寫(xiě)一個(gè)修改頂點(diǎn)的函數(shù)聪建。
#pragma surface surf Lambert vertex:vert
我們?cè)谏鲜龃a的末尾加入了vertex:vert表示我們將使用自己定義的vertex函數(shù),此函數(shù)名稱為vert交掏。
我們的vertex函數(shù)如下:
voidvert(inout appdata_full v){
//將_SnowDirection轉(zhuǎn)化到模型的局部坐標(biāo)系下
float4 sn=mul(UNITY_MATRIX_IT_MV,_SnowDirection);
if(dot(v.normal,sn.xyz)>=lerp(1,-1,(_Snow*2)/3))
{
v.vertex.xyz+=(sn.xyz+v.normal)*_SnowDepth*_Snow;
}
}
首先我們傳給vert函數(shù)一個(gè)參數(shù)appdata_full v妆偏,參數(shù)的類型為appdata_full(Unity內(nèi)置類型),該類型包含了紋理坐標(biāo)盅弛,法向量钱骂,頂點(diǎn)位置叔锐,以及切線信息。如果你還需要使用其他的數(shù)據(jù)類型见秽,你可以使用自定義的輸入結(jié)構(gòu)體作為pixel函數(shù)的第二個(gè)參數(shù)傳遞額外的信息 — 目前我們不需要這樣做愉烙。
_SnowDirection使用的是世界坐標(biāo)系,但是我們需要的其實(shí)是模型局部坐標(biāo)系下的_SnowDirection解取。所以我們需要先將_SnowDirection轉(zhuǎn)化到模型的局部坐標(biāo)系下步责。而我們只需要將_SnowDirection乘以Unity內(nèi)置矩陣 – UNITY_MATRIX_IT_MV(IT表示Inverse Transpose逆轉(zhuǎn)置矩陣,MV表示 ModelView矩陣禀苦,該矩陣表示是ModelView的逆轉(zhuǎn)置矩陣)蔓肯。
現(xiàn)在我們得到了該頂點(diǎn)的法向量(vert函數(shù)應(yīng)該是對(duì)每個(gè)vertex調(diào)用一次,相對(duì)于surf函數(shù)對(duì)每個(gè)pixel調(diào)用一次)振乏。我們?nèi)匀幌裆厦孀龇e雪效果時(shí)那樣蔗包,將轉(zhuǎn)換坐標(biāo)系空間后的雪落下相反方向和模型局部坐標(biāo)系下的法向量進(jìn)行點(diǎn)乘,得到的結(jié)果仍然和一個(gè)插值比較慧邮。不過(guò)此時(shí)插值項(xiàng)不再是_Snow调限,而是_Snow*2/3,這表示只有那些更接近雪落下方向的區(qū)域才會(huì)增加雪的厚度误澳,更符合自然現(xiàn)象耻矮。
而這些通過(guò)測(cè)試的區(qū)域辈赋,沿著(sn.xyz+v.normal)方向進(jìn)行加厚试躏,也就是將其頂點(diǎn)沿此方向伸展一定距離。注意到增厚的程度取決于_SnowDepth和_Snow辅肾,而增厚的方向是由物體法向和雪落的方向綜合作用的陪毡,這也符合自然現(xiàn)象米母。
我們來(lái)對(duì)比下不同的積雪深度效果:
我們的shader終于完成了。
源代碼
完整的shader源碼呈現(xiàn)如下:
Shader"Custom/SnowShader2"{
Properties{
_MainTex("Base (RGB)",2D)="white"{}
_Bump("Bump",2D)="bump"{}
_Snow("Snow Level",Range(0,1))=0
_SnowColor("Snow Color",Color)=(1.0,1.0,1.0,1.0)
_SnowDirection("Snow Direction",Vector)=(0,1,0)
_SnowDepth("Snow Depth",Range(0,0.3))=0.1
}
SubShader{
Tags{"RenderType"="Opaque"}
LOD200
CGPROGRAM
#pragmasurface surfLambertvertex:vert
sampler2D_MainTex;
sampler2D_Bump;
float_Snow;
float4_SnowColor;
float4_SnowDirection;
float_SnowDepth;
structInput{
float2 uv_MainTex;
float2 uv_Bump;
float3 worldNormal;
INTERNAL_DATA
};
voidsurf(InputIN,inoutSurfaceOutputo){
//該像素的真實(shí)顏色值
half4 c=tex2D(_MainTex,IN.uv_MainTex);
//從凹凸貼圖中得到該像素的法向量
o.Normal=UnpackNormal(tex2D(_Bump,IN.uv_Bump));
//得到世界坐標(biāo)系下的真正法向量(而非凹凸貼圖產(chǎn)生的法向量)和雪落
//下相反方向的點(diǎn)乘結(jié)果毡琉,即兩者余弦值铁瞒,并和_Snow(積雪程度)比較
if(dot(WorldNormalVector(IN,o.Normal),_SnowDirection.xyz)>lerp(1,-1,_Snow))
//此處我們可以看出_Snow參數(shù)只是一個(gè)插值項(xiàng),當(dāng)上述夾角余弦值大于
//lerp(1,-1,_Snow)=1-2*_Snow時(shí)桅滋,即表示此處積雪覆蓋慧耍,所以此值越大,
//積雪程度程度越大丐谋。此時(shí)給覆蓋積雪的區(qū)域填充雪的顏色
o.Albedo=_SnowColor.rgb;
else
//否則使用物體原先顏色芍碧,表示未覆蓋積雪
o.Albedo=c.rgb;
o.Alpha=1;
}
voidvert(inout appdata_full v){
//將_SnowDirection轉(zhuǎn)化到模型的局部坐標(biāo)系下
float4 sn=mul(UNITY_MATRIX_IT_MV,_SnowDirection);
if(dot(v.normal,sn.xyz)>=lerp(1,-1,(_Snow*2)/3))
{
v.vertex.xyz+=(sn.xyz+v.normal)*_SnowDepth*_Snow;
}
}
ENDCG
}
FallBack"Diffuse"
}
所有的資源代碼我都放到這里了。
最后來(lái)張血雨效果圖…