Point light shadows first push. Working solution based on rendering 6 different shadow maps.

Made a PointlLightShadowRenderer and a PointLightShadowFilter.
- This will need a lot of refactoring as a lot of code is duplicated with the PSSMShadowRenderer
- Also i plan to change the Shadow map rendering to a cubemap instead of 6 separate textures.
Added a cornell box model and a test case


git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9942 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 12 years ago
parent 8d841b70dd
commit 66ddbb654d
  1. 11
      engine/src/core-data/Common/MatDefs/Light/Lighting.j3md
  2. 40
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowFilter.frag
  3. 11
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowFilter.j3md
  4. 39
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowFilter15.frag
  5. 37
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.frag
  6. 11
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.j3md
  7. 19
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.vert
  8. 46
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM15.frag
  9. 14
      engine/src/core-data/Common/ShaderLib/PssmShadows.glsllib
  10. 16
      engine/src/core-data/Common/ShaderLib/PssmShadows15.glsllib
  11. 241
      engine/src/core/com/jme3/shadow/PointLightShadowFilter.java
  12. 753
      engine/src/core/com/jme3/shadow/PointLightShadowRenderer.java
  13. 43
      engine/src/core/com/jme3/shadow/ShadowUtil.java
  14. 237
      engine/src/test/jme3test/light/TestPointLightShadows.java
  15. BIN
      engine/test-data/Models/Test/CornellBox.j3o

@ -108,6 +108,9 @@ MaterialDef Phong Lighting {
Texture2D ShadowMap1
Texture2D ShadowMap2
Texture2D ShadowMap3
//pointLights
Texture2D ShadowMap4
Texture2D ShadowMap5
Float ShadowIntensity
Vector4 Splits
@ -117,6 +120,10 @@ MaterialDef Phong Lighting {
Matrix4 LightViewProjectionMatrix1
Matrix4 LightViewProjectionMatrix2
Matrix4 LightViewProjectionMatrix3
//pointLight
Matrix4 LightViewProjectionMatrix4
Matrix4 LightViewProjectionMatrix5
Vector3 LightPos
Float PCFEdge
Float ShadowMapSize
@ -209,6 +216,8 @@ MaterialDef Phong Lighting {
COLOR_MAP : ColorMap
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
ForcedRenderState {
@ -235,6 +244,8 @@ MaterialDef Phong Lighting {
COLOR_MAP : ColorMap
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
ForcedRenderState {

@ -18,6 +18,12 @@ uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
#ifdef POINTLIGHT
uniform vec3 m_LightPos;
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
#endif
#ifdef FADE
uniform vec2 m_FadeInfo;
#endif
@ -47,11 +53,15 @@ void main(){
vec4 projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos;
vec4 projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos;
vec4 projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos;
#ifdef POINTLIGHT
vec4 projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos;
vec4 projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
#endif
float shadow = 1.0;
#ifdef PSSM
float shadowPosition = m_ViewProjectionMatrixRow2.x * worldPos.x + m_ViewProjectionMatrixRow2.y * worldPos.y + m_ViewProjectionMatrixRow2.z * worldPos.z + m_ViewProjectionMatrixRow2.w;
float shadow = 1.0;
if(shadowPosition < m_Splits.x){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else if( shadowPosition < m_Splits.y){
@ -64,6 +74,32 @@ void main(){
shadowBorderScale = 0.125;
shadow = GETSHADOW(m_ShadowMap3, projCoord3);
}
#endif
#ifdef POINTLIGHT
vec3 vect = worldPos.xyz - m_LightPos;
vec3 absv= abs(vect);
float maxComp = max(absv.x,max(absv.y,absv.z));
if(maxComp == absv.y){
if(vect.y < 0.0){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else{
shadow = GETSHADOW(m_ShadowMap1, projCoord1);
}
}else if(maxComp == absv.z){
if(vect.z < 0.0){
shadow = GETSHADOW(m_ShadowMap2, projCoord2);
}else{
shadow = GETSHADOW(m_ShadowMap3, projCoord3);
}
}else if(maxComp == absv.x){
if(vect.x < 0.0){
shadow = GETSHADOW(m_ShadowMap4, projCoord4);
}else{
shadow = GETSHADOW(m_ShadowMap5, projCoord5);
}
}
#endif
#ifdef FADE
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));

@ -8,6 +8,10 @@ MaterialDef Post Shadow {
Texture2D ShadowMap1
Texture2D ShadowMap2
Texture2D ShadowMap3
//pointLights
Texture2D ShadowMap4
Texture2D ShadowMap5
Vector3 LightPos
Float ShadowIntensity
Vector4 Splits
@ -17,6 +21,9 @@ MaterialDef Post Shadow {
Matrix4 LightViewProjectionMatrix1
Matrix4 LightViewProjectionMatrix2
Matrix4 LightViewProjectionMatrix3
//pointLight
Matrix4 LightViewProjectionMatrix4
Matrix4 LightViewProjectionMatrix5
Float PCFEdge
@ -48,6 +55,8 @@ MaterialDef Post Shadow {
PCFEDGE : PCFEdge
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
}
@ -66,6 +75,8 @@ MaterialDef Post Shadow {
PCFEDGE : PCFEdge
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
}

@ -20,6 +20,12 @@ uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
#ifdef POINTLIGHT
uniform vec3 m_LightPos;
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
#endif
#ifdef FADE
uniform vec2 m_FadeInfo;
#endif
@ -47,10 +53,15 @@ vec4 main_multiSample(in int numSample){
vec4 projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos;
vec4 projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos;
vec4 projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos;
#ifdef POINTLIGHT
vec4 projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos;
vec4 projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
#endif
float shadow = 1.0;
#ifdef PSSM
float shadowPosition = m_ViewProjectionMatrixRow2.x * worldPos.x + m_ViewProjectionMatrixRow2.y * worldPos.y + m_ViewProjectionMatrixRow2.z * worldPos.z + m_ViewProjectionMatrixRow2.w;
float shadow = 1.0;
if(shadowPosition < m_Splits.x){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else if( shadowPosition < m_Splits.y){
@ -63,6 +74,32 @@ vec4 main_multiSample(in int numSample){
shadowBorderScale = 0.125;
shadow = GETSHADOW(m_ShadowMap3, projCoord3);
}
#endif
#ifdef POINTLIGHT
vec3 vect = worldPos.xyz - m_LightPos;
vec3 absv= abs(vect);
float maxComp = max(absv.x,max(absv.y,absv.z));
if(maxComp == absv.y){
if(vect.y < 0.0){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else{
shadow = GETSHADOW(m_ShadowMap1, projCoord1);
}
}else if(maxComp == absv.z){
if(vect.z < 0.0){
shadow = GETSHADOW(m_ShadowMap2, projCoord2);
}else{
shadow = GETSHADOW(m_ShadowMap3, projCoord3);
}
}else if(maxComp == absv.x){
if(vect.x < 0.0){
shadow = GETSHADOW(m_ShadowMap4, projCoord4);
}else{
shadow = GETSHADOW(m_ShadowMap5, projCoord5);
}
}
#endif
#ifdef FADE
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));

@ -1,11 +1,21 @@
#import "Common/ShaderLib/PssmShadows.glsllib"
#ifdef PSSM
varying float shadowPosition;
#endif
varying vec4 projCoord0;
varying vec4 projCoord1;
varying vec4 projCoord2;
varying vec4 projCoord3;
#ifdef POINTLIGHT
varying vec4 projCoord4;
varying vec4 projCoord5;
uniform vec3 m_LightPos;
varying vec4 worldPos;
#endif
#ifdef DISCARD_ALPHA
#ifdef COLOR_MAP
uniform sampler2D m_ColorMap;
@ -35,6 +45,7 @@ void main(){
#endif
float shadow = 1.0;
#ifdef PSSM
if(shadowPosition < m_Splits.x){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else if( shadowPosition < m_Splits.y){
@ -47,6 +58,32 @@ void main(){
shadowBorderScale = 0.125;
shadow = GETSHADOW(m_ShadowMap3, projCoord3);
}
#endif
#ifdef POINTLIGHT
vec3 vect = worldPos.xyz - m_LightPos;
vec3 absv= abs(vect);
float maxComp = max(absv.x,max(absv.y,absv.z));
if(maxComp == absv.y){
if(vect.y < 0.0){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else{
shadow = GETSHADOW(m_ShadowMap1, projCoord1);
}
}else if(maxComp == absv.z){
if(vect.z < 0.0){
shadow = GETSHADOW(m_ShadowMap2, projCoord2);
}else{
shadow = GETSHADOW(m_ShadowMap3, projCoord3);
}
}else if(maxComp == absv.x){
if(vect.x < 0.0){
shadow = GETSHADOW(m_ShadowMap4, projCoord4);
}else{
shadow = GETSHADOW(m_ShadowMap5, projCoord5);
}
}
#endif
#ifdef FADE
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));

@ -8,6 +8,9 @@ MaterialDef Post Shadow {
Texture2D ShadowMap1
Texture2D ShadowMap2
Texture2D ShadowMap3
//pointLights
Texture2D ShadowMap4
Texture2D ShadowMap5
Float ShadowIntensity
Vector4 Splits
@ -17,6 +20,10 @@ MaterialDef Post Shadow {
Matrix4 LightViewProjectionMatrix1
Matrix4 LightViewProjectionMatrix2
Matrix4 LightViewProjectionMatrix3
//pointLight
Matrix4 LightViewProjectionMatrix4
Matrix4 LightViewProjectionMatrix5
Vector3 LightPos
Float PCFEdge
@ -38,6 +45,8 @@ MaterialDef Post Shadow {
PCFEDGE : PCFEdge
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
RenderState {
@ -62,6 +71,8 @@ MaterialDef Post Shadow {
PCFEDGE : PCFEdge
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
RenderState {

@ -11,7 +11,17 @@ varying vec4 projCoord1;
varying vec4 projCoord2;
varying vec4 projCoord3;
#ifdef POINTLIGHT
uniform mat4 m_LightViewProjectionMatrix4;
uniform mat4 m_LightViewProjectionMatrix5;
varying vec4 projCoord4;
varying vec4 projCoord5;
varying vec4 worldPos;
#endif
#ifdef PSSM
varying float shadowPosition;
#endif
varying vec2 texCoord;
@ -30,9 +40,12 @@ const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
void main(){
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
#ifdef PSSM
shadowPosition = gl_Position.z;
vec4 worldPos=vec4(0.0);
#endif
// get the vertex in world space
vec4 worldPos = g_WorldMatrix * vec4(inPosition, 1.0);
worldPos = g_WorldMatrix * vec4(inPosition, 1.0);
#ifdef DISCARD_ALPHA
texCoord = inTexCoord;
@ -42,4 +55,8 @@ void main(){
projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos;
projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos;
projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos;
#ifdef POINTLIGHT
projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos;
projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
#endif
}

@ -1,12 +1,23 @@
#import "Common/ShaderLib/PssmShadows15.glsllib"
out vec4 outFragColor;
#ifdef PSSM
in float shadowPosition;
#endif
in vec4 projCoord0;
in vec4 projCoord1;
in vec4 projCoord2;
in vec4 projCoord3;
#ifdef POINTLIGHT
in vec4 projCoord4;
in vec4 projCoord5;
uniform vec3 m_LightPos;
in vec4 worldPos;
#endif
#ifdef DISCARD_ALPHA
#ifdef COLOR_MAP
uniform sampler2D m_ColorMap;
@ -35,8 +46,8 @@ void main(){
}
#endif
float shadow = 1.0;
#ifdef PSSM
if(shadowPosition < m_Splits.x){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else if( shadowPosition < m_Splits.y){
@ -49,6 +60,39 @@ void main(){
shadowBorderScale = 0.125;
shadow = GETSHADOW(m_ShadowMap3, projCoord3);
}
#endif
#ifdef POINTLIGHT
vec3 vect = worldPos.xyz - m_LightPos;
vec3 absv= abs(vect);
float maxComp = max(absv.x,max(absv.y,absv.z));
if(maxComp == absv.y){
if(vect.y < 0.0){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
outFragColor = vec4(projCoord0.z);
}else{
shadow = GETSHADOW(m_ShadowMap1, projCoord1);
outFragColor = vec4(projCoord1.z);
}
}else if(maxComp == absv.z){
if(vect.z < 0.0){
shadow = GETSHADOW(m_ShadowMap2, projCoord2);
outFragColor =vec4(projCoord2.z);
}else{
shadow = GETSHADOW(m_ShadowMap3, projCoord3);
outFragColor = vec4(projCoord3.z);
}
}else if(maxComp == absv.x){
if(vect.x < 0.0){
shadow = GETSHADOW(m_ShadowMap4, projCoord4);
outFragColor = vec4(projCoord4.z);
}else{
shadow = GETSHADOW(m_ShadowMap5, projCoord5);
outFragColor = vec4(projCoord5.z);
}
}
#endif
#ifdef FADE
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));

@ -3,7 +3,7 @@
#define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r
#else
#define SHADOWMAP sampler2D
#define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
#define SHADOWCOMPARE(tex,coord) step(coord.z / coord.w, texture2DProj(tex, coord).r)
#endif
#if FILTER_MODE == 0
@ -35,8 +35,14 @@ uniform SHADOWMAP m_ShadowMap0;
uniform SHADOWMAP m_ShadowMap1;
uniform SHADOWMAP m_ShadowMap2;
uniform SHADOWMAP m_ShadowMap3;
#ifdef POINTLIGHT
uniform SHADOWMAP m_ShadowMap4;
uniform SHADOWMAP m_ShadowMap5;
#endif
#ifdef PSSM
uniform vec4 m_Splits;
#endif
uniform float m_ShadowIntensity;
@ -53,10 +59,16 @@ float Shadow_DoShadowCompare(in SHADOWMAP tex, vec4 projCoord){
}
float Shadow_BorderCheck(in vec2 coord){
#ifdef PSSM
// Fastest, "hack" method (uses 4-5 instructions)
vec4 t = vec4(coord.xy, 0.0, 1.0);
t = step(t.wwxy, t.xyzz);
return dot(t,t);
#else
//fix me this is a big fat hack to avoid issues whith point lights,
//this function fail to return correct values, but it works with PSSM
return 0.0;
#endif
}
float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){

@ -9,8 +9,8 @@
#define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z)
#else
#define SHADOWMAP sampler2D
#define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r)
#define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r)
#define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z / coord.w, textureProjOffset(tex, coord, offset).r)
#define SHADOWCOMPARE(tex,coord) step(coord.z / coord.w, textureProj(tex, coord).r)
#define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
#endif
@ -45,18 +45,30 @@ uniform SHADOWMAP m_ShadowMap0;
uniform SHADOWMAP m_ShadowMap1;
uniform SHADOWMAP m_ShadowMap2;
uniform SHADOWMAP m_ShadowMap3;
#ifdef POINTLIGHT
uniform SHADOWMAP m_ShadowMap4;
uniform SHADOWMAP m_ShadowMap5;
#endif
#ifdef PSSM
uniform vec4 m_Splits;
#endif
uniform float m_ShadowIntensity;
const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
float shadowBorderScale = 1.0;
float Shadow_BorderCheck(in vec2 coord){
#ifdef PSSM
// Fastest, "hack" method (uses 4-5 instructions)
vec4 t = vec4(coord.xy, 0.0, 1.0);
t = step(t.wwxy, t.xyzz);
return dot(t,t);
#else
//fix me this is a big fat hack to avoid issues whith point lights,
//this function fail to return correct values, but it works with PSSM
return 0.0;
#endif
}
float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){

@ -0,0 +1,241 @@
/*
* Copyright (c) 2009-2012 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 com.jme3.shadow;
import com.jme3.asset.AssetManager;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector4f;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.shadow.PointLightShadowRenderer.CompareMode;
import com.jme3.shadow.PointLightShadowRenderer.FilterMode;
import com.jme3.texture.FrameBuffer;
import java.io.IOException;
/**
*
* This Filter does basically the same as a PointLightShadowRenderer except it renders
* the post shadow pass as a fulscreen quad pass instead of a geometry pass.
* It's mostly faster than PointLightShadowRenderer as long as you have more than a about ten shadow recieving objects.
* The expense is the draw back that the shadow Recieve mode set on spatial is ignored.
* So basically all and only objects that render depth in the scene receive shadows.
* See this post for more details http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599
*
* API is basically the same as the PssmShadowRenderer;
*
* @author Rémy Bouquet aka Nehon
*/
public class PointLightShadowFilter extends Filter {
private PointLightShadowRenderer plRenderer;
private ViewPort viewPort;
/**
* Creates a PSSM Shadow Filter
* More info on the technique at <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a>
* @param manager the application asset manager
* @param size the size of the rendered shadowmaps (512,1024,2048, etc...)
* @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps).
*/
public PointLightShadowFilter(AssetManager manager, int size) {
super("Post Shadow");
material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md");
plRenderer = new PointLightShadowRenderer(manager, size, material);
plRenderer.needsfallBackMaterial = true;
}
@Override
protected Material getMaterial() {
return material;
}
@Override
protected boolean isRequiresDepthTexture() {
return true;
}
public Material getShadowMaterial() {
return material;
}
Vector4f tmpv = new Vector4f();
@Override
protected void preFrame(float tpf) {
plRenderer.preFrame(tpf);
material.setMatrix4("ViewProjectionMatrixInverse", viewPort.getCamera().getViewProjectionMatrix().invert());
Matrix4f m = viewPort.getCamera().getViewProjectionMatrix();
material.setVector4("ViewProjectionMatrixRow2", tmpv.set(m.m20, m.m21, m.m22, m.m23));
}
@Override
protected void postQueue(RenderQueue queue) {
plRenderer.postQueue(queue);
}
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
plRenderer.setPostShadowParams();
}
@Override
protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
plRenderer.initialize(renderManager, vp);
this.viewPort = vp;
}
/**
* gets the point light used to cast shadows with this processor
*
* @return the point light
*/
public PointLight getLight() {
return plRenderer.getLight();
}
/**
* sets the light to use for casting shadows with this processor
*
* @param light the point light
*/
public void setLight(PointLight light) {
plRenderer.setLight(light);
}
/**
* returns the shdaow intensity
* @see #setShadowIntensity(float shadowIntensity)
* @return shadowIntensity
*/
public float getShadowIntensity() {
return plRenderer.getShadowIntensity();
}
/**
* Set the shadowIntensity, the value should be between 0 and 1,
* a 0 value gives a bright and invisilble shadow,
* a 1 value gives a pitch black shadow,
* default is 0.7
* @param shadowIntensity the darkness of the shadow
*/
final public void setShadowIntensity(float shadowIntensity) {
plRenderer.setShadowIntensity(shadowIntensity);
}
/**
* returns the edges thickness <br>
* @see #setEdgesThickness(int edgesThickness)
* @return edgesThickness
*/
public int getEdgesThickness() {
return plRenderer.getEdgesThickness();
}
/**
* Sets 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) {
plRenderer.setEdgesThickness(edgesThickness);
}
/**
* returns true if the PssmRenderer flushed the shadow queues
* @return flushQueues
*/
public boolean isFlushQueues() {
return plRenderer.isFlushQueues();
}
/**
* Set this to false if you want to use several PssmRederers to have multiple shadows cast by multiple light sources.
* Make sure the last PssmRenderer in the stack DO flush the queues, but not the others
* @param flushQueues
*/
public void setFlushQueues(boolean flushQueues) {
plRenderer.setFlushQueues(flushQueues);
}
/**
* sets the shadow compare mode see {@link CompareMode} for more info
* @param compareMode
*/
final public void setCompareMode(CompareMode compareMode) {
plRenderer.setCompareMode(compareMode);
}
/**
* Sets the filtering mode for shadow edges see {@link FilterMode} for more info
* @param filterMode
*/
final public void setFilterMode(FilterMode filterMode) {
plRenderer.setFilterMode(filterMode);
}
/**
* Define the length over which the shadow will fade out when using a shadowZextend
* @param length the fade length in world units
*/
public void setShadowZFadeLength(float length){
plRenderer.setShadowZFadeLength(length);
}
/**
* get the length over which the shadow will fade out when using a shadowZextend
* @return the fade length in world units
*/
public float getShadowZFadeLength(){
return plRenderer.getShadowZFadeLength();
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
}
}

@ -0,0 +1,753 @@
/*
* Copyright (c) 2009-2012 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 com.jme3.shadow;
import com.jme3.asset.AssetManager;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.post.SceneProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.OpaqueComparator;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.WireFrustum;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.ShadowCompareMode;
import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture;
import java.util.ArrayList;
import java.util.List;
/**
* 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 <a
* href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a><br>
* <p/>
* @author Rémy Bouquet aka Nehon
*/
public class PointLightShadowRenderer implements SceneProcessor {
/**
* <code>FilterMode</code> specifies how shadows are filtered
*/
public enum FilterMode {
/**
* Shadows are not filtered. Nearest sample is used, causing in blocky
* shadows.
*/
Nearest,
/**
* Bilinear filtering is used. Has the potential of being hardware
* accelerated on some GPUs
*/
Bilinear,
/**
* Dither-based sampling is used, very cheap but can look bad at low
* resolutions.
*/
Dither,
/**
* 4x4 percentage-closer filtering is used. Shadows will be smoother at
* the cost of performance
*/
PCF4,
/**
* 8x8 percentage-closer filtering is used. Shadows will be smoother at
* the cost of performance
*/
PCFPOISSON,
/**
* 8x8 percentage-closer filtering is used. Shadows will be smoother at
* the cost of performance
*/
PCF8
}
/**
* Specifies the shadow comparison mode
*/
public enum CompareMode {
/**
* Shadow depth comparisons are done by using shader code
*/
Software,
/**
* Shadow depth comparisons are done by using the GPU's dedicated
* shadowing pipeline.
*/
Hardware;
}
protected final int CAM_NUMBER = 6;
protected PointLight light;
//common
protected float shadowMapSize;
protected float shadowIntensity = 0.7f;
protected float zFarOverride = 0;
protected RenderManager renderManager;
protected ViewPort viewPort;
protected FrameBuffer[] shadowFB;
protected Texture2D[] shadowMaps;
protected Texture2D dummyTex;
protected Camera[] shadowCams;
protected Material preshadowMat;
protected Material postshadowMat;
protected GeometryList splitOccluders = new GeometryList(new OpaqueComparator());
protected Matrix4f[] lightViewProjectionsMatrices;
protected boolean noOccluders = false;
protected AssetManager assetManager;
protected boolean debug = false;
protected float edgesThickness = 1.0f;
protected FilterMode filterMode;
protected CompareMode compareMode;
protected Picture[] dispPic;
protected boolean flushQueues = true;
// define if the fallback material should be used.
protected boolean needsfallBackMaterial = false;
//Name of the post material technique
protected String postTechniqueName = "PostShadow";
//flags to know when to change params in the materials
protected boolean applyHWShadows = true;
protected boolean applyFilterMode = true;
protected boolean applyPCFEdge = true;
protected boolean applyShadowIntensity = true;
//a list of material of the post shadow queue geometries.
protected List<Material> matCache = new ArrayList<Material>();
//Holding the info for fading shadows in the far distance
protected Vector2f fadeInfo;
protected float fadeLength;
protected boolean applyFadeInfo = false;
/**
* Create a PSSM Shadow Renderer More info on the technique at <a
* href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a>
*
* @param manager the application asset manager
* @param size the size of the rendered shadowmaps (512,1024,2048, etc...)
* @param nbSplits the number of shadow maps rendered (the more shadow maps
* the more quality, the less fps).
* @param nbSplits the number of shadow maps rendered (the more shadow maps
* the more quality, the less fps).
*/
public PointLightShadowRenderer(AssetManager manager, int size) {
this(manager, size, new Material(manager, "Common/MatDefs/Shadow/PostShadowPSSM.j3md"));
}
/**
* Create a PSSM Shadow Renderer More info on the technique at <a
* href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a>
*
* @param manager the application asset manager
* @param size the size of the rendered shadowmaps (512,1024,2048, etc...)
* @param nbSplits the number of shadow maps rendered (the more shadow maps
* the more quality, the less fps).
* @param postShadowMat the material used for post shadows if you need to
* override it
*/
protected PointLightShadowRenderer(AssetManager manager, int size, Material postShadowMat) {
this.postshadowMat = postShadowMat;
assetManager = manager;
shadowMapSize = size;
shadowFB = new FrameBuffer[CAM_NUMBER];
shadowMaps = new Texture2D[CAM_NUMBER];
dispPic = new Picture[CAM_NUMBER];
lightViewProjectionsMatrices = new Matrix4f[CAM_NUMBER];
//DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
dummyTex = new Texture2D(size, size, Format.RGBA8);
preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");
postshadowMat.setFloat("ShadowMapSize", size);
shadowCams = new Camera[CAM_NUMBER];
for (int i = 0; i < CAM_NUMBER; i++) {
lightViewProjectionsMatrices[i] = new Matrix4f();
shadowFB[i] = new FrameBuffer(size, size, 1);
shadowMaps[i] = new Texture2D(size, size, Format.Depth);
shadowFB[i].setDepthTexture(shadowMaps[i]);
//DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
shadowFB[i].setColorTexture(dummyTex);
postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]);
//quads for debuging purpose
dispPic[i] = new Picture("Picture" + i);
dispPic[i].setTexture(manager, shadowMaps[i], false);
shadowCams[i] = new Camera(size, size);
}
setCompareMode(CompareMode.Hardware);
setFilterMode(FilterMode.Bilinear);
setShadowIntensity(0.7f);
}
/**
* Sets the filtering mode for shadow edges see {@link FilterMode} for more
* info
*
* @param filterMode
*/
final public void setFilterMode(FilterMode filterMode) {
if (filterMode == null) {
throw new NullPointerException();
}
if (this.filterMode == filterMode) {
return;
}
this.filterMode = filterMode;
postshadowMat.setInt("FilterMode", filterMode.ordinal());
postshadowMat.setFloat("PCFEdge", edgesThickness);
if (compareMode == CompareMode.Hardware) {
for (Texture2D shadowMap : shadowMaps) {
if (filterMode == FilterMode.Bilinear) {
shadowMap.setMagFilter(MagFilter.Bilinear);
shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
} else {
shadowMap.setMagFilter(MagFilter.Nearest);
shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
}
}
}
applyFilterMode = true;
}
public FilterMode getFilterMode() {
return filterMode;
}
/**
* sets the shadow compare mode see {@link CompareMode} for more info
*
* @param compareMode
*/
final public void setCompareMode(CompareMode compareMode) {
if (compareMode == null) {
throw new NullPointerException();
}
if (this.compareMode == compareMode) {
return;
}
this.compareMode = compareMode;
for (Texture2D shadowMap : shadowMaps) {
if (compareMode == CompareMode.Hardware) {
shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual);
if (filterMode == FilterMode.Bilinear) {
shadowMap.setMagFilter(MagFilter.Bilinear);
shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
} else {
shadowMap.setMagFilter(MagFilter.Nearest);
shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
}
} else {
shadowMap.setShadowCompareMode(ShadowCompareMode.Off);
shadowMap.setMagFilter(MagFilter.Nearest);
shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
}
}
postshadowMat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware);
applyHWShadows = true;
}
//debug function that create a displayable frustrum
private Geometry createFrustum(Vector3f[] pts, int i) {
WireFrustum frustum = new WireFrustum(pts);
Geometry frustumMdl = new Geometry("f", frustum);
frustumMdl.setCullHint(Spatial.CullHint.Never);
frustumMdl.setShadowMode(ShadowMode.Off);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.getAdditionalRenderState().setWireframe(true);
frustumMdl.setMaterial(mat);
switch (i) {
case 0:
frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink);
break;
case 1:
frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red);
break;
case 2:
frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green);
break;
case 3:
frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue);
break;
case 4:
frustumMdl.getMaterial().setColor("Color", ColorRGBA.Yellow);
break;
case 5:
frustumMdl.getMaterial().setColor("Color", ColorRGBA.Gray);
break;
default:
frustumMdl.getMaterial().setColor("Color", ColorRGBA.White);
break;
}
frustumMdl.updateGeometricState();
return frustumMdl;
}
public void initialize(RenderManager rm, ViewPort vp) {
renderManager = rm;
viewPort = vp;
//checking for caps to chosse the appropriate post material technique
if (renderManager.getRenderer().getCaps().contains(Caps.GLSL150)) {
postTechniqueName = "PostShadow15";
} else {
postTechniqueName = "PostShadow";
}
}
public boolean isInitialized() {
return viewPort != null;
}
@SuppressWarnings("fallthrough")
public void postQueue(RenderQueue rq) {
if (light == null) {
throw new IllegalStateException("The light can't be null for a " + this.getClass().getName());
}
GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);
if (occluders.size() == 0) {
return;
}
GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);
if (receivers.size() == 0) {
return;
}
Camera viewCam = viewPort.getCamera();
float zFar = zFarOverride;
if (zFar == 0) {
zFar = viewCam.getFrustumFar();
}
//We prevent computing the frustum points and splits with zeroed or negative near clip value
//float frustumNear = Math.max(viewCam.getFrustumNear(), 0.001f);
// ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points);
updateShadowCams();
Renderer r = renderManager.getRenderer();
renderManager.setForcedMaterial(preshadowMat);
renderManager.setForcedTechnique("PreShadow");
for (int i = 0; i < CAM_NUMBER; i++) {
//Updating shadow cam with curent split frustra
ShadowUtil.getOccludersInCamFrustum(occluders, shadowCams[i], splitOccluders);
//saving light view projection matrix for this split
lightViewProjectionsMatrices[i].set(shadowCams[i].getViewProjectionMatrix());
renderManager.setCamera(shadowCams[i], false);
if (debug && frustums[i].getParent() == null) {
((Node) viewPort.getScenes().get(0)).attachChild(frustums[i]);
}
r.setFrameBuffer(shadowFB[i]);
r.clearBuffers(false, true, false);
// System.out.println("Face " + i + " nb occl " + splitOccluders.size());
// render shadow casters to shadow map
viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCams[i], true);
}
if (flushQueues) {
occluders.clear();
}
//restore setting for future rendering
r.setFrameBuffer(viewPort.getOutputFrameBuffer());
renderManager.setForcedMaterial(null);
renderManager.setForcedTechnique(null);
renderManager.setCamera(viewCam, false);
}
//debug only : displays depth shadow maps
protected void displayShadowMap(Renderer r) {
Camera cam = viewPort.getCamera();
renderManager.setCamera(cam, true);
int h = cam.getHeight();
for (int i = 0; i < dispPic.length; i++) {
dispPic[i].setPosition((128 * i) + (150 + 64 * (i + 1)), h / 20f);
dispPic[i].setWidth(128);
dispPic[i].setHeight(128);
dispPic[i].updateGeometricState();
renderManager.renderGeometry(dispPic[i]);
}
renderManager.setCamera(cam, false);
}
/**
* For dubuging purpose Allow to "snapshot" the current frustrum to the
* scene
*/
public void displayDebug() {
debug = true;
}
public void postFrame(FrameBuffer out) {
if (debug) {
displayShadowMap(renderManager.getRenderer());
}
if (!noOccluders) {
//setting params to recieving geometry list
setMatParams();
Camera cam = viewPort.getCamera();
//some materials in the scene does not have a post shadow technique so we're using the fall back material
if (needsfallBackMaterial) {
renderManager.setForcedMaterial(postshadowMat);
}
//forcing the post shadow technique and render state
renderManager.setForcedTechnique(postTechniqueName);
//rendering the post shadow pass
viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, cam, flushQueues);
//resetting renderManager settings
renderManager.setForcedTechnique(null);
renderManager.setForcedMaterial(null);
renderManager.setCamera(cam, false);
}
}
private void updateShadowCams() {
//bottom
shadowCams[0].setAxes(Vector3f.UNIT_X.mult(-1f), Vector3f.UNIT_Z.mult(-1f), Vector3f.UNIT_Y.mult(-1f));
//top
shadowCams[1].setAxes(Vector3f.UNIT_X.mult(-1f), Vector3f.UNIT_Z, Vector3f.UNIT_Y);
//forward
shadowCams[2].setAxes(Vector3f.UNIT_X.mult(-1f), Vector3f.UNIT_Y, Vector3f.UNIT_Z.mult(-1f));
//backward
shadowCams[3].setAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z);
//left
shadowCams[4].setAxes(Vector3f.UNIT_Z, Vector3f.UNIT_Y, Vector3f.UNIT_X.mult(-1f));
//right
shadowCams[5].setAxes(Vector3f.UNIT_Z.mult(-1f), Vector3f.UNIT_Y, Vector3f.UNIT_X);
for (int i = 0; i < CAM_NUMBER; i++) {
shadowCams[i].setFrustumPerspective(90f, 1f, 0.1f, light.getRadius());
shadowCams[i].setLocation(light.getPosition());
shadowCams[i].update();
shadowCams[i].updateViewProjection();
}
if (debug && frustums == null) {
frustums = new Geometry[CAM_NUMBER];
Vector3f[] points = new Vector3f[8];
for (int i = 0; i < 8; i++) {
points[i] = new Vector3f();
}
for (int i = 0; i < CAM_NUMBER; i++) {
ShadowUtil.updateFrustumPoints2(shadowCams[i], points);
frustums[i] = createFrustum(points, i);
}
}
}
private Geometry[] frustums = null;
private void setMatParams() {
GeometryList l = viewPort.getQueue().getShadowQueueContent(ShadowMode.Receive);
//iteration throught all the geometries of the list to gather the materials
matCache.clear();
for (int i = 0; i < l.size(); i++) {
Material mat = l.get(i).getMaterial();
//checking if the material has the post technique and adding it to the material cache
if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) {
if (!matCache.contains(mat)) {
matCache.add(mat);
}
} else {
needsfallBackMaterial = true;
}
}
//iterating through the mat cache and setting the parameters
for (Material mat : matCache) {
if (mat.getParam("ShadowMapSize") == null) {
mat.setFloat("ShadowMapSize", shadowMapSize);
}
for (int j = 0; j < CAM_NUMBER; j++) {
mat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]);
}
mat.setVector3("LightPos", light.getPosition());
if (mat.getParam("ShadowMap0") == null) {
for (int j = 0; j < CAM_NUMBER; j++) {
mat.setTexture("ShadowMap" + j, shadowMaps[j]);
}
}
if (applyHWShadows || mat.getParam("HardwareShadows") == null) {
mat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware);
}
if (applyFilterMode || mat.getParam("FilterMode") == null) {
mat.setInt("FilterMode", filterMode.ordinal());
}
if (mat.getParam("PCFEdge") == null || applyPCFEdge) {
mat.setFloat("PCFEdge", edgesThickness);
}
if (mat.getParam("ShadowIntensity") == null || applyShadowIntensity) {
mat.setFloat("ShadowIntensity", shadowIntensity);
}
if (fadeInfo != null && mat.getParam("FadeInfo") == null || applyFadeInfo) {
mat.setVector2("FadeInfo", fadeInfo);
}
}
applyHWShadows = false;
applyFilterMode = false;
applyPCFEdge = false;
applyShadowIntensity = false;
applyFadeInfo = false;
//At least one material of the receiving geoms does not support the post shadow techniques
//so we fall back to the forced material solution (transparent shadows won't be supported for these objects)
if (needsfallBackMaterial) {
setPostShadowParams();
}
}
protected void setPostShadowParams() {
for (int j = 0; j < CAM_NUMBER; j++) {
postshadowMat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]);
}
postshadowMat.setVector3("LightPos", light.getPosition());
}
public void preFrame(float tpf) {
}
public void cleanup() {
}
public void reshape(ViewPort vp, int w, int h) {
}
/**
* returns the shdaow intensity
*
* @see #setShadowIntensity(float shadowIntensity)
* @return shadowIntensity
*/
public float getShadowIntensity() {
return shadowIntensity;
}
/**
* Set the shadowIntensity, the value should be between 0 and 1, a 0 value
* gives a bright and invisilble shadow, a 1 value gives a pitch black
* shadow, default is 0.7
*
* @param shadowIntensity the darkness of the shadow
*/
final public void setShadowIntensity(float shadowIntensity) {
this.shadowIntensity = shadowIntensity;
postshadowMat.setFloat("ShadowIntensity", shadowIntensity);
applyShadowIntensity = true;
}
/**
* How far the shadows are rendered in the view
*
* @see #setShadowZExtend(float zFar)
* @return shadowZExtend
*/
public float getShadowZExtend() {
return zFarOverride;
}
/**
* Set the distance from the eye where the shadows will be rendered default
* value is dynamicaly computed to the shadow casters/receivers union bound
* zFar, capped to view frustum far value.
*
* @param zFar the zFar values that override the computed one
*/
public void setShadowZExtend(float zFar) {
if (fadeInfo != null) {
fadeInfo.set(zFar - fadeLength, 1f / fadeLength);
}
this.zFarOverride = zFar;
}
/**
* returns the edges thickness
*
* @see #setEdgesThickness(int edgesThickness)
* @return edgesThickness
*/
public int getEdgesThickness() {
return (int) (edgesThickness * 10);
}
/**
* Sets 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) {
this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10));
this.edgesThickness *= 0.1f;
postshadowMat.setFloat("PCFEdge", edgesThickness);
applyPCFEdge = true;
}
/**
* returns true if the PssmRenderer flushed the shadow queues
*
* @return flushQueues
*/
public boolean isFlushQueues() {
return flushQueues;
}
/**
* Set this to false if you want to use several PssmRederers to have
* multiple shadows cast by multiple light sources. Make sure the last
* PssmRenderer in the stack DO flush the queues, but not the others
*
* @param flushQueues
*/
public void setFlushQueues(boolean flushQueues) {
this.flushQueues = flushQueues;
}
/**
* Define the length over which the shadow will fade out when using a
* shadowZextend This is useful to make dynamic shadows fade into baked
* shadows in the distance.
*
* @param length the fade length in world units
*/
public void setShadowZFadeLength(float length) {
if (length == 0) {
fadeInfo = null;
fadeLength = 0;
postshadowMat.clearParam("FadeInfo");
} else {
if (zFarOverride == 0) {
fadeInfo = new Vector2f(0, 0);
} else {
fadeInfo = new Vector2f(zFarOverride - length, 1.0f / length);
}
fadeLength = length;
postshadowMat.setVector2("FadeInfo", fadeInfo);
}
}
/**
* get the length over which the shadow will fade out when using a
* shadowZextend
*
* @return the fade length in world units
*/
public float getShadowZFadeLength() {
if (fadeInfo != null) {
return zFarOverride - fadeInfo.x;
}
return 0f;
}
/**
* gets the point light used to cast shadows with this processor
*
* @return the point light
*/
public PointLight getLight() {
return light;
}
/**
* sets the light to use for casting shadows with this processor
*
* @param light the point light
*/
public void setLight(PointLight light) {
this.light = light;
updateShadowCams();
}
}

@ -44,6 +44,7 @@ import com.jme3.util.TempVars;
import static java.lang.Math.max;
import static java.lang.Math.min;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
@ -66,18 +67,16 @@ public class ShadowUtil {
public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) {
int w = viewCam.getWidth();
int h = viewCam.getHeight();
float n = viewCam.getFrustumNear();
float f = viewCam.getFrustumFar();
points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), n));
points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), n));
points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), n));
points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), n));
points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), 0));
points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), 0));
points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), 0));
points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), 0));
points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), f));
points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), f));
points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), f));
points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), f));
points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), 1));
points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), 1));
points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), 1));
points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), 1));
}
/**
@ -494,4 +493,28 @@ public class ShadowUtil {
shadowCam.setProjectionMatrix(result);
}
/**
* Updates the shadow camera to properly contain the given
* points (which contain the eye camera frustum corners) and the
* shadow occluder objects.
*
* @param occluders
* @param shadowCam
* @param points
*/
public static void getOccludersInCamFrustum(GeometryList occluders,
Camera shadowCam,
GeometryList splitOccluders) {
for (int i = 0; i < occluders.size(); i++) {
Geometry g = occluders.get(i);
int planeState = shadowCam.getPlaneState();
shadowCam.setPlaneState(0);
if (shadowCam.contains(g.getWorldBound()) != Camera.FrustumIntersect.Outside) {
splitOccluders.add(g);
}
shadowCam.setPlaneState(planeState);
}
}
}

@ -0,0 +1,237 @@
/*
* Copyright (c) 2009-2012 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 jme3test.light;
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.PointLight;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.shadow.PointLightShadowFilter;
import com.jme3.shadow.PointLightShadowRenderer;
public class TestPointLightShadows extends SimpleApplication implements ActionListener {
public static void main(String[] args) {
TestPointLightShadows app = new TestPointLightShadows();
app.start();
}
Node lightNode;
private boolean hardwareShadows = true;
PointLightShadowRenderer plsr ;
PointLightShadowFilter plsf;
@Override
public void simpleInitApp() {
flyCam.setMoveSpeed(10);
cam.setLocation(new Vector3f(0.040581334f, 1.7745866f, 6.155161f));
cam.setRotation(new Quaternion(4.3868728E-5f, 0.9999293f, -0.011230096f, 0.0039059948f));
Node scene = (Node) assetManager.loadModel("Models/Test/CornellBox.j3o");
scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
rootNode.attachChild(scene);
rootNode.getChild("Cube").setShadowMode(RenderQueue.ShadowMode.Receive);
lightNode = (Node) rootNode.getChild("Lamp");
Geometry lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
//Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f));
lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
lightMdl.setShadowMode(RenderQueue.ShadowMode.Off);
lightNode.attachChild(lightMdl);
//lightMdl.setLocalTranslation(lightNode.getLocalTranslation());
Geometry box = new Geometry("box", new Box(0.2f, 0.2f, 0.2f));
//Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f));
box.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
box.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
rootNode.attachChild(box);
box.setLocalTranslation(-1f, 0.5f, -2);
plsr = new PointLightShadowRenderer(assetManager, 512);
plsr.setLight((PointLight) scene.getLocalLightList().get(0));
plsr.setFilterMode(PointLightShadowRenderer.FilterMode.Nearest);
// plsr.displayDebug();
viewPort.addProcessor(plsr);
plsf = new PointLightShadowFilter(assetManager, 512);
plsf.setLight((PointLight) scene.getLocalLightList().get(0));
plsf.setFilterMode(PointLightShadowRenderer.FilterMode.Nearest);
plsf.setEnabled(false);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(plsf);
viewPort.addProcessor(fpp);
initUIAndInputs();
}
BitmapText shadowTypeText;
BitmapText shadowCompareText;
BitmapText shadowFilterText;
BitmapText shadowIntensityText;
final static String TYPE_TEXT = "(Space) Shadow type : ";
final static String COMPARE_TEXT = "(enter) Shadow compare ";
final static String FILTERING_TEXT = "(f) Edge filtering : ";
final static String INTENSITY_TEXT = "(t:up, g:down) Shadow intensity : ";
private void initUIAndInputs() {
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
shadowTypeText = createText();
shadowCompareText = createText();
shadowFilterText = createText();
shadowIntensityText = createText();
shadowTypeText.setText(TYPE_TEXT+"Processor");
shadowCompareText.setText(COMPARE_TEXT+(hardwareShadows?"Hardware":"Software"));
shadowFilterText.setText(FILTERING_TEXT+plsr.getFilterMode().toString());
shadowIntensityText.setText(INTENSITY_TEXT+plsr.getShadowIntensity());
shadowTypeText.setLocalTranslation(10, cam.getHeight()-20, 0);
shadowCompareText.setLocalTranslation(10, cam.getHeight()-40, 0);
shadowFilterText.setLocalTranslation(10, cam.getHeight()-60, 0);
shadowIntensityText.setLocalTranslation(10, cam.getHeight()-80, 0);
guiNode.attachChild(shadowTypeText);
guiNode.attachChild(shadowCompareText);
guiNode.attachChild(shadowFilterText);
guiNode.attachChild(shadowIntensityText);
inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addMapping("changeFiltering", new KeyTrigger(KeyInput.KEY_F));
inputManager.addMapping("ShadowUp", new KeyTrigger(KeyInput.KEY_T));
inputManager.addMapping("ShadowDown", new KeyTrigger(KeyInput.KEY_G));
inputManager.addMapping("ThicknessUp", new KeyTrigger(KeyInput.KEY_Y));
inputManager.addMapping("ThicknessDown", new KeyTrigger(KeyInput.KEY_H));
inputManager.addMapping("toggleHW", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addListener(this, "toggleHW", "toggle", "ShadowUp", "ShadowDown", "ThicknessUp", "ThicknessDown", "changeFiltering");
}
int filteringIndex = 0;
int renderModeIndex = 0;
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("toggle") && keyPressed) {
renderModeIndex += 1;
renderModeIndex %= 3;
switch (renderModeIndex) {
case 0:
viewPort.addProcessor(plsr);
shadowTypeText.setText(TYPE_TEXT+"Processor");
break;
case 1:
viewPort.removeProcessor(plsr);
plsf.setEnabled(true);
shadowTypeText.setText(TYPE_TEXT+"Filter");
break;
case 2:
plsf.setEnabled(false);
shadowTypeText.setText(TYPE_TEXT+"None");
break;
}
} else if (name.equals("toggleHW") && keyPressed) {
hardwareShadows = !hardwareShadows;
plsr.setCompareMode(hardwareShadows ? PointLightShadowRenderer.CompareMode.Hardware : PointLightShadowRenderer.CompareMode.Software);
plsf.setCompareMode(hardwareShadows ? PointLightShadowRenderer.CompareMode.Hardware : PointLightShadowRenderer.CompareMode.Software);
shadowCompareText.setText(COMPARE_TEXT+(hardwareShadows?"Hardware":"Software"));
}
if (name.equals("changeFiltering") && keyPressed) {
filteringIndex = plsr.getFilterMode().ordinal();
filteringIndex = (filteringIndex + 1) % PointLightShadowRenderer.FilterMode.values().length;
PointLightShadowRenderer.FilterMode m = PointLightShadowRenderer.FilterMode.values()[filteringIndex];
plsr.setFilterMode(m);
plsf.setFilterMode(m);
shadowFilterText.setText(FILTERING_TEXT+m.toString());
}
if (name.equals("ShadowUp") && keyPressed) {
plsr.setShadowIntensity(plsr.getShadowIntensity() + 0.1f);
plsf.setShadowIntensity(plsr.getShadowIntensity() + 0.1f);
shadowIntensityText.setText(INTENSITY_TEXT+plsr.getShadowIntensity());
}
if (name.equals("ShadowDown") && keyPressed) {
plsr.setShadowIntensity(plsr.getShadowIntensity() - 0.1f);
plsf.setShadowIntensity(plsr.getShadowIntensity() - 0.1f);
shadowIntensityText.setText(INTENSITY_TEXT+plsr.getShadowIntensity());
}
if (name.equals("ThicknessUp") && keyPressed) {
plsr.setEdgesThickness(plsr.getEdgesThickness() + 1);
plsf.setEdgesThickness(plsr.getEdgesThickness() + 1);
System.out.println("Shadow thickness : " + plsr.getEdgesThickness());
}
if (name.equals("ThicknessDown") && keyPressed) {
plsr.setEdgesThickness(plsr.getEdgesThickness() - 1);
plsf.setEdgesThickness(plsr.getEdgesThickness() - 1);
System.out.println("Shadow thickness : " + plsr.getEdgesThickness());
}
}
float time;
@Override
public void simpleUpdate(float tpf) {
time += tpf;
lightNode.setLocalTranslation(FastMath.cos(time)*0.4f,lightNode.getLocalTranslation().y , FastMath.sin(time)*0.4f);
}
private BitmapText createText() {
BitmapText t = new BitmapText(guiFont, false);
t.setSize(guiFont.getCharSet().getRenderedSize()*0.75f);
return t;
}
}
Loading…
Cancel
Save