Torre de Babel

Cómo programar un vertex shader simple

by Francisco Charte.

Un vertex shader es un programa que recibe como entrada una serie de argumentos y genera unos resultados, casi como cualquier programa de ordenador. Su objetivo general es transformar vértices, si bien puede también influir en otros parámetros como el color o las coordenadas de textura.

En esta entrada y las siguientes utilizaré el lenguaje OGLSL para escribir los shaders, al ser éste el lenguaje asociado a OpenGL, que es la API gráfica más utilizada, y ser también multiplataforma, algo que no se aplica por ejemplo a DirectX y HLSL.
Para codificar y probar los distintos ejemplos usaré la herramienta Shader Maker (ver descripción de herramientas en tutorial previo), simplemente porque está más actualizada que ShaderDesigner y permite trabajar con GS, una posibilidad que no ofrece este último programa.

Parámetros de entrada

Desde un VS se tiene acceso a los siguientes parámetros de entrada:

  • gl_Vertex: Es un vector en coordenadas uniformes (4 elementos) que representa al vértice que debe procesar el shader.
  • gl_Normal: Vector con las coordenadas 3D de la normal asociada al vértice.
  • gl_Color: Se trata de un vector con los cuatro componentes (R,G,B,A) del color del vértice.
  • gl_MultiTexCoordN: Contiene las coordenadas de textura de la unidad de textura N. En el hardware actual existen múltiples unidades de textura. Es habitual que N oscile entre 0 y 7.

Además de la información relativa al vértice que debe procesarse, el VS también tiene acceso a una serie de datos invariantes, procedentes de OpenGL y que no puede modificar, conocidos como uniforms. Los más relevantes son:

  • gl_ProjectionMatrix: Matriz de proyección. Es la matriz 4x4 habitual.
  • gl_ModelViewMatrix: Matriz de modelo-vista.
  • gl_ModelViewProjectionMatrix: Matriz con el producto de las dos anteriores. Permite proyectar los puntos sin necesidad de realizar el producto de proyección y vista por cada vértice existente.
  • gl_NormalMatrix: Matriz 4x4 obtenida a través de la trasposición y cálculo de inversa de la matriz modelo-vista. Se utiliza para la transformación de la normal.

Parámetros de salida

El único resultado que obligatoriamente debe generar un VS es el vértice, transformado o no, que se enviará a las siguientes etapas del cauce gráfico. El parámetro al que debe asignarse dicho vértice es gl_Position y, al igual que gl_Vertex, es un vector con cuatro elementos, es decir, la posición se establece en coordenadas homogéneas.

Además el VS también puede modificar otros datos, compartidos con los PS y GS, denominados genéricamente varyings. Gracias a ellos puede influir en el color que se asociará al vértice y las coordenadas de textura. Dichos elementos variables son los siguientes:

  • gl_FrontColor: Color que se asignará a la cara delantera de la primitiva.
  • gl_BackColor: Color que se asignará a la cara trasera de la primitiva.
  • gl_TexCoord[N]: Coordenadas para la textura N-sima.

Los tres parámetros son vectores de cuatro elementos, los dos primeros en formato (R,G,B,A) y el tercero en coordenadas homogéneas, como (X,Y,Z,W).

Los varying son campos que permiten compartir información entre los diferentes shaders de un programa, pero no tienen una incidencia directa sobre el resultado final aplicado a la geometría. La asignación de un color a gl_FrontColor por parte del VS, por ejemplo, no se traduce en la asignación de ese color al píxel final. Será decisión del PS, posiblemente a partir del parámetro recibido en gl_FrontColor, el color que finalmente se utilice.

Composición del VS

Conociendo exclusivamente los parámetros de entrada y salida que puede utilizar un VS, ya es posible escribir uno sencillo que, por ejemplo, lleve a cabo una transformación fija sobre los vértices de una geometría. El objetivo es duplicar el tamaño en el eje Z y reducirlo a la mitad en el eje X.

El ejecutar el programa Shader Maker se inicia un proyecto con unos shaders básicos. Con la opción Test Model se modifica el modelo sobre el que se probarán. En este caso he elegido un cubo, de forma que el aspecto inicial es el que puede verse en la figura inferior.

Aspecto inicial del cubo, sin aplicar el VS

Usando el ratón sobre el área de visualización es posible tanto rotar el modelo como hacer zoom. Es importante que la opción Use GLSL Program, el primer check-box que hay en el apartado Geometry Processing, esté marcado, de lo contrario no se usarían los shaders para generar la imagen.

A continuación modificaremos el código que aparece en la página Vertex Shader del editor. El código quedará así:

void main()
{
    vec4 v = gl_Vertex; // Tomamos el vertice de origen


    // Reducimos dimensión en eje X

    v.x = v.x * 0.5;
    // y duplicamos en eje Z

    v.z = v.z * 2;

    // Se aplica establece el vértice transformado como posición

    gl_Position    = gl_ModelViewProjectionMatrix * v;

    // Parámetros de comunicación del VS con el PS, se dejan

    // tal cual

    gl_FrontColor  = gl_Color;
    gl_TexCoord[0] = gl_MultiTexCoord0;
}

Como puede verse, el código se asemeja en gran medida a cualquier programa escrito en C. Existe una función main(), que contiene las sentencias a ejecutar; se declaran variables como v, de tipo vec4, y se realizan operaciones de asignación y producto. El operador * en este contexto, sin embargo, no realiza siempre un producto escalar entre dos números. En la sentencia gl_Position = ... se está llevando a cabo el producto de dos matrices: la de modelo-vista-proyección por la matriz de una fila y cuatro columnas que representa al vértice.

Una vez escrito el código del shader, hay que hacer clic en el botón Compile and Link para compilarlo y vincularlo al programa que genera la imagen. En ese momento podrá verse que el cubo se convierte en un paralelepípedo (véase la figura inferior). Marcando y desmarcando la opción Use GLSL Program se puede activar/desactivar, respectivamente, el uso del VS, viendo así la imagen original sin shaders y la que se genera con éstos.

Código del VS y efecto que genera sobre la escena

Las últimas dos sentencias del VS proceden del esqueleto que genera Shader Maker, siendo su finalidad entregar un color y coordenadas de textura al PS. Éste los utiliza para realizar su trabajo, por lo que de eliminarse no funcionará correctamente.

El efecto que se consigue no es nada espectacular. Sin embargo, si se analiza qué está ocurriendo al activar el VS es fácil deducir algunos aspectos interesantes:

  • El programa que se ha escrito actúa sobre un único vértice, que recibe como entrada en gl_Vertex, y genera un único resultado, en gl_Position. El resto de los vértices de la geometría, por tanto, no influyen en el cálculo del vértice que está procesando el VS en un momento dado.
  • Una vez compilado y enviado a la GPU, el VS realmente se ejecuta en paralelo sobre un conjunto de vértices de la geometría. En este caso concreto la transformación de los vértices del cubo se lleva a cabo en un único ciclo, ya que existen tan solo ocho vértices y el hardware empleado permitiría ejecutar en paralelo varios cientos de vértices. Para conseguir este mismo rendimiento en una CPU habría que crear explícitamente múltiples hilos y, en el mejor de los casos, se podrían procesar hasta 4 u 8 vértices por ciclo, dependiendo del microprocesador.
  • Aunque en este ejemplo la transformación aplicada al vértice es constante, siempre se divide y multiplica por 2, nada impide que esos factores sean variables, tal y como se mostrará en el ejemplo de la próxima entrada, permaneced atentos.