WebGL: рендеринг множества объектов с использованием разных программ - PullRequest
2 голосов
/ 21 апреля 2020

Я сейчас изучаю WebGL.

Я сделал простую сцену с 10 треугольниками, и когда я увеличил количество треугольников до 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);

Есть идеи, что я могу сделать для оптимизации производительности?

1 Ответ

2 голосов
/ 22 апреля 2020

Есть много способов оптимизировать отрисовку многих вещей, но поскольку вы только начинаете, самое важное, что в общем случае настройка буферов должна происходить во время инициализации, а не во время рендеринга.

См. Рисование нескольких моделей в WebGL

Код в вопросе ищет местоположения в каждом треугольнике. он должен искать местоположения как время инициализации.

Код также создает новый буфер для каждого треугольника. Было бы быстрее создать один буфер и просто обновить его новым треугольником, и, конечно, в конечном итоге ему не хватит памяти для создания новых буферов.

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>

Было бы значительно быстрее, если вы поместите все треугольники в один буфер во время инициализации, а также цвета вершин каждого треугольника в буфер во время инициализации и просто нарисуйте их одним вызовом во время рендеринга. Если вы хотите, чтобы в каждом кадре были случайные треугольники, тогда все еще будет быстрее создать один буфер во время инициализации, заполнить N случайных треугольников в одном буфере и затем нарисовать их все в одном вызове отрисовки.

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>

Рисование группы случайных треугольников в каждом кадре, как у вас выше, возможно, является исключением. Большинство приложений WebGL рисуют трехмерные модели, созданные в пакете моделирования, поэтому гораздо чаще просто помещать данные в буфер один раз во время инициализации (например, данные для куба, сферы, автомобиля, человека, дерева), а затем Нарисуйте его во время рендеринга.

Также имейте в виду, что графические процессоры могут рисовать только столько пикселей, поэтому, если ваши треугольники большие (например, размер всего экрана), вы сможете нарисовать от 10 до нескольких 100). Экран 1920x1080 составляет около 2 миллионов пикселей. Таким образом, каждый полноэкранный треугольник также будет иметь размер около 2 миллионов пикселей. Рисунок 1000 из них составляет 2000 * 2 миллиона или 4 миллиарда пикселей. При 60 кадрах в секунду 240 миллиардов пикселей. GPU среднего класса может рисовать только 10 миллиардов в секунду, и это теоретический максимум, поэтому в лучшем случае он может делать это со скоростью ~ 2 кадра в секунду.

Большинство 3D-приложений рисуют сцену, где большинство треугольников находятся далеко и маленький. Они также используют буфер глубины и рисуют непрозрачные объекты спереди назад, чтобы пиксели сзади не рисовались.

...