zaycev / bevy-magic-light-2d

Experiment with computing 2D shading, lighting and shadows with Bevy Engine
Apache License 2.0
478 stars 41 forks source link

Camera zooming issues #57

Open morr opened 5 months ago

morr commented 5 months ago

Hi!

While zooming scene out the screen is being "split" into a "grid" and around each "grid cell" I observe black areas. The more I zoom out the larger black areas become.

Any ideas how to fix this?

Details provided below:

Here it is illustrated in krypta demo app (I added zoom to fork https://github.com/zaycev/bevy-magic-light-2d/compare/main...morr:bevy-magic-light-2d:zoom-out-issue) image

And this is how the issue looks in my bevy app where I try very large zoom. image image image image

morr commented 5 months ago

Updated the description of the issue, removed unnecessary details.

zaycev commented 5 months ago

Known issue. The problem is likely due to a bug in SDF. Thanks for putting all details.

zaycev commented 5 months ago

@morr I would try something like this for your game.

1) Change SDF to min operator instead of round merge. That would make walls completely black as no light can go into negative space:

@compute @workgroup_size(8, 8, 1)
fn main(@builtin(global_invocation_id) invocation_id: vec3<u32>) {
     let texel_pos  = vec2<i32>(invocation_id.xy);
     let dims = textureDimensions(sdf_out);
     let uv = (vec2<f32>(texel_pos) + 0.5) / vec2<f32>(dims);

     let world_pose = sdf_uv_to_world(uv,
        camera_params.inverse_view_proj,
        camera_params.sdf_scale);
    let r = 1.2;

     var sdf_merged = sdf_aabb_occluder(world_pose.xy, 0);
     for (var i: i32 = 1; i < i32(light_occluder_buffer.count); i++) {
        sdf_merged = min(sdf_merged, sdf_aabb_occluder(world_pose.xy, i));
     }

    textureStore(sdf_out, texel_pos, vec4<f32>(sdf_merged, 0.0, 0.0, 0.0));
}

2) To fix zero irradiance for walls, try to sample it from neighbor pixels (in gi_post_processing.wgsl):

@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
    let position = in.position;
    let uv = coords_to_viewport_uv(position.xy, view.viewport);

    // Read diffuse textures.
    let in_floor_diffuse   = textureSample(in_floor_texture,   in_floor_sampler, uv);
    let in_walls_diffuse   = textureSample(in_walls_texture,   in_walls_sampler, uv);
    let in_objects_diffuse = textureSample(in_objects_texture, in_objects_sampler, uv);

    let in_irradiance = textureSample(in_irradiance_texture, in_irradiance_texture_sampler, uv).xyz;

    // Calculate object irradiance.
    // TODO: parametrize this filter.
    // TODO: we don't really need to do this per pixel.
    var object_irradiance = in_irradiance;
    var walls_irradiance = in_irradiance;

    var k_size = 3;
    var k_width = 28;

    for (var i = -k_size; i <= k_size; i++) {
        for (var j = -k_size; j < 0; j++) {

            let offset = vec2<f32>(f32(i * k_width), f32(j * k_width));
            let irradiance_uv = coords_to_viewport_uv(position.xy - offset, view.viewport);

            let sample_irradiance = textureSample(
                in_irradiance_texture,
                in_irradiance_texture_sampler,
                irradiance_uv
            ).xyz;

            // TODO: Might also need a visibility check here.
            if any(irradiance_uv < vec2<f32>(0.0)) || any(irradiance_uv > vec2<f32>(1.0)) {
                continue;
            }

            object_irradiance = max(object_irradiance, sample_irradiance);
        }
    }

    k_size = 4;
    k_width = 16;
    for (var i = -k_size; i <= k_size; i++) {
        for (var j = -k_size; j < k_size; j++) {

            let offset = vec2<f32>(f32(i * k_width), f32(j * k_width));
            let irradiance_uv = coords_to_viewport_uv(position.xy - offset, view.viewport);

            let sample_irradiance = textureSample(
                in_irradiance_texture,
                in_irradiance_texture_sampler,
                irradiance_uv
            ).xyz;

            // TODO: Might also need a visibility check here.
            if any(irradiance_uv < vec2<f32>(0.0)) || any(irradiance_uv > vec2<f32>(1.0)) {
                continue;
            }

            walls_irradiance += sample_irradiance;
        }
    }

    walls_irradiance /= 16.0;

    let floor_irradiance_srgb   = lin_to_srgb(in_irradiance);
    let objects_irradiance_srgb = lin_to_srgb(object_irradiance);

    let final_floor   = in_floor_diffuse.xyz   * floor_irradiance_srgb;
    let final_walls   = in_walls_diffuse.xyz   * walls_irradiance;
    let final_objects = in_objects_diffuse.xyz * objects_irradiance_srgb;

    var out = vec4<f32>(final_floor, 1.0);
        out = vec4<f32>(mix(out.xyz, final_walls.xyz, in_walls_diffuse.w), 1.0);
        out = vec4<f32>(mix(out.xyz, final_objects.xyz, in_objects_diffuse.w), 1.0);

    return out;
}

It's very hacky, but might work for you. This is what I've got:

Screenshot 2024-04-13 at 10 05 37 PM Screenshot 2024-04-13 at 10 04 48 PM
morr commented 5 months ago

@zaycev thanks for quick response! The demo looks better with such fixes.

In my app I started getting black screen after changed shaders code, but this looks like my own fault, probably I did set up something incorrectly.

zaycev commented 5 months ago

In my app I started getting black screen after changed shaders code, but this looks like my own fault, probably I did set up something incorrectly.

Naga should tell you in the log if you have syntax error in shaders.

csandven commented 1 month ago

@morr were you able to fix this? I am experiencing the same issue.

tigerplush commented 1 month ago

@csandven the krypta example now supports zooming and I can't reproduce this issue - can you check if it's still available with 0.8.0 or 0.8.1?