Какова логика связывания буферов в webgl?
Иногда я испытываю трудности с объявлением буферов (с помощью createBuffer/bindBuffer/bufferdata) в другом порядке и повторным связыванием их в других частях кода, обычно в цикле отрисовки.
Если я не перепривязываю буфер вершин перед рисованием массивов, консоль жалуется на попытку доступа к вершинам диапазона. Я подозреваю, что последний связанный объект передается по указателю, а затем в отсеки, но когда я меняю порядок в начале кода, ничего не меняется. Что эффективно работает, так это перепривязка буфера в цикле отрисовки. Так что я не могу понять логику этого. Когда вам нужно перепривязать? Зачем вам нужно привязать? Что означает атрибут0?
1 ответ
Я не знаю, поможет ли это. Как говорили некоторые люди, GL/WebGL имеет множество внутренних состояний. Все функции, которые вы вызываете, устанавливают состояние. Когда все готово, вы звоните drawArrays
или же drawElements
и все это состояние используется для рисования вещей
Это было объяснено в другом месте на SO, но привязка буфера - это просто установка 1 из 2 глобальных переменных внутри WebGL. После этого вы обращаетесь к буферу по его точке привязки.
Вы можете думать об этом так
gl = function() {
// internal WebGL state
let lastError;
let arrayBuffer = null;
let vertexArray = {
elementArrayBuffer: null,
attributes: [
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, value: [0, 0, 0, 1], buffer: null },
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, value: [0, 0, 0, 1], buffer: null },
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, value: [0, 0, 0, 1], buffer: null },
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, value: [0, 0, 0, 1], buffer: null },
{ enabled: false, type: gl.FLOAT, size: 3, normalized: false,
stride: 0, offset: 0, value: [0, 0, 0, 1], buffer: null },
...
],
}
...
// Implementation of gl.bindBuffer.
// note this function is doing nothing but setting 2 internal variables.
this.bindBuffer = function(bindPoint, buffer) {
switch(bindPoint) {
case gl.ARRAY_BUFFER;
arrayBuffer = buffer;
break;
case gl.ELEMENT_ARRAY_BUFFER;
vertexArray.elementArrayBuffer = buffer;
break;
default:
lastError = gl.INVALID_ENUM;
break;
}
};
...
}();
После этого другие функции WebGL ссылаются на них. Например gl.bufferData
может сделать что-то вроде
// implementation of gl.bufferData
// Notice you don't pass in a buffer. You pass in a bindPoint.
// The function gets the buffer one of its internal variable you set by
// previously calling gl.bindBuffer
this.bufferData = function(bindPoint, data, usage) {
// lookup the buffer from the bindPoint
var buffer;
switch (bindPoint) {
case gl.ARRAY_BUFFER;
buffer = arrayBuffer;
break;
case gl.ELEMENT_ARRAY_BUFFER;
buffer = vertexArray.elemenArrayBuffer;
break;
default:
lastError = gl.INVALID_ENUM;
break;
}
// copy data into buffer
buffer.copyData(data); // just making this up
buffer.setUsage(usage); // just making this up
};
Отдельно от этих точек привязки есть ряд атрибутов. Атрибуты также являются глобальным состоянием по умолчанию. Они определяют, как извлекать данные из буферов для подачи в ваш вершинный шейдер. призвание gl.getAttribLocation(someProgram, "nameOfAttribute")
говорит вам, на какой атрибут будет смотреть вершинный шейдер, чтобы получить данные из буфера.
Итак, есть 4 функции, которые вы используете для настройки того, как атрибут будет получать данные из буфера. gl.enableVertexAttribArray
, gl.disableVertexAttribArray
, gl.vertexAttribPointer
, а также gl.vertexAttrib??
,
Они эффективно реализованы примерно так
this.enableVertexAttribArray = function(location) {
const attribute = vertexArray.attributes[location];
attribute.enabled = true; // true means get data from attribute.buffer
};
this.disableVertexAttribArray = function(location) {
const attribute = vertexArray.attributes[location];
attribute.enabled = false; // false means get data from attribute.value
};
this.vertexAttribPointer = function(location, size, type, normalized, stride, offset) {
const attribute = vertexArray.attributes[location];
attribute.size = size; // num values to pull from buffer per vertex shader iteration
attribute.type = type; // type of values to pull from buffer
attribute.normalized = normalized; // whether or not to normalize
attribute.stride = stride; // number of bytes to advance for each iteration of the vertex shader. 0 = compute from type, size
attribute.offset = offset; // where to start in buffer.
// IMPORTANT!!! Associates whatever buffer is currently *bound* to
// "arrayBuffer" to this attribute
attribute.buffer = arrayBuffer;
};
this.vertexAttrib4f = function(location, x, y, z, w) {
const attribute = vertexArray.attributes[location];
attribute.value[0] = x;
attribute.value[1] = y;
attribute.value[2] = z;
attribute.value[3] = w;
};
Теперь, когда вы звоните gl.drawArrays
или же gl.drawElements
система знает, как вы хотите извлечь данные из буферов, которые вы создали для предоставления своего вершинного шейдера. Смотрите здесь, как это работает.
Поскольку атрибуты являются глобальным состоянием, это означает, что каждый раз, когда вы звоните drawElements
или же drawArrays
как бы вы ни настраивали атрибуты, как они будут использоваться. Если вы установите атрибуты #1 и #2 для буферов, каждый из которых имеет 3 вершины, но вы попросите нарисовать 6 вершин с gl.drawArrays
вы получите ошибку. Точно так же, если вы создаете индексный буфер, который вы привязываете к gl.ELEMENT_ARRAY_BUFFER
точка привязки и этот буфер имеет индекс> 2, вы получите, что index out of range
ошибка. Если ваши буферы имеют только 3 вершины, то единственные допустимые индексы 0
, 1
, а также 2
,
Обычно, каждый раз, когда вы рисуете что-то другое, вы привязываете все атрибуты, необходимые для рисования этой вещи. Рисуете куб, который имеет позиции и нормали? Свяжите буфер с данными позиции, настройте атрибут, используемый для позиций, свяжите буфер с данными норм, настройте атрибут, используемый для нормалей, теперь нарисуйте. Затем вы рисуете сферу с позициями, цветами вершин и координатами текстуры. Свяжите буфер, содержащий данные о положении, настройте атрибут, используемый для положения. Свяжите буфер, содержащий данные о цветах вершин, настройте атрибут, используемый для цветов вершин. Привязать буфер, содержащий координаты текстуры, настроить атрибут, используемый для координат текстуры.
Единственный раз, когда вы не привязываете буферы, это когда вы рисуете одну и ту же вещь более одного раза. Например, рисование 10 кубов. Вы переплетаете буферы, затем устанавливаете форму для одного куба, рисуете, устанавливаете форму для следующего куба, рисуете, повторяете.
Я также должен добавить, что есть расширение [OES_vertex_array_object
], которая также является функцией WebGL 2.0. Vertex Array Object - это глобальное состояние, называемое выше vertexArray
который включает в себя elementArrayBuffer
и все атрибуты.
призвание gl.createVertexArray
делает новый из тех. призвание gl.bindVertexArray
устанавливает глобальный attributes
чтобы указать на тот в связанном vertexArray.
призвание gl.bindVertexArray
будет тогда
this.bindVertexArray = function(vao) {
vertexArray = vao ? vao : defaultVertexArray;
}
Преимущество этого состоит в том, что вы можете установить все атрибуты и буферы во время инициализации, а затем во время отрисовки всего один вызов WebGL установит все буферы и атрибуты.