Documented com.jme3.shadow

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7631 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
rem..om 2011-06-14 21:20:24 +00:00
parent bcef1b1e5c
commit 446275775f
5 changed files with 240 additions and 203 deletions

View File

@ -29,7 +29,6 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.shadow; package com.jme3.shadow;
import com.jme3.material.Material; import com.jme3.material.Material;
@ -48,29 +47,36 @@ import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture2D; import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture; import com.jme3.ui.Picture;
/**
* BasicShadowRenderer uses standard shadow mapping with one map
* it's useful to render shadows in a small scene, but edges might look a bit jagged.
*
* @author Kirill Vainer
*/
public class BasicShadowRenderer implements SceneProcessor { public class BasicShadowRenderer implements SceneProcessor {
private RenderManager renderManager; private RenderManager renderManager;
private ViewPort viewPort; private ViewPort viewPort;
private FrameBuffer shadowFB; private FrameBuffer shadowFB;
private Texture2D shadowMap; private Texture2D shadowMap;
private Camera shadowCam; private Camera shadowCam;
private Material preshadowMat; private Material preshadowMat;
private Material postshadowMat; private Material postshadowMat;
private Picture dispPic = new Picture("Picture"); private Picture dispPic = new Picture("Picture");
private boolean noOccluders = false; private boolean noOccluders = false;
private Vector3f[] points = new Vector3f[8]; private Vector3f[] points = new Vector3f[8];
private Vector3f direction = new Vector3f(); private Vector3f direction = new Vector3f();
public BasicShadowRenderer(AssetManager manager, int size){ /**
shadowFB = new FrameBuffer(size,size,1); * Creates a BasicShadowRenderer
shadowMap = new Texture2D(size,size,Format.Depth); * @param manager the asset manager
* @param size the size of the shadow map (the map is square)
*/
public BasicShadowRenderer(AssetManager manager, int size) {
shadowFB = new FrameBuffer(size, size, 1);
shadowMap = new Texture2D(size, size, Format.Depth);
shadowFB.setDepthTexture(shadowMap); shadowFB.setDepthTexture(shadowMap);
shadowCam = new Camera(size,size); shadowCam = new Camera(size, size);
preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md"); preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");
postshadowMat = new Material(manager, "Common/MatDefs/Shadow/PostShadow.j3md"); postshadowMat = new Material(manager, "Common/MatDefs/Shadow/PostShadow.j3md");
@ -78,44 +84,61 @@ public class BasicShadowRenderer implements SceneProcessor {
dispPic.setTexture(manager, shadowMap, false); dispPic.setTexture(manager, shadowMap, false);
for (int i = 0; i < points.length; i++){ for (int i = 0; i < points.length; i++) {
points[i] = new Vector3f(); points[i] = new Vector3f();
} }
} }
public void initialize(RenderManager rm, ViewPort vp){ public void initialize(RenderManager rm, ViewPort vp) {
renderManager = rm; renderManager = rm;
viewPort = vp; viewPort = vp;
reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight()); reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());
} }
public boolean isInitialized(){ public boolean isInitialized() {
return viewPort != null; return viewPort != null;
} }
/**
* returns the light direction used for this processor
* @return
*/
public Vector3f getDirection() { public Vector3f getDirection() {
return direction; return direction;
} }
/**
* sets the light direction to use to computs shadows
* @param direction
*/
public void setDirection(Vector3f direction) { public void setDirection(Vector3f direction) {
this.direction.set(direction).normalizeLocal(); this.direction.set(direction).normalizeLocal();
} }
/**
* debug only
* @return
*/
public Vector3f[] getPoints() { public Vector3f[] getPoints() {
return points; return points;
} }
public Camera getShadowCamera(){ /**
* debug only
* returns the shadow camera
* @return
*/
public Camera getShadowCamera() {
return shadowCam; return shadowCam;
} }
public void postQueue(RenderQueue rq){ public void postQueue(RenderQueue rq) {
GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast); GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);
if (occluders.size() == 0){ if (occluders.size() == 0) {
noOccluders = true; noOccluders = true;
return; return;
}else{ } else {
noOccluders = false; noOccluders = false;
} }
@ -130,7 +153,7 @@ public class BasicShadowRenderer implements SceneProcessor {
points); points);
Vector3f frustaCenter = new Vector3f(); Vector3f frustaCenter = new Vector3f();
for (Vector3f point : points){ for (Vector3f point : points) {
frustaCenter.addLocal(point); frustaCenter.addLocal(point);
} }
frustaCenter.multLocal(1f / 8f); frustaCenter.multLocal(1f / 8f);
@ -154,7 +177,7 @@ public class BasicShadowRenderer implements SceneProcessor {
renderManager.setForcedMaterial(preshadowMat); renderManager.setForcedMaterial(preshadowMat);
r.setFrameBuffer(shadowFB); r.setFrameBuffer(shadowFB);
r.clearBuffers(false,true,false); r.clearBuffers(false, true, false);
viewPort.getQueue().renderShadowQueue(ShadowMode.Cast, renderManager, shadowCam, true); viewPort.getQueue().renderShadowQueue(ShadowMode.Cast, renderManager, shadowCam, true);
r.setFrameBuffer(viewPort.getOutputFrameBuffer()); r.setFrameBuffer(viewPort.getOutputFrameBuffer());
@ -162,12 +185,16 @@ public class BasicShadowRenderer implements SceneProcessor {
renderManager.setCamera(viewCam, false); renderManager.setCamera(viewCam, false);
} }
public Picture getDisplayPicture(){ /**
* debug only
* @return
*/
public Picture getDisplayPicture() {
return dispPic; return dispPic;
} }
public void postFrame(FrameBuffer out){ public void postFrame(FrameBuffer out) {
if (!noOccluders){ if (!noOccluders) {
postshadowMat.setMatrix4("LightViewProjectionMatrix", shadowCam.getViewProjectionMatrix()); postshadowMat.setMatrix4("LightViewProjectionMatrix", shadowCam.getViewProjectionMatrix());
renderManager.setForcedMaterial(postshadowMat); renderManager.setForcedMaterial(postshadowMat);
viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, viewPort.getCamera(), true); viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, viewPort.getCamera(), true);
@ -186,5 +213,4 @@ public class BasicShadowRenderer implements SceneProcessor {
dispPic.setWidth(w / 5f); dispPic.setWidth(w / 5f);
dispPic.setHeight(h / 5f); dispPic.setHeight(h / 5f);
} }
} }

View File

@ -29,7 +29,6 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.shadow; package com.jme3.shadow;
import com.jme3.material.Material; import com.jme3.material.Material;
@ -57,36 +56,42 @@ import com.jme3.texture.Texture.ShadowCompareMode;
import com.jme3.texture.Texture2D; import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture; import com.jme3.ui.Picture;
/**
* PssmShadow renderer use Parrallel Split Shadow Mapping technique (pssm)<br>
* It splits the view frustum in several parts and compute a shadow map for each one.<br>
* splits are distributed so that the closer they are from the camera, the smaller they are to maximize the resolution used of the shadow map.<br>
* This result in a better quality shadow than standard shadow mapping.<br>
* for more informations on this read this http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html<br>
*
* @author Rémy Bouquet aka Nehon
*/
public class PssmShadowRenderer implements SceneProcessor { public class PssmShadowRenderer implements SceneProcessor {
/** /**
* <code>FilterMode</code> specifies how shadows are filtered * <code>FilterMode</code> specifies how shadows are filtered
*/ */
public enum FilterMode { public enum FilterMode {
/** /**
* Shadows are not filtered. Nearest sample is used, causing in blocky * Shadows are not filtered. Nearest sample is used, causing in blocky
* shadows. * shadows.
*/ */
Nearest, Nearest,
/** /**
* Bilinear filtering is used. Has the potential of being hardware * Bilinear filtering is used. Has the potential of being hardware
* accelerated on some GPUs * accelerated on some GPUs
*/ */
Bilinear, Bilinear,
/** /**
* Dither-based sampling is used, very cheap but can look bad * Dither-based sampling is used, very cheap but can look bad
* at low resolutions. * at low resolutions.
*/ */
Dither, Dither,
/** /**
* 4x4 percentage-closer filtering is used. Shadows will be smoother * 4x4 percentage-closer filtering is used. Shadows will be smoother
* at the cost of performance * at the cost of performance
*/ */
PCF4, PCF4,
/** /**
* 8x8 percentage-closer filtering is used. Shadows will be smoother * 8x8 percentage-closer filtering is used. Shadows will be smoother
* at the cost of performance * at the cost of performance
@ -94,19 +99,21 @@ public class PssmShadowRenderer implements SceneProcessor {
PCF8 PCF8
} }
/**
* Specifies the shadow comparison mode
*/
public enum CompareMode { public enum CompareMode {
/** /**
* Shadow depth comparisons are done by using shader code * Shadow depth comparisons are done by using shader code
*/ */
Software, Software,
/** /**
* Shadow depth comparisons are done by using the GPU's dedicated * Shadow depth comparisons are done by using the GPU's dedicated
* shadowing pipeline. * shadowing pipeline.
*/ */
Hardware; Hardware;
} }
private int nbSplits = 3; private int nbSplits = 3;
private float lambda = 0.65f; private float lambda = 0.65f;
private float shadowIntensity = 0.7f; private float shadowIntensity = 0.7f;
@ -119,21 +126,17 @@ public class PssmShadowRenderer implements SceneProcessor {
private Camera shadowCam; private Camera shadowCam;
private Material preshadowMat; private Material preshadowMat;
private Material postshadowMat; private Material postshadowMat;
private GeometryList splitOccluders = new GeometryList(new OpaqueComparator()); private GeometryList splitOccluders = new GeometryList(new OpaqueComparator());
private Matrix4f[] lightViewProjectionsMatrices; private Matrix4f[] lightViewProjectionsMatrices;
private ColorRGBA splits; private ColorRGBA splits;
private float[] splitsArray; private float[] splitsArray;
private boolean noOccluders = false; private boolean noOccluders = false;
private Vector3f direction = new Vector3f(); private Vector3f direction = new Vector3f();
private AssetManager assetManager; private AssetManager assetManager;
private boolean debug = false; private boolean debug = false;
private float edgesThickness = 1.0f; private float edgesThickness = 1.0f;
private FilterMode filterMode; private FilterMode filterMode;
private CompareMode compareMode; private CompareMode compareMode;
private Picture[] dispPic; private Picture[] dispPic;
private Vector3f[] points = new Vector3f[8]; private Vector3f[] points = new Vector3f[8];
@ -154,10 +157,10 @@ public class PssmShadowRenderer implements SceneProcessor {
dispPic = new Picture[nbSplits]; dispPic = new Picture[nbSplits];
lightViewProjectionsMatrices = new Matrix4f[nbSplits]; lightViewProjectionsMatrices = new Matrix4f[nbSplits];
splits = new ColorRGBA(); splits = new ColorRGBA();
splitsArray = new float[nbSplits+1]; splitsArray = new float[nbSplits + 1];
//DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
dummyTex= new Texture2D(size, size, Format.RGBA8); dummyTex = new Texture2D(size, size, Format.RGBA8);
preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md"); preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");
postshadowMat = new Material(manager, "Common/MatDefs/Shadow/PostShadowPSSM.j3md"); postshadowMat = new Material(manager, "Common/MatDefs/Shadow/PostShadowPSSM.j3md");
@ -190,22 +193,28 @@ public class PssmShadowRenderer implements SceneProcessor {
} }
} }
public void setFilterMode(FilterMode filterMode){ /**
if (filterMode == null) * Sets the filtering mode for shadow edges see {@link FilterMode} for more info
* @param filterMode
*/
public void setFilterMode(FilterMode filterMode) {
if (filterMode == null) {
throw new NullPointerException(); throw new NullPointerException();
}
if (this.filterMode == filterMode) if (this.filterMode == filterMode) {
return; return;
}
this.filterMode = filterMode; this.filterMode = filterMode;
postshadowMat.setInt("FilterMode", filterMode.ordinal()); postshadowMat.setInt("FilterMode", filterMode.ordinal());
postshadowMat.setFloat("PCFEdge", edgesThickness); postshadowMat.setFloat("PCFEdge", edgesThickness);
if (compareMode == CompareMode.Hardware){ if (compareMode == CompareMode.Hardware) {
for (Texture2D shadowMap : shadowMaps){ for (Texture2D shadowMap : shadowMaps) {
if (filterMode == FilterMode.Bilinear){ if (filterMode == FilterMode.Bilinear) {
shadowMap.setMagFilter(MagFilter.Bilinear); shadowMap.setMagFilter(MagFilter.Bilinear);
shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
}else{ } else {
shadowMap.setMagFilter(MagFilter.Nearest); shadowMap.setMagFilter(MagFilter.Nearest);
shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
} }
@ -213,25 +222,31 @@ public class PssmShadowRenderer implements SceneProcessor {
} }
} }
/**
* sets the shadow compare mode see {@link CompareMode} for more info
* @param compareMode
*/
public void setCompareMode(CompareMode compareMode) { public void setCompareMode(CompareMode compareMode) {
if (compareMode == null) if (compareMode == null) {
throw new NullPointerException(); throw new NullPointerException();
}
if (this.compareMode == compareMode) if (this.compareMode == compareMode) {
return; return;
}
this.compareMode = compareMode; this.compareMode = compareMode;
for (Texture2D shadowMap : shadowMaps){ for (Texture2D shadowMap : shadowMaps) {
if (compareMode == CompareMode.Hardware){ if (compareMode == CompareMode.Hardware) {
shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual); shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual);
if (filterMode == FilterMode.Bilinear){ if (filterMode == FilterMode.Bilinear) {
shadowMap.setMagFilter(MagFilter.Bilinear); shadowMap.setMagFilter(MagFilter.Bilinear);
shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
}else{ } else {
shadowMap.setMagFilter(MagFilter.Nearest); shadowMap.setMagFilter(MagFilter.Nearest);
shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
} }
} else{ } else {
shadowMap.setShadowCompareMode(ShadowCompareMode.Off); shadowMap.setShadowCompareMode(ShadowCompareMode.Off);
shadowMap.setMagFilter(MagFilter.Nearest); shadowMap.setMagFilter(MagFilter.Nearest);
shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
@ -280,10 +295,18 @@ public class PssmShadowRenderer implements SceneProcessor {
return viewPort != null; return viewPort != null;
} }
/**
* returns the light direction used by the processor
* @return
*/
public Vector3f getDirection() { public Vector3f getDirection() {
return direction; return direction;
} }
/**
* Sets the light direction to use to compute shadows
* @param direction
*/
public void setDirection(Vector3f direction) { public void setDirection(Vector3f direction) {
this.direction.set(direction).normalizeLocal(); this.direction.set(direction).normalizeLocal();
} }
@ -291,18 +314,20 @@ public class PssmShadowRenderer implements SceneProcessor {
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
public void postQueue(RenderQueue rq) { public void postQueue(RenderQueue rq) {
GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast); GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);
if (occluders.size() == 0) if (occluders.size() == 0) {
return; return;
}
GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive); GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);
if (receivers.size() == 0) if (receivers.size() == 0) {
return; return;
}
Camera viewCam = viewPort.getCamera(); Camera viewCam = viewPort.getCamera();
float zFar = zFarOverride; float zFar = zFarOverride;
if (zFar == 0) { if (zFar == 0) {
zFar=viewCam.getFrustumFar(); zFar = viewCam.getFrustumFar();
// zFar = PssmShadowUtil.computeZFar(occluders, receivers, viewCam); // zFar = PssmShadowUtil.computeZFar(occluders, receivers, viewCam);
} }
// System.out.println("Zfar : "+zFar); // System.out.println("Zfar : "+zFar);
@ -322,7 +347,7 @@ public class PssmShadowRenderer implements SceneProcessor {
PssmShadowUtil.updateFrustumSplits(splitsArray, viewCam.getFrustumNear(), zFar, lambda); PssmShadowUtil.updateFrustumSplits(splitsArray, viewCam.getFrustumNear(), zFar, lambda);
switch (splitsArray.length){ switch (splitsArray.length) {
case 5: case 5:
splits.a = splitsArray[4]; splits.a = splitsArray[4];
case 4: case 4:
@ -345,25 +370,7 @@ public class PssmShadowRenderer implements SceneProcessor {
ShadowUtil.updateFrustumPoints(viewCam, splitsArray[i], splitsArray[i + 1], 1.0f, points); ShadowUtil.updateFrustumPoints(viewCam, splitsArray[i], splitsArray[i + 1], 1.0f, points);
//Updating shadow cam with curent split frustra //Updating shadow cam with curent split frustra
// if(cropShadows){
ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders); ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders);
// }else{
// ShadowUtil.updateShadowCamera(shadowCam, points);
// }
//displaying the current splitted frustrum and the associated cropped light frustrums in wireframe.
//only for debuging purpose
// if (debug) {
// viewPort.attachScene(createFrustum(points, i));
// Vector3f[] pts = new Vector3f[8];
// for (int j = 0; j < pts.length; j++) {
// pts[j] = new Vector3f();
// }
// ShadowUtil.updateFrustumPoints2(shadowCam, pts);
// viewPort.attachScene(createFrustum(pts, i));
// if (i == nbSplits-1) {
// debug = false;
// }
// }
//saving light view projection matrix for this split //saving light view projection matrix for this split
lightViewProjectionsMatrices[i] = shadowCam.getViewProjectionMatrix().clone(); lightViewProjectionsMatrices[i] = shadowCam.getViewProjectionMatrix().clone();
@ -374,8 +381,6 @@ public class PssmShadowRenderer implements SceneProcessor {
// render shadow casters to shadow map // render shadow casters to shadow map
viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true); viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true);
//viewPort.getQueue().renderShadowQueue(ShadowMode.Cast, renderManager, shadowCam, i == nbSplits - 1);
} }
occluders.clear(); occluders.clear();
//restore setting for future rendering //restore setting for future rendering
@ -387,7 +392,7 @@ public class PssmShadowRenderer implements SceneProcessor {
} }
//debug only : displays depth shadow maps //debug only : displays depth shadow maps
public void displayShadowMap(Renderer r) { private void displayShadowMap(Renderer r) {
Camera cam = viewPort.getCamera(); Camera cam = viewPort.getCamera();
renderManager.setCamera(cam, true); renderManager.setCamera(cam, true);
int h = cam.getHeight(); int h = cam.getHeight();
@ -421,9 +426,10 @@ public class PssmShadowRenderer implements SceneProcessor {
renderManager.setCamera(cam, false); renderManager.setCamera(cam, false);
} }
if (debug) if (debug) {
displayShadowMap(renderManager.getRenderer()); displayShadowMap(renderManager.getRenderer());
} }
}
public void preFrame(float tpf) { public void preFrame(float tpf) {
} }
@ -434,6 +440,11 @@ public class PssmShadowRenderer implements SceneProcessor {
public void reshape(ViewPort vp, int w, int h) { public void reshape(ViewPort vp, int w, int h) {
} }
/**
* returns the labda parameter<br>
* see {@link setLambda(float lambda)}
* @return
*/
public float getLambda() { public float getLambda() {
return lambda; return lambda;
} }
@ -450,6 +461,11 @@ public class PssmShadowRenderer implements SceneProcessor {
this.lambda = lambda; this.lambda = lambda;
} }
/**
* How far the shadows are rendered in the view
* see {@link setShadowZExtend(float zFar)}
* @return
*/
public float getShadowZExtend() { public float getShadowZExtend() {
return zFarOverride; return zFarOverride;
} }
@ -463,6 +479,11 @@ public class PssmShadowRenderer implements SceneProcessor {
this.zFarOverride = zFar; this.zFarOverride = zFar;
} }
/**
* returns the shdaow intensity<br>
* see {@link setShadowIntensity(float shadowIntensity)}
* @return
*/
public float getShadowIntensity() { public float getShadowIntensity() {
return shadowIntensity; return shadowIntensity;
} }
@ -479,14 +500,22 @@ public class PssmShadowRenderer implements SceneProcessor {
postshadowMat.setFloat("ShadowIntensity", shadowIntensity); postshadowMat.setFloat("ShadowIntensity", shadowIntensity);
} }
/**
* returns the edges thickness <br>
* see {@link setEdgesThickness(int edgesThickness)}
* @return
*/
public int getEdgesThickness() { public int getEdgesThickness() {
return (int) (edgesThickness * 10); return (int) (edgesThickness * 10);
} }
/**
* Stes the shadow edges thickness. default is 1, setting it to lower values can help to reduce the jagged effect of the shadow edges
* @param edgesThickness
*/
public void setEdgesThickness(int edgesThickness) { public void setEdgesThickness(int edgesThickness) {
this.edgesThickness = Math.max(1, Math.min(edgesThickness,10)); this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10));
this.edgesThickness *= 0.1f; this.edgesThickness *= 0.1f;
postshadowMat.setFloat("PCFEdge", edgesThickness); postshadowMat.setFloat("PCFEdge", edgesThickness);
} }
} }

View File

@ -29,7 +29,6 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.shadow; package com.jme3.shadow;
import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingBox;
@ -52,18 +51,6 @@ import static java.lang.Math.*;
*/ */
public final class PssmShadowUtil { public final class PssmShadowUtil {
public static void main(String[] args){
float[] splits = new float[5];
float[] splitsShader = new float[3];
updateFrustumSplits(splits, 1, 1000, 0.5f);
System.arraycopy(splits, 1, splitsShader, 0, splitsShader.length);
System.out.println(Arrays.toString(splitsShader));
for (int i = 0; i < splits.length-1; i++){
System.out.println(splits[i] + " - " + splits[i+1]);
}
}
/** /**
* Updates the frustum splits stores in <code>splits</code> using PSSM. * Updates the frustum splits stores in <code>splits</code> using PSSM.
*/ */

View File

@ -29,7 +29,6 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.shadow; package com.jme3.shadow;
import com.jme3.light.DirectionalLight; import com.jme3.light.DirectionalLight;
@ -38,14 +37,19 @@ import com.jme3.light.PointLight;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
/**
* Creates a camera according to a light
* Handy to compute projection matrix of a light
* @author Kirill Vainer
*/
public class ShadowCamera { public class ShadowCamera {
private Vector3f[] points = new Vector3f[8]; private Vector3f[] points = new Vector3f[8];
private Light target; private Light target;
public ShadowCamera(Light target){ public ShadowCamera(Light target) {
this.target = target; this.target = target;
for (int i = 0; i < points.length; i++){ for (int i = 0; i < points.length; i++) {
points[i] = new Vector3f(); points[i] = new Vector3f();
} }
} }
@ -53,14 +57,14 @@ public class ShadowCamera {
/** /**
* Updates the camera view direction and position based on the light * Updates the camera view direction and position based on the light
*/ */
private void updateLightCamera(Camera lightCam){ public void updateLightCamera(Camera lightCam) {
if (target.getType() == Light.Type.Directional){ if (target.getType() == Light.Type.Directional) {
DirectionalLight dl = (DirectionalLight) target; DirectionalLight dl = (DirectionalLight) target;
lightCam.setParallelProjection(true); lightCam.setParallelProjection(true);
lightCam.setLocation(Vector3f.ZERO); lightCam.setLocation(Vector3f.ZERO);
lightCam.lookAtDirection(dl.getDirection(), Vector3f.UNIT_Y ); lightCam.lookAtDirection(dl.getDirection(), Vector3f.UNIT_Y);
lightCam.setFrustum(-1, 1, -1, 1, 1, -1); lightCam.setFrustum(-1, 1, -1, 1, 1, -1);
}else{ } else {
PointLight pl = (PointLight) target; PointLight pl = (PointLight) target;
lightCam.setParallelProjection(false); lightCam.setParallelProjection(false);
lightCam.setLocation(pl.getPosition()); lightCam.setLocation(pl.getPosition());
@ -68,5 +72,4 @@ public class ShadowCamera {
lightCam.setFrustumPerspective(45, 1, 1, 300); lightCam.setFrustumPerspective(45, 1, 1, 300);
} }
} }
} }

View File

@ -29,12 +29,10 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.shadow; package com.jme3.shadow;
import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingVolume; import com.jme3.bounding.BoundingVolume;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.math.Transform; import com.jme3.math.Transform;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
@ -58,7 +56,12 @@ import static java.lang.Math.*;
*/ */
public class ShadowUtil { public class ShadowUtil {
public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points){ /**
* Updates a points arrays with the frustum corners of the provided camera.
* @param viewCam
* @param points
*/
public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) {
int w = viewCam.getWidth(); int w = viewCam.getWidth();
int h = viewCam.getHeight(); int h = viewCam.getHeight();
float n = viewCam.getFrustumNear(); float n = viewCam.getFrustumNear();
@ -160,6 +163,12 @@ public class ShadowUtil {
} }
} }
/**
* Compute bounds of a geomList
* @param list
* @param transform
* @return
*/
public static BoundingBox computeUnionBound(GeometryList list, Transform transform) { public static BoundingBox computeUnionBound(GeometryList list, Transform transform) {
BoundingBox bbox = new BoundingBox(); BoundingBox bbox = new BoundingBox();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
@ -173,6 +182,12 @@ public class ShadowUtil {
return bbox; return bbox;
} }
/**
* Compute bounds of a geomList
* @param list
* @param mat
* @return
*/
public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) { public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) {
BoundingBox bbox = new BoundingBox(); BoundingBox bbox = new BoundingBox();
BoundingVolume store = null; BoundingVolume store = null;
@ -187,6 +202,11 @@ public class ShadowUtil {
return bbox; return bbox;
} }
/**
* Computes the bounds of multiple bounding volumes
* @param bv
* @return
*/
public static BoundingBox computeUnionBound(List<BoundingVolume> bv) { public static BoundingBox computeUnionBound(List<BoundingVolume> bv) {
BoundingBox bbox = new BoundingBox(); BoundingBox bbox = new BoundingBox();
for (int i = 0; i < bv.size(); i++) { for (int i = 0; i < bv.size(); i++) {
@ -196,6 +216,12 @@ public class ShadowUtil {
return bbox; return bbox;
} }
/**
* Compute bounds from an array of points
* @param pts
* @param transform
* @return
*/
public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) { public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) {
Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY); Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY); Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
@ -211,6 +237,12 @@ public class ShadowUtil {
return new BoundingBox(center, extent.x, extent.y, extent.z); return new BoundingBox(center, extent.x, extent.y, extent.z);
} }
/**
* Compute bounds from an array of points
* @param pts
* @param mat
* @return
*/
public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) { public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) {
Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY); Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY); Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
@ -228,16 +260,10 @@ public class ShadowUtil {
max.maxLocal(temp); max.maxLocal(temp);
} }
// min.x = FastMath.clamp(min.x, -1f, 1f);
// max.y = FastMath.clamp(max.y, -1f, 1f);
// min.x = FastMath.clamp(min.x, -1f, 1f);
// max.y = FastMath.clamp(max.y, -1f, 1f);
Vector3f center = min.add(max).multLocal(0.5f); Vector3f center = min.add(max).multLocal(0.5f);
Vector3f extent = max.subtract(min).multLocal(0.5f); Vector3f extent = max.subtract(min).multLocal(0.5f);
//Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned //Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned
return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z +2.5f); return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f);
//return new BoundingBox(center, extent.x, extent.y, extent.z);
} }
/** /**
@ -248,7 +274,7 @@ public class ShadowUtil {
* @param lightCam * @param lightCam
* @param points * @param points
*/ */
public static void updateShadowCamera(Camera shadowCam, Vector3f[] points){ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) {
boolean ortho = shadowCam.isParallelProjection(); boolean ortho = shadowCam.isParallelProjection();
shadowCam.setProjectionMatrix(null); shadowCam.setProjectionMatrix(null);
@ -304,7 +330,7 @@ public class ShadowUtil {
public static void updateShadowCamera(GeometryList occluders, public static void updateShadowCamera(GeometryList occluders,
GeometryList receivers, GeometryList receivers,
Camera shadowCam, Camera shadowCam,
Vector3f[] points){ Vector3f[] points) {
updateShadowCamera(occluders, receivers, shadowCam, points, null); updateShadowCamera(occluders, receivers, shadowCam, points, null);
} }
@ -321,48 +347,45 @@ public class ShadowUtil {
GeometryList receivers, GeometryList receivers,
Camera shadowCam, Camera shadowCam,
Vector3f[] points, Vector3f[] points,
GeometryList splitOccluders){ GeometryList splitOccluders) {
boolean ortho = shadowCam.isParallelProjection(); boolean ortho = shadowCam.isParallelProjection();
shadowCam.setProjectionMatrix(null); shadowCam.setProjectionMatrix(null);
if (ortho){ if (ortho) {
shadowCam.setFrustum(-1, 1, -1, 1, 1, -1); shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
}else{ } else {
shadowCam.setFrustumPerspective(45, 1, 1, 150); shadowCam.setFrustumPerspective(45, 1, 1, 150);
} }
// create transform to rotate points to viewspace // create transform to rotate points to viewspace
//Transform t = new Transform(shadowCam.getRotation());
Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix(); Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
// BoundingBox casterBB = computeUnionBound(occluders, viewProjMatrix);
// BoundingBox receiverBB = computeUnionBound(receivers, viewProjMatrix);
BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix); BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
ArrayList<BoundingVolume> visRecvList = new ArrayList<BoundingVolume>(); ArrayList<BoundingVolume> visRecvList = new ArrayList<BoundingVolume>();
for (int i = 0; i < receivers.size(); i++){ for (int i = 0; i < receivers.size(); i++) {
// convert bounding box to light's viewproj space // convert bounding box to light's viewproj space
Geometry receiver = receivers.get(i); Geometry receiver = receivers.get(i);
BoundingVolume bv = receiver.getWorldBound(); BoundingVolume bv = receiver.getWorldBound();
BoundingVolume recvBox = bv.transform(viewProjMatrix, null); BoundingVolume recvBox = bv.transform(viewProjMatrix, null);
if (splitBB.intersects(recvBox)){ if (splitBB.intersects(recvBox)) {
visRecvList.add(recvBox); visRecvList.add(recvBox);
} }
} }
ArrayList<BoundingVolume> visOccList = new ArrayList<BoundingVolume>(); ArrayList<BoundingVolume> visOccList = new ArrayList<BoundingVolume>();
for (int i = 0; i < occluders.size(); i++){ for (int i = 0; i < occluders.size(); i++) {
// convert bounding box to light's viewproj space // convert bounding box to light's viewproj space
Geometry occluder = occluders.get(i); Geometry occluder = occluders.get(i);
BoundingVolume bv = occluder.getWorldBound(); BoundingVolume bv = occluder.getWorldBound();
BoundingVolume occBox = bv.transform(viewProjMatrix, null); BoundingVolume occBox = bv.transform(viewProjMatrix, null);
boolean intersects = splitBB.intersects(occBox); boolean intersects = splitBB.intersects(occBox);
if (!intersects && occBox instanceof BoundingBox){ if (!intersects && occBox instanceof BoundingBox) {
BoundingBox occBB = (BoundingBox)occBox; BoundingBox occBB = (BoundingBox) occBox;
//Kirill 01/10/2011 //Kirill 01/10/2011
// Extend the occluder further into the frustum // Extend the occluder further into the frustum
// This fixes shadow dissapearing issues when // This fixes shadow dissapearing issues when
@ -371,20 +394,20 @@ public class ShadowUtil {
// The number is in world units // The number is in world units
occBB.setZExtent(occBB.getZExtent() + 50); occBB.setZExtent(occBB.getZExtent() + 50);
occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25)); occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25));
if (splitBB.intersects(occBB)){ if (splitBB.intersects(occBB)) {
// To prevent extending the depth range too much // To prevent extending the depth range too much
// We return the bound to its former shape // We return the bound to its former shape
// Before adding it // Before adding it
occBB.setZExtent(occBB.getZExtent() - 50); occBB.setZExtent(occBB.getZExtent() - 50);
occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25)); occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25));
visOccList.add(occBox); visOccList.add(occBox);
if(splitOccluders != null){ if (splitOccluders != null) {
splitOccluders.add(occluder); splitOccluders.add(occluder);
} }
} }
}else if (intersects){ } else if (intersects) {
visOccList.add(occBox); visOccList.add(occBox);
if(splitOccluders != null){ if (splitOccluders != null) {
splitOccluders.add(occluder); splitOccluders.add(occluder);
} }
} }
@ -409,15 +432,11 @@ public class ShadowUtil {
Vector3f splitMin = splitBB.getMin(null); Vector3f splitMin = splitBB.getMin(null);
Vector3f splitMax = splitBB.getMax(null); Vector3f splitMax = splitBB.getMax(null);
// actualMin.x = FastMath.clamp(actualMin.x, -1, 1);
// actualMin.y = FastMath.clamp(actualMin.y, -1, 1);
// actualMax.x = FastMath.clamp(actualMax.x, -1, 1);
// actualMax.y = FastMath.clamp(actualMax.y, -1, 1);
// float far = actualMin.z + actualMax.z * 4 + 1.0f + 1.5f;
splitMin.z = 0; splitMin.z = 0;
if (!ortho) if (!ortho) {
shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z); shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z);
}
Matrix4f projMatrix = shadowCam.getProjectionMatrix(); Matrix4f projMatrix = shadowCam.getProjectionMatrix();
@ -434,11 +453,6 @@ public class ShadowUtil {
cropMin.z = min(casterMin.z, splitMin.z); cropMin.z = min(casterMin.z, splitMin.z);
cropMax.z = min(receiverMax.z, splitMax.z); cropMax.z = min(receiverMax.z, splitMax.z);
// cropMin.set(splitMin);
// cropMax.set(splitMax);
// cropMin.z = Math.min(cropMin.z, cropMax.z - cropMin.z - 1000);
// cropMin.z = Math.max(10f, cropMin.z);
// Create the crop matrix. // Create the crop matrix.
float scaleX, scaleY, scaleZ; float scaleX, scaleY, scaleZ;
@ -453,41 +467,19 @@ public class ShadowUtil {
scaleZ = 1.0f / (cropMax.z - cropMin.z); scaleZ = 1.0f / (cropMax.z - cropMin.z);
offsetZ = -cropMin.z * scaleZ; offsetZ = -cropMin.z * scaleZ;
// scaleZ = 2.0f / (cropMax.z - cropMin.z);
// offsetZ = -0.5f * (cropMax.z + cropMin.z) * scaleZ;
Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX, Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
0f, scaleY, 0f, offsetY, 0f, scaleY, 0f, offsetY,
0f, 0f, scaleZ, offsetZ, 0f, 0f, scaleZ, offsetZ,
0f, 0f, 0f, 1f); 0f, 0f, 0f, 1f);
// cropMatrix.transposeLocal();
// Matrix4f cropMatrix = new Matrix4f();
// cropMatrix.setScale(new Vector3f(scaleX, scaleY, 1f));
// cropMatrix.setTranslation(offsetX, offsetY, 0);
Matrix4f result = new Matrix4f(); Matrix4f result = new Matrix4f();
result.set(cropMatrix); result.set(cropMatrix);
result.multLocal(projMatrix); result.multLocal(projMatrix);
// result.set(projMatrix);
// result.multLocal(cropMatrix);
shadowCam.setProjectionMatrix(result); shadowCam.setProjectionMatrix(result);
// shadowCam.setFrustum(cropMin.z, cropMax.z, // near, far
// cropMin.x, cropMax.x, // left, right
// cropMax.y, cropMin.y); // top, bottom
// compute size and center of final frustum
//float sizeX = (max.x - min.x) / 2f;
//float sizeY = (max.y - min.y) / 2f;
//float offsetX = (max.x + min.x) / -2f;
//float offsetY = (max.y + min.y) / -2f;
// compute center for frustum
//temp.set(offsetX, offsetY, 0);
//invRot.mult(temp, temp);
//shadowCam.setLocation(temp);
//shadowCam.setFrustum(min.z, max.z, -sizeX, sizeX, sizeY, -sizeY);
} }
} }