【问题标题】:How do I optimize WebGL to render more objects quickly?如何优化 WebGL 以快速渲染更多对象?
【发布时间】:2020-06-17 13:14:26
【问题描述】:

我的印象是 WebGL 比浏览器中的 2d 渲染器强大得多,但出于某种原因,我的 WebGL 代码运行速度要慢得多。有什么推荐的优化策略可以让我的 WebGL 代码运行得更快一点吗?

我的 rect 函数中是否有任何代码应该省略,因为我是 WebGL 新手,而且大多数教程都没有介绍如何制作 rect 函数。

WebGL 代码

const canvas = document.getElementById("canvas");
const vertexCode = `
precision mediump float;

attribute vec4 position;
uniform mat4 matrix;
uniform vec4 color;
varying vec4 col;

void main() {
  col = color;
  gl_Position = matrix * position;
}
`;
const fragmentCode = `
precision mediump float;

varying vec4 col;

void main() {
  gl_FragColor = col;
}
`;
const width = canvas.width;
const height = canvas.height;
const gl = canvas.getContext("webgl");
if(!gl) {
  console.log("WebGL not supported");
}
const projectionMatrix = [
  2/width, 0, 0, 0,
  0, -2/height, 0, 0,
  0, 0, 1, 0,
  -1, 1, 0, 1
];


const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexCode);
gl.compileShader(vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentCode);
gl.compileShader(fragmentShader);

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);

gl.linkProgram(program);

gl.useProgram(program);

function rect(x, y, w, h) {
  const vertex = [
    x, y, 0, 1,
    x+w, y, 0, 1,
    x, y+h, 0, 1,
    x+w, y+h, 0, 1
  ]
  const positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertex), gl.STATIC_DRAW);

  const positionLocation = gl.getAttribLocation(program, "position");
  gl.enableVertexAttribArray(positionLocation);
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.vertexAttribPointer(positionLocation, 4, gl.FLOAT, false, 0, 0);

  const projectionLocation = gl.getUniformLocation(program, `matrix`);
  gl.uniformMatrix4fv(projectionLocation, false, projectionMatrix);

  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function fill(r, g, b, a) {
  const projectionLocation = gl.getUniformLocation(program, `color`);
  gl.uniform4fv(projectionLocation, [r, g, b, a]);
}
let lastTime = new Date();
function animate() {
  let currentTime = new Date();
  console.log(1000 / (currentTime.getTime() - lastTime.getTime()));
  lastTime = new Date();
  requestAnimationFrame(animate);
  for(let i=0;i<200;i++) {
    fill(1, 0, 0, 1);
    rect(random(0, 800), random(0, 600), 10, 10);
  }
}
animate();
function random(low, high) {
  return low + Math.random() * (high-low)
}

使用普通的 2D 渲染器

const canvas = document.getElementById("canvas");
const c = canvas.getContext("2d");
let lastTime = new Date();
function animate() {
  let currentTime = new Date();
  console.log(1000 / (currentTime.getTime() - lastTime.getTime()));
  lastTime = new Date();
  requestAnimationFrame(animate);
  c.fillStyle = "black";
  c.fillRect(0, 0, 800, 600);
  c.fillStyle = "red";
  for(let i=0;i<200;i++) {
    c.fillRect(random(0, 800), random(0, 600), 10, 10);
  }
}
animate();
function random(low, high) {
  return low + Math.random() * (high-low)
}

【问题讨论】:

    标签: javascript webgl


    【解决方案1】:

    优化 WebGL 的方法有 1000 多种。您选择哪一种取决于您的需求。您优化的越多通常越不灵活。

    首先,您应该做显而易见的事情,而不是像您的代码现在那样为每个矩形创建新的缓冲区,并且还将任何可以移到外部的渲染移出(例如查找位置),这在 Józef Podlecki 的答案中显示.

    另一个是您可以使用统一矩阵移动和缩放矩形,而不是为每个矩形上传新顶点。目前尚不清楚更新矩阵对于矩形是否会更快或更慢,但是如果您正在绘制具有更多顶点的东西,那么使用矩阵肯定会更快。 This series of articles 提到了这一点并建立了矩阵。

    另一个是您可以使用vertex arrays,尽管这对于您的示例并不重要,因为您只绘制了一个东西。

    另一个是如果形状都相同(和你的一样),你可以使用instanced drawing通过一次绘制调用绘制所有 200 个矩形

    如果形状不完全相同,您可以使用 storing their data in textures 等创意技巧。

    另一个是您可以在缓冲区中放置多个形状。因此,例如,不要将一个矩形放入缓冲区put all 200 rectangles in the buffer and then draw them with one draw call。来自this presentation

    另请注意:requestAnimationFrame 的回调传递自页面加载以来的时间,因此无需创建 Date 对象。创建Date对象比不创建要慢,传递给requestAnimationFrame的时间比Date更准确

    let lastTime = 0;
    function animate(currentTime) {
      console.log(1000 / (currentTime - lastTime));
      lastTime = currentTime;
      ...
      requestAnimationFrame(animate);
    }
    requestAnimationFrame(animate);
    

    打印到控制台也很慢。你最好更新元素的内容

    let lastTime = 0;
    function animate(currentTime) {
      someElement.textContent = (1000 / (currentTime - lastTime)).toFixed(1);
      lastTime = currentTime;
      ...
      requestAnimationFrame(animate);
    }
    requestAnimationFrame(animate);
    

    【讨论】:

      【解决方案2】:

      绑定缓冲区后,您可以重用buffer,在rect 函数以及vertexAttribPointer 之外提取getAttribLocationgetUniformLocation,并在渲染后最后调用requestAnimationFrame

      const canvas = document.getElementById("canvas");
      const vertexCode = `
      precision mediump float;
      
      attribute vec4 position;
      uniform mat4 matrix;
      uniform vec4 color;
      varying vec4 col;
      
      void main() {
        col = color;
        gl_Position = matrix * position;
      }
      `;
      const fragmentCode = `
      precision mediump float;
      
      varying vec4 col;
      
      void main() {
        gl_FragColor = col;
      }
      `;
      const width = canvas.width;
      const height = canvas.height;
      const gl = canvas.getContext("webgl");
      if(!gl) {
        console.log("WebGL not supported");
      }
      const projectionMatrix = [
        2/width, 0, 0, 0,
        0, -2/height, 0, 0,
        0, 0, 1, 0,
        -1, 1, 0, 1
      ];
      
      
      const vertexShader = gl.createShader(gl.VERTEX_SHADER);
      gl.shaderSource(vertexShader, vertexCode);
      gl.compileShader(vertexShader);
      
      const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
      gl.shaderSource(fragmentShader, fragmentCode);
      gl.compileShader(fragmentShader);
      
      const program = gl.createProgram();
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      
      gl.linkProgram(program);
      
      gl.useProgram(program);
      
      const positionBuffer = gl.createBuffer();
      const positionLocation = gl.getAttribLocation(program, "position");
      const projectionLocation = gl.getUniformLocation(program, `matrix`);
      const projectionColorLocation = gl.getUniformLocation(program, `color`);
      gl.enableVertexAttribArray(positionLocation);
      const vertex = [
          0, 0, 0, 1,
          0, 0, 0, 1,
          0, 0, 0, 1,
          0, 0, 0, 1
        ]
      const floatArray = new Float32Array(vertex)
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
      gl.vertexAttribPointer(positionLocation, 4, gl.FLOAT, false, 0, 0);
      gl.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
      
      function rect(x, y, w, h) {
        floatArray[0] = x;
        floatArray[1] = y;
        floatArray[4] = x + w;
        floatArray[5] = y;
        floatArray[8] = x;
        floatArray[9] = y + h;
        floatArray[12] = x + w;
        floatArray[13] = y + h;
        
        gl.bufferData(gl.ARRAY_BUFFER, floatArray, gl.STATIC_DRAW);    
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        
      }
      function fill(r, g, b, a) { 
        gl.uniform4fv(projectionColorLocation, [r, g, b, a]);
      }
      let lastTime = new Date();
      function animate() {
        let currentTime = new Date();
        console.log(1000 / (currentTime.getTime() - lastTime.getTime()));
        lastTime = new Date();
        for(let i=0;i<200;i++) {
          fill(1, 0, 0, 1);
          rect(random(0, 800), random(0, 600), 10, 10);
        }
        requestAnimationFrame(animate);
        
      }
      animate();
      function random(low, high) {
        return low + Math.random() * (high-low)
      }
      &lt;canvas id="canvas" width="500" height="300"&gt;&lt;/canvas&gt;

      【讨论】:

      • 非常感谢。我使用了您的代码并将静态绘图更改为动态绘图,并且 fps 立即从 10 fps 飙升至 60 fps。 :D
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-08
      • 1970-01-01
      • 1970-01-01
      • 2013-07-05
      • 1970-01-01
      • 2010-09-13
      相关资源
      最近更新 更多