Easy shadows (in editor, at design-time, too) by shadow volumes (and shadow maps are coming as well)

Easy shadows
Easy shadows

You can now activate shadows by shadow volumes by toggling a trivial boolean property Shadows at the light source to true! This works (also) in editor at design-time.

This makes the shadow volumes we have documented here easily available, finally. No more messing around with X3D lights nodes.

A simple demo is in examples/viewport_and_scenes/shadows.

We also publish TCastleTransform.CastShadows property to control if some object casts shadows.


This is just the beginning of “really easy shadows”. I enjoy how simple API we have (just a checkbox really!) and how great it is to play with shadows in the editor. But we can improve the functionality underneath now 🙂

  1. The most important TODO here is that we plan to expose shadow maps in the same way. Actually shadow maps should be in the future the default algorithm activated by Shadows (and toggling between shadow maps and shadow volumes should be done using an independent property like ShadowMode = smShadowMaps, smShadowVolumes).

    Shadow maps have a number of advantages — they do not require the shadow caster to be 2-manifold, they already work on both desktop and mobile (OpenGLES), they can be applied on multiple light sources independently with correct result.

    While we had shadow maps implemented for years, with some impressive demos (see features section) but they do not work (yet!) across scenes, which means that the light that casts shadow must be in the same glTF/X3D file as the shadow receiver. This makes them not suitable to use shadow maps on our light nodes.

    The plan is to, well, remove this limitation (#284). Shadow maps should work cross-scene, they should not transform the X3D graph (whole work done by CastleInternalShadowMaps should be removed) and the renderer should just take and use at rendering a shadow map information attached to any light (and the renderer should add using that shadow map to particular shape).

  2. Shadow volumes for now carry a few limitations:

    • The unfixable limitation is that shadow caster has to be 2-manifold, i.e. every edge must have exactly 2 neighboring faces, so the whole shape is a closed volume. Both CGE and 3D authoring tools like Blender help you modeling such shapes — see Make 3D models of shadow casters geometry to be 2-manifold.

    • The current limitation of shadow volumes in CGE is that we allow them from only one light (so set Shadows to true only on a single light!)

      We could improve that, though note that it will increase the number of rendering passes, in general you need to do 2^shadow_volumes_lights passes. So this technique is really not feasible for multiple lights. (Shadow maps scale much better.)

    • A temporary limitation is that shadow volumes do not render properly on mobile (OpenGLES), we have a PR in progress to address that.


Easy shadows looks very good. When will this be available?

It is available already! :slight_smile:

Most things we announce are available immediately, unless I specifically write they are only on some branch/PR. Just head over to our downloads on Download | Castle Game Engine , they point now to “7.0-alpha.snapshot” version (already newer than “7.0-alpha.2”). This “snapshot” version is automatically updated whenever the “master” branch changes, after it passes a lot of automatic tests (it takes ~6h to make sure it compiles and all tests pass everywhere).

It already contains working shadows, including the examples/viewport_and_scenes/shadows .

You can always take a look at Release Latest release of Castle Game Engine (Auto-Updated Snapshot) · castle-engine/castle-engine · GitHub to see when were the last snapshot files updated (now it says “3 hours ago”) and what commit is there (now it is Add zero vectors to initialize tex coords in TCastleImageTransform · castle-engine/[email protected] · GitHub , the very last commit I did yesterday to “master”).

I see this method effects self-shadowing, as well. Lovely!

If you can increase this to 2 lights, it would allow for more impactful effects, because your user could use one for, say, the sun, and the second reserved for special effects like explosions or car headlights. You definitely want things like explosions and headlights to cast shadows because they look very weird without it.

If you could make this a dynamic thing per object, such as determined by the most intense lightsource nearby, or to multiply the amount of shadows per object based on relevant lightsources, it would make it possible for scenes like large medieval war-camps with lamps and firepits to have rich cast-shadow effects all around that move appropriately with the characters.

But this is an exciting start! Keep up the awesome work, CGE is becoming more impressive with everything you do!

Thank you for the good words, glad you like it!

As for incoming improvements to shadows:

  1. On top of my priority is now to also enable “shadow maps” in the same way (i.e. the trivial checkbox Shadows should turn them on, and some additional property like ShadowMode should control the algorithm - shadow volumes or shadow maps).

    We have shadow maps working already: Shadow Maps | Castle Game Engine but they do not yet work “across scenes” (they only work if the light source is part of the same X3D/glTF file) due to limitation of my current implementation, so I cannot expose them as trivially on light sources by “just a checkbox”. I shall remove this limitation :slight_smile:

    Shadow maps would also enable to make the decision “which light sources cast shadows” dynamic, as you propose. This feature is not (easily) possible with shadow volumes, where the whole world needs to be rendered with all lights’ combinations (and then we just mask the appropriate parts using stencil buffer). With shadow maps, such decision would be possible, as each shadow receiver in shadow maps is calculated independently.

    Shadow maps would also allow even better self-shadowing :slight_smile: In case of shadow volumes, the self-shadowing may sometimes show small artifacts (due to the fact that shadows are determined by silhouette of the shadow caster, which is derived from the mesh edges), like on the screenshot below.

    (no shadows)

    (shadow volumes with artifacts due to self-shadowing outlined)

    With shadow maps, you get a “bias” parameter to control this. With shadow volumes, there’s no way to add such bias without a performance cost (because if you shift the shadow quads, you will have to render additional geometry to make sure the shape is “closed”).

    And finally, you can use both shadow volumes and shadow maps in one world. That is, you will be able to e.g. have one bright light (like sun) cast hard shadows using shadow volumes, and the rest lights cast shadows using shadow maps.

  2. Extending shadow volumes to enable multiple lights is still useful of course.

    Shadow volumes e.g. are precise and have hard edges – which may be an advantage over shadow maps in some settings. E.g. shadow volumes are better to cast hard shadows like by a bright sun in the middle of sunny day.

1 Like


I tried the shadow with my characters (GLTF). It doesn’t work. The characters are in a correct shape (2-manifold), but I used more then one material in blender to set the colors.
After rebuilding to one material with image and uv-editing it works.
Is this behaviour correct? Is the limit only one material?

Unfortunately yes.

This is a consequence of limit " each shape must be 2-manifold" mentioned on Shadow Volumes | Castle Game Engine . In glTF it means “each glTF primitive must be 2-manifold” (glTF primitive corresponds to X3D shape). When exporting, Blender splits your object (with multiple materials) into multiple glTF primitives.

So, it would be more precise to say there “each glTF primitive (which is a subset of Blender mesh, limited to a single material)” must be 2-manifold.

It is possible to counter this limitation in the future. We could consider, for shadow volumes, larger units – and then only such “larger unit” (like whole Blender object) would need to be 2-manifold.