Radiosity

Started by
13 comments, last by JSandusky 8 years, 5 months ago

Hi,

I'm building a Lightmapper for my small engine and I'm running into a bit of a problem using radiosity. I'm splitting my scene into small patches and propagating the light using a simplified version of the form factor.


 private float FormFactor(Vector3 v, float d2, Vector3 receiverNormal, Vector3 emitterNormal, 
            float emitterArea)
   {
                 emitterArea * (-Vector3.Dot(emitterNormal, v)
                * Vector3.Dot(receiverNormal, v))
                /(Pi*d2+emitterArea);
  }

the problem I'm having is with the bounce light. They never converge and keep adding up energy. I could stop after some iteration but the code is probably incorrect since the energy never goes down.


                        if (Vector3.Dot(ne, lightdir)<0)
                        {
                            var form = FormFactor(lightdir, distance, nr, ne, emitter.Area);
                            emittedLight += emitter.Color *form* receiver.SurfaceColor;
                        }

this is the function where I had the bounce light.

Llightdir is the vector from the emitter patch to the receiver.

ne is the normalize normal of the emitter patch.

nr is the normalize normal of the receiver patch.

I try to scale my scene to see if maybe it was a energy or scaling problem but it didn't work.

the only thing that actually work was to divided by 4 the bounce light but that seems incorrect because in some scene the light ended up converging and on other there where just adding more energy.

So I'm wondering is there some kind of rule I'm missing. Should I add attenuation to the bounce light or the form factor is enough ? I spend the last week try to piece it together but most sources on internet didn't gave me clues on how to balance the bounce energy.

BTW I choose the form factor because it's easy to run on the cpu.

Advertisement
d2 usually means squared distance, but you seem to give distance.
Maybe that's the only thing wrong...

Thanks for noticing but distance is distanceSquare.

Try to compute the amount of power entering a patch and the amount of power coming out of it. If some power is being absorbed by each patch, the propagation should converge.

I thought the way people solve radiosity was by setting up a large sparse linear system of equations and solving it. There are methods that should be fast for that situation, notably the conjugate gradient method [EDIT: Never mind, your matrix is probably not symmetric]. But I've never done it myself.
I've had similar problems when doing this years ago, reading did not help,
but after some trial and error i got it working and so found my own way to understand it.
You can try my algorithm below...
I think there are two ways doing it:

1. Send emitter light to reveivers until only a small amount below given threshold is left.
That's the traditional radiosity approach.

2. Reflect light around until a given number of bounces.
That's what i'm doing, the equations i came up are slightly different from the stuff i've read about number one.

// r = receiver, e = emitter, lacc = summed light the emitter receives from all visible emitters
void InterreflectSamples (qVec3 &Rpos, qVec3 &Rdir, qVec3 &Epos, qVec3 &Edir, SampleNode *e, qVec3 &lacc)
{
qVec3 diff = Epos - Rpos;

float cosR = dot(Rdir, diff); // not really cosine because diff is not normalized, but that's just optimization and works
if (cosR>0)
{
float cosE = -dot(Edir, diff);
float d2 = diff.SqL() + 1.0e-11f; // SqL means squared length

float area = (float)Edir[3]; // storing area in the fourth number, because qVec3 is internally float[4]
float ff = (cosR * cosE) / (d2 * (PI * d2 + area)) * area; // form factor of a disc

if (cosE > 0)
{
qVec3 light = e->owncol[3] * e->owncol; // owncol = emitter diffuse color, owncol[3] = emission (> 0 for light sources)
light += cmul (e->lrec, e->owncol); // lrec = light received from other samples, cmul = component wise vector multiplication
light[3] = 0;
lacc += ff * light;
}
}
}
It should be used in a way like this:


while (numBounces--)
{
foreach (samples as receiver)
{
vec accumulatedLight (0,0,0);

foreach (samples as emitter) if (EmitterIsVisibleFromReceiver (emitter, accumulatedLight))
{
InterreflectSamples (receiver, emitter, accumulatedLight);
}

receiver->lrec = accumulatedLight; // <- bug fix here
}
}

for all samples
{
vec finalColorToProofResult = cmul (lrec, owncol) + owncol * owncol[3];
}
hope this helps (and hopefully there is no bug in the second pseude code).
To compare this with your code you would need to undo the optizations (seems i've removed this older code from my project).

EDIT:
Fixed a bug noticed by JSandusky below
seems i've failed again to format the code nicely, and again i can't find the edit button smile.png

All I see as input is emitter color. What happens to the accumulated color once it's been entirely accumulated before the next pass? Are you averaging? Adding them to the emitter color? etc?

The emitter should have a "reflectance" value if you're doing more than a single bounce (assuming you aren't just averaging passes). Without absorption it'll never converge.

If you're using PBR you could use something like:


smoothness * (sqrt(smoothness) + roughness); 

as a really rough approximation of absorption (that's Lagarde's specular dominate direction IIRC).

Personally, I really prefer rendering the scene from each lumel's position along the normal and multiplying by a weight map (in fisheye for draft, and hemicube for quality) for radiosity (on top of a brute force direct lighting map) and calling it a day.

Link to some source in my lightmapper for an example of how incredibly simple that approach can be:

https://github.com/JSandusky/Urho3D/blob/Lightmapping/Source/Tools/LightmapGenerator/FisheyeSceneSampler.cpp

What happens to the accumulated color once it's been entirely accumulated before the next pass?


You're right, there was a bug. I've fixed the original post.

There's no need for reflectance because the material is assumed perfectly diffuse.


Rendering enviroment maps for each texel may be a good idea for performance, but for accurary you need high resolution.
The number of pixels describes both distance and area of an object, so it's a big difference if a small object with high emission
ends up taking 1 or 2 pixels in the eviroment map, resulting in banding or noise. I found 256 x 256 env maps still not good enough for my needs.
For sure that's not an issue if you handle light sources seperately.

You're right, there was a bug. I've fixed the original post.


Please, don't do that. It makes it hard to understand the thread as a conversation. You should revert your edit and have a new post with the fixed code (or only the relevant parts, if that's more clear).

I did try your code against mine just in case I was missing something but the result is the same. The light keep adding up instead of converging. So its fine to just do a couple of pass but the problem arise when you need more precision and more pass. The value are suppose to average out after a few pass. But that is not my case. Did you manage to do more than 5 pass without blowing up the light ? I can't on my implementation so I have to assume that is incorrect.

The reflection value are suppose to be the albedo color but if you albedo is pure white, it'll just reflect as much energy as it receive which is incorrect.

The form factor seem to give away too much energy so the bounce are really strong.

I could implement energy conserving sort of thing but I though radiosity was more correct that other approximation.

This topic is closed to new replies.

Advertisement