This example is meant to demonstrate how to set up shadows with pythreejs. It is mainly based on the example code you can find in the three.js documentation, but is adapted to highlight some of the nuances of using it from pythreejs.
First, we set up an example scene for exploring shadow behavior.
from pythreejs import *
import ipywidgets
from IPython.display import display
view_width = 800
view_height = 600
Create some example geometry in nice coder colors:
sphere = Mesh(
SphereBufferGeometry(1, 32, 16),
MeshStandardMaterial(color='red')
)
cube = Mesh(
BoxBufferGeometry(1, 1, 1),
MeshPhysicalMaterial(color='green'),
position=[2, 0, 4]
)
plane = Mesh(
PlaneBufferGeometry(10, 10),
MeshPhysicalMaterial(color='gray'),
position=[0, -2, 0],)
plane.rotation = (-3.14/2, 0, 0, 'XYZ')
Create camera and lighting:
camera = PerspectiveCamera( position=[10, 6, 10], aspect=view_width/view_height)
key_light = SpotLight(position=[0, 10, 10], angle = 0.3, penumbra = 0.1)
ambient_light = AmbientLight()
scene = Scene(children=[sphere, cube, plane, camera, key_light, ambient_light])
controller = OrbitControls(controlling=camera)
renderer = Renderer(camera=camera, scene=scene, controls=[controller],
width=view_width, height=view_height, antialias=True)
renderer
Now we can start playing around with the shadows in such a way that the results of the different options are immediately shown in the rendered scene.
First, set the spot light to track the cube position:
key_light.target = cube
Turn on shadows in the renderer:
renderer.shadowMap.enabled = True
renderer.shadowMap.type = 'PCFSoftShadowMap' # default PCFShadowMap
Even with shadow maps enabled, there are still no shadows. This is because three.js only includes those lights and objects that has been explicitly marked for shadows in its calculations. Let's turn on some of these:
# Enable shadows for the light
key_light.castShadow = True
# Enable casting/receiving shadows for some objects:
sphere.castShadow = True
cube.castShadow = True
plane.receiveShadow = True
Let's move the cube closer to the sphere:
cube.position = (0, 1, 2)
Note that the light changed to track the position of the cube. It is also clear that the shadow from the cube is not being taken into account on the sphere. As before, we can turn this on with receiveShadow
on the sphere, but we also need to mark the sphere material for an update. This is needed for any shadow changes after the first frame with shadows is rendered.
# Also enable sphere to receive shadow:
sphere.receiveShadow = True
sphere.material.needsUpdate = True
Finally, let's zoom in on the details of the shadows:
camera.position = [2.92, 1.75, 2.92]
camera.quaternion = [-0.18, 0.38, 0.076, 0.90]
Here, we can see that there is some pixelation of the shadows (although it is smoothed by using PCFSoftShadowMap
). This can be fixed by increasing the resolution of the shadow map:
key_light.shadow.mapSize = (2048, 2048)