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

WebGL鼠标事件绘制像素:顶点缓冲区不足致绘制调用失败

WebGL鼠标事件传递GPU实现绘图的问题排查

我正在学习WebGL,想了解如何把鼠标位置事件传递给GPU,目标是做一个简单的绘图程序:当WebGL Canvas触发mouseover或mousedown事件时,给对应位置的像素上色。我知道用2D Canvas实现更简单,但目的是学习JS与GPU通过缓冲区(buffer)和uniforms通信的方式。

我的思路是每次鼠标移动时创建新缓冲区、绑定缓冲区、设置数据并调用drawArrays(参考Stack Overflow建议,不修改现有缓冲区)。相关代码如下:

const canvas = document.getElementById('canvas');
const gl = getGlContext('canvas', { preserveDrawingBuffer: true }); 

// 编译并附加着色器的工具函数
const { program } = setup(gl, vert1, frag1); 

gl.useProgram(program);

// 初始化占位缓冲区——如果没有这个,WebGL会提示没有绑定缓冲区,哪怕之后我会创建并绑定另一个?
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');

gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

// 设置分辨率
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);

canvas.addEventListener('mousemove', (e) => {
    const x = e.x - canvas.offsetLeft + 1;
    const y = e.y - canvas.offsetTop + 1;

    const buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([x, y]), gl.STATIC_DRAW);

    gl.vertexAttrib2f(positionAttributeLocation, x, y);
    gl.drawArrays(gl.POINTS, 0, 3);
});

运行代码后,鼠标移入Canvas时画布变空白,且出现错误:

Vertex buffer is not big enough for the draw call

我原本以为只需要长度为2的小型缓冲区存储单个[x,y]坐标,绑定缓冲区并设置GPU中的位置顶点属性后,绘制调用就能按照片段着色器设置的颜色绘制该像素坐标。显然我对缓冲区及缓冲区数据的设置理解有误,请问事件监听器中的代码哪里出错了?

附录:着色器代码

顶点着色器:

// 顶点着色器
// 在着色器内将像素坐标转换为裁剪空间,而非在JS中处理

attribute vec2 a_position;
uniform vec2 u_resolution;

void main() {
  // 将位置从像素转换为0.0到1.0
  vec2 zeroToOne = a_position / u_resolution;

  // 从0->1转换为0->2
  vec2 zeroToTwo = zeroToOne * 2.0;

  // 从0->2转换为-1->+1(裁剪空间)
  vec2 clipSpace = zeroToTwo - 1.0;

  gl_Position = vec4(clipSpace, 0.0, 1.0);
}

片段着色器:

// 片段着色器

precision mediump float;

uniform vec4 u_color;

void main() {
    gl_FragColor = vec4(1,0,1,1);
}

问题根源与修正方案

你的代码存在两个关键错误:

  1. drawArrays顶点数量参数不匹配
    调用gl.drawArrays(gl.POINTS, 0, 3)表示要绘制3个顶点,但你传入的缓冲区仅包含1个顶点的数据(Float32Array([x, y])是2个浮点数,对应1个vec2顶点)。GPU尝试读取3个顶点的内容,缓冲区数据量不足,因此抛出"Vertex buffer is not big enough"错误。

  2. 混用缓冲区属性与常量属性
    你先通过gl.vertexAttribPointer将a_position设置为从缓冲区读取数据,随后又调用gl.vertexAttrib2f直接给该属性赋值常量。这会让a_position变成常量属性(不再从缓冲区读取数据),但之前开启的gl.enableVertexAttribArray(positionAttributeLocation)仍会让WebGL尝试从缓冲区读取数据,进而触发错误。

另外,每次鼠标移动都创建新缓冲区会造成内存浪费,复用单个缓冲区更高效。

修正后的代码示例

const canvas = document.getElementById('canvas');
const gl = getGlContext('canvas', { preserveDrawingBuffer: true }); 

const { program } = setup(gl, vert1, frag1); 

gl.useProgram(program);

// 创建单个缓冲区复用
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// 预先分配缓冲区空间,后续更新数据
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(2), gl.DYNAMIC_DRAW);

const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');

gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);

// WebGL坐标系原点在左下角,需反转鼠标y坐标
canvas.addEventListener('mousemove', (e) => {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = canvas.height - (e.clientY - rect.top);

    // 绑定缓冲区并更新数据
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array([x, y]));

    // 绘制1个点
    gl.drawArrays(gl.POINTS, 0, 1);
});

额外说明

  • WebGL的y轴原点在左下角,而鼠标事件的y轴原点在左上角,必须反转y坐标才能对应画布的正确位置。
  • 使用gl.bufferSubData更新缓冲区数据比重复创建新缓冲区更高效。
  • 若要保留历史绘制内容,preserveDrawingBuffer: true可以实现,但更推荐的方式是记录所有历史点,每一帧重绘(前者会影响WebGL性能)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 10:45:05