WebGL渲染超出浏览器绘制时间

问题描述 投票:1回答:1

我们正在构建一个WebGL应用程序,其中包含一些高渲染负载的对象。有什么方法可以在浏览器绘制时间之外(即在后台)渲染那些对象?我们不希望FPS下降,并且可以分解渲染过程(在帧之间拆分)。

webgl
1个回答
0
投票

想到三个主意。

  1. 您可以通过帧缓冲区在许多帧上渲染纹理,完成后将该纹理渲染到画布上。

const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
  gl_Position = position;
  v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
uniform sampler2D tex;
varying vec2 v_texcoord;
void main() {
  gl_FragColor = texture2D(tex, v_texcoord);
}
`;

// compile shader, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  position: {
    numComponents: 2,
    data: [
      -1, -1,
       1, -1,
      -1,  1,
      -1,  1,
       1, -1,
       1,  1,
    ],
  },
  texcoord: {
    numComponents: 2,
    data: [
       0,  0,
       1,  0,
       0,  1,
       0,  1,
       1,  0,
       1,  1,
    ],  
  },
});

// create a framebuffer with a texture and depth buffer
// same size as canvas
// gl.createTexture, gl.texImage2D, gl.createFramebuffer
// gl.framebufferTexture2D
const framebufferInfo = twgl.createFramebufferInfo(gl);

const infoElem = document.querySelector('#info');

const numDrawSteps = 16;
let drawStep = 0;
let time = 0;

// draw over several frames. Return true when ready
function draw() {
  // draw to texture
  // gl.bindFrambuffer, gl.viewport
  twgl.bindFramebufferInfo(gl, framebufferInfo);
  
  if (drawStep == 0) {
    // on the first step clear and record time
    gl.disable(gl.SCISSOR_TEST);
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT  | gl.DEPTH_BUFFER_BIT);
    time = performance.now() * 0.001;
  }
  

  // this represents drawing something. 
  gl.enable(gl.SCISSOR_TEST);
  
  const halfWidth = framebufferInfo.width / 2;
  const halfHeight = framebufferInfo.height / 2;
  
  const a = time * 0.1 + drawStep
  const x = Math.cos(a      ) * halfWidth + halfWidth;
  const y = Math.sin(a * 1.3) * halfHeight + halfHeight;

  gl.scissor(x, y, 16, 16);
  gl.clearColor(
     drawStep / 16,
     drawStep / 6 % 1,
     drawStep / 3 % 1,
     1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  drawStep = (drawStep + 1) % numDrawSteps;
  return drawStep === 0;
}

let frameCount = 0;
function render() {
  ++frameCount;
  infoElem.textContent = frameCount;
  
  if (draw()) {
    // draw to canvas
    // gl.bindFramebuffer, gl.viewport
    twgl.bindFramebufferInfo(gl, null);
    
    gl.disable(gl.DEPTH_TEST);
    gl.disable(gl.BLEND);
    gl.disable(gl.SCISSOR_TEST);
    
    gl.useProgram(programInfo.program);
    
    // gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    
    // gl.uniform...
    twgl.setUniformsAndBindTextures(programInfo, {
      tex: framebufferInfo.attachments[0],
    });
    
    // draw the quad
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  }
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
<canvas></canvas>
<div id="info"></div>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
  1. 您可以制作2张画布。不在DOM中的webgl画布。您可以在许多帧上对其进行渲染,完成后可以使用ctx.drawImage(webglCanvas, ...)将其绘制到2D画布上。这与#1基本相同,不同之处在于您让浏览器“将纹理渲染到画布上”部分

const ctx = document.querySelector('canvas').getContext('2d');
const gl = document.createElement('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
  gl_Position = position;
  v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
uniform sampler2D tex;
varying vec2 v_texcoord;
void main() {
  gl_FragColor = texture2D(tex, v_texcoord);
}
`;

// compile shader, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

const infoElem = document.querySelector('#info');

const numDrawSteps = 16;
let drawStep = 0;
let time = 0;

// draw over several frames. Return true when ready
function draw() {  
  if (drawStep == 0) {
    // on the first step clear and record time
    gl.disable(gl.SCISSOR_TEST);
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT  | gl.DEPTH_BUFFER_BIT);
    time = performance.now() * 0.001;
  }
  

  // this represents drawing something. 
  gl.enable(gl.SCISSOR_TEST);
  
  const halfWidth = gl.canvas.width / 2;
  const halfHeight = gl.canvas.height / 2;
  
  const a = time * 0.1 + drawStep
  const x = Math.cos(a      ) * halfWidth + halfWidth;
  const y = Math.sin(a * 1.3) * halfHeight + halfHeight;

  gl.scissor(x, y, 16, 16);
  gl.clearColor(
     drawStep / 16,
     drawStep / 6 % 1,
     drawStep / 3 % 1,
     1);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  drawStep = (drawStep + 1) % numDrawSteps;
  return drawStep === 0;
}

let frameCount = 0;
function render() {
  ++frameCount;
  infoElem.textContent = frameCount;
  
  if (draw()) {
    // draw to canvas
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    ctx.drawImage(gl.canvas, 0, 0);
  }
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
<canvas></canvas>
<div id="info"></div>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
  1. 您可以使用OffscreenCanvas并在工作程序中进行渲染。不过,这仅在Chrome中提供。

请注意,如果您使用DOS的GPU(给GPU过多的工作),您仍然可以影响主线程的响应能力,因为大多数GPU不支持抢先式多任务处理。因此,如果您有很多真正繁重的工作,则将其分解为较小的任务。

例如,如果您从shadertoy.com中获取了最重的着色器之一,以1920x1080渲染时,其运行速度为0.5 fps,即使在屏幕外,也会迫使整个机器以0.5 fps的速度运行。要解决此问题,您需要在几帧上渲染较小的部分。如果它以0.5 fps的速度运行,则表明您需要将其分成至少120个较小的部分(也许更多),以保持主线程的响应速度,而在120个较小的部分中,您将仅每2秒查看一次结果。

实际上尝试显示一些问题。 Here's Iq's Happy Jumping Example drawn over 960 frames。即使我的2018年末Macbook Air每帧仅渲染2160像素(1920x1080画布的2列),仍然无法保持60fps。问题可能是场景的某些部分必须深入进行,并且无法事先知道场景的哪些部分。使用签名距离字段的阴影样式着色器更多是toy(因此,shaderTOY)而不是生产样式技术的原因之一。

无论如何,关键是如果您给GPU过多的工作,您仍然会获得无响应的计算机。

© www.soinside.com 2019 - 2024. All rights reserved.