Issues with SpotLight and Normal Maps

Hello,

I’m running into some issues regarding spotlights and normal maps. I’ve created a normal map through the JME editor and initialized it as a box using the following code:

Box b = new Box(width/2, height/2, WALL_LENGTH / 2);
Geometry box = new Geometry("Wall", b);
box.setShadowMode(ShadowMode.CastAndReceive);
TangentBinormalGenerator.generate(b);
box.setLocalTranslation(new Vector3f(loc.x + width / 2,
                                     loc.y + height / 2,
                                     loc.z + WALL_LENGTH / 2));
                                                                                    
Material mat = new Material(assetManager,
    "Common/MatDefs/Light/Lighting.j3md");
                                                                                    
// This asset is a 3 by 2 texture (3000 x 2000) px
mat.setTexture("DiffuseMap",
               assetManager.loadTexture("Textures/Terrain/Wall/moreBricks_d.jpg"));
                                                                                    
mat.setTexture("NormalMap",
               assetManager.loadTexture("Textures/Terrain/Wall/moreBricks_n.png"));
                                                                                    
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Diffuse", ColorRGBA.White);
mat.setColor("Specular", ColorRGBA.White);
mat.setFloat("Shininess", 0.1f);
box.setMaterial(mat);
                                                                                    
rootNode.attachChild(box);
                                                                                    
// make the object static
box.addControl(new RigidBodyControl(0));
physicsSpace.addAll(box);

I also initialize our light source (meant to look like a flash light) here:

flashLight1 = new SpotLight();
flashLight1.setSpotRange(38);
flashLight1.setSpotInnerAngle(0f * FastMath.DEG_TO_RAD);
flashLight1.setSpotOuterAngle(8f * FastMath.DEG_TO_RAD);
flashLight1.setColor(ColorRGBA.White.mult(lightInt1));
flashLight1.setPosition(cam.getLocation());
flashLight1.setDirection(cam.getDirection());
rootNode.addLight(flashLight1);
                                                                            
flashLight2 = new SpotLight();
flashLight2.setSpotRange(30);
flashLight2.setSpotInnerAngle(25f * FastMath.DEG_TO_RAD);
flashLight2.setSpotOuterAngle(34f * FastMath.DEG_TO_RAD);
flashLight2.setColor(ColorRGBA.White.mult(lightInt2));
flashLight2.setPosition(cam.getLocation());
flashLight2.setDirection(cam.getDirection());
rootNode.addLight(flashLight2);
                                                                            
flashLightRim = new SpotLight();
flashLightRim.setSpotRange(32);
flashLightRim.setSpotInnerAngle(20f * FastMath.DEG_TO_RAD);
flashLightRim.setSpotOuterAngle(22f * FastMath.DEG_TO_RAD);
flashLightRim.setColor(ColorRGBA.Gray.mult(lightIntRim));
flashLightRim.setPosition(cam.getLocation());
flashLightRim.setDirection(cam.getDirection());
rootNode.addLight(flashLightRim);
                                                                            
SpotLightShadowRenderer slsr1 = new SpotLightShadowRenderer(assetManager,
    SHADOWMAP_SIZE);
slsr1.setLight(flashLight1);
viewPort.addProcessor(slsr1);
SpotLightShadowFilter slsf1 = new SpotLightShadowFilter(assetManager,
    SHADOWMAP_SIZE);
slsf1.setLight(flashLight1);
slsf1.setEnabled(true);
FilterPostProcessor fpp1 = new FilterPostProcessor(assetManager);
fpp1.addFilter(slsf1);
viewPort.addProcessor(fpp1);
                                                                            
SpotLightShadowRenderer slsr2 = new SpotLightShadowRenderer(assetManager,
    SHADOWMAP_SIZE);
slsr2.setLight(flashLight2);
viewPort.addProcessor(slsr2);
SpotLightShadowFilter slsf2 = new SpotLightShadowFilter(assetManager,
    SHADOWMAP_SIZE);
slsf2.setLight(flashLight2);
slsf2.setEnabled(true);
FilterPostProcessor fpp2 = new FilterPostProcessor(assetManager);
fpp2.addFilter(slsf2);
viewPort.addProcessor(fpp2);
                                                                            
SpotLightShadowRenderer slsrRim = new SpotLightShadowRenderer(assetManager,
    SHADOWMAP_SIZE);
slsrRim.setLight(flashLightRim);
viewPort.addProcessor(slsrRim);
SpotLightShadowFilter slsfRim = new SpotLightShadowFilter(assetManager,
    SHADOWMAP_SIZE);
slsfRim.setLight(flashLightRim);
slsfRim.setEnabled(true);
FilterPostProcessor fppRim = new FilterPostProcessor(assetManager);
fppRim.addFilter(slsfRim);
viewPort.addProcessor(fppRim);

The result works but leads to odd particles gathering around the rims of the flash lights.
Imgur

If we just swap out the normal map for the diffuse, everything seems fine as well.

Any idea why?

Two things could be the problem:

  1. Shininess needs to be larger than 1.0 … recommended values 8 - 128.
  2. If you set UseMaterialColors, you must also set Ambient.

I followed your suggestion and got the same result. Seems like it’s a hardware problem. I ran the default TestSpotlight on JmeTests on my Mac (Macbook Retina Mid 2014) and still get the noise around the rims of the spotlight.

The discontinuity seems to occur at the edge of the rim, where the if-statement branch is not predictable for a framebuffer tile:

spotFallOff =  computeSpotFalloff(g_LightDirection, lightVec);
          if(spotFallOff <= 0.0){
              gl_FragColor.rgb = AmbientSum * diffuseColor.rgb;
              gl_FragColor.a   = alpha;
              return;
          }

Its well known that using control flow in the fragment shader based on a computation is generally a no-no. Its entirely possible that the Intel Iris Pro card in those MacBooks simply can’t handle it.

Huh, I see. Thanks for the heads up, I hadn’t known about it before.