¿Qué estás buscando?
Engine & platform

Eliminación de variantes de sombreado scriptables

CHRISTOPHE RICCIO / UNITY TECHNOLOGIESContributor
May 14, 2018|12 minutos
Eliminación de variantes de sombreado scriptables
Para tu comodidad, tradujimos esta página mediante traducción automática. No podemos garantizar la precisión ni la confiabilidad del contenido traducido. Si tienes alguna duda sobre la precisión del contenido traducido, consulta la versión oficial en inglés de la página web.

Reduzca masivamente el tiempo de compilación del reproductor y el tamaño de los datos permitiendo a los desarrolladores controlar qué variantes de sombreado son manejadas por el compilador de sombreado de Unity e incluidas en los datos del reproductor.

El tiempo de construcción del reproductor y el tamaño de los datos aumentan junto con la complejidad de su proyecto debido al creciente número de variantes de sombreado.

Con la eliminación de variantes de sombreado mediante scripts, introducida en la versión beta 2018.2, puede gestionar el número de variantes de sombreado generadas y, por tanto, reducir drásticamente el tiempo de compilación del reproductor y el tamaño de los datos.

Esta función le permite eliminar todas las variantes de sombreado con rutas de código no válidas, eliminar variantes de sombreado para características no utilizadas o crear configuraciones de construcción de sombreado como "depurar" y "liberar" sin que ello afecte al tiempo de iteración o a la complejidad del mantenimiento.

En esta entrada del blog, definiremos primero algunos de los términos que utilizamos. A continuación nos centraremos en la definición de las variantes de sombreado para explicar por qué podemos generar tantas. A continuación se describe la eliminación automática de variantes de sombreado y cómo se implementa la eliminación de variantes de sombreado mediante scripts en la arquitectura de canalización de sombreado de Unity. A continuación, echamos un vistazo a la API de despojamiento de variantes de sombreado mediante scripts antes de comentar los resultados en la demostración de Fountainbleau y concluir con algunos consejos sobre la escritura de scripts de despojamiento.

Aprender la eliminación de variantes de sombreado mediante scripts no es una tarea trivial, ¡pero puede suponer un enorme aumento de la eficacia del equipo!

Conceptos

Para comprender la función de eliminación de variantes de sombreado mediante scripts es importante conocer con precisión los distintos conceptos implicados.

  • Activo de sombreado: El código fuente del archivo completo con propiedades, sub-shader, pases y HLSL.
  • Fragmento de sombreador: El código de entrada HLSL con dependencias para una sola etapa de sombreado.
  • Etapa de sombreado: Una etapa específica en el pipeline de renderizado de la GPU, normalmente una etapa de sombreado de vértices y una etapa de sombreado de fragmentos.
  • Palabra clave de sombreado: Un identificador de preprocesador para ramas en tiempo de compilación a través de sombreadores.
  • Conjunto de palabras clave de sombreado: Un conjunto específico de palabras clave de sombreado que identifican una ruta de código concreta.
  • Variante de sombreado: El código de sombreado específico de la plataforma generado por el compilador de sombreado de Unity, para una única etapa de sombreado para un nivel gráfico específico, paso, conjunto de palabras clave de sombreado, etc.
  • Sombreador Uber: Una fuente de sombreado que puede producir muchas variantes de sombreado.

En Unity, los sombreadores uber se gestionan mediante sombreadores secundarios, pases y tipos de sombreadores de ShaderLab, así como mediante las directivas de preprocesador#pragma multi_compile y #pragma shader_feature.

Recuento del número de variantes de sombreado generadas

Para utilizar la eliminación de variantes de sombreado mediante scripts, es necesario comprender claramente qué es una variante de sombreado y cómo genera las variantes de sombreado el proceso de creación de sombreadores. El número de variantes de sombreado generadas es directamente proporcional al tiempo de construcción y al tamaño de los datos de la variante de sombreado Player. Una variante de sombreado es una salida de la tubería de construcción de sombreado.

Las palabras clave de sombreado son uno de los elementos que provocan la generación de variantes de sombreado. Un uso desconsiderado de las palabras clave de los sombreadores puede conducir rápidamente a una explosión del número de variantes de los sombreadores y, por tanto, a un tiempo de compilación extremadamente largo.

Para ver cómo se generan las variantes de sombreado, el siguiente sombreador sencillo cuenta cuántas variantes de sombreado produce:

Shader "ShaderVariantsStripping"
{
	SubShader
	{
		Pass
		{
			Name "ShaderVariantsStripping/Pass"

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile COLOR_ORANGE COLOR_VIOLET COLOR_GREEN COLOR_GRAY
			#pragma multi_compile OP_ADD OP_MUL OP_SUB

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = v.uv;
				return o;
			}

			fixed4 get_color()
			{
				#if defined(COLOR_ORANGE)
					return fixed4(1.0, 0.5, 0.0, 1.0);
				#elif defined(COLOR_VIOLET)
					return fixed4(0.8, 0.2, 0.8, 1.0);
				#elif defined(COLOR_GREEN)
					return fixed4(0.5, 0.9, 0.3, 1.0);
				#elif defined(COLOR_GRAY)
					return fixed4(0.5, 0.9, 0.3, 1.0);
				#else
					#error "Unknown 'color' keyword"
				#endif
			}

			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 diffuse = tex2D(_MainTex, i.uv);

				fixed4 color = get_color();

				#if defined(OP_ADD)
					return diffuse + color;
				#elif defined(OP_MUL)
					return diffuse * color;
				#elif defined(OP_SUB)
					return diffuse - color;
				#else
					#error "Unknown 'op' keyword"
				#endif
			}
			ENDCG
		}
	}
}

El número total de variantes de sombreado en un proyecto es determinista y viene dado por la siguiente ecuación:

Ecuación

El siguiente ejemplo trivial de ShaderVariantStripping aporta claridad a esta ecuación. Es un sombreador único que simplifica la ecuación de la siguiente manera:

Ecuación

Del mismo modo, este sombreador tiene un único sombreador secundario y un único pase, lo que simplifica aún más la ecuación en:

Ecuación

Las palabras clave de la ecuación se refieren tanto a las palabras clave de la plataforma como a las del shader. Un nivel gráfico es una combinación específica de conjunto de palabras clave de plataforma.

El ShaderVariantStripping/Pass tiene dos directivas de compilación múltiple. La primera directiva define 4 palabras clave (COLOR_ORANGE, COLOR_VIOLET, COLOR_GREEN, COLOR_GRAY) y la segunda directiva define 3 palabras clave (OP_ADD, OP_MUL, OP_SUB). Por último, el pase define 2 etapas de sombreado: una etapa de sombreado de vértices y una etapa de sombreado de fragmentos.

Este total de variantes de sombreado se da para una única API de gráficos compatible. Sin embargo, para cada API gráfica soportada en el proyecto, necesitamos un conjunto dedicado de variantes de sombreadores. Por ejemplo, si construimos un reproductor Android que admita tanto OpenGL ES 3 como Vulkan, necesitaremos dos conjuntos de variantes de sombreadores. Como resultado, el tiempo de creación del reproductor y el tamaño de los datos del sombreador son directamente proporcionales al número de API gráficas compatibles.

Canal de construcción de sombreadores

El pipeline de compilación de shaders en Unity es una caja negra en la que cada shader del proyecto se analiza para extraer fragmentos de shader antes de recoger las instrucciones de preprocesamiento de variantes, como multi_compile y shader_feature. Esto produce una lista de parámetros de compilación, uno por cada variante de sombreado.

Estos parámetros de compilación incluyen el fragmento del shader, el nivel gráfico, el tipo de shader, el conjunto de palabras clave del shader, el tipo y el nombre del pase. Cada uno de los parámetros de compilación establecidos se utiliza para producir una única variante de sombreado.

En consecuencia, Unity ejecuta un pase automático de eliminación de variantes de sombreado basado en dos heurísticas. En primer lugar, el despojamiento se basa en la configuración del proyecto, por ejemplo, si se desactiva el soporte de realidad virtual, las variantes de sombreado VR se despojan sistemáticamente. En segundo lugar, el stripping automático se basa en la configuración de la sección Shader Stripping de la configuración gráfica.

Opciones de eliminación automática de variantes de sombreado en GraphicsSettings.
Opciones de eliminación automática de variantes de sombreado en GraphicsSettings.

La eliminación automática de variantes de sombreado se basa en restricciones de tiempo de compilación. Unity no puede seleccionar automáticamente sólo las variantes de sombreado necesarias en tiempo de compilación porque esas variantes de sombreado dependen de la ejecución de C# en tiempo de ejecución. Por ejemplo, si un script C# añade una luz puntual pero no había luces puntuales en el momento de la compilación, entonces no hay forma de que el conducto de compilación de sombreadores averigüe que el Jugador necesitaría una variante de sombreador que haga sombreado de luz puntual.

Aquí tiene una lista de variantes de sombreado con palabras clave activadas que se eliminan automáticamente:

Modos de mapa de luz: LIGHTMAP_ON, DIRLIGHTMAP_COMBINED, DYNAMICLIGHTMAP_ON, LIGHTMAP_SHADOW_MIXING, SHADOWS_SHADOWMASK

Modos de niebla: FOG_LINEAR, FOG_EXP, FOG_EXP2

Variantes de instanciación: INSTANCING_ON

Además, cuando se desactiva la compatibilidad con la Realidad Virtual, se eliminan las variantes de sombreado con las siguientes palabras clave incorporadas activadas:

STEREO_INSTANCING_ON, STEREO_MULTIVIEW_ON, STEREO_CUBEMAP_RENDER_ON, UNITY_SINGLE_PASS_STEREO

Cuando se realiza el despojamiento automático, la canalización de compilación de sombreadores utiliza los conjuntos de parámetros de compilación restantes para programar la compilación de variantes de sombreadores en paralelo, lanzando tantas compilaciones simultáneas como hilos de núcleo de CPU tenga la plataforma.

He aquí una representación visual de ese proceso:

Arquitectura de canalización de sombreado con integración de scripts de eliminación de variantes de sombreado en naranja.
Arquitectura de canalización de sombreado con integración de scripts de eliminación de variantes de sombreado en naranja.

En la versión beta de Unity 2018.2, la arquitectura del conducto de sombreado introduce una nueva etapa justo antes de la programación de la compilación de variantes de sombreado, que permite a los usuarios controlar la compilación de variantes de sombreado. Esta nueva etapa se expone mediante retrollamadas C# al código de usuario, y cada retrollamada se ejecuta por fragmento de sombreado.

API de eliminación de variantes de sombreado mediante scripts

A modo de ejemplo, el siguiente script permite eliminar todas las variantes de sombreado que estarían asociadas a una configuración "DEBUG", identificada por una palabra clave "DEBUG" utilizada en la compilación de Player de desarrollo.

using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;

// Simple example of stripping of a debug build configuration
class ShaderDebugBuildProcessor : IPreprocessShaders
{
    ShaderKeyword m_KeywordDebug;

    public ShaderDebugBuildProcessor()
    {
        m_KeywordDebug = new ShaderKeyword("DEBUG");
    }

    // Multiple callback may be implemented.
    // The first one executed is the one where callbackOrder is returning the smallest number.
    public int callbackOrder { get { return 0; } }

    public void OnProcessShader(
        Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> shaderCompilerData)
    {
        // In development, don't strip debug variants
        if (EditorUserBuildSettings.development)
            return;

        for (int i = 0; i < shaderCompilerData.Count; ++i)
        {
            if (shaderCompilerData[i].shaderKeywordSet.IsEnabled(m_KeywordDebug))
            {
                shaderCompilerData.RemoveAt(i);
                --i;
            }
        }
    }
}


OnProcessShader se llama justo antes de la programación de la compilación de la variante del shader.

Cada combinación de una instancia Shader, una instancia ShaderSnippetData y una instancia ShaderCompilerData es un identificador de una única variante de sombreado que producirá el compilador de sombreado. Para eliminar esa variante del shader, sólo tenemos que eliminarla de la lista ShaderCompilerData.

Todas y cada una de las variantes de sombreado que el compilador de sombreado debe generar aparecerán en esta llamada de retorno. Cuando trabaje en la creación de scripts para la eliminación de variantes de sombreado, primero debe averiguar qué variantes es necesario eliminar, porque no son útiles para el proyecto.

Resultados
Eliminación de variantes de sombreado para un canal de renderizado

Un caso de uso para la eliminación de variantes de sombreado mediante scripts es la eliminación sistemática de variantes de sombreado no válidas de un canal de renderizado debido a las diversas combinaciones de palabras clave de sombreado.

Un script de eliminación de variantes de sombreado incluido en el canal de renderizado HD le permite reducir sistemáticamente el tiempo de construcción y el tamaño de un proyecto utilizando el canal de renderizado HD. Este script se aplica a los siguientes sombreadores:

HDRenderPipeline/Lit
HDRenderPipeline/LitTessellation
HDRenderPipeline/LayeredLit
HDRenderPipeline/LayeredLitTessellation

El script produce los siguientes resultados:

                                 Unstripped    Stripped 
Player Data Shader Variant Count 24350 (100%)  12122 (49.8%) 
Player Data Size on disk         511 MB        151 MB 
Player Build Time                4864 seconds  1356 seconds
creenshot de la demo de fotogrametría de Fontainebleau utilizando el HD Render Pipeline a partir de la resolución estándar de PlayStation 4 1920x1080.
creenshot de la demo de fotogrametría de Fontainebleau utilizando el HD Render Pipeline a partir de la resolución estándar de PlayStation 4 1920x1080.

Además, el canal de renderizado ligero para Unity 2018.2 tiene una interfaz de usuario para alimentar automáticamente un script de eliminación que puede eliminar automáticamente hasta el 98% de las variantes de sombreado, lo que esperamos que sea especialmente valioso para proyectos móviles.

Eliminación de variantes de sombreado para un proyecto

Otro caso de uso es un script para eliminar todas las funciones de renderizado de una tubería de renderizado que no se utilicen para un proyecto específico. Utilizando una demostración de prueba interna para la tubería de renderizado Lightweight, obtuvimos los siguientes resultados para todo el proyecto:

                                 Unstripped  Stripped 
Player Data Shader Variant Count 31080       7056 
Player Data Size on disk         121         116 
Player Build Time                839 seconds 286 seconds

Como podemos ver, el uso de scripts de eliminación de variantes de sombreado puede dar lugar a resultados significativos y con más trabajo en el script de eliminación podríamos llegar aún más lejos.

Captura de pantalla de una demostración de canalización ligera.
Captura de pantalla de una demostración de canalización ligera.
Consejos para escribir código de eliminación de variantes de sombreado
Mejora del diseño del código de sombreado

Un proyecto puede entrar rápidamente en una explosión de recuento de variantes de shaders, lo que conduce a un tiempo de compilación insostenible y al tamaño de los datos del reproductor. La eliminación de sombreadores mediante scripts ayuda a solucionar este problema, pero debería reevaluar cómo está utilizando las palabras clave de los sombreadores para generar variantes de sombreadores más relevantes. Podemos confiar en el #pragma skip_variants para comprobar las palabras clave no utilizadas en el editor.

Por ejemplo, en ShaderStripping/Color Shader las directivas de preprocesamiento se declaran con el siguiente código:

#pragma multi_compile COLOR_ORANGE COLOR_VIOLET COLOR_GREEN COLOR_GRAY // color keywords
#pragma multi_compile OP_ADD OP_MUL OP_SUB // operator keywords


Este enfoque implica que se generarán todas las combinaciones de palabras clave de color y palabras clave de operador.

Supongamos que queremos renderizar la siguiente escena:

COLOR_ORANGE + OP_ADD, COLOR_VIOLET + OP_MUL, COLOR_GREEN + OP_MUL.
COLOR_ORANGE + OP_ADD, COLOR_VIOLET + OP_MUL, COLOR_GREEN + OP_MUL.

En primer lugar, debemos asegurarnos de que cada palabra clave sea realmente útil. En esta escena COLOR_GRAY y OP_SUB no se utilizan nunca. Si podemos garantizar que estas palabras clave no se utilizan nunca, entonces debemos eliminarlas.

En segundo lugar, debemos combinar palabras clave que produzcan efectivamente una única ruta de código. En este ejemplo, la operación "añadir" se utiliza siempre con el color "naranja" exclusivamente. Así que podemos combinarlas en una sola palabra clave y refactorizar el código como se muestra a continuación.

#pragma multi_compile ADD_COLOR_ORANGE MUL_COLOR_VIOLET MUL_COLOR_GREEN

#if defined(ADD_COLOR_ORANGE)
	#define COLOR_ORANGE
	#define OP_ADD
#elif defined(MUL_COLOR_VIOLET)
	#define COLOR_VIOLET
	#define OP_MUL
#elif defined(MUL_COLOR_GREEN)
	#define COLOR_GREEN
	#define OP_MUL
#endif


Por supuesto, no siempre es posible refactorizar las palabras clave. En estos casos, la eliminación de variantes de sombreado mediante scripts es una herramienta muy valiosa.

Uso de callbackOrder para eliminar variantes de sombreado en varios pasos

Para cada fragmento, se ejecutan todos los scripts de eliminación de variantes de sombreado. Podemos ordenar la ejecución de los scripts ordenando el valor devuelto por la función miembro callbackOrder. La cadena de construcción del sombreador ejecutará las retrollamadas en orden creciente de callbackOrder, de modo que la más baja primero y la más alta al final.

Un caso de uso para utilizar múltiples scripts de eliminación de sombreado es separar los scripts por propósito. Por ejemplo:

  • Guión 1: Elimina sistemáticamente todas las variantes de sombreadores con rutas de código no válidas.
  • Guión 2: Elimina todas las variantes del shader de depuración.
Variantes
  • Guión 3: Elimina todas las variantes de sombreadores de la base de código que no son necesarias para el proyecto actual.
  • Guión 4: Registra las variantes de sombreado restantes y las despoja todas para acelerar el tiempo de iteración en los scripts de despojo.
Proceso para escribir un script de eliminación de variantes de sombreado

La eliminación de variantes de sombreado es extremadamente potente, pero requiere mucho trabajo para lograr buenos resultados.

En la vista Proyecto, filtre por todos los sombreadores.

Seleccione un sombreador y, en el Inspector, haga clic en Mostrar para abrir la lista de palabras clave / variantes de ese sombreador. Habrá una lista de palabras clave que se incluirán siempre en la compilación.

Asegúrese de que conoce las características gráficas específicas que utiliza el proyecto.

Compruebe si las palabras clave se utilizan en todas las etapas de sombreado. Sólo es necesaria una variante para las etapas que no utilicen estas palabras clave.

Elimine las variantes de sombreado en el script.

Verifique los visuales en la compilación.

Repita los pasos 2 - 6 para cada sombreador.

Descargar el proyecto de muestra

El proyecto de ejemplo utilizado para ilustrar esta entrada del blog puede descargarse aquí. Requiere Unity 2018.2.0b1.

Más información sobre la optimización del tamaño del despliegue binario en Unite Berlín

Venga a la charla de Jonas Echterhoff del 21 de junio y conozca todas las nuevas herramientas que le dan más control sobre lo que acaba en su construcción.