Resource: Ambient Occlusion for TGEA Interiors
by Ryan Mounts · 07/16/2008 (10:18 am) · 59 comments
NOTICE: Resource updated 8/4/08.
Link to TGEA AO Resource
Well, as I figured, the ambient occlusion code was very simple to port over from TGE, but the lightmap persistence and caching took a little more work. Hopefully everyone will find this useful. This is a feature that I've been wanting to see in Torque for a long time, so I'm pretty excited to see it coming together.
For those of you who have already looked at the TGE version of this resource, I suggest at least reading over the Persistence and Caching section again, because the TGEA version behaves slightly differently.
As before, if anyone finds/fixes any bugs, enhances performance, or makes interesting modifications to this resource, please share!
Pics: Side-by-side comparisons of two identical interiors, one with AO and one without. In the first pic, there are two small boxes in the Cornell box on the right, I promise! :) You just can't see them because the ambient light is constant and the textures are a single color. This just illustrates how much visual "pop" AO can add to an interior.


(Church dif created by Benjamin Naulls, of course)
Link to TGEA AO Resource
Well, as I figured, the ambient occlusion code was very simple to port over from TGE, but the lightmap persistence and caching took a little more work. Hopefully everyone will find this useful. This is a feature that I've been wanting to see in Torque for a long time, so I'm pretty excited to see it coming together.
For those of you who have already looked at the TGE version of this resource, I suggest at least reading over the Persistence and Caching section again, because the TGEA version behaves slightly differently.
As before, if anyone finds/fixes any bugs, enhances performance, or makes interesting modifications to this resource, please share!
Pics: Side-by-side comparisons of two identical interiors, one with AO and one without. In the first pic, there are two small boxes in the Cornell box on the right, I promise! :) You just can't see them because the ambient light is constant and the textures are a single color. This just illustrates how much visual "pop" AO can add to an interior.


(Church dif created by Benjamin Naulls, of course)
About the author
#22
if there is a bug in the underlying LM technique, the AO code might present an opportunity for accounting for it. eg, you could orient the hemisphere slightly towards "down", which would shoot more rays "down" and tend to make that side a bit darker, i think.
07/17/2008 (2:21 pm)
Ryan,if there is a bug in the underlying LM technique, the AO code might present an opportunity for accounting for it. eg, you could orient the hemisphere slightly towards "down", which would shoot more rays "down" and tend to make that side a bit darker, i think.
#23
Well, that screenshot doesn't show it off too well, but it definately feels more balanced. I'd be up for helping to track down the lightmapping problem however, real solution > angling hack.
07/17/2008 (2:43 pm)
This fixes it fairly well. in lightingSystem/synapseGaming/interior/sgPlanarMap.cpp, change// Generate a random vector between (-1,-1,-1) and (1,1,1) randPt.set(gRandGen.randF(-1.0f,1.0f), gRandGen.randF(-1.0f,1.0f), gRandGen.randF(-1.0f,1.0f));to
// Generate a random vector between (0,0,0) and (1,1,1) randPt.set(gRandGen.randF(0.0f,1.0f), gRandGen.randF(0.0f,1.0f), gRandGen.randF(0.0f,1.0f));You obviously don't have to change the values in the comments, that mainly for keeping track of it. What this does is instead of generating a random normal from negatives to positive, it just generates it from 0 to postive. Actually, you could probably just change the z random for this to work, lemme check...
Well, that screenshot doesn't show it off too well, but it definately feels more balanced. I'd be up for helping to track down the lightmapping problem however, real solution > angling hack.
#24
Also could it be related to the normal map bug? http://www.garagegames.com/mg/forums/result.thread.php?qt=76412
07/17/2008 (3:06 pm)
Nice one Morrock, can you provide the fix for the none coder :)Also could it be related to the normal map bug? http://www.garagegames.com/mg/forums/result.thread.php?qt=76412
#25
07/17/2008 (3:15 pm)
Sorry ando, I just relight my entire scene of AO to bad results. Even though it fixes the top is darker bug, it makes the AO in general much much worse. The top image was done with AO: quality: low, distance: 3, multiplier: 1. The bottom image is quality: medium, distance: 3, multiplier: 1. So no different quality or setting is going to help it, it looks pretty much the same at all detail levels.
#26
07/17/2008 (9:05 pm)
I was wondering if they have a Ambient Occlusion code for DTS that could work for building model and player model that would shade the normal map and give the DTS models a more realistic look?
#27
07/18/2008 (4:47 am)
Thats some improvement!
#28
Be great to get rid of the nasty border lines in map2dif
07/18/2008 (4:48 am)
It could be tied to the nasty lightmap borders which have reapeared in recent map2dif builds like the new tgea build... http://www.garagegames.com/mg/forums/result.thread.php?qt=30047Be great to get rid of the nasty border lines in map2dif
#29
I'm not quite sure how you generated that bottom pic... changing the random vector bounds doesn't really affect anything. You could change the random vector to a constant vector if you wanted (it just can't be the same direction as your surface normal), and the only visual difference will be some shadow banding that is related to the gaps between the ray dome vectors. Randomizing the lexel basis helps to smooth this banding effect.
@Ando
My hunch is that it has something to do with the lexel creation code... I remember looking at that code once and noticing something interesting. The lexels overhang the surface borders by about half a lexel all the way around (I believe this is what leads to light leaks, when the overlap is big enough to reach underneath a wall to the other side). But if the lexels overhang more on one side than another, that would definitely either cause the AO to be darker on that side, or cause a "light leak" if the lexel was inside a wall 'cause the ray casts wouldn't hit anything and it'd appear unoccluded. Anyway, this is kinda tough to track down...
07/18/2008 (7:25 am)
@MorrockI'm not quite sure how you generated that bottom pic... changing the random vector bounds doesn't really affect anything. You could change the random vector to a constant vector if you wanted (it just can't be the same direction as your surface normal), and the only visual difference will be some shadow banding that is related to the gaps between the ray dome vectors. Randomizing the lexel basis helps to smooth this banding effect.
@Ando
My hunch is that it has something to do with the lexel creation code... I remember looking at that code once and noticing something interesting. The lexels overhang the surface borders by about half a lexel all the way around (I believe this is what leads to light leaks, when the overlap is big enough to reach underneath a wall to the other side). But if the lexels overhang more on one side than another, that would definitely either cause the AO to be darker on that side, or cause a "light leak" if the lexel was inside a wall 'cause the ray casts wouldn't hit anything and it'd appear unoccluded. Anyway, this is kinda tough to track down...
#30
You were right, I left in a few lines of code from when I tried messing with the algorithm (I do this way too much). Now I'm not sure how I got that first screenshot of the extrusion being darker on the bottom...
@Kory
The problem with ambient occlusion for those non-static objects (animated objects, physics objects, players) is that in this AO algorithm, it determines occlusion by doing raycasts in all directions to check how occluded the space around it is. It bakes it into the lightmap, so no calculation is done in-game. But with non-static objects, the space around them may become more/less occluded as they move around, or as other objects interact with them; so it would need to be calculated and shaded in real-time. This method takes far to long to do real-time calculation. I would love to try to tackle something like real-time "ambient occlusion", but right now I don't know enough shader language to write most methods (like SSAO) or know how Torque does shadows well enough if I were to try to do it in the engine.
07/18/2008 (8:27 am)
@RyanYou were right, I left in a few lines of code from when I tried messing with the algorithm (I do this way too much). Now I'm not sure how I got that first screenshot of the extrusion being darker on the bottom...
@Kory
The problem with ambient occlusion for those non-static objects (animated objects, physics objects, players) is that in this AO algorithm, it determines occlusion by doing raycasts in all directions to check how occluded the space around it is. It bakes it into the lightmap, so no calculation is done in-game. But with non-static objects, the space around them may become more/less occluded as they move around, or as other objects interact with them; so it would need to be calculated and shaded in real-time. This method takes far to long to do real-time calculation. I would love to try to tackle something like real-time "ambient occlusion", but right now I don't know enough shader language to write most methods (like SSAO) or know how Torque does shadows well enough if I were to try to do it in the engine.
#31
07/18/2008 (9:55 am)
Well, this isn't rock solid proof, but it definitely illustrates that my hunch is most likely correct. The pic shows each surface's outer lexels in red (this has nothing to do with AO, but rather how the lexels correspond to the surface in world space). You can see how some sides are more red than others, indicating that the lexels overhang the surface differing amounts on different sides. Notice how it is more red above the ledge than below... so what's happening is that above the ledge, the lexels' centers are outside the interior and the rays are performing their jobs properly. Below the ledge, the lexels' center are inside the interior and the rays are not occluded. The proper solution would be to redo the lexel generation code so that they get centered on the surface as expected. I'll look into this, but it may be a while because I'm going on vacation next week. :)
#32
07/18/2008 (2:22 pm)
Nice job, how do you get the outer lexels to show color like that? What is up with the outer lexels dividing the center of the extrusion?
#33
So down where the texels get set, you can test if(i != sgPlanarLightMap::sglpInner) then make the texel red. Then I commented out the sgBlur line in sgMergeLighting so I could see the lexels clearly. Now why there is an outer lexel in the center of the ledge, the only thing I can think of that could cause that is if the lexel lied exactly on the center of that face. For a rectangular surface like that, the center happens to lie on the shared edge between the two tris that form the surface. All lexels on a tri edge are classified as outer. The AO doesn't depend on the inner/outer concept, so that shouldn't really matter.
As a side note, my hunch has shifted slightly, but I'll elaborate when I have more info.
07/18/2008 (3:46 pm)
The lexels are already divided into "inner" and "outer" and lighting is processed inner first and then outer. Look above the AO code in sgCalculateLighting to the outermost for-loop and you'll see how to test if the lexel is inner or outer.for(U32 i=0; i<sgPlanarLightMap::sglpCount; i++)
{
// set which list...
U32 templexelscount;
sgLexel *templexels;
if(i == sgPlanarLightMap::sglpInner)
{
templexelscount = sgInnerLexels.size();
templexels = sgInnerLexels.address();
}
else
{
templexelscount = sgOuterLexels.size();
templexels = sgOuterLexels.address();
}So down where the texels get set, you can test if(i != sgPlanarLightMap::sglpInner) then make the texel red. Then I commented out the sgBlur line in sgMergeLighting so I could see the lexels clearly. Now why there is an outer lexel in the center of the ledge, the only thing I can think of that could cause that is if the lexel lied exactly on the center of that face. For a rectangular surface like that, the center happens to lie on the shared edge between the two tris that form the surface. All lexels on a tri edge are classified as outer. The AO doesn't depend on the inner/outer concept, so that shouldn't really matter.
As a side note, my hunch has shifted slightly, but I'll elaborate when I have more info.
#34
07/19/2008 (10:31 am)
Okay, got this fixed now... I'll post pics and the fix when I get home. Thanks to those who helped look into this.
#35
07/19/2008 (10:42 am)
Wow I really hope all of this gets fully integrated with the next release.
#36
07/19/2008 (1:08 pm)
Fixed, looking forward to it :)
#37
If you don't want to download the resource again, here are the code changes in bold. This is in the AO code in sgCalculateLighting...
07/21/2008 (7:58 am)
The resource link has been updated with the fix for the shadow bias. My hunch was correct that the lexel positions were the culprit, but not for the reason I thought. I had made a wrong assumption that lexel.worldpos was the center of the lexel when it is actually the lower left corner. So the rays were being projected from the corner of the lexel instead of the center, which biased the shadow to the bottom and left. To fix it, I simply calculate the center of the lexel and shoot rays from there. :) I'll try to post a clean build this evening.If you don't want to download the resource again, here are the code changes in bold. This is in the AO code in sgCalculateLighting...
/// AO MOD LEXEL OCCLUSION START
if(sgInteriorInstance->mAO)
{
F32 sum = 0.0f;
F32 *rayDome;
F32 rayArea;
F32 AO;
F32 alpha;
U32 rayCount;
Point3F tangent, binormal, randPt, ray;
RayInfo info;
[b]
// Calculate the world position of the lexel center
Point3F sVec(sgLightMapSVector.x, sgLightMapSVector.y, sgLightMapSVector.z);
Point3F tVec(sgLightMapTVector.x, sgLightMapTVector.y, sgLightMapTVector.z);
Point3F lexelMid = lexel.worldPos + 0.5f*(sVec + tVec);[/b]// Cast a ray along the tranformed direction
if(gClientContainer.castRay([b]lexelMid, lexelMid[/b] + ray, mask, &info))
{
// Return ray to unit length before dot product
ray /= sgInteriorInstance->mAORadius;
// Sum up cos(angle between normal and ray) for all occluded rays
sum += mDot(lexel.normal, ray);
}
#39
07/21/2008 (5:32 pm)
Tried and tested, all is good and works perfect, great stuff. Thankyou for providing the exe I really appreciate it, I will get you a few beers :) GG be daft not to pick this up and reward you in some way for your efforts.
#40
07/22/2008 (6:29 am)
Thanks a lot for sharing Ryan!
Torque 3D Owner Ryan Mounts