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 :)