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:
Render Correcto
Render 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.