Light and Shadow
Light and Shadow are two separate things in 3D engines, although we percieve them together in real life:
- Lighting means that an object is brighter on the side facing the light direction, and darker on the backside. Computationally, this is relatively easy.
- Lighting does not mean that objects cast a shadow on the floor or other objects: Activating shadow processing is an additional step described here. Since casting shadows has an impact on performance, drop shadows and ambient occlusion shading are not activated by default.
Light Sources and Colors
You can add several types of light sources to a scene using rootNode.addLight(mylight).
The available light sources in com.jme3.light are:
- SpotLight
- PointLight
- AmbientLight
- DirectionalLight
You control the color and intensity of each light source. Typically you set the color to white (new ColorRGBA(1.0f,1.0f,1.0f,1.0f) or ColorRGBA.White), which makes all scene elements appear in their natural color.
You can choose to use lights in other colors than white, or darker colors. This influences the scene's atmosphere and will make the scene appear colder (e.g. ColorRGBA.Cyan) or warmer (ColorRGBA.Yellow), brighter (higher values) or darker (lower values).
You can get a list of all lights added to a Spatial by calling getWorldLightList() (includes inherited lights) or getLocalLightList() (only directly added lights), and iterating over the result.
PointLight
A PointLight has a location and shines from there in all directions as far as its radius reaches. The light intensity decreases with increased distance from the light source. A PointLight can at the moment not be used for casting shadows (using the PssmShadowRenderer - read more about this below).
Typical example: Lamp, lightbulb, torch, candle.
PointLight lamp_light = new PointLight(); lamp_light.setColor(ColorRGBA.Yellow); lamp_light.setRadius(4f); lamp_light.setPosition(new Vector3f(lamp_geo.getLocalTranslation())); rootNode.addLight(lamp_light);
DirectionalLight
A DirectionalLight has no position, only a direction. It sends out parallel beams of light and is considered "infinitely" far away. You typically have one directional light per scene. A DirectionalLight can be used together with shadows.
Typically example: Sun light.
DirectionalLight sun = new DirectionalLight(); sun.setColor(ColorRGBA.White); sun.setDirection(new Vector3f(-.5f,-.5f,-.5f).normalizeLocal()); rootNode.addLight(sun);
SpotLight
A SpotLight sends out a distinct beam or cone of light. A SpotLight has a direction, a position, distance (range) and two angles. The inner angle is the central maximum of the light cone, the outer angle the edge of the light cone. Everything outside the light cone's angles is not affected by the light.
Typical Example: Flashlight
SpotLight spot = new SpotLight(); spot.setSpotRange(100f); // distance spot.setSpotInnerAngle(15f * FastMath.DEG_TO_RAD); // inner light cone (central beam) spot.setSpotOuterAngle(35f * FastMath.DEG_TO_RAD); // outer light cone (edge of the light) spot.setColor(ColorRGBA.White.mult(1.3f)); // light color spot.setPosition(cam.getLocation()); // shine from camera loc spot.setDirection(cam.getDirection()); // shine forward from camera loc rootNode.addLight(spot);
If you want the spotlight to follow the flycam, repeat the setDirection(…) and setPosition(…) calls in the update loop, and kee syncing them with the camera position and direction.
AmbientLight
An AmbientLight simply influences the brightness and color of the scene globally. It has no direction and no location and shines equally everywhere. An AmbientLight does not cast any shadows, and it lights all sides of Geometries evenly, which makes 3D objects look unnaturally flat; this is why you typically do not use an AmbientLight alone without one of the other lights.
Typical example: Regulate overall brightness, tinge the whole scene in a warm or cold color.
AmbientLight al = new AmbientLight(); al.setColor(ColorRGBA.White.mult(1.3f)); rootNode.addLight(al);
Example:
mylight.setColor(ColorRGBA.White.mult(1.3f));
Light Follows Spatial
You can use a com.jme3.scene.control.LightControl to make a SpotLight or PointLight follow a Spatial. This can be used for a flashlight being carried by a character, or for car headlights, or an aircraft's spotlight, etc.
PointLight myLight = new PointLight(); rootNode.addLight(myLight); LightControl lightControl = new LightControl(myLight); spatial.addControl(lightControl); // this spatial controls the position of this light.
Obviously, this does not apply to AmbientLights, which have no position.
Simple Lighting
Full sample code:
For Simple Lighting we use Geometries with Materials based on Lighting.j3md (learn more about Materials here). Lighting.j3md-based materials dynamically support Shininess, and Ambient, Diffuse, and Specular light if there is a light source present. Note that this lighting method alone does not make the Geometry cast a shadow onto other Geometries automatically (see below for how to add drop shadows etc).
Geometry teapot = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); TangentBinormalGenerator.generate(teapot.getMesh(), true); Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); mat.setBoolean("m_UseMaterialColors", true); mat.setColor("m_Ambient", ColorRGBA.Orange); mat.setColor("m_Diffuse", ColorRGBA.Orange); mat.setColor("m_Specular", ColorRGBA.White); mat.setFloat("m_Shininess", 12); rootNode.attachChild(teapot);
The above example uses material colors and no textures. You can of course also use Lighting.j3md to create a lit Material that uses Texture Maps. The following example uses Shininess, Diffuse Map and Normal Map (a.k.a Bump Map).
Sphere rock = new Sphere(32,32, 2f); Geometry shiny_rock = new Geometry("Shiny rock", rock); rock.setTextureMode(Sphere.TextureMode.Projected); // better quality on spheres TangentBinormalGenerator.generate(rock); // for lighting effect Material mat_lit = new Material( assetManager, "Common/MatDefs/Light/Lighting.j3md"); mat_lit.setTexture("m_DiffuseMap", // surface color assetManager.loadTexture("Textures/Terrain/Pond/Pond.png")); mat_lit.setTexture("m_NormalMap", // surface bumps assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png")); mat_lit.setFloat("m_Shininess", 5f); // surface smoothness [1,128] shiny_rock.setMaterial(mat_lit); rootNode.attachChild(shiny_rock);
These light effects update live when the object or light source moves. If you shine a colored PointLight at this object, you will see a light reflection tinged in the color of the PointLight.
BasicShadowRenderer
Full code sample
Use the Shadow Renderer to make Geometries with Lighting.j3md-based Materials cast and receive basic drop shadows. This fast and simple implementation of a shadow effect is good for scenes with flat floors, but looks less realistic on uneven terrains. To use it, you add a jME SceneProcessor named com.jme3.shadow.BasicShadowRenderer to the viewPort.
BasicShadowRenderer bsr; ... public void simpleInitApp() { ... bsr = new BasicShadowRenderer(assetManager, 256); bsr.setDirection(new Vector3f(-.5f,-.5f,-.5f).normalizeLocal()); // light direction viewPort.addProcessor(bsr); ...
Shadow calculations (cast and receive) have a performance impact, therefor we recommend to use them smartly. Switch off the default shadow mode for the whole scene graph, and then specify the shadow behaviour individually for every scene node that needs shadows: You specifiy whether it casts shadows, receives shadows, both (slower), or neither (faster).
rootNode.setShadowMode(ShadowMode.Off); // reset all wall.setShadowMode(ShadowMode.CastAndReceive); // normal behaviour (slow) floor.setShadowMode(ShadowMode.Receive); // can't see shadow cast below floor anyway... airplane.setShadowMode(ShadowMode.Cast); // nothing casts shadows onto airplane anyway... ghost.setShadowMode(ShadowMode.Off); // ghost is translucent anyway...
DirectionalLightShadowRenderer
Full sample code
DirectionalLight sun = new DirectionalLight(); sun.setColor(ColorRGBA.White); sun.setDirection(cam.getDirection()); rootNode.addLight(sun); /* Drop shadows */ final int SHADOWMAP_SIZE=1024; DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 3); dlsr.setLight(sun); viewPort.addProcessor(dlsr); DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE, 3); dlsf.setLight(sun); dlsf.setEnabled(true); FilterPostProcessor fpp = new FilterPostProcessor(assetManager); fpp.addFilter(dlsf); viewPort.addProcessor(fpp);
Parallel-Split Shadow Map (deprecated)
Full sample code
The more advanced PSSM shadow renderer can cast real-time shadows, even on curved surfaces such as terrains. It is a bit more resource hungry than the BasicShadowRenderer. To activate PSSM drop shadows, add a jME SceneProcessor named com.jme3.shadow.PssmShadowRenderer to the viewPort. PSSM stands for the Parallel-Split Shadow Map technique.
private PssmShadowRenderer pssmRenderer; ... public void simpleInitApp() { .... pssmRenderer = new PssmShadowRenderer(assetManager, 1024, 3); pssmRenderer.setDirection(new Vector3f(-.5f,-.5f,-.5f).normalizeLocal()); // light direction viewPort.addProcessor(pssmRenderer);
The constructor expects the following values:
- Your assetManager object
- The size of the rendered shadowmaps (512, 1024, 2048, etc…)
- The number of shadow maps rendered (the more shadow maps, the more quality, the less FPS).
You can set the following properties on the pssmRenderer object:
- setDirection(Vector3f) – the direction of the light
- setLambda(0.65f) – Factor to use to reduce the split size
- setShadowIntensity(0.7f) – shadow darkness (1 black, 0 invisible)
- setShadowZextend() – distance how far away from camera shadows will still be computed
As said above, it's more efficient to specify individual shadow behaviour for each Geometry.
teapot.setShadowMode(ShadowMode.CastAndReceive); terrain.setShadowMode(ShadowMode.Receive);
Screen Space Ambient Occlusion
Full sample code
- jme3/src/test/jme3test/post/TestSSAO.java, jme3/src/test/jme3test/post/TestSSAO2.java – Screen-Space Ambient Occlusion shadows
- jme3/src/test/jme3test/post/TestTransparentSSAO.java – Screen-Space Ambient Occlusion shadows plus transparancy
Ambient Occlusion refers to the shadows that nearby objects cast on each other under an ambient lighting. It‘s an approximation of how light radiates in a real life scene. To activate Ambient Occlusion shadows, add a jME SceneProcessor named com.jme3.post.SSAOFilter to the viewPort. SSAO stands for the Screen Space Ambient Occlusion technique.
FilterPostProcessor fpp = new FilterPostProcessor(assetManager); SSAOFilter ssaoFilter = new SSAOFilter(12.94f, 43.92f, 0.33f, 0.61f); fpp.addFilter(ssaoFilter); viewPort.addProcessor(fpp);









