[XNA] Frustum culling en XNA

Frustum culling es una técnica que en programación gráfica en 3D consiste en «pintar» por pantalla sólo los objetos que están siendo enfocados por la cámara, ahorrando tiempo de procesador, y por lo tanto consiguiendo como resultado framerates más altos.Hacer que los juegos corran rápido es crítico, y más en una plataforma de código manejado como es XNA, donde ahí tiene alguna desventaja respecto otros frameworks de desarrollo gráficos basados en C++, así pues, la aplicación de técnicas como esta, nos pueden sacar de más de un apuro. Además, como va siendo habitual, comentar que esta técnica no es específica de XNA, sinó que se usa en todos los frameworks de desarrollo gráfico en 3D.

Lo que está en la cámara tiene forma de pirámide (en realidad, de «trozo de pirámide», ya que tenremos que considerar que el nearplane normalmente será mayor que 0). Si no aplicamos la técnica de frustum culling, el procesador intentará «pintar» todos los modelos de nuestro juego, desperdiciando inútilmente el procesador. ¿Cómo se aplica la técnica de frustum culling en XNA? La verdad es que es mucho más fácil de lo que puede parecer!

Para hacer esta implementación tengo preparado un código que carga un montón de modelos en pantalla y los mueve de un lado para otro aleatóriamente. Además, en el ejemplo veremos por pantalla el número de FPS, y podremos activar / desactivar el Frustum Culling (pulsando la tecla Q se activa, y volviéndola a pulsar se desactiva). Así podremos ver en tiempo real la diferencia entre un modo y el otro. En mi sistema, consigo hasta 30fps más activando el frustum culling… Esto variará mucho de la máquina de cada uno. Si tienes un maquinón con 4 procesadores, quizá necesites añadir más modelos a la escena para percibir la diferencia. Sólo para que os hagáis una idea:


Frustum = true => 62 fps


Frustum = false => 39 fps!!!

Como podéis ver… la diferencia es abismal. Así pues, demostrada la importancia de esta técnica, vamos a ver su más que sencilla implementación.

La idea es simple, XNA tiene una clase llamada BoundingFrustum, que nos permite detectar si hay colisión entre el frustum de la cámara y un boundingsphere. Así pues necesitaremos asignar un boundingsphere a cada uno de los modelos que queremos que se vean afectados por esta técnica. Esto no es un problema, ya que el boundingsphere es un objeto ligero y rápido como el rayo 🙂 

Una forma de crear automáticamente un boundingsphere para vuestros modelos es este código:

foreach (ModelMesh mesh in defaultModel.Meshes)
{
    BoundingSphere meshBoundingSphere = mesh.BoundingSphere;

    BoundingSphere.CreateMerged(ref defaultModelBoundingSphere,
                                ref meshBoundingSphere,
                                out defaultModelBoundingSphere);
}

Este código habría que llamarlo después de cargar el modelo, y guardar en algún lugar el BoundingSphere de este.

Ahora toca modificar la típica clase «cámara» que todos tenemos en nuestros proyectos 3D de XNA (no pegaré todo el código aquí, podéis descargarlo de este artículo si queréis). Considerando que tenemos una clase Camera, añadiremos el atributo siguiente:

 public BoundingFrustum boundingFrustum;

Otra cosa en la clase cámara… cada vez que modifiquemos su matriz View (eso será cada vez que movamos la cámara), habrá que llamar al código siguiente:

boundingFrustum = new BoundingFrustum(view * projection);

Y con eso hemos acabado con la clase cámara… ahora vamos a ver qué hacemos con este BoundingFrustum.

La forma «típica» de pintar modelos 3D consiste en recorrer los Meshes del modelo, y por cada uno de ellos, sus BasicEffect, ir estableciendo las matrices de World, Vista y Proyección, para finalmente pintar el mesh. Para aplicar el Frustum Culling pondremos un poco de código extra, que es este:

            foreach (ModelMesh mesh in model.Meshes)
            {
                if(this.bounding != null && camera.frustumEnabled)
                {
                    ContainmentType currentContainmentType = ContainmentType.Disjoint;

                    currentContainmentType = camera.boundingFrustum.Contains(this.bounding);

                    if (currentContainmentType == ContainmentType.Disjoint)
                        continue;
                }

                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.PreferPerPixelLighting = true;

                    effect.World =
                        Matrix.CreateFromYawPitchRoll(
                        this.rotation.Y,
                        this.rotation.X,
                        this.rotation.Z) *
                        Matrix.CreateScale(this.scale) *
                        Matrix.CreateTranslation(this.position);

                    effect.Projection = camera.projection;
                    effect.View = camera.view;
                }
                mesh.Draw();
            }

Lo que hemos hecho es simple, básicamente nos aseguramos que el boundingsphere del mesh a pintar está dentro del frustum, en caso contrario, simplemente no lo pintamos, y pasamos al siguiente mesh. Finito!! Con una técnica así de fácil se pueden conseguir grandes resultados.

El código puede ser descargado en este enlace.

Finalmente, quiero agradecer a Tyler Gasve, por inspirarme y motivarme a escribir este artículo 🙂

 

Un comentario sobre “[XNA] Frustum culling en XNA”

Deja un comentario

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