单独绘制每个像素 WebGL

3

首先,让我谈谈我的目标。我正在创建另一个像素艺术在线编辑器,并决定仅使用WebGL(完全不使用Canvas2D)。这很蠢因为我对这项技术一无所知,但我正在努力!那么问题来了:如何更改唯一像素的颜色?我曾经因为另一个"误解"而苦苦挣扎,后来发现了这个带有粒子系统的答案:

https://dev59.com/-mQn5IYBdhLWcg3w7KvS#16465343

我对它进行了改写,使其与我的情况更接近:

https://jsfiddle.net/kurz/rt2n7hmb/1/

正如您所看到的,当您在画布上绘制时,有10个像素具有相同的颜色。

下面是主要的绘图方法:

function mouseMoveEvent(e) {
    /*... detect x and y...*/
    gl.bindBuffer(gl.ARRAY_BUFFER, particleBuffer);
    var data = new Float32Array(2);
    data[0] = x;
    data[1] = y;
    gl.uniform4f(
        gl.getUniformLocation(shaderProgram, "color"),
        Math.random(),
        Math.random(),
        Math.random(),
        1.0
    );
    gl.bufferSubData(gl.ARRAY_BUFFER, particleId * particleSize * sizeOfFloat, data);
    particleId = (particleId + 1) % vertexBufferSize;
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.POINTS, 0, vertexBufferSize);
}

那么如何将此示例更改为每个像素都具有唯一颜色的绘制方式?

你们中的一些人可能会说:“使用canvas2d!”-不!我认为未来不是canvas2d。 “那就用库!”-不,再次否定!对于像绘制正方形这样“简单”的事情,我不想包含大型库。

谢谢!


2
WebGL 是用于 3D 的,使用 Canvas :-P。我尊重你使用任何你想要的选择。但这等同于说“我不会用锤子敲钉子,我会用卡车来做,因为那是我决定要做的”。 - EJTH
但是基于WebGL的许多令人惊叹的2D图形引擎!(Pixi.js) - Kurz
2
如果你仍然想做一个“在线像素艺术编辑器”,为什么不使用Canvas呢?它有很多好的功能(导出jpg、png等,易于2D绘图等),而且它也有更多的支持。 - EJTH
1
WebGL适用于二维图形,但不适用于三维图形。WebGL是一个光栅化引擎, 可以随意使用。如果您在Chrome浏览器中,此网页完全是通过OpenGL ES 2.0进行渲染的,这也是WebGL基于的技术。 - gman
2个回答

3
你需要理解你发布的代码传递了一个变量到片元着色器,而并非进行任何绘制。片元着色器才是在进行绘制。
OpenGL是一种工具,用于操作现代图形卡上渲染过程的不同阶段。它不试图覆盖将像素传输到屏幕缓冲区的用例。(glDrawPixels已被弃用,在ES 2.0或WebGL中不可用)
然而,你可以在应用程序中使用它的方法。
我建议阅读这些两篇文章,以了解先决条件,尽管你应该注意它是面向OpenGL 3.2和C++的,而非WebGL和JavaScript。因此,你可以忽略诸如上下文创建、加载纹理的方式以及无需创建VertexArrays等内容。
WebGL解决方案1:
- 创建空纹理。 - 每帧,绘制一个四边形到屏幕上,显示此纹理。 - 在鼠标单击时,调用glTexSubImage2D来修改纹理中的1个像素。

WebGL解决方案2:

  • 更改gl_PointSize(顶点着色器第8行)
  • 只绘制1个点
  • 不清除屏幕(javascript第45行)
  • 保留webGL屏幕缓冲区(javascript第86或89行)

查看修改后的源代码

这种解决方案并不是精确修改1个像素,我认为解决方案1是更好的方法

为什么应该使用Canvas2D:

认为Canvas2D不如WebGL是一个错误,一个是螺丝刀,另一个是锤子。一个用于用螺丝固定东西,另一个用于用钉子固定东西。

当您想要操作3D渲染管道时,请使用WebGL;当您想要操作光栅图像时,请使用Canvas2D。

简而言之:

如果您使用Canvas2D,则可以立即开始制作所需的应用程序,并获得良好的性能。如果您想使用WebGL,则应努力了解渲染管道的各个阶段。


顺便说一下:您可以在同一个应用程序中混合使用这两个API。 2D画布可以被3D画布用作纹理,而3D画布可以用作2D画布的drawImage输入。 - Philipp
但是在webGL画布中,每次修改用作纹理的2D画布时,您都必须再次将其发送到GPU内存。此外,我看不出您如何将其应用于此上下文。 - Owen Delahoy
https://webglfundamentals.org/ - oceanGermanique
@OwenDelahoy,你介意解释一下为什么你首先将初始的vertices缓冲区大小设置为vertexBufferSize * particleSize,即10 * 2,即20吗?我在尝试做类似的事情时遇到了这个问题,我得到了“顶点缓冲区不足以进行绘制调用”的错误,但我不清楚如何确定初始缓冲区大小。缓冲区大小难道不应该只是2,即一个x坐标和一个y坐标吗? - Seth Lutske

3
直接回答您的问题,gl.uniform4f是一个全局变量。那么您实际上正在做这件事:
var color = randomColor(); // uniform4f call
for each point:
    drawPoint(p.x, p.y,color);

你需要将它更改为这样:

for each point:
    var color = gl.uniform4f(...);
    drawPoint(p.x, p.y, color);

然而,我想指出,使用粒子系统来做像素编辑器可能不是最好的选择。想象一下,如果用户反复移动相同的区域,你会有很多冗余数据。其次,如何(有效地)支持擦除操作?或者填充?
最终,我认为你要转向使用纹理。纹理基本上是一个数据数组的花哨名称,但它对格式和内容有限制。然而,GPU可以非常快速地访问它们。所以你想做的就是画一个与屏幕大小相同的四边形,并通过纹理提供单独的像素数据告诉它要绘制什么。然后在用户执行操作并重新绘制时直接操作纹理数据。
在webgl中实现这个过程太长了,所以我建议跟随一些网上的教程。目前最好的webgl教程系列是webgl fundamentals。我建议从那里开始。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接