ands / lightmapper

A C/C++ single-file library for drop-in lightmap baking. Just use your existing OpenGL renderer to bounce light!
1.41k stars 133 forks source link

Noisy lightmaps #10

Closed braddabug closed 6 years ago

braddabug commented 7 years ago

I'm having a weird problem that I can't figure out. You can see a screenshot and the generated lightmap here: https://imgur.com/a/2NqIT

It's like some of the samples are just wrong (usually black instead of grayish, but sometimes white), as if depending on the hemisphere orientation the light is either visible or not visible, but that's wild speculation. I tried turning off interpolation to render all the hemispheres, but that didn't help.

ands commented 7 years ago

Hi,

What do you write into the alpha channels in your own surface fragment shaders? (should be 1.0 for front faces) Do you maybe clear the framebuffer on your own? Or change the target framebuffer/viewport/scissor rect? Do you modify the matrices before multiplying them with the vertex positions? What are your lmCreate parameters and api call order?

Cheers :), Andreas

braddabug commented 7 years ago

My code is based on the example, so the shaders are the same. And I can't find anywhere that I'm clearing the framebuffer or changing anything before drawing.

My call to lmCreate() looks like this:

lm_context *ctx = lmCreate(
        64,               // hemisphere resolution (power of two, max=512)
        0.01f, 200.0f,   // zNear, zFar of hemisphere cameras
        0, 0, 0, // background color (white for ambient occlusion)
        0, 0.00f);        // lightmap interpolation threshold (small differences are interpolated rather than sampled)
                          // check debug_interpolation.tga for an overview of sampled (red) vs interpolated (green) pixels.

Then I'm calling lmSetTargetLightmap(), then lmSetGeometry(), then the lmBegin()/lmEnd() loop.

The full source is here: https://github.com/braddabug/thegame/tree/master/Tools/LightmapTool

It's got to be some little thing I'm doing wrong. I checked with Valgrind on Linux and it didn't report anything. OpenGL doesn't complain about anything either.

ands commented 7 years ago

Hi braddabug,

thanks for supplying code that I can use to reproduce the issue :). I'll see if I can get this compiled and running tomorrow, because I didn't see anything obviously wrong by quickly reading through the code. Btw.: Did you check the polygon winding on your loaded mesh(es)?

Kind regards, Andreas

ands commented 6 years ago

Hey :) First I thought that the noise looked very random, but after building and running your tool I quickly realized that you are using a very tiny light source. So, the noise is just a very severe undersampling artifact. I actually got ok-ish results when increasing the hemisphere rendering resolution. Try these parameters:

lm_context *ctx = lmCreate( 512, // hemisphere resolution (power of two, max=512) 0.01f, 200.0f, // zNear, zFar of hemisphere cameras ambientRed, ambientGreen, ambientBlue, // background color (white for ambient occlusion) 3, 0.01f);

It will be slower and there's still some minor noise on some wall parts. You can easily get rid of noise artifacts (and thus improve performance, because you don't need such a high hemisphere rendering resolution) by using larger light sources, if applicable.

Here's a shot with two bounces: Indirect Light

braddabug commented 6 years ago

Thanks! Now that I think about it that makes perfect sense.

The tiny point light will eventually become a directional "sun" light. Maybe it'd be best to investigate representing the entire sky as an HDR floating-point cubemap, that way aliasing becomes less of an issue since the cubemap can easily be filtered/smoothed. I think.

jpcy commented 6 years ago

You could ray trace the directional sun light using Embree. Use the lightmapper rasterizer to get the worldspace position of each luxel, then trace a ray in the direction of the sun to see if it's occluded. For smoother shadows, take multiple samples with jittered sampling positions and average them. Then run the lightmapper to get bounce lighting.

ands commented 6 years ago

If you want to use this lib for indirect lighting, you could also render your scene with the usual direct scene lighting during baking (a static shadow map, materials, point/directional/spotlights, etc.)

braddabug commented 6 years ago

I think that will work. I modified my code to do a initial pass where I shoot a ray from each texel to the light, and that calculates pretty nice direct lighting. Then I feed that in to the emissive texture and start the indirect lighting passes. It looks quite a bit nicer. Practically no noise!

I did have to modify lightmapper.h to get things like the lightmap texel position (world coords and texel coords) and normal since the contents of lm_context are hidden. I just added this function:

void lmTexelInfo(lm_context* ctx, float* outWorldPos, float* outDirection, int* outTexelCoord, int* hemisphereSideIndex)
{
    if (outWorldPos)
    {
        outWorldPos[0] = ctx->meshPosition.sample.position.x;
        outWorldPos[1] = ctx->meshPosition.sample.position.y;
        outWorldPos[2] = ctx->meshPosition.sample.position.z;
    }

    if (outDirection)
    {
        outDirection[0] = ctx->meshPosition.sample.direction.x;
        outDirection[1] = ctx->meshPosition.sample.direction.y;
        outDirection[2] = ctx->meshPosition.sample.direction.z;
    }

    if (outTexelCoord)
    {
        outTexelCoord[0] = ctx->meshPosition.rasterizer.x;
        outTexelCoord[1] = ctx->meshPosition.rasterizer.y;
    }

    if (hemisphereSideIndex)
    {
        hemisphereSideIndex[0] = ctx->meshPosition.hemisphere.side;
    }
}

EDIT: I tweaked the code a little bit. I wanted the hemisphere side index, but I guess I was getting the hemisphere index instead.