Version: 2018.4
Métodos de Scripting y Gameplay
Optimizando Scripts

Optimizaciones de Renderizado

Esta sección introduce los tecnicismos de la optimización del renderizado. Muestra cómo bake los resultados de la iluminación para un mejor rendimiento, y cómo los desarrolladores de Shadowgun lograron texturas de alto contraste, con una iluminación baked, para hacer que su juego se viera genial. Si usted está mirando por información en general acerca de cómo un juego móvil optimizado se ve, revise la página Graphics Methods

Vuélvase Artístico!

A veces optimizar el renderizado en su juego requiere algo de trabajo sucio. Toda la estructura que Unity proporciona hace que sea fácil comenzar a trabajar rápido, pero si usted requiere una fidelidad en hardware limitado de alta categoría, hacer las cosas por usted mismo y soslayando la estructura es la manera de ir, proporciona que usted pueda introducir un cambio clave estructural que hace que las cosas sean más rápido. Sus herramientas de elección son scripts del editor, shaders simples, y una producción de arte pasado de moda.

Cómo entrar debajo del capote

Primero que todo, revise esto introducción a cómo los shaders están escritos.

  • Shaders Integrados
    • Examine el código fuente de los shaders integrados. A menudo, si usted quiere hacer que un nuevo shader haga algo diferente, usted lo puede lograr al tomar las partes de dos shaders ya existentes y colocarlos juntos.
  • Depuración del Surface Shader (#pragma debug)
    • Un CG Shader es generado de cada surface shader, y luego compilado completamente desde ahí. Si usted agrega #pragma debug a la parte arriba de su surface shader, cuando usted abra el shader compilado vía el inspector, usted verá el código CG intermediario. Esto es útil para inspeccionar cómo una parte especifica de un shader es en realidad calculado, y puede también ser de gran ayuda para agarrar ciertos aspectos que usted quiera de un surface shader y luego aplicarlos a un shader CG.
  • Shader incluye los archivos
    • Mucho código ayuda de shader es incluido en cada shader, y por lo usual no es utilizado, pero esto es por qué a veces usted verá shaders llamar funciones como WorldReflectionVector que no parecen estar definidos en cualquier parte. Unity tiene varios archivos incluidos de shaders integrados que contienen estas funciones de ayuda. Para encontrar una función en especifico, usted necesitará buscar a través de todos los diferentes includes.
    • Estos archivos son una parte importante de la estructura interna que Unity utiliza para que sea fácil escribir shaders; los archivos proporcionan cosas como sombras en tiempo real, diferentes tipos de luz, lightmaps, y soporte multi-plataforma.
  • Documentación de Hardware hardware and best practices for writing shaders. Tenga en cuenta que nosotros vamos a sugerir ser más agresivos con la precisión de los puntos flotantes sin embargo.

Shadowgun con profunidad

Shadowgun es un logro gráfico excelente considerando el hardware en el que corre. Mientras que la calidad del arte parece ser la clave al rompe-cabeza, hay un par te trucos para lograr tal calidad que los programadores pueden lograr para maximizar su potencial artístico.

En la página de Graphics Methods nosotros utilizamos la estatua de oro en Shadowgun como un ejemplo de una optimización genial; en vez de utilizar un mapa normal para darle a su estatua algo de definición solida, ellos simplemente baked el detalle de la iluminación a la textura. Aquí, nosotros vamos a mostrarle cómo y por qué usted debería utilizar una técnica similar en su propio juego.

Código Shader para En Tiempo Real vs Estatua Dorada Baked

// This is the pixel shader code for drawing normal-mapped
// specular highlights on static lightmapped geometry

// 5 texture reads, lots of instructions

SurfaceOutput o;

fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
fixed4 c = tex * _Color;
o.Albedo = c.rgb;

o.Gloss = tex.a;
o.Specular = _Shininess;

o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));

float3 worldRefl = WorldReflectionVector (IN, o.Normal);
fixed4 reflcol = texCUBE (_Cube, worldRefl);
reflcol *= tex.a;
o.Emission = reflcol.rgb * _ReflectColor.rgb;
o.Alpha = reflcol.a * _ReflectColor.a;

fixed atten = LIGHT_ATTENUATION(IN);
fixed4 c = 0;

half3 specColor;
fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);

const float3x3 unity_DirBasis = float3x3( 
float3( 0.81649658,  0.0, 0.57735028),
float3(-0.40824830,  0.70710679, 0.57735027),
float3(-0.40824829, -0.70710678, 0.57735026) );

half3 lm = DecodeLightmap (lmtex);

half3 scalePerBasisVector = DecodeLightmap (lmIndTex);

half3 normalInRnmBasis = saturate (mul (unity_DirBasis, o.Normal));
lm *= dot (normalInRnmBasis, scalePerBasisVector);

return half4(lm, 1);
// This is the pixel shader code for lighting which is
// baked into the texture

// 2 texture reads, very few instructions

fixed4 c = tex2D (_MainTex, i.uv.xy);   

c.xyz += texCUBE(_EnvTex,i.refl) * _ReflectionColor * c.a;

return c;

Reflective Bumped Specular Baked Light with Reflection

Render to Texel

La luz en tiempo real es en realidad de mayor calidad, pero el aumento de rendimiento de una versión baked es masivo. Entonces cómo se hace esto? Una herramienta del editor llamada Render to Texel fue creada para este propósito. Este bake la luz a la textura a través del siguiente proceso:

  • Transforme la tangente del espacio del normal map a espacio del mundo vía script.
  • Cree un mapa de posición del espacio del mundo vía script.
  • Renderice a Textura un pase de pantalla completa de una textura entera utilizando dos mapas previos, con un pase adicional por luz.
  • Promedie resultados de varios puntos panorámicos para producir algo que se ve plausibles con todo alrededor, o al menos de unos ángulos de vista comunes en su juego.

Esto es cómo las mejoras optimizaciones gráficas funciona. Ellos eluden toneladas de cálculos al realizarlas en el editor o antes de que el juego corra. En general, esto es lo que usted quiere hacer:

  • Cree algo que se ve genial no se preocupe por el rendimiento.
  • Utilice herramientas como el lightmapper de Unity y extensiones del editor como Render to Texel y Sprite Packer para bake a algo que sea muy simple de renderizar.
    • Hacer sus propias herramientas es la mejor manera de hacer esto, usted puede crear las herramientas perfectas que encajen a cualquier problema que su juego presente.
  • Cree shaders y scripts que modulan su output baked para darle algo de “brillo”; un efecto llamativo para crear una ilusión de una luz dinámica.

Concepto de Frecuencia de luz

Tal como el Bass (Bajo) y Treble (Agudos) de una pista de audio, las imágenes también tienen componentes de alta frecuencia y baja frecuencia, y cuando usted está renderizando, es mejor manejarlas de maneras diferentes, similar a cómo los estéreos utilizan subwoofers y tweeters para producir un sonido de cuerpo completo. Una manera de visualizar las diferentes frecuencias de una imagen es utilizar el filtro “High Pass” en Photoshop. Filters->Other->High Pass. Si usted ha hecho trabajo de audio antes, usted reconocerá el nombre High Pass. Esencialmente lo que hace es cortar todas las frecuencias menores que X, el parámetro que usted pase al filtro. Para imágenes, un Gaussian Blur (Desenfoque gaussiano) es equivalente al Low Pass.

Esto aplica a las gráficas en tiempo real ya que la frecuencia es una buena manera de separar cosas y determinar cómo manejar qué. Por ejemplo, en un entorno lightmapped, la imagen final es obtenida por lo compuesto del lightmap, el cual es baja frecuencia, y las texturas, que son de alta frecuencia. En Shadowgun, la luz de baja frecuencia es aplicada al personaje rápidamente con unos light probes (sondas de luz), la luz de alta frecuencia es falsificadaa través del uso de un shader bumpmapped simple con una dirección de luz arbitraría.

En general, al utilizar diferentes métodos para renderizar diferentes frecuencias de luz, por ejemplo, baked vs dynamic (dinámico), por objeto vs por nivel, por pixel vs por vértice, etc, usted puede crear cuerpos completos de imagen en hardware limitado. Opciones de estilo a parte, por lo general es una buena idea intentar tener una fuerte variación de colores o valores en altas y bajas frecuencias.

Frecuencia en Práctica: Descomposición de Shadowgun

  • Fila de arriba
    • Ultra-Low-Frequency Specular Vertex Light (Dynamic) | High Frequency Alpha Channel | Low Frequency Lightmap | High Frequency Albedo
  • Fila de la Mitad
    • Specular Vertex Light * Alpha | High Frequency Additive Details | Lightmap * Color Channel
  • Fondo
    • Final Sum

Tenga en cuenta: Usualmente estas descomposiciones se refieren a los pasos e un renderizador deferred, pero este no es el caso aquí. Todo esto es hecho en un solo pass. Estos son los dos shaders relevantes con los cuales esta composición se baso:

Lightmapped con Virtual Gloss Per-Vertex Additive

Shader "MADFINGER/Environment/Virtual Gloss Per-Vertex Additive (Supports Lightmap)" {
Properties {
    _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
    //_MainTexMipBias ("Base Sharpness", Range (-10, 10)) = 0.0
    _SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0)
    _SpecRange ("Specular Range", Float) = 20
    _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 1)
    _Shininess ("Shininess", Range (0.01, 1)) = 0.078125
    _ScrollingSpeed("Scrolling speed", Vector) = (0,0,0,0)
}

SubShader {
    Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
    LOD 100



    CGINCLUDE
    #include "UnityCG.cginc"
    sampler2D _MainTex;
    float4 _MainTex_ST;
    samplerCUBE _ReflTex;

    #ifdef LIGHTMAP_ON
    float4 unity_LightmapST;
    sampler2D unity_Lightmap;
    #endif

    //float _MainTexMipBias;
    float3 _SpecOffset;
    float _SpecRange;
    float3 _SpecColor;
    float _Shininess;
    float4 _ScrollingSpeed;

    struct v2f {
        float4 pos : SV_POSITION;
        float2 uv : TEXCOORD0;
        #ifdef LIGHTMAP_ON
        float2 lmap : TEXCOORD1;
        #endif
        fixed3 spec : TEXCOORD2;
    };


    v2f vert (appdata_full v)
    {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);

        o.uv = v.texcoord + frac(_ScrollingSpeed * _Time.y);

        float3 viewNormal = UnityObjectToViewPos(v.normal);
        float3 viewPos = UnityObjectToViewPos(v.vertex);
        float3 viewDir = float3(0,0,1);
        float3 viewLightPos = _SpecOffset * float3(1,1,-1);

        float3 dirToLight = viewPos - viewLightPos;

        float3 h = (viewDir + normalize(-dirToLight)) * 0.5;
        float atten = 1.0 - saturate(length(dirToLight) / _SpecRange);

        o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten;

        #ifdef LIGHTMAP_ON
        o.lmap = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
        #endif
        return o;
    }
    ENDCG


    Pass {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 c = tex2D (_MainTex, i.uv);

            fixed3 spec = i.spec.rgb * c.a;

            #if 1
            c.rgb += spec;
            #else           
            c.rgb = c.rgb + spec - c.rgb * spec;
            #endif

            #ifdef LIGHTMAP_ON
            fixed3 lm = DecodeLightmap (tex2D(unity_Lightmap, i.lmap));
            c.rgb *= lm;
            #endif

            return c;
        }
        ENDCG 
    }   
}
}

Lightprobes con Virtual Gloss Per-Vertex Additive

Shader "MADFINGER/Environment/Lightprobes with VirtualGloss Per-Vertex Additive" {
Properties {
    _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
    _SpecOffset ("Specular Offset from Camera", Vector) = (1, 10, 2, 0)
    _SpecRange ("Specular Range", Float) = 20
    _SpecColor ("Specular Color", Color) = (1, 1, 1, 1)
    _Shininess ("Shininess", Range (0.01, 1)) = 0.078125    
    _SHLightingScale("LightProbe influence scale",float) = 1
}

SubShader {
    Tags { "RenderType"="Opaque" "LightMode"="ForwardBase"}
    LOD 100



    CGINCLUDE
    #pragma multi_compile _ LIGHTMAP_ON
    #include "UnityCG.cginc"
    sampler2D _MainTex;
    float4 _MainTex_ST;


    float3 _SpecOffset;
    float _SpecRange;
    float3 _SpecColor;
    float _Shininess;
    float _SHLightingScale;

    struct v2f {
        float4 pos : SV_POSITION;
        float2 uv : TEXCOORD0;
        float3 refl : TEXCOORD1;
        fixed3 spec : TEXCOORD3;
        fixed3 SHLighting: TEXCOORD4;
    };


    v2f vert (appdata_full v)
    {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.uv = v.texcoord;

        float3 worldNormal = UnityObjectToWorldDir(v.normal);       
        float3 viewNormal = UnityObjectToViewPos(v.normal);
        float4 viewPos = UnityObjectToViewPos(v.vertex);
        float3 viewDir = float3(0,0,1);
        float3 viewLightPos = _SpecOffset * float3(1,1,-1);

        float3 dirToLight = viewPos.xyz - viewLightPos;

        float3 h = (viewDir + normalize(-dirToLight)) * 0.5;
        float atten = 1.0 - saturate(length(dirToLight) / _SpecRange);

        o.spec = _SpecColor * pow(saturate(dot(viewNormal, normalize(h))), _Shininess * 128) * 2 * atten;

        o.SHLighting    = ShadeSH9(float4(worldNormal,1)) * _SHLightingScale;

        return o;
    }
    ENDCG


    Pass {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 c    = tex2D (_MainTex, i.uv);

            c.rgb *= i.SHLighting;
            c.rgb += i.spec.rgb * c.a;

            return c;
        }
        ENDCG 
    }   
}
}

Mejores Prácticas

Optimización de GPU: Alpha-Testing

Algunos GPUs, particularmente aquellos encontrado en dispositivos móviles, incurren en una sobre carga de rendimiento alta por alpha-testing (o uso de las operaciones discard y clip en shaders pixel). Usted debería remplazar los shaders alpha-test con alpha-blended si es posible- Dónde alpha-testing no puede ser evitado, usted debería mantener el número general de pixeles alpha-tested visibles a un mínimo.

Compresión de Textura iOS

Algunas imágenes, especialmente si se está utilizando la compresión de textura iOS/Android PVR, son propensos a defectos visuales en el canal alpha. En tales casos, usted podría necesitar ajustar los parámetros de compresión PVRT directamente en su software de imágenes. Usted puede hacer eso al instalar el PVR export plugin o utilizando [PVRTexTool](http://www.imgtec.com/powervr/insider/powervr-pvrtextool.asp de Imagination Tech, los creadores del formato PVRTC. El archivo de la imagen comprimida resultante con una extensión .pvr será importada por el editor de Unity directamente y los parámetros de compresión serán preservados. Si texturas comprimidas PVRT no le dan una calidad visual suficientemente buena o usted necesita en especial una imagen nitida (como usted podría hacerlo para texturas GUI) entonces usted debería considerar utilizar texturas 16-bit más bien de 32-bit. Al hacer esto, usted va a reducir el ancho de banda de memoria y los requerimientos de almacenamiento por la mitad.

Compresión de Textura para Android

Todos los dispositivos Android con soporte para OpenGL ES 2.0 también soporta el formato de compresión ETC1; por lo tanto se alienta a que cuando se posible se utilice ETC1 como el formato de textura preferido.

Si se está apuntando a una arquitectura especifica de gráficas, tal como Nvidia Tegra o Qualcomm Snapdragon, puede valer la pena considerar utilizar los formatos de compresión de propiedad disponibles en esas arquitecturas. El mercado de Android también le permite un filtro basado en el formato de compresión de textura soportado, significando un archivo de distribución (.apk) con por ejemplo DXT compressed textures pueden prevenirse de ser descargados en un dispositivo que no los soporta.

Un ejercicio

Descargue Render to Texel. Bake iluminación a su modelo. Corra el filtro High Pass en el resultado en Photoshop. Edite el shader “Mobile/Cubemapped”, incluido en el paquete Render to Texel, para que los detalles de luz de baja frecuencia sean remplazados por una vertex light.

Métodos de Scripting y Gameplay
Optimizando Scripts