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

WebGL环形绘制问题:如何修改现有代码达成目标效果

解决WebGL绘制带宽度环形的问题

你的代码当前存在两个核心问题:

  1. 使用gl.LINE_STRIP绘制会将大圆的最后一个顶点与小圆的第一个顶点连接,产生多余线段;
  2. 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);

原理说明

  1. 先通过模板缓冲区标记出大圆的区域;
  2. 在标记区域内绘制大圆的颜色;
  3. 再在同一区域内用背景色绘制小圆,相当于挖空中间部分,形成环形。

原代码问题总结

  • 你将两个圆的顶点拼接后用LINE_STRIP绘制,会导致大圆与小圆的顶点之间产生多余连线;
  • 若你仅需要绘制粗线条环形,可分别用LINE_LOOP绘制两个圆,但gl.lineWidth()存在平台兼容性问题(多数设备最大支持宽度不超过10),因此不推荐这种方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 18:35:21