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的实现逻辑,几个误区先理清楚:
- 原Shader里的
a_v不是WebGL/ThreeJS内置属性,是开发者手动给每个顶点传入的自定义值,含义是当前顶点到所属三角形对边的最短距离,和UV坐标没有任何关系,你直接拿vUv.y赋值必然出问题,也和UV展开方式无关,怀疑错方向了。 - 你代码里定义的
alpha、center属性在这个效果里完全没用,可以直接删掉。 - 原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
相关产品推荐
相关产品推荐

