Отсечение изображений в Webgl

Двухмерный холст предоставляет API-интерфейс, называемый draw image: context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height); где sx - позиция, с которой начинается отсечение изображения.

http://www.w3schools.com/tags/canvas_drawimage.asp,

Я пытаюсь использовать webgl для рендеринга 2D-изображения с помощью texImage2D. я хотел проверить, есть ли способ реализовать отсечение с помощью webgl.

Я использую следующий учебник для визуализации 2D-изображений с WebGL. http://webglfundamentals.org/webgl/lessons/webgl-image-processing.html

Исходное изображение:

Исходное изображение

Отсечение с помощью drawImage(2D):

Отсечение с помощью webgl:

var gl,program,positionLocation,originalImageTexture,canvas;
var x = 10;
var y = 20;
function setupWebGL(){

 var canvas = document.getElementById("canvas");
 gl = canvas.getContext("webgl");
   if (!gl) {
     return;
   }
   // setup GLSL program
   var program = createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
   gl.useProgram(program);
   // look up where the vertex data needs to go.
   positionLocation = gl.getAttribLocation(program, "a_position");
  texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
   var texture = gl.createTexture();
   gl.bindTexture(gl.TEXTURE_2D, texture);
  
   // lookup uniforms
   var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
   textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
   colorLocation = gl.getUniformLocation(program, "u_color");
    // set the resolution
   gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
  
   // Set the parameters so we can render any size 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.enable(gl.BLEND);
   gl.blendEquation( gl.FUNC_ADD );
   gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
   gl.disable(gl.DEPTH_TEST);
}

    function draw() {
    // use canvas to simulate an image
   var image = document.createElement("canvas");
    document.body.appendChild(image); // so we can see the source image
    image.width  = 200;
    image.height = 150;
    var ctx   = image.getContext("2d");
    ctx.fillRect(0, 0, image.width, image.height);
    for (var py = 0; py < image.height; py += 25) {
      for (var px = 0; px < image.width; px += 25) {
        ctx.fillStyle = "rgb(" + (py / image.height * 255 | 0) + "," + 
                                 (px / image.width  * 255 | 0) + "," + 
                                  255 + ")";
        ctx.beginPath();
        ctx.arc(px + 12, py + 12, 10, 0, Math.PI * 2);
        ctx.fill();
      }
    }
      
   setupWebGL();
      
    var srcX = 12;
    var srcY = 35;
    var srcWidth = 75;
    var srcHeight = 50;
      
    var dstX = 100;
    var dstY = 110;
    var dstWidth  = srcWidth;
    var dstHeight = srcHeight;
    
    var u0 = 50 / image.width;
    var v0 = 0 / image.height;
    var u1 = (50 + image.width) / image.width;
    var v1 = (0 + image.height) / image.height;
      
   // provide texture coordinates for the rectangle.
   var texCoordBuffer = gl.createBuffer();
   gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([u0, v0, u1,v0, u0,v1, u0,v1, u1,v0, u1,v1]), gl.STATIC_DRAW);
    gl.enableVertexAttribArray(texCoordLocation);
    gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

  
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,gl.RGBA, gl.UNSIGNED_BYTE, image);
  
    // set the size of the image
    gl.uniform2f(textureSizeLocation, image.width, image.height);
    // Create a buffer for the position of the rectangle corners.
    var positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    setRectangle( gl, x, y, image.width, image.height);
    
 
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  
}

    function setRectangle(gl, x, y, width, height) {
 var x1 = x;
 var x2 = x + width;
 var y1 = y;
 var y2 = y + height;
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([x1, y1,x2, y1,x1, y2,x1, y2,x2, y1,x2, y2]), gl.STATIC_DRAW);
}

draw();
canvas { border: 1px solid black; }
<canvas width="400" height="300" id="canvas"></canvas>
<script src="//webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<!-- vertex shader -->
<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 * vec2(1, -1), 0, 1);

   // pass the texCoord to the fragment shader
   // The GPU will interpolate this value between points.
   v_texCoord = a_texCoord;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

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

1 ответ

Решение

Вам нужно настроить координаты текстуры, чтобы выбрать часть текстуры, которая вас интересует.

Координаты текстуры идут от 0 до 1, так что если вы хотите конвертировать из drawImage затем дали

drawImage(image, srcX, srcY, srcWidth, srcHeight, 
          dstX, dstY, dstWidth, dstHeight);

В WebGL вы должны настроить текстовую координату, используя src значения и координаты вершины, где рисовать, используя dst ценности.

u0 = srcX / image.width;
v0 = srcY / image.height;

u1 = (srcX + srcWidth) / image.width;
v1 = (srcY + srcHeight) / image.height;

Теперь обновите ваши texcoords. В примере, который вы связали с

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  u0,  v0,
  u1,  v0,
  u0,  v1,
  u0,  v1,
  u1,  v0,
  u1,  v1]), gl.STATIC_DRAW);

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

Так, например, вы можете оставить координаты UV в диапазоне от 0 до 1, как они были изначально, но обновите шейдер, чтобы вы могли передать смещение и масштаб, который умножит их, чтобы получить их там, где вы хотите. Или, если вы продолжите эти статьи, вы можете использовать матрицу, чтобы манипулировать ими с большей гибкостью.

Точно так же для целевого размера вместо обновления вершин вы можете использовать различные математические функции в шейдере для перемещения простого квадратного блока и масштабирования и размера до любого желаемого вами размера.

Что касается кода вы разместили эту строку

var u1 = (50 + image.width) / image.width;

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

Вторая проблема - эта линия

setRectangle( gl, x, y, image.width, image.height);

Если вы не намеревались растянуть изображение, вы просите обрезанное изображение (скажем, image.width - 50) участвовать image.width пикселей, поэтому, если ваше изображение составляет 75 пикселей, вы получите 25 пикселей источника, растянутого до 75 пикселей пункта назначения.

не уверен, что это то, что вы хотели. Вот исправленная версия

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

var gl,program,positionLocation,originalImageTexture,canvas;
var x = 10;
var y = 20;
function setupWebGL(){

 var canvas = document.getElementById("canvas");
 gl = canvas.getContext("webgl");
   if (!gl) {
     return;
   }
   // setup GLSL program
   var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
   gl.useProgram(program);
   // look up where the vertex data needs to go.
   positionLocation = gl.getAttribLocation(program, "a_position");
  texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
   var texture = gl.createTexture();
   gl.bindTexture(gl.TEXTURE_2D, texture);
  
   // lookup uniforms
   var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
   textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
   colorLocation = gl.getUniformLocation(program, "u_color");
    // set the resolution
   gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
  
   // Set the parameters so we can render any size 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.enable(gl.BLEND);
   gl.blendEquation( gl.FUNC_ADD );
   gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
   gl.disable(gl.DEPTH_TEST);
}

    function draw() {
    // use canvas to simulate an image
   var image = document.createElement("canvas");
    document.body.appendChild(image); // so we can see the source image
    image.width  = 200;
    image.height = 150;
    var ctx   = image.getContext("2d");
    ctx.fillRect(0, 0, image.width, image.height);
    for (var py = 0; py < image.height; py += 25) {
      for (var px = 0; px < image.width; px += 25) {
        ctx.fillStyle = "rgb(" + (py / image.height * 255 | 0) + "," + 
                                 (px / image.width  * 255 | 0) + "," + 
                                  255 + ")";
        ctx.beginPath();
        ctx.arc(px + 12, py + 12, 10, 0, Math.PI * 2);
        ctx.fill();
      }
    }
      
   setupWebGL();
      
    var srcX = 50;
    var srcY = 0;
    var srcWidth = image.width - 50;
    var srcHeight = image.height;
      
    var dstX = x;
    var dstY = y;
    var dstWidth  = srcWidth;
    var dstHeight = srcHeight;
    
 var u0 = srcX / image.width;
  var v0 = srcY / image.height;
 var u1 = (srcX + srcWidth)  / image.width;
  var v1 = (srcY + srcHeight) / image.height;
 
   // provide texture coordinates for the rectangle.
   var texCoordBuffer = gl.createBuffer();
   gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
   gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([u0, v0, u1,v0, u0,v1, u0,v1, u1,v0, u1,v1]), gl.STATIC_DRAW);
    gl.enableVertexAttribArray(texCoordLocation);
    gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

  
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,gl.RGBA, gl.UNSIGNED_BYTE, image);
  
    // set the size of the image
    gl.uniform2f(textureSizeLocation, image.width, image.height);
    // Create a buffer for the position of the rectangle corners.
    var positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    setRectangle( gl, dstX, dstY, dstWidth, dstHeight);
   
 
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  
}

    function setRectangle(gl, x, y, width, height) {
 var x1 = x;
 var x2 = x + width;
 var y1 = y;
 var y2 = y + height;
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([x1, y1,x2, y1,x1, y2,x1, y2,x2, y1,x2, y2]), gl.STATIC_DRAW);
}

draw();
canvas { border: 1px solid red; }
<canvas width="400" height="300" id="canvas"></canvas>
<script src="//webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<!-- vertex shader -->
<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 * vec2(1, -1), 0, 1);

   // pass the texCoord to the fragment shader
   // The GPU will interpolate this value between points.
   v_texCoord = a_texCoord;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

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

Я также хотел еще раз подчеркнуть, что обычно это не так. Как я уже говорил ранее для этого конкретного случая, я бы использовал позицию единичного квадрата и единичный квадрат для текстовых координат. Затем я использовал бы матрицы для перевода и смещения обоих. Это позволило бы мне делать все то, что может делать API 2D-холста. Масштабируйте изображение, обрезайте изображение, поворачивайте изображение, переворачивайте изображение. Это даже позволило бы мне делать вещи, которые API-интерфейс Canvas не может делать, например, вращать текстуру внутри прямоугольника.

var gl,program,positionLocation,originalImageTexture,canvas;
function setupWebGL(){

 var canvas = document.getElementById("canvas");
 gl = canvas.getContext("webgl");
   if (!gl) {
     return;
   }
   // setup GLSL program
   var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
   gl.useProgram(program);
   // look up where the vertex data needs to go.
   positionLocation = gl.getAttribLocation(program, "a_position");
  texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
   var texture = gl.createTexture();
   gl.bindTexture(gl.TEXTURE_2D, texture);
  
   // lookup uniforms
   matrixLocation = gl.getUniformLocation(program, "u_matrix");
   texMatrixLocation = gl.getUniformLocation(program, "u_texMatrix");
  
   // Set the parameters so we can render any size 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);

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

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,gl.RGBA, gl.UNSIGNED_BYTE, image);
  
    // Create a buffer for the position of the rectangle corners.
    var positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0 , 1, 1]), gl.STATIC_DRAW);
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

   gl.enable(gl.BLEND);
   gl.blendEquation( gl.FUNC_ADD );
   gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
   gl.disable(gl.DEPTH_TEST);
}

var originX, originY, srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, angleInRadians,texAngle;

function draw(time) {
    time *= 0.001; // make time in seconds
  
  
    switch (((time / 4) | 0) % 5) {
      case 0:
        originX = 0;
        originY = 0;
        srcX = 50;
        srcY = 0;
        srcWidth = image.width - 50;
        srcHeight = image.height;
      
        dstX = 10;
        dstY = 20;
        dstWidth  = srcWidth;
        dstHeight = srcHeight;
            
        angleInRadians = 0;
        texAngle = 0;
        break;
      case 1:
         // clip it more
         srcX = 50 + Math.sin(time      ) * 20;
         srcY = 50 + Math.sin(time * 1.3) * 20;
         srcWidth  = image.width  - (50 + Math.sin(time      ) * 20) - srcX;
         srcHeight = image.height - (50 + Math.sin(time * 1.3) * 20) - srcY;
         dstWidth  = srcWidth;
         dstHeight = srcHeight;
         break;
      case 2:
        // spin image around top left
        angleInRadians = time;
        break;
      case 3:
        // spin image around center
        angleInRadians = time;
        dstX = 100;
        dstY = 100;
        originX = -srcWidth / 2;
        originY = -srcHeight / 2;
        break;
      case 4:
        // spin texture around center
        texAngle = -time;
        break;
    }
  
      
    var x = dstX;
    var y = dstY;
    // We have a 1 unit square. If will scale by destWidth and destHeight we'll stretch
    // it to the size we want
    var xScale = dstWidth;
    var yScale = dstHeight;
    
    // We also have a 1 unit square for the texcoords. We can scale that to clip
    var texXOff   = srcX      / image.width;
    var texYOff   = srcY      / image.height;
    var texXScale = srcWidth  / image.width;
    var texYScale = srcHeight / image.height;

    // Compute the matrices
    var projectionMatrix = make2DProjection(gl.canvas.clientWidth, gl.canvas.clientHeight);
    var translationMatrix = makeTranslation(x, y);
    var rotationMatrix = makeRotation(angleInRadians);
    var originMatrix = makeTranslation(originX, originY);
    var scaleMatrix = makeScale(xScale, yScale);
  
    // Multiply the matrices.
    var matrix = matrixMultiply(scaleMatrix, originMatrix);
    matrix = matrixMultiply(matrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);
    matrix = matrixMultiply(matrix, projectionMatrix);
      
    // compute matrixes for texture
    var texPreRot = makeTranslation(-0.5, -0.5);
    var texRot    = makeRotation(texAngle);
    var texPostRot= makeTranslation(0.5, 0.5);
    var texOffMat = makeTranslation(texXOff, texYOff);
    var texScaleMat = makeScale(texXScale, texYScale);
  
    var texMatrix = matrixMultiply(texScaleMat, texOffMat);
    texMatrix = matrixMultiply(texMatrix, texPreRot);
    texMatrix = matrixMultiply(texMatrix, texRot);
    texMatrix = matrixMultiply(texMatrix, texPostRot);
    

    // Set the matrices
    gl.uniformMatrix3fv(matrixLocation, false, matrix);      
    gl.uniformMatrix3fv(texMatrixLocation, false, texMatrix);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
 
    requestAnimationFrame(draw);
}

    // use canvas to simulate an image
   image = document.createElement("canvas");
    document.body.appendChild(image); // so we can see the source image
    image.width  = 200;
    image.height = 150;
    var ctx   = image.getContext("2d");
    ctx.fillRect(0, 0, image.width, image.height);
    for (var py = 0; py < image.height; py += 25) {
      for (var px = 0; px < image.width; px += 25) {
        ctx.fillStyle = "rgb(" + (py / image.height * 255 | 0) + "," + 
                                 (px / image.width  * 255 | 0) + "," + 
                                  255 + ")";
        ctx.beginPath();
        ctx.arc(px + 12, py + 12, 10, 0, Math.PI * 2);
        ctx.fill();
      }
    }
      
setupWebGL();
requestAnimationFrame(draw);
canvas { border: 1px solid red; }
<canvas width="400" height="300" id="canvas"></canvas>
<script src="//webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="//webglfundamentals.org/webgl/resources/webgl-2d-math.js"></script>
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;

uniform mat3 u_matrix;    // manipulates position
uniform mat3 u_texMatrix; // manipulates texcoords

varying vec2 v_texCoord;

void main() {
     gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);

   // pass the texCoord to the fragment shader
   // The GPU will interpolate this value between points.
   v_texCoord = (u_texMatrix * vec3(a_texCoord, 1)).xy;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

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

Другие вопросы по тегам