Voy a preparar una lista de entradas enfocadas en el desarrollo de shaders, sombreadores en español, para Unity. Me centraré en los de tipo vertex and fragment, dejando de lado de momento los de superficie ( surface ).
Este tipo de shaders tiene un funcionamiento muy fácil de comprender, requiriendo de dos programas, el de procesado de vértices y el de fragmentos, Vertex Shader y Fragment Shader respectivamente. Un fragmento es potencialmente un pixel, porque aun ha de pasar por diferentes «filtros» ( depth test, alpha test, stencil test,… ) y puede que llegue a ser descartado.
The fragment shader is the OpenGL pipeline stage after a primitive is rasterized. For each sample of the pixels covered by a primitive, a «fragment» is generated. Each fragment has a Window Space position, a few other values, and it contains all of the interpolated per-vertex output values from the last Vertex Processing stage. [ LINK ]
La única información que siempre recibirá el programa vertex, son las coordenadas locales de todos los vértices de la malla del elemento a renderizar, pero también puede recibir las coordenadas UV, normales, tangente y color.
Unity utiliza la semántica de Cg/HLSL para especificar la finalidad de cada argumento que recibirán los programas. Cg o C for Graphics, un lenguaje de alto nivel desarrollado entre NVidia y Microsoft para la programación de shaders vertex and fragment, y HLSL o High Level Shader Language desarrollado en exclusiva por Microsoft, son muy similares puesto que se desarrollaron a la par y Microsoft estuvo involucrada en ambos desarrollos. Por su parte OpenGL utiliza GLSL.
El termino «semántica» en este contexto se refiere a unas palabras reservadas que se usan no para indicar el tipo de dato, sino la finalidad del mismo o dicho de forma más concreta, indica como mover la información por el pipeline de renderizado. Mediante su uso se consigue automatizar que información recibirán ( y devolverán ) los programas del shader, sin necesidad de preocuparse de como esta haya sido conseguida o desde donde haya sido enviada.
v2f Vertex ( float4 position : POSITION ) // Coordenadas locales del vértice
{
//...
}
El listado de terminos que se cita en la documentación de Unity es el siguiente: POSITION, NORMAL, TEXCOORD0, TEXCOORD1|2|3, TANGENT Y COLOR, aunque hay que tener un detalle en cuenta, habiendo de usar el prefijo SV_ ( System Value ) en ciertos casos. El caso más habitual es SV_POSITION como salida ( output ) del programa vertex representa la posición ya transformada del vértice, generalmente del espacio local al de pantalla, aplicando la matriz de transformación MVP, mientras que como entrada ( input ) del programa fragment contiene la posición del fragmento/pixel, conseguida en la fase de rasterización interpolando la posición de los vértices que componen la cara a la que pertenece.
El programa vertex ha de pasar al programa fragment la posición ya transformada de los vértices, para que durante la rasterización se calcule la posición de cada posible pixel a pintar, cada fragmento. El programa fragment tiene una tarea muy concreta, indicar el color de cada pixel que se va a pintar. En la imagen superior se aprecia el proceso por el que pasan las coordenadas al aplicar la matriz MVP, mientras que en la imagen inferior y de forma muy resumida, se muestra el pipeline de renderizado.
https://djbardsley.wordpress.com
El shader más básico que se puede desarrollar es el que aplica un mismo color fijo a todo fragmento, pero por avanzar un poco voy a indicar un color que se pueda modificar desde el inspector de Unity. Además también se coloreará en base a la coordenadas locales ( local/object coordinates ), consiguiendo un curioso efecto similar al típico color picker.
Shader "Custom/Shader 0 - Color and Tint"
{
Properties
{
_Tint ( "Tint color", Color ) = ( 1, 1, 1, 1 )
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex Vertex
#pragma fragment Fragment
#include "UnityCG.cginc"
uniform float4 _Tint;
struct v2f
{
float4 position : SV_POSITION;
float3 localPosition : TEXCOORD0;
};
v2f Vertex ( float4 position : POSITION )
{
v2f o;
o.localPosition = position.xyz;
o.position = UnityObjectToClipPos ( position );
return o;
}
float4 Fragment ( v2f o ) : SV_TARGET
{
return float4 ( o.localPosition, 1 ) * _Tint;
}
ENDCG
}
}
}
El resultado es el siguiente, habiendo especificado el blanco como color de tintado, lo que es lo mismo que no afectar en nada al resultado.



