You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Shader在Three.js中增加EdgesGeometry的线条厚度?

用Shader实现Three.js无面边缘效果(替代EdgesGeometry/线框)

嘿,我完全懂你的需求——要那种只露几何体边缘、没有任何面的效果,而且因为WebGL对linewidth的限制,没法靠原生线框实现,必须用Shader来搞定对吧?我给你一套直接能用的方案,还会拆解关键逻辑,你跟着来就行:

核心思路

我们的目标是在片元着色器里判断当前像素是否属于几何体的边缘,只渲染边缘部分,非边缘区域直接透明。判断边缘的常用方法有两种:

  • 基于相邻顶点的法线差异:如果两个相邻面的法线夹角超过某个阈值,就认为是边缘
  • 基于相邻顶点的位置差异:如果顶点在屏幕空间的位置差超过阈值,就标记为边缘

下面的Shader用的是第一种方法(法线差异),效果更稳定,适合大多数几何体。

完整着色器代码

顶点着色器

varying vec3 vNormal;
varying vec3 vViewPosition;

void main() {
    vNormal = normalize(normalMatrix * normal);
    vViewPosition = normalize((modelViewMatrix * vec4(position, 1.0)).xyz);
    
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

片元着色器

varying vec3 vNormal;
varying vec3 vViewPosition;

// 边缘检测阈值,值越大,边缘越粗
uniform float edgeThreshold;
// 边缘颜色
uniform vec3 edgeColor;

void main() {
    // 计算屏幕空间的法线导数(相邻像素的法线变化)
    vec3 dNormaldx = dFdx(vNormal);
    vec3 dNormaldy = dFdy(vNormal);
    
    // 计算法线的变化量,用来判断是否是边缘
    float edgeFactor = length(dNormaldx) + length(dNormaldy);
    
    // 如果变化量超过阈值,渲染边缘颜色,否则透明
    if (edgeFactor > edgeThreshold) {
        gl_FragColor = vec4(edgeColor, 1.0);
    } else {
        gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
    }
}

怎么在Three.js里使用

你需要创建一个ShaderMaterial,把上面的着色器传进去,然后和你的几何体绑定:

// 1. 创建几何体(比如一个立方体)
const geometry = new THREE.BoxGeometry(2, 2, 2);

// 2. 配置ShaderMaterial
const edgeShaderMaterial = new THREE.ShaderMaterial({
    vertexShader: 上面的顶点着色器代码,
    fragmentShader: 上面的片元着色器代码,
    uniforms: {
        edgeThreshold: { value: 0.1 }, // 调整这个值控制边缘粗细
        edgeColor: { value: new THREE.Color(0xffffff) } // 边缘颜色
    },
    transparent: true, // 开启透明,让非边缘区域消失
    side: THREE.DoubleSide // 如果是单面几何体,可能需要开双面渲染
});

// 3. 创建网格并添加到场景
const mesh = new THREE.Mesh(geometry, edgeShaderMaterial);
scene.add(mesh);

关键细节说明

  • edgeThreshold:值越大,被判定为边缘的区域越宽,你可以根据自己的需求调整(比如0.05是细边缘,0.2是粗边缘)
  • 如果你的几何体是复杂模型,可能需要调整side为THREE.DoubleSide,确保内外边缘都能被检测到
  • 如果你想基于位置差异来检测边缘,可以把片元着色器里的法线导数换成位置导数:
    vec3 dPosdx = dFdx(vViewPosition);
    vec3 dPosdy = dFdy(vViewPosition);
    float edgeFactor = length(dPosdx) + length(dPosdy);
    
    这种方法适合一些法线变化不明显但位置有突变的边缘场景。

额外优化

如果觉得边缘有点锯齿,可以开启antialias(在WebGLRenderer初始化时设置antialias: true),或者在片元着色器里对边缘做平滑处理:

// 替换原来的if判断,用smoothstep做平滑过渡
float alpha = smoothstep(edgeThreshold - 0.01, edgeThreshold + 0.01, edgeFactor);
gl_FragColor = vec4(edgeColor, alpha);

这样就能得到平滑的边缘效果啦,完全不需要线框或者EdgesGeometry,而且不受linewidth的限制!

内容的提问来源于stack exchange,提问作者s.z.r.harrison

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:08:27