• three.js使用卷积法实现物体描边效果


    法线延展法

    网上使用法线延展法实现物体描边效果的文章比较多,这里不再描述。

    但是这种方法有个缺点:当两个面的法线夹角差别较大时,两个面的描边无法完美连接。如下图所示:

     卷积法

    这里使用另一种方法卷积法实现物体描边效果,一般机器学习使用该方法比较多。先看效果图:

        

    使用three.js具体的实现方法如下:

    1. 创建着色器材质,隐藏不需要描边的物体进行渲染,将需要描边的位置渲染成白色,其他位置渲染成黑色。
    2. 利用片源着色器计算卷积,白色是物体内部,黑色是物体外部,灰色是边框。
    3. 设置材质透明、不融合,将边框叠加到原图上,可以使用FXAA抗锯齿。

    这三步就可以实现了,很简单吧。下面我们将详细介绍实现方法,不想看的可以直接去看完整实现代码:

    完整代码: https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/helper/SelectHelper.js

    详细的实现过程:

    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. 描边实现完整代码:https://gitee.com/tengge1/ShadowEditor/blob/master/ShadowEditor.Web/src/helper/SelectHelper.js

    2. 基于three.js的开源三维场景编辑器:https://github.com/tengge1/ShadowEditor

    3. three.js后期处理描边:https://threejs.org/examples/#webgl_postprocessing_outline

    4. 卷积工作原理:https://www.zhihu.com/question/39022858?sort=created

    5. 法线延展法实现物体描边:https://blog.csdn.net/srk19960903/article/details/73863853

  • 相关阅读:
    Netty之WebSocket和四种IO介绍
    java递归展示树形图代码实现以及遇到的问题
    String、String Buffer、String Builder
    物联网的开发应该是什么样子?
    最新 去掉 Chrome 新标签页的8个缩略图
    复化梯形求积分——用Python进行数值计算
    每日设置Bing首页图片为壁纸
    分段二次插值——用Python进行数值计算
    埃尔米特插值问题——用Python进行数值计算
    牛顿插值法——用Python进行数值计算
  • 原文地址:https://www.cnblogs.com/tengge/p/11924006.html
Copyright © 2020-2023  润新知