Что такое массивы вершин в OpenGL и WebGL2? - PullRequest
0 голосов
/ 09 мая 2018

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

1 Ответ

0 голосов
/ 09 мая 2018

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

Массив вершин - это набор всех состояний атрибутов плюс привязка 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, потому что это больше D.R.Y.

...