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

如何用单个顶点着色器在WebGL中绘制多条三次贝塞尔曲线?

多曲线WebGL绘制问题:仅显示第一条三次贝塞尔曲线

我要实现一个WebGL程序,通过单个顶点着色器在画布上显示多条三次贝塞尔曲线,让着色器为每条曲线计算指定数量的插值点。

已知条件:

  • 曲线数量为N,每条曲线由4个控制点定义,需传入N*4个控制点
  • 每条曲线的计算精度为precision,生成包含precision个0到1之间插值值的ts数组(比如precision=6时,ts为[0,0.2,0.4,0.6,0.8,1])

需求:通过控制点缓冲区和ts缓冲区,让顶点着色器并行计算N条曲线的所有插值点,计算遵循三次贝塞尔公式:

curve(t) = (1 - t)³ * p0 + 3*(1 - t)²tp1 + 3*(1 - t)t²p2 + t³*p3

当前问题:编写了顶点着色器和WebGL代码后,运行仅显示第一条曲线的点,推测问题出在vertexAttribPointer和vertexAttribDivisor参数设置上,请求排查修复。

顶点着色器代码

attribute vec2 p0;
attribute vec2 p1;
attribute vec2 p2;
attribute vec2 p3;
attribute float t;

uniform mat4 transformation;

varying vec4 fragmentColor;

void main() {
    float tSquared = t * t;
    float tCube = tSquared * t;
    float _t = 1.0 - t;
    float _tSquared = _t * _t;
    float _tCube = _tSquared * _t;
    vec4 p = transformation * vec4(_tCube * p0 + 3.0 * _tSquared * t * p1 + 3.0 * _t * tSquared * p2 + tCube * p3, 1.0, 1.0);
    fragmentColor = vec4(1.0) - p;
    gl_PointSize = 4.0;
    gl_Position = p;
}

WebGL实现代码

let vertexShader = context.createShader(context.VERTEX_SHADER)
context.shaderSource(vertexShader, await (await fetch('./shaders/multi-bezier-points-computer.glsl')).text())
context.compileShader(vertexShader)
if (!context.getShaderParameter(vertexShader, context.COMPILE_STATUS)) {
    console.error(context.getShaderInfoLog(vertexShader))
    context.deleteShader(vertexShader)
    return
}

let fragmentShader = context.createShader(context.FRAGMENT_SHADER)
context.shaderSource(fragmentShader, await (await fetch('./shaders/fragment-shader.glsl')).text())
context.compileShader(fragmentShader)
if (!context.getShaderParameter(fragmentShader, context.COMPILE_STATUS)) {
    console.error(context.getShaderInfoLog(fragmentShader))
    context.deleteShader(fragmentShader)
    return
}

const program: WebGLProgram = context.createProgram()
context.attachShader(program, vertexShader)
context.attachShader(program, fragmentShader)
context.linkProgram(program)
if (!context.getProgramParameter(program, context.LINK_STATUS)) {
    console.error(context.getProgramInfoLog(program))
    context.deleteProgram(program)
    return
}

context.useProgram(program)

const precision = 30

let controlPoints = new Float32Array([
    -0.5, 0.5,
    -0.25, -0.5,
    0.25, 0.5,
    0.5, -0.5,
    0.5, -0.5,
    0.45, 0.5,
    -0.1, -0.2,
    -0.7, 0.7
])
const controlPointsBuffer = context.createBuffer()
context.bindBuffer(context.ARRAY_BUFFER, controlPointsBuffer)
context.bufferData(context.ARRAY_BUFFER, controlPoints, context.STATIC_DRAW)
const p0 = context.getAttribLocation(program, 'p0')
const p1 = context.getAttribLocation(program, 'p1')
const p2 = context.getAttribLocation(program, 'p2')
const p3 = context.getAttribLocation(program, 'p3')
const stride = 2 * Float32Array.BYTES_PER_ELEMENT
context.vertexAttribPointer(p0, 2, context.FLOAT, false, stride, 0)
context.vertexAttribPointer(p1, 2, context.FLOAT, false, stride, stride)
context.vertexAttribPointer(p2, 2, context.FLOAT, false, stride, 2 * stride)
context.vertexAttribPointer(p3, 2, context.FLOAT, false, stride, 3 * stride)
context.vertexAttribDivisor(p0, precision)
context.vertexAttribDivisor(p1, precision)
context.vertexAttribDivisor(p2, precision)
context.vertexAttribDivisor(p3, precision)
context.enableVertexAttribArray(p0)
context.enableVertexAttribArray(p1)
context.enableVertexAttribArray(p2)
context.enableVertexAttribArray(p3)

let ts = new Float32Array(precision * Math.ceil(controlPoints.length / 12))
for (let j = 0; j < Math.ceil(controlPoints.length / 12); j++) {
    for (let i = 0; i < precision; i++) {
        ts[i + j * precision] = i / (precision - 1)
    }
}
const tsBuffer = context.createBuffer()
context.bindBuffer(context.ARRAY_BUFFER, tsBuffer)
context.bufferData(context.ARRAY_BUFFER, ts, context.STATIC_DRAW)
let t = context.getAttribLocation(program, 't')
context.vertexAttribPointer(t, 1, context.FLOAT, false, 0, 0)
context.enableVertexAttribArray(t)
const transformation = context.getUniformLocation(program, 'transformation')
let matrix = [
    Math.cos(0), -Math.sin(0), 0, 0,
    Math.sin(0), Math.cos(0), 0, 0,
    0, 0, 0, 0,
    0, 0, 0, 1
]
context.uniformMatrix4fv(transformation, false, matrix)
context.clear(context.COLOR_BUFFER_BIT)
context.drawArrays(context.POINTS, 0, ts.length)

问题排查与修复

1. 核心错误点

  • 控制点缓冲区步长(stride)错误:当前步长仅设置为单个点的字节数,导致控制点被交叉读取,无法区分不同曲线的控制点组。
  • 顶点属性除数(vertexAttribDivisor)错误:给控制点属性设置的除数为precision,逻辑上应该是每绘制一条曲线(一个实例)切换一次控制点,除数应为1。
  • 曲线数量计算错误:用controlPoints.length / 12计算曲线数,实际每条曲线占8个浮点数(4个点×2个分量),应改为controlPoints.length / 8。
  • 绘制API错误:使用普通drawArrays无法配合实例化属性,需改用drawArraysInstanced实现多实例绘制。

2. 修改后的关键代码

控制点缓冲区配置修正

// 步长改为每条曲线的总字节数:4个点×2个分量×每个float的字节数
const stride = 4 * 2 * Float32Array.BYTES_PER_ELEMENT;
// 修正每个控制点的偏移量,对应组内的第0/1/2/3个点
context.vertexAttribPointer(p0, 2, context.FLOAT, false, stride, 0);
context.vertexAttribPointer(p1, 2, context.FLOAT, false, stride, 2 * Float32Array.BYTES_PER_ELEMENT);
context.vertexAttribPointer(p2, 2, context.FLOAT, false, stride, 4 * Float32Array.BYTES_PER_ELEMENT);
context.vertexAttribPointer(p3, 2, context.FLOAT, false, stride, 6 * Float32Array.BYTES_PER_ELEMENT);
// 设置除数:每1个实例(一条曲线)切换一次控制点
context.vertexAttribDivisor(p0, 1);
context.vertexAttribDivisor(p1, 1);
context.vertexAttribDivisor(p2, 1);
context.vertexAttribDivisor(p3, 1);

ts数组生成修正

// 正确计算曲线数量
const curveCount = controlPoints.length / (4 * 2);
let ts = new Float32Array(precision * curveCount);
for (let j = 0; j < curveCount; j++) {
    for (let i = 0; i < precision; i++) {
        ts[i + j * precision] = i / (precision - 1);
    }
}

绘制API修正

// 用实例化绘制API,参数:绘制模式、起始顶点、每个实例的顶点数、实例数(曲线数)
context.drawArraysInstanced(context.POINTS, 0, precision, curveCount);

3. 修正逻辑说明

  • 实例化绘制配合vertexAttribDivisor(1),让每组控制点对应一个曲线实例,每个实例绘制precision个顶点(对应一组t值)。
  • 修正步长后,控制点缓冲区会按曲线组读取数据,避免交叉混淆。
  • 实例化绘制自动处理多曲线的顶点分配,无需手动扩展顶点数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 21:12:15