Содержание

WebGL2Fundamentals.org

Fix, Fork, Contribute

WebGL2 Индексированные вершины

Эта статья предполагает, что вы хотя бы прочитали статью об основах. Если вы ещё не читали её, лучше начните с неё.

Это короткая статья о gl.drawElements. В WebGL есть 2 базовые функции отрисовки: gl.drawArrays и gl.drawElements. В большинстве статей на сайте, где явно вызывается одна из них, используется gl.drawArrays, так как она наиболее прямолинейна.

gl.drawElements, с другой стороны, использует буфер с индексами вершин и рисует на его основе.

Давайте возьмём пример, который рисует прямоугольники из первой статьи, и сделаем его с использованием gl.drawElements.

В том коде мы создавали прямоугольник из 2 треугольников, по 3 вершины в каждом, всего 6 вершин.

Вот код, который задаёт 6 позиций вершин:

  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
     x1, y1,   // вершина 0
     x2, y1,   // вершина 1
     x1, y2,   // вершина 2
     x1, y2,   // вершина 3
     x2, y1,   // вершина 4
     x2, y2,   // вершина 5
  ]), gl.STATIC_DRAW);

Вместо этого мы можем использовать данные для 4 вершин:

  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
     x1, y1,  // вершина 0
     x2, y1,  // вершина 1
     x1, y2,  // вершина 2
     x2, y2,  // вершина 3
  ]), gl.STATIC_DRAW);

Но тогда нам нужно добавить ещё один буфер с индексами, потому что WebGL всё равно требует, чтобы для отрисовки 2 треугольников мы указали 6 вершин в сумме.

Для этого мы создаём ещё один буфер, но используем другую точку привязки. Вместо ARRAY_BUFFER используем ELEMENT_ARRAY_BUFFER, который всегда используется для индексов.

// создаём буфер
const indexBuffer = gl.createBuffer();

// делаем этот буфер текущим 'ELEMENT_ARRAY_BUFFER'
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

// Заполняем текущий element array buffer данными
const indices = [
  0, 1, 2,   // первый треугольник
  2, 1, 3,   // второй треугольник
];
gl.bufferData(
    gl.ELEMENT_ARRAY_BUFFER,
    new Uint16Array(indices),
    gl.STATIC_DRAW
);

Как и все данные в WebGL, индексы должны быть в определённом формате. Мы преобразуем индексы в беззнаковые 16-битные числа с помощью new Uint16Array(indices) и загружаем их в буфер.

Важно отметить, что в отличие от точки привязки ARRAY_BUFFER, которая является глобальным состоянием, точка привязки ELEMENT_ARRAY_BUFFER является частью текущего vertex array.

В коде мы создавали и привязывали vertex array, а затем настраивали буфер индексов. Это значит, что, как и атрибуты, каждый раз при привязке этого vertex array буфер индексов тоже будет привязан. Подробнее см. статью об атрибутах.

При отрисовке вызываем drawElements:

// Рисуем прямоугольник.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
-gl.drawArrays(primitiveType, offset, count);
+var indexType = gl.UNSIGNED_SHORT;
+gl.drawElements(primitiveType, count, indexType, offset);

Результат тот же, что и раньше, но теперь мы задали данные только для 4 вершин вместо 6. Всё равно пришлось попросить WebGL нарисовать 6 вершин, но это позволило переиспользовать данные 4 вершин через индексы.

Использовать индексированные или неиндексированные данные — решать вам.

Важно: индексированные вершины обычно не позволят сделать куб с 8 позициями вершин, потому что обычно вы хотите ассоциировать с каждой вершиной дополнительные данные, которые различаются в зависимости от того, с какой гранью используется эта вершина. Например, если вы хотите дать каждой грани куба свой цвет, нужно передать этот цвет вместе с позицией. Поэтому, даже если одна и та же позиция используется 3 раза (по одной на каждую грань, к которой вершина принадлежит), всё равно нужно повторить позицию 3 раза — по одной на каждую грань, с разным цветом. Это значит, что для куба потребуется 24 вершины (по 4 на каждую грань) и 36 индексов для 12 треугольников.

Допустимые типы для indexType выше: gl.UNSIGNED_BYTE (индексы от 0 до 255, используйте new Uint8Array(indices)), gl.UNSIGNED_SHORT (максимальный индекс 65535, как выше), и gl.UNSIGNED_INT (максимальный индекс 4294967296, используйте new Uint32Array(indices)).

Есть предложения или замечания? Создайте issue на GitHub.
comments powered by Disqus