Alpha blending en XNA (III de IV)

Recordemos que al principio del todo presentamos un gran conjunto de parámetros que están relacionados con el Alpha dentro de la API de XNA:

GraphicsDevice.RenderState.AlphaBlendEnable = false;

GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;

GraphicsDevice.RenderState.BlendFactor = new Color(255, 255, 255, 255);

GraphicsDevice.RenderState.SourceBlend = Blend.One;

GraphicsDevice.RenderState.DestinationBlend = Blend.Zero;

 

GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = false;

GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;

GraphicsDevice.RenderState.AlphaSourceBlend = Blend.One;

GraphicsDevice.RenderState.AlphaDestinationBlend = Blend.Zero;

 

GraphicsDevice.RenderState.AlphaTestEnable = false;

GraphicsDevice.RenderState.AlphaFunction = CompareFunction.Always;

GraphicsDevice.RenderState.ReferenceAlpha = 0;

En esta ocasión nos centraremos en el segundo bloque, este está relacionado con el canal alpha del backbuffer y mucha gente los suele confundir con los del primer grupo. Lo más importante es que todos estos parámetros vienen desactivados por defecto, y seguirán así mientras no pongamos SeparateAlphaBlendEnable a true. En muchos sitios podemos ver como se asignan valores a AlphaSourceBlend y AlphaDestinationBlend sin haber activado SeparateAlphaBlendEnable por lo que esos cambios no tendrán ningún efecto. También hay que tener en cuenta que ciertas instrucciones de XNA modifican el Device.RenderState, como pueden ser la instrucción de SpriteBacth ó la de pass de un effect.

Pasemos a explicar cual es la finalidad de este grupo de instrucciones, cuando queremos pintar objetos translucidos en la escena y estamos realizando un render sobre el framebuffer, este a su vez es también una imagen y por lo tanto tiene sus canales R, G, B, y A. El canal Alpha del framebuffer suele usarse para guardar información extra sobre todo cuando se aplican efectos de postprocessing en los que el render final es una composición de varios renders previos.

Bien con SeparateAlphaBlendEnable = false a la hora de realizar el test de Alpha durante el render se usa la fórmula:

(Source * SourceBlend) (blendFunction) (Destination * DestinationBlend)

esta operación se realiza tanto para los canales RGB como para el canal alpha del framebuffer, sin embargo cuando configuramos SeparateAlphaBlendEnable = true podemos cambiar esto y hacer que se apliquen dos operaciones (una para los canales RGB y otra para el canal alpha).

Operación1 sobre canales RGB

(Source.RGB * SourceBlend) (blendFunction) (Destination.RGB * DestinationBlend)

Operación2 sobre el canal Alpha

(Source.A * AlphaSourceBlend) (AlphaBlendOperation) (Destination.A * AlphaDestinationBlend)

Veamos un ejemplo, utilizaremos la siguiente textura en PNG que contendrá tanto los canales RGB como el canal Alpha, aunque veámoslos por separado:

Canales RGB

Glass

Canal Alpha

Alpha

Vamos a aplicar una operación a los canales RGB con BlendFactor para hacer la vidriera semitranslucida, y por otra parte vamos a usar el canal Alpha de la textura y vamos a mezclarlo con el canal alpha de FrameBuffer. Para poder apreciar la diferencia en todos los canales del FrameBuffer usaremos el siguiente procedimiento,  renderizamos sobre un RenderTarget2D y posteriormente pintaremos la textura de este RenderTarget2D usando un SpriteBatch al cual le aplicaremos un Shader:

XNA Code:

//Pintamos sobre un RenderTarget2D

GraphicsDevice.SetRenderTarget(0, rt);

 

graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,

        Color.Black, 1.0f, 0);

 

//Background

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

spritebatch.Draw(background, Vector2.Zero, Color.White);

spritebatch.End();

 

GraphicsDevice.VertexDeclaration = vertexDeclaration;

 

GraphicsDevice.RenderState.CullMode = CullMode.None;

 

//Configuración del RenderState

GraphicsDevice.RenderState.AlphaBlendEnable = true;

GraphicsDevice.RenderState.BlendFactor = new Color(127, 127, 127);

GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;

GraphicsDevice.RenderState.SourceBlend = Blend.BlendFactor;

GraphicsDevice.RenderState.DestinationBlend = Blend.InverseBlendFactor;

 

GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = true;

GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;

GraphicsDevice.RenderState.AlphaSourceBlend = Blend.SourceAlpha;

GraphicsDevice.RenderState.AlphaDestinationBlend = Blend.Zero;

 

//Render

effect.Begin();

foreach (EffectPass pass in effect.CurrentTechnique.Passes)

{

    pass.Begin();

 

    effect.Parameters["World"].SetValue(Matrix.CreateRotationY(rotation));

    effect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(0, 0, -4), Vector3.Zero, Vector3.Up));

    effect.Parameters["Projection"].SetValue(Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, 1, 10000));

    effect.Parameters["DiffuseTexture"].SetValue(textures[index]);

 

    graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(

            PrimitiveType.TriangleList, boxData, 0, 2);

 

    pass.End();

}

effect.End();

 

//Ahora pintamos la imágen almacenada en el RenderTarget2D sobre un SpriteBatch

GraphicsDevice.SetRenderTarget(0, null);

 

spritebatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);

 

alphaEffect.Begin();

alphaEffect.CurrentTechnique.Passes[0].Begin();

alphaEffect.Parameters["Alpha"].SetValue(alpha);

 

spritebatch.Draw(rt.GetTexture(), Vector2.Zero, Color.White);

 

alphaEffect.CurrentTechnique.Passes[0].End();

alphaEffect.End();

 

spritebatch.End();

Shader

sampler ColorMapSampler;

bool Alpha;

 

float4 PixelShaderFunctionAlpha(float2 TexCoord : TEXCOORD0) : COLOR0

{

    if(Alpha)

    {

        float a = tex2D(ColorMapSampler, TexCoord).a;

        return float4(a, a, a, 1);

    }

    else

    {

        return tex2D(ColorMapSampler, TexCoord);

    }

}

 

technique Technique1

{

    pass Pass1

    {

        PixelShader = compile ps_2_0 PixelShaderFunctionAlpha();

    }

}

Este Shader nos permitirá mediante el valor de un parámetro “Alpha”, ver los canales RGB ó el canal alpha del resultado final.

 

Resultado en los canales RGB del FrameBuffer:

Capture2

 

Resultado en el canal alpha del FrameBuffer

Capture1

 

Bueno como veis si antes teníamos muchas posibilidades, ahora con SeparateAlphaBlendEnable estas se incrementan aún más, ya que AlphaSourceBlend y AlphaDestinationBlend pueden ser configurados con cualquiera de los valores Blend vistos anteriormente para SourceBlend y DestinationBlend, de igual modo AlphaBlendOperation puede tomar cualquiera de los valores que vimos para BlendFunction.

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

Alpha Blending en XNA (I de IV)

Alpha Blending en XNA (II de IV)

Alpha Blending en XNA (III de IV)

Alpha Blending en XNA (IV de IV)

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

Alpha blending en XNA (II de IV)

Recordemos la formula general usada para las transparecias:

(Source * SourceBlend) (blendFunction) (Destination * DestinationBlend)

 

En los dos ejemplos anteriores usamos como BlendFunction la operación suma que suele ser la más común, pero se pueden usar otras, la lista completa es:

Add     result = (Source * SourceBlend) + (Destination * DestinationBlend)

Max     result = max( (Source * SourceBlend), (Destination * DestinationBlend)

Min     result = min( (Source * SourceBlend), (Destination * DestinationBlend)

ReverseSubtract     result = (Destination * DestinationBlend) – (Source * SourceBlend)

Subtract     result = (Source * SourceBlend) – (Destination * DestionationBlend)

Para los parámetros SourceBlend y DestionationBlend que como vimos anteriormente nos indica el nivel de aportación a la mezcla del Source (lo que queremos pintar con transparencia) y del Destination (lo que ya está se ha pintado en el framebuffer, el fondo), y los configuramos para que usaran un valor constante de alpha para toda la textura usando el BlendFactor ó que usaran el canal alpha de la textura, pero también existen muchos otros valores, aquí os dejo la lista completa también:

Zero
Each component of the color is multiplied by (0, 0, 0, 0).

One
Each component of the color is multiplied by (1, 1, 1, 1).

SourceColor
Each component of the color is multiplied by the source color. This can be represented as (Rs, Gs, Bs, As), where R, G, B, and A respectively stand for the red, green, blue, and alpha source values.

InverseSourceColor
Each component of the color is multiplied by the inverse of the source color. This can be represented as (1 − Rs, 1 − Gs, 1 − Bs, 1 − As) where R, G, B, and A respectively stand for the red, green, blue, and alpha destination values.

SourceAlpha
Each component of the color is multiplied by the alpha value of the source. This can be represented as (As, As, As, As), where As is the alpha source value.

InverseSourceAlpha
Each component of the color is multiplied by the inverse of the alpha value of the source. This can be represented as (1 − As, 1 − As, 1 − As, 1 − As), where As is the alpha destination value.

DestinationAlpha
Each component of the color is multiplied by the alpha value of the destination. This can be represented as (Ad, Ad, Ad, Ad), where Ad is the destination alpha value.

InverseDestinationAlpha
Each component of the color is multiplied by the inverse of the alpha value of the destination. This can be represented as (1 − Ad, 1 − Ad, 1 − Ad, 1 − Ad), where Ad is the alpha destination value.

DestinationColor
Each component color is multiplied by the destination color. This can be represented as (Rd, Gd, Bd, Ad), where R, G, B, and A respectively stand for red, green, blue, and alpha destination values.

InverseDestinationColor
Each component of the color is multiplied by the inverse of the destination color. This can be represented as (1 − Rd, 1 − Gd, 1 − Bd, 1 − Ad), where Rd, Gd, Bd, and Ad respectively stand for the red, green, blue, and alpha destination values.

SourceAlphaSaturation
Each component of the color is multiplied by either the alpha of the source color, or the inverse of the alpha of the source color, whichever is greater. This can be represented as (f, f, f, 1), where f = min(A, 1 − Ad).

BothInverseSourceAlpha
(Win32 only) Each component of the source color is multiplied by the inverse of the alpha of the source color, and each component of the destination color is multiplied by the alpha of the source color. This can be represented as (1 − As, 1 − As, 1 − As, 1 − As), with a destination blend factor of (As, As, As, As); the destination blend selection is overridden. This blend mode is supported only for the SourceBlend render state.

BlendFactor
Each component of the color is multiplied by a constant set in BlendFactor.

InverseBlendFactor
Each component of the color is multiplied by the inverse of a constant set in BlendFactor. This blend mode is supported only if SupportsBlendFactor is true in the SourceBlendCapabilities or DestinationBlendCapabilities properties.

BothSourceAlpha
This mode is obsolete. The same effect can be achieved by setting the source and destination blend factors to SourceAlpha and InverseSourceAlpha in separate calls.

Ahora es cuando muchos os estaréis preguntando que para que tantos valores, pues como veis las posibles combinaciones son muchas y con todas ellas el resultado es algo diferente, que sean más o menos útiles ya es otra cosa XD.

Las combinaciones más usadas y conocidas son:

Alpha Blending     (source * Blend.SourceAlpha) + (destination * Blend.InvSourceAlpha)

Additive Blending     (source * Blend.One) + (destination * Blend.One)

Multiplicative Blending     (source * Blend.Zero) + (destination * Blend.SourceColor)

2X Multiplicative Blending     (source * Blend.DestinationColor) + (destination * Blend.SourceColor)

 

BlendModes

1. Alpha Blending, 2. Additive Blending, 3. Multiplicative Blending, 4. 2X Multiplicative Blending

También cabe destacar que existen diferentes formas de pensar en la transparencia esta que acabamos de ver podemos llamar la forma convencional en la mayoría de APIs.

Conventional Alpha Blending

Idea:

blend(source, destination) = (source.rgb * source.a) + (destination.rgb * (1 – source.a))

En XNA:

GraphicsDevice.RenderState.AlphaBlendEnable = true;

GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;

GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;

GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

Pero como podemos observar en esta situación los valores RGB y el alpha son independientes, de manera que sería posible tener un objeto totalmente transparente con valores de color en RGB, esto a un informático no le preocupa en exceso, pero no es un comportamiento muy real. Por ello existen otras formas de pensar en la transparencia como:

Premultiplied Alpha Blending

Idea:

blend(source, destination) = source.rgb + ( destination.rgb * (1 – source.a) )

En XNA:

GraphicsDevice.RenderState.AlphaBlendEnable = true;

GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;

GraphicsDevice.RenderState.SourceBlend = Blend.One;

GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

En este caso el alpha y el RGB están ligados y para que un objeto sea transparente debe tener el máximo de transparencia, y el color de RGB debe ser nulo (0,0,0). Si os fijáis la formula es la misma que la anterior solo que el source.rgb lo multiplicamos por 1, esto implica que si el objeto es translucido tendremos que realizar una conversión previa de las texturas para pasarlas a premultiplied format:

color.rgb *= color.a

Uno de los grandes motivos por el que se tiende a usar está técnica es porque se adapta mejor con DTX compression.

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

Alpha Blending en XNA (I de IV)

Alpha Blending en XNA (II de IV)

Alpha Blending en XNA (III de IV)

Alpha Blending en XNA (IV de IV)

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

Alpha blending en XNA (I de IV)

Alpha blending es la técnica con la cual podemos trabajar con transparencias tanto en 2D como en 3D, nos centraremos en pintar un plano con una textura y veremos diferentes formas de aplicar esta técnica.

Para el primer ejemplo usaremos una textura de vidriera:

Glass

la cual nos gustaría aplicar a un plano en 3D y que se viese translucido. Si simplemente la textura el resultado sería algo como:

Capture1

Aquí entran en juego todos los parámetros con los que podemos trabajar, estos los podemos dividir en 3 grupos y sus valores por defecto son:

 

GraphicsDevice.RenderState.AlphaBlendEnable = false;

GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;

GraphicsDevice.RenderState.BlendFactor = new Color(255, 255, 255, 255);

GraphicsDevice.RenderState.SourceBlend = Blend.One;

GraphicsDevice.RenderState.DestinationBlend = Blend.Zero;

 

GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = false;

GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;

GraphicsDevice.RenderState.AlphaSourceBlend = Blend.One;

GraphicsDevice.RenderState.AlphaDestinationBlend = Blend.Zero;

 

GraphicsDevice.RenderState.AlphaTestEnable = false;

GraphicsDevice.RenderState.AlphaFunction = CompareFunction.Always;

GraphicsDevice.RenderState.ReferenceAlpha = 0;

 

Todos estos son muchos parámetros y mucha gente termina confundiéndolos, para empezar nos centraremos solo en el primer grupo:

AlphaBlendEnable deberemos ponerlo a true si queremos activar el test de AlphaBlending.

BlendFunction, SourceBlend y DestinationBlend serán usados para determinar el color de cada pixel en el framebuffer tras la mezcla mediante la siguiente fórmula:

(Source * SourceBlend) (blendFunction) (Destination * DestinationBlend)

En este ejemplo concreto:

– Source representará los colores almacenados en la textura de vidriera.

– SourceBlend será el valor por el que multiplicaremos Source e indicará su contribución a la mezcla.

– BlendFunction representa una operación que puede ser suma, recta….

– Destination es el color que ya está en el framebaffer en nuestro caso la imagen de fondo con los árboles.

– DestinationBlend indicará la contribución del fondo a la mezcla.

Para irnos familiarizando con todos estos parámetros seguiremos con el ejemplo de la vidriera, nosotros lo que queríamos era que se viese translucida (con el mismo valor de translucidez por toda la superficie) para ello usaremos el parámetro BlendFactor que os ayudará a aplicar un valor constante de translucidez a toda la superficie, la manera de usarlo será configurar el valor de BlendFactor y posteriormente usar este desde SourceBlend y DestinationBlend, por ejemplo:

 

//Activamos el AlphaBlending

GraphicsDevice.RenderState.AlphaBlendEnable = true;

 

//Los valores de color están entre (0-255) con 127 le estamos indicando que queremos que sea un 50% translucida

GraphicsDevice.RenderState.BlendFactor = new Color(127, 127, 127);

 

//Utilizamores como operador la suma para la mezcla

GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;

 

//Multiplica los valores RGB de la vidriera por el valor de BlendFactor 

GraphicsDevice.RenderState.SourceBlend = Blend.BlendFactor;

 

//Multiplica los valores RGB del fondo por el valor inverso de Blend factor (1 - BlendFactor)

GraphicsDevice.RenderState.DestinationBlend = Blend.InverseBlendFactor;

 

El resultado:

Capture2

 

En este caso estábamos trabajando con una textura que no traía información en el canal Alpha ó este veían vacio (todo a 1), las texturas suelen almacenar 4 canales Red, Green, Blue, Alpha (RGBA).

Probemos ahora con una textura con canal alpha para lo cual necesitaremos un formato que sea capaz de almacenarlo por ejemplo PNG:

Canales RGBGlass 

Canal AlphacanalAlpha

 

El canal alpha al ser un solo canal, si lo presentamos en una imagen esta será en blanco y negro. En este caso podemos ver como la vidriera no tiene los mismos niveles de translucidez en toda su superficie, todo lo que esté más cercano al blanco será opaco, por lo que tanto el marco como el centro serán totalmente opacos (resultado de multiplicar RGB * 1).

Ahora ya el BlendFactor no nos sirve necesitamos que tanto SourceBlend como DestinationBlend reciban información del canal alpha de la textura, esto lo haremos de la siguiente forma:

//Activamos el AlphaBlending

GraphicsDevice.RenderState.AlphaBlendEnable = true;

 

//Utilizamores como operador la suma para la mezcla

GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;

 

//Multiplica los valores RGB de la vidriera por la información almacenada en el canal alpha de la textura

GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;

 

//Multiplica los valores RGB del fondo por el inverso de los valores multiplicados en el canal alpha de la textura (1 - alpha)

GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

 

El resultado es el siguiente:

Capture3

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

Alpha Blending en XNA (I de IV)

Alpha Blending en XNA (II de IV)

Alpha Blending en XNA (III de IV)

Alpha Blending en XNA (IV de IV)

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

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