2D обработка изображений с WebGL - PullRequest
5 голосов
/ 28 декабря 2011

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

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

Ответы [ 3 ]

18 голосов
/ 30 декабря 2011

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

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

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

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

Для рисования изображения требуется загрузка изображения, и так как это происходит асинхроннонам нужно немного изменить наш код.Возьмите весь код, который у нас был, и поместите его в функцию "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(...)

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

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

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

Вот рабочие версии с несколько иной прогрессией

Draw Rect in Clip Space

Рисование Rect в пикселях

Рисование Rect с происхождением слева вверху

Нарисуйте кучу канатов в разныхцвета

Нарисуйте изображение

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

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

Нарисуйте изображение со сверткой 3x3

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

3 голосов
/ 28 декабря 2011

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

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

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

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

1 голос
/ 06 декабря 2012

Просто попробуйте 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">
...