Pre-multiplied alpha is alpha blending done correctly. It involves changing your alpha blending states, and ensuring your texture colour channels (R, G, B) are pre-multiplied (hence the name) with the alpha channel (A).
Firstly the alpha blending operations will have to be changed:
- Set your source alpha blend to ONE (probably instead of SRCALPHA) – How solid our source texture appears depends on our alpha value alone (pre-multiplied colour channels at work here).
- Set your destination blend to INVSRCALPHA – The more solid our texture appears, the less light we should let through from behind
// Standard fare alpha texture operations Device.SetTextureStageState(0, TextureStage.AlphaArg1, TextureArgument.Texture); Device.SetTextureStageState(0, TextureStage.AlphaArg2, TextureArgument.Diffuse); Device.SetTextureStageState(0, TextureStage.AlphaOperation, TextureOperation.Modulate); // Important Device.SetRenderState(RenderState.AlphaBlendEnable, True); // New alpha blending render states Device.SetRenderState(RenderState.SourceBlend, Blend.One); Device.SetRenderState(RenderState.DestinationBlend, Blend.InverseSourceAlpha);
The next part is loading the texture data and calculating the new colour channel values – below is a C# snippet for reading a 32-bit bitmap and performing this calculation. We take a bitmap that has been loaded into memory, iterate over each pixel in the image, calculate our new R, G and B values and save them back into the bitmap. Using SetPixel and GetPixel would be far too slow so this example locks the bitmap data and accesses the colour values directly.
[StructLayout(LayoutKind.Sequential, Pack=1)]
struct RgbaColor
{
// Huh?! turns out PixelFormat.Format32bppArgb should
// actually be called Format32bppBgra.
public byte Blue;
public byte Green;
public byte Red;
public byte Alpha;
}
public unsafe static class DangerousTextureLoader
{
private static Texture LoadFromBitmap(Bitmap bitmap)
{
// Lock the entire bitmap for Read/Write access as we'll be reading the pixel
// colour values and altering them in-place.
var bmlock = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite, bitmap.PixelFormat);
// This code only works with 32bit argb images
Debug.Assert(bmlock.PixelFormat == PixelFormat.Format32bppArgb);
var ptr = (byte*) bmlock.Scan0.ToPointer();
for (var y = 0; y < bmlock.Height; y++)
{
for (var x = 0; x < bmlock.Width; x++)
{
// Obtain the memory location where our pixel data resides and cast it
// into a struct to improve sanity.
var color = (RgbaColor*)(ptr + (y * bmlock.Stride) + (x * sizeof(RgbaColor)));
var alphaFloat = (*color).Alpha/255.0f;
(*color).Red = Convert.ToByte(alphaFloat * (*color).Red);
(*color).Green = Convert.ToByte(alphaFloat * (*color).Green);
(*color).Blue = Convert.ToByte(alphaFloat * (*color).Blue);
}
}
// SlimDX specific stuff here
var newTexture = new Texture(_device, bitmap.Width, bitmap.Height, 0,
Usage.None, GetD3DFormat(bitmap.PixelFormat), Pool.Managed);
// SlimDX specific stuff
var textureLock = newTexture.LockRectangle(0, LockFlags.None);
textureLock.Data.WriteRange(bmlock.Scan0, bmlock.Height * bmlock.Stride);
// The bitmap lock is freed here
bitmap.UnlockBits(bmlock);
// SlimDX specific stuff
newTexture.UnlockRectangle(0);
newTexture.FilterTexture(0, Filter.Default);
return newTexture;
}
}
You can read a better explanation and a link to a research paper at TomF’s tech blog. He also highlights how pre-multiplied alpha will help compress your textures using DXTn formats.