在WebGL中的鼠标事件上绘制像素 – 顶点缓冲区大小不足以进行绘制调用

huangapple go评论58阅读模式
英文:

Draw pixels on mouse event in webgl - Vertex buffer is not big enough for the draw call

问题

我正在学习WebGL并尝试了解如何将鼠标位置事件传递给GPU。我的目标是创建一个简单的绘图程序,在WebGL画布上的mouseovermousedown事件将着色该位置的像素。我知道在2D画布上更容易实现这一目标,但我选择这个路径是为了学习如何通过缓冲区和uniform变量在JS和GPU之间通信。

我的想法是,在每次鼠标移动时,创建一个新的缓冲区,绑定它,设置数据并进行绘制。以下是一些简单的代码:

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);
});

当我运行这段代码并将鼠标移到画布上时,画布变空白,并出现以下错误:

顶点缓冲区不够大以进行绘制调用

显然,我没有正确理解如何设置我的缓冲区和缓冲区数据。我的期望是只需要一个长度为2的小缓冲区来捕获单个[x,y]坐标。一旦缓冲区被绑定,并且GPU中的位置属性已设置,绘制调用应该以片段着色器中设置的颜色绘制该像素坐标。我的事件监听器有什么问题,导致我的代码没有产生预期的行为?

附录:

供参考,我的着色器非常简单:

// 顶点着色器
// 这里有一些额外的代码,用于在着色器内部将像素转换为裁剪空间,而不是在JavaScript代码中进行转换

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);
}
英文:

I am learning webgl and trying to understand how to communicate mouse positioning events to the GPU. My goal is to create a simple draw program, where a mouseover or mousedown event on the webgl canvas will color the pixel at that position. I am aware this is easier with 2d canvas, but the point is to learn about communicating between JS and GPU via buffers and uniforms.

My idea is that on every mousemove, to create a new buffer, bind it, set the data, and drawArrays. (I chose this path as opposed to modifying an existing buffer based on advice in (WebGL) How to add vertices to an already initialized vertex buffer?. Some simple code:

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

// simple util to compile and attach shaders
const { program } = setup(gl, vert1, frag1); 

gl.useProgram(program);

// Initialize placeholder buffer buffer - without this,
// webgl says there is no buffer attached, even though I 
// create and attach another buffer later?
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);

// set the resolution
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);
});

When I run this code, and mouse into the canvas, the canvas goes blank and I get the error:

> Vertex buffer is not big enough for the draw call

Clearly I am not understanding how to set up my buffers and buffer data. My expectations were that a small buffer of length 2 is all that is needed to capture a single [x,y] coordinate. Once the buffer is bound and the vertex attribute for position is set in the GPU, the draw call should draw that pixel coordinate in the color set in the fragment shader. What is wrong with my event listener that my code is not giving the expected behavior?

Appendix:

For reference, my shaders are very simple:

// vertex shader
// Some extra code here to convert pixels to clip space within the shader,
// as opposed to within the javascript code

attribute vec2 a_position;
uniform vec2 u_resolution;

void main() {
  // convert the position from pixels to 0.0 to 1.0
  vec2 zeroToOne = a_position / u_resolution;

  // convert from 0->1 to 0->2
  vec2 zeroToTwo = zeroToOne * 2.0;

  // convert from 0->2 to -1->+1 (clip space)
  vec2 clipSpace = zeroToTwo - 1.0;

  gl_Position = vec4(clipSpace, 0.0, 1.0);
}
// fragment shader
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = vec4(1,0,1,1);
}

答案1

得分: 1

以下是您代码中的翻译部分:

  1. 在您的drawArrays调用中指定了3个索引,尽管您的缓冲区中只有一个2D点,正确的索引数量应为1
  2. vertexAttrib2f为一个在属性数组未启用时设置静态值的属性,您应该在这里使用的是vertexAttribPointer
  3. 此外,之前的虚拟缓冲区和属性指针设置是多余的,因为它们未被使用。

实际上,您可以在不使用任何缓冲区的情况下完成您的这个特定示例(绘制单个像素),只需调用:

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

因此,您的代码将变为:

<!-- 开始代码片段:js 隐藏:false 控制台:true Babel:false -->

<!-- 语言:lang-js -->
    const canvas = document.getElementById('canvas');
    const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true }); 

    // 编写一个用于编译和附加着色器的简单工具函数
    const program = setup(gl, vert1.textContent, frag1.textContent); 

    gl.useProgram(program);

    // 禁用位置属性
    const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
    gl.disableVertexAttribArray(positionAttributeLocation);

    // 设置分辨率
    const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');
    gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);

    canvas.addEventListener('mousemove', (e) => {
        const br = canvas.getBoundingClientRect();
        const x = e.clientX - br.left;
        const y = br.height - (e.clientY - br.top);
        gl.vertexAttrib2f(positionAttributeLocation, x, y);
        gl.drawArrays(gl.POINTS, 0, 1);
    });

    function setup(ctx, vert, frag) {
      const vs = ctx.createShader(ctx.VERTEX_SHADER);
      ctx.shaderSource(vs, vert);
      ctx.compileShader(vs);

      const fs = ctx.createShader(ctx.FRAGMENT_SHADER);
      ctx.shaderSource(fs, frag);
      ctx.compileShader(fs);

      const program = ctx.createProgram();
      ctx.attachShader(program, vs);
      ctx.attachShader(program, fs);
      ctx.linkProgram(program);
      console.log(ctx.getProgramInfoLog(program));
      return program;
    }

<!-- 语言:lang-css -->
    canvas { background: #efe; }

<!-- 语言:lang-html -->
    <script id="vert1" type="x-vertex-shader">
    // Some extra code here to convert pixels to clip space within the shader,
    // as opposed to within the javascript code

    attribute vec2 a_position;
    uniform vec2 u_resolution;

    void main() {
      // convert the position from pixels to 0.0 to 1.0
      vec2 zeroToOne = a_position / u_resolution;

      // convert from 0->1 to 0->2
      vec2 zeroToTwo = zeroToOne * 2.0;

      // convert from 0->2 to -1->+1 (clip space)
      vec2 clipSpace = zeroToTwo - 1.0;

      gl_Position = vec4(clipSpace, 0.0, 1.0);
    }
    </script>
    <script id="frag1" type="x-fragment-shader">
    precision mediump float;

    uniform vec4 u_color;

    void main() {
        gl_FragColor = vec4(1,0,1,1);
    }
    </script>
    <canvas id="canvas" width="600" height="400"></canvas>

<!-- 结束代码片段 -->

如果您决定仍然使用缓冲区(当需要任意数量的点/属性时),您应该重复使用一个单一的缓冲区,而不是在每个事件中手动分配和设置新的缓冲区。

英文:

There's multiple things wrong with your code.

  1. Your drawArrays call specifies 3 indices, despite you only having a single 2D point in your buffer, the correct number of indices would be 1.
  2. vertexAttrib2f sets a static value to an attribute that's being used when the attribute array is not enabled, what you want to use here is vertexAttribPointer.
  3. On that note, the previous dummy buffer and attribute pointer setup is redundant as it's not being used.

Effectively you can do this specific example of yours (plotting an individual pixel) without any buffers by just calling:

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

So your code would become:
<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const canvas = document.getElementById(&#39;canvas&#39;);
const gl = canvas.getContext(&#39;webgl&#39;, { preserveDrawingBuffer: true }); 
// simple util to compile and attach shaders
const program = setup(gl, vert1.textContent, frag1.textContent); 
gl.useProgram(program);
// disable position attribute
const positionAttributeLocation = gl.getAttribLocation(program, &#39;a_position&#39;);
gl.disableVertexAttribArray(positionAttributeLocation);
// set the resolution
const resolutionUniformLocation = gl.getUniformLocation(program, &#39;u_resolution&#39;);
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
canvas.addEventListener(&#39;mousemove&#39;, (e) =&gt; {
const br = canvas.getBoundingClientRect();
const x = e.clientX - br.left;
const y = br.height - (e.clientY - br.top);
gl.vertexAttrib2f(positionAttributeLocation, x, y);
gl.drawArrays(gl.POINTS, 0, 1);
});
function setup(ctx, vert, frag) {
const vs = ctx.createShader(ctx.VERTEX_SHADER);
ctx.shaderSource(vs, vert);
ctx.compileShader(vs);
const fs = ctx.createShader(ctx.FRAGMENT_SHADER);
ctx.shaderSource(fs, frag);
ctx.compileShader(fs);
const program = ctx.createProgram();
ctx.attachShader(program, vs);
ctx.attachShader(program, fs);
ctx.linkProgram(program);
console.log(ctx.getProgramInfoLog(program));
return program;
}

<!-- language: lang-css -->

canvas { background: #efe; }

<!-- language: lang-html -->

&lt;script id=&quot;vert1&quot; type=&quot;x-vertex-shader&quot;&gt;
// Some extra code here to convert pixels to clip space within the shader,
// as opposed to within the javascript code
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
// convert the position from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0-&gt;1 to 0-&gt;2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0-&gt;2 to -1-&gt;+1 (clip space)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace, 0.0, 1.0);
}
&lt;/script&gt;
&lt;script id=&quot;frag1&quot; type=&quot;x-fragment-shader&quot;&gt;
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = vec4(1,0,1,1);
}
&lt;/script&gt;
&lt;canvas id=&quot;canvas&quot; width=&quot;600&quot; height=&quot;400&quot;&gt;&lt;/canvas&gt;

<!-- end snippet -->

If you decide to still use buffers (when you need an arbitrary number of points / attributes) you should reuse a single buffer rather than manually allocating and setting up new buffers with every event.

huangapple
  • 本文由 发表于 2023年6月12日 01:56:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76451786.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定