Esclarecimento de glVertexAttribPointer

94

Só quero ter certeza de que entendi isso corretamente (eu perguntaria no chat do SO, mas está morto lá!):

Temos um Vertex Array, que tornamos "atual" ao
ligá- lo, então temos um Buffer, que ligamos a um alvo e
então preenchemos esse alvo por meio do glBufferData qual essencialmente preenche tudo o que estava ligado a esse alvo, ou seja, nosso Buffer
e então chamamos o glVertexAttribPointerque descreve como os dados são dispostos - os dados sendo tudo o que está vinculado GL_ARRAY_BUFFER e este descritor é salvo em nosso Vertex Array original

(1) Meu entendimento está correto?
A documentação é um pouco esparsa sobre como tudo se correlaciona.

(2) Existe algum tipo de Vertex Array padrão? Porque eu esqueci / omitido glGenVertexArrayse glBindVertexArraye meu programa funcionou bem sem ele.


Edit: eu perdi um passo ... glEnableVertexAttribArray.

(3) O atributo de vértice está vinculado ao array de vértices no momento em que glVertexAttribPointeré chamado, e então podemos habilitar / desabilitar esse atributo glEnableVertexAttribArraya qualquer momento, independentemente de qual array de vértices está atualmente vinculado?

Ou (3b) O atributo de vértice está vinculado à matriz de vértices no momento em que glEnableVertexAttribArrayé chamado e, portanto, podemos adicionar o mesmo atributo de vértice a várias matrizes de vértice chamando glEnableVertexAttribArrayem momentos diferentes, quando diferentes matrizes de vértices são vinculados?

mpen
fonte

Respostas:

210

Algumas das terminologias estão um pouco erradas:

  • A Vertex Arrayé apenas uma matriz (normalmente a float[]) que contém dados de vértice. Não precisa estar vinculado a nada. Não deve ser confundido com um Vertex Array Objectou VAO, sobre o qual falarei mais tarde
  • A Buffer Object, comumente referido como a Vertex Buffer Objectao armazenar vértices, ou VBO, é o que você está chamando de apenas a Buffer.
  • Nada é salvo de volta no array de vértices, glVertexAttribPointerfunciona exatamente como glVertexPointerou glTexCoordPointerfunciona, apenas em vez de atributos nomeados, você fornece um número que especifica seu próprio atributo. Você passa esse valor como index. Todas as suas glVertexAttribPointerchamadas são colocadas em fila para a próxima vez que você ligar glDrawArraysou glDrawElements. Se você tiver um limite VAO, o VAO armazenará as configurações de todos os seus atributos.

O principal problema aqui é que você está confundindo atributos de vértice com VAOs. Os atributos de vértice são apenas a nova maneira de definir vértices, texcoords, normais, etc. para desenho. Estado de armazenamento VAOs. Vou primeiro explicar como o desenho funciona com atributos de vértice e, em seguida, explicar como você pode reduzir o número de chamadas de método com VAOs:

  1. Você deve habilitar um atributo antes de usá-lo em um sombreador. Por exemplo, se você deseja enviar vértices para um sombreador, provavelmente o enviará como o primeiro atributo, 0. Portanto, antes de renderizar, é necessário habilitá-lo com glEnableVertexAttribArray(0);.
  2. Agora que um atributo está habilitado, você precisa definir os dados que ele usará. Para fazer isso, você precisa vincular seu VBO - glBindBuffer(GL_ARRAY_BUFFER, myBuffer);.
  3. E agora podemos definir o atributo - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);. Na ordem do parâmetro: 0 é o atributo que você está definindo, 3 é o tamanho de cada vértice, GL_FLOATé o tipo, GL_FALSEsignifica não normalizar cada vértice, os últimos 2 zeros significam que não há passo ou deslocamento nos vértices.
  4. Desenhe algo com ele - glDrawArrays(GL_TRIANGLES, 0, 6);
  5. A próxima coisa que você desenhar pode não usar o atributo 0 (realisticamente, ele usará, mas este é um exemplo), então podemos desativá-lo - glDisableVertexAttribArray(0);

Envolva isso em glUseProgram()chamadas e você terá um sistema de renderização que funciona com shaders corretamente. Mas digamos que você tenha 5 atributos diferentes, vértices, texcoords, normais, cores e coordenadas de mapa de luz. Em primeiro lugar, você faria uma única glVertexAttribPointerchamada para cada um desses atributos e teria que habilitar todos os atributos com antecedência. Digamos que você defina os atributos 0-4 conforme eu os listo. Você habilitaria todos eles assim:

for (int i = 0; i < 5; i++)
    glEnableVertexAttribArray(i);

E então você teria que vincular VBOs diferentes para cada atributo (a menos que você os armazene todos em um VBO e use offsets / stride), então você precisa fazer 5 glVertexAttribPointerchamadas diferentes , de glVertexAttribPointer(0,...);a glVertexAttribPointer(4,...);para vértices e coordenadas do mapa de luz, respectivamente.

Esperançosamente, esse sistema sozinho faz sentido. Agora, vou passar para os VAOs para explicar como usá-los para reduzir o número de chamadas de método ao fazer esse tipo de renderização. Observe que o uso de um VAO não é necessário.

A Vertex Array Objectou VAO é usado para armazenar o estado de todas as glVertexAttribPointerchamadas e os VBOs que foram direcionados quando cada uma das glVertexAttribPointerchamadas foi feita.

Você gera um com uma chamada para glGenVertexArrays. Para armazenar tudo o que você precisa em um VAO, vincule-o a glBindVertexArraye faça uma chamada completa . Todas as chamadas draw bind são interceptadas e armazenadas pelo VAO. Você pode desvincular o VAO comglBindVertexArray(0);

Agora quando você deseja desenhar o objeto, você não precisa chamar novamente todos os vínculos VBO ou as glVertexAttribPointerchamadas, você só precisa vincular o VAO com a glBindVertexArraychamada glDrawArraysou glDrawElementse você estará desenhando exatamente a mesma coisa como se você estavam fazendo todas essas chamadas de método. Você provavelmente deseja desvincular o VAO posteriormente também.

Depois de desvincular o VAO, todo o estado retorna ao que era antes de você vincular o VAO. Não tenho certeza se as alterações feitas enquanto o VAO está vinculado são mantidas, mas isso pode ser facilmente descoberto com um programa de teste. Eu acho que você pode pensar glBindVertexArray(0);em uma vinculação ao VAO "padrão" ...


Atualização: Alguém chamou minha atenção para a necessidade do sorteio real. Acontece que você não precisa fazer uma chamada FULL draw ao configurar o VAO, apenas todas as coisas de ligação. Não sei por que eu pensei que era necessário antes, mas está corrigido agora.

Robert Rouhani
fonte
10
"A Vertex Buffer Object, ou VBO (às vezes referido como apenas um Buffer Object)" É "às vezes" chamado assim porque é realmente assim que é chamado. É apenas um objeto de buffer, não diferente de qualquer outro objeto de buffer que você possa usar para blocos uniformes, transferência de pixel, feedback de transformação ou qualquer outro uso. A especificação OpenGL nunca se refere a nada como um "objeto de buffer de vértice"; mesmo a especificação de extensão original nunca o chama assim.
Nicol Bolas
3
Excelente resposta. Obrigado por dedicar seu tempo para escrever isso! Algumas perguntas de acompanhamento: (1) Você disse "antes de renderizar e antes de definir o atributo, você precisa habilitá-lo com glEnableVertexAttribArray (0)" - você tem certeza que precisa ser habilitado antes da chamada para glVertexAttribPointer? Em meus testes, a ordem não parece importar. (2) Se bem entendi, os atributos do vértice são globais e apenas seu estado ativado / desativado é salvo no VAO atualmente vinculado?
maio
1
(1) Não acho que a ordem seja importante, contanto que você a tenha ativado antes glDrawArraysou glDrawElements. Vou atualizar a postagem para refletir isso (2) Sim, mas não é apenas o estado de ativação / desativação que está armazenado, é tudo relacionado a essas chamadas - o que foi vinculado a GL_ARRAY_BUFFER no momento, o tipo, passo e deslocamento. Essencialmente, ele está armazenando o suficiente para alterar todos os atributos de vértice de volta para a forma como você os configurou com o VAO.
Robert Rouhani
2
sim, os VAOs são projetados para permitir que você substitua a maior parte de um método de desenho pela vinculação de um VAO. Cada entidade pode ter um VAO separado e ainda funcionará bem. Você ainda terá que atualizar os uniformes e vincular suas próprias texturas. E você tem que usar os mesmos índices de atributo que você tem que vincular os atributos do seu shader com índices de atributo, seja por meio layout(location = x)do shader ou com glBindAttributeLocationao compilar o shader. Exemplo
Robert Rouhani
8
Observe que no OpenGL moderno não há mais nenhum objeto de matriz de vértice padrão, você deve criar um ou seu aplicativo não funcionará em um contexto compatível com versões futuras.
Overv
3

A terminologia e a sequência das APIs a serem chamadas são bastante confusas. O que é ainda mais confuso é como os vários aspectos - buffer, atributo genérico de vértice e variável de atributo shader são associados. Veja Terminologia OpenGL para uma boa explicação.

Além disso, o link OpenGL-VBO, shader, VAO mostra um exemplo simples com as chamadas API necessárias. É particularmente bom para quem está fazendo a transição do modo imediato para o pipeline programável.

Espero que ajude.

Edit: Como você pode ver nos comentários abaixo, as pessoas podem fazer suposições e tirar conclusões precipitadas. A realidade é que é bastante confuso para iniciantes.

ap-osd
fonte
" Veja a Terminologia OpenGL para uma boa explicação. " Em menos de 1 minuto procurando, eu já encontrei um pedaço de desinformação: "Estes são substituídos por atributos de vértice genéricos com um identificador (chamado índice) que se associa a uma variável de sombreador (para cooordena, cor, etc.) que processa o atributo. " Eles não são chamados de "índices"; eles são "locais". Essa é uma distinção muito importante porque os atributos de vértice também têm "índices" , que são muito diferentes das localizações. Esse é um site muito terrível.
Nicol Bolas
2
Esse é um comentário justo, mas não totalmente preciso. Se você olhar para a API OpenGL para definir um atributo genérico glVertexAttribPointer , void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer), o identificador é referido como index. O mesmo identificador no contexto do programa é chamado locationna API glGetAttribLocation .
ap-osd