2011-12-28 15 views
5

Tengo la intención de crear un editor de fotos simple en JS. Mi pregunta principal es, ¿es posible crear filtros que se procesen en tiempo real? Por ejemplo, ajustando el brillo y la saturación. Todo lo que necesito es una imagen 2D donde pueda aplicar filtros usando la GPU.Procesamiento de imágenes 2D con WebGL

Todos los tutoriales que he leído son muy complejos y realmente no explican lo que significa la API. Por favor, apúntame en la dirección correcta. Gracias.

Respuesta

3

Puede crear un sombreador de píxeles personalizado para cada operación que pretenda usar. Simplemente aprenda algunos GLSL y siga los tutoriales "Learning WebGL" para obtener un conocimiento básico de WebGL.

Puede renderizar su imagen con el sombreador modificando los parámetros que puede incluir para controlar los diferentes estilos visuales y luego cuando el usuario hace clic en "ok" puede volver a leer los píxeles para almacenarla como su imagen actual.

Simplemente recuerde evitar las imágenes de dominio cruzado, ya que eso desactivará la lectura de píxeles.

Además, consulte el quick reference card (PDF) para obtener información rápida sobre las operaciones del sombreador.

+0

Ahora puedo renderizar una imagen plana en el lienzo. ¿Es posible escribirlo sin la parte 3D? En cuanto a los materiales de aprendizaje, ¿qué debería buscar exactamente? Parece que hay OpenGL ES, el OpenGL normal, GLSL, WebGL junto con todas las versiones diferentes también. –

+0

Podrías dibujar tu textura con proyección orográfica con un cuadrante alineado con la pantalla. De los materiales de aprendizaje puede enfocarse en GLSL, que es el idioma que va a usar para sus efectos. – Chiguireitor

17

Iba a escribir un tutorial y publicarlo en mi blog, pero no sé cuándo tendré tiempo para terminar, así que esto es lo que tengo Here's a more detailed set of posts on my blog.

WebGL es en realidad una biblioteca de rasterización. Obtengo atributos (flujos de datos), uniformes (variables) y espero que proporcione coordenadas de "espacio de clip" en 2d y datos de color para píxeles.

Aquí está un ejemplo simple de 2D en WebGL (algunos detalles quedan fuera)

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

aquí está el 2 shaders

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

Esto dibujará un rectángulo verde todo el tamaño del lienzo.

En WebGL es su responsabilidad proporcionar un sombreador de vértices que proporcione coordenadas de espacio de clips. Las coordenadas del espacio de clips siempre van de -1 a +1 independientemente del tamaño del lienzo. Si desea 3d le toca a usted para suministrar shaders que convierten a partir de 3d a 2d porqueWebGL es sólo una API de rasterización

En un ejemplo sencillo, si se desea trabajar en píxeles que podría pasar en un rectángulo que utiliza píxeles en lugar de las coordenadas espaciales de clip y Convertir en clip espacio en el shader

por ejemplo:

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

Ahora podemos dibujar rectángulos cambiando los datos que la oferta

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

Notará que WebGL considera que la esquina inferior derecha es 0,0. Para que sea la esquina superior derecha más tradicional que se usa para los gráficos 2D, solo volteamos la coordenada y.

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

Quiere manipular las imágenes que necesita para pasar las texturas.De la misma manera el tamaño del lienzo está representado por clipspace coordina las texturas se están referenciados por las coordenadas de texturas que van de 0 a 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> 

Para dibujar una imagen requiere cargar la imagen y puesto que esto suceda de forma asíncrona que necesitamos para cambiar nuestro código un poco. Tome todo el código que teníamos y lo puso en una función llamada "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(...) 

Si usted quiere hacer el procesamiento de la imagen que acaba de cambiar de sombreado. Ejemplo, Cambiar rojo y azul

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

O combinar con los píxeles junto a él.

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

y tenemos que pasar en el tamaño de la textura

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

etc ... Haga clic en el último eslabón abajo para una muestra de convolución.

Aquí están las versiones trabajando con una progresión ligeramente diferente

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

Draw an image with multiple effects

+0

Gracias por la explicación detallada. Estoy teniendo un pequeño problema aquí, espero que lo sepan. http://stackoverflow.com/questions/8679596/extra-space-in-webgl-despite-same-dimension –

+0

Una pregunta más. ¿Cómo invoco dinámicamente una combinación de funciones según la entrada del usuario? Por ejemplo, desenfoque + saturación o solo afilar solo. En tu ejemplo, te vi usando Kernel, algo que no entiendo. Gracias. –

+0

Obtuve la cuestión del núcleo aquí (http://docs.gimp.org/en/plug-in-convmatrix.html) y aquí (http://www.codeproject.com/KB/graphics/ImageConvolution.aspx) En cuanto a sumarlos, tienes 2 cosas fuera de mi cabeza. # 1) puede intentar generar sombreadores sobre la marcha. # 2) puede renderizar a framebuffers y repetir para cada paso – gman

0

Sólo trate glfx (http://evanw.github.com/glfx.js/) Creo que es exactamente lo que necesita. Puede usar un conjunto de sombreadores predefinidos o agregar fácilmente el suyo;) ¡disfrútelo! ¡Es muy fácil con 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"> 
Cuestiones relacionadas