Отсечение изображений в 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>