Hola a todos! Mi nombre es Grigory Dyadichenko, y soy el fundador y director de tecnología de Foxsys Studios. Hoy hablaremos sobre sombreadores de vértices. El artículo examinará la práctica desde el punto de vista de Unity, ejemplos muy simples, así como muchos enlaces para estudiar información sobre sombreadores en Unity. Si eres bueno escribiendo sombreadores, entonces no encontrarás nada nuevo para ti. Cualquiera que quiera comenzar a escribir sombreadores en Unity, bienvenido a cat.

Poco de teoría
Para una mejor comprensión del proceso de sombreado, echemos un vistazo a una pequeña teoría. Un sombreador de vértices o sombreador de vértices es una etapa programable de un sombreador que funciona con vértices individuales. Los vértices a su vez almacenan varios atributos que son procesados por esta parte del sombreador para obtener los atributos convertidos en la salida.
Ejemplos donde se usan sombreadores de vértices
Deformación de objetos : ondas realistas, el efecto de las ondas de la lluvia, deformación cuando golpea una bala, todo esto se puede hacer con sombreadores de vértices, y se verá más realista que lo mismo hecho a través de Bump Mapping en la parte del fragmento del sombreador. Dado que esto es un cambio en la geometría. Los sombreadores de nivel 3.0 sobre este tema tienen una técnica llamada asignación de desplazamiento, ya que ahora tienen acceso a texturas en la parte del vértice del sombreador.
Animación de objetos. Los juegos se ven más vivos e interesantes cuando las plantas reaccionan a un personaje o los árboles se mecen con el viento. Para esto, también se usan sombreadores de vértices.
Iluminación de dibujos animados o estilizada. En muchos juegos, desde el punto de vista del estilo, no es la iluminación pbr lo que parece mucho más interesante, sino la estilización. Al mismo tiempo, no tiene sentido calcular nada en la parte del fragmento.
Desollado En el momento dado en los motores de juego, este problema está resuelto, pero es útil comprender los sombreadores de vértices para entender cómo funciona.
Ejemplos simples de trabajo con vértices

No quiero que suceda, como en las viejas lecciones sobre cómo dibujar un búho, así que vamos por etapas. Crea un sombreador de superficie estándar. Esto se puede hacer con el botón derecho del mouse en la Vista del proyecto o en el panel superior de la pestaña Activos. Crear-> Sombreador-> Sombreador de superficie estándar.
Y obtenemos un espacio en blanco estándar.
Sombreador de superficieShader "Custom/SimpleVertexExtrusionShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = ca;
}
ENDCG
}
FallBack "Diffuse"
}
Cómo funciona y, en general, lo analizaremos en detalle en el artículo después de la práctica básica, además lo entenderemos parcialmente durante la implementación de sombreadores. Por ahora, deje que algunas de las cosas permanezcan como se dan. En resumen, no hay magia (en términos de cómo se conectan los parámetros, etc.) Solo para ciertas palabras clave, la unidad genera código para usted, para no escribirlo desde cero. Por lo tanto, este proceso no es lo suficientemente obvio. Puede leer más sobre el sombreador de superficie y sus propiedades en Unity aquí.
docs.unity3d.com/Manual/SL-SurfaceShaders.htmlEliminaremos todo lo superfluo para que no distraiga, ya que en el momento dado no es necesario. Y consigue un sombreador tan corto.
Shader simplificadoShader "Custom/SimpleVertexExtrusionShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
struct Input
{
float4 color : COLOR;
};
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}

Solo el color del modelo con iluminación. En este caso, Unity es responsable de calcular la iluminación.
Primero, agregue el efecto más simple de los ejemplos de Unity. La extrusión es normal, y en su ejemplo analizaremos cómo funciona.
Para hacer esto, agregue el modificador
vertex: vert a la línea
#pragma surface surf Standard fullforwardshadows . Si pasamos
inout appdata_full v como parámetro a una función, entonces, en esencia, esta función es un modificador de vértice. En esencia, es parte del sombreador de vértices, creado por la unidad generadora de código, que realiza el procesamiento preliminar de los vértices. También en el bloque
Propiedades , agregue el campo
_Amount aceptando valores de 0 a 1. Para usar el campo
_Amount en el sombreador, también debemos definirlo allí. En la función, simplemente
cambiaremos a la normalidad dependiendo de
_Amount , donde 0 es la posición estándar del vértice (desplazamiento cero) y 1 es el desplazamiento exactamente a la normalidad.
SimpleVertexExtrusionShaderShader "Custom/SimpleVertexExtrusionShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_Amount ("Extrusion Amount", Range(0,1)) = 0.5
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex:vert
#pragma target 3.0
struct Input
{
float4 color : COLOR;
};
fixed4 _Color;
float _Amount;
void vert (inout appdata_full v)
{
v.vertex.xyz += v.normal * _Amount;
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
Puedes notar una característica importante de los sombreadores. Aunque el sombreador se ejecuta en cada cuadro, el resultado obtenido durante la operación del sombreador no se almacena en la malla, sino que se usa solo para renderizar. Por lo tanto, es imposible relacionarse con las funciones del sombreador, así como con la
Actualización en scripts. Se aplican cada cuadro sin cambiar los datos de la malla, sino simplemente modificando la malla para su posterior representación.
Por ejemplo, una de las formas más fáciles de hacer una animación es usar el tiempo para cambiar la amplitud. La unidad tiene variables integradas, una lista completa de las cuales se puede encontrar aquí
docs.unity3d.com/Manual/SL-UnityShaderVariables.html En este caso, escribiremos un nuevo sombreador basado en nuestro sombreador anterior. En lugar de
_Amount, hagamos el valor flotante
_Amplitude y usemos la variable Unity
incorporada _SinTime .
_SinTime es el seno del tiempo y, por lo tanto, toma valores de -1 a 1. Sin embargo, no olvide que todas las variables de tiempo incorporadas en los sombreadores de la unidad son vectores
float4 . Por ejemplo,
_SinTime se define como
(sin (t / 8), sin (t / 4), sin (t / 2), sin (t)) , donde t es el tiempo. Por lo tanto, tomamos el componente z para que la animación sea más rápida. Y obtenemos:
SimpleVertexExtrusionWithTimeShader "Custom/SimpleVertexExtrusionWithTime"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_Amplitude ("Extrusion Amplitude", float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex:vert
#pragma target 3.0
struct Input
{
float4 color : COLOR;
};
fixed4 _Color;
float _Amplitude;
void vert (inout appdata_full v)
{
v.vertex.xyz += v.normal * _Amplitude * (1 - _SinTime.z);
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
Así que estos fueron ejemplos simples. ¡Es hora de dibujar un búho!
Deformación de objetos.
Ya he escrito un artículo completo sobre el tema de un efecto de deformación con un análisis detallado de las matemáticas del proceso y la lógica del pensamiento al desarrollar dicho efecto
habr.com/en/post/435828 Este será nuestro búho.
Todos los sombreadores en el artículo están escritos en hlsl. Este lenguaje en realidad tiene su propia documentación voluminosa, que muchos olvidan y se preguntan de dónde provienen la mitad de las funciones cableadas, aunque están definidas en HLSL
docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl- funciones intrínsecasPero, de hecho, los sombreadores de superficie en una unidad son un tema grande y voluminoso en sí mismo. Además, no siempre querrás meterte con la iluminación de Unity. A veces necesitas hacer trampa y escribir el sombreador más rápido que solo tiene el conjunto correcto de efectos predefinidos. En unidad, puedes escribir sombreadores de nivel inferior.
Sombreadores de bajo nivel

De acuerdo con la vieja tradición de trabajar con sombreadores, en lo sucesivo atormentaremos al conejo de Stanford.
En general, el denominado Unity ShaderLab es esencialmente una visualización de un inspector con campos en materiales y cierta simplificación de los sombreadores de escritura.
Tome la estructura general del sombreador Shaderlab:
Estructura de sombreado generalShader "MyShaderName"
{
Properties
{
//
}
SubShader // ( )
{
Pass
{
//
}
//
}
//
FallBack "VertexLit" // ,
}
Directivas de compilación como
#pragma vertex vert#pragma frag fragdetermine qué funciones de sombreador compilar como sombreadores de vértices y fragmentos, respectivamente.
Digamos que tomamos uno de los ejemplos más comunes: un sombreador para mostrar el color de las normales:
Visualización Normal NormalShader "Custom/SimpleNormalVisualization"
{
Properties
{
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal * 0.5 + 0.5;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4 (i.color, 1);
}
ENDCG
}
}
FallBack "VertexLit"
}

En este caso, en la parte del vértice, escribimos el valor normal convertido al color del vértice, y en la parte del píxel usamos este color como el color del modelo.
La función
UnityObjectToClipPos es una función auxiliar de Unity (del archivo
UnityCG.cginc ) que traduce los vértices del objeto a la posición asociada con la cámara. Sin él, un objeto, cuando entra en la visibilidad de la cámara (frustrum), se dibujará en las coordenadas de la pantalla, independientemente de la posición de la transformación. Como inicialmente las posiciones de los vértices se presentan en las coordenadas del objeto. Solo valores relativos a su pivote.
Este bloque
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
Esta es la definición de la estructura que se procesará en la parte del vértice y se transferirá al fragmento. En este caso, se determina que se toman dos parámetros de la malla: la posición del vértice y el color del vértice. Puede leer más sobre qué datos se pueden lanzar en una unidad en este enlace
docs.unity3d.com/Manual/SL-VertexProgramInputs.htmlAclaraciones importantes Los nombres de los atributos de malla no importan. Es decir, digamos en el atributo de color que puede escribir el vector de desviación de la posición original (de esta manera, a veces tienen un efecto cuando el personaje se va para que la hierba "se repele"). La forma en que se procesará este atributo depende completamente de su sombreador.
Conclusión
Gracias por su atencion! Escribir algunos efectos complejos es problemático sin una parte fragmentaria, por esta razón discutiremos similares en artículos separados. Espero que durante este artículo se haya aclarado un poco cómo se escribe el código de los sombreadores de vértices en general y dónde puedes encontrar información para estudiar, ya que los sombreadores son un tema muy profundo.
En futuros artículos, analizaremos los otros tipos de sombreadores, efectos individuales e intentaré describir mi lógica de pensamiento al crear efectos nuevos o complejos.
También se ha creado un repositorio donde se agregarán todos los resultados de esta serie de artículos
github.com/Nox7atra/ShaderExamples . Espero que esta información sea útil para los principiantes que recién comienzan su viaje en el estudio de este tema.
Algunos enlaces útiles (incluidas las fuentes):
www.khronos.org/opengl/wiki/Vertex_Shaderdocs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-referencedocs.unity3d.com/en/current/Manual/SL-Reference.htmldocs.unity3d.com/Manual/GraphicsTutorials.htmlwww.malbred.com/3d-grafika-3d-redaktory/sovremennaya-terminologiya-3d-grafiki/vertex-shader-vershinnyy-sheyder.html3dpapa.ru/accurate-displacement-workflow