Как оптимизировать WebGL, чтобы быстрее отображать больше объектов? - PullRequest
1 голос
/ 17 июня 2020

У меня сложилось впечатление, что WebGL намного мощнее, чем 2d-рендерер в браузере, но по какой-то причине мой код WebGL работает намного медленнее. Есть ли какие-либо рекомендуемые стратегии оптимизации, которые я могу использовать, чтобы мой код 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)
}

Ответы [ 2 ]

1 голос
/ 17 июня 2020

Есть тысячи способов оптимизировать WebGL. Какой из них вы выберете, зависит от ваших потребностей. Чем больше вы оптимизируете обычно , тем менее гибким.

Сначала вы должны сделать очевидное и не создавать новые буферы для каждого прямоугольника, как сейчас делает ваш код, а также переместить все, что может быть перемещенным наружу (например, поиск местоположения), что показано в ответе Юзефа Подлецкого.

Другой способ: вы можете перемещать и масштабировать прямоугольник, используя единую матрицу, а не загружать новые вершины для каждого прямоугольника. Неясно, будет ли обновление матрицы для прямоугольников быстрее или медленнее, но если бы вы рисовали что-то с большим количеством вершин, определенно было бы быстрее делать это с помощью матрицы. В этой серии статей упоминается это и строится до матриц.

Еще вы можете использовать массивы вершин , хотя это не важно для вашего примера, поскольку вы только рисуете одна вещь.

Другой - если формы все такие же (как у вас), вы можете использовать отрисовку экземпляра , чтобы нарисовать все 200 прямоугольников за один вызов отрисовки

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

Другой вариант - вы можете поместить более одной формы в буфер. Так, например, вместо помещения одного прямоугольника в буфер поместите все 200 прямоугольников в буфер, а затем нарисуйте их одним вызовом рисования . Из этой презентации

Также обратите внимание: обратному вызову 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);
1 голос
/ 17 июня 2020

Вы можете повторно использовать буфер , извлекать getAttribLocation, getUniformLocation вне функции rect, а также vertexAttribPointer после привязки буфера и, наконец, вызова 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)
}
<canvas id="canvas" width="500" height="300"></canvas>
...