[SOLVED] Spotlight shines through object

I am finding my spotlight shining through an object and expected the texture to block light.

I have a spotlight placed above (at 10f-on-y) a sphere (radius 1f at 1.3f-on-y). The inner/outer angles are small (2/5 degs_to_rad). I see the spotlight on the top and just underneath. I expect light inside an object to shine out because the inside of a mesh has no texture. My object has DiffuseMap, NormalMap, Diffuse, Specular, CastAndReceive, etc. My SpotLightShdowRenderer has EdgeFilteringMode - Nearest, etc.

Any ideas before I post all the gory code?

1 Like

What are the shadow modes of the sphere and the horizontal surface?

(And few bits more for context. I used a dim directional light to see the area)

sphere.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
floor.setShadowMode(RenderQueue.ShadowMode.Receive);
sun.setColor(ColorRGBA.White.mult(0.1f)); // dim
DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, 1024, 3);
SpotLightShadowRenderer slsr = new SpotLightShadowRenderer(assetManager, 1024);
slsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
slsr.setShadowIntensity(0.6f);
2 Likes

The only oddity that stands out (for me) is having both a DirectionalLightShadowRenderer and a SpotLightShadowRenderer. But I don’t think that’s causing the issue shown in the screenshot.

Do you invoke setLight() on either or both of the shadow renderers? (It’s not shown in your code excerpt.)

In these cases it’s usually a good idea to put together a single class test case that illustrates the issue.

For a lot of problems folks encounter, the test case will work fine already and give them a place to start from. For the others, it gives something convenient to post to the forum so we don’t play a game of 20 questions trying to zero in on the real problem. (And if it turns out to be a bug somewhere in the engine, then we have a ready made test case already.)

1 Like

Great! I didn’t want to post code and have someone create a project for it, if the post prompted the ‘aha’ answer. But once questions start popping up … I’ll pair it all down to a ‘class Main’ that reproduces the issue.

1 Like

Always a good idea.

When I’ve been really stumped, that’s usually the first thing I do before posting a question… consequently, in like 10 years here I’ve posted maybe three questions because most of the time I cannot reproduce my issues in an isolated test case. (Unless it’s a JME bug and then I have the keys to just fix it. :))

It doesn’t hurt to post a probe question as you’ve done but specific problems usually are the result of specific code.

1 Like

[EXAMPLE] Create a JME3 SDK 3.3 project as:
File > New Project… > JME3 > BasicGame
{project} > Libraries > Add Library… > jme3-test-data
or
File > New Project… > JME3 > Basic Gradle Game
{project} > Build Scripts > build.gradle
project.ext { jmeVer = ‘3.4.0-stable’ }
then
Replace Source Package > [com].mygame > Main.java with the code below.
Set useRenderers as desired and it will use Renderers or Filters.

package mygame;

import com.jme3.app.DebugKeysAppState;
import com.jme3.app.FlyCamAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.app.StatsAppState;
import com.jme3.light.DirectionalLight;
import com.jme3.light.SpotLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.shadow.SpotLightShadowFilter;
import com.jme3.shadow.SpotLightShadowRenderer;
import com.jme3.texture.Texture;
import com.jme3.util.TangentBinormalGenerator;

public class Main extends SimpleApplication {

    public Main() {
        super(new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState());
    }
    
    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    protected SpotLight spotlight;
    protected DirectionalLight sun;
    
    // Renderers or Filters?
    protected boolean useRenderers = true;

    protected boolean moveTheSun = false;
    
    @Override
    public void simpleInitApp() {
        // Create the sphere.
        Sphere sphereComp = new Sphere(32, 32, 1f);
        Spatial sphere = new Geometry("Sphere", sphereComp);
        TangentBinormalGenerator.generate(sphereComp);
        sphereComp.scaleTextureCoordinates(new Vector2f(4,4));

        Texture sphereTex = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg");
        sphereTex.setWrap(Texture.WrapMode.Repeat);
        Texture sphereMap = assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png");
        sphereMap.setWrap(Texture.WrapMode.Repeat);

        Material sphereMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        sphereMat.setTexture("DiffuseMap", sphereTex);
        sphereMat.setTexture("NormalMap", sphereMap);
        sphereMat.setBoolean("UseMaterialColors",true);
        sphereMat.setColor("Diffuse",ColorRGBA.LightGray);
        sphereMat.setColor("Specular",ColorRGBA.Blue);
        sphereMat.setFloat("Shininess", 4f); // [0,128]

        sphere.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
        sphere.setMaterial(sphereMat);
        sphere.setLocalTranslation(4f, 1.3f, 4f);
        rootNode.attachChild(sphere);

        // Create the floor.
        Box floorComp = new Box(10f, 0.1f, 10f);
        Spatial floor = new Geometry("Floor", floorComp);
        TangentBinormalGenerator.generate(floorComp);
        floorComp.scaleTextureCoordinates(new Vector2f(16,16));
        
        Texture floorTex = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg");
        floorTex.setWrap(Texture.WrapMode.Repeat);
        Texture floorMap = assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png");
        floorMap.setWrap(Texture.WrapMode.Repeat);

        Material floorMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        floorMat.setTexture("DiffuseMap", floorTex);
        floorMat.setTexture("NormalMap", floorMap);
        floorMat.setBoolean("UseMaterialColors",true);
        floorMat.setColor("Diffuse",ColorRGBA.White);
        floorMat.setColor("Specular",ColorRGBA.White);
        floorMat.setFloat("Shininess", 1f);  // [0,128]
        
        floor.setShadowMode(RenderQueue.ShadowMode.Receive);
        floor.setMaterial(floorMat);
        floor.setLocalTranslation(0f, 0.2f, 0f);
        rootNode.attachChild(floor);

        // Create the sun.
        sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-10,-10,-10).normalizeLocal());
        sun.setColor(ColorRGBA.White.mult(0.1f));
        rootNode.addLight(sun);

        // Create the spotlight.
        spotlight = new SpotLight(
            new Vector3f(4f, 10f, 4f),
            new Vector3f(0f, -1f, 0f),
            100f,
            ColorRGBA.Green.mult(1.5f),
            2f * FastMath.DEG_TO_RAD,
            5f * FastMath.DEG_TO_RAD
        );
        rootNode.addLight(spotlight);

        if (useRenderers) {

            // Create the sun's shadowing.
            DirectionalLightShadowRenderer dlsr =
                new DirectionalLightShadowRenderer(assetManager, 1024, 3);
            dlsr.setLight(sun);
            viewPort.addProcessor(dlsr);

            // Create the spotlight's shadowing.
            SpotLightShadowRenderer slsr = new SpotLightShadowRenderer(assetManager, 1024);
            slsr.setLight(spotlight);
            slsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
            slsr.setShadowIntensity(0.6f);
            viewPort.addProcessor(slsr);
            
        } else { // use filters
            
            // Create the sun's shadowing.
            DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 1024, 3);
            dlsf.setLight(sun);
            dlsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
            dlsf.setShadowIntensity(0.6f);

            SpotLightShadowFilter slsf = new SpotLightShadowFilter(assetManager, 1024);
            slsf.setLight(spotlight);
            slsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
            slsf.setShadowIntensity(0.6f);

            FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
            fpp.addFilter(dlsf);
            fpp.addFilter(slsf);
            // fpp.setFlushShadowQueues(false); // deprecated and removed

            viewPort.addProcessor(fpp);
        }
        
        cam.setLocation(new Vector3f(2f,1f,8f));
        cam.setRotation(cam.getRotation().fromAngleAxis(FastMath.PI*7f/8f, Vector3f.UNIT_Y));
    }

    protected float angle = 0;
    protected Vector3f baseDirection = new Vector3f(10f, -10f, 10f);
    
    @Override
    public void simpleUpdate(float tpf) {
        if (moveTheSun) {
            Matrix3f m = new Matrix3f();
            m.fromAngleAxis(angle, Vector3f.UNIT_Y);
            sun.setDirection(m.mult(baseDirection));
            angle += FastMath.PI/1024;
        }
    }
}
3 Likes

Is anyone able to confirm the behavior? Or see a missed setting in the source?

  • No rush; just bumping in case someone thought someone else was looking into it.
1 Like

I plan to take a closer look at this soon.

EDIT:

  • I’ve reproduced the issue.
  • I don’t see anything obviously wrong with the sample app.
  • Currently I’m trying to determine why TestSpotLightShadows (in jme3-examples) behaves differently from the sample app.

EDIT #2:

The proximate cause of the issue was that the shadow intensity was set rather low (0.6f). Increasing it to 1f will cause the green spot under the sphere to disappear.

Judging from the sample app provided, I worry you may have unrealistic hopes for JME’s built-in shadow filters and post-processors. JMonkeyEngine shadows are not ray traced; they are more like semi-transparent black spots painted onto shadow receivers.

To the best of my knowledge, JME can’t provide a high degree of verisimilitude when multiple lights of different colors illuminate a scene. However, lighting is not my specialty. If I’m mistaken, someone please chime in!

I used the slsr.setShadowIntensity(0.6f); from an example and didn’t think to try better values. 1f does indeed give an expectedly dark shadow! :sunglasses:

Just experimenting, I thought I’d try some shadows, since I want some spotlights to shine on frames on walls and give shadows. I am finding quite good results and my ‘hopes’ are good as well, :smiley: Thank you.

Should I mark this post Solved, or do you want to create a Bug Report and mark it that way? The setShadowIntensity setting is a viable workaround.

1 Like

I think the reason a shadow intensity of 0.6 might not have worked so well is because of how bright the light is in this case.

…but it’s also hard to say for sure without experimenting.

Edit: and by the way, just in case it wasn’t just a test color, pure colored lights may have strange effects if you are not expecting them. For example, a pure green light shining on a pure blue cube will give no light at all… which can be counter-intuitive until you think about it. (Every few years I get tripped up but that same thing when I throw a pure red light value into a shader or test app and wonder why my blue cubes are all unlit… ;))

1 Like

In my investigation, I didn’t uncover any bugs in the Engine.

I think @pspeed is correct that brighter lights require higher shadow intensities in order to look right. Since you’re satisfied with that solution, this topic is solved.

Thank you! And it makes sense that pure color interactions are not only bad (technically) but unrealistic.

1 Like