three.js利用卷積法如何實(shí)現(xiàn)物體描邊效果
法線延展法
網(wǎng)上使用法線延展法實(shí)現(xiàn)物體描邊效果的文章比較多,這里不再描述。
但是這種方法有個(gè)缺點(diǎn):當(dāng)兩個(gè)面的法線夾角差別較大時(shí),兩個(gè)面的描邊無(wú)法完美連接。如下圖所示:
卷積法
這里使用另一種方法卷積法實(shí)現(xiàn)物體描邊效果,一般機(jī)器學(xué)習(xí)使用該方法比較多。先看效果圖:
使用three.js具體的實(shí)現(xiàn)方法如下:
- 創(chuàng)建著色器材質(zhì),隱藏不需要描邊的物體進(jìn)行渲染,將需要描邊的位置渲染成白色,其他位置渲染成黑色。
- 利用片源著色器計(jì)算卷積,白色是物體內(nèi)部,黑色是物體外部,灰色是邊框。
- 設(shè)置材質(zhì)透明、不融合,將邊框疊加到原圖上,可以使用FXAA抗鋸齒。
這三步就可以實(shí)現(xiàn)了,很簡(jiǎn)單吧。下面我們將詳細(xì)介紹實(shí)現(xiàn)方法,不想看的可以直接去看完整實(shí)現(xiàn)代碼:
完整代碼:https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/helper/SelectHelper.js
詳細(xì)的實(shí)現(xiàn)過(guò)程:
1. 使用three.js正常繪制場(chǎng)景,得到下圖,這里不介紹了。
2. 創(chuàng)建著色器材質(zhì),隱藏所有不需要描邊的物體。將需要描邊的物體繪制成白色,其他地方繪制成黑色。
隱藏不需要描邊的物體后,將整個(gè)場(chǎng)景材質(zhì)替換。
renderScene.overrideMaterial = this.maskMaterial;
著色器材質(zhì):
const maskMaterial = new THREE.ShaderMaterial({ vertexShader: MaskVertex, fragmentShader: MaskFragment, depthTest: false });
MaskVertex:
void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }
MaskFragment:
void main() { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); }
效果圖:
3. 創(chuàng)建著色器材質(zhì)進(jìn)行卷積計(jì)算,每四個(gè)像素顏色求平均值得到一個(gè)像素。描邊物體內(nèi)部是白色,外部是黑色,物體邊緣處會(huì)得到灰色?;疑褪俏覀兯璧倪吙?。
const edgeMaterial = new THREE.ShaderMaterial({ vertexShader: EdgeVertex, fragmentShader: EdgeFragment, uniforms: { maskTexture: { value: this.maskBuffer.texture }, texSize: { value: new THREE.Vector2(width, height) }, color: { value: selectedColor }, thickness: { type: 'f', value: 4 }, transparent: true }, depthTest: false });
其中texSize是計(jì)算卷積的canvas寬度和高度,為了讓邊框更平滑,可以設(shè)置為原來(lái)canvas的兩倍。color是邊框顏色,thickness是邊框粗細(xì)。
注意,要將材質(zhì)transparent設(shè)置為true。
EdgeVertex:
varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }
EdgeFragment:
uniform sampler2D maskTexture; uniform vec2 texSize; uniform vec3 color; uniform float thickness; varying vec2 vUv; void main() { vec2 invSize = thickness / texSize; vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize); vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy); vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy); vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw); vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw); float diff1 = (c1.r - c2.r)*0.5; float diff2 = (c3.r - c4.r)*0.5; float d = length(vec2(diff1, diff2)); gl_FragColor = d > 0.0 ? vec4(color, 1.0) : vec4(0.0, 0.0, 0.0, 0.0); }
效果圖:
4. 創(chuàng)建著色器材質(zhì),將邊框疊加到原來(lái)的圖片上。由于FXAA比較復(fù)雜,這里使用簡(jiǎn)單的疊加方法。
著色器材質(zhì):
const copyMaterial = new THREE.ShaderMaterial({ vertexShader: CopyVertexShader, fragmentShader: CopyFragmentShader, uniforms: { tDiffuse: { value: edgeBuffer.texture }, resolution: { value: new THREE.Vector2(1 / width, 1 / height) } }, transparent: true, depthTest: false });
注意,transparent要設(shè)置為true,否則會(huì)把原來(lái)的圖片覆蓋掉。
CopyVertexShader:
varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }
CopyFragmentShader:
uniform float opacity; uniform sampler2D tDiffuse; varying vec2 vUv; void main() { vec4 texel = texture2D( tDiffuse, vUv ); gl_FragColor = opacity * texel; }
得到最終效果圖:
參考資料:
1. 描邊實(shí)現(xiàn)完整代碼:https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/helper/SelectHelper.js
2. 基于three.js的開(kāi)源三維場(chǎng)景編輯器:https://github.com/tengge1/ShadowEditor
3. three.js后期處理描邊:https://threejs.org/examples/
4. 卷積工作原理:https://www.zhihu.com/question/39022858?sort=created
5. 法線延展法實(shí)現(xiàn)物體描邊:http://www.dbjr.com.cn/article/175213.htm
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
BootstrapTable+KnockoutJS自定義T4模板快速生成增刪改查頁(yè)面
這篇文章主要介紹了BootstrapTable+KnockoutJS自定義T4模板快速生成增刪改查頁(yè)面 的相關(guān)資料,需要的朋友可以參考下2016-08-08利用BootStrap彈出二級(jí)對(duì)話框的簡(jiǎn)單實(shí)現(xiàn)方法
彈出二級(jí)對(duì)話框,即在對(duì)話框的基礎(chǔ)上再?gòu)棾鲆粋€(gè)對(duì)話框.這篇文章主要介紹了利用BootStrap彈出二級(jí)對(duì)話框的簡(jiǎn)單實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2016-09-09JavaScript的String字符串對(duì)象常用操作總結(jié)
String對(duì)象用于存儲(chǔ)字符串?dāng)?shù)據(jù),這里我們做了JavaScript的String字符串對(duì)象常用操作總結(jié),需要的朋友可以參考下2016-05-05JavaScript 申明函數(shù)的三種方法 每個(gè)函數(shù)就是一個(gè)對(duì)象(一)
JavaScript 申明函數(shù)的三種方法 每個(gè)函數(shù)就是一個(gè)對(duì)象(一)2009-12-12解決微信授權(quán)成功后點(diǎn)擊按返回鍵出現(xiàn)空白頁(yè)和報(bào)錯(cuò)的問(wèn)題
這篇文章主要介紹了解決微信授權(quán)成功后點(diǎn)擊按返回鍵出現(xiàn)空白頁(yè)和報(bào)錯(cuò)的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Laydate時(shí)間組件在火狐瀏覽器下有多時(shí)間輸入框時(shí)只能給第一個(gè)輸入框賦值的解決方法
這篇文章主要介紹了Laydate時(shí)間組件在火狐瀏覽器下有多時(shí)間輸入框時(shí)只能給第一個(gè)輸入框賦值的解決方法,需要的朋友可以參考下2016-08-08