Lighting and shadows

Hi everyone. I’m newbie in jMonkey and I’m trying to build a 3d environment for my game.

I’ve just implemented a DirectionalLight and enabled shadows. But weird thing happens when camera passes through the shadows.





This is before camera is passing through the shadow. Everything is normal and shadows are OK.





This is while camera is passing through the shadow. I think because the object that is owner of the is culled the shadow is culled too. If the problem is this, why the object is culled so early?



I really don’t understand what is happening, I played with culling settings but i couldn’t manage to correct this…





And I have a second problem. As I said before I’m using DirectionalLight. But the brightness of the light is too low, so it cannot manage to provide a shiny and sunny day environment… I’ve used more than one lights with only one of them casting shadows. Is there any chance to play with the brightness of the light or am I doing something wrong? I mostly used codes from examples and I’m really stuk in here :slight_smile: any help please?



package com.game;

import java.util.HashMap;

import jmetest.flagrushtut.lesson9.Lesson9;

import com.jme.app.BaseGame;
import com.jme.image.Texture;
import com.jme.input.ChaseCamera;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.pass.BasicPassManager;
import com.jme.renderer.pass.RenderPass;
import com.jme.renderer.pass.ShadowedRenderPass;
import com.jme.scene.Node;
import com.jme.scene.SceneElement;
import com.jme.scene.Skybox;
import com.jme.scene.Text;
import com.jme.scene.state.CullState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.game.extensions.jme.ModelFileLoader;
import com.game.test.objects.TestCar;
import com.game.test.objects.TestInputHandler;
import com.game.test.objects.TestLevel;

/**
 * @author randomhero
 */

public class LightTestMain extends BaseGame {

   protected InputHandler input;

   protected Timer timer;

   private Camera cam;

   private ChaseCamera chaser;

   // display attributes for the window. We will keep these values
   // to allow the user to change them
   private int width, height, depth, freq;

   private boolean fullscreen;

   private static ShadowedRenderPass shadowPass = new ShadowedRenderPass();

   private BasicPassManager passManager;

   // the root node of the scene graph
   private Node scene;

   private TestCar playerCar;

   private TestLevel testLevel;

   private Skybox skybox;

   protected Text fps;

   protected Node fpsNode;

   protected float tpf;

   protected StringBuffer updateStringBuffer = new StringBuffer(30);

   protected StringBuffer tempBuffer = new StringBuffer();

   public static void main(String[] args) {
      LightTestMain app = new LightTestMain();
      app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG, Lesson9.class
            .getClassLoader().getResource("images/yogurt.jpg"));
      // new ShadowTweaker(shadowPass).setVisible(true);
      app.start();
   }

   protected void cleanup() {
   }

   protected void initGame() {
      display.setTitle("Yogurt Test Game Window");

      scene = new Node("Scene graph node");
      // Create a ZBuffer to display pixels closest to the camera above
      // farther ones.
      ZBufferState buf = display.getRenderer().createZBufferState();
      buf.setEnabled(true);
      buf.setFunction(ZBufferState.CF_LEQUAL);
      scene.setRenderState(buf);

      // Time for a little optimization. We don't need to render back face
      // triangles, so lets
      // not. This will give us a performance boost for very little effort.
      CullState cs = display.getRenderer().createCullState();
      cs.setCullMode(CullState.CS_NONE);
      scene.setRenderState(cs);

      // BUILD THINGS....
      buildSkyBox();
      buildPlayerCar();
      buildLevel();
      buildChaseCamera();
      buildInput();
      buildLighting();
      buildPassManager();
      
      // Then our font Text object.
      // This is what will actually have the text at the bottom.
      fps = Text.createDefaultTextLabel("FPS label");
      fps.setCullMode(SceneElement.CULL_NEVER);
      fps.setTextureCombineMode(TextureState.REPLACE);

      // Finally, a stand alone node (not attached to root on purpose)
      fpsNode = new Node("FPS node");
      fpsNode.setRenderState(fps.getRenderState(RenderState.RS_ALPHA));
      fpsNode.setRenderState(fps.getRenderState(RenderState.RS_TEXTURE));
      fpsNode.attachChild(fps);
      fpsNode.setCullMode(SceneElement.CULL_NEVER);

      // update the scene graph for rendering
      scene.updateGeometricState(0.0f, true);
      scene.updateRenderState();

      fpsNode.updateGeometricState(0.0f, true);
      fpsNode.updateRenderState();
   }

   protected void initSystem() {
      // store the properties information
      width = properties.getWidth();
      height = properties.getHeight();
      depth = properties.getDepth();
      freq = properties.getFreq();
      fullscreen = properties.getFullscreen();

      // get display system
      try {
         display = DisplaySystem.getDisplaySystem(properties.getRenderer());
         display.setMinStencilBits(8);
         display.createWindow(width, height, depth, freq, fullscreen);

         cam = display.getRenderer().createCamera(width, height);
      } catch (JmeException e) {
         e.printStackTrace();
         System.exit(1);
      }
      // paint background black
      display.getRenderer().setBackgroundColor(ColorRGBA.black);
      // initialize the camera
      cam.setFrustumPerspective(45.0f, (float) width / (float) height, 1,
            5000);
      cam.setParallelProjection( false );
        Vector3f loc = new Vector3f( 0.0f, 0.0f, 25.0f );
        Vector3f left = new Vector3f( -1.0f, 0.0f, 0.0f );
        Vector3f up = new Vector3f( 0.0f, 1.0f, 0.0f );
        Vector3f dir = new Vector3f( 0.0f, 0f, -1.0f );
        cam.setFrame( loc, left, up, dir );
      //cam.setLocation(new Vector3f(200, 1000, 200));

      // Signal that we've changed our camera's location/frustum.
      cam.update();

      // get timer
      timer = Timer.getTimer();

      // set the camera
      display.getRenderer().setCamera(cam);

      // set key ESC as exit key
      KeyBindingManager.getKeyBindingManager().set("exit",
            KeyInput.KEY_ESCAPE);
   }

   protected void reinit() {
      display.recreateWindow(width, height, depth, freq, fullscreen);
   }

   /**
    * draws the scene graph
    *
    * @see com.jme.app.SimpleGame#render()
    */
   protected void render(float interpolation) {
      // Clear the screen
      display.getRenderer().clearBuffers();
      
      display.getRenderer().draw(fpsNode);
      // display.getRenderer().draw(scene);
      /** Have the PassManager render. */
      passManager.renderPasses(display.getRenderer());
   }

   protected void update(float interpolation) {
      // update the time to get the framerate
      timer.update();
      interpolation = timer.getTimePerFrame();
      // update the keyboard input (move the player around)
      input.update(interpolation);
      // update the chase camera to handle the player moving around.
      chaser.update(interpolation);

      skybox.setLocalTranslation(chaser.getCamera().getLocation());

      updateStringBuffer.setLength(0);
      updateStringBuffer.append("FPS: ").append((int) timer.getFrameRate());
      updateStringBuffer.append(display.getRenderer().getStatistics(
            tempBuffer));
      fps.print(updateStringBuffer);

      // if escape was pressed, we exit
      if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
         finished = true;
      }

      scene.updateGeometricState(interpolation, true);
      scene.updateRenderState();
      scene.updateWorldData(interpolation);

   }

   /**
    * close the window and also exit the program.
    */
   protected void quit() {
      super.quit();
      System.exit(0);
   }

   private void buildPlayerCar() {
      Node playerCarModel = ModelFileLoader.loadObjModel(
            "models/car_no_tire.obj", "car1", 0.165f);
      playerCar = new TestCar("Player Node", playerCarModel);
      playerCar.setAcceleration(15);
      playerCar.setBraking(15);
      playerCar.setTurnSpeed(2.5f);
      playerCar.setWeight(25);
      playerCar.setMaxSpeed(25);
      playerCar.setMinSpeed(15);

      playerCar.setLocalTranslation(new Vector3f(0, 0, 0));
      scene.attachChild(playerCar);
      scene.updateGeometricState(0, true);

      playerCar.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
   }

   private void buildLevel() {
      Node levelModel = null;

      levelModel = ModelFileLoader.loadObjModel("models/level_cartoon.obj", "level_01",
            0.50f);
      testLevel = new TestLevel("pist_01", levelModel);
      // testLevel.setNormalsMode(SceneElement.NM_USE_PROVIDED);
      // testLevel.setCullMode(SceneElement.CULL_DYNAMIC);
      // testLevel.setLastFrustumIntersection(Camera.INSIDE_FRUSTUM);

      testLevel.updateModelBound();
      testLevel.updateRenderState();

      scene.attachChild(testLevel);
      testLevel.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
      testLevel.updateRenderState();
   }

   private void buildInput() {
      input = new TestInputHandler(playerCar, properties.getRenderer());
   }

   private void buildChaseCamera() {
      HashMap<String, Object> props = new HashMap<String, Object>();
      props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "6");
      props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3");
      props.put(ThirdPersonMouseLook.PROP_MAXASCENT, "" + 45
            * FastMath.DEG_TO_RAD);
      props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(5, 0,
            30 * FastMath.DEG_TO_RAD));
      props.put(ChaseCamera.PROP_DAMPINGK, "4");
      props.put(ChaseCamera.PROP_SPRINGK, "9");
      chaser = new ChaseCamera(cam, playerCar, props);
      chaser.setMaxDistance(2.0f);
      chaser.setMinDistance(1.0f);
   }

   private void buildLighting() {

      /** Set up a basic, default light. */
      DirectionalLight light = new DirectionalLight();
      light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
      light.setDirection(new Vector3f(1, -1, 0));
      light.setShadowCaster(true);
      light.setEnabled(true);

      /** Set up a basic, default light. */
      DirectionalLight light2 = new DirectionalLight();
      light2.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      light2.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
      light2.setDirection(new Vector3f(-1, -1, 0));
      light2.setShadowCaster(false);
      light2.setEnabled(true);

      /** Set up a basic, default light. */
      DirectionalLight light3 = new DirectionalLight();
      light3.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      light3.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
      light3.setDirection(new Vector3f(0, -1, 1));
      light3.setShadowCaster(false);
      light3.setEnabled(true);

      /** Set up a basic, default light. */
      DirectionalLight light4 = new DirectionalLight();
      light4.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      light4.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
      light4.setDirection(new Vector3f(0, -1, -1));
      light4.setShadowCaster(false);
      light4.setEnabled(true);

//      /** Set up a basic, default light. */
//      DirectionalLight light5 = new DirectionalLight();
//      light5.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
//      light5.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
//      light5.setDirection(new Vector3f(1, -1, 1));
//      light5.setShadowCaster(false);
//      light5.setEnabled(true);
//
//      /** Set up a basic, default light. */
//      DirectionalLight light6 = new DirectionalLight();
//      light6.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
//      light6.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
//      light6.setDirection(new Vector3f(1, -1, -1));
//      light6.setShadowCaster(false);
//      light6.setEnabled(true);
//
//      /** Set up a basic, default light. */
//      DirectionalLight light7 = new DirectionalLight();
//      light7.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
//      light7.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
//      light7.setDirection(new Vector3f(-1, -1, 1));
//      light7.setShadowCaster(false);
//      light7.setEnabled(true);
//
//      /** Set up a basic, default light. */
//      DirectionalLight light8 = new DirectionalLight();
//      light8.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
//      light8.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
//      light8.setDirection(new Vector3f(-1, -1, -1));
//      light8.setShadowCaster(false);
//      light8.setEnabled(true);

      LightState lightState = display.getRenderer().createLightState();
      lightState.setEnabled(true);
      lightState.setGlobalAmbient(new ColorRGBA(.2f, .2f, .2f, 1f));
      lightState.attach(light);
      lightState.attach(light2);
      lightState.attach(light3);
      lightState.attach(light4);
//      lightState.attach(light5);
//      lightState.attach(light6);
//      lightState.attach(light7);
//      lightState.attach(light8);

      scene.setRenderState(lightState);
      scene.updateRenderState();
   }

   private void buildPassManager() {
      passManager = new BasicPassManager();

      RenderPass rPass = new RenderPass();
      rPass.add(skybox);
      passManager.add(rPass);

      shadowPass.add(scene);
      shadowPass.addOccluder(playerCar);
      shadowPass.addOccluder(testLevel);
      shadowPass.setRenderShadows(true);
      shadowPass.setLightingMethod(ShadowedRenderPass.MODULATIVE);
      passManager.add(shadowPass);
   }

   private void buildSkyBox() {
      skybox = new Skybox("skybox", 150, 150, 150);

      Texture north = TextureManager.loadTexture(LightTestMain.class
            .getClassLoader().getResource("images/sb_pos_z.jpg"),
            Texture.MM_LINEAR, Texture.FM_LINEAR);
      Texture south = TextureManager.loadTexture(LightTestMain.class
            .getClassLoader().getResource("images/sb_neg_z.jpg"),
            Texture.MM_LINEAR, Texture.FM_LINEAR);
      Texture east = TextureManager.loadTexture(LightTestMain.class
            .getClassLoader().getResource("images/sb_pos_x.jpg"),
            Texture.MM_LINEAR, Texture.FM_LINEAR);
      Texture west = TextureManager.loadTexture(LightTestMain.class
            .getClassLoader().getResource("images/sb_neg_x.jpg"),
            Texture.MM_LINEAR, Texture.FM_LINEAR);
      Texture up = TextureManager.loadTexture(LightTestMain.class
            .getClassLoader().getResource("images/sb_pos_y.jpg"),
            Texture.MM_LINEAR, Texture.FM_LINEAR);
      Texture down = TextureManager.loadTexture(LightTestMain.class
            .getClassLoader().getResource("images/sb_neg_y.jpg"),
            Texture.MM_LINEAR, Texture.FM_LINEAR);
      skybox.setTexture(Skybox.NORTH, north);
      skybox.setTexture(Skybox.WEST, west);
      skybox.setTexture(Skybox.SOUTH, south);
      skybox.setTexture(Skybox.EAST, east);
      skybox.setTexture(Skybox.UP, up);
      skybox.setTexture(Skybox.DOWN, down);
      skybox.preloadTextures();
      scene.attachChild(skybox);
   }

}

:-o wow, awesome screenies!

If I remember this correctly, this is a limitation of the shadowing-algorithm used in Renanse's implementation; once the camera is in the shadow volume it doesn't work.



As for "shininess" and "brightness", that greatly depends on the MaterialState you use. For the more advanced effects you see in modern games shaders are used.



And of course there's no better effect to give you that "sunny day" feeling than the jME LensFlare effect :slight_smile:

First of all thanks for this quick reply and useful answers to my questions llama


llama said:

If I remember this correctly, this is a limitation of the shadowing-algorithm used in Renanse's implementation; once the camera is in the shadow volume it doesn't work.
so then I think I'm gonna try something else, maybe I can try to implement another shadowing-algorithm.

llama said:

As for "shininess" and "brightness", that greatly depends on the MaterialState you use. For the more advanced effects you see in modern games shaders are used.
yeah you are right, I forgot to define MaterialState s for objects I used in the scene. I'm gonna try that one right now.

llama said:

And of course there's no better effect to give you that "sunny day" feeling than the jME LensFlare effect :)
I'm gonna use lens flare effect for "sunny day" but as I said i need on my first post "a shiny and sunny day"  :D . But I think the light in the scene can be brighter. And for some ideas on my head, I'm gonna need some bright lights.





Gentleman Hal said:

:-o wow, awesome screenies!
thanks man  :D
randomhero said:

so then I think I'm gonna try something else, maybe I can try to implement another shadowing-algorithm.

I think it has something to do with "z failure" or something like that! (I really have no clue  XD)
Wait till renanse pops in and he'll be able to explain because you'll be better off spending your time "passing" that "z" than writing a new shadowing algorithm!


randomhero said:

I'm gonna use lens flare effect for "sunny day" but as I said i need on my first post "a shiny and sunny day"  :D . But I think the light in the scene can be brighter. And for some ideas on my head, I'm gonna need some bright lights.

jME supports bloom, that'll give you a crazy shiny day!


randomhero said:

thanks man  :D

You're welcome!  :)


- Chris

jme's shadowing method is usually called the z-pass(depth pass) test. a newer method is called z-fail method(or carmacks reverse). it takes care of the "camera in shadow" issue but is slower since it needs volumes to be capped.

MrCoder said:

jme's shadowing method is usually called the z-pass(depth pass) test. a newer method is called z-fail method(or carmacks reverse). it takes care of the "camera in shadow" issue but is slower since it needs volumes to be capped.


See, I told you I had no clue what I was on about!  :P

Inverse my comment a little and I'm kinda right....  :|

Cardboard cut-out land!  XD

awesome screenshot indeed!!  :-o

darkfrog said:

Cardboard cut-out land!  XD
It's a race game that we are working on right now with a friend. We are thinking some good features about it. Some cool worlds will be like this card board land :) I will keep you all involved about the game. Yesterday I couldn't work on any more on the lighting and materials. But today (and now) I'm going to use MaterialStatus on the objects.

and I made some research on z-Fail implementation. And I thought that implementing it may not be difficult as I thought.

ın depth pass the generation of the stencil mask works as follows:
  1. Disable writes to the depth and colour buffers.
  2. Use back-face culling.
  3. Set the stencil operation to increment on depth pass (only count shadows in front of the object).
  4. Render the shadow volumes (because of culling, only their front faces are rendered).
  5. Use front-face culling.
  6. Set the stencil operation to decrement on depth pass.
  7. Render the shadow volumes (only their back faces are rendered).
And in depth fail
  1. Disable writes to the depth and colour buffers.
  2. Use front-face culling.
  3. Set the stencil operation to increment on depth fail (only count shadows behind the object).
  4. Render the shadow volumes.
  5. Use back-face culling.
  6. Set the stencil operation to decrement on depth fail.
  7. Render the shadow volumes.
(thanks wikipedia)

If I could find such time, I'm going to try this too. I read that z-fail is less performant but, trying will not kill the cat :)

doesn,t z-fail have some patent…um shit, that made it unattractive when talk of shadows in jme started

zfail is easy enough to implement, it's just capping and flipping the test.  Like Mr. Coder said, it's slower, so my plan was to swap methods based on whether we are in a shadow volume or not.  I never got around to writing code to determine that but will do that (and implement two sided stencil support) in the near future when we focus on shadows at work.  If you do that first, so much the better. :slight_smile:

I found a new approach to this stencil shadows problem… I’ve found a paper named “ZP+: Correct Z-pass Stencil Shadows” on the web page http://artis.inrialpes.fr/Publications/2005/HHLH05/ . Now I’m trying to understand if this is an effective method (It says that Doom3 uses this algorithm, then it must be effective :slight_smile: ), but I’ve not really discussed the algorithm with myself. This weekend I’m going to read this paper again and do some research on internet, to see that if this is a well algorithm or not. Maybe after this, I’ll try to implement this algorithm.


That paper has come up several times on this forum.  I’ve looked into it myself as well.  I wasn’t too impressed, but then, maybe that’s just me.  On the other hand, other smarter people then myself have also come to that conclusion.

renanse said:

That paper has come up several times on this forum.  I've looked into it myself as well.  I wasn't too impressed, but then, maybe that's just me.  On the other hand, other smarter people then myself have also come to that conclusion.


on weekend as I said I've read the paper again with more attention. First when I saw this paper, it seems like it can be worth of a shot. But some points (as told in Eric Lengyel's blog entry in your link) like "switching to z-fail at least one case to get correct results", "implementation of the z-fail method in the paper (for performance comparisons) is being extremely poor" etc etc...

and I saw your point about ZP+ ... I'm just searching for a good algorithm, and some point I step on wrong blocks :) thanks for quick reply about the algorithm.

So what do you think the best method to correct this shadowing problem? Do you prefer just z-fail, or a mixed z-pass/z-fail implementation? or do you have any other thoughts on your mind?

I figure on a mixed zpass/zfail approach.  We just need a few things to get that working, the biggest one being a method that detects if the camera in inside a shadow volume.  I see adding zfail as fairly low-hanging fruit toward having a complete stencil-shadow volume technique.  From there, I'd say we should look at fully implementing other shadow techniques such as shadow mapping.