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

ThreeJS移植外部WebGL Shader实现边框效果异常问题

问题背景

我尝试将非ThreeJS原生的线框边框Shader应用到ThreeJS几何体上,目前未能成功实现预期效果。
以下是参考的原始Shader代码:

<script id="vs" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute float a_v;

varying float v_v;

void main() {
  // 常规WebGL项目中至少需要给a_position乘变换矩阵
  gl_Position = a_position;
  v_v = a_v;
}
</script>

<script id="fs" type="x-shader/x-fragment">
precision mediump float;

varying float v_v;

uniform float u_borderSize;
uniform vec4 u_baseColor;
uniform vec4 u_borderColor;

void main() {
   float mixAmount = step(u_borderSize, v_v);
   gl_FragColor = mix(u_baseColor, 
                      u_borderColor, 
                      mixAmount);
}
</script>

以下是我编写的适配ThreeJS的代码:

var vertexShader = `
    varying vec2 vUv;

    attribute float alpha;
    varying float vAlpha;
    
    attribute vec3 center;
    varying vec3 vCenter;

    void main() {
        vUv = uv;
        vAlpha = alpha;
        vCenter = center;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;
var fragmentShader = `
    uniform vec3 color1;
    uniform float color1Alpha;
    uniform vec3 borderColor;
    uniform float borderThickness;

    varying vec2 vUv;
    varying float vAlpha;
    varying vec3 vCenter;
    
    void main() {
        float u_borderSize = borderThickness;
        vec4 u_baseColor = vec4(color1, color1Alpha);
        vec4 u_borderColor = vec4(borderColor, 1.0);

        // 卡壳点:不知道怎么获取正确的v_v值
        float v_v = vUv.y;

        float mixAmount = step(u_borderSize, v_v);
        gl_FragColor = mix(u_baseColor, 
                            u_borderColor, 
                            mixAmount);
    }
`;

预期效果

线框边框效果参考图

现存疑问

  • 不清楚当前实现缺失哪些必要环节,也不明确顶点属性a_v的来源逻辑;
  • 怀疑取值异常是几何体UV展开方式不当导致的;
  • 不确定参考的原始Shader是否适配我的实现需求。

解答

你的问题核心是没搞懂原Shader的实现逻辑,几个误区先理清楚:

  1. 原Shader里的a_v不是WebGL/ThreeJS内置属性,是开发者手动给每个顶点传入的自定义值,含义是当前顶点到所属三角形对边的最短距离,和UV坐标没有任何关系,你直接拿vUv.y赋值必然出问题,也和UV展开方式无关,怀疑错方向了。
  2. 你代码里定义的alpha、center属性在这个效果里完全没用,可以直接删掉。
  3. 原Shader的逻辑完全可以实现你要的三角形线框边框效果,只要补上a_v属性的计算环节就行。

具体实现步骤

1. 处理几何体

ThreeJS默认生成的BufferGeometry大多是索引结构,同一个顶点会被多个三角形复用,没法给同一个顶点赋不同三角形对应的边距值,所以首先要把几何体转成非索引结构:

// 以立方体为例,换成你自己的几何体即可
const geometry = new THREE.BoxGeometry(2, 2, 2).toNonIndexed();
const posAttr = geometry.getAttribute('position');
const vertexCount = posAttr.count;
// 新建存储边距的数组,每个顶点对应1个浮点值
const edgeDistanceArr = new Float32Array(vertexCount);

// 每3个顶点为一组,遍历所有三角形
for (let i = 0; i < vertexCount; i += 3) {
  // 读取当前三角形三个顶点的坐标
  const p0 = new THREE.Vector3(posAttr.getX(i), posAttr.getY(i), posAttr.getZ(i));
  const p1 = new THREE.Vector3(posAttr.getX(i+1), posAttr.getY(i+1), posAttr.getZ(i+1));
  const p2 = new THREE.Vector3(posAttr.getX(i+2), posAttr.getY(i+2), posAttr.getZ(i+2));

  // 计算每个顶点到对边的距离,存入数组
  edgeDistanceArr[i] = new THREE.Line3(p1, p2).distanceToPoint(p0);
  edgeDistanceArr[i+1] = new THREE.Line3(p0, p2).distanceToPoint(p1);
  edgeDistanceArr[i+2] = new THREE.Line3(p0, p1).distanceToPoint(p2);
}

// 将边距数组作为自定义属性挂到几何体上,属性名要和shader里的attribute对应
geometry.setAttribute('a_v', new THREE.BufferAttribute(edgeDistanceArr, 1));

2. 修正Shader代码

顶点着色器只需要保留必要的变换和a_v传值:

attribute float a_v;
varying float v_v;

void main() {
  v_v = a_v;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

片元着色器注意step的判断逻辑:v_v值越小代表离三角形边越近,小于边框厚度时要显示边框色,别把mix的颜色顺序写反:

precision mediump float;
uniform vec3 color1;
uniform float color1Alpha;
uniform vec3 borderColor;
uniform float borderThickness;

varying float v_v;

void main() {
  vec4 baseColor = vec4(color1, color1Alpha);
  vec4 borderCol = vec4(borderColor, 1.0);
  // v_v < borderThickness 时mixAmount为0,取边框色;大于时取基础色
  float mixAmount = step(borderThickness, v_v);
  gl_FragColor = mix(borderCol, baseColor, mixAmount);
}

3. 材质配置

用ShaderMaterial的时候记得把side设为THREE.DoubleSide,不然几何体背面的边框不会显示。


内容的提问来源于stack exchange,提问作者Eketol

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 00:24:24