Si el ejemplo anterior trataba sobre la complejidad geométrica de una escena, en éste se va a explicar un método para reducir el trabajo que tiene que realizar el raster a la hora de aplicar los Pixel Shaders.
Normalmente, el bucle de dibujado de una escena consiste en recorrer todos los objetos (si son completamente opacos no hace falta ordenarlos de más lejano a más cercano) y ejecutar las instrucciones de dibujado de cada uno. La GPU recibe todos los datos, descompone las formas complejas (rectángulos, triangle strips, triangle fans) en triángulos sencillos y ejecuta los siguientes pasos por cada uno:
Proceso de raster, simplificado.
Por lo tanto, en el mismo momento en que se determina que un triángulo o vértice es visible (pasar el Z-Test), se procede a actualizar su profundidad en el Z-Buffer (sobrescribiendo los valores antiguos) y se rasterizan todos los píxeles afectados por la nueva geometría.
Z-Buffer (también llamado Depth Buffer) con la profundidad de la escena.
Pero esta implementación, aunque es completamente correcta y lógica, tiene un punto débil: en una escena ordenada de más lejano a más cercano (o, si no lo está, también pueden darse las condiciones necesarias desde determinada posición y ángulo), cada objeto dibujado superpone al anterior; de esta manera, todos los objetos pasarán el Z-Test y serán enviados al raster. Si los Pixel Shader de los objetos tienen un número elevado de instrucciones, se estará desaprovechando una buena parte del trabajo de la GPU en dibujar píxeles que más tarde serán ocluidos; para evitar esta pérdida de rendimiento, vamos a implementar una técnica llamada Z-Prepass.
El Z-Prepass es un algoritmo muy sencillo, en el que se dibuja toda la escena dos veces; aún teniendo el doble de coste geométrico, el ahorro de trabajo en los Pixel Shader suele compensar ésta desventaja. Los pasos a seguir son los siguientes:
- Paso previo:
- Activar el Z-Test (RenderState.DepthBufferEnable = true).
- Z-Prepass:
- Activar escritura en Depth Buffer (RenderState.DepthBufferWriteEnable = true).
- Desactivar escritura en canales de color (RenderState.ColorWriteChannels = ColorWriteChannels.None).
- Dibujar escena con el shader de Z-Prepass.
- Escena final:
- Reactivar escritura en canales de color (RenderState.ColorWriteChannels = ColorWriteChannels.All).
- Desactivar escritura en Depth Buffer (RenderState.DepthBufferWriteEnable = false).
- Dibujar escena con los shaders correspondientes.
Los cambios de estado de ColorWriteChannels no son explícitamente obligatorios, pero ayudan a ganar algo de rendimiento; debido a que la única funcionalidad que nos interesa del shader especial de Z-Prepass es la transformación de los vértices y que la GPU rellene el Depth Buffer, no nos importa descartar la información de color pues no le vamos a dar utilidad ninguna.
Así pues, contando con un Z-Buffer pregenerado, en el segundo pase de geometría, y al estar desactivada la escritura (sólo se comprueban los valores), sólo se rasterizan los píxeles estrictamente visibles, evitando el redibujado de partes ocultas. Además, existe hardware (como la XBOX 360 y algunos modelos de tarjetas gráficas) optimizado para ejecutar un Z-Prepass con la mayor eficiencia posible.
Por último, queda aclarar que el Z-Prepass no es la panacea ni una solución universal; su mayor ventaja es aplicarlo a escenas en las que el trabajo de los Pixel Shaders puede ralentizar el proceso de render en general, debido a que ejecutan un gran número de instrucciones (normal mapping, übershader, sombras con PCF). En los peores casos se puede llegar incluso a perder rendimiento, debido a que el coste de dibujar la geometría dos veces es mayor que el de rasterizar toda la escena.