【问题标题】:How to implement a scrolling texture in WebGL?如何在 WebGL 中实现滚动纹理?
【发布时间】:2016-03-17 12:17:46
【问题描述】:

我以this 为例介绍了如何使用WebGL 将法​​线纹理渲染到画布并成功实现。

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

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

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

基本上,我正在寻找的是我将从网络中的另一台电脑获取 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

但是对于如何准确实现它仍然有些困惑。 有什么建议吗?

【问题讨论】:

  • 这已经在 opengl 中完成here,我不是 c/c++ 人,所以任何人都可以理解这一点并用 webgl javascript 术语解释我
  • 嗯你在评论中提到的答案没有使用任何c/c++ 并且可以在WebGL中1对1实现,你到底有什么不明白的?

标签: javascript canvas opengl-es webgl


【解决方案1】:

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

here 已经给出了有效的答案。

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

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

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

这里是the same answer I gave you before 为无限数据稍作修改

注意:假设每帧向纹理添加 1 列。它所做的就是不断在纹理中写入一列。所以想象一下纹理中只有 8 列。纹理的第一帧看起来像这样,只有 1 列数据

 [1.......]

然后它使用 2 次绘制调用来绘制这个,用于绘制右侧第一列和左侧纹理的其余部分的相同纹理。

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

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

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

【讨论】:

  • 我同意我已经问过很多次了,但接受它们的唯一原因是我一步一步地走,同时对 webgl 有了更多的了解。我不确定该怎么做,因为我有了更多的想法,因此将问题提升到了一个新的水平。老问题可能会帮助另一个新手,因此我不想删除它们
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-03
  • 2016-08-28
  • 1970-01-01
相关资源
最近更新 更多