Shadowmapping issues

Hello,

I’m having some troubles with my implementation of shadow mapping in jME. I think the problem is that I don’t really understand what camera values that is needed to be set and what all the defaults are. The camera I’m concerned about is the one in the TextureRenderer.



This is the result I’m getting so far. As you can se I’m having some kind of distortion in the projected shadow, and also a black thick line coming from nowhere. Otherwise the shadows works ok. The distortion is not view dependant so it must have something to do with the projection camera I think. The shadows are totally black now just for testing. 







This is the ShadowMapPass I’ve made:


/*
 * Copyright (c) 2003-2006 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package shadow;

import java.net.URL;
import java.util.ArrayList;
import java.util.IdentityHashMap;

import jmetest.renderer.TestEnvMap;

import org.lwjgl.opengl.*;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.light.Light;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.TextureRenderer;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.SceneElement;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.batch.GeomBatch;
import com.jme.scene.batch.TriangleBatch;
import com.jme.scene.shadow.MeshShadows;
import com.jme.scene.shadow.ShadowVolume;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.ColorMaskState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.GLSLShaderObjectsState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.StencilState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.renderer.pass.Pass;
import com.jme.renderer.pass.ShadowGate;
import com.jme.renderer.pass.DefaultShadowGate;
import com.jme.math.Matrix4f;
import com.jme.renderer.Camera;
import com.jme.renderer.AbstractCamera;
import com.jmex.effects.*;
import com.jme.util.geom.Debugger;
/**
 * This is a shadow map pass.
 *
 * @author Robert Larsson 2008-05-09
 */
public class ShadowMapPass extends Pass
{
   private static int shadowMapSize = 512;
   
   private TextureRenderer shadowMapRenderer;
   
   private Texture shadowMapTexture;
   
   private float nearPlane = 1.0f, farPlane = 1000.0f, fov = 45.0f, aspect = 1.0f;
   
   private Vector3f shadowCameraLookAt;
   private Vector3f shadowCameraLocation;
   
   public Node occluderNode; // should not be public in the final version
   
   TextureState shadowTextureState;
   
   CullState cullFrontFace;
   TextureState noTexture;
   ColorMaskState colorDisabled;
   AlphaState discardShadowFragments;
   
   private static final long serialVersionUID = 1L;
   

   /** one init is enough*/
   protected boolean initialised = false;
   
   /**
   * a place to internally save previous enforced states setup before
   * rendering this pass
   *
   * @author Joshua Slack
   */
   protected RenderState[] preStates = new RenderState[RenderState.RS_MAX_STATE];

   /**
    * saves any states enforced by the user for replacement at the end of the
    * pass.
    *
    * @author Joshua Slack
    */
   protected void saveEnforcedStates()
   {
       for (int x = RenderState.RS_MAX_STATE; --x >= 0; )
       {
           preStates[x] = context.enforcedStateList[x];
       }
   }

   /**
    * replaces any states enforced by the user at the end of the pass.
    *
    * @author Joshua Slack
    */
   protected void replaceEnforcedStates()
   {
       for (int x = RenderState.RS_MAX_STATE; --x >= 0; )
       {
           context.enforcedStateList[x] = preStates[x];
       }
   }
 
    public void init(Renderer r)
    {
       // just init once
       if (initialised)
            return;

        initialised = true; // now it's initialised

       // create and setup the texture to render the depthmap to, this will be the shadowmap
       shadowMapTexture = new Texture();
       shadowMapTexture.setMipmapState(Texture.MM_NONE); // no mipmaps
       shadowMapTexture.setWrap(Texture.WM_CLAMP_S_CLAMP_T); // don't tile the texture
       shadowMapTexture.setFilter(Texture.FM_NEAREST); // linear is no good then doing shadowsmaps
       shadowMapTexture.setRTTSource(Texture.RTT_SOURCE_DEPTH); // this texture should hold a depth map
       
       // ask for a texture renderer
       shadowMapRenderer = DisplaySystem.getDisplaySystem().createTextureRenderer(shadowMapSize, shadowMapSize, TextureRenderer.RENDER_TEXTURE_2D);
       shadowMapRenderer.setupTexture(shadowMapTexture); // set which texture it should render to
       
       // create the camera that's going to be our light source
       shadowMapRenderer.getCamera().setFrustumPerspective(fov, aspect, nearPlane, farPlane);
       shadowCameraLocation = new Vector3f(50,50,50); // just position is somewhere close to the objects
       shadowMapRenderer.getCamera().setLocation(shadowCameraLocation);
       shadowCameraLookAt = new Vector3f(); // the camera should be pointed to the origo
       shadowMapRenderer.getCamera().lookAt(shadowCameraLookAt, Vector3f.UNIT_Y.clone());
       shadowMapRenderer.getCamera().update();
       
        // create the shadow texture state so the texture can be used
       shadowTextureState = r.createTextureState();
       shadowTextureState.setTexture(shadowMapTexture); // set which texture this texturestate should contain
       
       // need this when rendering the shadowmap (no need for textures)
        noTexture = r.createTextureState();
        noTexture.setEnabled(false); // disable textures

        // need this when rendering the shadowmap (no color rendering)
        colorDisabled = r.createColorMaskState();
        colorDisabled.setAll(false); // disable RGBA
       
        // Then rendering and comparing the shadow map with the current
        // depth the result will be set to alpha 1 if not in shadow and
        // to 0 if it's is in shadow
        // Therefore it should discard (not draw) fragments of alpha below 0.99f.
       discardShadowFragments = r.createAlphaState();
       discardShadowFragments.setBlendEnabled( false ); // no blending needed, will use alpha kill
      discardShadowFragments.setTestEnabled( true ); // enable alpha test
        discardShadowFragments.setReference(0.99f); // set at which level to compare to
        discardShadowFragments.setTestFunction(AlphaState.TF_GEQUAL); // only draw >= 0.99f
   
        // one render back faces when rendering the shadow map
        // this deals with the precision issue then comparing depths
        cullFrontFace = r.createCullState();
        cullFrontFace.setEnabled(true);
        cullFrontFace.setCullMode(CullState.CS_FRONT);
    }
   
    public void doRender(Renderer r)
    {
       init(r); // do an init if required
       
       r.clearBuffers(); // start with clearing out the old stuff from the buffers

       /////////////////////////////////////////////////////////////////////////
       // 1st pass, render shadow map as a depth map from the light's viewport
       /////////////////////////////////////////////////////////////////////////
       saveEnforcedStates();

       // hopefully the context states affects the texturerenderer
        context.enforceState(noTexture);
        context.enforceState(colorDisabled); // no need for color then rendering the depth map (z-buffer)
        context.enforceState(cullFrontFace);

        // render the depth map to the texture from the light's perspective
        shadowMapRenderer.render(occluderNode, shadowMapTexture,true);

       replaceEnforcedStates();

       /////////////////////////////////////////////////////////////////////////
       // 2nd pass, render the scene from the normal camera view
       /////////////////////////////////////////////////////////////////////////
       
       // I will probably need to backup the matrix of the texture so do it
       // for safety
       Matrix4f backupMatrix;
       if(shadowMapTexture.getMatrix() == null)
        {
           // no matrix needed for backup
           backupMatrix = null;
           // create a matrix for the texture
           shadowMapTexture.setMatrix(new Matrix4f());
        }else
        {
           // make a copy of it
           backupMatrix = new Matrix4f(shadowMapTexture.getMatrix());
        }
       
       // This will at rendering multiply the texture matrix with the current
       // camera view inverse.
        shadowMapTexture.setEnvironmentalMapMode( Texture.EM_EYE_LINEAR );
        // calculate the texture matrix as: biasMatrix*lightProjectionMatrix*lightModelViewMatrix
        // together with the eye linear mode the result will be a projected texture
        ProjectedTextureUtil.updateProjectedTexture(shadowMapTexture, fov, aspect, nearPlane, farPlane, shadowCameraLocation, shadowCameraLookAt, Vector3f.UNIT_Y.clone());
       
       saveEnforcedStates();

       // only draw fragments with alpha >= 0.99f
       context.enforceState(discardShadowFragments);
       
       // use the shadowmap texture then drawing
       context.enforceState(shadowTextureState);
       
       // compare the shadowmap depth wich the current fragment depth
       GL11.glTexParameteri(GL11.GL_TEXTURE_2D,ARBShadow.GL_TEXTURE_COMPARE_MODE_ARB,ARBShadow.GL_COMPARE_R_TO_TEXTURE_ARB);
      // use the following test when comparing: shadowmap depth is <= the current fragment depth
       GL11.glTexParameteri(GL11.GL_TEXTURE_2D,ARBShadow.GL_TEXTURE_COMPARE_FUNC_ARB,GL11.GL_LEQUAL);
      
       // draw the scene, now only the lit areas (not in shadow) should be visible
       occluderNode.onDraw(r);
       r.renderQueue(); // force it to render

       // retrieve the backup
       shadowMapTexture.setMatrix(backupMatrix);
       // set it as a normal texture again (probably need then rendering to this texture)
       shadowMapTexture.setEnvironmentalMapMode( Texture.EM_NONE );
       
       replaceEnforcedStates();
    }
   
    /**
    * Release pbuffers in TextureRenderer's. Preferably called from user cleanup method.
    */
   public void cleanup()
   {
      super.cleanUp();
      if( shadowMapRenderer != null )
         shadowMapRenderer.cleanup();
   }
}



This is the simple test case/scene I use:

/*
 * Copyright (c) 2003-2006 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package shadow;

import com.jme.app.SimplePassGame;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.pass.RenderPass;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.MaterialState;

/**
 * <code>TestShadowPass</code>
 *
 * @author
 * @version
 */
public class TestShadowPass extends SimplePassGame
{
    private Node occluders;
    private static ShadowMapPass sPass = new ShadowMapPass();

    /**
     * Entry point for the test,
     *
     * @param args
     */
    public static void main(String[] args)
    {
        TestShadowPass app = new TestShadowPass();
       
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }
   
    TestShadowPass()
    {
        this.depthBits = 16;
    }

    /**
     * builds the scene.
     *
     * @see com.jme.app.BaseGame#initGame()
     */
    protected void simpleInitGame()
    {
        display.setTitle("Test for Shadow maps");
        display.getRenderer().setBackgroundColor(ColorRGBA.gray.clone());

        rootNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
 
        cam.setLocation(new Vector3f(-50,50,-50));
        cam.lookAt(new Vector3f(),Vector3f.UNIT_Y.clone());
       
        pManager.add(sPass);
       
        // just create a simple test scene
        occluders = new Node();
       
        Box floor = new Box("Floor", new Vector3f(), 100, 1, 100);
        floor.setModelBound(new BoundingBox());
        floor.updateModelBound();
        occluders.attachChild(floor);
       
        Box box = new Box("Box", new Vector3f(15,10,-15), 10, 10, 5);
        box.setModelBound(new BoundingBox());
        box.updateModelBound();
        occluders.attachChild(box);

        Sphere s = new Sphere("Sphere", new Vector3f(0,5,0),16,16,5);
        s.setModelBound(new BoundingSphere());
        s.updateModelBound();
        occluders.attachChild(s);
       
        // some fancy green material
        MaterialState ms = display.getRenderer().createMaterialState();
        ms.setAmbient(new ColorRGBA(63.0f/255.0f,190.0f/255.0f,0.0f,1.0f));
        ms.setEmissive(new ColorRGBA(0,0,0,0));
        ms.setDiffuse(new ColorRGBA(63.0f/255.0f,190.0f/255.0f,0.0f,1.0f));
        occluders.setRenderState(ms);

        rootNode.attachChild(occluders);
        rootNode.updateRenderState();
        rootNode.updateGeometricState(0, true);
       
        occluders.lockMeshes();
        occluders.lockTransforms();
        occluders.lockBounds();
       
        sPass.occluderNode = rootNode;
       
        RenderPass rPass = new RenderPass();
        rPass.add(fpsNode);
        pManager.add(rPass);
    }
   
    protected void simpleUpdate()
    {

    }
}



Is there anyone who has successefully implemented shadow mapping in jME and know what I'm doing wrong?

Thanks for helping :)

Nice, looks like it works perfectly now  8)

Are you going to release this under user code?

You might be better off asking in a openGL specific forum.

hopefully one of the developers could give some insight, jME needs a ShadowMap pass anyway, so it would be a nice addition.

maybe the diagonal black line is generated by the diffuse light.

this book here says:

"note that, when the shadow map is generated, only z-buffering is required; lighting, texturing and the writing of color values into the color buffer can be turned off"



i guess you disable color and texturing already in your pass but not the lightning?

I think they disable lightning just for performance because lights doesn't affect the z-buffer so no reason to have them enabled.



But for correctness I added a "no light" state when rendering the z-buffer in this version, unfortunately with no improvements at all. :frowning:



The "new" shadowmap pass:


/*
 * Copyright (c) 2003-2006 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package shadow;

import game.Game;

import java.net.URL;
import java.util.ArrayList;
import java.util.IdentityHashMap;

import jmetest.renderer.TestEnvMap;

import org.lwjgl.opengl.*;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.light.Light;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.TextureRenderer;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.SceneElement;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.batch.GeomBatch;
import com.jme.scene.batch.TriangleBatch;
import com.jme.scene.shadow.MeshShadows;
import com.jme.scene.shadow.ShadowVolume;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.ColorMaskState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.GLSLShaderObjectsState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.StencilState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.renderer.pass.Pass;
import com.jme.renderer.pass.ShadowGate;
import com.jme.renderer.pass.DefaultShadowGate;
import com.jme.math.Matrix4f;
import com.jme.renderer.Camera;
import com.jme.renderer.AbstractCamera;
import com.jmex.effects.*;
import com.jme.util.geom.Debugger;
/**
 * This is a shadow map pass.
 *
 * @author Robert Larsson 2008-05-09
 */
public class ShadowMapPass extends Pass
{
   private static int shadowMapSize = 512;
   
   private TextureRenderer shadowMapRenderer;
   
   private Texture shadowMapTexture;
   
   private float nearPlane = 1.0f, farPlane = 1000.0f, fov = 45.0f, aspect = 1.0f;
   
   private Vector3f shadowCameraLookAt;
   private Vector3f shadowCameraLocation;
   
   public Node occluderNode; // should not be public in the final version
   
   TextureState shadowTextureState;
   
   CullState cullFrontFace;
   TextureState noTexture;
   ColorMaskState colorDisabled;
   AlphaState discardShadowFragments;
   LightState noLights;
   
   private static final long serialVersionUID = 1L;
  

   /** one init is enough*/
   protected boolean initialised = false;
   
   /**
   * a place to internally save previous enforced states setup before
   * rendering this pass
   *
   * @author Joshua Slack
   */
   protected RenderState[] preStates = new RenderState[RenderState.RS_MAX_STATE];

   /**
    * saves any states enforced by the user for replacement at the end of the
    * pass.
    *
    * @author Joshua Slack
    */
   protected void saveEnforcedStates()
   {
       for (int x = RenderState.RS_MAX_STATE; --x >= 0; )
       {
           preStates[x] = context.enforcedStateList[x];
       }
   }

   /**
    * replaces any states enforced by the user at the end of the pass.
    *
    * @author Joshua Slack
    */
   protected void replaceEnforcedStates()
   {
       for (int x = RenderState.RS_MAX_STATE; --x >= 0; )
       {
           context.enforcedStateList[x] = preStates[x];
       }
   }
 
    public void init(Renderer r)
    {
       // just init once
       if (initialised)
            return;

        initialised = true; // now it's initialised

       // create and setup the texture to render the depthmap to, this will be the shadowmap
       shadowMapTexture = new Texture();
       shadowMapTexture.setMipmapState(Texture.MM_NONE); // no mipmaps
       shadowMapTexture.setWrap(Texture.WM_CLAMP_S_CLAMP_T); // don't tile the texture
       shadowMapTexture.setFilter(Texture.FM_NEAREST); // linear is no good then doing shadowsmaps
       shadowMapTexture.setRTTSource(Texture.RTT_SOURCE_DEPTH); // this texture should hold a depth map
       
       // ask for a texture renderer
       shadowMapRenderer = DisplaySystem.getDisplaySystem().createTextureRenderer(shadowMapSize, shadowMapSize, TextureRenderer.RENDER_TEXTURE_2D);
       shadowMapRenderer.setupTexture(shadowMapTexture); // set which texture it should render to
       
       // create the camera that's going to be our light source
       shadowMapRenderer.getCamera().setFrustumPerspective(fov, aspect, nearPlane, farPlane);
       shadowCameraLocation = new Vector3f(50,50,50); // just position is somewhere close to the objects
       shadowMapRenderer.getCamera().setLocation(shadowCameraLocation);
       shadowCameraLookAt = new Vector3f(); // the camera should be pointed to the origo
       shadowMapRenderer.getCamera().lookAt(shadowCameraLookAt, Vector3f.UNIT_Y.clone());
       shadowMapRenderer.getCamera().update();
       
        // create the shadow texture state so the texture can be used
       shadowTextureState = r.createTextureState();
       shadowTextureState.setTexture(shadowMapTexture); // set which texture this texturestate should contain
       
       // need this when rendering the shadowmap (no need for textures)
        noTexture = r.createTextureState();
        noTexture.setEnabled(false); // disable textures

        // need this when rendering the shadowmap (no color rendering)
        colorDisabled = r.createColorMaskState();
        colorDisabled.setAll(false); // disable RGBA
       
        // Then rendering and comparing the shadow map with the current
        // depth the result will be set to alpha 1 if not in shadow and
        // to 0 if it's is in shadow
        // Therefore it should discard (not draw) fragments of alpha below 0.99f.
       discardShadowFragments = r.createAlphaState();
       discardShadowFragments.setBlendEnabled( false ); // no blending needed, will use alpha kill
      discardShadowFragments.setTestEnabled( true ); // enable alpha test
        discardShadowFragments.setReference(0.99f); // set at which level to compare to
        discardShadowFragments.setTestFunction(AlphaState.TF_GEQUAL); // only draw >= 0.99f
   
        // one render back faces when rendering the shadow map
        // this deals with the precision issue then comparing depths
        cullFrontFace = r.createCullState();
        cullFrontFace.setEnabled(true);
        cullFrontFace.setCullMode(CullState.CS_FRONT);
       
        // no lights when rendering the depth buffer
        noLights = r.createLightState();
        noLights.setEnabled(false);
    }
   
    public void doRender(Renderer r)
    {
       init(r); // do an init if required
       
       r.clearBuffers(); // start with clearing out the old stuff from the buffers

       /////////////////////////////////////////////////////////////////////////
       // 1st pass, render shadow map as a depth map from the light's viewport
       /////////////////////////////////////////////////////////////////////////
       saveEnforcedStates();

       // hopefully the context states affects the texturerenderer
        context.enforceState(noTexture);
        context.enforceState(colorDisabled); // no need for color then rendering the depth map (z-buffer)
        context.enforceState(cullFrontFace);
        context.enforceState(noLights);
       
        // render the depth map to the texture from the light's perspective
        shadowMapRenderer.render(occluderNode, shadowMapTexture,true);

       replaceEnforcedStates();

       /////////////////////////////////////////////////////////////////////////
       // 2nd pass, render the scene from the normal camera view
       /////////////////////////////////////////////////////////////////////////
       
       // I will probably need to backup the matrix of the texture so do it
       // for safety
       Matrix4f backupMatrix;
       if(shadowMapTexture.getMatrix() == null)
        {
           // no matrix needed for backup
           backupMatrix = null;
           // create a matrix for the texture
           shadowMapTexture.setMatrix(new Matrix4f());
        }else
        {
           // make a copy of it
           backupMatrix = new Matrix4f(shadowMapTexture.getMatrix());
        }
       
       // This will at rendering multiply the texture matrix with the current
       // camera view inverse.
        shadowMapTexture.setEnvironmentalMapMode( Texture.EM_EYE_LINEAR );
        // calculate the texture matrix as: biasMatrix*lightProjectionMatrix*lightModelViewMatrix
        // together with the eye linear mode the result will be a projected texture
        ProjectedTextureUtil.updateProjectedTexture(shadowMapTexture, fov, aspect, nearPlane, farPlane, shadowCameraLocation, shadowCameraLookAt, Vector3f.UNIT_Y.clone());
       
       saveEnforcedStates();

       // only draw fragments with alpha >= 0.99f
       context.enforceState(discardShadowFragments);
       
       // use the shadowmap texture then drawing
       context.enforceState(shadowTextureState);
       
       // compare the shadowmap depth wich the current fragment depth
       GL11.glTexParameteri(GL11.GL_TEXTURE_2D,ARBShadow.GL_TEXTURE_COMPARE_MODE_ARB,ARBShadow.GL_COMPARE_R_TO_TEXTURE_ARB);
      // use the following test when comparing: shadowmap depth is <= the current fragment depth
       GL11.glTexParameteri(GL11.GL_TEXTURE_2D,ARBShadow.GL_TEXTURE_COMPARE_FUNC_ARB,GL11.GL_LEQUAL);
      
       // draw the scene, now only the lit areas (not in shadow) should be visible
       occluderNode.onDraw(r);
       r.renderQueue(); // force it to render

       // retrieve the backup
       shadowMapTexture.setMatrix(backupMatrix);
       // set it as a normal texture again (probably need then rendering to this texture)
       shadowMapTexture.setEnvironmentalMapMode( Texture.EM_NONE );
       
       replaceEnforcedStates();
    }
   
    /**
    * Release pbuffers in TextureRenderer's. Preferably called from user cleanup method.
    */
   public void cleanup()
   {
      super.cleanUp();
      if( shadowMapRenderer != null )
         shadowMapRenderer.cleanup();
   }
}





I've had the same problem, but dont remember which parameter value fixed it. I didnt manage to make it work with alpha testing, so i used intensity instead of alpha test and LEQUAL depth test function. You dont need shadows on backfaces, so you can add bias. I had the black line, when the projected shadow stretched outside the shadow texture. So either fit the shadow into the texture, or dont use the texture where it would fall outside its range.

Thanks for the tip.



I tried INTENSITY instead of alpha kill but got the same result, this is the line i used


GL11.glTexParameteri(GL11.GL_TEXTURE_2D, ARBDepthTexture.GL_DEPTH_TEXTURE_MODE_ARB, GL11.GL_INTENSITY);



The polygon offset helped a little (thanks for pointing that out). I actually had it in the first place but must have removed and forgot it then debugging.

sorry for replying twice in a row…



I’ve searched the forum and found this thread about a bug in the setFrustumPerspective.

http://www.jmonkeyengine.com/jmeforum/index.php?topic=6531.msg58022#msg58022

So i’ve looked if it had been corrected in my version of jME and it’s not (don’t know with the latest?).



Correcting it resulted in correct shadows :slight_smile:



See below.







Also, as vear said, shadows at the borders of the shadowmap texture gets very ugly so I’ve got to find out a more robust method to handle that.

The contour around the objects are also ugly.



The pass as it’s now:


/*
 * Copyright (c) 2003-2006 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package shadow;

import java.net.URL;
import java.util.ArrayList;
import java.util.IdentityHashMap;

import jmetest.renderer.TestEnvMap;

import org.lwjgl.opengl.*;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.light.Light;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.renderer.TextureRenderer;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.SceneElement;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.batch.GeomBatch;
import com.jme.scene.batch.TriangleBatch;
import com.jme.scene.shadow.MeshShadows;
import com.jme.scene.shadow.ShadowVolume;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.ColorMaskState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.GLSLShaderObjectsState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.StencilState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.renderer.pass.Pass;
import com.jme.renderer.pass.ShadowGate;
import com.jme.renderer.pass.DefaultShadowGate;
import com.jme.math.Matrix4f;
import com.jme.renderer.Camera;
import com.jme.renderer.AbstractCamera;
import com.jmex.effects.*;
import com.jme.util.geom.Debugger;
/**
 * This is a shadow map pass.
 *
 * @author Robert Larsson 2008-05-09
 */
public class ShadowMapPass extends Pass
{
   private static int shadowMapSize = 512;
   
   private TextureRenderer shadowMapRenderer;
   
   private Texture shadowMapTexture;
   
   private float nearPlane = 1.0f, farPlane = 1000.0f, fov = 45.0f, aspect = 1.0f;
   
   private Vector3f shadowCameraLookAt;
   private Vector3f shadowCameraLocation;
   
   public Node occluderNode; // should not be public in the final version
   
   TextureState shadowTextureState;
   
   CullState cullFrontFace;
   TextureState noTexture;
   ColorMaskState colorDisabled;
   AlphaState discardShadowFragments;
   LightState noLights;
   
   private static final long serialVersionUID = 1L;
   

   /** one init is enough*/
   protected boolean initialised = false;
   
   /**
   * a place to internally save previous enforced states setup before
   * rendering this pass
   *
   * @author Joshua Slack
   */
   protected RenderState[] preStates = new RenderState[RenderState.RS_MAX_STATE];

   /**
    * saves any states enforced by the user for replacement at the end of the
    * pass.
    *
    * @author Joshua Slack
    */
   protected void saveEnforcedStates()
   {
       for (int x = RenderState.RS_MAX_STATE; --x >= 0; )
       {
           preStates[x] = context.enforcedStateList[x];
       }
   }

   /**
    * replaces any states enforced by the user at the end of the pass.
    *
    * @author Joshua Slack
    */
   protected void replaceEnforcedStates()
   {
       for (int x = RenderState.RS_MAX_STATE; --x >= 0; )
       {
           context.enforcedStateList[x] = preStates[x];
       }
   }
 
    public void init(Renderer r)
    {
       // just init once
       if (initialised)
            return;

        initialised = true; // now it's initialised

       // create and setup the texture to render the depthmap to, this will be the shadowmap
       shadowMapTexture = new Texture();
       shadowMapTexture.setMipmapState(Texture.MM_NONE); // no mipmaps
       shadowMapTexture.setWrap(Texture.WM_CLAMP_S_CLAMP_T); // don't tile the texture
       shadowMapTexture.setFilter(Texture.FM_NEAREST); // linear is no good then doing shadowsmaps
       shadowMapTexture.setRTTSource(Texture.RTT_SOURCE_DEPTH); // this texture should hold a depth map
       
       // ask for a texture renderer
       shadowMapRenderer = DisplaySystem.getDisplaySystem().createTextureRenderer(shadowMapSize, shadowMapSize, TextureRenderer.RENDER_TEXTURE_2D);
       shadowMapRenderer.setupTexture(shadowMapTexture); // set which texture it should render to
       
       // create the camera that's going to be our light source
       shadowMapRenderer.getCamera().setFrustumPerspective(fov, aspect, nearPlane, farPlane);
       shadowCameraLocation = new Vector3f(70,50,50); // just position is somewhere close to the objects
       shadowMapRenderer.getCamera().setLocation(shadowCameraLocation);
       shadowCameraLookAt = new Vector3f(); // the camera should be pointed to the origo
       shadowMapRenderer.getCamera().lookAt(shadowCameraLookAt, Vector3f.UNIT_Y.clone());
       shadowMapRenderer.getCamera().update();
       
        // create the shadow texture state so the texture can be used
       shadowTextureState = r.createTextureState();
       shadowTextureState.setTexture(shadowMapTexture); // set which texture this texturestate should contain
       
       // need this when rendering the shadowmap (no need for textures)
        noTexture = r.createTextureState();
        noTexture.setEnabled(false); // disable textures

        // need this when rendering the shadowmap (no color rendering)
        colorDisabled = r.createColorMaskState();
        colorDisabled.setAll(false); // disable RGBA
       
        // Then rendering and comparing the shadow map with the current
        // depth the result will be set to alpha 1 if not in shadow and
        // to 0 if it's is in shadow
        // Therefore it should discard (not draw) fragments of alpha below 0.99f.
       discardShadowFragments = r.createAlphaState();
       discardShadowFragments.setBlendEnabled( false ); // no blending needed, will use alpha kill
      discardShadowFragments.setTestEnabled( true ); // enable alpha test
        discardShadowFragments.setReference(0.99f); // set at which level to compare to
        discardShadowFragments.setTestFunction(AlphaState.TF_GEQUAL); // only draw >= 0.99f
   
        // one render back faces when rendering the shadow map
        // this deals with the precision issue then comparing depths
        cullFrontFace = r.createCullState();
        cullFrontFace.setEnabled(true);
        cullFrontFace.setCullMode(CullState.CS_FRONT);
       
        // no lights when rendering the depth buffer
        noLights = r.createLightState();
        noLights.setEnabled(false);
}
   
    public void doRender(Renderer r)
    {
       init(r); // do an init if required
       
       r.clearBuffers(); // start with clearing out the old stuff from the buffers

       /////////////////////////////////////////////////////////////////////////
       // 1st pass, render shadow map as a depth map from the light's viewport
       /////////////////////////////////////////////////////////////////////////
       saveEnforcedStates();
       r.setPolygonOffset(1.1f, 4.0f); // the famous shadow mapping offset :)

       // hopefully the context states affects the texturerenderer
        context.enforceState(noTexture);
        context.enforceState(colorDisabled); // no need for color then rendering the depth map (z-buffer)
        context.enforceState(cullFrontFace);
        context.enforceState(noLights);
       
        // render the depth map to the texture from the light's perspective
        shadowMapRenderer.render(occluderNode, shadowMapTexture,true);

        r.clearPolygonOffset();
       replaceEnforcedStates();

       /////////////////////////////////////////////////////////////////////////
       // 2nd pass, render the scene from the normal camera view
       /////////////////////////////////////////////////////////////////////////
       
       // I will probably need to backup the matrix of the texture so do it
       // for safety
       Matrix4f backupMatrix;
       if(shadowMapTexture.getMatrix() == null)
        {
           // no matrix needed for backup
           backupMatrix = null;
           // create a matrix for the texture
           shadowMapTexture.setMatrix(new Matrix4f());
        }else
        {
           // make a copy of it
           backupMatrix = new Matrix4f(shadowMapTexture.getMatrix());
        }
       
       // This will at rendering multiply the texture matrix with the current
       // camera view inverse.
        shadowMapTexture.setEnvironmentalMapMode( Texture.EM_EYE_LINEAR );
        // calculate the texture matrix as: biasMatrix*lightProjectionMatrix*lightModelViewMatrix
        // together with the eye linear mode the result will be a projected texture
        ProjectedTextureUtil.updateProjectedTexture(shadowMapTexture, fov, aspect, nearPlane, farPlane, shadowCameraLocation, shadowCameraLookAt, Vector3f.UNIT_Y.clone());
       
       saveEnforcedStates();

       // only draw fragments with alpha >= 0.99f
       context.enforceState(discardShadowFragments);
       
       // use the shadowmap texture then drawing
       context.enforceState(shadowTextureState);
       
       // compare the shadowmap depth wich the current fragment depth
       GL11.glTexParameteri(GL11.GL_TEXTURE_2D,ARBShadow.GL_TEXTURE_COMPARE_MODE_ARB,ARBShadow.GL_COMPARE_R_TO_TEXTURE_ARB);
      // use the following test when comparing: shadowmap depth is <= the current fragment depth
       GL11.glTexParameteri(GL11.GL_TEXTURE_2D,ARBShadow.GL_TEXTURE_COMPARE_FUNC_ARB,GL11.GL_LEQUAL);
       //GL11.glTexParameteri(GL11.GL_TEXTURE_2D, ARBDepthTexture.GL_DEPTH_TEXTURE_MODE_ARB, GL11.GL_INTENSITY);
       // draw the scene, now only the lit areas (not in shadow) should be visible
       occluderNode.onDraw(r);
       r.renderQueue(); // force it to render

       // retrieve the backup
       shadowMapTexture.setMatrix(backupMatrix);
       // set it as a normal texture again (probably need then rendering to this texture)
       shadowMapTexture.setEnvironmentalMapMode( Texture.EM_NONE );
       
       replaceEnforcedStates();
    }
   
    /**
    * Release pbuffers in TextureRenderer's. Preferably called from user cleanup method.
    */
   public void cleanup()
   {
      super.cleanUp();
      if( shadowMapRenderer != null )
         shadowMapRenderer.cleanup();
   }
}

It's hard to use in a real implementation in the current form so I will have to redesign it. But I don't know when I get the time so feel free to grab the code already and use and improve in any way you want.



What's needed for it to be usefull:

  • Some kind of new light that holds the projection data in it (now i use a camera, not a light)
  • Parallelprojection implementation of the ProjectedTextureUtil in jME (if you want to have a sun projecting the shadows)
  • Split the rendering in a ambient/diffuse pass and a specular pass so that the shadows aint all black

I get this error when trying to test…




May 14, 2008 1:44:16 PM com.jme.app.BaseGame start
INFO: Application started.
May 14, 2008 1:44:16 PM com.jme.system.PropertiesIO <init>
INFO: PropertiesIO created
May 14, 2008 1:44:16 PM com.jme.system.PropertiesIO load
INFO: Read properties
2008-05-14 13:44:17.064 java[2785] CFLog (0): CFMessagePort: bootstrap_register(): failed 1103 (0x44f), port = 0x11b03, name = 'java.ServiceProvider'
See /usr/include/servers/bootstrap_defs.h for the error codes.
2008-05-14 13:44:17.064 java[2785] CFLog (99): CFMessagePortCreateLocal(): failed to name Mach port (java.ServiceProvider)
May 14, 2008 1:44:18 PM com.jme.input.joystick.DummyJoystickInput <init>
INFO: Joystick support is disabled
May 14, 2008 1:44:18 PM com.jme.system.lwjgl.LWJGLDisplaySystem <init>
INFO: LWJGL Display System created.
May 14, 2008 1:44:18 PM com.jme.system.PropertiesIO save
INFO: Saved properties
May 14, 2008 1:44:18 PM com.jme.app.BaseSimpleGame initSystem
INFO: jME version 1.0
May 14, 2008 1:44:19 PM com.jme.renderer.lwjgl.LWJGLRenderer <init>
INFO: LWJGLRenderer created. W:  800H: 600
May 14, 2008 1:44:19 PM com.jme.app.BaseSimpleGame initSystem
INFO: Running on: null
Driver version: null
ATI Technologies Inc. - ATI Radeon X1600 OpenGL Engine - 2.0 ATI-1.4.56
May 14, 2008 1:44:19 PM com.jme.renderer.AbstractCamera <init>
INFO: Camera created.
May 14, 2008 1:44:19 PM com.jme.util.lwjgl.LWJGLTimer <init>
INFO: Timer resolution: 1000 ticks per second
May 14, 2008 1:44:19 PM com.jme.scene.Node <init>
INFO: Node created.
May 14, 2008 1:44:19 PM com.jme.scene.Node <init>
INFO: Node created.
May 14, 2008 1:44:19 PM com.jme.scene.Node attachChild
INFO: Child (FPS label) attached to this node (FPS node)
May 14, 2008 1:44:19 PM com.jme.scene.Node <init>
INFO: Node created.
May 14, 2008 1:44:19 PM com.jme.scene.Node attachChild
INFO: Child (Floor) attached to this node (null)
May 14, 2008 1:44:19 PM com.jme.scene.Node attachChild
INFO: Child (Box) attached to this node (null)
May 14, 2008 1:44:19 PM com.jme.scene.Node attachChild
INFO: Child (Sphere) attached to this node (null)
May 14, 2008 1:44:19 PM com.jme.scene.Node attachChild
INFO: Child (null) attached to this node (rootNode)
May 14, 2008 1:44:19 PM com.jme.renderer.lwjgl.LWJGLTextureRenderer <init>
INFO: FBO support detected.
May 14, 2008 1:44:19 PM com.jme.renderer.lwjgl.LWJGLTextureRenderer initCamera
INFO: Init RTT camera
May 14, 2008 1:44:19 PM com.jme.renderer.AbstractCamera <init>
INFO: Camera created.
May 14, 2008 1:44:19 PM com.jme.renderer.lwjgl.LWJGLTextureRenderer setupTexture
INFO: setup tex with id 2: 512,512
May 14, 2008 1:44:19 PM class com.jme.renderer.lwjgl.LWJGLTextureRenderer render(Spatial, Texture, boolean)
SEVERE: Exception
java.lang.RuntimeException: FrameBuffer: 1, has caused a GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT exception
        at com.jme.renderer.lwjgl.LWJGLTextureRenderer.checkFBOComplete(Unknown Source)
        at com.jme.renderer.lwjgl.LWJGLTextureRenderer.render(Unknown Source)
        at ShadowMapPass.doRender(ShadowMapPass.java:156)
        at com.jme.renderer.pass.Pass.renderPass(Unknown Source)
        at com.jme.renderer.pass.BasicPassManager.renderPasses(Unknown Source)
        at com.jme.app.SimplePassGame.render(Unknown Source)
        at com.jme.app.BaseGame.start(Unknown Source)
        at ShadowMapTest.main(ShadowMapTest.java:37)
May 14, 2008 1:44:20 PM class ShadowMapTest start()
SEVERE: Exception in game loop
org.lwjgl.opengl.OpenGLException: Invalid framebuffer operation (1286)
        at org.lwjgl.opengl.Util.checkGLError(Util.java:53)
        at org.lwjgl.opengl.Display.swapBuffers(Display.java:591)
        at org.lwjgl.opengl.Display.update(Display.java:609)
        at com.jme.renderer.lwjgl.LWJGLRenderer.displayBackBuffer(Unknown Source)
        at com.jme.app.BaseGame.start(Unknown Source)
        at ShadowMapTest.main(ShadowMapTest.java:37)
May 14, 2008 1:44:20 PM com.jme.app.BaseSimpleGame cleanup
INFO: Cleaning up resources.
May 14, 2008 1:44:20 PM com.jme.app.BaseGame start
INFO: Application ending.

texture memory or poly count…hard to decide, but i do think volume shadow has a better quality though.  :smiley:



nice work btw~

Yes, but there is always a possibility of using the Java imaging API and 'blurring' the shadows. :wink:



Also, I don't know about your experience Neakor, but jME's shadow volumes seem like a huge performance hit, while shadow maps (historically) are not as intensive.  I think both systems could work nicely together.



I am actually going to find some time to try to implement blurring, however I guess I may need to build my own Shadow Map due to the error.

I'm very glad you guy's are working on this. I've wanted to do it for a while now, but don't yet have the knowledge. Please finish this, you're so close!

I'm gonna get on this pretty soon. I've done it once before but that code died with a svn sync long ago. This code offcourse helps our in remembering :slight_smile:

I'll get what's needed into the core to make it nicer, and then add Parallel Split Shadowmaps on top of the basic shadowmap impl… (you need that for anything bigger than a supersmall world).

Sweet, sweet, sweet. 



Glad someone with more experience is here to help.  :slight_smile:



there is only so much that can be pulled out of the OpenGL books I have around here.



Side Note: does anyone else get the framebuffer error I had??

That's very nice to hear, MrCoder. Looking forward to it :wink:

basixs - I don't know why you got that error. Maybe because you use jME 2.0? I haven't switched over to the new version yet. Did you use my test scene or one of your own?



mrcorder - Yes, Parallel Split Shadowmaps sounds like a good idea. Will be interesting to see your implementation of it. :slight_smile:

basixs said:

Also, I don't know about your experience Neakor, but jME's shadow volumes seem like a huge performance hit, while shadow maps (historically) are not as intensive.  I think both systems could work nicely together.


my experience is not so bad. well it depends on how u look at it. the shadow volume quadraples the triangle count, so yes, there is a significant performance hit. but a character model is about 3k to 4k triangles, plus the terrain and stuff, you should get about 40k to 50k triangles on a typical mmo scene. then if u multiply that by 4, u get 160k to 200k which is reasonable. u should get a 100 to 150 fps on a middle range machine.

if you are using the gpu for skinning anyway it's pretty natural to do the stencil volume extrusion there as well, which makes stuff very cheap. with shadow maps though it's possible to apply to a whole lot more geometry. as always a balance… stencil shadows are more precise but look very hard etc.