如何在WebGL中实现滚动纹理?

时间:2016-03-17 12:17:46

标签: javascript canvas opengl-es webgl

我已经通过this作为示例,了解如何使用WebGL将正常纹理渲染到画布并成功实现它。

现在我正在尝试实现一个滚动图像,我通过Ajax Polling或Websockets从同一网络中的另一台机器获取逐行像素数据,并且必须使用WebGL渲染相同的画布。

所以,现在我知道我们可以轻松渲染每秒60帧,这意味着如果我每秒从网络中的另一台计算机获得50行像素数据,我应该可以轻松地渲染它们而不会产生任何混乱。

我可以使用CPU(javascript)进行逐像素传输,在这里我渲染第一行数据,在接收第二行数据时,我将第一行像素数据移动到第二行并将新行渲染到第一行 。这有效但我无法看到平滑的滚动,我也尝试使用gl.texSubImage2D将所有行数据保存在单个数组中并在获取新行时循环通过它,但它也无法按预期工作。

基本上,我正在寻找的是我将从网络中的另一台PC获得1行像素数据然后我将其渲染为纹理的第一行,然后当我收到第二行时,那么GPU应该将第一行像素数据移动到第二行,在我渲染通过网络接收的第一行新像素数据后,我将通过调用gl.texSubImage2D渲染第一行并调用gl.drawArrays以确保我们在没有任何混蛋的情况下渲染。这应该发生在纹理结束之前,因此GPU负责移动像素,CPU负责将新的像素数据线发送到GPU。

因此,GPU将以这种方式处理移动像素以完成滚动图像,而不是CPU执行它并挂起浏览器。

我也经历了what is the most efficient way of moving multiple objects (stored in VBO) in space? should I use glTranslatef or a shader?  和 https://www.opengl.org/discussion_boards/showthread.php/136155-Scrolling-textures

但是如何完全实现它仍然有点困惑。 有什么建议吗?

1 个答案:

答案 0 :(得分:1)

你已经问了4次同样的问题。 Herehereherehere

已经给出了工作答案here

它基本上表明,虽然你可以以60fps渲染,但你不太可能以60fps的速度接收数据。它还显示没有移动数据的理由。为什么要为自己创造更多的工作?

你显然想要一个无限滚动的显示。首先,正如我已经指出的那样,无法保证您的数据足够快到达。用户可能连接速度较慢,或者可能正在使用其他连接(netflix,youtube,torrent)。

所以你不能假设你有足够的数据来不断滚动。

这里针对无限数据略微修改了the same answer I gave you before

注意:它假定每帧的纹理添加了1列。它所做的只是继续在纹理中写一列。所以想象一下纹理中只有8列。第一帧纹理看起来像这样只有一列数据

 [1.......]

然后使用2个绘制调用绘制相同的纹理,只绘制右边的第一列和左边的纹理的其余部分。

 [.......][1]

下一帧纹理看起来像这样,有两列数据

 [12......]

它绘制了这两部分

 [......][12]

当第9列数据进入时(我们的纹理只有8列大)我们替换第1列,所以纹理看起来像这样

 [92345678]

然后我们用2个绘制调用来绘制它

 [2345678][9]

永远重复此过程,它看起来像无限滚动纹理。

代码假装通过调用getNextLineData接收无限数据集。 getNextLineData只会生成一个新的数据列。该功能需要由从网络接收数据的东西取代。

如果你真的想要这个,虽然你将不得不处理网络问题。当网络很慢时,您将不得不决定该做什么。你停止滚动吗?你一直在滚动空白数据吗?重复相同的数据?什么?

同样,网络可能很快。如果你开始接收数据的速度太快,你会怎么做?画出额外的列?滚动更快?我猜你可能想要缓存数据,但每帧只使用1列,所以你需要一个列列表。 getNextLineData会删除最旧的列,您的网络代码会添加数据列。

你提到了心电图。如果您需要数据生效,那么您不希望缓存数据,您希望显示与每帧收到的数据一样多的数据。最简单的方法是在用完数据之前调用getNextLineData

另请注意,此示例不需要WebGL。你可以用canvas 2d轻松做到这一点。创建一个画布作为“纹理”。使用ctx.putImageData每帧更新一列,然后将ctx2.drawImage调用到单独的画布调用中,类似于此WebGL示例中的2 drawImage次调用

var m4 = twgl.m4;
var gl = twgl.getWebGLContext(document.getElementById("c"));
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,
    ],
  },
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
      
// make the texture the same size as the canvas just to make it easy
var texWidth = gl.canvas.width;
var texHeight = gl.canvas.height;
      
// we're only using 1 texture so just make and bind it now
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
      gl.TEXTURE_2D, 0, gl.LUMINANCE, texWidth, texHeight, 0, 
      gl.LUMINANCE, gl.UNSIGNED_BYTE, null);
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.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
      
var destColumn = 0;
      
// We're using 1 byte wide texture pieces so we need to 
// set UNPACK_ALIGNMENT to 1 as it defaults to 4
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
            
function addLineToTexture(lineData) {
  gl.texSubImage2D(gl.TEXTURE_2D, 0, destColumn, 0, 1, texHeight,
                   gl.LUMINANCE, gl.UNSIGNED_BYTE, lineData);
      
  // advance column and wrap back to 0
  destColumn = (destColumn + 1) % texWidth;
}

// 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.ortho(0, targetWidth, targetHeight, 0, -1, 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);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, uniforms);
  twgl.drawBufferInfo(gl, gl.TRIANGLES, bufferInfo);
  
}

// Scroll constantly
  
function render() {
  addLineToTexture(getNextLineData());
  
  function drawFirstPart() {
    var srcX = 0;
    var srcY = 0;
    var srcWidth  = destColumn;
    var srcHeight = texHeight;

    var dstX = texWidth - destColumn;
    var dstY = 0;
    var dstWidth  = destColumn;
    var dstHeight = texHeight;

    var targetWidth  = gl.canvas.width;
    var targetHeight = gl.canvas.height;

    drawImage(
      tex, texWidth, texHeight,  
      srcX, srcY, srcWidth, srcHeight,
      dstX, dstY, dstWidth, dstHeight,  
      targetWidth, targetHeight);
  }

  function drawSecondPart() {
    var srcX = destColumn;
    var srcY = 0;
    var srcWidth  = texWidth - destColumn + 1;
    var srcHeight = texHeight;

    var dstX = 0;
    var dstY = 0;
    var dstWidth  = texWidth - destColumn + 1;
    var dstHeight = texHeight;

    var targetWidth  = gl.canvas.width;
    var targetHeight = gl.canvas.height;

    drawImage(
      tex, texWidth, texHeight,  
      srcX, srcY, srcWidth, srcHeight,
      dstX, dstY, dstWidth, dstHeight,
      targetWidth, targetHeight);
  }

  drawFirstPart();
  drawSecondPart();
  
  requestAnimationFrame(render);
}
render();
  
  
// =====================================================================
// Everything below this line represents stuff from the server.
// so it's mostly irrelevant to the answer
// this code just generates endless data

var count = 0;
var data;

function getNextLineData() {
  if (!data) {
    data = new Uint8Array(texHeight);
  }
  
  ++count;
  for (var ii = 0; ii < data.length; ++ii) {
    data[ii] = 0;
  }
  addPoint(count, 0.010, 255, data);
  addPoint(count, 0.031, 240, data);
  addPoint(count, 0.023, 220, data);
  addPoint(count, 0.013, 200, data);
  
  return data;
}

function addPoint(count, mult, value, data) {
  var s = Math.floor((Math.sin(count * mult) * 0.5 + 0.5) * (data.length - 1));
  data[s] = value;
}
canvas { border: 1px solid red; }
<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" width="500" height="150"></canvas>