PDA

View Full Version : How to render 2D textured "billboards" in 3D world



Exaxis
03-15-2012, 09:55 PM
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_FILTE R,GL_LINEAR); // Linear Filtering
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTE R,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.

Exaxis
03-16-2012, 09:17 AM
I have since found a good billboarding tutorial (http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat) 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?

BionicBytes
03-16-2012, 03:02 PM
Set up alpha blending or use alpha test.

Exaxis
03-16-2012, 06:08 PM
Just read this on achieving translucence with alpha and blending. (http://www.opengl.org/wiki/Transparency_Sorting) 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?

BionicBytes
03-17-2012, 06:44 AM
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.

Exaxis
03-17-2012, 09:03 AM
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!