How to render 2D textured "billboards" in 3D world

i’m trying to achieve an effect like that used in old 3D games like Daggerfall, where many objects in the world are actually flat textures that rotate to look at the player.

I know how to load a bitmap to a texture and then texture a flat quad, but what I don’t know how to do is achieve a transparency effect. I can texture the entire quad as a whole, but I can’t figure out how to make it so that you see the painted parts of the texture and see through the “clear” parts. My initial assumption was that I simply had to alter my bitmap loading code to work with 32-bit bitmaps with an alpha channel, and this si what i wound up with:


bool Bitmap_24::ReadAlphaBitmap(char* Filename)
{
	FILE*                pFileStream;
	BITMAPFILEHEADER     bmFileHeader;
	BITMAPINFO           bmInfo;
	int                  iBytesPerLine;
	int                  iTotalBytes;

	// Open the file, obtaining our stream
	if(!(pFileStream = fopen(Filename, "rb")))
	{
		//TODO: Error handle 
		MessageBox(NULL, "Bitmap file was not opened", "An error occurred", MB_ICONERROR | MB_OK);
		return false;
	}

	// read the BITMAPFILEHEADER
	fread((char *)&bmFileHeader, sizeof(BITMAPFILEHEADER), 1, pFileStream);

	// Read the BITMAPINFO
	// NOTE: Because this bitmap should have a 24-bit pixel resolution, there
	// should be no color table, so the entire BITMAPINFO can be read together in one call.
	fread((char *)&bmInfo, sizeof(BITMAPINFO), 1, pFileStream);

	//TODO: Bitmap resolution / compression validation
	/*
	if((bmInfo.bmiHeader.biBitCount != 24) || (bmInfo.bmiHeader.biCompression != 0))
	{
		MessageBox(NULL, "Bitmap was not 24bpp or was compressed", "An error occurred", MB_ICONERROR | MB_OK);
		return false;
	}
	*/

	// Populate Image Data Member Variables
	m_iWidth = (int)bmInfo.bmiHeader.biWidth;
	m_iHeight = (int)bmInfo.bmiHeader.biHeight;

	// Calculate the number of bytes in a line
	/*
	 * This is simple to calculate. We have the number of pixels in a row (Image width.)
	 * We know that there are 32 bits per pixel.
	 * Because each pixel is 32 bits, each pixel is 4 bytes. We can find the byte width (w/out padding):
	 *               notPaddedByteWidth = (pixelWidth * 32) / 8
	 * Depending on the image width, there may be extra "padding" bytes in the array. In a bitmap,
	 * each "scan line" or "row" must have a number of bytes that is a multiple of 4. If the width in
	 * bytes is not naturally a multiple of four, empty padding bytes are added - 1, 2, or 3 depending.
	 * We can figure this out with a simple mod function:
	 *               padBytesPerLine = notPaddedByteWidth % 4
	 * padBytesPerLine tells us how many extra bytes are included in each line. Knowing this, each line has
	 * notPaddedByteWidth + padBytesPerLine bytes per line. The total number of bytes taken up by the pixel
	 * data, then, is this number of bytes per line multiplied by the image height.
	*/

	//iBytesPerLine = ((m_iWidth * 32) / 8) + (((m_iWidth * 8) / 4) % 4);
	iBytesPerLine = ((m_iWidth * 32) / 8) + (((m_iWidth * 32) / 8) % 4);
	iTotalBytes = iBytesPerLine * m_iHeight;

	// Now that we know how many bytes there are, we can initialize our member array.
	// We can figure out the size of the array in DWORDS by dividing the number of bytes
	// by 4. (One DWORD = 4 bytes).
	//m_pPixelArray = new DWORD[iTotalBytes / 4];
	m_pPixelArray = new DWORD[iTotalBytes];

	// Move the file pointer to the beginning of the array.
	fseek(pFileStream, bmFileHeader.bfOffBits, SEEK_SET);

	// Read in the array
	fread(m_pPixelArray, sizeof(unsigned char), iTotalBytes, pFileStream);

	// At this point, the entire bitmap should be loaded into memory
	return true;
}

This is VERY similar to my code to load a 24-bit bitmap in memory, which works successfully. The only changes I made were where I calculate the size of the pixel data - I changed it to be based on a 32-bit-per-pixel bitmap instead of 24.

Then in my OpenGL code I use the bitmap data as a texture like so:


bool BitmapAlphaToTexture(Bitmap_24 *pImageArray, GLuint *pTexArray, int iNumElements)
{	

	// Generate the OpenGL Texture Objects
	glGenTextures(iNumElements, pTexArray);
	
	//Set Pixel Unpacking
	glPixelStorei(GL_UNPACK_ALIGNMENT, 4);   // Handles the fact that bitmap rows must
	                                         // be a multiple of 4 bytes
	
	for(int i = 0; i < iNumElements; i++)
	{
		//Bind a texture
		glBindTexture(GL_TEXTURE_2D, pTexArray[i]);
		
		//Unload image data into the current texture
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, pImageArray[i].GetWidth(), pImageArray[i].GetHeight(), 
			         0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pImageArray[i].GetPixels());
		
		//Specify Filtering
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Linear Filtering
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Linear Filtering

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	}

	//TODO: Free memory
	
	return true;
}

The result is a completely garbled image that doesn’t at all resemble the original image, and there appears to be no “alpha” effect to boot - at no point can i see through parts of the texture.

If anyone could point me in the right direction with this I’d appreciate it. I’m starting to get the feeling there’s more to it than I initially thought.

I have since found a good billboarding tutorial for performing the rotation aspect of billboarding.

So now my only remaining question has to do with the texturing. How does one get a texture that has transparent parts?

Set up alpha blending or use alpha test.

Just read this on achieving translucence with alpha and blending. It seems like a good resource, but I think that I want to achieve something even more basic than this. I don’t need translucence, only the ability to render flat textures whose parts are either completely transparent or opaque.

Is the process like this:

1 - Read in an image file with an alpha channel.
2 - Set up OpenGL for using alpha.
3 - Draw textured quad.

Or am I off and there is more to it?

Then use alpha test.
GlEnable (glAlphaTest)
glAlphaFunc (glGreater, 0.0)

This will clip away all fragments which don’t have alpha greater than 0.0. Ie the bits of the texture where the alpha channel is black.

Thanks! That makes the whole process a lot clearer. Now I just have to get my 32-bit alpha bitmaps loaded and displaying them correctly!