Torre de Babel

Cómo programar un geometry shader

by Francisco Charte.

Los GS son programas algo más complejos que los VS y PS. A diferencia de éstos, no reciben como entrada un único vértice/fragmento ni tampoco tienen que limitarse a emitir un vértice/fragmento. Un GS se ejecuta una vez por cada primitiva a procesar: punto, línea o triángulo. El resultado de su ejecución es un número variable de primitivas de salida, entre 0 y un máximo que depende actualmente del hardware que se utilice.

Parámetros de entrada

Cuando se ejecuta un GS éste sabe que debe procesar una primitiva gráfica que, como ya se ha indicado, puede ser un punto, una línea o un triángulo. El tipo de primitiva lo conocerá consultando gl_VerticesIn, un entero que será 1, 2 ó 3, como sería de esperar.

La posición de cada uno de esos vértices está almacenada en el campo gl_PositionIn[N], un vector que tendrá tantos elementos como indique gl_VerticesIn. Esos vértices ya han pasado por el VS, algo que debe tenerse en cuenta en caso de que se hayan aplicado transformaciones.

Al igual que los vértices, el GS también recibe información sobre el color y las coordenadas de textura en los parámetros gl_FrontColorIn[N], gl_BackColorIn[N] y gl_TexCoordIn[N][M], donde M sería el número de texturas utilizadas y como máximo será gl_MaxTextureCoords.

Parámetros de salida

Para generar el resultado que interese el GS puede utilizar la información de entrada, actuando sobre los vértices de la primitiva original, o bien ignorarla completamente y generar una nueva geometría. Para ello utilizará dos funciones EmitVertex() y EndPrimitive().

Antes de llamar a EmitVertex() hay que asignar a gl_Position la posición del vértice que va a enviarse al cauce gráfico. Es posible enviar de manera sucesiva uno, dos o tres vértices, dependiendo del tipo de primitiva a generar. Tras cada conjunto de vértices ha de invocarse a la función EndPrimitive().

Además de emitir los vértices, el GS también puede establecer parámetros como el color o las coordenadas de textura, justo antes de llamar a EmitVertex(), usando los campos gl_FrontColor, gl_BackColor y gl_TexCoord, como haría un VS.

Un GS sencillo

En OGLSL un shader de este tipo debe comenzar siempre con dos líneas en las que se especifique la versión del lenguaje OGLSL y la extensión a usar, tal como las siguientes:

#version 120
#extension GL_EXT_geometry_shader4 : enable

Tras ellas se introduciría la habitual función main() con el código del shader.

El GS cuyo código aparece a continuación tiene el objetivo de generar, por cada vértice de un triángulo de entrada, otros tantos triángulos, con las coordenadas ligeramente desplazadas. Además se asigna un color diferente a cada vértice del triángulo de salida. De esta forma se consigue que la geometría original se convierta en una matriz tridimensional de triángulos, un efecto muy sencillo de implementar y bastante llamativo.

void main( void )
{
 // Las primitivas de entrada serán triángulos, 3 vértices

 for(int i = 0; i < 3; i++) {
   vec4 punto=gl_PositionIn[i];
   vec4 color = vec4 (1,1,1,1);

   // Por cada vértice se emite un nuevo triángulo

   gl_FrontColor=vec4(1,0,0,1)*color;
   gl_Position = gl_ModelViewProjectionMatrix*punto;
   EmitVertex();

   // con sus vértices ligeramente desplazados

   gl_FrontColor=vec4(0,1,0,1)*color;
   gl_Position = gl_ModelViewProjectionMatrix*(
         punto+vec4(0.5,0.5,0,0));
   EmitVertex();

   // y con un color en cada vértice

   gl_FrontColor=vec4(0,0,1,1)*color;
   gl_Position = gl_ModelViewProjectionMatrix*(
         punto+vec4(0.5,-0.5,0,0));
   EmitVertex();

   EndPrimitive();
 }
}

En la figura siguiente puede apreciarse el efecto del GS sobre un cubo y una esfera.

Los GS permiten implementar algoritmos realmente elaborados, como puede ser la teselación de una superficie a partir de unos puntos de control, la obtención y dibujo de las tangentes a una curva, etc. El límite prácticamente lo impone la propia imaginación.

Es posible combinar los distintos shaders desarrollados como ejemplo, de manera que se calcule la intensidad de la incidencia de la luz en el VS, después se generen los triángulos a partir de los vértices en el GS y finalmente se les aplique el coloreado en el PS.