Problemas con el ZBuffer en XNA

El zbuffer es el algoritmo que se utiliza a la hora de dibujar los objetos en pantalla para decidir que objetos están delante de otros, recibe este nombre ya que se realiza un test de las coordenadas z de los objetos para comparar la profundidad a la que se encuentran en el mundo 3D. Esto a muchos matemáticos o físicos les puede resultar extraño ya que están acostumbrados a que en sus sistema de referencia la z es el vector que apunta hacia arriba, en los gráficos 3D el eje z es el que “entra hacia dentro de la pantalla” para sus valores positivos. Mucha gente piensa que esto se debe a discusiones entre informáticos y matemáticos o físicos XD, pero no es cierto, simplemente se debe a que se intentan facilitar las operaciones que se tienen que realizar con la cámara para representar los modelos y por ellos la cámara en un mundo 3D siempre se pone “como mirando al suelo” perpendicular al plano XY.

Los problemas de Zbuffer pueden ser confundidos con problemas de flip de normales, suelen tener un aspecto como:

 

Capture

Render Correcto

Capture1Render con problema de Zbuffer

Como se puede apreciar en la segunda imagen, el algoritmo que decide que se debe pintar delante y que detrás está fallando, de todas formas para detectar este tipo de problemas también os podréis fijar en que si rotáis el modelo puede que desde algunos ángulos se vea correctamente y desde otros no.

Este es uno de los primeros problemas con los que se encuentran muchas de las personas que empiezan a trabajar con XNA, esto se debe generalmente a problemas en el código del render. XNA facilita muchas tareas y los que descubren esta tecnología después de haber trabajado con muchas otras librerías anteriores ven el “cielo abierto” y este tipo de problemas los saben detectar rápidamente y buscarle soluciones, el problema normalmente lo tienen los desarrolladores con menos experiencia que se adaptan rápidamente a la facilidad de trabajar con XNA pero cuando les surge un problema de este tipo no entienden el porque ocurre, muchos incluso piensan que el problema se debe al formato de modelos que usan ó a que no se ha exportado bien desde alguno de los programas de diseño con los que trabajan 3D Studio Max, Maya, SoftImage, blender, …

El problema suele aparecer cuando se intenta mezclar un render2D y un render3D al mismo tiempo usando para dibujar esto las clases SpriteBatch y BasicEffect que vienen por defecto con XNA, un posible ejemplo:

Matrix[] transforms = new Matrix[model.Bones.Count];

model.CopyAbsoluteBoneTransformsTo(transforms); 

 

localWorld = Matrix.Identity                

* Matrix.CreateRotationY(rotation.Y)                

* Matrix.CreateRotationX(rotation.X)                

* Matrix.CreateRotationZ(rotation.Z)                

* Matrix.CreateTranslation(position)                

* Matrix.CreateScale(scale); 

 

foreach (ModelMesh mesh in model.Meshes)

{    

    foreach (BasicEffect effect in mesh.Effects)    

    {        

        effect.EnableDefaultLighting();        

        effect.PreferPerPixelLighting = true;         

        effect.World = transforms[mesh.ParentBone.Index] * localWorld * camera.World;        

        effect.View = camera.View;        

        effect.Projection = camera.Projection;    

    }     

    mesh.Draw();

} 

 

spritebatch.Begin();

 

spritebatch.DrawString(font, "Test", Vector2.Zero, Color.White);

spritebatch.Draw(Texture, position, Color.White);

 

spritebatch.End(); 

 

base.Draw(gameTime);

Este es el render básico 3D que podemos encontrar en los tutoriales de XNA, en el que pintamos un modelo 3D usando BasicEffect, y en una segunda parte pintamos un texto y una textura en 2D con spritebatch, este código probará estos errores de ZBuffer de los que hemos hablado, una posible solución sería:

Cambiar la línea del spritebatch.Begin(); por:

spritebatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

Con esto le estamos indicando que realice el test de alfa para la textura y el texto, que envíe los elementos 2D directamente sin ordenarlos previamente y que salve el estado del Device y lo restaura justo después de realizar el dibujado 2D. La clave está en lo último en salvar el Device y el motivo es que spritebatch modifica la configuración del Device actual (la configuración con la que hemos pintado el modelo 3D y que viene por defecto) desactivando el Zbuffer y no restaurándolo después.

Lo importante es que aunque uséis esta solución entendáis porque soluciona el problema, guardar el estado del Device y restaurarlo en cada frame es algo más lento que si configuráramos directamente el Device con una configuración completa valida para todo aquello que queremos dibujar, o si solo cambiáramos ciertos parámetros del Device y no todos, de todas formas tampoco estamos hablando de unas grandes pérdidas de tiempo, aunque para obtener resultado más profesionales esto si que sería algo a estudiar.

Además del Zbuffer ¿qué otras cosas modifica el spritebatch de la configuración del Device que puedan darme problemas?, pues aquí os dejo una lista completa de las configuraciones que realiza el spritebatch sobre el Device:

GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;

GraphicsDevice.RenderState.DepthBufferEnable = false; 

 

GraphicsDevice.RenderState.AlphaBlendEnable = true;

GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;

GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;

GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = false; 

 

GraphicsDevice.RenderState.AlphaTestEnable = true;

GraphicsDevice.RenderState.AlphaFunction = CompareFunction.Greater;

GraphicsDevice.RenderState.ReferenceAlpha = 0; 

 

GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Clamp;

GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Clamp; 

GraphicsDevice.SamplerStates[0].MagFilter = TextureFilter.Linear;

GraphicsDevice.SamplerStates[0].MinFilter = TextureFilter.Linear;

GraphicsDevice.SamplerStates[0].MipFilter = TextureFilter.Linear; 

GraphicsDevice.SamplerStates[0].MipMapLevelOfDetailBias = 0.0f;

GraphicsDevice.SamplerStates[0].MaxMipLevel = 0; 

Además de esto también modifica los vértices, los índices, la declaración de vértices, e incluso las propiedades del Device para pixel and vertex shader.

Una solución más eficiente sería simplemente reactivar el zbuffer después del SpriteBatch.End(), de la siguiente forma:

GraphicsDevice.RenderState.DepthBufferEnable = true;

GraphicsDevice.RenderState.AlphaBlendEnable = false;

GraphicsDevice.RenderState.AlphaTestEnable = false;

Y eso fue todo XD.

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.

6 comentarios sobre “Problemas con el ZBuffer en XNA”

  1. Interesante post, me ha gustado especialmente la parte en la que se especifica que cambios hace exactamente el SpriteBatch, muy útil por si quieres restaurar el device a mano.

    Spear

  2. «…el problema normalmente lo tienen los desarrolladores con menos experiencia que se adaptan rápidamente a la facilidad de trabajar con XNA…» Aquí uno de esos principiantes, me adapte rápidamente hasta que empecé a descubrir el lado oscuro del 3D, ZBuffers, Shaders…
    Gracias por la info!!

  3. Yo tengo este mismo problema, tengo el xna 4.0 pero no acepta la linea del Spritebatch.Begin() como la pusiste, no me aparece esa sobrecarga de metodo ni reconoce la palabra SaveStateMode.
    Sera la version del framework??

    Ayuda!!!!! 😉

Deja un comentario

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