Three.js卷积法大显身手!告别断裂,轻松实现平滑3D物体描边效果

2024-06-21 开发技术 140 次阅读 0 次点赞
本文介绍了如何使用Three.js结合卷积法实现平滑的物体描边效果,用于物体选中高亮或轮廓识别。文章指出传统法线延展法存在法线夹角大时描边断裂的缺陷,而卷积法通过图像处理中的卷积运算检测边缘,效果更自然。实现步骤包括:首先生成掩膜纹理(将目标物体渲染为白色,其他区域黑色),然后通过片元着色器进行卷积计算得到灰色边框,最后将边框叠加到原始场景上。该方法相比法线延展法更平滑,能有效避免断裂问题,提升三维场景视觉体验。

在三维场景中,物体描边效果是一种常见的视觉增强技术,广泛应用于物体选中高亮、轮廓识别等场景。本文将介绍如何使用Three.js结合卷积法实现平滑的物体描边效果。

法线延展法的局限

网上关于使用法线延展法实现物体描边的文章很多,这种方法通过沿法线方向扩展模型顶点来生成描边。然而,这种方法存在一个明显的缺陷:当两个面的法线夹角差别较大时,两个面的描边无法完美连接,产生明显的断裂感。

法线延展法描边的问题

卷积法解决方案

卷积法是另一种实现物体描边的方法,在机器学习领域应用广泛。这种方法通过图像处理中的卷积运算来检测物体边缘,效果更加平滑自然。

最终效果预览

卷积法描边效果图1

卷积法描边效果图2

卷积法描边效果图3

实现步骤

使用Three.js实现卷积描边只需三步:

  1. 生成掩膜纹理:隐藏不需要描边的物体,将需要描边的物体渲染成白色,其他区域渲染成黑色
  2. 卷积计算:通过片元着色器进行卷积计算,白色区域为物体内部,黑色为外部,灰色为边框
  3. 叠加边框:设置材质透明,将计算出的边框叠加到原图上

详细实现

第一步:正常绘制场景

首先使用Three.js正常绘制三维场景,这一步不做特殊处理。

Three.js正常绘制场景

第二步:创建掩膜材质

创建着色器材质,隐藏不需要描边的物体,将需要描边的物体绘制成白色。

// 设置覆盖材质
renderScene.overrideMaterial = this.maskMaterial;

掩膜顶点着色器 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);
}

效果如下图所示,需要描边的物体显示为白色:

Three.js描边掩膜效果

第三步:卷积计算边框

创建着色器材质进行卷积计算,对每四个像素的颜色求平均值得到一个像素。物体内部为白色,外部为黑色,边缘处会得到灰色的边框。

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的两倍。同时要将材质 transparent 设置为 true

卷积片元着色器 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);
}

卷积计算后的边框效果:

Three.js卷积边框

第四步:叠加边框到原图

创建着色器材质,将卷积计算出的边框叠加到原始场景上。

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,否则边框会覆盖原图。

叠加片元着色器 CopyFragmentShader

uniform float opacity;
uniform sampler2D tDiffuse;
varying vec2 vUv;

void main() {
    vec4 texel = texture2D( tDiffuse, vUv );
    gl_FragColor = opacity * texel;
}

最终效果

完成上述步骤后,即可获得平滑的物体描边效果:

Three.js描边最终效果

参考资料

Three.js后期处理描边示例:https://threejs.org/examples/#webgl_postprocessing_outline

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

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

总结

卷积法相比法线延展法,能够产生更加平滑的描边效果,避免因法线夹角导致的断裂问题。通过Three.js的着色器材质和卷积运算,我们可以轻松实现这一效果,为三维场景中的物体选中、高亮显示等功能提供更好的视觉体验。

最后更新于1小时前
本文由人工编写,AI优化,转载请注明原文地址: Three.js卷积法大显身手!告别断裂,轻松实现平滑3D物体描边效果

评论 (0)

登录 后发表评论

暂无评论,快来发表第一条评论吧!