javascript 使用 WebGL 进行 2D 图像处理

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8652985/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 04:07:13  来源:igfitidea点击:

2D Image Processing With WebGL

javascriptimage-processinghtml5-canvaswebgl

提问by Dalton Tan

I intend to create a simple photo editor in JS. My main question is, is it possible to create filters that render in real-time? For example, adjusting brightness and saturation. All I need is a 2D image where I can apply filters using the GPU.

我打算在 JS 中创建一个简单的照片编辑器。我的主要问题是,是否可以创建实时渲染的过滤器?例如,调整亮度和饱和度。我所需要的只是一个 2D 图像,我可以在其中使用 GPU 应用过滤器。

All the tutorials I've read are very complex and don't really explain what the API mean. Please point me in the right direction. Thanks.

我读过的所有教程都非常复杂,并没有真正解释 API 的含义。请指出我正确的方向。谢谢。

采纳答案by Chiguireitor

You can make a custom pixel shader for each operation you're intending to use. Just learn some GLSL and follow the "Learning WebGL" tutorials to get a grasp of basic WebGL.

您可以为要使用的每个操作制作自定义像素着色器。只需学习一些 GLSL 并按照“学习 WebGL”教程来掌握基本的 WebGL。

You can render your image with the shader modifying the parameters you can include to control the different visual styles and then when the user clicks "ok" you can read back the pixels to store it as your current image.

您可以使用着色器渲染您的图像,修改您可以包含的参数以控制不同的视觉样式,然后当用户单击“确定”时,您可以读回像素以将其存储为当前图像。

Just remember to avoid cross domain images, because that will disable the reading back of pixels.

请记住避免跨域图像,因为这将禁用像素的回读。

Also, check the quick reference card(PDF) for quick info on shader operations.

此外,请查看快速参考卡(PDF) 以获取有关着色器操作的快速信息。

回答by gman

I was going to write a tutorial and post it on my blog but I don't know when I'll have time to finish so here's what I haveHere's a more detailed set of posts on my blog.

我打算写一个教程并将其发布在我的博客上,但我不知道什么时候才能完成,所以这就是我所拥有的。这是我博客上的一组更详细的帖子

WebGL is actually a rasterization library. I takes in attributes (streams of data), uniforms (variables) and expects you to provide "clip space" coordinates in 2d and color data for pixels.

WebGL 实际上是一个光栅化库。我接受属性(数据流)、制服(变量),并希望您提供 2d 中的“剪辑空间”坐标和像素的颜色数据。

Here's a simple example of 2d in WebGL (some details left out)

这是 WebGL 中 2d 的一个简单示例(省略了一些细节)

// Get A WebGL context
var gl = canvas.getContext("experimental-webgl");

// setup GLSL program
vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);

// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");

// Create a buffer and put a single clipspace rectangle in
// it (2 triangles)
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
   -1.0, -1.0,
    1.0, -1.0,
   -1.0,  1.0,
   -1.0,  1.0,
    1.0, -1.0,
    1.0,  1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

// draw
gl.drawArrays(gl.TRIANGLES, 0, 6);

Here's the 2 shaders

这是 2 个着色器

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
void main() {
   gl_Position = vec4(a_position, 0, 1);
}
</script>

<script id="2d-fragment-shader" type="x-shader/x-fragment">
void main() {
   gl_FragColor = vec4(0,1,0,1);  // green
}
</script>

This will draw a green rectangle the entire size of the canvas.

这将绘制一个与画布整个大小相同的绿色矩形。

In WebGL it's your responsibility to provide a vertex shader that provides clipspace coordinates. Clipspace coordinates always go from -1 to +1 regardless of the size of the canvas. If you want 3d it's up to you to supply shaders that convert from 3d to 2d becauseWebGL is only a rasterization API

在 WebGL 中,您有责任提供一个顶点着色器来提供剪辑空间坐标。无论画布的大小如何,剪辑空间坐标始终从 -1 到 +1。如果您想要 3d,则由您提供从 3d 转换为 2d 的着色器,因为WebGL 只是一个光栅化 API

In one simple example, if you want to work in pixels you could pass in a rectangle that uses pixels instead of clip space coordinates and convert to clip space in the shader

在一个简单的例子中,如果你想以像素为单位工作,你可以传入一个使用像素而不是剪辑空间坐标的矩形,并在着色器中转换为剪辑空间

For example:

例如:

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;

void main() {
   // convert the rectangle 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 (clipspace)
   vec2 clipSpace = zeroToTwo - 1.0;

   gl_Position = vec4(clipSpace, 0, 1);
}
</script>

Now we can draw rectangles by changing the data we supply

现在我们可以通过改变我们提供的数据来绘制矩形

// set the resolution
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);

// setup a rectangle from 10,20 to 80,30 in pixels
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    10, 20,
    80, 20,
    10, 30,
    10, 30,
    80, 20,
    80, 30]), gl.STATIC_DRAW);

You'll notice WebGL considers the bottom right corner to be 0,0. To get it to be the more traditional top right corner used for 2d graphics we just flip the y coordinate.

您会注意到 WebGL 将右下角视为 0,0。为了让它成为用于 2d 图形的更传统的右上角,我们只需翻转 y 坐标。

   gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

You want to manipulate images you need to pass in textures. In the same way the size of the canvas is represented by clipspace coordinates textures are are referenced by texture coordinates that go from 0 to 1.

您想操作需要传入纹理的图像。以同样的方式,画布的大小由剪辑空间坐标表示,纹理由从 0 到 1 的纹理坐标引用。

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;

uniform vec2 u_resolution;

varying vec2 v_texCoord;

void main() {
   // convert the rectangle 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 (clipspace)
   vec2 clipSpace = zeroToTwo - 1.0;

   gl_Position = vec4(clipSpace, 0, 1);

   // pass the texCoord to the fragment shader
   // The GPU will interpolate this value between points.
   v_texCoord = a_texCoord;
}
</script>

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision float mediump;

// our texture
uniform sampler2D u_image;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>

To draw an image requires loading the image and since that happen asynchronously we need to change our code a little. Take all the code we had and put it in a function called "render"

绘制图像需要加载图像,因为这是异步发生的,我们需要稍微更改我们的代码。将我们拥有的所有代码放入一个名为“render”的函数中

var image = new Image();
image.src = "http://someimage/on/our/server";  // MUST BE SAME DOMAIN!!!
image.onload = function() {
  render();
}

function render() {
   ...
   // all the code we had before except gl.draw


   // look up where the vertex data needs to go.
   var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");

   // provide texture coordinates for the rectangle.
   var texCoordBuffer = gl.createBuffer();
   gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
       1.0,  1.0, 
       0.0,  1.0, 
       0.0,  0.0, 
       1.0,  1.0, 
       0.0,  0.0, 
       1.0,  0.0]), gl.STATIC_DRAW);
   gl.enableVertexAttribArray(texCoordLocation);
   gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

   var texture = gl.createTexture();
   gl.bindTexture(gl.TEXTURE_2D, texture);
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

   gl.draw(...)

If you want to do image processing you just change your shader. Example, Swap red and blue

如果你想做图像处理,你只需改变你的着色器。示例,交换红色和蓝色

void main() {
   gl_FragColor = texture2D(u_image, v_texCoord).bgra;
}

Or blend with the pixels next to it.

或者与它旁边的像素混合。

uniform vec2 u_textureSize;

void main() {
   vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
   gl_FragColor = (texture2D(u_image, v_texCoord) +
                   texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
                   texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
}

And we have to pass in the size of the texture

我们必须传入纹理的大小

var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
...
gl.uniform2f(textureSizeLocation, image.width, image.height);

Etc... Click the last link below for a convolution sample.

等等... 单击下面的最后一个链接以获取卷积示例。

Here are working versions with a slightly different progression

以下是进展略有不同的工作版本

Draw Rect in Clip Space

在剪辑空间中绘制矩形

Draw Rect in Pixels

以像素为单位绘制矩形

Draw Rect with origin at top left

以左上角为原点绘制矩形

Draw a bunch of rects in different colors

绘制一堆不同颜色的矩形

Draw an image

绘制图像

Draw an image red and blue swapped

绘制一个红色和蓝色交换的图像

Draw an image with left and right pixels averaged

绘制左右像素平均的图像

Draw an image with a 3x3 convolution

用 3x3 卷积绘制图像

Draw an image with multiple effects

绘制具有多种效果的图像

回答by Nedudi

Just try glfx ( http://evanw.github.com/glfx.js/) I think it is exactly what you need. You can use set of predefined shaders or easily add yours ;) enjoy! It is very easy with glfx!

试试 glfx ( http://evanw.github.com/glfx.js/) 我认为这正是你所需要的。您可以使用一组预定义的着色器或轻松添加您的 ;) 享受!使用 glfx 非常容易!

<script src="glfx.js"></script>
<script>

window.onload = function() {
    // try to create a WebGL canvas (will fail if WebGL isn't supported)
    try {
        var canvas = fx.canvas();
    } catch (e) {
        alert(e);
        return;
    }

    // convert the image to a texture
    var image = document.getElementById('image');
    var texture = canvas.texture(image);

    // apply the ink filter
    canvas.draw(texture).ink(0.25).update();

    // replace the image with the canvas
    image.parentNode.insertBefore(canvas, image);
    image.parentNode.removeChild(image);
};

</script>
<img id="image" src="image.jpg">