aclaración de glVertexAttribPointer

94

Solo quiero asegurarme de que entiendo esto correctamente (preguntaría en SO Chat, ¡pero está muerto allí!):

Tenemos un Vertex Array, que hacemos "actual" al vincularlo,
luego tenemos un Buffer, que vinculamos a un Target y
luego llenamos ese Target a través del glBufferData cual esencialmente llena todo lo que estaba vinculado a ese objetivo, es decir, nuestro Buffer
y luego llamamos glVertexAttribPointerque describe cómo se distribuyen los datos: los datos son lo que están vinculados GL_ARRAY_BUFFER y este descriptor se guarda en nuestro Vertex Array original

(1) ¿Es correcto mi entendimiento?
La documentación es un poco escasa sobre cómo se correlaciona todo.

(2) ¿Existe algún tipo de matriz de vértices predeterminada? Porque se me olvidó / omitido glGenVertexArraysy glBindVertexArrayy mi programa funcionó bien sin él.


Editar: echaba de menos un paso ... glEnableVertexAttribArray.

(3) ¿Vertex Attrib está vinculado al Vertex Array en el momento en que glVertexAttribPointerse llama, y ​​luego podemos habilitar / deshabilitar ese atributo glEnableVertexAttribArrayen cualquier momento, independientemente de qué Vertex Array esté vinculado actualmente?

O (3b) ¿Vertex Attrib está vinculado al Vertex Array en el momento en que glEnableVertexAttribArrayse llama y, por lo tanto, podemos agregar el mismo Vertex Attrib a múltiples Vertex Arrays llamando glEnableVertexAttribArrayen diferentes momentos, cuando diferentes Vertex Arrays están vinculados?

mpen
fuente

Respuestas:

210

Parte de la terminología está un poco fuera de lugar:

  • A Vertex Arrayes solo una matriz (normalmente a float[]) que contiene datos de vértice. No necesita estar vinculado a nada. No confundir con a Vertex Array Objecto VAO, que repasaré más adelante
  • A Buffer Object, comúnmente conocido como Vertex Buffer Objectcuando se almacenan vértices, o VBO para abreviar, es lo que llama simplemente a Buffer.
  • Nada se guarda en la matriz de vértices, glVertexAttribPointerfunciona exactamente como glVertexPointero glTexCoordPointerfunciona, solo que en lugar de atributos con nombre, puede proporcionar un número que especifica su propio atributo. Pasa este valor como index. Todas sus glVertexAttribPointerllamadas se ponen en cola para la próxima vez que llame glDrawArrayso glDrawElements. Si tiene un VAO vinculado, el VAO almacenará la configuración de todos sus atributos.

El problema principal aquí es que está confundiendo los atributos de vértice con los VAO. Los atributos de vértice son solo la nueva forma de definir vértices, texcoords, normales, etc. para dibujar. Estado de la tienda VAO. Primero voy a explicar cómo funciona el dibujo con atributos de vértice, luego explicaré cómo puede reducir la cantidad de llamadas a métodos con VAO:

  1. Debe habilitar un atributo antes de poder usarlo en un sombreador. Por ejemplo, si desea enviar vértices a un sombreador, lo más probable es que lo envíe como el primer atributo, 0. Por lo tanto, antes de renderizar, debe habilitarlo con glEnableVertexAttribArray(0);.
  2. Ahora que un atributo está habilitado, debe definir los datos que utilizará. Para hacerlo, debe vincular su VBO - glBindBuffer(GL_ARRAY_BUFFER, myBuffer);.
  3. Y ahora podemos definir el atributo - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);. En orden de parámetro: 0 es el atributo que está definiendo, 3 es el tamaño de cada vértice, GL_FLOATes el tipo, GL_FALSEsignifica no normalizar cada vértice, los últimos 2 ceros significan que no hay zancada o desplazamiento en los vértices.
  4. Dibuja algo con él - glDrawArrays(GL_TRIANGLES, 0, 6);
  5. Es posible que lo siguiente que dibuje no use el atributo 0 (de manera realista lo hará, pero este es un ejemplo), por lo que podemos deshabilitarlo: glDisableVertexAttribArray(0);

Envuelva eso en glUseProgram()llamadas y tendrá un sistema de renderizado que funciona con sombreadores correctamente. Pero digamos que tiene 5 atributos diferentes, vértices, coordenadas de texto, normales, colores y coordenadas de mapa de luz. En primer lugar, realizaría una única glVertexAttribPointerllamada para cada uno de estos atributos y tendría que habilitar todos los atributos de antemano. Digamos que define los atributos 0-4 como los tengo enumerados. Los habilitarías a todos así:

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

Y luego tendría que vincular diferentes VBO para cada atributo (a menos que los almacene todos en un VBO y use compensaciones / zancadas), luego debe realizar 5 glVertexAttribPointerllamadas diferentes , desde glVertexAttribPointer(0,...);hasta glVertexAttribPointer(4,...);para vértices hasta coordenadas de mapa de luz, respectivamente.

Con suerte, ese sistema solo tiene sentido. Ahora voy a pasar a los VAO para explicar cómo usarlos para reducir la cantidad de llamadas a métodos al realizar este tipo de renderizado. Tenga en cuenta que no es necesario utilizar un VAO.

A Vertex Array Objecto VAO se utiliza para almacenar el estado de todas las glVertexAttribPointerllamadas y los VBO a los que se dirigió cuando se realizó cada una de las glVertexAttribPointerllamadas.

Generas uno con una llamada a glGenVertexArrays. Para almacenar todo lo que necesita en un VAO, vincúlelo con glBindVertexArrayy luego haga una llamada de extracción completa . Todas las llamadas de vinculación de dibujo son interceptadas y almacenadas por VAO. Puede desvincular el VAO conglBindVertexArray(0);

Ahora, cuando desee dibujar el objeto, no necesita volver a llamar todos los enlaces VBO o las glVertexAttribPointerllamadas, solo necesita vincular el VAO con glBindVertexArrayluego llamar glDrawArrayso glDrawElementsy estará dibujando exactamente lo mismo que si estaban haciendo todas esas llamadas a métodos. Probablemente también desee desvincular el VAO después.

Una vez que desvincula la VAO, todo el estado vuelve a ser como era antes de vincular la VAO. No estoy seguro de si se mantienen los cambios que realiza mientras la VAO está vinculada, pero eso se puede resolver fácilmente con un programa de prueba. Supongo que se puede considerar glBindVertexArray(0);vinculante para el VAO "predeterminado" ...


Actualización: Alguien me llamó la atención sobre la necesidad de la llamada de sorteo real. Resulta que, en realidad, no necesita hacer una llamada de extracción COMPLETA al configurar el VAO, solo todas las cosas vinculantes. No sé por qué pensé que era necesario antes, pero ahora está arreglado.

Robert Rouhani
fuente
10
"Un objeto de búfer de vértice, o VBO (a veces denominado simplemente un objeto de búfer)" Se "a veces" se llama así porque en realidad así se llama. Es solo un objeto de búfer, no es diferente de cualquier otro objeto de búfer que pueda usar para bloques uniformes, transferencia de píxeles, retroalimentación de transformación o cualquier otro uso. La especificación OpenGL nunca se refiere a nada como un "objeto de búfer de vértice"; incluso la especificación de extensión original nunca lo llama así.
Nicol Bolas
3
Excelente respuesta. ¡Gracias por tomarse el tiempo para escribir esto! Sin embargo, un par de preguntas de seguimiento: (1) Dijiste "antes de renderizar y antes de definir el atributo, debes habilitarlo con glEnableVertexAttribArray (0)". ¿Estás seguro de que debe habilitarse antes de la llamada a glVertexAttribPointer? En mis pruebas, el orden no parece importar. (2) Si lo entiendo correctamente, los atributos de vértice son globales y solo su estado habilitado / deshabilitado se guarda en el VAO vinculado actualmente.
mpen
1
(1) No creo que el pedido importe, siempre que lo hayas habilitado antes glDrawArrayso glDrawElements. Actualizaré la publicación para reflejar eso (2) Sí, pero no es solo el estado de habilitación / deshabilitación lo que se almacena, es todo lo relacionado con esas llamadas: lo que estaba vinculado a GL_ARRAY_BUFFER en ese momento, el tipo, la zancada y el desplazamiento. Básicamente, está almacenando lo suficiente para cambiar todos los Atributos de vértice de nuevo a la forma en que los configuró con VAO.
Robert Rouhani
2
sí, los VAO están diseñados para permitirle reemplazar la mayor parte de un método de extracción con un VAO. Cada entidad puede tener una VAO separada y seguirá funcionando bien. Sin embargo, aún tendrás que actualizar los uniformes y unir tus propias texturas. Y debe usar los mismos índices de atributos que tiene que vincular los atributos de su sombreador con los índices de atributos, ya sea a través layout(location = x)del sombreador o glBindAttributeLocational compilar el sombreador. Ejemplo
Robert Rouhani
8
Tenga en cuenta que en el OpenGL moderno ya no hay un objeto de matriz de vértice predeterminado, debe crear uno usted mismo o su aplicación no funcionará en un contexto compatible con versiones posteriores.
Overv
3

La terminología y la secuencia de las API a llamar es bastante confusa. Lo que es aún más confuso es cómo se asocian los diversos aspectos: búfer, atributo de vértice genérico y variable de atributo de sombreado. Consulte Terminología de OpenGL para obtener una explicación bastante buena.

Además, el enlace OpenGL-VBO, shader, VAO muestra un ejemplo sencillo con las llamadas API necesarias. Es particularmente bueno para aquellos que están en transición del modo inmediato al canal programable.

Espero eso ayude.

Editar: como puede ver en los comentarios a continuación, las personas pueden hacer suposiciones y sacar conclusiones precipitadas. La realidad es que es bastante confuso para los principiantes.

ap-osd
fuente
" Consulte la terminología OpenGL para obtener una explicación bastante buena " . En menos de 1 minuto, ya encontré una información errónea: "Estos se reemplazan con atributos de vértice genéricos con un identificador (llamado índice) que se asocia a una variable de sombreado (por coordenadas, color, etc.) que procesan el atributo ". No se llaman "índices"; son "ubicaciones". Esa es una distinción muy importante porque los atributos de vértice también tienen "índices" , que es muy diferente de las ubicaciones. Ese es un sitio web terrible.
Nicol Bolas
2
Ese es un comentario justo pero no del todo exacto. Si nos fijamos en la API de OpenGL para definir un atributo genérico glVertexAttribPointer , void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer)el identificador se denomina index. El mismo identificador en el contexto del programa se llama locationen la API glGetAttribLocation .
ap-osd