google / filament

Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WebGL2
https://google.github.io/filament/
Apache License 2.0
17.44k stars 1.84k forks source link

No point or spot light shadows on the first rendered frame #7715

Closed nimrod-gileadi closed 3 months ago

nimrod-gileadi commented 4 months ago

I have a piece of code which sets up a scene with some POINT light sources, and renders one frame only. The point lights are meant to cast shadows but they don't. If I render two frames, instead, I get shadows.

I figured out the reason. In this piece of code, there is filtering based on shadowMap->hasVisibleShadows(): https://github.com/google/filament/blob/90d90094dc9eed59b7e75969b8128857217cea2c/filament/src/ShadowMapManager.cpp#L388

However, the value of shadowMap->hasVisibleShadows() is not available until the call to updatePoint here, which is part of the Execute phase of prepareShadowPass: https://github.com/google/filament/blob/90d90094dc9eed59b7e75969b8128857217cea2c/filament/src/ShadowMapManager.cpp#L897

Commenting out the if statement makes the shadows appear on the first frame. ShadowMap instances are cached between render calls, which is why on the second frame I get shadows.

Desktop (please complete the following information):

Additional context

I'm working in the Google code base and can share internal code to reproduce. I haven't tried building externally.

romainguy commented 4 months ago

@pixelflinger isn't that a change you made recently?

pixelflinger commented 4 months ago

I feel like I fixed this recently. Can you confirm this is with a recent version?

nimrod-gileadi commented 4 months ago

I can confirm this is happening with the latest code in the main branch (and google3 HEAD).

To demonstrate, I edited the loop here to be:

    auto const& passList = prepareShadowPass.getData().passList;
    int i = 0;
    static int render_count = 0;
    render_count++;
    for (auto const& entry: passList) {
        if (render_count <= 3) {
          std::cerr << "Render " << render_count << ", pass " << i
                    << "(shadowType = " << (int) entry.shadowMap->getShadowType()
                    << ") visibleShadows="
                    << entry.shadowMap->hasVisibleShadows()
                    << "\n";
        }
        i++;

        if (!entry.shadowMap->hasVisibleShadows()) {
            continue;
        }

When I run the lightbulb sample (with ./samples/lightbulb -m -d -p -i ../../third_party/environments/lightroom_14b.hdr ../../assets/models/monkey/monkey.obj), I get the following logs, showing that on the first frame, the spot lights don't cast a shadow, but on the second and third frame they do:

Render 1, pass 0(shadowType = 0) visibleShadows=1
Render 1, pass 1(shadowType = 1) visibleShadows=0
Render 1, pass 2(shadowType = 1) visibleShadows=0
Render 2, pass 0(shadowType = 0) visibleShadows=1
Render 2, pass 1(shadowType = 1) visibleShadows=1
Render 2, pass 2(shadowType = 1) visibleShadows=1
Render 3, pass 0(shadowType = 0) visibleShadows=1
Render 3, pass 1(shadowType = 1) visibleShadows=1
Render 3, pass 2(shadowType = 1) visibleShadows=1

In the original bug I mentioned point lights specifically, but this seems to apply to spot lights too.

nimrod-gileadi commented 4 months ago

Or more visually, edit FilamentApp.cpp to stop rendering after the first 1 or 2 frames: https://github.com/google/filament/blob/a068143953d9f5feb7a79b5318898ccbced30080/libs/filamentapp/src/FilamentApp.cpp#L458

to:

        static int render_count = 0;
        constexpr int kFramesToRender = 1;
        render_count++;
        if (render_count <= kFramesToRender && renderer->beginFrame(window->getSwapChain())) {

First render vs second render (note the effect of the red FOCUSED_SPOT light):

first_render second_render
pixelflinger commented 3 months ago

Okay, I'm able to reproduce and understand the problem. As things are set-up right now, the shadow visible/not visible status of point/spot lights is delayed by one frame. This is because it is set in the "execute" closure of AddPass(), but consumed in the "setup" closure of the next addPass(), so it's not ready yet at that point.