Эта статья предполагает, что вы хотя бы прочитали статью об основах. Если вы ещё не читали её, лучше начните с неё.
Это короткая статья о 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)
).