2D lights and shadows sample

Adding lightsources and objects casting shadows can really add to the visual appeal of a game. Tutorials and examples of this effect are fairly common (example: https://github.com/mattdesl/lwjgl-basics/wiki/2D-Pixel-Perfect-Shadows) and for Defold users @d954mas and Sayuris1 have shared their solutions here and on Discord:

We’ve taken the project from Sayuris1, made a few improvements and packaged into a reusable sample project which can be added to any Defold project in just a few steps. Here’s the sample project:

And a demo: Lights 1.0 (move with arrow keys)

28 Likes

:+1:
(can’t load your demo in safari on both mac and iPhone)

2 Likes

confirming it doesn’t work on Safari

Develop > Experimental Features > WebGL 2.0 (and may be WebGPU)

1 Like

It’s cool that now there’s an example of this. It works in Firefox, but the shadows are updated with some delays and shifts. Is this intended as part of the “rude example” or is it a bug?

1 Like

I’ve updated one of the shaders. It should work now: Lights 1.0

This is caused by the size of the occluder map being too small. I’ve updated the example to pick a size more appropriate to the screen resolution.

8 Likes

it works now, but without shadows in Safari (Chrome is fine).

1 Like

It’s working in Safari 14.1.2 with shadows for me. FWIW I actually find the performance in Safari is better than that in Chrome.

1 Like

works for me know (I’m not sure what it was and why)
UPD: Hmm, I don’t understand what’s going on

1 Like

Amazing! Thank you!
I use it on my game. It works! I’ll read the render light config to apply on my own render later.
Two questions:

  1. Why the light stay on scene after a restart / reload (video) and how to remove it?

  1. I should create two separated tilemaps to achieve a good result (one with the material and one without). Is there any other solution? Extra tilemap will affect the performance? (this game is really simple so I don’t mind but I’d like to know it)

thank you again!

Ah, you found a bug! The lightsource.script did not remove the light when deleted. I’ve pushed a new version of the sample project that fixes this.

If you want only certain tiles to cast shadows then yes, you need two tilemaps, one with the light_occluder_tile_map.material and one without.

As with all other components each additional component you add will have an impact. But usually it’s only a small impact. You should measure it using the profiler to be sure.

Yes, maybe. The tilemap material (or rather the shader program) could perhaps be modified such that it doesn’t render tilemap layers with a certain z-value. You could create your tilemap so that layers with a z-value larger than some value will cast shadows while others won’t.

2 Likes

I changet 0.5 to 1 in default_size = default_size * 0.5 and now everything works fine. I don’t know why, but 0.5 is not the correct value for my display.

There are also some strange things in the scene editor. Hm…

1 Like

Yeah, it was a quick tweak to make some kind of best guess, but I think it’s better to pass a size that works for your project like this instead:

lights.init({ render_target_size = 512 })

I set render_target_size to 1368 in the example project, is that correct? This is the maximum game resolution in the project settings.

There is also a point that if you set the lighting radius of 1024 or more then there will be noticeable inaccuracies in the raycasting of black figures, they become rude. How would it be possible to adjust their accuracy higher?

It’s going to be pretty expensive to calculate lights with this kind of radius on such a massive render target.

The problem with precision of shadows is caused by a light diameter which is larger than the render target.

1 Like

Two additional articles that might help someone:

10 Likes

This sample has been updated with some additional functionality and fixes:

  • Lights of different sizes are now rendering properly
  • The light falloff property was previously ignored
  • The light falloff is now applied only to the alpha channel
  • Lights can now be temporarily disabled

A big thank you to @Haath for contribution of light falloff fixes.

The HTML5 demo project has been updated: Lights 1.0

18 Likes

Is there any way to change the way sprites are rendered in shadows, I.E. making sprites/pixels invisible when they are covered by a shadow?

Depends what exact effect you would like to have, but one idea is that you draw shadows and occluders after you draw other sprites (so the sprites are covered by shadows). For more smooth effect you can change tint of sprites covered by shadows depending on their position after Occluder, but there are multiple solutions for this that come too my mind, don’t know which one would be the best :sweat_smile: I wonder if there are some other interesting solutions

This repository is mostly just a sample for inspiration. For specialized functionality like this you should play with the render script and shaders. I’ve actually already experimented with exactly what you’re describing to create some sort of fog-of-war that hides shadowed objects.

varying mediump vec2 var_texcoord0;

uniform lowp sampler2D tex0;
uniform lowp sampler2D tex1;
uniform lowp vec4 tint0;
uniform lowp vec4 tint1;

void main()
{
    vec4 tint0_pm = vec4(tint0.xyz * tint0.w, tint0.w);
    vec4 tint1_pm = vec4(tint1.xyz * tint1.w, tint1.w);

    vec4 color0 = texture2D(tex0, var_texcoord0.xy) * tint0_pm;
    vec4 color1 = texture2D(tex1, var_texcoord0.xy) * tint1_pm;

    if (color0.a > 0.1 && color1.a < 0.5)
    {
        discard;
    }

    gl_FragColor = color0;
}

Here I’m drawing the shadow-clipped sprites together with the lights, and discard pixels where the lights texture (color1) has alpha less than 0.5, which in this case is slightly above the ambient light alpha of my game.

5 Likes