DirectionalLight position has meaning?

Hi, just wondering about the directional light. I used the default scene setup as is used in the animation blending demo in Babylon101 and setup a directional light. I got this result when I didn’t set the position:

var directionalLight: DirectionalLight = new DirectionalLight("light2", new Vector3(0, -0.5, -1.0), scene);

So, a diagonally, downward direction. I did NOT define a position and got THIS result:


I then provided a position:

directionalLight.position = new Vector3(0, 5, 5);

… and got the following result:


Just curious what’s going on here? What causes this? Maya and all the other apps have taught me that DirectionalLights are infinite, it doesn’t matter where you put them (except maybe in Max cos it works within a finite space) so I am wondering what is causing shadows to be culled so strangely just because of the lightPosition?

1 Like

You need the light position to build the light view matrix used by the shadow generator to render the scene in the shadow map.

Even if using an orthographic projection in the end (as you do for directional lights), you still need a view matrix to transform the geometry to the light coordinate system (where the “look at” vector corresponds to the light direction for directional lights). To build this matrix, you really need a position!

Often you can read explanations about directional lights that says you don’t need a position, but if you look at their code, you can see they use a (0,0,0) position to build their view matrix… Or in some others they don’t explicitly put values in the translation part of the matrix, but it’s still 0 values you get there by default.

This position defines where the light frustum is positionned in the world. See this PG:

The colored box is the light frustum and you can see the light gizmo representing the light position and direction. Everything in this box (and only things in this box) will be rendered in the shadow map.

It’s not very obvious in the screenshot, but the light position lies on the near plane. It’s because I set light.shadowMinZ = 0 (I have also set light.shadowMaxZ = 3, which is the distance to the far plane of the light frustum). If I change the value, the near plane (and the light frustum) will be moved accordingly (the light position being still the same):

Here shadowMinZ = 0.5. As you can see, a part of the sphere is now outside of the box and so is not rendered in the shadow map, making the shadow wrong. I could also have moved the light without changing the shadowMinZ value for the same result.

You can play in the inspector by changing the position/direction of the light as well as the shadowMinZ / shadowMaxZ values of the shadow generator to better see how it works.

By default, the x and y extents of the light frustum (the position of the left/right/top/bottom planes of the frustum) are automatically computed by Babylon because light.autoUpdateExtends = true. You can set this property to false, but I can’t see a clean way of updating the ortho left/right/top/bottom values manually because those properties are private (probably something to correct).

The values for the near/far planes are stored in shadowMinZ and shadowMaxZ, properties that you can change (as in the PG). You can also let Babylon compute them automatically by setting light.autoCalcShadowZBounds = true (false by default). Note that when Babylon computes the bounds automatically, it does so by taking into account only the objects that are shadow casters! That’s why in the screenshots the light frustum does not encompass the ground, which is not a shadow caster but only a receiver.


Normally, to know if a point is in shadow, you compute its projection into the light frustum, and if it is inside you compare its depth against the depth corresponding to this position in the shadow map. So, if the point is NOT inside the light frustum, it is not considered shadowed and should be fully lit.

Look at this screenshot again:

According to the explanations above, the points of the ground that are not inside the cube should not be shadowed! They still are because the shadowing code does not apply a rejection based on the depth, only on the x/y coordinates: if the point is inside the frustum according to the left/right/top/bottom planes it’s ok, even if the point is farer than the far plane (or nearer than the near plane).

HOWEVER, that’s not the case for the PCF/PCSS filtering methods, they do take into account the depth for the rejection test (for historical reasons I think). Same screenshot than above but with PCF this time:

As you can see, the shadows stop at the frustum boundaries. To correct the problem, you need to increase the light shadow far plane distance (light.shadowMaxZ).

So at this point, you ask: why not setting shadowMinZ to a very small value (-1e10) and shadowMaxZ to a very big value (1e10) to get rid of those problems? We can even set the left/right/top/bottom properties to very small/big values and call it a day, no problems anymore with directional lights, our frustum is always big enough to contain all the objects of the scene.

The problem is that you loose details/precision in the shadow map. The bigger your frustum (in x/y directions), the more objects will be projected to the same pixels in the shadow map, so the less details. The more stretched your frustum (in z direction), the less precision you have on the depth buffer as bigger ranges of Z values will have to be mapped to the [0, 1] range used for the final rendering.

Here’s a screenshot with shadowMinZ=-50000 and shadowMaxZ=50000 (PCF filtering):

As you can see the shadow is wrong. The object (sphere) is very simple and the artifacts are not really visible (except that the size is wrong), but with other objects you will get much stronger artifacts. Also:

It’s the biggest artifact possible, the shadow disappeared! I have set shadowMinZ=-100000.000 and shadowMaxZ=5 without filtering methods.


My gosh, thank you 1000x and more for that stellar response and for the time it would have taken you to provide it :open_mouth:

I understand now, thank you!!!

This explanation should be in the official docs. Thank you very much!

Yup a doc update or link to this post would be great @Evgeni_Popov :slight_smile:

Updated doc on its way: