Обработка 2D-изображений с помощью WebGL

Я намерен создать простой фоторедактор на JS. Мой главный вопрос: можно ли создавать фильтры, которые отображаются в реальном времени? Например, регулировка яркости и насыщенности. Все, что мне нужно, — это 2D-изображение, к которому я могу применять фильтры с помощью графического процессора.

Все учебники, которые я читал, очень сложны и на самом деле не объясняют, что означает API. Пожалуйста, укажите мне правильное направление. Спасибо.


person Dalton Tan    schedule 28.12.2011    source источник


Ответы (3)


Вы можете создать собственный пиксельный шейдер для каждой операции, которую собираетесь использовать. Просто изучите GLSL и следуйте руководствам "Изучение WebGL", чтобы получить представление об основах WebGL.

Вы можете визуализировать свое изображение с помощью шейдера, изменяющего параметры, которые вы можете включить для управления различными визуальными стилями, а затем, когда пользователь нажимает «ОК», вы можете прочитать пиксели, чтобы сохранить их как текущее изображение.

Просто не забывайте избегать междоменных изображений, потому что это отключит обратное считывание пикселей.

Кроме того, проверьте краткий справочник (PDF) для краткая информация об операциях с шейдерами.

person Chiguireitor    schedule 28.12.2011
comment
Теперь я могу визуализировать плоское изображение на холсте. Можно ли его написать без 3D части? Что касается учебных материалов, что именно мне следует искать? Кажется, есть OpenGL ES, обычный OpenGL, GLSL, WebGL, а также все другие версии. - person Dalton Tan; 29.12.2011
comment
Вы можете просто нарисовать свою текстуру с орографической проекцией с помощью четырехугольника, выровненного по экрану. Из учебных материалов вы можете сосредоточиться на GLSL, языке, который вы собираетесь использовать для своих эффектов. - person Chiguireitor; 29.12.2011

Я собирался написать учебник и опубликовать его в своем блоге, но не знаю, когда у меня будет время закончить, так что вот что у меня есть Вот более подробный набор постов в моем блоге.

WebGL на самом деле является библиотекой растеризации. Я беру атрибуты (потоки данных), униформы (переменные) и ожидаю, что вы предоставите координаты «обрезного пространства» в 2D и цветовые данные для пикселей.

Вот простой пример 2d в WebGL (некоторые детали опущены)

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

Вот 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>

Это нарисует зеленый прямоугольник во весь размер холста.

В WebGL вы несете ответственность за предоставление вершинного шейдера, предоставляющего координаты пространства отсечения. Координаты пространства отсечения всегда идут от -1 до +1 независимо от размера холста. Если вы хотите 3D, вы должны предоставить шейдеры, преобразующие 3D в 2D, потому что WebGL — это только API растеризации

В одном простом примере, если вы хотите работать с пикселями, вы можете передать прямоугольник, который использует пиксели вместо координат пространства отсечения, и преобразовать его в пространство отсечения в шейдере.

Например:

<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>

Теперь мы можем рисовать прямоугольники, изменяя данные, которые мы предоставляем

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

Вы заметите, что WebGL считает нижний правый угол равным 0,0. Чтобы сделать его более традиционным верхним правым углом, используемым для 2D-графики, мы просто инвертируем координату y.

   gl_Position = vec4(clipSpace * vec2(1, -1), 0, 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>

Чтобы нарисовать изображение, требуется загрузить изображение, и, поскольку это происходит асинхронно, нам нужно немного изменить наш код. Возьмите весь код, который у нас был, и поместите его в функцию под названием «рендеринг».

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(...)

Если вы хотите выполнять обработку изображений, вы просто меняете свой шейдер. Например, поменять местами красный и синий.

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

Или смешайте с пикселями рядом с ним.

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

И мы должны передать размер текстуры

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

И т. д. Щелкните последнюю ссылку ниже, чтобы просмотреть образец свертки.

Вот рабочие версии с немного другим прогрессом

Нарисовать прямоугольник в пространстве клипа

Рисовать прямоугольник в пикселях

Нарисуйте Rect с исходной точкой вверху слева

Нарисуйте несколько прямоугольников разных цветов

Нарисовать изображение

Нарисуйте изображение, поменяв местами красный и синий

Нарисуйте изображение с усреднением левого и правого пикселей

Нарисуйте изображение с помощью свертки 3x3

Нарисуйте изображение с несколькими эффектами

person gman    schedule 30.12.2011
comment
Спасибо за подробное объяснение. У меня тут небольшая проблема, надеюсь, ты знаешь о ней. stackoverflow.com/questions/8679596 / - person Dalton Tan; 31.12.2011
comment
Еще один вопрос. Как динамически вызывать комбинацию функций на основе пользовательского ввода? Например, размытие + насыщенность или только повышение резкости. В вашем примере я видел, что вы используете ядро, чего-то я не понимаю. Спасибо. - person Dalton Tan; 31.12.2011
comment
Я получил ядро ​​отсюда (docs.gimp.org/en/plug- in-convmatrix.html) и здесь (codeproject.com/KB/graphics /ImageConvolution.aspx) Что касается их сложения, у меня в голове две вещи. #1) вы можете попытаться генерировать шейдеры на лету. #2) вы можете рендерить в фреймбуферы и повторять для каждого шага - person gman; 31.12.2011
comment
привет, я видел и другую вашу статью webglfundamentals.org/webgl/lessons/webgl -image-processing.html, и я видел, что он скопирован из других мест в Интернете, но я не смог найти никакой информации о u_image во фрагментном шейдере? Где это устанавливается из программы JavaScript? Это конкретное имя, u_image, устанавливается автоматически, когда вы привязываете его к gl.TEXTURE_2D? Я не смог найти никаких документов по этому поводу, вы знаете, где они? - person bluejayke; 28.05.2020

Просто попробуйте 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">
person Nedudi    schedule 05.12.2012