Friday, March 22, 2013

Three.js Directional Light Shadow Boxes

‹prev | My Chain | next›

I think that I am closing in on understanding light in Three.js. I have been working with DirectionalLight, which I had expected to behave like the Sun—light rays from both should be parallel no matter where you move around. What I had not understood was the importance of the “shadow box”—the box inside which shadows are rendered. I also failed to grasp the various properties of shadow box. Tonight, I hope to rectify the situation.

From previous experience in other environments, I expect that there is not an origin point for directional light. The x, y, and z coordinates supplied to a directional light describe the direction of the light. For instance, light at (1, 1, 1) would be coming from the right, up, and in front of the scene. It would be exactly the same thing to have a directional light from (100, 100, 100). The light would still be coming from equal parts to the right, up, and in front of the scene.

Anyhow, that I understood. What I did not was the shadow box. Until yesterday, I had not even realized that the shadow box bounded the portion of the scene in which shadows would occur (I assumed that shadows would be everywhere). Even then, I had trouble understanding how the shadow box got positioned. Three.js includes a debugging property for lights, shadowCameraVisible that helps:



The problem with this shadow box is that it only encompasses half of my river game (the camera is rotated to get better view). I am not concerned about shadows on the grass, but it would look odd for shadows to come and go as the raft makes its way down the winding river.

What I need to do is shift the light's shadowCameraNear property back around 250 (the entire river and grass is 500 by 500). Instead of doing that, I can shift the origin of the light back to (250, 250, 250):
    var sunlight = new THREE.DirectionalLight();
    sunlight.position.set(250, 250, 250);
This does not change the direction of the directional light—it still comes from equal parts to the right, up and in front (again, it does not look like it because the camera was rotated). What this does give me is the ability to say that the shadowCameraNear property, which describes the distance from the light's position to the closest face of the shadow box, should be about 250. The opposite side, shadowCameraFar, should be 600 away from the light's position. This makes the thickness of the shadow box around 350, which ought to be sufficient to encompass the river.

Those two properties, along with other dimensions of the box can tightly positioned around the river with:
    var sunlight = new THREE.DirectionalLight();
    sunlight.position.set(250, 250, 250);
    sunlight.intensity = 0.5;
    sunlight.castShadow = true;
    sunlight.shadowCameraVisible = true;
    sunlight.shadowCameraNear = 250;
    sunlight.shadowCameraFar = 600;
    sunlight.shadowCameraLeft = -200;
    sunlight.shadowCameraRight = 200;
    sunlight.shadowCameraTop = 200;
    sunlight.shadowCameraBottom = -200;
The result is a pretty nice looking shadow box:



When I zoom in on the raft doing sweet jumps over sharks, I am not at all rewarded for my efforts. The shadows are barely visible:



It turns out that I need to set two more properties if I want nicely defined shadows: shadowMapWidth and shadowMapHeight. Both describe the resolution of the texture used to draw the shadows. The default of 512 for both is too small for my needs. After a bit of experimentation, I settle for 2048 for both:
  function addSunlight(scene) {
    var sunlight = new THREE.DirectionalLight();
    sunlight.position.set(250, 250, 250);
    sunlight.intensity = 0.5;
    sunlight.castShadow = true;
    sunlight.shadowDarkness = 0.9;
    sunlight.shadowMapWidth = sunlight.shadowMapHeight = 2048;
    sunlight.shadowCameraNear = 250;
    sunlight.shadowCameraFar = 600;
    sunlight.shadowCameraLeft = -200;
    sunlight.shadowCameraRight = 200;
    sunlight.shadowCameraTop = 200;
    sunlight.shadowCameraBottom = -200;

    scene.add(sunlight);
  }
Now I see nicely defined shadows—both under the raft and from the sides of the river:



It is unfortunate that so much code is required to describe sunlight. I think that I could get away with some it in 3D Game Programming for Kids. Things like castShadow are obvious. The purpose of intensity and shadowDarkeness are similarly easy (and also eminently play-able).

The position is a little trickier. Calling it the start point of a ray that will be parallel to all other light rays is a tough thing to understand. Much less would be a disservice to readers that might want to use directional lights in the future. I believe that I could make slightly better high-level descriptions of the various shadowCameraXxx properties. Mostly, it is a shame that so many lines need to be typed in a book that must treat code length as a premium.

Still, it is likely worth the cost. Those shadows really are nice.



Day #698

1 comment:

  1. Thanks for writing this. Your example had properties in it that weren't in any of the examples I'd found until now, which allowed my scene to start behaving as I'd expect!

    ReplyDelete