卡通渲染略講

本來(lái)呢,我是不打算寫卡通渲染相關(guān)的東西的鹃祖。雖然我挺喜歡玩女神異聞錄5這種卡通風(fēng)格的游戲,但是從技術(shù)路線來(lái)講普舆,我更希望走寫實(shí)路線的渲染恬口,例如最令我震撼的神秘海域4的畫(huà)面。不過(guò)由于我現(xiàn)在待的公司搞了大半年的卡通風(fēng)格的游戲沼侣,(項(xiàng)目中道崩殂祖能,成為每個(gè)開(kāi)發(fā)心中永遠(yuǎn)的痛。蛾洛。养铸。)所以我還是寫一下相關(guān)的東西,萬(wàn)一以后還要搞轧膘,我可以有個(gè)入口知道要從哪里開(kāi)始弄钞螟。好了,廢話說(shuō)到這兒谎碍,開(kāi)始正篇筛圆。

由于卡通渲染是旨在還原美術(shù)人員手繪的感覺(jué),所以它的漫反射呈現(xiàn)色塊的感覺(jué)椿浓,而不是漸變太援。然后我們利用光的方向和法線方向做點(diǎn)乘后得到的結(jié)果作為范圍劃定的依據(jù)闽晦,分了四層,像這樣:

 fixed diff = dot(worldNormal,normalize(worldLight));
 diff = diff * 0.5 + 0.5;
 fixed w = fwidth(diff) * 2.0;
 if (diff < _DiffuseSeg.x + w){
        diff = lerp(_DiffuseSeg.x,_DiffuseSeg.y,smoothstep(_DiffuseSeg.x - w,_DiffuseSeg.x + w,diff));
  }else if (diff < _DiffuseSeg.y + w){
        diff = lerp(_DiffuseSeg.y,_DiffuseSeg.z,smoothstep(_DiffuseSeg.y - w,_DiffuseSeg.y + w,diff));
  }else if (diff < _DiffuseSeg.z + w){
        diff = lerp(_DiffuseSeg.z,_DiffuseSeg.w,smoothstep(_DiffuseSeg.z - w,_DiffuseSeg.z + w,diff));
  }else{
        diff = _DiffuseSeg.w;
  }

其中_DiffuseSeg為(0.1提岔,0.3仙蛉,0.6,1.0)碱蒙,這個(gè)可以根據(jù)需求自己調(diào)整荠瘪,不必拘泥。為什么要寫的那么復(fù)雜而不是直接給 diff設(shè)值呢赛惩,因?yàn)橹苯淤x值顏色的分界線會(huì)有明顯的抗鋸齒哀墓,所以用fwidth函數(shù)先求出鄰域內(nèi)的梯度值w,再在邊界處+-w進(jìn)行漸變混合來(lái)消除鋸齒感喷兼。
高光區(qū)域也類似篮绰,我們先得到高光項(xiàng),然后判斷范圍季惯,超過(guò)這個(gè)范圍就是1吠各,否則就是0,沒(méi)有高光勉抓。為了抗鋸齒贾漏,我們也得做類似之前做過(guò)的事。

 fixed spec = saturate(dot(worldNormal,halfVector));
 spec = pow(spec,_Gloss); 
 w = fwidth(spec);
 if (spec < _SpecularSeg + w){
      spec = lerp(0,1,smoothstep(_SpecularSeg - w,_SpecularSeg + w,spec));
 }else{
      spec = 1;
 }

后來(lái)我們覺(jué)得在shader里寫了一大堆ifelse效率不高藕筋,所以換了個(gè)實(shí)現(xiàn)方式纵散,這種方式是這篇論文A Non-Photorealistic Lighting Model for Automatic Technical Illustration中提出的一個(gè)公式。

公式1

其中Kcool和Kwarm分別由公式2得到隐圾。
公式2

其中 Kd是漫反射顏色困食, Kblue = (0,0,b),b[0,1],Kwarm = (y,y,0),y[0,1]翎承,alpha和beta都是用戶可調(diào)節(jié)的參數(shù)。

    fixed4 k_blue = fixed4(0,0,_Blue,1);
    fixed4 k_yellow = fixed4(_Yellow,_Yellow,0,1);
    fixed4 k_cool = k_blue + _Alpha * kd;
    fixed4 k_warm = k_yellow + _Beta * kd;

    fixed temp = dot(normalize(worldLight),worldNormal);
    fixed4 diffuse = (1 + temp)/2 * k_cool + (1 - (1+temp/2)) * k_warm;
    diffuse *= _DiffuseCol * _LightColor0 * atten;

最后根據(jù)這篇論文Stylized highlights for cartoon rendering and animation還可以對(duì)高亮區(qū)域進(jìn)行風(fēng)格化符匾,其主要思想就是對(duì)Blinn-Phong模型中的半角向量進(jìn)行修改操作叨咖,實(shí)現(xiàn)高亮區(qū)域的縮放、旋轉(zhuǎn)啊胶、平移甸各、分塊和方塊化。(注意這里的順序不要弄錯(cuò)了Q嫫骸Hで恪!)
代碼如下:

                //縮放
                halfVector -= _ScaleX * halfVector.x * float3(1,0,0);
                halfVector = normalize(halfVector);
                halfVector -= _ScaleY * halfVector.y * float3(0,1,0);
                halfVector = normalize(halfVector);

                //旋轉(zhuǎn)
                float xR = _RotationX * DegreeToRadian;
                float3x3 rotX = float3x3(1, 0, 0,
                                        0, cos(xR), sin(xR),
                                        0, -sin(xR), cos(xR));
                float yR = _RotationY * DegreeToRadian;
                float3x3 rotY = float3x3(cos(yR), 0, -sin(yR),
                                        0, 1, 0,
                                        sin(yR), 0, cos(yR));
                float zR = _RotationZ * DegreeToRadian;
                float3x3 rotZ = float3x3(cos(zR), sin(zR), 0,
                                        -sin(zR), cos(zR), 0,
                                        0, 0, 1);
                halfVector = mul(rotZ,mul(rotY,mul(rotZ,halfVector)));

                //平移
                halfVector += float3(_TranslationX,_TranslationY,0);
                halfVector = normalize(halfVector);

                //分塊
                fixed signX = 1;
                if (halfVector.x < 0){
                    signX = -1;
                }
                fixed signY = 1;
                if (halfVector.y < 0){
                    signY = -1;
                }
                halfVector -= _SplitX * signX * float3(1,0,0) - _SplitY * signY * float3(0,1,0);
                halfVector = normalize(halfVector);

                //方塊化
                float sqrThetaX = acos(halfVector.x);
                float sqrThetaY = acos(halfVector.y);
                fixed sqrnormX = sin(pow(2 * sqrThetaX, _SquareN));
                fixed sqrnormY = sin(pow(2 * sqrThetaY, _SquareN));
                fixed minority = min(sqrnormX,sqrnormY);
                halfVector -= _SquareScale * (minority * halfVector.x * fixed3(1, 0, 0) + minority * halfVector.y * fixed3(0, 1, 0));
                halfVector = normalize(halfVector);

這里我就不展開(kāi)講了某饰,我對(duì)此興趣不是很大>_<
另外我照著公式寫儒恋,也沒(méi)有采用馮樂(lè)樂(lè)前輩的方法同時(shí)用兩個(gè)角度去改變半角向量善绎,結(jié)果調(diào)來(lái)調(diào)去沒(méi)調(diào)出四個(gè)方塊的高亮,奇怪了诫尽。禀酱。。

最后牧嫉,卡通渲染里必不可少的效果剂跟,黑色描邊,經(jīng)過(guò)項(xiàng)目中的實(shí)踐酣藻,過(guò)程式幾何輪廓渲染的方法效果比較令人滿意(雖然它對(duì)cube這種東西沒(méi)有辦法)曹洽。它的思想是先弄個(gè)pass渲染背面,讓頂點(diǎn)沿著扁平化(這里的扁平化其實(shí)就是讓法線向量的z統(tǒng)一成一個(gè)值辽剧,我這里取0.01送淆,網(wǎng)上也有取-0.05或者其他什么值的)過(guò)后的法線方向擴(kuò)張,使得背部可見(jiàn)抖仅,再把這部分渲染成輪廓線的顏色即可坊夫。然后第二個(gè)pass里就做正常的卡通渲染做的事。
代碼如下:

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal); 
                normal.z = 0.01;
                float2 offset = TransformViewToProjection(normal.xy);
                float height = o.vertex.z / unity_CameraProjection._m11;//加入這個(gè)參數(shù)可讓物體描邊在離攝像頭遠(yuǎn)的時(shí)候不至于太細(xì)撤卢,近的時(shí)候不至于太粗
                float scale = sqrt(height / _OutlineScale);
                o.vertex.xy += offset * scale * _Outline;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {  
                return _OutlineCol;
            }

這里有個(gè)技巧环凿,為了讓描邊在鏡頭拉遠(yuǎn)時(shí)不至于看不見(jiàn),拉近時(shí)不至于過(guò)粗放吩,加入了height這個(gè)變量智听,并且進(jìn)行開(kāi)方操作使得它的變化平緩一些,_OutlineScale來(lái)控制平緩的度渡紫。

最后放上實(shí)現(xiàn)的效果到推,模型都是從馮樂(lè)樂(lè)前輩的NPR Labs那兒弄來(lái)的。

效果圖

項(xiàng)目地址

PS. 如果想深入學(xué)習(xí)卡通渲染的化惕澎,unity chan是個(gè)很好的入手項(xiàng)目莉测,unity官方商店有一代,最近他們?nèi)毡緐nity官方又在GitHub上弄了個(gè)二代唧喉,這是地址

參考
【NPR】卡通渲染

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捣卤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子八孝,更是在濱河造成了極大的恐慌董朝,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件干跛,死亡現(xiàn)場(chǎng)離奇詭異子姜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)楼入,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門哥捕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)牧抽,“玉大人,你說(shuō)我怎么就攤上這事扭弧⊙掷眩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵鸽捻,是天一觀的道長(zhǎng)呼巴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)御蒲,這世上最難降的妖魔是什么衣赶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮厚满,結(jié)果婚禮上府瞄,老公的妹妹穿的比我還像新娘。我一直安慰自己碘箍,他們只是感情好遵馆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著丰榴,像睡著了一般货邓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上四濒,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天换况,我揣著相機(jī)與錄音,去河邊找鬼盗蟆。 笑死戈二,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喳资。 我是一名探鬼主播觉吭,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼仆邓!你這毒婦竟也來(lái)了鲜滩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宏赘,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后黎侈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體察署,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年峻汉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贴汪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脐往。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扳埂,靈堂內(nèi)的尸體忽然破棺而出业簿,到底是詐尸還是另有隱情,我是刑警寧澤阳懂,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布梅尤,位于F島的核電站,受9級(jí)特大地震影響岩调,放射性物質(zhì)發(fā)生泄漏巷燥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一号枕、第九天 我趴在偏房一處隱蔽的房頂上張望缰揪。 院中可真熱鬧,春花似錦葱淳、人聲如沸钝腺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)艳狐。三九已至,卻和暖如春坑傅,著一層夾襖步出監(jiān)牢的瞬間僵驰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工唁毒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蒜茴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓浆西,卻偏偏與公主長(zhǎng)得像粉私,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子近零,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344