Monday, June 9, 2008

Small but significant change to texture loading

I posted on QuakeOne about a BSP texture issue that affects all GLQuake engines, so I was intrigued enough to sit down and actually do something about it.

In software Quake, BSP textures are stored as 4 miplevels within the BSP itself. Each of these is restricted to the Quake palette, and the appropriate miplevel is used depending on distance from the view origin.

However, GLQuake does not use these. Instead, it generates miplevels through the GL_MipMap function for eveything but the first (miplevel 0). This means that the original colour balance of software Quake is totally lost in each and every GL engine.

Now, while this may be technically superior and more accurate, there is a lot to be said for reverting to the original software Quake method and using the stored mipmaps. Software Quake had a lot going for it that looked much better than GLQuake, and the colour balance (with 8 bit textures) was one of those things. This colour balance came from two factors: lighting and textures. Lighting is already fixed by restoring overbrights to GLQuake, but why not go the distance and also restore the original textures?

So I did. ;) MHQuake is now more accurate than any other engine out there for retaining the software Quake colour balance (it's less faithful in other ways, of course). It's a subtle difference, and you likely won't even see it unless you go looking for it, but it's there.

As a bonus, MHQuake is also capable of accurately reproducing map hacks that might exist which use a different image at each miplevel. I don't expect that there's too many of those, but you never know...

It's only a matter of time before other engines also get this feature (despite what I said above, I'd be shocked if DarkPlaces didn't already have it...), so here's my code (specific to my own engine, but should be easily adapted:


if (flags & TEX_MIPMAP)
{
int miplevel = 1;

// if it's an 8 bit texture coming from a BSP we handle it differently, as we want to take the first 4 (i.e.
// (next 3) miplevels from the BSP... this ensures that the colour balance correctly matches software quake
if ((flags & TEX_BSP) && !(flags & TEX_32BIT))
{
// BSP textures are ensured to be large enough to survive this...
for (; miplevel < 4; miplevel++)
{
// advance to next stored BSP mipmap
data += width * height;

// take down width and height
width >>= 1;
height >>= 1;

// take down scaled_width and scaled_height
scaled_width >>= 1;
scaled_height >>= 1;

// deal with texture resizing
if (width == scaled_width && height == scaled_height)
{
int i;
int size = width * height;

// upload is already set and valid, so we can copy it right in
for (i = 0; i < size; i++) upload[i] = d_8to24table[data[i]];
}
else
{
// resample the new data into upload (already set and valid here too), never a 32 bpp base
GL_ResampleTexture (data, width, height, upload, scaled_width, scaled_height, false);
}

// upload (don't increment miplevel for this one as the for loop does it for us)
glTexImage2D (GL_TEXTURE_2D, miplevel, GL_RGBA, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, upload);
}
}

// BSP mipmapping sets up so that it will fall through correctly to here...
while (scaled_width > 1 || scaled_height > 1)
{
// generate our mipmaps
GL_MipMap ((byte *) upload, scaled_width, scaled_height);

// take down to next level
scaled_width >>= 1;
scaled_height >>= 1;

// never go < 1
if (scaled_width < 1) scaled_width = 1;
if (scaled_height < 1) scaled_height = 1;

glTexImage2D (GL_TEXTURE_2D, miplevel++, GL_RGBA, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, upload);
}
}

0 comments: