WebGL环形绘制问题:如何修改现有代码达成目标效果
解决WebGL绘制带宽度环形的问题
你的代码当前存在两个核心问题:
- 使用
gl.LINE_STRIP绘制会将大圆的最后一个顶点与小圆的第一个顶点连接,产生多余线段; LINE_STRIP是绘制连续线条,无法实现填充的环形效果(若你需要的是填充圆环)。
下面提供两种可靠的实现方案,对应你最初的两种思路:
方案一:三角剖分生成填充环形
通过生成内外环交替的顶点,使用TRIANGLE_STRIP绘制填充的环形,这是兼容性最好、性能最优的方案。
修改后的代码
const TAU_SEGMENTS = 360; const TAU = Math.PI * 2; // 生成环形的三角带顶点数据 export function ring(x0: number, y0: number, outerRadius: number, innerRadius: number, startAng = 0, endAng = TAU) { const ang = Math.min(TAU, endAng - startAng); const segments = Math.round(TAU_SEGMENTS * ang / TAU); const ret: number[] = []; // 按「外点→内点」的顺序生成每个分段的顶点 for(let i = 0; i <= segments; i++) { const currentAng = startAng + ang * i / segments; // 外圆顶点 const outerX = x0 + outerRadius * Math.cos(currentAng); const outerY = y0 + outerRadius * Math.sin(currentAng); ret.push(outerX, outerY); // 内圆顶点 const innerX = x0 + innerRadius * Math.cos(currentAng); const innerY = y0 + innerRadius * Math.sin(currentAng); ret.push(innerX, innerY); } // 完整圆环需要闭合三角带,添加首尾重复顶点 if (ang === TAU) { const firstOuterX = x0 + outerRadius * Math.cos(startAng); const firstOuterY = y0 + outerRadius * Math.sin(startAng); ret.push(firstOuterX, firstOuterY); const firstInnerX = x0 + innerRadius * Math.cos(startAng); const firstInnerY = y0 + innerRadius * Math.sin(startAng); ret.push(firstInnerX, firstInnerY); } return ret; } // WebGL绘制逻辑 const gl = container.current.getContext("webgl2"); const program = initProgram(gl); // 生成外半径1、内半径0.9的环形顶点 const position = new Float32Array(ring(0, 0, 1, 0.9)); const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW); const gl_position = gl.getAttribLocation(program, "position"); gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(gl_position); // 使用TRIANGLE_STRIP绘制填充环形 gl.drawArrays(gl.TRIANGLE_STRIP, 0, position.length / 2);
原理说明
- 顶点按「外圆顶点→内圆顶点」的顺序交替排列,
TRIANGLE_STRIP会自动将相邻的三个顶点组合成三角形,最终形成填充的环形; - 完整圆环添加首尾重复顶点,避免三角带末端出现缺口。
方案二:模板测试实现「大圆挖小圆」
若不想生成复杂的环形顶点,可通过模板缓冲区实现叠加挖空效果,适合背景为纯色的场景。
修改后的代码
const TAU_SEGMENTS = 360; const TAU = Math.PI * 2; // 修改arc函数,返回中心+圆周点,用于TRIANGLE_FAN绘制填充圆 export function arc(x0: number, y0: number, radius: number, startAng = 0, endAng = TAU) { const ang = Math.min(TAU, endAng - startAng); const ret = [x0, y0]; // 始终添加中心顶点 const segments = Math.round(TAU_SEGMENTS * ang / TAU); for(let i = 0; i <= segments; i++) { const x = x0 + radius * Math.cos(startAng + ang * i / segments); const y = y0 + radius * Math.sin(startAng + ang * i / segments); ret.push(x, y); } return ret; } // WebGL绘制逻辑 const gl = container.current.getContext("webgl2"); const program = initProgram(gl); // 生成大圆和小圆的顶点数据 const outerCircle = new Float32Array(arc(0, 0, 1)); const innerCircle = new Float32Array(arc(0, 0, 0.9)); // 创建缓冲区 const outerBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, outerBuffer); gl.bufferData(gl.ARRAY_BUFFER, outerCircle, gl.STATIC_DRAW); const innerBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, innerBuffer); gl.bufferData(gl.ARRAY_BUFFER, innerCircle, gl.STATIC_DRAW); const gl_position = gl.getAttribLocation(program, "position"); gl.vertexAttribPointer(gl_position, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(gl_position); // 开启模板测试 gl.enable(gl.STENCIL_TEST); gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); // 第一步:标记大圆区域到模板缓冲区 gl.stencilFunc(gl.ALWAYS, 1, 0xFF); // 所有片段都通过测试,模板值设为1 gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); // 测试通过时替换模板值 gl.colorMask(false, false, false, false); // 不写入颜色缓冲区 gl.bindBuffer(gl.ARRAY_BUFFER, outerBuffer); gl.drawArrays(gl.TRIANGLE_FAN, 0, outerCircle.length / 2); // 第二步:绘制大圆颜色 gl.colorMask(true, true, true, true); // 恢复颜色写入 gl.stencilFunc(gl.EQUAL, 1, 0xFF); // 只绘制模板值为1的区域 gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); // 不修改模板值 // 假设你的着色器支持传入颜色,这里设置为环形的颜色(比如红色) // gl.uniform4f(gl.getUniformLocation(program, "u_color"), 1, 0, 0, 1); gl.bindBuffer(gl.ARRAY_BUFFER, outerBuffer); gl.drawArrays(gl.TRIANGLE_FAN, 0, outerCircle.length / 2); // 第三步:用背景色绘制小圆,实现挖空 // 设置为背景色(比如白色) // gl.uniform4f(gl.getUniformLocation(program, "u_color"), 1, 1, 1, 1); gl.bindBuffer(gl.ARRAY_BUFFER, innerBuffer); gl.drawArrays(gl.TRIANGLE_FAN, 0, innerCircle.length / 2); // 关闭模板测试 gl.disable(gl.STENCIL_TEST);
原理说明
- 先通过模板缓冲区标记出大圆的区域;
- 在标记区域内绘制大圆的颜色;
- 再在同一区域内用背景色绘制小圆,相当于挖空中间部分,形成环形。
原代码问题总结
- 你将两个圆的顶点拼接后用
LINE_STRIP绘制,会导致大圆与小圆的顶点之间产生多余连线; - 若你仅需要绘制粗线条环形,可分别用
LINE_LOOP绘制两个圆,但gl.lineWidth()存在平台兼容性问题(多数设备最大支持宽度不超过10),因此不推荐这种方式。
内容的提问来源于stack exchange,提问作者Link
相关产品推荐
相关产品推荐

