javascript - WebGL:使用不同的程序渲染大量对象

标签 javascript webgl

我现在正在学习 WebGL。

我制作了一个包含 10 个三 Angular 形的简单场景,当我将三 Angular 形数量增加到 1000 个时,场景开始卡住。我使用 3 个着色器和 2 个程序(用于模拟真实环境)。我知道我应该从渲染周期主体中取出一些东西,但我不知道是什么。

我的代码如下:

function render() {
  requestAnimationFrame(render);

  context.clear(context.COLOR_BUFFER_BIT);

  for (let i = 0; i < 10; i++) {
    const currentProgram = i % 2 === 0 ? blueProgram : redProgram;
    context.useProgram(currentProgram);

    const a_Position = context.getAttribLocation(currentProgram, "a_Position");

    const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms

    const buffer = context.createBuffer();
    context.bindBuffer(context.ARRAY_BUFFER, buffer);

    context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.STATIC_DRAW);
    context.enableVertexAttribArray(a_Position);
    context.vertexAttribPointer(
      a_Position,
      2,
      context.FLOAT,
      false,
      0,
      0,
    );

    context.drawArrays(context.TRIANGLES, 0, 3);
  }
}

requestAnimationFrame(render);

我可以为性能优化做些什么吗?

最佳答案

有很多方法可以优化绘制很多东西,但由于 yoi 才刚刚开始,最重要的是一般来说设置缓冲区应该在初始化时发生,而不是渲染时。

参见Draw multiple models in WebGL

问题中的代码正在查找每个三 Angular 形的位置。它应该在初始化时查找位置。

代码还为每个三 Angular 形创建一个新的缓冲区。创建一个缓冲区并用新三 Angular 形更新它会更快,当然,创建新缓冲区最终会耗尽内存。

const context = document.querySelector('canvas').getContext('webgl');

const vs = `
attribute vec4 a_Position;
void main() {
  gl_Position = a_Position;
}
`;

const redFS = `
precision highp float;
void main() {
  gl_FragColor = vec4(1, 0, 0, 1);
}
`;

const blueFS = `
precision highp float;
void main() {
  gl_FragColor = vec4(0, 0, 1, 1);
}
`;

const blueProgram = twgl.createProgram(context, [vs, blueFS]);
const blueProgramInfo = {
  program: blueProgram,
  a_PositionLocation: context.getAttribLocation(blueProgram, "a_Position"),
};

const redProgram = twgl.createProgram(context, [vs, redFS]);
const redProgramInfo = {
  program: redProgram,
  a_PositionLocation: context.getAttribLocation(redProgram, "a_Position"),
};

const buffer = context.createBuffer();

function rand(min, max) {
  return Math.random() * (max - min) + min;
}

// pre allocate
const triangleData = new Float32Array(6);  // 3 vertices, 2 values per

function getTriangleGeometry() {
  const x = rand(-1, 1);
  const y = rand(-1, 1);
  triangleData[0] = x; 
  triangleData[1] = y;
  triangleData[2] = x + rand(-0.1, 0.1);
  triangleData[3] = y + rand(-0.1, 0.1);
  triangleData[4] = x + rand(-0.1, 0.1);
  triangleData[5] = y + rand(-0.1, 0.1);
  return triangleData;
}

function render() {
  context.clear(context.COLOR_BUFFER_BIT);

  for (let i = 0; i < 100; i++) {
    const currentProgramInfo = i % 2 === 0 ? blueProgramInfo : redProgramInfo;
    context.useProgram(currentProgramInfo.program);

    const a_Position = currentProgramInfo.a_PositionLocation;

    const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms

    context.bindBuffer(context.ARRAY_BUFFER, buffer);
    context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.STATIC_DRAW);
    context.enableVertexAttribArray(a_Position);
    context.vertexAttribPointer(
      a_Position,
      2,
      context.FLOAT,
      false,
      0,
      0,
    );

    context.drawArrays(context.TRIANGLES, 0, 3);
  }
  requestAnimationFrame(render);
}

requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>

问题中的代码似乎使用了 2 个程序,一个绘制蓝色,一个绘制红色。如果有一个带有制服的程序来选择颜色可能会更快。

const context = document.querySelector('canvas').getContext('webgl');

const vs = `
attribute vec4 a_Position;
void main() {
  gl_Position = a_Position;
}
`;

const fs = `
precision highp float;
uniform vec4 u_Color;
void main() {
  gl_FragColor = u_Color;
}
`;


const program = twgl.createProgram(context, [vs, fs]);
const programInfo = {
  program: program,
  a_PositionLocation: context.getAttribLocation(program, "a_Position"),
  u_ColorLocation: context.getUniformLocation(program, "u_Color"),
};

const buffer = context.createBuffer();

function rand(min, max) {
  return Math.random() * (max - min) + min;
}

// pre allocate
const triangleData = new Float32Array(6);  // 3 vertices, 2 values per

function getTriangleGeometry() {
  const x = rand(-1, 1);
  const y = rand(-1, 1);
  triangleData[0] = x; 
  triangleData[1] = y;
  triangleData[2] = x + rand(-0.1, 0.1);
  triangleData[3] = y + rand(-0.1, 0.1);
  triangleData[4] = x + rand(-0.1, 0.1);
  triangleData[5] = y + rand(-0.1, 0.1);
  return triangleData;
}

const blue = [0, 0, 1, 1];
const red = [1, 0, 0, 1];

function render() {
  context.clear(context.COLOR_BUFFER_BIT);

  context.useProgram(programInfo.program);
  
  const a_Position = programInfo.a_PositionLocation;
  context.bindBuffer(context.ARRAY_BUFFER, buffer);
  context.enableVertexAttribArray(a_Position);
  context.vertexAttribPointer(
    a_Position,
    2,
    context.FLOAT,
    false,
    0,
    0,
  );

  for (let i = 0; i < 100; i++) {
    const color = i % 2 === 0 ? blue : red;
    context.uniform4fv(programInfo.u_ColorLocation, color);
   
    const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
    context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.STATIC_DRAW);

    context.drawArrays(context.TRIANGLES, 0, 3);
  }
  requestAnimationFrame(render);
}

requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>

如果您在初始化时将所有三 Angular 形放入单个缓冲区中,并在初始化时将每个三 Angular 形的顶点颜色放入缓冲区中,然后在渲染时使用单个绘制调用来绘制它们,那么速度会快得多。如果您希望每一帧都有随机三 Angular 形,那么在初始化时创建一个缓冲区,在单个缓冲区中填充 N 个随机三 Angular 形,然后在一次绘制调用中将它们全部绘制出来仍然会更快。

const context = document.querySelector('canvas').getContext('webgl');

const vs = `
attribute vec4 a_Position;
attribute vec4 a_Color;

varying vec4 v_Color;

void main() {
  gl_Position = a_Position;
  v_Color = a_Color;
}
`;

const fs = `
precision highp float;
varying vec4 v_Color;
void main() {
  gl_FragColor = v_Color;
}
`;


const program = twgl.createProgram(context, [vs, fs]);
const programInfo = {
  program: program,
  a_PositionLocation: context.getAttribLocation(program, "a_Position"),
  a_ColorLocation: context.getAttribLocation(program, "a_Color"),
  u_ColorLocation: context.getUniformLocation(program, "u_Color"),
};

const positionBuffer = context.createBuffer();
const colorBuffer = context.createBuffer();

function rand(min, max) {
  return Math.random() * (max - min) + min;
}

const numTriangles = 1000;

const positionData = new Float32Array(numTriangles * 3 * 2);  
const colorData = new Float32Array(numTriangles * 3 * 4);

const blue = [0, 0, 1, 1];
const red = [1, 0, 0, 1];

// the color data does not change so fill it out at init time
for (let i = 0; i < numTriangles; ++i) {
  const offset = i * 4;
  colorData.set(i % 2 === 0 ? blue : red, offset);
}
context.bindBuffer(context.ARRAY_BUFFER, colorBuffer);
context.bufferData(context.ARRAY_BUFFER, colorData, context.STATIC_DRAW);

function getTriangleGeometry() {
  for (let i = 0; i < numTriangles; ++i) {
    const offset = i * 3 * 2;  // 3 verts per tri, 2 values per ver
    const x = rand(-1, 1);
    const y = rand(-1, 1);
    positionData[offset    ] = x; 
    positionData[offset + 1] = y;
    positionData[offset + 2] = x + rand(-0.1, 0.1);
    positionData[offset + 3] = y + rand(-0.1, 0.1);
    positionData[offset + 4] = x + rand(-0.1, 0.1);
    positionData[offset + 5] = y + rand(-0.1, 0.1);
  }
  return positionData;
}


function render() {
  context.clear(context.COLOR_BUFFER_BIT);

  context.useProgram(programInfo.program);
  
  const a_Position = programInfo.a_PositionLocation;
  context.bindBuffer(context.ARRAY_BUFFER, positionBuffer);
  const triangleGeometry = getTriangleGeometry(); // returns Float32Array filled with randoms
  context.bufferData(context.ARRAY_BUFFER, triangleGeometry, context.DYNAMIC_DRAW);
  context.enableVertexAttribArray(a_Position);
  context.vertexAttribPointer(
    a_Position,
    2,
    context.FLOAT,
    false,
    0,
    0,
  );
  
  const a_Color = programInfo.a_ColorLocation;
  context.bindBuffer(context.ARRAY_BUFFER, colorBuffer);
  context.enableVertexAttribArray(a_Color);
  context.vertexAttribPointer(
    a_Color,
    4,
    context.FLOAT,
    false,
    0,
    0,
  );

  context.drawArrays(context.TRIANGLES, 0, numTriangles * 3);
  requestAnimationFrame(render);
}

requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
<canvas></canvas>

像上面那样在每一帧绘制一堆随机三 Angular 形可以说是一个异常(exception)。大多数 WebGL 应用程序绘制在建模包中创建的 3D 模型,因此更常见的做法是在初始化时将数据放入缓冲区一次(例如立方体、球体、汽车、人类、树的数据),然后在渲染时绘制它。

另请注意,GPU 只能绘制一定数量的像素,因此如果您的三 Angular 形很大(例如整个屏幕的大小),您将只能绘制 10 到几百个像素。 1920x1080 的屏幕大约有 200 万像素。因此,每个全屏三 Angular 形也大约有 200 万像素。其中绘制 1000 个就是 2000 * 200 万或 40 亿像素。每秒 60 帧,2400 亿像素。中高端 GPU 每秒只能绘制 100 亿次,这是理论上的最大值,因此最多只能每秒绘制约 2 帧。

大多数 3D 应用程序绘制的场景中,大多数三 Angular 形都距离较远且较小。他们还使用深度缓冲区并从前到后绘制不透明对象,这样后面的像素就不会被绘制。

关于javascript - WebGL:使用不同的程序渲染大量对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61349681/

相关文章:

javascript - 在html中查找某个数字并将其替换为自定义文本

javascript - 每次部署新版本的 Web 应用程序时如何清理浏览器缓存?

javascript - 如何在 WebGL 中沿 x 轴移动纹理?

javascript - Css 移动导航 body 滚动

javascript - 无法在 Angular 服务中更新函数内部的值

recursion - WebGL 递归

three.js - 在 THREE.js 中检索顶点数据

JavaScript WebGL RGBA 数组到屏幕?

javascript - 主干表单更新选择框值

opengl-es - Qt Enterprise for IMX6 不使用硬件加速?