Occlusion Culling (IV de IV)

************************************************************************************************

Back-face culling

Frustum Culling

Occlusion Culling

Optimizaciones para el Occlusion Culling

************************************************************************************************

En el post anterior explicamos los principios básicos del occlusión culling, y este último post lo dedicaremos a la implementación de dicha técnica de forma eficiente. 

Recordemos rápidamente en que consistía el occlusión culling. El objectivo es no enviar a la tarjeta gráfica aquellos objetos que van a ser eclipsados y para ello renderizabamos los ObjectBounds de los objetos de nuestra escena ordenados de más cercanos a más lejanos. Tras cada dibujado le preguntamos a la tarjeta gráfica mediante occlusion query cuantos pixeles se han renderizado del ObjectBounds si este es igual 0 no enviaremos el “objeto complejo” a la GPU.

Un ejemplo con imágenes sería:

image

Renderizamos los ObjectBounds de delante hacia atrás.

image

En dicha iteración pintamos un ObjectBound que quedará por detrás de los ya dibujados

image

El resultado de la occlusion query para dicho ObjectBound serán 0 pixeles, de forma que el objeto “complejo” que tenía por Boundingbox este último cubo será descartado del render.

 

El primer problema que nos encontramos es que la CPU debe esperar a que la GPU le devuelva un resultado, y como ya sabemos la transferencia entre CPU y GPU es muy costosa. En el algoritmo original esta espera la realizamos para cada ObjectBound:

 

image

 

Como se puede apreciar en la imagen tanto la CPU como la GPU está ociosas durante ciertos periodos de tiempo, y mientras una trabaja la otra espera.

Sumado a esto tenemos el problema de la pérdida de paralelismo, es decir la CPU y GPU suelen trabajar en paralelo para conseguir un mayor rendimiento (cuando la CPU termina de preparar un frame se lo envía a la GPU para que empiece a renderizarlo y mientras tanto la CPU empieza a preparar el frame siguiente).

Afortunadamente las gráficas de hoy dan solución a este problema, permitiéndonos almacenar el resultado de varias occlusion query en la GPU. Esto significa que las occlusion queries pueden ser batched, podemos lanzar varias encadenadas y luego ir recogiendo los resultados de forma que los tiempos de espera quedan enmascarados. Es importante resaltar que algunas GPU tienen limitado el número de resultados que pueden almacenar, por lo que en dichos casos tendremos que lanzar las queries en grupos del número máximo soportado.

 

image

 

En este gráfico se puede observar visualmente la mejora de rendimiento tras usar el batching de queries.

 

En el gráfico anterior vimos una mejora notable en el paralelismo de la CPU y GPU, usando dicha técnica eliminamos (enmascaramos) la mayoría de la esperas producidas a nivel de GPU, pero aún tenemos un “parón” en GPU ya que tenemos que esperar el resultado de la última query lanzada.

Para evitar esta latencia podemos aplicar la siguiente idea, recoger el resultado de las últimas queries en el frame siguiente.

 

image

 

De esta forma eliminamos esa última espera que nos quedaba a nivel de GPU, (en la CPU es normal e importante que nos queden espacios ociosos ya que este se encarga también de actualizar la escena, lo que puede implicar IA, Física…)

 

Hay más temas a tener en cuenta, como por ejemplo:

– Los ObjectBounds deben ajustarse lo mejor posible a los objetos para no tener problemas de oclusiones no deseadas.

fig29-01

En este caso estaríamos aproximando el árbol mediante un boundingBox, y tras aplicar occlusion culling el resultado sería que el coche no se dibujaría. Una solución a este problema pasaría por mejorar nuestra aproximación usando por ejemplo varios boundingbox (uno para el tronco y otro para la copa). El ajuste optimo para nuestro tipo de escena será el objetivo a conseguir para que nunca se produzcan errores de este tipo.

 

– Algo a tener también en cuenta sería el aplicar algoritmo de visibilidad espacial para poder descartar gran cantidad de objetos más rápidamente y reducir al mínimo el número de queries a lanzar.

image

Como se puede ver en este gráfico el 90% del tiempo lo empleamos en comprobar objetos para los cuales el resultado del test será negativo. Para evitar esto podríamos usar una jerarquía o una agrupaciones de objetos para que tras descartar el boundingbox de una agrupación de objetos, todos los objetos que contiene pueden ser descartados directamente.

 

Por último comentar que esta técnica hace posible que los juegos AAA (triple A) actuales tengan esa gran cantidad de detalles, y todos los engines de las grandes empresas lo usan. Incluso existe una empresa con gran éxito llamada Umbra Software la cual tiene como producto estrella un middleware dedicado a realizar esta técnica de forma eficiente en varias plataformas. Aquí os dejo unos vídeos en los que podéis ver algunos ejemplos de como funciona el occlusion booster que tienen implementado.

 

GPU accelerated occlusion culling

 

Umbra Software GDC 2009

Publicado por

jcanton

Javier is a Computer Science Engineer who has always had a passion for 3D graphics and software architecture. He learned C# almost at the same time as he learned to talk, and his first word was "base". He enjoys imparting talks about technology and has contributed in many important software and video game events. He has participated in multitude of software projects involving multitouch technologies, innovative user interfaces, augmented reality, and video games. Some of these projects were developed for companies such as Microsoft, Syderis, and nVidia. His professional achievements include being MVP for Windows DirectX and DirectX XNA for the last eight years, Xbox Ambassador, as well as Microsoft Student Partner and Microsoft Most Valuable Student during his years at college. Currently he works at Plainconcepts and he is the development team lead at WaveEngine project.

2 comentarios en “Occlusion Culling (IV de IV)”

  1. Hola Javi,

    Supongo que el orden en que apliques los distintos métodos de Occlusion Culling importa, no? Me refiero a que si por ejemplo primero aplicas Frustum Culling, te quitas todos los polys que haya fuera de la zona de visión; luego occlusion, para quedarte sólo con los que están delante y, ya por último, backface culling, no? De esta forma te ahorras aplicar los dos últimos a los polígonos de fuera de la visión, y el último a los polígonos tapados.

    ¿Es correcto?

    ¡Saludos!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *