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.

Shaders en XNA

Hace unas semanas Jesús Bosch me lió para dar un webcast online (si Jesús fue así ahora no lo niegues XD), y la idea principal es que iba a ser un webcast orientado a desarrolladores de XNA por lo que todos tendrían ya unos ciertos conocimientos previos sobre esta tecnología.

La charla entonces la centré en una introducción al mundo de los shaders y a como poder hacer uso de ellos dentro de XNA, los shaders para que el que no sepa de que estoy hablando son mini programas que se escriben para ser ejecutados en la GPU (Graphics Processing Unit, el procesador de las tarjetas gráficas de nuestros ordenadores).

Estas tarjetas están especializadas en cálculos en coma flotante y matriciales a diferencia de las CPU que son procesadores de propósito general, por lo que el rendimiento de nuestros juegos y aplicaciones con XNA y Shaders puede verse incrementado enormemente. Es más para el que lo desconozca en XNA todo se pinta mediante Shaders, por eso para poder ejecutar XNA en cualquier ordenador se requiere una tarjeta que soporte Pixel and Vertex Shader 1.1 al menos, esto tampoco es una restricción muy grande para los días que corren, ya cualquier chip gráfico de los que llevan varios años integrando en las placas base como las típicas de Intel 950 ya soportan esto.

Este es el principal motivo por el cual el rendimiento 3D de aplicaciones XNA está tan cercano al de aplicaciones DirectX escritas en C++. Como actualmente el cálculo intensivo se desplaza a la GPU, para la CPU quedan otro tipo de cálculos como puede ser la lógica del juego ó la IA, y es esto lo que se escribe en C#.

Para conseguir esta transparencia dentro de XNA y que los usuarios que prueban esta tecnología puedan desarrollar juegos con un alto rendimiento gráfico sin tener que preocuparse de aprender un lenguaje de Shaders como el HLSL, el equipo de desarrollo de XNA a creado dos clases básicas que se usan para pintar en 2D y en 3D.

Para 2D tenemos el spriteBatch:

Imagen:

spriteBatch.Begin();

spriteBatch.Draw(Texture, Rectangle, Color.White);

spriteBatch.End();

Texto:
spriteBatch.Begin();

spriteBatch.DrawString(font, "Hola Mundo", Position, Color.Red);

spriteBatch.End();

 

al cual le indicamos si queremos pintar un texto ó una imagen y el se encarga de dibujarlo en pantalla sobre un plano que coloca frente a la cámara, todo esto lo hace mediante un shader y de forma transparente para nosotros, el código de dicho shader está liberado y podéis verlo aquí.

Para pintar modelos 3D han desarrollado el objeto BasicEffect:

foreach (ModelMesh mesh in MyModel)

{

    foreach (BasicEffect effect in mesh.Effects)

    {

        effect.EnableDefaultLighting();

        effect.World = world;

        effect.View = camera.view;

        effect.Projection = camera.projection;

    }

    mesh.Draw();

}

el cual también usa un shader internamente y del cual también podemos bichear su código fuente aquí.

A pesar de estas facilidades si realmente queremos sacarle partido a nuestras tarjetas gráficas lo mejor es escribir nuestros propios shaders, y este webcast sobre shaders es un punto por el que empezar a conocer todo ese mundo, (aviso que son 2 horas a un buen ritmo XD).

 

Ahora está disponible en Channel 9 Spain gracias a Alejandro Hidalgo, espero que os sea útil.

Multitouch

Bueno, año nuevo y estrenando blog gracias a Rodrigo Corral XD, haciendo un repaso del trabajo realizado en 2009 he decidido escribir un post sobre la tecnología multitouch la cual me ha consumido bastante tiempo del 2009.

Para mi todo empezo tras ver algunos vídeos de las primeras surperficies multitouch en internet, vídeos de Microsoft, Intel, Google, quedé encantado y alucinado con la riqueza visual de las aplicaciones, siempre pensé que algún día terminaría realizando algún trabajo con este tipo de tecnología.

Esta es una tecnología apasionante que está apunto de estallar y que generará mucho negocio, un día mi hermano me estuvo enseñando algunos vídeos de pequeños proyectos hechos por algunos geeks de internet y aquello volvió despertó mi interés sobre esta tecnología.

El primer contacto fue en 2008 con un proyecto para la universidad realizado junto a Juliet Moreiro y Sergio Escalada (mi novia y un compañero), en el cual intentamos realizar nuestra propia superficie multitactil creando tanto el software como el hardware (aunque este con elementos caseros).

Hardware:

Construir el hardware con elementos caseros fue lo que más costó, la idea básica queda aclarada en el siguiente gráfico:

Basado en la tecnología llamada «Diffused Illumnation» en la cual la idea principal es iluminar una superficie traslucida desde abajo con luz IR y usar una cámara IR para detectar solo la luz con dicha logitud de onda, al acercar por ejemplo una mano desde arriba a dicha superficie todo aquello que se situe más cerca reflejará dicha luz IR con mayor intensidad que lo que se encuentre a mayor distacia, es decir la llema de nuestros dedos será diferenciada del resto de la mano gracias a los niveles de intensidad reflejada.

Bien para construir esto lo primero que ibamos a necesitar es una caja, para lo que usamos cartón pulma que compramos en una papelería:

Después necesitamos una cámara IR, no cual parecía una tarea dificil hasta que descubrimos que cualquier webcam es capaz de detectar el IR solo que viene de fábrica con un filtro de IR para que la luz de dicha logitud de onda no llegue al sensor, tan simple como quitarle dicho filtro y ya llegaba la información IR al sensor, pero también la visible y nosotros queríamos que solo llegara la IR por lo que necesitamos un filtro de luz visible, la sorpresa fue descubrir que los negativos de fotos podían ser usados como filtros visibles, ya que dejan pasar el IR pero no el visible, bueno esto no es un filtro ideal pero para lo que ibamos a construir nos servía.

Tras trucar la webcam, ya solo faltaba iluminar la superfice con led infrarroja para lo que usamos un pequeño circuito de 20 Led alimentados por un transformador a 12 V:

Y el resultado final era el siguiente, visto sin la superficie traslucida, para lo cual compramos acetato también en una papelería:

(Una auténtica fricada XD)

Software:

Lo primero que necesitabamos era crear el «driver» para poder usar dicho dispositivo desde código, este «driver» consistía en un sistema que fuese capaz de ir capturando las imágenes de la cámara, aplicarle algunos filtros y realizar tracking sobre los puntos detectados, esa lista de puntos sería usada por nuestras aplicaciones. Para este sistema decidimos basarnos en una solución open source ya existen llamada TBeta y desarrollada por NUI Group, esta aplicación ya realiza dicha tarea y actua como servidor, enviando por un socket UDP dicha lista de puntos, aquí podéis ver una captura de la aplicación:

 

Bien pues ya solo necesitamos crear una aplicación multitouch que mediante un thread idependiente fuese obteniendo dicha información del socket abierto por TBeta y lo usara para simular las pulsaciones sobre los elementos de la interfaz. Para que dichas aplicaciones fuese muy vistosas decidemos usar XNA, aquí dejo un enlace del proyecto completo realizado con XNA y Visual Studio.

Estaba claro que con dicho hardware se obtenía la mitad de la sensación multitouch porque a pesar de que podías escalar y rotar fotografias, estas solo se veían desde la pantalla del ordenador ya que la superficie creada no mostraba ninguna imagen. Meses más tarde pude probar dicho software en un prototipo más elaborado y aquí tenéis un vídeo del resultado.

[View:http://www.youtube.com/watch?v=_jEK1SXKOUY:550:0]

Este trabajo me permitió entrar a colobar en el departamento de I+D+I de Syderis Technologies, en donde mi trabajo ha estado centrado en el diseño y creación de un SDK profesional para el desarrollo de aplicaciones multitactiles sobre un hardware propietario de la empresa, creando un framework multitactil basado en DirectX (aunque los desarrolladores no tienen que conocer nada de DirectX, sino que sus desarrollos se pueden apoyar sobre una librería de controles propia desarrollada ó sobre XNA si necesitan mayor control a nivel de gráficos), herramientas de reconocimiento de gestos, plantillas para visual studio 2008, lanzador de aplicaciones y un largo etc que está sujeto a un NDA por lo que tampoco puedo contar demasiado, pero si que os puedo mostrar un vídeo de prueba que he realizado sobre un prototipo profesional.

[View:http://www.youtube.com/watch?v=qH_ySl8F-KA:550:0]

[View:http://www.youtube.com/watch?v=PF2g-_i2r98:550:0]

 

Saludos