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)

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