Что такое массивы вершин в OpenGL и WebGL2?

Я работал с WebGL1 в течение некоторого времени, но теперь, когда я узнаю больше о WebGL2, я запутался, что Vertex Arrayна самом деле делать. Например, в следующем примере я могу удалить все ссылки на них (например, создание, привязка, удаление), и пример продолжает работать.

1 ответ

Решение

Это было объяснено в другом месте, но вы можете считать, что и WebGL1, и WebGL2 имеют массив вершин. Просто у WebGL1 по умолчанию есть только один, где в качестве WebGL2 вы можете создавать несколько вершинных массивов (хотя 99,9% всех реализаций WebGL1 поддерживают их как расширение)

Vertex Array - это совокупность всех атрибутов, а также ELEMENT_ARRAY_BUFFER связывание.

Вы можете думать о состоянии WebGL, как это

function WebGLRenderingContext() {
   // internal WebGL state
   this.lastError: gl.NONE,
   this.arrayBuffer = null;
   this.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 },
       ...
     ],
   }
   ...

И вы можете думать о gl.bindBuffer как это реализовано

   // 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;
         this.arrayBuffer = buffer;
         break;
       case gl.ELEMENT_ARRAY_BUFFER;
         this.vertexArray.elementArrayBuffer = buffer;
         break;
       default:
         this.lastError = gl.INVALID_ENUM;
         break;
     }
   };

Таким образом, вы можете увидеть выше, позвонив gl.bindBuffer с gl.ELEMENT_ARRAY_BUFFER устанавливает elementArray часть текущего vertexArray

Вы также можете увидеть vertexArray имеет ряд атрибутов. Они определяют, как извлекать данные из буферов для подачи в ваш вершинный шейдер. призвание gl.getAttribLocation(someProgram, "nameOfAttribute") говорит вам, на какой атрибут будет смотреть вершинный шейдер, чтобы получить данные из буфера.

Есть 4 функции, которые вы используете для настройки того, как атрибут будет получать данные из буфера. gl.enableVertexAttribArray, gl.disableVertexAttribArray, gl.vertexAttribPointer, а также gl.vertexAttrib??,

Они эффективно реализованы примерно так

this.enableVertexAttribArray = function(location) {
  const attribute = this.vertexArray.attributes[location];
  attribute.enabled = true;  // true means get data from attribute.buffer 
};

this.disableVertexAttribArray = function(location) {
  const attribute = this.vertexArray.attributes[location];
  attribute.enabled = false; // false means get data from attribute.value
};

this.vertexAttribPointer = function(location, size, type, normalized, stride, offset) {
  const attribute = this.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     = this.arrayBuffer;
};

this.vertexAttrib4f = function(location, x, y, z, w) {
  const attribute = this.vertexArray.attributes[location];
  attribute.value[0] = x;
  attribute.value[1] = y;
  attribute.value[2] = z;
  attribute.value[3] = w;
};

Теперь, когда вы звоните gl.drawArrays или же gl.drawElements система знает, как вы хотите извлечь данные из буферов, которые вы создали для предоставления своего вершинного шейдера. Смотрите здесь, как это работает.

Тогда есть 3 функции, которые будут управлять всем состоянием, связанным с this.vertexArray, Они есть gl.createVertexArray, gl.bindVertexArray а также gl.deleteVertexArray, В WebGL1 они доступны на OES_vertex_array_object расширение слегка переименовано. На WebGL2 они просто доступны по умолчанию, что также является функцией WebGL 2.0.

призвание gl.createVertexArray создает новый массив вершин призвание gl.bindVertexArray наборы this.vertexArray указать на тот, который вы передаете. Вы можете представить, что это реализовано так

 this.bindVertexArray = function(vao) {
   this.vertexArray = vao ? vao : defaultVertexArray;
 }    

Выгода должна быть очевидной. Перед каждой вещью, которую вы хотите нарисовать, вам нужно установить все атрибуты. Установка каждого атрибута требует минимум одного вызова на каждый используемый атрибут. Чаще всего 3 звонка на атрибут. Один звонок gl.bindBuffer привязать буфер к ARRAY_BUFFER и один звонок gl.vertexAttribPointer затем связать этот буфер с определенным атрибутом и установить, как извлечь данные и один вызов gl.enableVertexAttribArray включить получение данных из буфера для атрибута.

Для типичной модели с позициями, нормалями и координатами текстуры это 9 вызовов, +1 больше, если вы используете индексы и вам нужно привязать буфер к ELEMENT_ARRAY_BUFFER,

С массивами вершин все эти вызовы происходят во время инициализации. Вы создаете массив вершин для каждой вещи, которую хотите нарисовать, затем настраиваете атрибуты для этой вещи. Во время розыгрыша это займет всего один звонок gl.bindVertexArray настроить все атрибуты и ELEMENT_ARRAY_BUFFER,

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

обратите внимание, если вы ищете образцы, возможно, сравните соответствующие примеры на https://webglfundamentals.org/ с https://webgl2fundamentals.org/. Сайт WebGL2 везде использует массивы вершин. Вы заметите в примерах WebGL1 непосредственно перед рисованием, для каждого фрагмента данных вершины привязывается буфер для этих данных, а затем устанавливается атрибут для этих данных. В примерах WebGL2 это происходит во время инициализации, а не во время рисования. Во время розыгрыша все, что происходит, вызывает gl.bindVertexArray

Еще одна вещь о массивах вершин, о которой нужно знать, это то, что они обычно требуют большей организации. Если вы собираетесь рисовать один и тот же объект более одного раза в разных шейдерных программах, возможно, одна шейдерная программа будет использовать разные атрибуты для одних и тех же данных. Другими словами, без дополнительной организации shaderprogram1 может использовать атрибут 3 для позиции, а shaderprogram2 может использовать атрибут 2 для позиции. В этом случае один и тот же массив вершин не будет работать с обеими программами для одних и тех же данных.

Решение состоит в том, чтобы вручную назначить местоположения. Вы можете сделать это в самих шейдерах в WebGL2. Вы также можете сделать это, позвонив gl.bindAttribLocation перед связыванием шейдеров для каждой шейдерной программы в WebGL1 и WebGL2. Я склонен думать, используя gl.bindAttribLocation лучше, чем делать это в GLSL, потому что это более сухой

Другие вопросы по тегам