如何通过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
相关产品推荐
相关产品推荐

