【譯】Unity3D Shader 新手教程(6/6) —— 更好的卡通Shader (轉)

轉自:http://www.cnblogs.com/polobymulberry/p/4395127.html

動機

如果你想了解以下幾件事,我建議你閱讀以下這篇教程:

想知道如何寫一個multipass的toon shader。

在shader中學習更多不同參考坐標系(空間space)以及其作用夕晓。

深入學習一個實用的fragment shader。

學習矩陣相乘和Unity內建矩陣的使用厢破。

該教程比第五篇教程更實用。

準備工作

為了實現(xiàn)一個描邊的toon shader治拿,我們需要做的是:

為模型描邊摩泪。

第四篇文章中的介紹的toon shader(使用的是surface shader)移植到vertex&fragment shader中。

描邊

有很多方法進行描邊劫谅,在第四篇文章中见坑,我們使用了rim lighting(邊緣光照)來給我們人物加上描邊效果∪侣樱現(xiàn)在我們采用另一種方法,額外使用一個Pass改善已有的描邊效果荞驴。

不同于之前描邊效果的實現(xiàn)不皆,在這篇教程中,你可以將你看不到的模型部分(比如背面)放大一些熊楼,再渲染成全黑霹娄,這樣也是可以實現(xiàn)描邊效果的。這種方法可以將原模型的正面完好無損呈現(xiàn)出來孙蒙。

所以我們首先試著:

單獨寫一個僅僅用來繪制模型背面的Pass项棠。

擴展模型背面的頂點悲雳,使其看起來變大了一些挎峦。

下面這個Pass就是用來僅僅繪制模型背面(Cull Front,剔除正面的多邊形):

Pass{

CullFront

LightingOff

}

現(xiàn)在讓我們考慮最簡單的部分 — 將傳入該Pass的所有像素值繪制成黑色!

CGPROGRAM

#pragmavertex vert

#pragmafragment frag

#include"UnityCG.cginc"

//剩下的功能在此處實現(xiàn)

float4 frag(v2f IN):COLOR

{

returnfloat4(0,0,0,1);

}

ENDCG

該fragment函數(shù)返回float4(0,0,0,1) — 全黑合瓢。

現(xiàn)在為我們的shader添加輸入結構體坦胶。我們利用該結構體(包含vertex和normal)來將我們模型的每個頂點沿法向進行延伸擴展 — 該頂點是背面面片上的點。所以我們輸入結構體必須含有頂點位置vertex和頂點法向normal信息晴楔。

structa2v

{

float4 vertex:POSITION;

float3 normal:NORMAL;

};

structv2f

{

float4 pos:POSITION;

};

接下來我們在Properties代碼區(qū)域定義一個_Outline屬性值顿苇,范圍為0.0~1.0,我們在CG代碼中定義一個相同的變量float _Outline税弃。

最后我們在vertex函數(shù)vert中延著法向normal伸展頂點:

float_Outline;

v2f vert(a2v v)

{

v2f o;

o.pos=mul(UNITY_MATRIX_MVP,v.vertex+(float4(v.normal,0)*_Outline));

returno;

}

我們所做的就是將v.vertex沿著normal伸展了_Outline比例大小纪岁,然后使用Unity內置的矩陣UNITY_MATRIX_MVP將結果轉換到投影空間(projection space)。

矩陣在shader中用來轉化很多事情则果。我們可以從下圖看出幔翰,一個4x4的矩陣乘上一個4x1的矩陣,得到還是一個4*1的矩陣西壮。Unity中有很多預定好的矩陣遗增,我們可以使用這些矩陣得到各種空間坐標系的轉換。

目前你的代碼應該保證像下面這樣了(注意這是在第五部分教程的基礎上添加的代碼):

Pass{

//剔除模型正面款青,只渲染背面

CullFront

LightingOff

CGPROGRAM

#pragmavertex vert

#pragmafragment frag

#include"UnityCG.cginc"

structa2v

{

float4 vertex:POSITION;

float3 normal:NORMAL;

};

structv2f

{

float4 pos:POSITION;

}

float_Outline;

v2f vert(a2v v)

{

v2f o;

o.pos=mul(UNITY_MATRIX_MVP,v.vertex+(float4(v.normal,0)*_Outline));

returno;

}

floatfrag(v2f IN):COLOR

{

returnfloat4(0,0,0,1)

}

ENDCG

}

看上去好像有點效果做修,但是仔細看他的嘴巴,我們可以看到是有很大問題抡草。這是因為實現(xiàn)邊緣效果的Pass是可以寫入深度緩存的饰及。所以在有些情況下,模型正面是無法正常繪制的康震。

拿此處的嘴舉例旋炒,此處的嘴巴的上嘴唇是屬于正面的,而下嘴唇是反面(多邊形方向為逆時針)签杈。所以Cull Front后會剔除上嘴唇瘫镇,保留下嘴唇鼎兽。而下嘴唇的法向很明顯差不多是朝上的,所以在vert函數(shù)中會在下嘴唇上方產(chǎn)生這種黑條狀的面片铣除。又因為我們是可以寫入深度緩存的谚咬,所以會將這黑色面片寫入到深度緩存,而這黑色面片恰好在嘴唇前面尚粘,所以嘴唇正面在繪制時通過不了深度測試择卦,只留下這黑色的面片。

自然而然地我們肯定能想到郎嫁,讓這個黑色面片不進行深度緩存測試不就行了秉继。下面這幅圖就是在該Pass中關閉Z buffer測試的結果。

使用下面這段代碼:

Pass{

CullFront

LightingOff

ZWriteOff

關閉Z buffer測試后泽铛,哪些多余的黑色面片確實不存在了尚辑。可是又有一個新問題出現(xiàn)了盔腔。因為黑色面片始終通過不了Z Buffer測試杠茬,所以模型本身的面片會覆掉這些黑色面片。我們看到下面這張圖弛随,前面的模型擋住了后面模型產(chǎn)生的黑色邊緣瓢喉。這又不是我們想要的。

現(xiàn)在我們大概知道問題的本質就是黑色面片是沿著法向擴展了一定長度舀透,其Z值也就發(fā)生了變化栓票。如果我們特意處理下Z值,使其產(chǎn)生的背面的黑色面片的Z值小一點愕够,也就是離視點遠一些走贪,而不是像一個新產(chǎn)生的模型一樣附在物體表面。這樣的話链烈,對于邊緣效果厉斟,其主要作用的將是x和y分量,而不是z分量强衡。

現(xiàn)在回到我們的vertex函數(shù)擦秽,然后做一些矩陣變換。

將背面產(chǎn)生的黑色面片在Z方向壓扁

首先迎接的挑戰(zhàn)是我們的頂點和法向是在模型空間 — 但是我們要將其轉換到視空間(相機為原點的空間漩勤,還未經(jīng)過投影變換)感挥,這是因為在視空間中,z軸指向相機越败,也就是模型z值恰好表示模型距離相機的遠近触幼。

下面介紹幾個Unity內建的矩陣。

首先我們不再將頂點轉換到投影空間中究飞,而是將頂點先轉換到視空間中 — 這很簡單置谦,僅僅需要使用一個不同的矩陣堂鲤。

然后我們要將對應法向值轉化到視空間中 — 這里使用了一個trick,因為將法向從模型空間轉換到視空間不能簡單使用矩陣UNITY_MATRIX_MV媒峡。得使用UNITY_MATRIX_MV的逆轉置矩陣UNITY_MATRIX_IT_MV(其中IT表示Inverse Transpose)瘟栖。直接將法向乘以UNITY_MATRIX_MV得到的結果將不再垂直原來的面片。本質原因其實是因為頂點是一個點谅阿,而法向是一個方向向量半哟。

比如下圖以及下面的推導公式:

所以我們所要做的就是:

將頂點轉化到視空間中∏┎停— pos = mul( UNITY_MATRIX_MV, v.vertex);

將法向轉化到視空間中寓涨。— normal = mul( (float3x3)UNITY_MATRIX_IT_MV, v.normal);

修正法向量的z分量為某個特定最小值 —normal.z= -0.4(這樣黑色邊緣延伸擴展就會沿模型背面擴展氯檐,不會出現(xiàn)在模型前面了)

重新單位化法向(因為在之前的步驟中戒良,我們改變了法向,破壞了它的單位長度)

使用_Outline縮放法向長度男摧,然后加到將頂點位置沿法向平移這么長蔬墩。

將頂點轉化到投影空間中译打。

所有代碼看起來就像下面這樣:

v2f vert(a2v v)

{

v2f o;

float4 pos=mul(UNITY_MATRIX_MV,v.vertex);

float3 normal=mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);

normal.z=-0.4;

pos=pos+float4(normalize(normal),0)*_Outline;

o.pos=mul(UNITY_MATRIX_P,pos);

returno;

}

注意Unity中使用的矩陣是4x4 — 但是我們的法向是float3類型 — 我們必須將矩陣轉化為3x3 — (float3x3)UNITY_MATRIX_IT_MV耗拓,否則我們會在Unity的控制臺得到很多錯誤。

如果我們使用ZWrite On — 效果看起來像下面這樣:

這種效果對我們已經(jīng)足夠了奏司。

卡通化

剩下的就是將我們之前使用表面著色器制作的Toon Shader應用到vertex&fragment shader中乔询。

首先我們像教程第四部分那樣定義一個_Ramp屬性值,并相應的定義sampler2D _Ramp韵洋。

使用ramp texture(漸變紋理) — 然后我們添加一個_ColorMerge屬性變量(一個float類型的值)竿刁,利用其降低模型顏色的種類。

我們改變教程第五部分的fragment函數(shù) — 就像下面這樣:

float4 frag(v2f i):COLOR

{

//根據(jù)uv坐標從紋理中獲得對應像素值

float4 c=tex2D(_MainTex,i.uv);

//降低顏色種類

c.rgb=(floor(c.rgb*_ColorMerge)/_ColorMerge);

//從bump紋理中得到對應像素的法向

float3 n=UnpackNormal(tex2D(_Bump,i.uv2));

//獲得漫射光顏色

float3 lightColor=UNITY_LIGHTMODEL_AMBIENT.xyz;

//計算出光源距離

floatlengthSq=dot(i.lightDirection,i.lightDirection);

//根據(jù)計算出的光源位置計算光強的衰減

floatatten=1.0/(1.0+lengthSq);

//光的入射角

floatdiff=saturate(dot(n,normalize(i.lightDirection)));

//利用漸變紋理

diff=tex2D(_Ramp,float2(diff,0.5));

//根據(jù)入射角搪缨,光衰減得到最終光照亮度

lightColor+=_LightColor0.rgb*(diff*atten);

//將光照亮度與本身顏色相乘食拜,得到最終顏色

c.rgb=lightColor*c.rgb*2;

returnc;

}

我們所要做的就是利用_MainTex紋理進行采樣,然后降低顏色種類副编,最后使用漸變紋理獲得的數(shù)值作為光強负甸。

下圖使我們最終的效果:

完整的源碼在這里

對于其他光照的ForwardAdd部分痹届,就留給你們自己寫吧呻待!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市队腐,隨后出現(xiàn)的幾起案子蚕捉,更是在濱河造成了極大的恐慌,老刑警劉巖柴淘,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迫淹,死亡現(xiàn)場離奇詭異秘通,居然都是意外死亡,警方通過查閱死者的電腦和手機敛熬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門充易,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荸型,你說我怎么就攤上這事盹靴。” “怎么了瑞妇?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵稿静,是天一觀的道長。 經(jīng)常有香客問我辕狰,道長改备,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任蔓倍,我火速辦了婚禮悬钳,結果婚禮上,老公的妹妹穿的比我還像新娘偶翅。我一直安慰自己默勾,他們只是感情好,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布聚谁。 她就那樣靜靜地躺著母剥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪形导。 梳的紋絲不亂的頭發(fā)上环疼,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音朵耕,去河邊找鬼炫隶。 笑死,一個胖子當著我的面吹牛阎曹,可吹牛的內容都是我干的伪阶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼芬膝,長吁一口氣:“原來是場噩夢啊……” “哼望门!你這毒婦竟也來了?” 一聲冷哼從身側響起锰霜,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤筹误,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后癣缅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厨剪,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡哄酝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了祷膳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陶衅。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖直晨,靈堂內的尸體忽然破棺而出搀军,到底是詐尸還是另有隱情,我是刑警寧澤勇皇,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布罩句,位于F島的核電站,受9級特大地震影響敛摘,放射性物質發(fā)生泄漏门烂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一兄淫、第九天 我趴在偏房一處隱蔽的房頂上張望屯远。 院中可真熱鬧,春花似錦捕虽、人聲如沸慨丐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咖气。三九已至挨措,卻和暖如春挖滤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浅役。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工斩松, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人觉既。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓惧盹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞪讼。 傳聞我的和親對象是個殘疾皇子钧椰,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內容