When writing HLSL shader programs, input and output variables need to have their “intent” indicated via semantics. This is a standard concept in HLSL shader language; see the Semantics documentation on MSDN for more details.
Usted puede descargar los ejemplos mostrados abajo como un proyecto zipped de Unity, aquí.
La función principal vertex shader (indicada por el #pragma vertex
directiva) necesita tener todas las semánticas en todos los parámetros de input.
Estos corresponden a elementos de datos individuales Mesh, como la posición del vértice, el normal mesh y las coordenadas de textura.
Mire las entradas del vertex program para más detalles.
Aquí está un ejemplo de un shader de vértices simple que toma la posición de vértice y una coordenada de textura como entrada. El pixel shader visualiza la coordenada de textura como un color.
Shader "Unlit/Show UVs"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct v2f {
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
};
v2f vert (
float4 vertex : POSITION, // vertex position input
float2 uv : TEXCOORD0 // first texture coordinate input
)
{
v2f o;
o.pos = UnityObjectToClipPos(vertex);
o.uv = uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.uv, 0, 0);
}
ENDCG
}
}
}
En vez de deletrear todas las entradas individuales una por una, también es posible declarar una estructura de ellas, e indicar la semántica en cada variable miembro individual del struct. Mire ejemplos shader program para aprender a cómo hacer esto.
A menudo un fragment (pixel) shader tiene de salida un color, y tiene una
semántica SV_Target
. El fragment shader en el ejemplo de arriba hace
exactamente esto:
fixed4 frag (v2f i) : SV_Target
La función frag
tiene un tipo de retorno de fixed4
(baja precisión
RGBA color). Como solamente devuelve un solo valor, la semántica
es indicada en la función en sí, : SV_Target
.
También es posible retornar una estructura con las salidas. El fragment shader de arriba podría haber estado escrito de esta manera también, y haria exactamente lo mismo:
struct fragOutput {
fixed4 color : SV_Target;
};
fragOutput frag (v2f i)
{
fragOutput o;
o.color = fixed4(i.uv, 0, 0);
return o;
}
Devolver estructuras del fragment shader es en su mayoría útil para shaders que simplemente no devuelven un solo color. Adicionalmente la semántica soporta por el fragment shader tiene como salida lo siguiente.
SV_Target1
,SV_Target2
, etc .: Son colores adicionales escritos por el shader. Esto se utiliza cuando se renderiza en más de un destino de render a la vez (conocido como la técnica de renderizado Multiple Render Targets o MRT). SV_Target0
es el mismo que SV_Target
.
Usualmente el fragment shader no anula el valor del Z buffer, y un valor predeterminado se utiliza de la rasterización triangular regular. Sin embargo, para algunos efectos es útil tener como salida unos valores de profundidad del Z buffer personalizados por pixel.
Tenga en cuenta que en muchas GPUs esto desactiva algunas optimizaciones del búfer de profundidad, por lo que no anula el valor del búfer Z sin una buena razón. El precio de “SV_Depth” varía dependiendo de la arquitectura de la GPU, pero en general es bastante similar al costo de la prueba alfa (usando la función clip()
incorporada en HLSL). Renderizar shaders que modifiquen la profundidad después de todos los shaders opacos regulares (por ejemplo, usando AlphaTest
rendering queue.
El valor de salida de profundidad debe ser un solo float
.
Un vertex shader necesita output la posición final del espacio de clip de un vértice, de modo que la GPU sepa dónde en la pantalla rasterizarla, y a qué profundidad. Esta salida necesita tener la semántica SV_POSITION
y ser de tipo float4
.
Cualquier otra salida (“interpoladores” o “variaciones”) producida por el vertex shader son lo que su shader particular necesita. Los valores de salida del vertex shader se interpolarán a través de la cara de los triángulos renderizados y los valores en cada píxel se pasarán como entradas al fragment shader.
Muchas GPUs modernas no se preocupan realmente de la semántica que tienen estas variables; Sin embargo, algunos sistemas antiguos (sobre todo, el shader model 2 GPU en Direct3D 9) tenían reglas especiales sobre la semántica:
TEXCOORD0
, TEXCOORD1
etc se utilizan para indicar datos arbitrarios de alta precisión, tales como coordenadas y posiciones de textura.COLOR0
y COLOR1
en las vertex outputs y fragment inputs son para datos de rango de 0–1 de baja precisión (como valores de color simples).Para un soporte mejor multi-plataforma, etiquete los vertex outputs y
fragment inputs con semántica TEXCOORDn
.
Mire ejemplos shader program para ejemplos.
Hay límites para cuántas variables de interpolador se pueden utilizar en total para pasar la información del vértice al shader de fragmentos. El límite Depende de la plataforma y la GPU, y las directrices generales son:
float4
variable (.xy for one coordinate, .zw
for the second coordinate).#pragma target 3.0
).#pragma target 4.0
).Sin importar de su hardware objetivo en particular, por lo general es una buena idea utilizar tan pocos interpoladores como sea posible por razones de rendimiento.
Un shader de fragmentos puede recibir la posición del píxel que se representa como una semántica especial VPOS
.
Esta característica sólo existe a partir del modelo shader 3.0, por lo que el shader necesita tener la directiva de compilación #pragma target 3.0
.
En diferentes plataformas el tipo subyacente de la entrada de posición del espacio de la pantalla varía, por lo que para la máxima portabilidad, utilice el tipo UNITY_VPOS_TYPE
para este (seráfloat4
en la mayoría de las plataformas y float2 en Direct3D9).
Además, usar la semántica de posición de píxeles hace que sea difícil tener tanto la posición de espacio de clip (SV_POSITION) como VPOS en la misma estructura de vértice a fragmento. Por lo tanto, el shader de vértices debe emitir la posición del espacio de clip como una variable “fuera” separada. Mire el ejemplo siguiente:
Shader "Unlit/Screen Position"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
// note: no SV_POSITION in this struct
struct v2f {
float2 uv : TEXCOORD0;
};
v2f vert (
float4 vertex : POSITION, // vertex position input
float2 uv : TEXCOORD0, // texture coordinate input
out float4 outpos : SV_POSITION // clip space position output
)
{
v2f o;
o.uv = uv;
outpos = UnityObjectToClipPos(vertex);
return o;
}
sampler2D _MainTex;
fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
{
// screenPos.xy will contain pixel integer coordinates.
// use them to implement a checkerboard pattern that skips rendering
// 4x4 blocks of pixels
// checker value will be negative for 4x4 blocks of pixels
// in a checkerboard pattern
screenPos.xy = floor(screenPos.xy * 0.25) * 0.5;
float checker = -frac(screenPos.r + screenPos.g);
// clip HLSL instruction stops rendering a pixel if value is negative
clip(checker);
// for pixels that were kept, read the texture and output it
fixed4 c = tex2D (_MainTex, i.uv);
return c;
}
ENDCG
}
}
}
Un fragment shader puede recibir una variable que indica si la superficie renderizada está frente a la cámara, o mirando hacia fuera de la cámara. Esto es útil al renderizar la geometría que debe ser visible desde ambos lados, a menudo utilizada en hojas y objetos finos similares. La variable de entrada con semántica VFACE
contendrá un valor positivo para los triángulos frontales y un valor negativo para los inversos.
Esta característica sólo existe desde el modelo shader 3.0 en adelante, por lo que el shader necesita tener la directiva de compilación #pragma target 3.0
.
Shader "Unlit/Face Orientation"
{
Properties
{
_ColorFront ("Front Color", Color) = (1,0.7,0.7,1)
_ColorBack ("Back Color", Color) = (0.7,1,0.7,1)
}
SubShader
{
Pass
{
Cull Off // turn off backface culling
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
float4 vert (float4 vertex : POSITION) : SV_POSITION
{
return UnityObjectToClipPos(vertex);
}
fixed4 _ColorFront;
fixed4 _ColorBack;
fixed4 frag (fixed facing : VFACE) : SV_Target
{
// VFACE input positive for frontbaces,
// negative for backfaces. Output one
// of the two colors depending on that.
return facing > 0 ? _ColorFront : _ColorBack;
}
ENDCG
}
}
}
El shader anterior utiliza el estado Cull para desactivar el desmonte de la cara posterior (por defecto, los triángulos orientados hacia atrás no se renderizan en absoluto). Aquí está el shader aplicado a un montón de Quad meshes, girado en diferentes orientaciones:
A vertex shader can receive a variable that has the “vertex number” as an unsigned integer. This is mostly useful when you want to fetch additional per-vertex data from textures or ComputeBuffers.
Esta característica sólo existe desde DX10 (shader model 4.0) y GLCore/OpenGL ES 3, por lo que el shader necesita tener la directiva de compilación #pragma target 3.5
.
Shader "Unlit/VertexID"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.5
struct v2f {
fixed4 color : TEXCOORD0;
float4 pos : SV_POSITION;
};
v2f vert (
float4 vertex : POSITION, // vertex position input
uint vid : SV_VertexID // vertex ID, needs to be uint
)
{
v2f o;
o.pos = UnityObjectToClipPos(vertex);
// output funky colors based on vertex ID
float f = (float)vid;
o.color = half4(sin(f/10),sin(f/100),sin(f/1000),0) * 0.5 + 0.5;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return i.color;
}
ENDCG
}
}
}
(Usted puede descargar los ejemplos mostrados arriba como un proyecto Unity zipped, aquí)