threejs-卡通輪廓效果(threejs模型輪廓發(fā)光)
在我的 3D 游戲和設(shè)計(jì)中,我經(jīng)常選擇可愛(ài)的低多邊形卡通風(fēng)格。我一直想給我的模型一個(gè)真正的卡通輪廓,所以這就是我們今天要做的工作。在以后的文章中,我們將著眼于為三角形著色以使其看起來(lái)也很卡通。
本文是「我正在進(jìn)行的中等難度 ThreeJS 教程系列」的一部分。我一直想要在介紹“如何繪制立方體”和“讓我們用瘋狂的著色器填充屏幕”關(guān)卡之間找到一些東西。所以就在這里。
如果你在網(wǎng)上搜索“OpenGL 輪廓效果”,你會(huì)遇到很多相互矛盾的信息。經(jīng)過(guò)大量研究,我確定有兩種常用方法可以使用現(xiàn)代 3D 圖形 API 創(chuàng)建輪廓。
- 繪制一個(gè)對(duì)象兩次;一次是輪廓顏色,一次是正常。
- 通過(guò)在像素級(jí)別檢測(cè)邊緣的后處理效果運(yùn)行整個(gè)場(chǎng)景。
第二種選擇是當(dāng)今 Unity 等現(xiàn)代游戲引擎中最常用的。但是我不想使用它,因?yàn)樗婕岸鄠€(gè)后處理步驟,這在移動(dòng) GPU 上可能很慢并且消耗更多內(nèi)存。此外,后處理和 WebVR 目前還沒(méi)有很好地融合,所以我暫時(shí)避免使用它。(當(dāng)我介紹發(fā)光效果時(shí),我們會(huì)重新討論這個(gè))。讓我們關(guān)注第一種方法,兩次繪制同一個(gè)對(duì)象。
兩次渲染一個(gè)對(duì)象可能看起來(lái)很浪費(fèi),但請(qǐng)記住,大多數(shù) GPU 都是帶寬有限的。一旦你把幾何圖形傳到 GPU 上,它就可以一遍又一遍地渲染同樣的東西,幾乎沒(méi)有成本。在大多數(shù)情況下,我想概述的東西是靜態(tài)幾何。
讓我們從兩次渲染圓環(huán)結(jié)開(kāi)始,一次是黑色,一次是黃色。完成這項(xiàng)工作的這個(gè)技巧是縮放輪廓,使其比主要對(duì)象略大。
//create a cubeobj = new THREE.Group()const c1 = new THREE.Mesh( new THREE.TorusKnotBufferGeometry(0.6,0.1), new THREE.MeshLambertMaterial({ color:’black’, }))const s = 1.03c1.scale.set(s,s,s)obj.add(c1)obj.add(new THREE.Mesh( new THREE.TorusKnotBufferGeometry(0.6,0.1), new THREE.MeshPhongMaterial({ color:’yellow’, })))
運(yùn)行它,看看會(huì)發(fā)生什么。唔。根據(jù)您使用的幾何形狀,您將看到全黑或雙色調(diào)、部分黑色和部分黃色的東西。還注意到一些從黃色中突出的黑色三角形嗎?這就是所謂的z戰(zhàn)斗。那么問(wèn)題是什么。
實(shí)際上,這有點(diǎn)道理。黑結(jié)略微擴(kuò)大,因此在看不到黃色的地方,黑夜就在它的前面。那么我們?cè)撊绾谓鉀Q呢?
15 秒內(nèi)解釋剔除
我們將利用一個(gè)小技巧。
當(dāng) GPU 渲染三角形時(shí),它通常只渲染正面的三角形。這些是面向相機(jī)的三角形。根據(jù)定義,任何背對(duì)相機(jī)的三角形都是不可見(jiàn)的,因此無(wú)需費(fèi)心繪制它們。如果我們有一個(gè)球體,那么實(shí)際上只會(huì)繪制前半球。GPU 已「剔除」構(gòu)成背面半球的三角形。
對(duì)于輪廓效果,我們希望僅使用正面幾何圖形繪制常規(guī)對(duì)象。這已經(jīng)在發(fā)生。但是,對(duì)于輪廓,我們只希望繪制背面的三角形。然后它們將位于規(guī)則形狀的后面,僅在邊緣可見(jiàn)。
碰巧的是,ThreeJS 已經(jīng)知道如何繪制正面和背面。我們只需要告訴它我們想要什么。下面的代碼和上面一樣,只是side正確設(shè)置了兩種材質(zhì)的屬性。
obj = new THREE.Group()const c1 = new THREE.Mesh( new THREE.TorusKnotBufferGeometry(0.6,0.1), new THREE.MeshLambertMaterial({ color:’black’, side: THREE.BackSide }))const s = 1.03c1.scale.set(s,s,s)obj.add(c1)obj.add(new THREE.Mesh( new THREE.TorusKnotBufferGeometry(0.6,0.1), new THREE.MeshPhongMaterial({ color:’yellow’, side: THREE.FrontSide })))
現(xiàn)在看起來(lái)像這樣:
image.png
完美的!
修復(fù)法線
實(shí)際上不,它不是很完美。如果你仔細(xì)觀察,你會(huì)發(fā)現(xiàn)物體背面的輪廓比正面的輪廓要細(xì)。那是因?yàn)槲覀冎皇窃诜糯笳麄€(gè)對(duì)象。這種幼稚的方法只適用于像球體這樣的完美凸面物體。對(duì)于結(jié)或任何真實(shí)模型,我們需要正確地加厚幾何圖形。
碰巧大多數(shù)幾何圖形上已經(jīng)有法線。這些法線垂直于幾何體的表面。如果我們?cè)诜ň€方向上擴(kuò)展點(diǎn),那么一切都應(yīng)該正常工作。我們可以通過(guò)稍微修改的頂點(diǎn)著色器來(lái)做到這一點(diǎn)。有關(guān)自定義頂點(diǎn)著色器的說(shuō)明,請(qǐng)參閱其他文章。
//create a cubeconst mat = new THREE.MeshLambertMaterial({ color:’black’, side:THREE.BackSide })mat.onBeforeCompile = (shader) => { const token = ‘#include <begin_vertex>’ const customTransform = ` vec3 transformed = position objectNormal*0.02; ` shader.vertexShader = shader.vertexShader.replace(token,customTransform)}
上面的代碼為輪廓對(duì)象創(chuàng)建了一個(gè)自定義材質(zhì)。其余與之前相同。在著色器內(nèi)部,它objectNormal向每個(gè)頂點(diǎn)的位置添加一小部分,將其向外擴(kuò)展。將 更改0.02為更大的值以獲得更粗的輪廓。
現(xiàn)在看起來(lái)像這樣:
image.png
壯麗的。您已經(jīng)創(chuàng)建了一個(gè)類(lèi)似于卡通的輪廓。