Consigue un 20% de descuento usando el código RELEASE en la compra de tu libro. Por tiempo limitado.

Introducción al lenguaje de programación de shaders

3.3.0. Cg / HLSL vertex input & vertex output

Esta publicación está disponible en...

Un tipo de dato que usaremos con frecuencia en la creación de nuestros shaders son los “struct”. Para aquellos que conocen el lenguaje C, un struct o estructura es una declaración de tipo de datos compuesto, que define una lista agrupada de varios elementos del mismo tipo el cual permite acceder a diferentes variables a través de un único puntero. Los struct los utilizaremos para definir tanto los input como los output en nuestro shader y su sintaxis es la siguiente:

struct name
{
    vector[n] name : SEMANTIC[n];
};

Primero declaramos el struct y luego su nombre. En seguida, dentro del campo del struct almacenamos la semántica en nuestro vector para utilizarla posteriormente. El struct “name” corresponde al nombre de la estructura, el “vector” corresponde al tipo de vector que usaremos (e.g. float2, half4) para asignar una semántica. Por último, “SEMANTIC” corresponde a la semántica que pasaremos como input u output.

Por defecto, Unity agrega dos funciones de tipo struct los cuales son: appdata y v2f. Appdata corresponde al “vertex input” y v2f hace referencia al “vertex output”.

El vertex input será el lugar donde almacenaremos las propiedades de nuestros objetos (e.g. posición de los vértices, normales, etc) a modo de “entrada” para llevarlos al “vertex shader stage”. En cambio, vertex output será donde almacenaremos las propiedades rasterizadas para llevarlas al “fragment shader stage”.

Vamos a pensar en las semánticas como “propiedades de acceso” de un objeto. Según la documentación oficial de Microsoft:

“una semántica es una cadena conectada a una entrada o salida del shader que transmite información sobre el uso previsto de un parámetro”.

Vamos a ejemplificar utilizando la semántica POSITION[n].

En páginas anteriores hablamos de las propiedades de una primitiva. Como ya sabemos, una primitiva cuenta con su posición de vértices, tangentes, normales, coordenadas UV y color en los vértices. Una semántica nos permite acceder de maneras individual a estas propiedades, es decir, si declaramos un vector de cuatro dimensiones y le pasamos la semántica POSITION[n] entonces ese vector contendrá la posición de los vértices de la primitiva. Supongamos, si declaramos el siguiente vector:

float4 pos : POSITION;

Esto quiere decir que dentro del vector de cuatro dimensiones llamado “pos” estamos almacenando la posición de los vértices en object-space de un objeto.

Las semánticas más comunes que utilizaremos corresponden a:

  • POSITION[n].
  • TEXCOORD[n].
  • TANGENT[n].
  • NORMAL[n].
  • COLOR[n].
struct vertexInput (e.g. appdata)
{
    float4 vertPos : POSITION;
    float2 texCoord : TEXCOORD0;
    float3 normal : NORMAL0;
    float3 tangent : TANGENT0;
    float3 vertColor:  COLOR0;
};

struct vertexOutput (e.g. v2f)
{
    float4 vertPos : SV_POSITION;
    float2 texCoord : TEXCOORD0;
    float3 tangentWorld : TEXCOORD1;
    float3 binormalWorld : TEXCOORD2;
    float3 normalWorld : TEXCOORD3;
    float3 vertColor:  COLOR0;
};

TEXCOORD[n] permite acceder a las coordenadas UV de nuestra primitiva y posee hasta cuatro dimensiones (x, y, z, w).

TANGENT[n] da acceso a las tangentes de nuestra primitiva. Si deseamos crear mapas de normales será necesario trabajar con esta semántica que posee hasta cuatro dimensiones también.

A través de NORMAL[n] podemos acceder a las normales de nuestra primitiva y posee hasta cuatro dimensiones. Si deseamos trabajar con iluminación dentro de nuestro shader, tendremos que utilizar esta semántica.

Finalmente COLOR[n] nos permite acceder al color de los vértices de nuestra primitiva y posee hasta cuatro dimensiones al igual que el resto. Generalmente este color corresponde a un color blanco (1, 1, 1, 1) por defecto

Para entender este concepto, vamos a prestar atención a las estructuras que han sido declaradas de manera automática dentro de nuestro shader USB_simple_color. Iniciaremos con appdata.

struct appdata
{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
};

Como podemos ver, dentro de la estructura se encuentran dos vectores: vertex y uv. Por una parte, “vertex” posee la semántica POSITION, esto quiere decir que dentro del vector estamos almacenando la posición de los vértices del objeto en object-space. Estos vértices son transformados a clip-space posteriormente en el vertex shader stage a través de la función UnityObjectToClipPos(v.vertex).

Por otra parte, el vector uv posee la semántica TEXCOORD0, la cual da acceso a las coordenadas UV de la textura.

¿Por qué el vector vertex posee cuatro dimensiones (float4)? Porque dentro del vector estamos almacenando los valores XYZW, donde W es igual a “uno” debido a que los vértices corresponden a una posición en el espacio.

Dentro de la estructura v2f podemos encontrar los mismos vectores que en appdata, con una pequeña diferencia en la semántica SV_POSITION, la cual cumple la misma función que POSITION[n], pero posee el prefijo “SV_” (System Value).

struct v2f
{
    float2 uv : TEXCOORD0;
    UNITY_FOG_COORDS(1)
    float4 vertex : SV_POSITION;    
};

Cabe destacar que estos vectores están siendo conectados en el vertex shader stage de la siguiente manera:

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    ...
}

o.vertex” es igual al vertex output, o sea, al vector vertex que ha sido declarado en la estructura v2f, en cambio, “v.vertex” es igual al vertex input, quiere decir, al vector vertex que fue declarado en la estructura appdata. Esta misma lógica se cumple en los vectores uv.

Siguenos para mantenerte informado sobre todas las novedades, actualizaciones y más.

Únete al grupo para compartir tus experiencias con otros desarrolladores.

¡Suscríbete a nuestro canal y sigue aprendiendo sobre desarrollo de juegos!

jettelly-logo

Jettelly Team

Somos un equipo de desarrolladores independientes con más de 9 años de experiencia en videojuegos. Como estudio independiente, hemos desarrollado Nom Noms el que publicamos con Hyperbeard en 2019. Actualmente estamos desarrollando La Biblia de Shader en Unity.

Síguenos en nuestras redes sociales.