如何在WebGL中实现一个滚动纹理? [英] How to implement a scrolling texture in WebGL?

查看:248
本文介绍了如何在WebGL中实现一个滚动纹理?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经通过了解如何呈现正常纹理使用WebGL并成功实现它的画布。



现在我想实现一个滚动图像,其中我从同一网络中的另一台机器逐行像素数据通过



现在我知道我们可以轻松地渲染每秒60帧,这意味着如果我得到50行每秒从网络中的另一台计算机传输的像素数据,我应该很容易能够渲染它们没有任何混乱。



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



基本上,我正在寻找的是我将从网络中的另一个pc中获取1行像素数据,然后我将渲染为纹理的第一行,然后当我收到第二行,然后GPU应该移动第一行像素数据到第二行,并且在渲染通过网络接收的第一行像素数据后,我将通过调用gl.texSubImage2D渲染第一行,并调用gl.drawArrays来确保我们渲染没有任何jerks。这应该发生到纹理结束,因此GPU负责移动像素,CPU负责发送新的一行像素数据到GPU。



因此,在这个方式GPU将处理移动像素以完成滚动图像,而不是CPU做它和悬挂浏览器。



我还经历了这是最有效的方式在空间中移动多个对象(存储在VBO中)?应该使用glTranslatef还是着色器?

https://www.opengl.org/discussion_boards/showthread.php/136155-Scrolling-textures



但仍然有点困惑如何实现它。
任何建议?

解决方案

您已经提出同一问题4次了。 此处此处此处此处



已提供工作答案这里



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



你显然想要一个无限滚动的显示。首先,正如我已经指出的,不能保证你的数据将足够快地到达。用户可能处于缓慢的连接或可能正在使用其他连接(netflix,youtube,torrent)。



因此,您不能假设您会足够的数据不断滚动。



这里是我给你之前对于无限数据稍微修改



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

  [1 .......] 

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

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

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

  [12 ......] 

2部分

  [......] [12] 

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

  [92345678] 


[2345678] [9]

重复此过程,它将看起来像一个无限滚动纹理。



代码假装通过调用 getNextLineData 接收无限数据集。 getNextLineData 只是生成一个新的数据列。这个函数需要被从网络接收数据的东西取代。



如果你真的想要这个工作,虽然你将要处理网络问题。你必须决定在网络缓慢时该怎么做。你停止滚动吗?您是否继续使用空白数据滚动?重复相同的数据?什么?



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



如果你需要的数据是活的,那么你不想缓存数据,你想要显示尽可能多的接收每个帧。最简单的方法是调用 getNextLineData ,直到用完数据。



WebGL。你可以很容易地做到用画布2d。创建一个画布为纹理。使用 ctx.putImageData 更新每帧的列,然后调用 ctx2.drawImage code> drawImage 在此WebGL示例中调用



  var m4 = twgl.m4; var gl = twgl .getWebGLContext(document.getElementById(c)); var programInfo = twgl.createProgramInfo(gl,[vs,fs]); // a unit quadvar arrays = {position:{numComponents:2,data: [0,0,1,0,0,1,0,1,1,0,1,1,],},}; var bufferInfo = twgl.createBufferInfoFromArrays(gl,arrays); //使纹理与画布大小相同,只是为了使它easyvar texWidth = gl.canvas.width; var texHeight = gl.canvas.height; //我们只使用1个纹理,所以只是make和绑定它nowvar 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; //我们使用1个字节宽的纹理片,因此我们需要将UNPACK_ALIGNMENT设置为1,因为它默认为4gl.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并回绕到0 destColumn =(destColumn + 1)%texWidth;} //我们传入texWidth和texHeight,因为与图像不同//我们不能查找纹理的宽度和高度//我们传递在targetWidth和targetHeight中告诉它//我们绘制的东西的大小。我们可以用gl.canvas.width和// gl.canvas.height来查看画布的大小,但也许我们可以画一个framebuffer //等等,所以可以通过// srcX ,srcY,srcWidth,srcHeight以像素为单位//从texWidth和texHeight计算// dstX,dstY,dstWidth,dstHeight以像素为单位//从targetWidth和targetHeight函数计算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,}; //这些调整单位四舍五入生成纹理坐标//以选择src纹理的一部分//注意:没有检查srcX + srcWidth超出//纹理或以任何方式在范围内。相同的srcY + srcHeight m4.translate(tmat,[srcX / texWidth,srcY / texHeight,0],tmat); m4.scale(tmat,[srcWidth / texWidth,srcHeight / texHeight,1],tmat); //这些从像素转换到剪辑空间m4.ortho(0,targetWidth,targetHeight,0,-1,1,mat)//这些移动和缩放单位四边形到我们想要的大小//在目标中作为像素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); } //滚动不断的函数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(); // ================================================ ===================== //此行下面的所有内容都代表来自服务器的东西//因此它与答案几乎无关//该代码只是生成无尽的数据count = 0; var data; function getNextLineData(){if(!data){data = new Uint8Array(texHeight); } ++ count; for(var ii = 0; ii  

 

 < script src =https:// twgljs .org / dist / twgl-full.min.js>< / script>< script id =vstype =not-js> //我们总是传递一个0到1的单位四/ /然后使用矩阵来操纵它的属性vec4位置;均匀mat4矩阵;均匀mat4纹理矩阵;变化vec2 texcoord; void main(){gl_Position = matrix * position; texcoord =(textureMatrix * position).xy;}< / script>< script id =fstype =not-js> precision mediump float; gl_FragColor = texture2D(texture,texcoord);}< / script>< canvas id =cwidth =500height =150>< / canvas>  


I have gone through this as a example on how to render a normal texture to a canvas using WebGL and successfully implemented it.

Now I am trying to implement a scrolling image where I am getting line by line pixel data from another machine in the same network via Ajax Polling or Websockets and have to render the same using WebGL to canvas.

So, now I know we can easily render 60 frames per second which means if I get 50 lines of pixel data per second from another computer in the network ,I should be easily able to render them without any jerks.

I am able to do pixel by pixel transfer using CPU(javascript),where I render 1st line of data ,and on receiving second line of data ,I move 1st line of pixel data to 2nd and render the new line to the first line . This works but I am unable to see a smooth scrolling , I have also tried to use gl.texSubImage2D by keeping all line data in a single Array and looping through it on getting a new line ,but it also doesn't work as expected.

Basically ,what I am looking for is I will get 1 line of pixel data from another pc in the network and then I will render that as 1st line of the texture ,then when I receive the second line , then the GPU should move the 1st line of pixel data to second line and after I render the 1st new line of pixel data received via network ,I will render the first line by calling gl.texSubImage2D and call gl.drawArrays to make sure we are rendering without any jerks. This should happen until the end of texture, so GPU takes care of moving the pixels and CPU takes care of sending new line of pixel data to GPU.

So, in this way GPU will take care of moving the pixels to accomplish a scrolling image ,instead of CPU doing it and hanging the browser.

I have also gone through what is the most efficient way of moving multiple objects (stored in VBO) in space? should I use glTranslatef or a shader? and https://www.opengl.org/discussion_boards/showthread.php/136155-Scrolling-textures

But still a little confused on how to implement it exactly. Any suggestions?

解决方案

You have asked this same question 4 times already. Here, here, here, and here.

An working answer was already given here.

It basically showed that while you can render at 60fps you are unlikely to be able to receive data at 60fps. It also showed there's NO REASON TO MOVE THE DATA. Why create more work for yourself?

You apparently want an infinitely scrolling display. First off, as I already pointed out, there's no guarantee your data will arrive fast enough. The user might be on a slow connection or might be using the connection for something else (netflix, youtube, torrents).

So you can't assume you'll have enough data to constantly scroll.

Here's the same answer I gave you before slightly modified for infinite data

Notes: It assumes 1 column is added to the texture per frame. All it does is keep writing a column into the texture. So imagine there are only 8 columns in the texture. The first frame the texture looks like this with only 1 column of data

 [1.......]

It then draws this using 2 draw calls for the same texture drawing just the first column on the right and the rest of the texture on the left.

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

Next frame the texture looks like this with 2 columns of data

 [12......]

And it draws these 2 parts

 [......][12]

When the the 9th column of data comes in (our texture is only 8 columns large) we replace the 1st column so the texture looks like this

 [92345678]

We then draw this with 2 draw calls

 [2345678][9]

Repeat this forever and it will look like an infinite scrolling texture.

The code pretends it's receiving an infinite set of data by calling getNextLineData. getNextLineData just generates a new column of data. That function would need to be replaced by something that receives data from the network.

If you really want this to work though you're going to have to deal with network issues. You're going to have to decide what to do when the network is slow. Do you stop scrolling? Do you keep scrolling with blank data? Repeating the same data? What?

Similarly the network could be fast. If you start receiving data too fast what do you do? Draw extra columns? Scroll faster? I'd guess you probably want to cache the data but only use 1 column of it per frame so you'd need a list of columns. getNextLineData would pull off the oldest column and your network code would add columns of data.

You mentioned ECG though. If you need the data to be live then you wouldn't want to cache the data, you'd want to show as much of it as you received each frame. The simplest way would be to call getNextLineData until you run out of data.

Also note nothing about this example requires WebGL. You could just as easily do it with canvas 2d. Create one canvas to be the "texture". Update a column per frame using ctx.putImageData then call ctx2.drawImage into a separate canvas calls similar to the 2 drawImage calls in this WebGL example

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中实现一个滚动纹理?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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