Light & Shadow issues

Obviously the whole light&shadow thing is very important. Therefore I got 3 questions about it in which I am not clear and need some explanation.






  1. This is a scene just put into the scenegraph with 1 DirectionalLight.
  2. The same scene, but now I activated Additive lighting to cast shadows. Two things are dazzling me in here:

    a) Unfortunately the whole scene darkens enormously! How come that? I used a directional Light, which should be something like a sun! The light can't be that dark all out of a sudden - not that dark. In this case I would need 2 directional lights on the same position to cast an appropriate amount of brightness :expressionless: (the code posted below is the sample to that. Press "H" to toggle shadows)

    b) I am not clear about the shadows: even if I set the Ambient Color to "black" I can't get really dark shadows. Or is this just the way it works?


  3. This thing confuses me most. What you see here is a cleanly modelled house in blender which I imported into the scene. In the middle of the house I put in a red light. Below the house is a box. Additive lighting/shadows is activated in the scene. What I do not understand here is: why does the box below the house receive a red light? Shouldn't it be completely dark?? There is absolutely no way that light shall shine through walls. I also tried it out with multiple boxes instead of the house :expressionless:



    Any hints :?



import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.light.DirectionalLight;
import com.jme.light.LightNode;
import com.jme.light.PointLight;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.pass.ShadowedRenderPass;
import com.jme.scene.Line;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Spatial.LightCombineMode;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.shape.Sphere;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.renderer.pass.BasicPassManager;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.MaterialState;
import com.jme.system.DisplaySystem;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.BlendState.DestinationFunction;
import com.jme.scene.state.BlendState.SourceFunction;
 
class TestSimpleShadoPass extends SimpleGame {
   static ShadowedRenderPass sPass = new ShadowedRenderPass();
   static ShadowedRenderPass sPass2 = new ShadowedRenderPass();
    BasicPassManager pManager;
    Node unlit;
    boolean showShadows = false;
    InputHandler input;
 
    public static void main(String[] args) {
   TestSimpleShadoPass app = new TestSimpleShadoPass();
   app.setConfigShowMode(ConfigShowMode.NeverShow);
    app.start();
    }
 
    TestSimpleShadoPass() {
   stencilBits = 8;
    }
 
    public void simpleInitGame()
    {
      display.getRenderer().setBackgroundColor(ColorRGBA.black);
      
      input = new InputHandler();
      
      // A rotated quad that sits on x-z plane.
      Quad floor = new Quad("Floor", 15, 15);
      rootNode.attachChild(floor);
      Quaternion r = new Quaternion();
      r.fromAngleAxis(-FastMath.PI / 2, new Vector3f(1, 0, 0));
      floor.setLocalRotation(r);
   
      // A box and a sphere to occlude the light.
      Node occluders = new Node("Occluders");
      Box b = new Box("Box", new Vector3f(-2, 0, -2), new Vector3f(1, 8, 0.5f));
      occluders.attachChild(b);
      Sphere s = new Sphere("Sphere", 24, 24, 1.5f);
      s.setLocalTranslation(new Vector3f(3, 1.5f, 0));
      occluders.attachChild(s);
      rootNode.attachChild(occluders);
                     
      // The light that cast shadows.
      DirectionalLight dr = new DirectionalLight();
      dr.setEnabled(true);
      dr.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      dr.setAmbient(new ColorRGBA(0f, 0f, 0f, 0.0f));
      dr.setDirection(new Vector3f(-15, 20, -15).negate());
      dr.setShadowCaster(true);
      
      //Create a sphere to show where the light is in the demo.
        Sphere LightSphere = new Sphere("lp", 10, 10, 1.0f);
        LightSphere.setModelBound(new BoundingBox());
        LightSphere.updateModelBound();
        LightSphere.setLightCombineMode(Spatial.LightCombineMode.Off);//diese sollen nicht beleuchtet sein
        LightSphere.setSolidColor(ColorRGBA.red);
        LightSphere.setLocalTranslation(new Vector3f(20,10,10));
        rootNode.attachChild(LightSphere);
      //Create a new PointLight
        PointLight pl = new PointLight();
        pl.setDiffuse(ColorRGBA.red);
        pl.setQuadratic(0.0001f);
        pl.setAttenuate(true);
        pl.setEnabled(true);
        pl.setAmbient(new ColorRGBA(0f, 0f, 0f, 0.0f));
        pl.setShadowCaster(true);
        pl.setLocation(new Vector3f(20,10,10));
        LightNode pln = new LightNode("pln");
       pln.setLight(pl);
       pln.setLocalTranslation(LightSphere.getLocalTranslation());
       rootNode.attachChild(pln);
       
      lightState.detachAll();
      lightState.attach(dr);
      lightState.attach(pl);

   
      cam.setLocation(new Vector3f(0, 15, -15));
      cam.lookAt(new Vector3f(0, 0, 0), new Vector3f(0, 1, 0));
   
      rootNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
      
      createInput();
      createUnlit();
      unlit.setLightCombineMode(LightCombineMode.Off);
      rootNode.attachChild(unlit);
   
      CullState cs = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
      cs.setCullFace(CullState.Face.Back);
      cs.setEnabled(true);
      rootNode.setRenderState(cs);
      
//      new jmetest.renderer.ShadowTweaker(sPass).setVisible(true);
      
      sPass.add(rootNode);
      sPass.addOccluder(occluders);
      sPass.setRenderShadows(true);
      sPass.setLightingMethod(ShadowedRenderPass.LightingMethod.Additive);
      
      sPass.setEnabled(true);
      pManager = new BasicPassManager();
      pManager.add(sPass);
      
      sPass2.add(rootNode);
      sPass2.addOccluder(unlit);
      sPass2.setRenderShadows(false);
      sPass2.setEnabled(true);
      pManager.add(sPass2);
    }
    @Override
    public void simpleUpdate()
    { 
       input.update(tpf);
       
       if (KeyBindingManager.getKeyBindingManager().isValidCommand("toggleShadows", false)) {
          if(!showShadows)
                 showShadows = true;
              else
                 showShadows = false;
      }
       pManager.updatePasses(tpf);
    }
    @Override
    public void simpleRender()
    {
       if(showShadows)
       {
          pManager.renderPasses(DisplaySystem.getDisplaySystem().getRenderer());
          ShadowedRenderPass.blended.setSourceFunction(BlendState.SourceFunction.One);
          ShadowedRenderPass.blended.setDestinationFunction(BlendState.DestinationFunction.One);
          ShadowedRenderPass.blendTex.setSourceFunction(BlendState.SourceFunction.Zero);
          ShadowedRenderPass.blendTex.setDestinationFunction(BlendState.DestinationFunction.One);
       }
    }
   
    public void createInput()
    {
       KeyBindingManager.getKeyBindingManager().set("toggleShadows", KeyInput.KEY_H);
    }
   
    public void createUnlit()
    {
       unlit = new Node();
       
       // create Grid
        for (int x = -300; x <= 300; x=x+10) {
            Line l = new Line("lineYGridx" + x, new Vector3f[]{new Vector3f((float) x, 0f, -300f), new Vector3f((float) x, 0f, 300f)}, null, null, null);
            l.setSolidColor(ColorRGBA.darkGray);
            l.setModelBound(new BoundingBox());   
            l.updateModelBound();
            l.setCastsShadows(false);
            unlit.attachChild(l);
        }

        for (int z = -300; z <= 300; z=z+10) {
            Line l = new Line("lineYGridz" + z, new Vector3f[]{new Vector3f(-300f, 0f, (float) z), new Vector3f(300f, 0f, (float) z)}, null, null, null);
            l.setSolidColor(ColorRGBA.darkGray);
            l.setModelBound(new BoundingBox());   
            l.updateModelBound();
            l.setCastsShadows(false);
            unlit.attachChild(l);
        }
    }
}

owaye said:

Obviously the whole light&shadow thing is very important. Therefore I got 3 questions about it in which I am not clear and need some explanation.



1) This is a scene just put into the scenegraph with 1 DirectionalLight.
2) The same scene, but now I activated Additive lighting to cast shadows. Two things are dazzling me in here:
a) Unfortunately the whole scene darkens enormously! How come that? I used a directional Light, which should be something like a sun! The light can't be that dark all out of a sudden - not *that* dark. In this case I would need 2 directional lights on the same position to cast an appropriate amount of brightness :| (the code posted below is the sample to that. Press "H" to toggle shadows)
b) I am not clear about the shadows: even if I set the Ambient Color to "black" I can't get really dark shadows. Or is this just the way it works?

3) This thing confuses me most. What you see here is a cleanly modelled house in blender which I imported into the scene. In the middle of the house I put in a red light. Below the house is a box. Additive lighting/shadows is activated in the scene. What I do not understand here is: *why* does the box below the house receive a red light? Shouldn't it be completely dark?? There is absolutely no way that light shall shine through walls. I also tried it out with multiple boxes instead of the house :|

Any hints :?


First things first, stencil shadowing is not the only way to do shadows. There is also generating a shadow map before running the program, and applying the same shadow map to the level every time you load it. I think people have made code to do this in JME many times. Now onto your questions.

2) a) There is a way to up the brightness of the light, can't say it off of the top of my head, though.
b) Don't set the ambient color to black, set it off. Even though it is black, it is still lighting things up.
3) Hope that no one sees that in your game :)

Issues (a) and (b) are the same for mode 2. Stencil shadows uses a certain blending mode to apply the shadows to the scene, depending on the blending mode, the shadows are applied differently. jME has a helper class to choose the correct mode, I think it's ShadowsEditor or some other class, search for classes extending JFrame…



For issue #3, are you using the stencil shadows still? If so, see the above explanation on how to fix it.

I c… I'd not have thought that this is still such difficult thing, now in the year 2008, 4 years after Doom3…I can also remember games like Thief3, Deus Ex2 from 2004 which had nice shadows as well, wondering how they did it then,  or did they license Carmack's Reverse?. IMO the Stencil Shadows gave the best result and looked most satisfying. Any chance that ZFail (or what it's called) gets implemented sometime in JME? I've also found some 2-3 other posts in the forum requesting that feature (from 2006), but of course many more people would happy about it…



(edit: if I at least would get the Shadow Maps's working correctly to reconsider that… the reason it doesn't work might also be the reason for the bad performance I got (about 8fps in the small sample code). If I understand correctly there should be no problem with my GPU - at least I think so - when I try this sample code from Ati http://ati.amd.com/developer/samples/shadowmap.html which works very nice and smooth on my system with about 90+ fps). Unfortunately I am not a shader/OpenGL programmer and don't know what causes the "not-smoothed" edges and bad performance on the JME ShadowMapping Test Sample…)

Thanks for the answers!



@Jedimace1: hmmm the shadowmapping seems to be somewhat like the “Render” option in UnrealdEd for static objects. That’s of course very benefitial for the performance; I will consider that later, since for now I need to have dynamic light for a day-night-cycle.



@Momoko_Fan: I didn’t know that I had to apply additional blended states for the stencil shadows. I guess you meant the ShadowTweaker class as a helper => this indeed helped; there were 4 more values I had to set! It also worked to make the light disappear behind walls, with some settings :slight_smile:



I updated my code from the first post with new values, and unfortunately I came upon a new issue… don’t know if this is related to the shadow settings: as you can see, as soon as the red lightsource (but any other lightsource is the same) gets behind an object, the shadows “invert”. How come that :?



I updated my code from the first post with new values, and unfortunately I came upon a new issue... don't know if this is related to the shadow settings: as you can see, as soon as the red lightsource (but any other lightsource is the same) gets behind an object, the shadows "invert". How come that huh

That's a known issue.. jME implements Zpass method, so when you get inside the shadow volume, you have issues like you mentioned. You might want to consider shadowmapping, it requires more housekeeping but it's somewhat more reliable cause your models don't have to be convex and you can go inside the shadow, etc.

:-o Oh no!, dam! I also just saw that this can only be solved by "Carmack's Reverse"…lol, patented too.



Now I will go to shadowmapping as suggested. Obviously I had a complete wrong imagination of shadowmapping anyways. I always thought that this is a 1-time claculation of shadows for the whole scene and that the lighting would be static the whole time, therefore dynamic light would not be possible. Though now I read in wiki that this of course can be used for realtime shadows - only that it's not as accurate as stencilshadows.



Well I guess that's something I can live with :slight_smile:

I guess there is no easy generic way to enable shadows for any scene set up.

Probably you will need to implement your own shadow mapping which fits your needs.



The following valve paper is not really directly related to the discussion here, but it shows a bit, how much thought and hard work it takes to make a game look really good:

http://www.valvesoftware.com/publications/2007/NPAR07_IllustrativeRenderingInTeamFortress2.pdf



There are also many papers out there regarding shadow mapping, who show the different problems and solutions, like this one:

http://ati.amd.com/developer/gdc/2006/Isidoro-ShadowMapping.pdf



Maybe you can also take a look at other open source engines and look how they do it.

I now tried the ShadowMapping Test sample from http://www.jmonkeyengine.com/jmeforum/index.php?topic=8926.0 which works on my system. Unfortunately I get much worse performance than with stencil shadows. Additionally the overall setup of lights (e.g. adding lights, setting light direction…) is most inflexible, which Momoko_Fan already hinted.



Also the shadows had following appearance on the edges showing 3 tones of gray  :?





I don’t know why that’s the case, it’s not that big on the Original Test Sample compared to my shortened implementation below. But still it does look ugly. I could imagine that it would look much nicer without these “steps”. Maybe my GPU is the faulty part? I have an Ati9700





simplified code…


import com.jme.app.SimplePassGame;
import com.jme.light.DirectionalLight;
import com.jme.light.PointLight;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.pass.DirectionalShadowMapPass;
import com.jme.renderer.pass.RenderPass;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.CullState;

public class CopyOfTestDirectionalShadowMapPass extends SimplePassGame {
    private Node occluders;
    private static DirectionalShadowMapPass sPass;
    private static DirectionalShadowMapPass sPass2;
    Quad floor;
    Box b;
    Sphere s;

    public static void main(String[] args) {
        CopyOfTestDirectionalShadowMapPass app = new CopyOfTestDirectionalShadowMapPass();
       
        app.setConfigShowMode(ConfigShowMode.AlwaysShow);
        app.start();
    }
   
    CopyOfTestDirectionalShadowMapPass() {
    }

    protected void simpleInitGame() {
        display.setTitle("jME - Shadow Mapping Pass Test");
        display.getRenderer().setBackgroundColor(ColorRGBA.black.clone());

        setupLights();
        setupOccluders();
       
        rootNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
       
        RenderPass rPass = new RenderPass();
        rPass.add(statNode);
        rPass.add(rootNode);
        pManager.add(rPass);
       
        sPass = new DirectionalShadowMapPass(new Vector3f(-1,-1,-1)); // the direction of the "light"
        sPass.add(rootNode);
        sPass.addOccluder(occluders);
        pManager.add(sPass);
       
        sPass2 = new DirectionalShadowMapPass(new Vector3f(-1f,-1,0.5f)); // the direction of the "light"
        sPass2.add(rootNode);
        sPass2.addOccluder(occluders);
        pManager.add(sPass2);
    }
   
    protected void simpleUpdate() {
    }
   
    private void setupLights() {

       DirectionalLight dr = new DirectionalLight();
        dr.setEnabled(true);
        dr.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        dr.setAmbient(new ColorRGBA(.2f, .2f, .2f, .3f));
        dr.setDirection(new Vector3f(0.5f, -0.4f, 0).normalizeLocal());
        dr.setShadowCaster(true);

        PointLight pl = new PointLight();
        pl.setEnabled(true);
        pl.setDiffuse(ColorRGBA.red);
        pl.setAmbient(new ColorRGBA(.25f, .25f, .25f, .25f));
        pl.setLocation(new Vector3f(0,500,0));

        DirectionalLight dr2 = new DirectionalLight();
        dr2.setEnabled(true);
        dr2.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
        dr2.setAmbient(new ColorRGBA(.2f, .2f, .2f, .4f));
        dr2.setDirection(new Vector3f(-0.2f, -0.3f, .2f).normalizeLocal());
        dr2.setShadowCaster(true);

        CullState cs = display.getRenderer().createCullState();
        cs.setCullFace(CullState.Face.Back);
        cs.setEnabled(true);
        rootNode.setRenderState(cs);

        lightState.detachAll();
        lightState.attach(dr);
        lightState.attach(dr2);
        lightState.attach(pl);
        lightState.setGlobalAmbient(new ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f));
    }

    private void setupOccluders() {
       occluders = new Node("occs");
        rootNode.attachChild(occluders);
      floor = new Quad("Floor", 45, 45);
      occluders.attachChild(floor);
      Quaternion r = new Quaternion();
      r.fromAngleAxis(-FastMath.PI / 2, new Vector3f(1, 0, 0));
      floor.setLocalRotation(r);
   
      b = new Box("Box", new Vector3f(-2, 0, -2), new Vector3f(1, 16, 2f));
      occluders.attachChild(b);
      s = new Sphere("Sphere", 24, 24, 1.5f);
      s.setLocalTranslation(new Vector3f(3, 1.5f, 0));
      occluders.attachChild(s);
       
      occluders.lock();
    }
}



Anyways, in the end I don't know how to go on with lighting & shadows now:
On the one hand I can't use ShadowMapping because of the horrible performance, ugly edges, too unflexible light setup -
On the other hand I can't use StencilShadows, because of the shadow issues whenever the camera gets into a shadowVolume (as close as far... is there really no way to exclude the camera from the volume, although it is in the volume?)

*Sigh*... or is there any third way left for proper Shadow Management  :? :( :(
is there any third way left for proper Shadow Management  huh sad sad

Yeah, the 3rd way is always "do-it-yourself". While at it, you'll not only solve your problem, but help many other people with the same issue  ;)
I c... I'd not have thought that this is still such difficult thing, now in the year 2008, 4 years after Doom3...I can also remember games like Thief3, Deus Ex2 from 2004 which had nice shadows as well, wondering how they did it then,  or did they license Carmack's Reverse?

AFAIK Zfail is not patented. Maybe that paper you mentioned but Zfail has already been implemented in many both open source and freeware applications and engines.

Any chance that ZFail (or what it's called) gets implemented sometime in JME?

And who will exactly do that? The number of active developers on jME right now is 0.
Also, since stencil shadows now loses popularity quickly (being a software-based technique), it will probably get removed at some point for a better shadow mapping implementation like PSSM.

(edit: if I at least would get the Shadow Maps's working correctly to reconsider that... the reason it doesn't work might also be the reason for the bad performance I got (about 8fps in the small sample code). If I understand correctly there should be no problem with my GPU - at least I think so - when I try this sample code from Ati http://ati.amd.com/developer/samples/shadowmap.html which works very nice and smooth on my system with about 90+ fps). Unfortunately I am not a shader/OpenGL programmer and don't know what causes the "not-smoothed" edges and bad performance on the JME ShadowMapping Test Sample...)

I am not sure, but I think it uses shaders on all ATI cards as they don't support hardware soft shadows. In that case it might be running the shader in software which would give the results you mentioned.

I tried now shadow maps on the ogre3d engine demos for comparison. The types of projection I used were "uniform" and "uniform-focused" (whatever "focused" is…). For shadow mapping there were 3 different Material-settings:

Standard => Works flawless in any setting, but shadow seems to be projected only on ground (still better than nothing)

Depth Shadowmap => Ugly artifacts on uniform. No artifacts on uniform-focused and also showed Shadows on objects

Depth Shadowmap (PCF) => Ugly artifacts on uniform. No Artifacts on uniform-focused, but shows same result as pictures posted somewhere above.



So it's an PCF issue I guess. Obviously PCF was introduced into ATI first with the r500+ series (X1300,x1600,x1900 etc.), whereas Nvidia hat implemented it since Geforce3. I guess my GPU simply sux therefore, because it's a lower series. It's about time for a new computer anyways… 

Stencil Shadows gave me the best result though on either OpenGL/DirectX in the demos without any artifacts on both OpenGL & DirectX (also the issue with the camera entering the volume is solved here; ogre3d is but no option for me)



Maybe Momoko_Fan might be right that stencils might disappear since I've read some articles saying that they are not very usable for tree/leaves, foliage, "open objects"… which is not good.


Momoko_Fan said:
Quote:
Any chance that ZFail (or what it's called) gets implemented sometime in JME?

And who will exactly do that? The number of active developers on jME right now is 0.
Also, since stencil shadows now loses popularity quickly (being a software-based technique), it will probably get removed at some point for a better shadow mapping implementation like PSSM.
lol... and who will implement PSSM??
:-o Arrr... not good that we have no active developers anymore. We really need some Computer Graphics Specialists... :|