如何从ArrayBuffer在WebGL中渲染图像 [英] How to render images in WebGL from ArrayBuffer

查看:282
本文介绍了如何从ArrayBuffer在WebGL中渲染图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在服务器端读取图像,并通过AJAX调用推送到Web浏览器.我有一个要求,我必须使用WebGL逐行渲染它们.

例如:图像是640X480,其中640是宽度,而480是高度.现在,像素总数将为640 * 480 = 307200像素.因此,我想使用WebGL在一个循环中以640(总宽度)的间隔渲染整个图像.

现在我在webgl中有Texture2D(据我所知),但是对从何处开始一无所知.我也有ArrayBuffer,只有一件事是使用Texture2D,我想逐行缓慢地渲染它.

如果满足要求,我已经准备好使用任何js库. 因此,要逐行编写图像,我们可以执行以下操作.

顶点着色器

  • attribute vec2 a_position;?
    attribute vec2 a_texCoord;?
    
    void main() {
       ???
    }
    

  • 片段着色器

     #ifdef GL_ES
     precision mediump float;
     #endif
    
    uniform float time;
    uniform vec2 mouse;
    uniform vec2 resolution;
    
    void main( void ) {
      vec2 position = 1.0 - gl_FragCoord.xy / resolution;
      vec3 color = vec3(1.0);
    
      if (time > position.y * 10.0) {
          color = texture2D(uImage0, uv);
      }
    
     gl_FragColor = vec4(color, 1.0);
    
    }
    

  • 用于逐像素渲染的Javascript

      function createTextureFromArray(gl, dataArray, type, width, height) {
            var data = new Uint8Array(dataArray);
            var texture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.texImage2D(gl.TEXTURE_2D, 0, type, width, height, 0, type, gl.UNSIGNED_BYTE, data);
            return texture;
     }
    
       var arrayBuffer = new ArrayBuffer(640*480);
       for (var i=0; i < 640; i++) {
           for (var j=0; j < 480; j++) {
               arrayBuffer[i] = Math.floor(Math.random() * 255) + 0;     //filling buffer with random data between 0 and 255 which will be further filled to the texture 
               //NOTE : above data is just dummy data , I will get this data from server pixel by pixel.
           }
       }
       var gl = canvas.getContext('webgl');
       // setup GLSL program
       var program = createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
       gl.useProgram(program);
       //what should I add after this ?
    

    任何人都可以完成代码,我不知道如何编写代码来完成此操作.

解决方案

目前尚不清楚您要完成的工作以及为什么要使用WebGL.您是否一次发送一行数据,并且想要在接收到一行数据时呈现该数据行?您是否正在发送所有数据,而只想水平显示一条线?

如果您有整个图像可用,则可以使用canvas2d渲染其中越来越大的一部分. drawImage函数采用可选的源矩形和目标矩形.

// at init time
var x = 0;

// at render time
while (x < img.width) {
  var srcX = x;
  var srcY = 0;
  var srcWidth = 1;  // one pixel per frame
  var srcHeight = img.height;
  var dstX = x;
  var dstY = 0;
  var dstWidth = 1;
  var dstHeight = img.height;
  ctx.drawImage(img, srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight);
  ++x;
}

如果一次要向他们发送1行数据,则可以使用ImageData制作高度为1x的图像,并使用putImageData进行绘制.

// at init time or whenever you know the height
var imageData = ctx.createImageData(1, height);
var x = 0;

// on received next line of data
for (var ii = 0; ii < imageData.length; ++ii) {
  imageData.data[ii] = receivedColumnOfPixels[ii];
}

ctx.putImageData(imageData, x, 0);
++x;

如果要缩放ImageData,请将其放在第二个画布中,并使用第一种技术将该画布用作drawImage的输入.

您可以在WebGL中执行相同的操作.如果内存中有整个纹理,则每个帧都会调整位置和纹理坐标以绘制其中的不同部分.如果一次要接收一列数据,则只需使用高度为1 x的纹理并将其绘制在适当的位置即可.或者,使用gl.texSubImage2D将1 x高度数据复制到完整大小的纹理中,然后适当调整位置和纹理坐标,以将要绘制的纹理部分绘制到要绘制的画布部分. 在WebGL中实现的

drawImage看起来像这样.我使用的是 twgl.js ,因为WebGL太冗长了.

 var m4 = twgl.m4;
var gl = document.getElementById("c").getContext("webgl");
// compiles shader, links and looks up locations
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);

// a unit quad
var arrays = {
  position: { 
    numComponents: 2, 
    data: [
      0, 0,  
      1, 0, 
      0, 1, 
      0, 1, 
      1, 0,  
      1, 1,
    ],
  },
};
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

// create a texture using a canvas so we don't have to download one
var ctx = document.createElement("canvas").getContext("2d");
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.lineWidth = 20;
["red", "orange", "yellow"].forEach(function(color, ndx, array) {
  ctx.strokeStyle = color;
  ctx.beginPath();
  ctx.arc((ndx + 1) / (array.length + 1) * ctx.canvas.width, ctx.canvas.height / 2, ctx.canvas.height * 0.4, 0, Math.PI * 2, false);
  ctx.stroke();
});
ctx.fillStyle = "white";
ctx.font = "40px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("DrawImage", ctx.canvas.width / 2, ctx.canvas.height / 2);

// calls gl.createTexture, gl.bindTexture, gl.texImage2D, gl.texParameteri
var tex = twgl.createTexture(gl, { src: ctx.canvas });
var texWidth  = ctx.canvas.width;
var texHeight = ctx.canvas.height;

// we pass in texWidth and texHeight because unlike images
// we can't look up the width and height of a texture

// we pass in targetWidth and targetHeight to tell it
// the size of the thing we're drawing too. We could look 
// up the size of the canvas with gl.canvas.width and
// gl.canvas.height but maybe we want to draw to a framebuffer
// etc.. so might as well pass those in.

// srcX, srcY, srcWidth, srcHeight are in pixels 
// computed from texWidth and texHeight

// dstX, dstY, dstWidth, dstHeight are in pixels
// computed from targetWidth and targetHeight
function drawImage(
    tex, texWidth, texHeight,
    srcX, srcY, srcWidth, srcHeight,
    dstX, dstY, dstWidth, dstHeight,
    targetWidth, targetHeight) {
  var mat  = m4.identity();
  var tmat = m4.identity();
  
  var uniforms = {
    matrix: mat,
    textureMatrix: tmat,
    texture: tex,
  };

  // these adjust the unit quad to generate texture coordinates
  // to select part of the src texture

  // NOTE: no check is done that srcX + srcWidth go outside of the
  // texture or are in range in any way. Same for srcY + srcHeight

  m4.translate(tmat, [srcX / texWidth, srcY / texHeight, 0], tmat);
  m4.scale(tmat, [srcWidth / texWidth, srcHeight / texHeight, 1], tmat);

  // these convert from pixels to clip space
  m4.translate(mat, [-1, 1, 0], mat);
  m4.scale(mat, [2 / targetWidth, -2 / targetHeight, 1], mat); 

  // these move and scale the unit quad into the size we want
  // in the target as pixels
  m4.translate(mat, [dstX, dstY, 0], mat);
  m4.scale(mat, [dstWidth, dstHeight, 1], mat);

  gl.useProgram(programInfo.program);
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  // calls gl.uniformXXX, gl.activeTexture, gl.bindTexture
  twgl.setUniforms(programInfo, uniforms);
  // calls gl.drawArray or gl.drawElements
  twgl.drawBufferInfo(gl, gl.TRIANGLES, bufferInfo);
  
}
  
function render(time) {
  time *= 0.001;
  var targetWidth  = gl.canvas.width;
  var targetHeight = gl.canvas.height;

  // pick some various src rects and dst rects
  var srcX = Math.abs(Math.sin(time * 1   )) * texWidth;
  var srcY = Math.abs(Math.sin(time * 1.81)) * texHeight;
  var srcWidth  = (texWidth  - srcX) * Math.abs(Math.sin(time * 2.12));
  var srcHeight = (texHeight - srcY) * Math.abs(Math.sin(time * 1.53));

  var dstX = Math.abs(Math.sin(time * 0.34)) * targetWidth;
  var dstY = Math.abs(Math.sin(time * 2.75)) * targetHeight;
  var dstWidth  = (targetWidth  - dstX) * Math.abs(Math.sin(time * 1.16));
  var dstHeight = (targetHeight - dstY) * Math.abs(Math.sin(time * 1.17));
  
  drawImage(
    tex, texWidth, texHeight,  
    srcX, srcY, srcWidth, srcHeight,
    dstX, dstY, dstWidth, dstHeight,
    targetWidth, targetHeight);

  requestAnimationFrame(render);
}
requestAnimationFrame(render); 

 canvas { border: 1px solid black; } 

 <script src="https://twgljs.org/dist/twgl-full.min.js"></script>
<script id="vs" type="not-js">
// we will always pass a 0 to 1 unit quad
// and then use matrices to manipulate it
attribute vec4 position;   

uniform mat4 matrix;
uniform mat4 textureMatrix;

varying vec2 texcoord;

void main () {
  gl_Position = matrix * position;
  
  texcoord = (textureMatrix * position).xy;
}
</script>
<script id="fs" type="not-js">
precision mediump float;

varying vec2 texcoord;
uniform sampler2D texture;

void main() {
  gl_FragColor = texture2D(texture, texcoord);
}
</script>
<canvas id="c"></canvas> 

要了解它们,请使用参阅这些文章,然后这些文章中的前进或后退方式.

I am having a image that I am reading in server side and pushing to web browser via AJAX call. I have a requirement where I have to render them line by line using WebGL.

For Example : Image is 640X480 where 640 is width and 480 is height. Now the total number of pixels will be 640*480 = 307200 pixels. So, I want to render the whole image in 640(total width) intervals in a loop using WebGL.

Now I have texture2D(as per my knowledge) in webgl to do so, but not getting any idea of where to start . I also having the ArrayBuffer with me , only thing is using Texture2D I want to render it slowly ,line by line.

I am ready to go for any js libraries ,if they are satisfying the requirements. So, to write a image line by line we can do something like this.

Vertex Shader

  • attribute vec2 a_position;?
    attribute vec2 a_texCoord;?
    
    void main() {
       ???
    }
    

  • Fragment Shader

     #ifdef GL_ES
     precision mediump float;
     #endif
    
    uniform float time;
    uniform vec2 mouse;
    uniform vec2 resolution;
    
    void main( void ) {
      vec2 position = 1.0 - gl_FragCoord.xy / resolution;
      vec3 color = vec3(1.0);
    
      if (time > position.y * 10.0) {
          color = texture2D(uImage0, uv);
      }
    
     gl_FragColor = vec4(color, 1.0);
    
    }
    

  • Javascript For rendering pixel by pixel

      function createTextureFromArray(gl, dataArray, type, width, height) {
            var data = new Uint8Array(dataArray);
            var texture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.texImage2D(gl.TEXTURE_2D, 0, type, width, height, 0, type, gl.UNSIGNED_BYTE, data);
            return texture;
     }
    
       var arrayBuffer = new ArrayBuffer(640*480);
       for (var i=0; i < 640; i++) {
           for (var j=0; j < 480; j++) {
               arrayBuffer[i] = Math.floor(Math.random() * 255) + 0;     //filling buffer with random data between 0 and 255 which will be further filled to the texture 
               //NOTE : above data is just dummy data , I will get this data from server pixel by pixel.
           }
       }
       var gl = canvas.getContext('webgl');
       // setup GLSL program
       var program = createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
       gl.useProgram(program);
       //what should I add after this ?
    

    Can anybody complete the code , I have no idea of how to write code to accomplish this.

解决方案

It's not clear at all what you're trying to accomplish and why you are using WebGL at all. Are you sending one line of data at a time and you want to render that one individual line of data when its received? Are you sending all the data and you just want reveal it a line at time horizontally?

If you have the entire image available then you can just render a larger and larger portion of it using canvas2d. The drawImage function takes optional source and destination rectangles.

// at init time
var x = 0;

// at render time
while (x < img.width) {
  var srcX = x;
  var srcY = 0;
  var srcWidth = 1;  // one pixel per frame
  var srcHeight = img.height;
  var dstX = x;
  var dstY = 0;
  var dstWidth = 1;
  var dstHeight = img.height;
  ctx.drawImage(img, srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight);
  ++x;
}

If you're sending them 1 line of data at a time you can use ImageData to make a 1xheight image and use putImageData to draw it.

// at init time or whenever you know the height
var imageData = ctx.createImageData(1, height);
var x = 0;

// on received next line of data
for (var ii = 0; ii < imageData.length; ++ii) {
  imageData.data[ii] = receivedColumnOfPixels[ii];
}

ctx.putImageData(imageData, x, 0);
++x;

If you want to scale the ImageData put it in a second canvas and use that canvas as input to drawImage using the first technique.

You can do the same things in WebGL. If you have the entire texture in memory then each frame adjust your positions and texture coordinates to draw a different part of it. If you're receiving 1 column of data at a time then just use a texture that's 1 x height and draw that at the appropriate place. OR, copy that 1 x height data into the fullsize texture using gl.texSubImage2D and then adjust the positions and texture coordinates appropriately to draw the part of the texture you want to draw to the part of the canvas you want to draw it.

drawImage implemented in WebGL would look something like this. I'm using twgl.js because WebGL is too verbose.

var m4 = twgl.m4;
var gl = document.getElementById("c").getContext("webgl");
// compiles shader, links and looks up locations
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);

// a unit quad
var arrays = {
  position: { 
    numComponents: 2, 
    data: [
      0, 0,  
      1, 0, 
      0, 1, 
      0, 1, 
      1, 0,  
      1, 1,
    ],
  },
};
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

// create a texture using a canvas so we don't have to download one
var ctx = document.createElement("canvas").getContext("2d");
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.lineWidth = 20;
["red", "orange", "yellow"].forEach(function(color, ndx, array) {
  ctx.strokeStyle = color;
  ctx.beginPath();
  ctx.arc((ndx + 1) / (array.length + 1) * ctx.canvas.width, ctx.canvas.height / 2, ctx.canvas.height * 0.4, 0, Math.PI * 2, false);
  ctx.stroke();
});
ctx.fillStyle = "white";
ctx.font = "40px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("DrawImage", ctx.canvas.width / 2, ctx.canvas.height / 2);

// calls gl.createTexture, gl.bindTexture, gl.texImage2D, gl.texParameteri
var tex = twgl.createTexture(gl, { src: ctx.canvas });
var texWidth  = ctx.canvas.width;
var texHeight = ctx.canvas.height;

// we pass in texWidth and texHeight because unlike images
// we can't look up the width and height of a texture

// we pass in targetWidth and targetHeight to tell it
// the size of the thing we're drawing too. We could look 
// up the size of the canvas with gl.canvas.width and
// gl.canvas.height but maybe we want to draw to a framebuffer
// etc.. so might as well pass those in.

// srcX, srcY, srcWidth, srcHeight are in pixels 
// computed from texWidth and texHeight

// dstX, dstY, dstWidth, dstHeight are in pixels
// computed from targetWidth and targetHeight
function drawImage(
    tex, texWidth, texHeight,
    srcX, srcY, srcWidth, srcHeight,
    dstX, dstY, dstWidth, dstHeight,
    targetWidth, targetHeight) {
  var mat  = m4.identity();
  var tmat = m4.identity();
  
  var uniforms = {
    matrix: mat,
    textureMatrix: tmat,
    texture: tex,
  };

  // these adjust the unit quad to generate texture coordinates
  // to select part of the src texture

  // NOTE: no check is done that srcX + srcWidth go outside of the
  // texture or are in range in any way. Same for srcY + srcHeight

  m4.translate(tmat, [srcX / texWidth, srcY / texHeight, 0], tmat);
  m4.scale(tmat, [srcWidth / texWidth, srcHeight / texHeight, 1], tmat);

  // these convert from pixels to clip space
  m4.translate(mat, [-1, 1, 0], mat);
  m4.scale(mat, [2 / targetWidth, -2 / targetHeight, 1], mat); 

  // these move and scale the unit quad into the size we want
  // in the target as pixels
  m4.translate(mat, [dstX, dstY, 0], mat);
  m4.scale(mat, [dstWidth, dstHeight, 1], mat);

  gl.useProgram(programInfo.program);
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  // calls gl.uniformXXX, gl.activeTexture, gl.bindTexture
  twgl.setUniforms(programInfo, uniforms);
  // calls gl.drawArray or gl.drawElements
  twgl.drawBufferInfo(gl, gl.TRIANGLES, bufferInfo);
  
}
  
function render(time) {
  time *= 0.001;
  var targetWidth  = gl.canvas.width;
  var targetHeight = gl.canvas.height;

  // pick some various src rects and dst rects
  var srcX = Math.abs(Math.sin(time * 1   )) * texWidth;
  var srcY = Math.abs(Math.sin(time * 1.81)) * texHeight;
  var srcWidth  = (texWidth  - srcX) * Math.abs(Math.sin(time * 2.12));
  var srcHeight = (texHeight - srcY) * Math.abs(Math.sin(time * 1.53));

  var dstX = Math.abs(Math.sin(time * 0.34)) * targetWidth;
  var dstY = Math.abs(Math.sin(time * 2.75)) * targetHeight;
  var dstWidth  = (targetWidth  - dstX) * Math.abs(Math.sin(time * 1.16));
  var dstHeight = (targetHeight - dstY) * Math.abs(Math.sin(time * 1.17));
  
  drawImage(
    tex, texWidth, texHeight,  
    srcX, srcY, srcWidth, srcHeight,
    dstX, dstY, dstWidth, dstHeight,
    targetWidth, targetHeight);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

canvas { border: 1px solid black; }

<script src="https://twgljs.org/dist/twgl-full.min.js"></script>
<script id="vs" type="not-js">
// we will always pass a 0 to 1 unit quad
// and then use matrices to manipulate it
attribute vec4 position;   

uniform mat4 matrix;
uniform mat4 textureMatrix;

varying vec2 texcoord;

void main () {
  gl_Position = matrix * position;
  
  texcoord = (textureMatrix * position).xy;
}
</script>
<script id="fs" type="not-js">
precision mediump float;

varying vec2 texcoord;
uniform sampler2D texture;

void main() {
  gl_FragColor = texture2D(texture, texcoord);
}
</script>
<canvas id="c"></canvas>

To understand them matrix math see these articles and work your way backward or forward in those articles.

这篇关于如何从ArrayBuffer在WebGL中渲染图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆