Game Development Community

TGEA 1.7 Bug: Atlas and object lighting from lightmap

by Jeff Faust · in Torque Game Engine Advanced · 04/09/2008 (1:22 pm) · 15 replies

Player lighting (and that of other objects) is not adjusting properly in response to the lightmap when using Atlas. The lighting does adjust when stepping into an interior, but when stepping into the static shadow of an interior or into the shadow of a mountain, the Player lighting remains the same.

It actually ends up using a default ambient that is set at the end of sgLightManager::getLightingAmbientColor() in sgLightManager.cpp. Recently, the default ambient assigned in this piece of code was raised from (0.5 0.5 0.5) to (1.0 1.0 1.0), so objects now appear much brighter on Atlas terrain than they did in recent betas.

Digging deeper, the reason for all this appears to be this code excerpt from lightingSystem/synapseGaming/atlas/sgAtlas.cpp:
// Given a ray, this will return the color from the lightmap of this object, return true if handled
bool sgAtlasSystem::getColorFromRayInfo(RayInfo collision, ColorF& result)
{
   return false;
}

About the author

Jeff Faust creates special effects indie middleware and games for Faust Logic. --- Blog: Effectronica.com --- Twitter: @FaustLogic


#1
04/14/2008 (7:42 am)
Follow-up:
I've looked into implementing the gAtlasSystem::getColorFromRayInfo() shown above and the main difficulty seems to be that there's no obvious way to sample the atlas lightmap for a color that could be used for lighting. For normal terrain, the lightmap for a TerrainBlock is a GBitmap and one can get a value using GBitmap::sampleTexel(). Given an AtlasInstance, does anyone know of a way to query for equivalent information?
#2
04/14/2008 (8:33 am)
I'm not familiar with the lighting kit, but it seems to me that the AtlasInstanceColorSource provides this function in the form of the getColor method. The color source instance is linked to the Atlas proxy.
#3
04/14/2008 (9:24 am)
Rene, that's useful information, but that getColor() method is logically quite distant from where and when the sgAtlasSystem::getColorFromRayInfo() needs to get at the color data. As is, it looks like the proxy is only around at startup to deal with static scene lighting issues, and it is gone by the time we need to sample the lightmap for dynamic lighting info. It's a good starting point, but it looks like there's a lot of work to do to make that information available later.
#4
04/14/2008 (10:08 am)
Sorry, Jeff, that was some useless stuff I posted there. Didn't really look as I should have done (and my post even sort of implied you weren't looking; I apologize).

Going over it now, I see the predicament. Unfortunately, extracting texel information from an Atlas texture TOC like this is a bit of work. You'd have to convert into TOC space, get the stub, make sure its data is online, convert to chunk space and then query the texture.

Is the getColorFromRayInfo supposed to return a fully blended color value or is it just supposed to query the lightmap?
#5
04/14/2008 (10:37 am)
I kind of figured that getting the info out of atlas would be topologically more complex than for normal terrain, so I'm not surprised.

Regarding getColorFromRayInfo(), I believe all that's needed is the lightmap info. In case you don't know, what's happening here, is that that the lighting system wants a base ambient light color to combine with other lighting to make it appear that an object like a Player is reacting to the lighting that's represented by the lightmap. A ray is cast to determine which type of surface with a lightmap we are near, and then that surface is queried with additional positional information for a lighting color. sgAtlasSystem::getColorFromRayInfo() is the method that does this for atlas. It's supposed to transform positional information found in the RayInfo structure into the space of the intersected AtlasInstance and then figure out what the lightmap color is there.

The call usually originates from an object's prepBatchRender() or renderObject() method, from which you may be able to draw some conclusions as whether the necessary atlas pieces are likely to be available or not.
#6
04/14/2008 (1:46 pm)
Ok, thanks for enlightening me here. Think I have all the info I need to write up some code to query the lightmap file. Will post then.

As for data availability: the coarsest LOD will always be there, so worst-case is that the lighting information may only be a crude approximation for some frames.
#7
04/14/2008 (1:59 pm)
Hey that sounds great, I'm grateful for any help. The issue with LOD is somewhat interesting. For mobile objects like Player, it would never be a problem, since they are always moving around and resampling the lightmap. It could be some concern for stationary objects. I'm pretty sure they would only check once and then reuse what they got from then on. If it started with a crude approximation it would stick with that unless we can somehow flag it. Still, it makes sense to make a first stab at a solution and see what happens.
#8
04/14/2008 (4:40 pm)
Jeff, could you try the code below and see what results you get? Testing with my datasets is kind of limited. I stepped through the code and the lightmap querying seems to be correct (though I haven't actually tested with a deeper tree), but I'm not sure about the effect on lighting.

By the way, for the code to work, you also have to add the following to AtlasInstance:

AtlasFile* getLightMapFile()
   {
	   return mLightMapFile;
   }

And here's the getColorFromRayInfo code:

bool sgAtlasSystem::getColorFromRayInfo(RayInfo collision, ColorF& result)
{
	if( !dynamic_cast< AtlasInstance* >( collision.object ) )
		return false;

	// Get to the lightmap file and retrieve the texture TOC.
	// Note that this code only works with the lightmaps produced by this module.
	// It does not work with lightmaps packaged with the main map (suppose there
	// is no need it does).

	AtlasInstance* atlas = ( AtlasInstance* ) collision.object;
	AtlasFile* lightMapFile = atlas->getLightMapFile();
	if( !lightMapFile )
		return false;
	AtlasResourceTexTOC* arttoc;
	if( !lightMapFile->getTocBySlot( 0, arttoc ) )
		return false;

	// Get some key numbers.

	U32 treeDepth = arttoc->getTreeDepth();
	U32 chunkSize = arttoc->getTextureChunkSize();
	U32 lightmapSize = arttoc->getNodeCount( treeDepth ) * chunkSize;

	// Scale the texcoords to lightmap coords.

	Point2I lightmapCoords( collision.texCoord.x * F32( lightmapSize ),
		collision.texCoord.y * F32( lightmapSize ) );

	// Scale the lightmap coords into chunk coords.

	Point2I chunkXY( lightmapCoords.x / chunkSize, lightmapCoords.y / chunkSize );

	// Walk the TOC tree bottom up and stop at the first matching
	// piece of data that is available.

	for( int i = treeDepth - 1; i >= 0; -- i )
	{
		// Get the stub and, if its data is online, query its texture.

		AtlasResourceTexStub* stub = arttoc->getStub( i, chunkXY );
		if( stub->hasResource() )
		{
			// Data is there.  Grab the bitmap and read out the color information.

			if( !stub->mChunk->isBitmapTexFormat( stub->mChunk->mFormat ) )
				return false;
			
			GBitmap* bitmap = stub->mChunk->bitmap;

			U32 shift = treeDepth - i - 1;
			U32 x = ( lightmapCoords.x >> shift ) - chunkXY.x * chunkSize;
			U32 y = ( lightmapCoords.y >> shift ) - chunkXY.y * chunkSize;

			ColorI color;
			bitmap->getColor( x, y, color );

			result = ColorF( F32( color.red ) / 255.0, F32( color.green ) / 255,
				F32( color.blue ) / 255.0, 1.0 );

			return true;
		}
		else
		{
			// No data.

			//TODO: This is the place to request the data to be loaded.  However, as long
			//  as there isn't also a place where we kick out chunks no longer needed, this
			//  is not a reasonable thing to do.
			//arttoc->requestLoad( stub, AtlasTOC::NormalPriority,
			//	F32( treeDepth - i ) / F32( treeDepth + 1 ) );

			// When going up a level, we need to scale our texcoords
			// to the new level dimensions.

			chunkXY.x >>= 1;
			chunkXY.y >>= 1;
		}
	}

	return false;
}
#9
04/14/2008 (4:45 pm)
Ah, forgot about the LOD thing: with small lightmap sizes, you end up with the whole thing paged in anyway. Locking lightmaps to a single tile (= tree of depth 1), would be an easy solution for reasonably sized lightmaps to guarantee data availability. For larger maps, however, a more elaborate solution would have to be found.
#10
04/15/2008 (7:42 am)
Rene, I spent a little time testing your getColorFromRayInfo() implementation. First off, it appears to be doing the indexing correctly, which is the hard part. As I move a Player through a scene, it's lighting seems to adjust properly as it passes in and out of lightmap shadows.

The most obvious thing wrong is that the results are way too dark. This is also true on regular terrain, and if you look at sgTerrainSystem::getColorFromRayInfo() you'll find that its color result is multiplied by 2.0. This is what I did in your code and the results are much closer to expectations, though even doubled, I think it's still a little dark.

The lighting changes did seem to snap a little more than what I observe on regular terrain, but this could have to do with the lightmap size. I was using a lightmapDimension of 256 and I believe regular terrain lightmaps default to 1024.

I attempted to increase my lightmapDimension to 1024 and this caused a crash in your code. When compiled for debug, it throws the following assert inside the arttoc->getStub() call:
"AtlasTOC::getNodeIndex - out of range y for level!"

All in all, this is an excellent first pass and probably just needs some fine-tuning and some deeper testing.
#11
04/15/2008 (8:09 am)
Note to self: that's what you get, if you don't really test *all* of your code.

Yesterday, I didn't really test with a tree of more than one level, so the entire "up-one-level" stuff went through unverified and (not suprisingly) is turning out to be faulty now. Will correct that and post the changes.

Glad, though, it seems to be the first step towards a solution.
#12
04/15/2008 (12:14 pm)
The line that reads

U32 lightmapSize = arttoc->getNodeCount( treeDepth ) * chunkSize;

above should be

U32 lightmapSize = BIT( treeDepth - 1 ) * chunkSize;

The first version calculates the area rather than edge width.

This should fix the TOC access error.
#13
04/15/2008 (2:25 pm)
Yes, that got around the crash. It was nice to be able to test it on a crisper lightmap where I could see clearly where the Player moved in and out of building shadows and it tracked accurately. Also, the lighting seemed to adjust more smoothly than I was seeing on the lower res map.

This is an excellent and useful piece of code, and it's great to see the improved dynamic lighting changes on characters running through an atlas environment.
#14
04/15/2008 (3:48 pm)
Great! And great work pinpointing the root cause of the issue (I am more and more convinced that fixing stuff is 90% searching and 10% solving).
#15
06/15/2008 (11:21 am)
I never noticed it in TGEA 1.7.0 (though the problem is there also), but in TGEA 1.7.1 I noticed that the color of the player was skewing towards blue in a scene with a Sun that was reddish.

It turns our that this call:
bitmap->getColor( x, y, color );
is returning a color with the red and blue components reversed.

I simple fix is to reverse the red and blue when constructing the "result" color, but the actual problem is probably elsewhere. I've been looking through the code that creates the bitmap, but so far I've been unable to locate where the red and blue get swapped.