Game Development Community

Texture quality and distance to object

by Funky Diver · 01/22/2005 (8:34 pm) · 6 comments

This code snippet helps to define the distance between the camera and an object when the object will be rendered using hi-res or low-res textures, and to completely disable the usage of low-res textures.

I started to dig this problem when I've decided to make a 3D puzzle game. When I came up with a working demo, I've found that all small pieces had strange textures due to the fact that engine kicked to the low-res textures while rendering the object. That was the short pre-story ;)
Now, the actual code...

1) Let's define all the necessary variables first. Obviously, we will be making changes to the gTexManager.h and gTexManager.cc files. Open gTexManager.h file and find this place:
struct TextureManager
{
...
  public:
   static const char * csmTexturePrefix;

   static void setSmallTexturesActive(const bool t) { smUseSmallTextures = t;    }
   static bool areSmallTexturesActive()             { return smUseSmallTextures; }
   static GBitmap *loadBitmapInstance(const char *textureName);
...
}
And replace it with the following code:
struct TextureManager
{
...
public:
   static const char * csmTexturePrefix;
   //********** AG ****************** >>
   static bool smTextureDisableLoRes; //Texture LoRes management: disable/enable switching between the hi- and low-res textures  
   static float smTextureLoResDist; //Texture LoRes management: the distance from camera when it start to use the low-res texture   
   //********** AG ****************** <<
   static void setSmallTexturesActive(const bool t) { smUseSmallTextures = t;    }  
   static bool areSmallTexturesActive(); //AG Texture LoRes management: we must move the function body from here to the gTexManager.cc file
   static GBitmap *loadBitmapInstance(const char *textureName);
...
}
We are done with the gTexManager.h file, now, to the gTexManager.cc one...
In the top of gTexManager.cc file find these two lines:
bool TextureManager::smUseSmallTextures = false;
  bool TextureManager::smIsZombie = false;
and add the following lines:
bool TextureManager::smTextureDisableLoRes = false; //By default, texture switching is enabled
  float TextureManager::smTextureLoResDist = 15.0; //The minimum distance between the camera and the object when engine switch to the hi-res textures

2) We have to expose our new variables to the engine's script, so we can change them at run-time. Find the code that defines TextureDictionary::create() function in gTexManager.cc file:
void TextureDictionary::create()
{
   smTOList = NULL;
   smHashTableSize = 1023;
   smTable = new TextureObject *[smHashTableSize];
   for(U32 i = 0; i < smHashTableSize; i++)
      smTable[i] = NULL;

   //Con::addVariable("$pref::OpenGL::mipReduction", texDetailLevelCB, "0");
   //Con::addVariable("$pref::OpenGL::anisotropy", anisotropyCB, "0");

   Con::addVariable("$pref::OpenGL::force16BitTexture",    TypeBool, &sgForce16BitTexture);
   Con::addVariable("$pref::OpenGL::forcePalettedTexture", TypeBool, &sgForcePalettedTexture);
   Con::addVariable("$pref::OpenGL::allowCompression",     TypeBool, &sgAllowTexCompression);
   Con::addVariable("$pref::OpenGL::disableSubImage",      TypeBool, &sgDisableSubImage);

   Con::addVariable("$pref::OpenGL::textureTrilinear",     TypeBool, &sgTextureTrilinear);
   Con::addVariable("$pref::OpenGL::textureAnisotropy",    TypeF32,  &sgTextureAnisotropy);
}
Add two new lines like
void TextureDictionary::create()
{
   smTOList = NULL;
   smHashTableSize = 1023;
   smTable = new TextureObject *[smHashTableSize];
   for(U32 i = 0; i < smHashTableSize; i++)
      smTable[i] = NULL;

   //Con::addVariable("$pref::OpenGL::mipReduction", texDetailLevelCB, "0");
   //Con::addVariable("$pref::OpenGL::anisotropy", anisotropyCB, "0");

   Con::addVariable("$pref::OpenGL::force16BitTexture",    TypeBool, &sgForce16BitTexture);
   Con::addVariable("$pref::OpenGL::forcePalettedTexture", TypeBool, &sgForcePalettedTexture);
   Con::addVariable("$pref::OpenGL::allowCompression",     TypeBool, &sgAllowTexCompression);
   Con::addVariable("$pref::OpenGL::disableSubImage",      TypeBool, &sgDisableSubImage);

   Con::addVariable("$pref::OpenGL::textureTrilinear",     TypeBool, &sgTextureTrilinear);
   Con::addVariable("$pref::OpenGL::textureAnisotropy",    TypeF32,  &sgTextureAnisotropy);
   //********** AG ****************** >>
   Con::addVariable("$pref::OpenGL::textureDisableLoRes",    TypeBool,  &TextureManager::smTextureDisableLoRes); 
   Con::addVariable("$pref::OpenGL::textureLoResDistance",    TypeF32,  &TextureManager::smTextureLoResDist); 
  //********** AG ****************** <<
   
}
Good.

3) We have to define the main function that tells us if low-res textures are enabled. Find the last method of the TextureManager structure defined in gTexManager.cc file and add these lines:
bool TextureManager::areSmallTexturesActive() //AG Texture LoRes management: returns true if we must use small textures and it's enabled
{ 
	return !smTextureDisableLoRes && smUseSmallTextures; 
}

4) Now the most interesting part: the actual rendering code of a game object. Torque defines two base classes that are used for in-mission object: ShapeBase and TSStatic. We need to change renderObject() functions for both. In the shapeBase.cc find these lines (I love those comments of DMM :D ):
void ShapeBase::renderObject(SceneState* state, SceneRenderImage* image)
{
...
   // This is something of a hack, but since the 3space objects don't have a
   //  clear conception of texels/meter like the interiors do, we're sorta
   //  stuck.  I can't even claim this is anything more scientific than eyeball
   //  work.  DMM
   F32 axis = (getObjBox().len_x() + getObjBox().len_y() + getObjBox().len_z()) / 3.0;
   F32 dist = (getRenderWorldBox().getClosestPoint(state->getCameraPosition()) - state->getCameraPosition()).len();
   if (dist != 0)
   {
      F32 projected = dglProjectRadius(dist, axis) / 350;
      if (projected < (1.0 / 16.0))
      {
         TextureManager::setSmallTexturesActive(true);
      }
   }
...
}
And replace them with
void ShapeBase::renderObject(SceneState* state, SceneRenderImage* image)
{
...
   // This is something of a hack, but since the 3space objects don't have a
   //  clear conception of texels/meter like the interiors do, we're sorta
   //  stuck.  I can't even claim this is anything more scientific than eyeball
   //  work.  DMM
   F32 axis = (getObjBox().len_x() + getObjBox().len_y() + getObjBox().len_z()) / 3.0;
   F32 dist = (getRenderWorldBox().getClosestPoint(state->getCameraPosition()) - state->getCameraPosition()).len();
   if (dist != 0)
   {
      if (dist > TextureManager::smTextureLoResDist) //AG Texture LoRes management: check if we in the range to use the hi-res textures
      {
         TextureManager::setSmallTexturesActive(true);
      }
   }
...
}

The code for TSStatic is almost identical, so fins in tsStatic.cc
void TSStatic::renderObject(SceneState* state, SceneRenderImage* image)
{
...
   // This is something of a hack, but since the 3space objects don't have a
   //  clear conception of texels/meter like the interiors do, we're sorta
   //  stuck.  I can't even claim this is anything more scientific than eyeball
   //  work.  DMM
   F32 axis = (getObjBox().len_x() + getObjBox().len_y() + getObjBox().len_z()) / 3.0;
   F32 dist = (getRenderWorldBox().getClosestPoint(state->getCameraPosition()) - state->getCameraPosition()).len();
   if (dist != 0)
   {
      F32 projected = dglProjectRadius(dist, axis) / 350;
      if (projected < (1.0 / 16.0))
      {
         TextureManager::setSmallTexturesActive(true);
      }
   }
...
}
and replace with
void TSStatic::renderObject(SceneState* state, SceneRenderImage* image)
{
...
   // This is something of a hack, but since the 3space objects don't have a
   //  clear conception of texels/meter like the interiors do, we're sorta
   //  stuck.  I can't even claim this is anything more scientific than eyeball
   //  work.  DMM
   F32 axis = (getObjBox().len_x() + getObjBox().len_y() + getObjBox().len_z()) / 3.0;
   F32 dist = (getRenderWorldBox().getClosestPoint(state->getCameraPosition()) - state->getCameraPosition()).len();
   if (dist != 0)
   {
      if (dist > TextureManager::smTextureLoResDist) //AG Texture LoRes management: check if we in the range to use the hi-res textures
      {
         TextureManager::setSmallTexturesActive(true);
      }
   }
...
}

We are done with the code.

How to use it in the engine? Quite easy: find the demo/client/defaults.cs and add our new variables (can be near $pref::OpenGL group of the variables):
$pref::OpenGL::textureDisableLoRes = "0";
$pref::OpenGL::textureLoResDistance = "30";
Then remove all the *.dso and prefs.cs (saved preferences by the game, so it will be recreated again automatically) files from your "demo/client" folder.
Try to experiment with $pref::OpenGL::textureLoResDistance, changing to 5, 10, and 100.
...
Thanks to everyone who gave me ideas! Special thanks to Ben Garney for the inspiration!

#1
01/22/2005 (10:53 pm)
Glad to see this resource up. I've added it to my todo list for the next Torque release - it's well written and pretty useful looking, so I'm going to sit down and see about integrating it.

Nice work, Alexander! :)
#2
01/27/2005 (2:26 pm)
Thank you very much, Ben!
#3
03/15/2005 (7:06 am)
Ben...
that was long time ago.. so I guess in the current TGE 1.3 there is a quicker way to edit enable/disable lowres and to edit the distance at which the textures switch between high res and low res?

or even integrated somwhere in the world editor?
Or a per object modification so more important objects can look better at greated distance then less important objects?

Thanks for any info
#4
06/09/2006 (4:10 am)
I assume this is in 1.4?
#5
07/25/2006 (6:17 pm)
Did this code make it into TSE Ben?
#6
09/28/2007 (5:58 pm)
Alexander you're my hero!
Works perfectly in 1.5 version ;)