Shader no es mágico. Escribiendo sombreadores en Unity. Sombreadores de vértices

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 superficie
Shader "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.html

Eliminaremos todo lo superfluo para que no distraiga, ya que en el momento dado no es necesario. Y consigue un sombreador tan corto.

Shader simplificado
Shader "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.

SimpleVertexExtrusionShader
Shader "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:

SimpleVertexExtrusionWithTime
Shader "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ínsecas

Pero, 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 general
Shader "MyShaderName"
{
Properties
{
//
}
SubShader // ( )
{
Pass
{
//
}
//
}
//
FallBack "VertexLit" // ,
}


Directivas de compilación como
#pragma vertex vert
#pragma frag frag
determine 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 Normal
Shader "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.html

Aclaraciones 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_Shader
docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-reference
docs.unity3d.com/en/current/Manual/SL-Reference.html
docs.unity3d.com/Manual/GraphicsTutorials.html
www.malbred.com/3d-grafika-3d-redaktory/sovremennaya-terminologiya-3d-grafiki/vertex-shader-vershinnyy-sheyder.html
3dpapa.ru/accurate-displacement-workflow

Source: https://habr.com/ru/post/474812/


All Articles