¡Hola! Hoy sigo discutiendo . Este artículo será dos veces más grande que el anterior. ¡Agárrate fuerte! el renderizado en Unity ¿Qué es un sombreador? En base a lo descrito en un artículo anterior, un shader es un pequeño programa que se puede utilizar para crear efectos interesantes en nuestros proyectos. Contiene cálculos matemáticos y listas de instrucciones (comandos). Nos permiten procesar el color de cada píxel en el área que cubre el objeto en la pantalla de nuestra computadora o trabajar con transformaciones de objetos (por ejemplo, para crear césped o agua dinámicos). Este programa nos permite dibujar elementos (usando sistemas de coordenadas) basados en las propiedades de nuestro objeto poligonal. Los sombreadores se ejecutan en la porque tiene una arquitectura paralela que consta de miles de núcleos pequeños y eficientes diseñados para manejar tareas simultáneamente. Por cierto, la CPU fue diseñada para el procesamiento en serie secuencial. GPU Tenga en cuenta que hay tres tipos de archivos relacionados con sombreadores en Unity. Primero, tenemos programas con la extensión ".shader" capaces de compilar diferentes tipos de canalizaciones de renderizado. En segundo lugar, tenemos programas con la extensión ".shadergraph" que solo pueden compilar en URP o HDRP. Además, disponemos de archivos con extensión ".hlsl" que nos permiten crear funciones personalizadas. Por lo general, se usan en un tipo de nodo denominado Función personalizada, que se encuentra en el gráfico de sombreado. También hay otro tipo de sombreador con la extensión ".cginc" - Compute Shader. Está asociado con el CGPROGRAM ".shader", mientras que ".hlsl" está asociado con el HLSLPROGRAM ".shadergraph". En Unity, se definen al menos cuatro tipos de estructuras para la generación de shaders. Entre ellos, podemos encontrar una combinación de sombreadores de vértices y fragmentos, sombreadores de superficie para el cálculo automático de iluminación y sombreadores de cálculo para conceptos más avanzados. Una pequeña excursión al lenguaje shader Antes de comenzar a escribir sombreadores en general, debemos tener en cuenta que hay tres lenguajes de programación de sombreadores en Unity: HLSL (lenguaje de sombreado de alto nivel - Microsoft) Cg (C para gráficos - NVIDIA) - un formato obsoleto ShaderLab - un lenguaje declarativo - Unity Vamos a repasar rápidamente Cg, y tocar un poco HLSL. ShaderLab Cg es un lenguaje de programación de alto nivel diseñado para compilar en la mayoría de las GPU. NVIDIA lo desarrolló en colaboración con Microsoft y utilizó una sintaxis muy similar a HLSL. La razón por la que los shaders funcionan con el lenguaje Cg es que pueden compilar tanto con HLSL como con GLSL (OpenGL Shading Language), acelerando y optimizando el proceso de creación de material para videojuegos. Todos los shaders en Unity (excepto Shader Graph y Compute) están escritos en un lenguaje declarativo llamado ShaderLab. La sintaxis de este lenguaje nos permite mostrar las propiedades del shader en el inspector de Unity. Esto es muy interesante porque podemos manipular los valores de variables y vectores en tiempo real, personalizando nuestro shader para obtener el resultado deseado. En ShaderLab podemos definir manualmente varias propiedades y comandos, entre ellos el Fallback. Es compatible con los diferentes tipos de pipelines de renderizado que existen en Unity. Fallback es un bloque de código fundamental en los juegos multiplataforma. Nos permite compilar otro shader en lugar del que generó el error. Si un sombreador se rompe durante la compilación, Fallback devuelve otro sombreador y el hardware de gráficos puede continuar con su trabajo. Esto es necesario para que no tengamos que escribir sombreadores diferentes para Xbox y PlayStation, sino usar sombreadores unificados. Tipos básicos de shaders en Unity Los tipos básicos de shaders en Unity nos permiten crear subrutinas para ser usadas para diferentes propósitos. Analicemos de qué es responsable cada tipo: Este tipo de shader se caracteriza por la optimización de la escritura de código que interactúa con el modelo de iluminación base y solo funciona con Built-In RP. Sombreador de superficie estándar. Shader sin iluminación. Se refiere al modelo de color primario y será la estructura base que normalmente usamos para crear nuestros efectos. Estructuralmente, es muy similar al shader Unlit. Estos sombreadores se utilizan principalmente en los efectos de posprocesamiento de RP integrados y requieren la función "OnRenderImage()" (C#). Sombreador de efectos de imagen. Este tipo se caracteriza por el hecho de que se ejecuta en la tarjeta de video y es estructuralmente muy diferente a los shaders mencionados anteriormente. Calcular sombreador. Un tipo de sombreador experimental que permite recopilar y procesar el trazado de rayos en tiempo real. Funciona solo con HDRP y DXR. Sombreador Ray Tracing. Un sombreador vacío basado en gráficos con el que puede trabajar sin conocimiento de los lenguajes de sombreado usando nodos. Gráfico de sombreado en blanco. Es un subshader que se puede usar en otros shaders de Shader Graph. Subgráfico. Renderizar en Unity es un tema difícil, así que lee esta guía con atención. Estructura de sombreado Para analizar la estructura de los shaders, todo lo que necesitamos hacer es crear un shader simple basado en Unlit y analizarlo. Cuando creamos un shader por primera vez, Unity agrega un código predeterminado para facilitar el proceso de compilación. En el shader podemos encontrar para que la GPU pueda interpretarlos. bloques de código estructurados Si abrimos nuestro shader, su estructura se ve similar: Shader "Unlit/OurSampleShaderUnlit" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags {"RenderType"="Opaque"} LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler 2D _MainTex; float4 _MainTex; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o, o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } } Con el ejemplo actual y su estructura básica, se vuelve un poco más claro. Un shader comienza con una ruta en el inspector del editor de Unity (InspectorPath) y un nombre (shaderName), luego propiedades (por ejemplo, texturas, vectores, colores, etc.) y luego SubShader. Al final, un parámetro de respaldo opcional admite diferentes variantes. Trabajando con ShaderLab La mayoría de los shaders comienzan declarando el shader y su ruta en el inspector de Unity, así como su nombre. Ambas propiedades, como SubShader y Fallback, están escritas dentro del campo "Shader" en el lenguaje declarativo de ShaderLab. Shader "OurPath/shaderName" { // The shader code will be here } Tanto la ruta como el nombre del sombreador se pueden cambiar según sea necesario dentro del proyecto. Las propiedades de los sombreadores corresponden a una lista de parámetros que se pueden manipular desde el inspector de Unity. Hay ocho propiedades diferentes, tanto en términos de valor como de utilidad. Usamos estas propiedades en relación con el shader que queremos crear o modificar, ya sea dinámicamente o en tiempo de ejecución. La sintaxis para declarar una propiedad es la siguiente: PropertyName ("display name", type) = defaultValue "PropertyName" representa el nombre de la propiedad (por ejemplo, _MainTex), "display name" especifica el nombre de la propiedad en el inspector de Unity (por ejemplo, Texture), "type" indica su tipo (por ejemplo, Color, Vector, 2D, etc.). Finalmente, "defaultValue" es el valor predeterminado asignado a la propiedad (por ejemplo, si la propiedad es "Color", podemos establecerla en blanco (1, 1, 1, 1, 1, 1). El segundo componente de un shader es el SubShader. Cada shader consta de al menos un SubShader para una carga perfecta. Cuando haya más de un SubShader, Unity procesará cada uno de ellos y seleccionará el más adecuado según las especificaciones del hardware, comenzando por el primero y terminando por el último de la lista (por ejemplo, para separar el shader para iOS y Android). Cuando SubShader no es compatible, Unity intentará usar el componente Fallback correspondiente al sombreador estándar para que el hardware pueda continuar su tarea sin errores gráficos. Shader "OurPack/OurShader" { Properties { ... } SubShader { // Here will be the shader configuration } } Puede leer más sobre parámetros y subsombreadores y . aquí aquí Mezcla Necesitamos mezcla para el proceso de combinar dos píxeles en uno. La combinación se admite tanto en Built-In como en SRP. La fusión ocurre en el paso que combina el color final de un píxel con su profundidad. Esta etapa ocurre al final de la canalización de renderizado después de la etapa del sombreador de fragmentos cuando se ejecuta el búfer de plantilla, el búfer z y la mezcla de colores. De forma predeterminada, esta propiedad no está escrita en el sombreador, ya que es una característica opcional y se usa principalmente cuando se trabaja con objetos transparentes. Por ejemplo, se usa cuando necesitamos dibujar un píxel con un píxel de baja opacidad frente a otro (esto se usa a menudo en la interfaz de usuario). Podemos habilitar la mezcla aquí: Blend [SourceFactor] [DestinationFactor] Puede leer más sobre la combinación . aquí Z-Buffer (Búfer de profundidad) Para comprender ambos conceptos, primero debemos aprender cómo funcionan el Z-Buffer (también conocido como Depth Buffer) y la prueba de profundidad. Antes de comenzar, debemos considerar que los píxeles tienen valores de profundidad. Estos valores se almacenan en el búfer de profundidad, que determina si un objeto va delante o detrás de otro objeto en la pantalla. La prueba de profundidad, por otro lado, es una condición que determina si un píxel se actualizará o no en el búfer de profundidad. Como ya sabemos, un píxel tiene un valor asignado que se mide en color RGB y se almacena en el búfer de color. Z-buffer agrega un valor adicional que mide la profundidad del píxel en términos de distancia a la cámara, pero solo para aquellas superficies que se encuentran dentro de su área frontal. Esto permite que 2 píxeles tengan el mismo color pero diferente profundidad. Cuanto más cerca esté el objeto de la cámara, menor será el valor del búfer Z y los píxeles con valores de búfer más pequeños sobrescribirán los píxeles con valores más grandes. Para entender el concepto, supongamos que tenemos una cámara y algunas primitivas en nuestra escena, y todas están ubicadas en el eje espacial "Z". La palabra "búfer" se refiere al "espacio de memoria" donde se almacenarán temporalmente los datos, por lo que el Z-buffer se refiere a los valores de profundidad entre los objetos de nuestra escena y la cámara que se asignan a cada píxel. Podemos controlar la prueba de profundidad gracias a los parámetros ZTest en Unity. sacrificio Esta propiedad, compatible con RP integrado y URP/HDRP, controla qué caras del polígono se eliminarán al procesar la profundidad de píxeles. ¿Qué quiere decir esto? Recuerde que un objeto poligonal tiene bordes interiores y bordes exteriores. Por defecto, los bordes exteriores son visibles (CullBack). Sin embargo, podemos activar los bordes interiores: Ambos bordes del objeto se renderizan. Eliminar. Por defecto, los bordes posteriores del objeto se muestran Cull Back. Se renderizan los bordes frontales del objeto. Frente de desecho. Este comando tiene tres valores, a saber, Atrás, Adelante y Apagado. El comando Atrás está activo de forma predeterminada; sin embargo, por lo general, la línea de código asociada con la selección no está visible en el sombreador con fines de optimización. Si queremos cambiar los parámetros, tenemos que agregar la palabra "Cull" seguida del modo que queremos usar. Shader "Culling/OurShader" { Properties { [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0 } SubShader { // Cull Front // Cull Off Cull [_Cull] } } También podemos configurar dinámicamente a través de la dependencia "UnityEngine.Rendering.CullMode". Es Enum y se pasa como argumento a una función. los parámetros de Culling en el inspector de Unity Uso de Cg/HLSL En nuestro shader podemos encontrar al menos tres variantes de directivas por defecto. Estas son directivas de procesador incluidas en Cg o HLSL. Su función es ayudar a nuestro shader a reconocer y compilar ciertas funciones que de otro modo no se reconocerían como tales: Permite que la etapa del sombreador de vértices se compile en la GPU como un sombreador de vértices. #pragma vértice vert. Esta directiva realiza la misma función que pragma vertex, con la diferencia de que permite compilar una etapa de shader de fragmentos llamada "frag" como un shader de fragmentos en el código. Fragmento de fragmento de #pragma. A diferencia de las directivas anteriores, tiene una doble función. Primero, multi_compile se refiere a un shader de variantes que nos permite generar variantes con diferente funcionalidad en nuestro shader. En segundo lugar, la palabra "_fog" incluye la función de niebla de la ventana Iluminación en Unity. Si vamos a Entorno/Otros Ajustes, podemos activar o desactivar las opciones de niebla de nuestro shader. #pragma multi_compile_fog. También podemos conectar archivos Cg/HLSL a nuestro shader. Por lo general, hacemos esto cuando conectamos UnityCG.cginc. Incluye coordenadas de niebla, posiciones de objetos para recortar, transformaciones de texturas, transporte de niebla y mucho más, incluidas las constantes UNITY_PI. Lo más importante que podemos hacer con Cg/HLSL es escribir funciones de procesamiento directo para sombreadores de vértices y fragmentos, para usar variables de estos lenguajes y varias coordenadas como coordenadas de textura (TEXCOORD0). #pragma vertex vert #pragma fragment frag v2f vert (appdata v) { // Ability to work with the vertex shader } fixed4 frag (v2f i) : SV_Target { // Ability to work with fragment shader } Puede leer más sobre Cg/HLSL . aquí Gráfico de sombreado Shader Graph es una nueva solución para Unity que te permite crear tus soluciones sin tener conocimiento del lenguaje de sombreado. Se utilizan nodos visuales para trabajar con él (pero nadie prohíbe combinarlos con el lenguaje de sombreado). Shader Graph solo funciona con HDRP y URP. Debe recordar que al trabajar con Shader Graph, las versiones desarrolladas para Unity 2018 son versiones BETA y no reciben soporte. Las versiones desarrolladas para Unity 2019.1+ son activamente compatibles y obtienen soporte. Otro problema es que es muy probable que los shaders creados con esta interfaz no se compilen correctamente en diferentes versiones. Esto se debe a que se agregan nuevas características en cada actualización. Entonces, ¿Shader Graph es una buena herramienta para el desarrollo de shaders? Por supuesto que es. Y puede ser manejado no solo por un programador gráfico sino también por un diseñador técnico o artista. Para crear un gráfico, todo lo que tenemos que hacer es seleccionar el tipo que queremos en el editor de Unity. Antes de comenzar, presentemos brevemente el sombreador de vértices/fragmentos en el nivel de Shader Graph. Como podemos ver, hay tres puntos de entrada definidos en la etapa del sombreador de vértices, a saber: Posición (3), Normal (3) y Tangente (3), al igual que en un sombreador Cg o HLSL. Cuando se compara con un shader regular, esto significa que Posición(3) = POSICIÓN[n], Normal(3) = NORMAL[n] y Tangente(3) = TANGENTE[n]. ¿Por qué un Shader Graph tiene tres dimensiones, pero un Cg o HLSL tiene 4? Recuerde que las cuatro dimensiones de un vector corresponden a su componente W, que es "uno o cero" en la mayoría de los casos. Cuando W = 1, el vector corresponde a una posición espacial o puntual. Mientras que cuando W = 0, el vector corresponde a una dirección en el espacio. Entonces, para configurar nuestro sombreador, primero vamos al editor y creamos dos parámetros: color - _Color y Texture2D - _MainTex. Para crear un vínculo entre las propiedades de ShaderLab y nuestro programa, debemos crear variables en el campo CGPROGRAM. Sin embargo, este proceso es diferente en Shader Graph. Debemos arrastrar y soltar las propiedades que queremos usar en el espacio de trabajo del nodo. Todo lo que tenemos que hacer para que Texture2D funcione junto con el nodo Sample Texture 2D es conectar la salida de la propiedad _MainTex a la entrada Texture(T2). Para multiplicar ambos nodos (color y textura), solo necesitamos llamar al nodo Multiplicar y pasar ambos valores como punto de entrada. Finalmente, necesitamos enviar la salida de color en el color base en la etapa del sombreador de fragmentos. Ahora guarde el sombreador y listo. Nuestro primer shader está listo. También podemos pasar a la configuración general del gráfico, dividida en secciones de Nodo y Gráfico. Tienen propiedades personalizables que nos permiten cambiar la reproducción del color. Podemos encontrar opciones de blending, alpha clipping, etc. Además, podemos personalizar las propiedades de los nodos en nuestra configuración de Shader Graph. Los propios nodos proporcionan análogos de ciertas funciones que escribimos en ShaderLab. Como ejemplo, el código de la función Pinza: void Unity_Clamp_float4(float4 In, float4 Min, float4 Max, out float4 Out) { Out = clamp(In, Min, Max); } De esta manera, podemos simplificar nuestras vidas y reducir el tiempo para escribir shaders a expensas de los gráficos visuales. Conclusión Puedo hablar mucho sobre sombreadores y durante mucho tiempo, así como tocar el proceso de renderizado en sí. He discutido todos los conceptos básicos en esta . Aquí no he discutido los sombreadores de trazado de rayos y Compute-Shading. He cubierto los lenguajes de sombreado superficialmente y describí los procesos solo desde la punta del iceberg. guía sobre renderizado en Unity