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
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.
Primero que todo, revise esto introducción a cómo los shaders están escritos.
#pragma debug
)#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.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.
// 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;
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:
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:
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.
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:
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;
#ifndef LIGHTMAP_OFF
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;
#ifndef LIGHTMAP_OFF
float2 lmap : TEXCOORD1;
#endif
fixed3 spec : TEXCOORD2;
};
v2f vert (appdata_full v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord + frac(_ScrollingSpeed * _Time.y);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal);
float4 viewPos = mul(UNITY_MATRIX_MV, 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;
#ifndef LIGHTMAP_OFF
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
#ifndef LIGHTMAP_OFF
fixed3 lm = DecodeLightmap (tex2D(unity_Lightmap, i.lmap));
c.rgb *= lm;
#endif
return c;
}
ENDCG
}
}
}
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_OFF 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 = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord;
float3 worldNormal = mul((float3x3)_Object2World, v.normal);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_MV, v.normal);
float4 viewPos = mul(UNITY_MATRIX_MV, 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
}
}
}
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.
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.
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.
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.