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

4.0.4. Estructura de una función en HLSL

Esta publicación está disponible en...

Al igual que en C#, dentro del lenguaje HLSL podemos declarar funciones vacías (void) o funciones que retornan a un valor (return).

Dependiendo del tipo de función tendremos que utilizar “declaraciones”. Estas nos permiten determinar si un valor corresponde a un input (in), output (out), variable global (uniform) o un valor constante (const).

Iniciaremos analizando una función vacía, su sintaxis es la siguiente:

void functionName_precision (declaration type arg)
{
    float value = 0;
    arg = value;
}

Para declarar una función vacía, iniciamos con la nomenclatura void, luego el nombre de la función acompañada de la precisión y finalmente los argumentos. Como podemos ver en el ejemplo anterior, dentro del campo de la función se escribe el algoritmo u operación a realizar. Generalmente en los argumentos debemos definir si es que estos serán inputs u outputs.

¿Cómo podemos saber si llevan declaración? Todo va a depender de las funciones que deseemos pasar como argumentos. Para entender este concepto, vamos a suponer que deseamos crear una función que sea capaz de calcular la iluminación en un objeto, para ello, una de las propiedades que vamos a necesitar serán las normales, ya que con esta podemos saber desde qué dirección la fuente de luz estará iluminando nuestro objeto. Por lo tanto, las normales serían un “input a calcular” dentro de nuestra función vacía.

void FakeLight_float (in float3 Normal, out float3 Out)
{
    float[n] operation = Normal;
     Out = operation; 
}

La función anterior no cumple ninguna funcionalidad específica, pero la utilizaremos para entender los conceptos mencionados anteriormente.

FakeLight” corresponde al nombre de la función, y “_float” corresponde a la precisión de la misma, esta puede ser de tipo float o half ya que, como sabemos, estos son los formatos compatibles con el lenguaje HLSL.

En una función vacía siempre debemos agregar la precisión, de otra manera no podrá ser compilada dentro de nuestro programa. Luego en los argumentos podemos notar que las normales del objeto (float3 Normal) han sido declaradas como input ya que posee la declaración “in”, así mismo, existe un output denominado “out” el cual será el valor final de nuestra operación.

Para utilizar este tipo de funciones dentro de otra, en nuestro código debemos declarar tanto los inputs como outputs previo a la función en sí misma.

Vamos a simular nuestra función FakeLight dentro del fragment shader stage como si realmente realizara una operación:

// create our function
void FakeLight_float (in float3 Normal, out float3 Out)
{
    float[n] operation = Normal;
    Out = operation; 
}

half4 frag (v2f i) : SV_Target
{
    float3 n = i.normal;      // declare the normals. 
    float3 col = 0;           // declare the output. 
    FakeLight_float (n, col); // pass both values ​​as arguments.

    return float4(col.rgb, 1);
}

En el ejemplo anterior hay varias situaciones que están ocurriendo. Primero, ha sido declarada la función “FakeLight” sobre la función “frag” debido a que la GPU lee nuestro código desde arriba hacia abajo. Luego, en el fragment shader stage, hemos creado un vector de tres dimensiones llamado “n” y otro vector de tres dimensiones también llamado “col”. En este caso ambos son de tipo vector de tres dimensiones, esto se debe a que utilizaremos ambos vectores como argumentos en la función FakeLight_float, la cual nos pide tanto un input como output de tipo vector de tres dimensiones. Entonces, el primer argumento corresponde al input de normales del objeto y el segundo, al resultado de la operación que se está realizando dentro de la función FakeLight_float.

El vector col ha sido inicializado en “cero”, esto quiere decir que posee “0” para el color rojo, verde y azul (RGB), el cual corresponde a un color negro por defecto, sin embargo, ya que ha sido declarado como output, ahora posee el resultado que se esta llevando a cabo dentro de la función FakeLight_float.

Finalmente retornamos a un vector de cuatro dimensiones en donde, los primeros tres valores corresponden al vector col en RGB y “uno” para el Alpha.

¿Por qué estamos retornando un vector de cuatro dimensiones? Esto se debe a que la función frag es de tipo half4, o sea, un vector de cuatro dimensiones.

Ahora analizaremos la estructura de una función que retorna un valor. En esencia son muy similares, con la diferencia que en este caso no será necesario agregar la precisión de la función. Para ejemplificar vamos a utilizar la misma función FakeLight, pero esta vez retornará un valor.

// create our function 
half3 FakeLight (float3 Normal)
{
    float[n] operation = Normal;
    return operation; 
}

half4 frag (v2f i) : SV_Target
{
    // declare the normals. 
    float3 n = i.normal;        
    float3 col = FakeLight_float (n);    

    return float4(col.rgb, 1);
}

A diferencia de la función vacía, simplemente agregamos el argumento Normal dado que no requiere output, así mismo, en el fragment shader stage hicimos el vector “col” igual a esta función debido a que retorna la misma cantidad de dimensiones que posee este vector.

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.