three.js使用卷積法實現物體描邊效果

法線延展法

網上使用法線延展法實現物體描邊效果的文章比較多,這裏不再描述。

但是這種方法有個缺點:當兩個面的法線夾角差別較大時,兩個面的描邊無法完美連接。如下圖所示:

 

 

 卷積法

這裏使用另一種方法卷積法實現物體描邊效果,一般機器學習使用該方法比較多。先看效果圖:

    

 

 

使用three.js具體的實現方法如下:

  1. 創建着色器材質,隱藏不需要描邊的物體進行渲染,將需要描邊的位置渲染成白色,其他位置渲染成黑色。
  2. 利用片源着色器計算卷積,白色是物體內部,黑色是物體外部,灰色是邊框。
  3. 設置材質透明、不融合,將邊框疊加到原圖上,可以使用FXAA抗鋸齒。

這三步就可以實現了,很簡單吧。下面我們將詳細介紹實現方法,不想看的可以直接去看完整實現代碼:

完整代碼: 

詳細的實現過程:

1. 使用three.js正常繪製場景,得到下圖,這裏不介紹了。

 

 

2. 創建着色器材質,隱藏所有不需要描邊的物體。將需要描邊的物體繪製成白色,其他地方繪製成黑色。

隱藏不需要描邊的物體后,將整個場景材質替換。

renderScene.overrideMaterial = this.maskMaterial;

着色器材質:

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. 創建着色器材質進行卷積計算,每四個像素顏色求平均值得到一個像素。描邊物體內部是白色,外部是黑色,物體邊緣處會得到灰色。灰色就是我們所需的邊框。

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是計算卷積的canvas寬度和高度,為了讓邊框更平滑,可以設置為原來canvas的兩倍。color是邊框顏色,thickness是邊框粗細。 注意,要將材質transparent設置為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. 創建着色器材質,將邊框疊加到原來的圖片上。由於FXAA比較複雜,這裏使用簡單的疊加方法。

着色器材質:

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要設置為true,否則會把原來的圖片覆蓋掉。

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. 描邊實現完整代碼:

2. 基於three.js的開源三維場景編輯器:

3. three.js後期處理描邊:

4. 卷積工作原理:

5. 法線延展法實現物體描邊:

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!