Shadow system refactoring.

- Basic and PSSM shadow renderer are now deprecated
- There is now one processor and its filter conterpart for each light type
- created an abstract shadow processor that hold the common shadowing code. It's totally independent of the shadow technique used.
- extracted the CompareMode and FilterMode enum to their own files.
- renamed FilterMode enum to EdgeFilteringMode
- refactored the shader code, to avoid duplicate code. all shadow related code is now gathered into Shadows.glsllib and Shadows15.glsllib.
- added spot light Shadows
- removed the ShadowCamera class as it was not used.
- removed "pssm" in the naming of classes, shader and shader libs since it's not relevant anymore

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9971 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 12 years ago
parent 304ecb4bf7
commit 0dadaa80f5
  1. 8
      engine/src/core-data/Common/MatDefs/Light/Lighting.j3md
  2. 12
      engine/src/core-data/Common/MatDefs/Shadow/BasicPostShadow.frag
  3. 28
      engine/src/core-data/Common/MatDefs/Shadow/BasicPostShadow.j3md
  4. 31
      engine/src/core-data/Common/MatDefs/Shadow/BasicPostShadow.vert
  5. 84
      engine/src/core-data/Common/MatDefs/Shadow/PostShadow.frag
  6. 63
      engine/src/core-data/Common/MatDefs/Shadow/PostShadow.j3md
  7. 71
      engine/src/core-data/Common/MatDefs/Shadow/PostShadow.vert
  8. 72
      engine/src/core-data/Common/MatDefs/Shadow/PostShadow15.frag
  9. 55
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowFilter.frag
  10. 8
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowFilter.j3md
  11. 58
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowFilter15.frag
  12. 96
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.frag
  13. 85
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.j3md
  14. 62
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM.vert
  15. 105
      engine/src/core-data/Common/MatDefs/Shadow/PostShadowPSSM15.frag
  16. 0
      engine/src/core-data/Common/ShaderLib/BasicShadow.glsllib
  17. 96
      engine/src/core-data/Common/ShaderLib/Shadows.glsllib
  18. 96
      engine/src/core-data/Common/ShaderLib/Shadows15.glsllib
  19. 233
      engine/src/core/com/jme3/shadow/AbstractShadowFilter.java
  20. 563
      engine/src/core/com/jme3/shadow/AbstractShadowRenderer.java
  21. 4
      engine/src/core/com/jme3/shadow/BasicShadowRenderer.java
  22. 48
      engine/src/core/com/jme3/shadow/CompareMode.java
  23. 174
      engine/src/core/com/jme3/shadow/DirectionalLightShadowFilter.java
  24. 257
      engine/src/core/com/jme3/shadow/DirectionalLightShadowRenderer.java
  25. 73
      engine/src/core/com/jme3/shadow/EdgeFilteringMode.java
  26. 178
      engine/src/core/com/jme3/shadow/PointLightShadowFilter.java
  27. 668
      engine/src/core/com/jme3/shadow/PointLightShadowRenderer.java
  28. 2
      engine/src/core/com/jme3/shadow/PssmShadowFilter.java
  29. 193
      engine/src/core/com/jme3/shadow/PssmShadowRenderer.java
  30. 10
      engine/src/core/com/jme3/shadow/ShadowUtil.java
  31. 143
      engine/src/core/com/jme3/shadow/SpotLightShadowFilter.java
  32. 211
      engine/src/core/com/jme3/shadow/SpotLightShadowRenderer.java
  33. 154
      engine/src/test/jme3test/light/ShadowTestUIManager.java
  34. 311
      engine/src/test/jme3test/light/TestDirectionalLightShadow.java
  35. 166
      engine/src/test/jme3test/light/TestPointLightShadows.java
  36. 122
      engine/src/test/jme3test/light/TestPssmShadow.java
  37. 198
      engine/src/test/jme3test/light/TestSpotLightShadows.java

@ -200,8 +200,8 @@ MaterialDef Phong Lighting {
Technique PostShadow15{
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadowPSSM.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowPSSM15.frag
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
WorldParameters {
WorldViewProjectionMatrix
@ -228,8 +228,8 @@ MaterialDef Phong Lighting {
}
Technique PostShadow{
VertexShader GLSL100: Common/MatDefs/Shadow/PostShadowPSSM.vert
FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowPSSM.frag
VertexShader GLSL100: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag
WorldParameters {
WorldViewProjectionMatrix

@ -0,0 +1,12 @@
#import "Common/ShaderLib/BasicShadow.glsllib"
uniform SHADOWMAP m_ShadowMap;
varying vec4 projCoord;
void main() {
vec4 coord = projCoord;
coord.xyz /= coord.w;
float shad = Shadow_GetShadow(m_ShadowMap, coord) * 0.7 + 0.3;
gl_FragColor = vec4(shad,shad,shad,1.0);
}

@ -0,0 +1,28 @@
MaterialDef Basic Post Shadow {
MaterialParameters {
Texture2D ShadowMap
Matrix4 LightViewProjectionMatrix
}
Technique {
VertexShader GLSL100: Common/MatDefs/Shadow/BasicPostShadow.vert
FragmentShader GLSL100: Common/MatDefs/Shadow/BasicPostShadow.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
}
Defines {
NO_SHADOW2DPROJ
}
RenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
}

@ -0,0 +1,31 @@
uniform mat4 m_LightViewProjectionMatrix;
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldMatrix;
varying vec4 projCoord;
attribute vec3 inPosition;
const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0);
void main(){
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
// get the vertex in world space
vec4 worldPos = g_WorldMatrix * vec4(inPosition, 1.0);
// convert vertex to light viewProj space
//projCoord = biasMat * (m_LightViewProjectionMatrix * worldPos);
vec4 coord = m_LightViewProjectionMatrix * worldPos;
projCoord = biasMat * coord;
//projCoord.z /= gl_DepthRange.far;
//projCoord = (m_LightViewProjectionMatrix * worldPos);
//projCoord /= projCoord.w;
//projCoord.xy = projCoord.xy * vec2(0.5, -0.5) + vec2(0.5);
// bias from [-1, 1] to [0, 1] for sampling shadow map
//projCoord = (projCoord.xyzw * vec4(0.5)) + vec4(0.5);
}

@ -1,12 +1,72 @@
#import "Common/ShaderLib/Shadow.glsllib"
uniform SHADOWMAP m_ShadowMap;
varying vec4 projCoord;
void main() {
vec4 coord = projCoord;
coord.xyz /= coord.w;
float shad = Shadow_GetShadow(m_ShadowMap, coord) * 0.7 + 0.3;
gl_FragColor = vec4(shad,shad,shad,1.0);
}
#import "Common/ShaderLib/Shadows.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;
#else
uniform sampler2D m_DiffuseMap;
#endif
uniform float m_AlphaDiscardThreshold;
varying vec2 texCoord;
#endif
#ifdef FADE
uniform vec2 m_FadeInfo;
#endif
void main(){
#ifdef DISCARD_ALPHA
#ifdef COLOR_MAP
float alpha = texture2D(m_ColorMap,texCoord).a;
#else
float alpha = texture2D(m_DiffuseMap,texCoord).a;
#endif
if(alpha<=m_AlphaDiscardThreshold){
discard;
}
#endif
float shadow = 1.0;
#ifdef POINTLIGHT
shadow = getPointLightShadows(worldPos, m_LightPos,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5,
projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5);
#else
#ifdef PSSM
shadow = getDirectionalLightShadows(m_Splits, shadowPosition,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,
projCoord0, projCoord1, projCoord2, projCoord3);
#else
//spotlight
shadow = getSpotLightShadows(m_ShadowMap0,projCoord0);
#endif
#endif
#ifdef FADE
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
#endif
shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
gl_FragColor = vec4(shadow, shadow, shadow, 1.0);
}

@ -1,8 +1,59 @@
MaterialDef Post Shadow {
MaterialParameters {
Texture2D ShadowMap
Matrix4 LightViewProjectionMatrix
Int FilterMode
Boolean HardwareShadows
Texture2D ShadowMap0
Texture2D ShadowMap1
Texture2D ShadowMap2
Texture2D ShadowMap3
//pointLights
Texture2D ShadowMap4
Texture2D ShadowMap5
Float ShadowIntensity
Vector4 Splits
Vector2 FadeInfo
Matrix4 LightViewProjectionMatrix0
Matrix4 LightViewProjectionMatrix1
Matrix4 LightViewProjectionMatrix2
Matrix4 LightViewProjectionMatrix3
//pointLight
Matrix4 LightViewProjectionMatrix4
Matrix4 LightViewProjectionMatrix5
Vector3 LightPos
Float PCFEdge
Float ShadowMapSize
}
Technique {
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
}
Defines {
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
RenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
Technique {
@ -15,7 +66,13 @@ MaterialDef Post Shadow {
}
Defines {
NO_SHADOW2DPROJ
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
RenderState {

@ -1,31 +1,72 @@
uniform mat4 m_LightViewProjectionMatrix;
uniform mat4 m_LightViewProjectionMatrix0;
uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldMatrix;
uniform mat4 g_ViewMatrix;
uniform vec3 m_LightPos;
varying vec4 projCoord0;
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 vec3 lightVec;
varying vec4 projCoord;
varying vec2 texCoord;
attribute vec3 inPosition;
#ifdef DISCARD_ALPHA
attribute vec2 inTexCoord;
#endif
const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0);
void main(){
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
#ifndef POINTLIGHT
#ifdef PSSM
shadowPosition = gl_Position.z;
#endif
vec4 worldPos=vec4(0.0);
#endif
// get the vertex in world space
vec4 worldPos = g_WorldMatrix * vec4(inPosition, 1.0);
// convert vertex to light viewProj space
//projCoord = biasMat * (m_LightViewProjectionMatrix * worldPos);
vec4 coord = m_LightViewProjectionMatrix * worldPos;
projCoord = biasMat * coord;
//projCoord.z /= gl_DepthRange.far;
//projCoord = (m_LightViewProjectionMatrix * worldPos);
//projCoord /= projCoord.w;
//projCoord.xy = projCoord.xy * vec2(0.5, -0.5) + vec2(0.5);
// bias from [-1, 1] to [0, 1] for sampling shadow map
//projCoord = (projCoord.xyzw * vec4(0.5)) + vec4(0.5);
worldPos = g_WorldMatrix * vec4(inPosition, 1.0);
#ifdef DISCARD_ALPHA
texCoord = inTexCoord;
#endif
// populate the light view matrices array and convert vertex to light viewProj space
projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos;
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;
#else
vec4 vLightPos = g_ViewMatrix * vec4(m_LightPos,1.0);
vec4 vPos = g_ViewMatrix * worldPos;
lightVec = vLightPos.xyz - vPos.xyz;
#endif
}

@ -0,0 +1,72 @@
#import "Common/ShaderLib/Shadows15.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;
in vec4 worldPos;
uniform vec3 m_LightPos;
#endif
#ifdef DISCARD_ALPHA
#ifdef COLOR_MAP
uniform sampler2D m_ColorMap;
#else
uniform sampler2D m_DiffuseMap;
#endif
uniform float m_AlphaDiscardThreshold;
varying vec2 texCoord;
#endif
#ifdef FADE
uniform vec2 m_FadeInfo;
#endif
void main(){
#ifdef DISCARD_ALPHA
#ifdef COLOR_MAP
float alpha = texture2D(m_ColorMap,texCoord).a;
#else
float alpha = texture2D(m_DiffuseMap,texCoord).a;
#endif
if(alpha < m_AlphaDiscardThreshold){
discard;
}
#endif
float shadow = 1.0;
#ifdef POINTLIGHT
shadow = getPointLightShadows(worldPos, m_LightPos,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5,
projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5);
#else
#ifdef PSSM
shadow = getDirectionalLightShadows(m_Splits, shadowPosition,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,
projCoord0, projCoord1, projCoord2, projCoord3);
#else
//spotlight
shadow = getSpotLightShadows(m_ShadowMap0,projCoord0);
#endif
#endif
#ifdef FADE
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
#endif
shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
outFragColor = vec4(shadow, shadow, shadow, 1.0);
}

@ -1,4 +1,4 @@
#import "Common/ShaderLib/PssmShadows.glsllib"
#import "Common/ShaderLib/Shadows.glsllib"
uniform sampler2D m_Texture;
uniform sampler2D m_DepthTexture;
@ -59,46 +59,21 @@ void main(){
#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;
if(shadowPosition < m_Splits.x){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else if( shadowPosition < m_Splits.y){
shadowBorderScale = 0.5;
shadow = GETSHADOW(m_ShadowMap1, projCoord1);
}else if( shadowPosition < m_Splits.z){
shadowBorderScale = 0.25;
shadow = GETSHADOW(m_ShadowMap2, projCoord2);
}else if( shadowPosition < m_Splits.w){
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);
}
}
shadow = getPointLightShadows(worldPos, m_LightPos,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5,
projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5);
#else
#ifdef PSSM
float shadowPosition = m_ViewProjectionMatrixRow2.x * worldPos.x + m_ViewProjectionMatrixRow2.y * worldPos.y + m_ViewProjectionMatrixRow2.z * worldPos.z + m_ViewProjectionMatrixRow2.w;
shadow = getDirectionalLightShadows(m_Splits, shadowPosition,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,
projCoord0, projCoord1, projCoord2, projCoord3);
#else
//spotlight
shadow = getSpotLightShadows(m_ShadowMap0,projCoord0);
#endif
#endif
#ifdef FADE

@ -11,8 +11,7 @@ MaterialDef Post Shadow {
//pointLights
Texture2D ShadowMap4
Texture2D ShadowMap5
Vector3 LightPos
Float ShadowIntensity
Vector4 Splits
Vector2 FadeInfo
@ -23,7 +22,8 @@ MaterialDef Post Shadow {
Matrix4 LightViewProjectionMatrix3
//pointLight
Matrix4 LightViewProjectionMatrix4
Matrix4 LightViewProjectionMatrix5
Matrix4 LightViewProjectionMatrix5
Vector3 LightPos
Float PCFEdge
@ -44,7 +44,7 @@ MaterialDef Post Shadow {
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewProjectionMatrix
}
Defines {

@ -1,5 +1,5 @@
#import "Common/ShaderLib/MultiSample.glsllib"
#import "Common/ShaderLib/PssmShadows15.glsllib"
#import "Common/ShaderLib/Shadows15.glsllib"
uniform COLORTEXTURE m_Texture;
@ -59,53 +59,29 @@ vec4 main_multiSample(in int numSample){
#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;
if(shadowPosition < m_Splits.x){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else if( shadowPosition < m_Splits.y){
shadowBorderScale = 0.5;
shadow = GETSHADOW(m_ShadowMap1, projCoord1);
}else if( shadowPosition < m_Splits.z){
shadowBorderScale = 0.25;
shadow = GETSHADOW(m_ShadowMap2, projCoord2);
}else if( shadowPosition < m_Splits.w){
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);
}
}
shadow = getPointLightShadows(worldPos, m_LightPos,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5,
projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5);
#else
#ifdef PSSM
float shadowPosition = m_ViewProjectionMatrixRow2.x * worldPos.x + m_ViewProjectionMatrixRow2.y * worldPos.y + m_ViewProjectionMatrixRow2.z * worldPos.z + m_ViewProjectionMatrixRow2.w;
shadow = getDirectionalLightShadows(m_Splits, shadowPosition,
m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,
projCoord0, projCoord1, projCoord2, projCoord3);
#else
//spotlight
shadow = getSpotLightShadows(m_ShadowMap0,projCoord0);
#endif
#endif
#ifdef FADE
shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
#endif
shadow= shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
return color * vec4(shadow, shadow, shadow, 1.0);
}

@ -1,96 +0,0 @@
#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;
#else
uniform sampler2D m_DiffuseMap;
#endif
uniform float m_AlphaDiscardThreshold;
varying vec2 texCoord;
#endif
#ifdef FADE
uniform vec2 m_FadeInfo;
#endif
void main(){
#ifdef DISCARD_ALPHA
#ifdef COLOR_MAP
float alpha = texture2D(m_ColorMap,texCoord).a;
#else
float alpha = texture2D(m_DiffuseMap,texCoord).a;
#endif
if(alpha<=m_AlphaDiscardThreshold){
discard;
}
#endif
float shadow = 1.0;
#ifdef PSSM
if(shadowPosition < m_Splits.x){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else if( shadowPosition < m_Splits.y){
shadowBorderScale = 0.5;
shadow = GETSHADOW(m_ShadowMap1, projCoord1);
}else if( shadowPosition < m_Splits.z){
shadowBorderScale = 0.25;
shadow = GETSHADOW(m_ShadowMap2, projCoord2);
}else if( shadowPosition < m_Splits.w){
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));
#endif
shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
gl_FragColor = vec4(shadow, shadow, shadow, 1.0);
}

@ -1,85 +0,0 @@
MaterialDef Post Shadow {
MaterialParameters {
Int FilterMode
Boolean HardwareShadows
Texture2D ShadowMap0
Texture2D ShadowMap1
Texture2D ShadowMap2
Texture2D ShadowMap3
//pointLights
Texture2D ShadowMap4
Texture2D ShadowMap5
Float ShadowIntensity
Vector4 Splits
Vector2 FadeInfo
Matrix4 LightViewProjectionMatrix0
Matrix4 LightViewProjectionMatrix1
Matrix4 LightViewProjectionMatrix2
Matrix4 LightViewProjectionMatrix3
//pointLight
Matrix4 LightViewProjectionMatrix4
Matrix4 LightViewProjectionMatrix5
Vector3 LightPos
Float PCFEdge
Float ShadowMapSize
}
Technique {
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadowPSSM.vert
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowPSSM15.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
}
Defines {
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
RenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
Technique {
VertexShader GLSL100: Common/MatDefs/Shadow/PostShadowPSSM.vert
FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowPSSM.frag
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
}
Defines {
HARDWARE_SHADOWS : HardwareShadows
FILTER_MODE : FilterMode
PCFEDGE : PCFEdge
SHADOWMAP_SIZE : ShadowMapSize
FADE : FadeInfo
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
}
RenderState {
Blend Modulate
DepthWrite Off
PolyOffset -0.1 0
}
}
}

@ -1,62 +0,0 @@
uniform mat4 m_LightViewProjectionMatrix0;
uniform mat4 m_LightViewProjectionMatrix1;
uniform mat4 m_LightViewProjectionMatrix2;
uniform mat4 m_LightViewProjectionMatrix3;
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat4 g_WorldMatrix;
varying vec4 projCoord0;
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;
attribute vec3 inPosition;
#ifdef DISCARD_ALPHA
attribute vec2 inTexCoord;
#endif
const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.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
worldPos = g_WorldMatrix * vec4(inPosition, 1.0);
#ifdef DISCARD_ALPHA
texCoord = inTexCoord;
#endif
// populate the light view matrices array and convert vertex to light viewProj space
projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos;
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,105 +0,0 @@
#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;
#else
uniform sampler2D m_DiffuseMap;
#endif
uniform float m_AlphaDiscardThreshold;
varying vec2 texCoord;
#endif
#ifdef FADE
uniform vec2 m_FadeInfo;
#endif
void main(){
#ifdef DISCARD_ALPHA
#ifdef COLOR_MAP
float alpha = texture2D(m_ColorMap,texCoord).a;
#else
float alpha = texture2D(m_DiffuseMap,texCoord).a;
#endif
if(alpha < m_AlphaDiscardThreshold){
discard;
}
#endif
float shadow = 1.0;
#ifdef PSSM
if(shadowPosition < m_Splits.x){
shadow = GETSHADOW(m_ShadowMap0, projCoord0);
}else if( shadowPosition < m_Splits.y){
shadowBorderScale = 0.5;
shadow = GETSHADOW(m_ShadowMap1, projCoord1);
}else if( shadowPosition < m_Splits.z){
shadowBorderScale = 0.25;
shadow = GETSHADOW(m_ShadowMap2, projCoord2);
}else if( shadowPosition < m_Splits.w){
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));
#endif
shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
outFragColor = vec4(shadow, shadow, shadow, 1.0);
}

@ -3,7 +3,7 @@
#define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r
#else
#define SHADOWMAP sampler2D
#define SHADOWCOMPARE(tex,coord) step(coord.z / coord.w, texture2DProj(tex, coord).r)
#define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
#endif
#if FILTER_MODE == 0
@ -49,29 +49,31 @@ uniform float m_ShadowIntensity;
const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
float shadowBorderScale = 1.0;
float Shadow_DoShadowCompareOffset(in SHADOWMAP tex, in vec4 projCoord, in vec2 offset){
float Shadow_DoShadowCompareOffset(SHADOWMAP tex, vec4 projCoord, vec2 offset){
vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw);
return SHADOWCOMPARE(tex, coord);
}
float Shadow_DoShadowCompare(in SHADOWMAP tex, vec4 projCoord){
float Shadow_DoShadowCompare(SHADOWMAP tex, vec4 projCoord){
return SHADOWCOMPARE(tex, projCoord);
}
float Shadow_BorderCheck(in vec2 coord){
#ifdef PSSM
float Shadow_BorderCheck(vec2 coord){
// 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){
float Shadow_Nearest(SHADOWMAP tex, vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0){
return 1.0;
}
return Shadow_DoShadowCompare(tex,projCoord);
}
float Shadow_DoDither_2x2(SHADOWMAP tex, vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
return 1.0;
@ -87,7 +89,7 @@ float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
return shadow;
}
float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
float Shadow_DoBilinear_2x2(SHADOWMAP tex, vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
return 1.0;
@ -102,7 +104,7 @@ float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
return mix( mx.x, mx.y, f.y );
}
float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){
float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
@ -136,7 +138,7 @@ float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){
const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
@ -161,3 +163,69 @@ float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
return shadow;
}
#ifdef POINTLIGHT
float getPointLightShadows(vec4 worldPos,vec3 lightPos,
SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,SHADOWMAP shadowMap4,SHADOWMAP shadowMap5,
vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3,vec4 projCoord4,vec4 projCoord5){
float shadow = 1.0;
vec3 vect = worldPos.xyz - 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(shadowMap0, projCoord0 / projCoord0.w);
}else{
shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w);
}
}else if(maxComp == absv.z){
if(vect.z < 0.0){
shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w);
}else{
shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w);
}
}else if(maxComp == absv.x){
if(vect.x < 0.0){
shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w);
}else{
shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
}
}
return shadow;
}
#else
#ifdef PSSM
float getDirectionalLightShadows(vec4 splits,float shadowPosition,
SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,
vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3){
float shadow = 1.0;
if(shadowPosition < splits.x){
shadow = GETSHADOW(shadowMap0, projCoord0 );
}else if( shadowPosition < splits.y){
shadowBorderScale = 0.5;
shadow = GETSHADOW(shadowMap1, projCoord1);
}else if( shadowPosition < splits.z){
shadowBorderScale = 0.25;
shadow = GETSHADOW(shadowMap2, projCoord2);
}else if( shadowPosition < splits.w){
shadowBorderScale = 0.125;
shadow = GETSHADOW(shadowMap3, projCoord3);
}
return shadow;
}
#else
float getSpotLightShadows(SHADOWMAP shadowMap, vec4 projCoord){
float shadow = 1.0;
projCoord /= projCoord.w;
shadow = GETSHADOW(shadowMap, projCoord);
//a small falloff to make the shadow blend nicely into the not lighten
//we translate the texture coordinate value to a -1,1 range so the length
//of the texture coordinate vector is actually the radius of the lighten area on the ground
projCoord = projCoord * 2.0 - 1.0;
float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
}
#endif
#endif

@ -9,18 +9,18 @@
#define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z)
#else
#define SHADOWMAP sampler2D
#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 SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r)
#define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r)
#define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
#endif
#if FILTER_MODE == 0
#define GETSHADOW SHADOWCOMPARE
#define GETSHADOW Shadow_Nearest
#define KERNEL 1
#elif FILTER_MODE == 1
#ifdef HARDWARE_SHADOWS
#define GETSHADOW SHADOWCOMPARE
#define GETSHADOW Shadow_Nearest
#else
#define GETSHADOW Shadow_DoBilinear_2x2
#endif
@ -59,16 +59,18 @@ 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
return dot(t,t);
}
float Shadow_Nearest(in SHADOWMAP tex, in vec4 projCoord){
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0){
return 1.0;
}
return SHADOWCOMPARE(tex,projCoord);
}
float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
@ -109,9 +111,9 @@ float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
return mix( mx.x, mx.y, f.y );
}
float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){
vec2 pixSize = pixSize2 * shadowBorderScale;
float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){
vec2 pixSize = pixSize2 * shadowBorderScale;
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
if (border > 0.0)
@ -146,7 +148,7 @@ float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){
const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
float shadow = 0.0;
float border = Shadow_BorderCheck(projCoord.xy);
@ -173,4 +175,68 @@ float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
return shadow * 0.08333333333;
}
#ifdef POINTLIGHT
float getPointLightShadows(in vec4 worldPos,in vec3 lightPos,
in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5,
in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){
float shadow = 1.0;
vec3 vect = worldPos.xyz - 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(shadowMap0, projCoord0 / projCoord0.w);
}else{
shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w);
}
}else if(maxComp == absv.z){
if(vect.z < 0.0){
shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w);
}else{
shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w);
}
}else if(maxComp == absv.x){
if(vect.x < 0.0){
shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w);
}else{
shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
}
}
return shadow;
}
#else
#ifdef PSSM
float getDirectionalLightShadows(in vec4 splits,in float shadowPosition,
in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,
in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){
float shadow = 1.0;
if(shadowPosition < splits.x){
shadow = GETSHADOW(shadowMap0, projCoord0 );
}else if( shadowPosition < splits.y){
shadowBorderScale = 0.5;
shadow = GETSHADOW(shadowMap1, projCoord1);
}else if( shadowPosition < splits.z){
shadowBorderScale = 0.25;
shadow = GETSHADOW(shadowMap2, projCoord2);
}else if( shadowPosition < splits.w){
shadowBorderScale = 0.125;
shadow = GETSHADOW(shadowMap3, projCoord3);
}
return shadow;
}
#else
float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){
float shadow = 1.0;
projCoord /= projCoord.w;
shadow = GETSHADOW(shadowMap,projCoord);
//a small falloff to make the shadow blend nicely into the not lighten
//we translate the texture coordinate value to a -1,1 range so the length
//of the texture coordinate vector is actually the radius of the lighten area on the ground
projCoord = projCoord * 2.0 - 1.0;
float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
}
#endif
#endif

@ -0,0 +1,233 @@
/*
* 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.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.texture.FrameBuffer;
import java.io.IOException;
/**
*
* Generic abstract filter that holds common implementations for the different
* shadow filtesr
*
* @author Rémy Bouquet aka Nehon
*/
public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> extends Filter {
protected T shadowRenderer;
protected ViewPort viewPort;
/**
* Abstract class constructor
*
* @param manager the application asset manager
* @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048,
* etc...)
* @param nbShadowMaps the number of shadow maps rendered (the more shadow
* maps the more quality, the less fps).
* @param shadowRenderer the shadowRenderer to use for this Filter
*/
@SuppressWarnings("all")
protected AbstractShadowFilter(AssetManager manager, int shadowMapSize, T shadowRenderer) {
super("Post Shadow");
material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md");
this.shadowRenderer = shadowRenderer;
this.shadowRenderer.setPostShadowMaterial(material);
}
@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) {
shadowRenderer.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) {
shadowRenderer.postQueue(queue);
}
@Override
protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
shadowRenderer.setPostShadowParams();
}
@Override
protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
shadowRenderer.needsfallBackMaterial = true;
shadowRenderer.initialize(renderManager, vp);
this.viewPort = vp;
}
/**
* returns the shdaow intensity
*
* @see #setShadowIntensity(float shadowIntensity)
* @return shadowIntensity
*/
public float getShadowIntensity() {
return shadowRenderer.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) {
shadowRenderer.setShadowIntensity(shadowIntensity);
}
/**
* returns the edges thickness <br>
*
* @see #setEdgesThickness(int edgesThickness)
* @return edgesThickness
*/
public int getEdgesThickness() {
return shadowRenderer.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) {
shadowRenderer.setEdgesThickness(edgesThickness);
}
/**
* returns true if the PssmRenderer flushed the shadow queues
*
* @return flushQueues
*/
public boolean isFlushQueues() {
return shadowRenderer.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) {
shadowRenderer.setFlushQueues(flushQueues);
}
/**
* sets the shadow compare mode see {@link CompareMode} for more info
*
* @param compareMode
*/
final public void setShadowCompareMode(CompareMode compareMode) {
shadowRenderer.setShadowCompareMode(compareMode);
}
/**
* returns the shadow compare mode
*
* @see CompareMode
* @return the shadowCompareMode
*/
public CompareMode getShadowCompareMode() {
return shadowRenderer.getShadowCompareMode();
}
/**
* Sets the filtering mode for shadow edges see {@link EdgeFilteringMode}
* for more info
*
* @param filterMode
*/
final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) {
shadowRenderer.setEdgeFilteringMode(filterMode);
}
/**
* returns the the edge filtering mode
*
* @see EdgeFilteringMode
* @return
*/
public EdgeFilteringMode getEdgeFilteringMode() {
return shadowRenderer.getEdgeFilteringMode();
}
@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,563 @@
/*
* 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.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Matrix4f;
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.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.WireFrustum;
import com.jme3.shadow.PssmShadowRenderer.FilterMode;
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;
/**
* abstract shadow renderer that holds commons feature to have for a shadow renderer
* @author Rémy Bouquet aka Nehon
*/
public abstract class AbstractShadowRenderer implements SceneProcessor {
protected int nbShadowMaps = 1;
protected float shadowMapSize;
protected float shadowIntensity = 0.7f;
protected RenderManager renderManager;
protected ViewPort viewPort;
protected FrameBuffer[] shadowFB;
protected Texture2D[] shadowMaps;
protected Texture2D dummyTex;
protected Material preshadowMat;
protected Material postshadowMat;
protected Matrix4f[] lightViewProjectionsMatrices;
protected boolean noOccluders = false;
protected AssetManager assetManager;
protected boolean debug = false;
protected float edgesThickness = 1.0f;
protected EdgeFilteringMode edgeFilteringMode;
protected CompareMode shadowCompareMode;
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
//a list of material of the post shadow queue geometries.
protected List<Material> matCache = new ArrayList<Material>();
/**
* Create an abstract shadow renderer, this is to be called in extending classes
* @param assetManager the application asset manager
* @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048,
* etc...)
* @param nbShadowMaps the number of shadow maps rendered (the more shadow
* maps the more quality, the less fps).
*/
protected AbstractShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbShadowMaps) {
this.assetManager = assetManager;
this.postshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PostShadow.j3md");
this.nbShadowMaps = nbShadowMaps;
this.shadowMapSize = shadowMapSize;
shadowFB = new FrameBuffer[nbShadowMaps];
shadowMaps = new Texture2D[nbShadowMaps];
dispPic = new Picture[nbShadowMaps];
lightViewProjectionsMatrices = new Matrix4f[nbShadowMaps];
//DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
dummyTex = new Texture2D(shadowMapSize, shadowMapSize, Format.RGBA8);
preshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md");
postshadowMat.setFloat("ShadowMapSize", shadowMapSize);
for (int i = 0; i < nbShadowMaps; i++) {
lightViewProjectionsMatrices[i] = new Matrix4f();
shadowFB[i] = new FrameBuffer(shadowMapSize, shadowMapSize, 1);
shadowMaps[i] = new Texture2D(shadowMapSize, shadowMapSize, 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(assetManager, shadowMaps[i], false);
}
setShadowCompareMode(CompareMode.Hardware);
setEdgeFilteringMode(EdgeFilteringMode.Bilinear);
setShadowIntensity(0.7f);
}
/**
* set the post shadow material for this renderer
* @param postShadowMat
*/
protected final void setPostShadowMaterial(Material postShadowMat) {
this.postshadowMat = postShadowMat;
postshadowMat.setFloat("ShadowMapSize", shadowMapSize);
for (int i = 0; i < nbShadowMaps; i++) {
postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]);
}
setShadowCompareMode(shadowCompareMode);
setEdgeFilteringMode(edgeFilteringMode);
setShadowIntensity(shadowIntensity);
}
/**
* Sets the filtering mode for shadow edges see {@link FilterMode} for more
* info
*
* @param filterMode
*/
final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) {
if (filterMode == null) {
throw new NullPointerException();
}
if (this.edgeFilteringMode == filterMode) {
return;
}
this.edgeFilteringMode = filterMode;
postshadowMat.setInt("FilterMode", filterMode.getMaterialParamValue());
postshadowMat.setFloat("PCFEdge", edgesThickness);
if (shadowCompareMode == CompareMode.Hardware) {
for (Texture2D shadowMap : shadowMaps) {
if (filterMode == EdgeFilteringMode.Bilinear) {
shadowMap.setMagFilter(MagFilter.Bilinear);
shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
} else {
shadowMap.setMagFilter(MagFilter.Nearest);
shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
}
}
}
}
/**
* returns the the edge filtering mode
*
* @see EdgeFilteringMode
* @return
*/
public EdgeFilteringMode getEdgeFilteringMode() {
return edgeFilteringMode;
}
/**
* sets the shadow compare mode see {@link CompareMode} for more info
*
* @param compareMode
*/
final public void setShadowCompareMode(CompareMode compareMode) {
if (compareMode == null) {
throw new NullPointerException();
}
if (this.shadowCompareMode == compareMode) {
return;
}
this.shadowCompareMode = compareMode;
for (Texture2D shadowMap : shadowMaps) {
if (compareMode == CompareMode.Hardware) {
shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual);
if (edgeFilteringMode == EdgeFilteringMode.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);
}
/**
* returns the shadow compare mode
*
* @see CompareMode
* @return the shadowCompareMode
*/
public CompareMode getShadowCompareMode() {
return shadowCompareMode;
}
//debug function that create a displayable frustrum
protected 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;
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;
}
/**
* This mehtod is called once per frame.
* it is responsible for updating the shadow cams according to the light view.
* @param viewCam the scene cam
*/
protected abstract void updateShadowCams(Camera viewCam);
/**
* this method must return the geomtryList that contains the oclluders to be rendered in the shadow map
* @param shadowMapIndex the index of the shadow map being rendered
* @param sceneOccluders the occluders of the whole scene
* @param sceneReceivers the recievers of the whole scene
* @return
*/
protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers);
/**
* return the shadow camera to use for rendering the shadow map according the given index
* @param shadowMapIndex the index of the shadow map being rendered
* @return the shadowCam
*/
protected abstract Camera getShadowCam(int shadowMapIndex);
/**
* responsible for displaying the frustum of the shadow cam for debug purpose
* @param shadowMapIndex
*/
protected void doDisplayFrustumDebug(int shadowMapIndex) {
}
@SuppressWarnings("fallthrough")
public void postQueue(RenderQueue rq) {
GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);
GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);
if (receivers.size() == 0 || occluders.size() == 0) {
return;
}
updateShadowCams(viewPort.getCamera());
Renderer r = renderManager.getRenderer();
renderManager.setForcedMaterial(preshadowMat);
renderManager.setForcedTechnique("PreShadow");
for (int shadowMapIndex = 0; shadowMapIndex < nbShadowMaps; shadowMapIndex++) {
if (debugfrustums) {
doDisplayFrustumDebug(shadowMapIndex);
}
renderShadowMap(shadowMapIndex, occluders, receivers);
}
debugfrustums = false;
if (flushQueues) {
occluders.clear();
}
//restore setting for future rendering
r.setFrameBuffer(viewPort.getOutputFrameBuffer());
renderManager.setForcedMaterial(null);
renderManager.setForcedTechnique(null);
renderManager.setCamera(viewPort.getCamera(), false);
}
protected void renderShadowMap(int shadowMapIndex, GeometryList occluders, GeometryList receivers) {
GeometryList mapOccluders = getOccludersToRender(shadowMapIndex, occluders, receivers);
Camera shadowCam = getShadowCam(shadowMapIndex);
//saving light view projection matrix for this split
lightViewProjectionsMatrices[shadowMapIndex].set(shadowCam.getViewProjectionMatrix());
renderManager.setCamera(shadowCam, false);
renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]);
renderManager.getRenderer().clearBuffers(false, true, false);
// render shadow casters to shadow map
viewPort.getQueue().renderShadowQueue(mapOccluders, renderManager, shadowCam, true);
}
boolean debugfrustums = false;
public void displayFrustum() {
debugfrustums = true;
}
//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);
}
}
/**
* This method is called once per frame and is responsible of setting the material
* parameters than sub class may need to set on the post material
* @param material the materail to use for the post shadow pass
*/
protected abstract void setMaterialParameters(Material material);
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) {
mat.setFloat("ShadowMapSize", shadowMapSize);
for (int j = 0; j < nbShadowMaps; j++) {
mat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]);
}
for (int j = 0; j < nbShadowMaps; j++) {
mat.setTexture("ShadowMap" + j, shadowMaps[j]);
}
mat.setBoolean("HardwareShadows", shadowCompareMode == CompareMode.Hardware);
mat.setInt("FilterMode", edgeFilteringMode.getMaterialParamValue());
mat.setFloat("PCFEdge", edgesThickness);
mat.setFloat("ShadowIntensity", shadowIntensity);
setMaterialParameters(mat);
}
//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();
}
}
/**
* for internal use only
*/
protected void setPostShadowParams() {
setMaterialParameters(postshadowMat);
for (int j = 0; j < nbShadowMaps; j++) {
postshadowMat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]);
postshadowMat.setTexture("ShadowMap" + j, shadowMaps[j]);
}
}
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);
}
/**
* 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);
}
/**
* 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;
}
}

@ -52,7 +52,9 @@ import com.jme3.ui.Picture;
* it's useful to render shadows in a small scene, but edges might look a bit jagged.
*
* @author Kirill Vainer
* @deprecated use {@link DirectionalLightShadowRenderer} with one split.
*/
@Deprecated
public class BasicShadowRenderer implements SceneProcessor {
private RenderManager renderManager;
@ -79,7 +81,7 @@ public class BasicShadowRenderer implements SceneProcessor {
shadowCam = new Camera(size, size);
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/BasicPostShadow.j3md");
postshadowMat.setTexture("ShadowMap", shadowMap);
dispPic.setTexture(manager, shadowMap, false);

@ -0,0 +1,48 @@
/*
* 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;
/**
* 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;
}

@ -0,0 +1,174 @@
/*
* 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.DirectionalLight;
import com.jme3.material.Material;
import java.io.IOException;
/**
*
* This Filter does basically the same as a DirectionalLightShadowRenderer
* except it renders the post shadow pass as a fulscreen quad pass instead of a
* geometry pass. It's mostly faster than PssmShadowRenderer 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 DirectionalLightShadowFilter extends AbstractShadowFilter<DirectionalLightShadowRenderer> {
/**
* Creates a DirectionalLightShadowFilter 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 assetManager the application asset manager
* @param shadowMapSize 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 DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits) {
super(assetManager, shadowMapSize, new DirectionalLightShadowRenderer(assetManager, shadowMapSize, nbSplits));
}
/**
* return the light used to cast shadows
*
* @return the DirectionalLight
*/
public DirectionalLight getLight() {
return shadowRenderer.getLight();
}
/**
* Sets the light to use to cast shadows
*
* @param light a DirectionalLight
*/
public void setLight(DirectionalLight light) {
shadowRenderer.setLight(light);
}
/**
* returns the labda parameter
*
* @see #setLambda(float lambda)
* @return lambda
*/
public float getLambda() {
return shadowRenderer.getLambda();
}
/**
* Adjust the repartition of the different shadow maps in the shadow extend
* usualy goes from 0.0 to 1.0 a low value give a more linear repartition
* resulting in a constant quality in the shadow over the extends, but near
* shadows could look very jagged a high value give a more logarithmic
* repartition resulting in a high quality for near shadows, but the quality
* quickly decrease over the extend. the default value is set to 0.65f
* (theoric optimal value).
*
* @param lambda the lambda value.
*/
public void setLambda(float lambda) {
shadowRenderer.setLambda(lambda);
}
/**
* How far the shadows are rendered in the view
*
* @see setShadowZExtend(float zFar)
* @return shadowZExtend
*/
public float getShadowZExtend() {
return shadowRenderer.getShadowZExtend();
}
/**
* 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) {
shadowRenderer.setShadowZExtend(zFar);
}
/**
* 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) {
shadowRenderer.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 shadowRenderer.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,257 @@
/*
* 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.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.OpaqueComparator;
import com.jme3.scene.Node;
/**
* DirectionalLightShadowRenderer 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 DirectionalLightShadowRenderer extends AbstractShadowRenderer {
protected float lambda = 0.65f;
protected float zFarOverride = 0;
protected Camera shadowCam;
protected ColorRGBA splits;
protected GeometryList splitOccluders = new GeometryList(new OpaqueComparator());
protected float[] splitsArray;
protected DirectionalLight light;
protected Vector3f[] points = new Vector3f[8];
//Holding the info for fading shadows in the far distance
protected Vector2f fadeInfo;
protected float fadeLength;
/**
* Create a DirectionalLightShadowRenderer 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 assetManager the application asset manager
* @param shadowMapSize 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 DirectionalLightShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbSplits) {
super(assetManager, shadowMapSize, nbSplits);
nbShadowMaps = Math.max(Math.min(nbSplits, 4), 1);
splits = new ColorRGBA();
splitsArray = new float[nbSplits + 1];
shadowCam = new Camera(shadowMapSize, shadowMapSize);
shadowCam.setParallelProjection(true);
for (int i = 0; i < points.length; i++) {
points[i] = new Vector3f();
}
}
/**
* return the light used to cast shadows
*
* @return the DirectionalLight
*/
public DirectionalLight getLight() {
return light;
}
/**
* Sets the light to use to cast shadows
*
* @param light a DirectionalLight
*/
public void setLight(DirectionalLight light) {
this.light = light;
}
@Override
protected void updateShadowCams(Camera viewCam) {
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);
//shadowCam.setDirection(direction);
shadowCam.getRotation().lookAt(light.getDirection(), shadowCam.getUp());
shadowCam.update();
shadowCam.updateViewProjection();
PssmShadowUtil.updateFrustumSplits(splitsArray, frustumNear, zFar, lambda);
switch (splitsArray.length) {
case 5:
splits.a = splitsArray[4];
case 4:
splits.b = splitsArray[3];
case 3:
splits.g = splitsArray[2];
case 2:
case 1:
splits.r = splitsArray[1];
break;
}
}
@Override
protected GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers) {
// update frustum points based on current camera and split
ShadowUtil.updateFrustumPoints(viewPort.getCamera(), splitsArray[shadowMapIndex], splitsArray[shadowMapIndex + 1], 1.0f, points);
//Updating shadow cam with curent split frustra
ShadowUtil.updateShadowCamera(sceneOccluders, sceneReceivers, shadowCam, points, splitOccluders);
return splitOccluders;
}
@Override
protected Camera getShadowCam(int shadowMapIndex) {
return shadowCam;
}
@Override
protected void doDisplayFrustumDebug(int shadowMapIndex) {
((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex));
ShadowUtil.updateFrustumPoints2(shadowCam, points);
((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex));
}
@Override
protected void setMaterialParameters(Material material) {
material.setColor("Splits", splits);
}
/**
* returns the labda parameter see #setLambda(float lambda)
*
* @return lambda
*/
public float getLambda() {
return lambda;
}
/*
* Adjust the repartition of the different shadow maps in the shadow extend
* usualy goes from 0.0 to 1.0
* a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged
* a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend.
* the default value is set to 0.65f (theoric optimal value).
* @param lambda the lambda value.
*/
public void setLambda(float lambda) {
this.lambda = lambda;
}
/**
* 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;
}
/**
* 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;
}
}

@ -31,45 +31,50 @@
*/
package com.jme3.shadow;
import com.jme3.light.DirectionalLight;
import com.jme3.light.Light;
import com.jme3.light.PointLight;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
/**
* Creates a camera according to a light
* Handy to compute projection matrix of a light
* @author Kirill Vainer
* <code>ShadowEdgeFiltering</code> specifies how shadows are filtered
*/
public class ShadowCamera {
public enum EdgeFilteringMode {
private Vector3f[] points = new Vector3f[8];
private Light target;
/**
* Shadows are not filtered. Nearest sample is used, causing in blocky
* shadows.
*/
Nearest(0),
/**
* Bilinear filtering is used. Has the potential of being hardware
* accelerated on some GPUs
*/
Bilinear(1),
/**
* Dither-based sampling is used, very cheap but can look bad at low
* resolutions.
*/
Dither(2),
/**
* 4x4 percentage-closer filtering is used. Shadows will be smoother at the
* cost of performance
*/
PCF4(3),
/**
* 8x8 percentage-closer filtering is used. Shadows will be smoother at the
* cost of performance
*/
PCFPOISSON(4),
/**
* 8x8 percentage-closer filtering is used. Shadows will be smoother at the
* cost of performance
*/
PCF8(5);
int materialParamValue;
public ShadowCamera(Light target) {
this.target = target;
for (int i = 0; i < points.length; i++) {
points[i] = new Vector3f();
}
private EdgeFilteringMode(int val) {
materialParamValue = val;
}
/**
* Updates the camera view direction and position based on the light
*/
public void updateLightCamera(Camera lightCam) {
if (target.getType() == Light.Type.Directional) {
DirectionalLight dl = (DirectionalLight) target;
lightCam.setParallelProjection(true);
lightCam.setLocation(Vector3f.ZERO);
lightCam.lookAtDirection(dl.getDirection(), Vector3f.UNIT_Y);
lightCam.setFrustum(-1, 1, -1, 1, 1, -1);
} else {
PointLight pl = (PointLight) target;
lightCam.setParallelProjection(false);
lightCam.setLocation(pl.getPosition());
// direction will have to be calculated automatically
lightCam.setFrustumPerspective(45, 1, 1, 300);
}
public int getMaterialParamValue() {
return materialParamValue;
}
}

@ -44,90 +44,44 @@ 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
*
/**
*
* 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;
public class PointLightShadowFilter extends AbstractShadowFilter<PointLightShadowRenderer> {
/**
* 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).
* Creates a PointLightShadowFilter
*
* @param assetManager the application asset manager
* @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048,
* etc...)
*/
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;
public PointLightShadowFilter(AssetManager assetManager, int shadowMapSize) {
super(assetManager, shadowMapSize,new PointLightShadowRenderer(assetManager, shadowMapSize));
}
@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();
return shadowRenderer.getLight();
}
/**
@ -136,93 +90,7 @@ public class PointLightShadowFilter extends Filter {
* @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();
shadowRenderer.setLight(light);
}
@Override

@ -34,451 +34,47 @@ 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/>
* PointLightShadowRenderer renders shadows for a point light
*
* @author Rémy Bouquet aka Nehon
*/
public class PointLightShadowRenderer implements SceneProcessor {
/**
* <code>FilterMode</code> specifies how shadows are filtered
*/
public enum FilterMode {
public class PointLightShadowRenderer extends AbstractShadowRenderer {
/**
* 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;
public static 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"));
}
protected GeometryList shadowMapOccluders = new GeometryList(new OpaqueComparator());
private Geometry[] frustums = null;
/**
* 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>
* Creates a PointLightShadowRenderer
*
* @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
* @param assetManager the application asset manager
* @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048,
* etc...)
*/
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);
public PointLightShadowRenderer(AssetManager assetManager, int shadowMapSize) {
super(assetManager, shadowMapSize, CAM_NUMBER);
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;
shadowCams[i] = new Camera(shadowMapSize, shadowMapSize);
}
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;
}
@Override
protected void updateShadowCams(Camera viewCam) {
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));
@ -505,7 +101,22 @@ public class PointLightShadowRenderer implements SceneProcessor {
shadowCams[i].updateViewProjection();
}
if (debug && frustums == null) {
}
@Override
protected GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers) {
ShadowUtil.getOccludersInCamFrustum(sceneOccluders, shadowCams[shadowMapIndex], shadowMapOccluders);
return shadowMapOccluders;
}
@Override
protected Camera getShadowCam(int shadowMapIndex) {
return shadowCams[shadowMapIndex];
}
@Override
protected void doDisplayFrustumDebug(int shadowMapIndex) {
if (frustums == null) {
frustums = new Geometry[CAM_NUMBER];
Vector3f[] points = new Vector3f[8];
for (int i = 0; i < 8; i++) {
@ -516,220 +127,14 @@ public class PointLightShadowRenderer implements SceneProcessor {
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);
if (frustums[shadowMapIndex].getParent() == null) {
((Node) viewPort.getScenes().get(0)).attachChild(frustums[shadowMapIndex]);
}
}
/**
* 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;
@Override
protected void setMaterialParameters(Material material) {
material.setVector3("LightPos", light.getPosition());
}
/**
@ -748,6 +153,5 @@ public class PointLightShadowRenderer implements SceneProcessor {
*/
public void setLight(PointLight light) {
this.light = light;
updateShadowCams();
}
}

@ -61,7 +61,9 @@ import java.io.IOException;
* API is basically the same as the PssmShadowRenderer;
*
* @author Rémy Bouquet aka Nehon
* @deprecated use {@link DirectionalLightShadowFilter}
*/
@Deprecated
public class PssmShadowFilter extends Filter {
private PssmShadowRenderer pssmRenderer;

@ -48,6 +48,7 @@ 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;
@ -66,17 +67,21 @@ import java.util.List;
* 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>
* 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
* @deprecated use {@link DirectionalLightShadowRenderer}
*/
@Deprecated
public class PssmShadowRenderer implements SceneProcessor {
/**
* <code>FilterMode</code> specifies how shadows are filtered
* @deprecated use {@link EdgeFilteringMode}
*/
public enum FilterMode {
@Deprecated
public enum FilterMode{
/**
* Shadows are not filtered. Nearest sample is used, causing in blocky
@ -89,30 +94,32 @@ public class PssmShadowRenderer implements SceneProcessor {
*/
Bilinear,
/**
* Dither-based sampling is used, very cheap but can look bad
* at low resolutions.
* 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
* 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
* 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
* 8x8 percentage-closer filtering is used. Shadows will be smoother at
* the cost of performance
*/
PCF8
}
/**
* Specifies the shadow comparison mode
* Specifies the shadow comparison mode
* @deprecated use {@link CompareMode}
*/
@Deprecated
public enum CompareMode {
/**
@ -169,24 +176,30 @@ public class PssmShadowRenderer implements SceneProcessor {
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>
* 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).
* @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 PssmShadowRenderer(AssetManager manager, int size, int nbSplits) {
this(manager, size, nbSplits, new Material(manager, "Common/MatDefs/Shadow/PostShadowPSSM.j3md"));
this(manager, size, nbSplits, new Material(manager, "Common/MatDefs/Shadow/PostShadow.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>
* 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
* @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 PssmShadowRenderer(AssetManager manager, int size, int nbSplits, Material postShadowMat) {
@ -219,7 +232,7 @@ public class PssmShadowRenderer implements SceneProcessor {
//DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
shadowFB[i].setColorTexture(dummyTex);
postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]);
postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]);
//quads for debuging purpose
dispPic[i] = new Picture("Picture" + i);
@ -240,8 +253,10 @@ public class PssmShadowRenderer implements SceneProcessor {
}
/**
* Sets the filtering mode for shadow edges see {@link FilterMode} for more info
* @param filterMode
* Sets the filtering mode for shadow edges see {@link FilterMode} for more
* info
*
* @param filterMode
*/
final public void setFilterMode(FilterMode filterMode) {
if (filterMode == null) {
@ -271,7 +286,8 @@ public class PssmShadowRenderer implements SceneProcessor {
/**
* sets the shadow compare mode see {@link CompareMode} for more info
* @param compareMode
*
* @param compareMode
*/
final public void setCompareMode(CompareMode compareMode) {
if (compareMode == null) {
@ -351,7 +367,8 @@ public class PssmShadowRenderer implements SceneProcessor {
/**
* returns the light direction used by the processor
* @return
*
* @return
*/
public Vector3f getDirection() {
return direction;
@ -359,7 +376,8 @@ public class PssmShadowRenderer implements SceneProcessor {
/**
* Sets the light direction to use to compute shadows
* @param direction
*
* @param direction
*/
public void setDirection(Vector3f direction) {
this.direction.set(direction).normalizeLocal();
@ -425,12 +443,23 @@ public class PssmShadowRenderer implements SceneProcessor {
lightViewProjectionsMatrices[i].set(shadowCam.getViewProjectionMatrix());
renderManager.setCamera(shadowCam, false);
if (debugfrustums) {
// frustrumFromBound(b.casterBB,ColorRGBA.Blue );
// frustrumFromBound(b.receiverBB,ColorRGBA.Green );
// frustrumFromBound(b.splitBB,ColorRGBA.Yellow );
((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, i));
ShadowUtil.updateFrustumPoints2(shadowCam, points);
((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, i));
}
r.setFrameBuffer(shadowFB[i]);
r.clearBuffers(false, true, false);
// render shadow casters to shadow map
viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true);
}
debugfrustums = false;
if (flushQueues) {
occluders.clear();
}
@ -441,6 +470,11 @@ public class PssmShadowRenderer implements SceneProcessor {
renderManager.setCamera(viewCam, false);
}
boolean debugfrustums = false;
public void displayFrustum() {
debugfrustums = true;
}
//debug only : displays depth shadow maps
protected void displayShadowMap(Renderer r) {
@ -457,8 +491,9 @@ public class PssmShadowRenderer implements SceneProcessor {
renderManager.setCamera(cam, false);
}
/**For dubuging purpose
* Allow to "snapshot" the current frustrum to the scene
/**
* For dubuging purpose Allow to "snapshot" the current frustrum to the
* scene
*/
public void displayDebug() {
debug = true;
@ -515,36 +550,22 @@ public class PssmShadowRenderer implements SceneProcessor {
//iterating through the mat cache and setting the parameters
for (Material mat : matCache) {
if (mat.getParam("Splits") == null) {
mat.setColor("Splits", splits);
}
if (mat.getParam("ShadowMapSize") == null) {
mat.setFloat("ShadowMapSize", shadowMapSize);
}
mat.setColor("Splits", splits);
mat.setFloat("ShadowMapSize", shadowMapSize);
for (int j = 0; j < nbSplits; j++) {
mat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]);
}
if (mat.getParam("ShadowMap0") == null) {
for (int j = 0; j < nbSplits; 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);
for (int j = 0; j < nbSplits; j++) {
mat.setTexture("ShadowMap" + j, shadowMaps[j]);
}
mat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware);
mat.setInt("FilterMode", filterMode.ordinal());
mat.setFloat("PCFEdge", edgesThickness);
mat.setFloat("ShadowIntensity", shadowIntensity);
if (mat.getParam("ShadowIntensity") == null || applyShadowIntensity) {
mat.setFloat("ShadowIntensity", shadowIntensity);
}
if (fadeInfo != null && mat.getParam("FadeInfo") == null || applyFadeInfo) {
mat.setVector2("FadeInfo", fadeInfo);
if (fadeInfo != null) {
mat.setVector2("FadeInfo", fadeInfo);
}
}
@ -567,7 +588,8 @@ public class PssmShadowRenderer implements SceneProcessor {
postshadowMat.setColor("Splits", splits);
for (int j = 0; j < nbSplits; j++) {
postshadowMat.setMatrix4("LightViewProjectionMatrix" + j, lightViewProjectionsMatrices[j]);
}
postshadowMat.setTexture("ShadowMap" + j, shadowMaps[j]);
}
}
public void preFrame(float tpf) {
@ -580,8 +602,8 @@ public class PssmShadowRenderer implements SceneProcessor {
}
/**
* returns the labda parameter
* see #setLambda(float lambda)
* returns the labda parameter see #setLambda(float lambda)
*
* @return lambda
*/
public float getLambda() {
@ -602,6 +624,7 @@ public class PssmShadowRenderer implements SceneProcessor {
/**
* How far the shadows are rendered in the view
*
* @see #setShadowZExtend(float zFar)
* @return shadowZExtend
*/
@ -610,20 +633,23 @@ public class PssmShadowRenderer implements SceneProcessor {
}
/**
* 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.
* 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){
if (fadeInfo != null) {
fadeInfo.set(zFar - fadeLength, 1f / fadeLength);
}
this.zFarOverride = zFar;
}
/**
* returns the shdaow intensity
*
* @see #setShadowIntensity(float shadowIntensity)
* @return shadowIntensity
*/
@ -632,10 +658,10 @@ public class PssmShadowRenderer implements SceneProcessor {
}
/**
* 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
* 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) {
@ -646,6 +672,7 @@ public class PssmShadowRenderer implements SceneProcessor {
/**
* returns the edges thickness
*
* @see #setEdgesThickness(int edgesThickness)
* @return edgesThickness
*/
@ -654,8 +681,10 @@ public class PssmShadowRenderer implements SceneProcessor {
}
/**
* 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
* 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));
@ -666,6 +695,7 @@ public class PssmShadowRenderer implements SceneProcessor {
/**
* returns true if the PssmRenderer flushed the shadow queues
*
* @return flushQueues
*/
public boolean isFlushQueues() {
@ -673,18 +703,21 @@ public class PssmShadowRenderer implements SceneProcessor {
}
/**
* 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
* 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.
* 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) {
@ -702,15 +735,17 @@ public class PssmShadowRenderer implements SceneProcessor {
postshadowMat.setVector2("FadeInfo", fadeInfo);
}
}
/**
* get the length over which the shadow will fade out when using a shadowZextend
/**
* 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){
public float getShadowZFadeLength() {
if (fadeInfo != null) {
return zFarOverride - fadeInfo.x;
}
return 0f;
return 0f;
}
}

@ -361,9 +361,7 @@ public class ShadowUtil {
if (ortho) {
shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
} else {
shadowCam.setFrustumPerspective(45, 1, 1, 150);
}
}
// create transform to rotate points to viewspace
Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
@ -442,9 +440,9 @@ public class ShadowUtil {
splitMin.z = 0;
if (!ortho) {
shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z);
}
// if (!ortho) {
// shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z);
// }
Matrix4f projMatrix = shadowCam.getProjectionMatrix();

@ -0,0 +1,143 @@
/*
* 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.SpotLight;
import java.io.IOException;
/**
*
* This Filter does basically the same as a SpotLightShadowRenderer
* except it renders the post shadow pass as a fulscreen quad pass instead of a
* geometry pass. It's mostly faster than PssmShadowRenderer 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 SpotLightShadowFilter extends AbstractShadowFilter<SpotLightShadowRenderer> {
/**
* Creates a SpotLight Shadow Filter
* @param assetManager the application asset manager
* @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048,
* etc...)
* the more quality, the less fps).
*/
public SpotLightShadowFilter(AssetManager assetManager, int shadowMapSize) {
super(assetManager, shadowMapSize, new SpotLightShadowRenderer(assetManager, shadowMapSize));
}
/**
* return the light used to cast shadows
*
* @return the SpotLight
*/
public SpotLight getLight() {
return shadowRenderer.getLight();
}
/**
* Sets the light to use to cast shadows
*
* @param light a SpotLight
*/
public void setLight(SpotLight light) {
shadowRenderer.setLight(light);
}
/**
* How far the shadows are rendered in the view
*
* @see setShadowZExtend(float zFar)
* @return shadowZExtend
*/
public float getShadowZExtend() {
return shadowRenderer.getShadowZExtend();
}
/**
* 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) {
shadowRenderer.setShadowZExtend(zFar);
}
/**
* 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) {
shadowRenderer.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 shadowRenderer.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,211 @@
/*
* 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.DirectionalLight;
import com.jme3.light.SpotLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.OpaqueComparator;
import com.jme3.scene.Node;
/**
* SpotLightShadowRenderer 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 SpotLightShadowRenderer extends AbstractShadowRenderer {
protected float zFarOverride = 0;
protected Camera shadowCam;
protected GeometryList mapOccluders = new GeometryList(new OpaqueComparator());
protected SpotLight light;
protected Vector3f[] points = new Vector3f[8];
//Holding the info for fading shadows in the far distance
protected Vector2f fadeInfo;
protected float fadeLength;
/**
* Create a SpotLightShadowRenderer This use standard shadow mapping
*
* @param assetManager the application asset manager
* @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048,
* etc...) the more quality, the less fps).
*/
public SpotLightShadowRenderer(AssetManager assetManager, int shadowMapSize) {
super(assetManager, shadowMapSize, 1);
shadowCam = new Camera(shadowMapSize, shadowMapSize);
for (int i = 0; i < points.length; i++) {
points[i] = new Vector3f();
}
}
/**
* return the light used to cast shadows
*
* @return the SpotLight
*/
public SpotLight getLight() {
return light;
}
/**
* Sets the light to use to cast shadows
*
* @param light a SpotLight
*/
public void setLight(SpotLight light) {
this.light = light;
}
@Override
protected void updateShadowCams(Camera viewCam) {
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);
//shadowCam.setDirection(direction);
shadowCam.setFrustumPerspective(light.getSpotOuterAngle() * FastMath.RAD_TO_DEG * 2.0f, 1, 1f, light.getSpotRange());
shadowCam.getRotation().lookAt(light.getDirection(), shadowCam.getUp());
shadowCam.setLocation(light.getPosition());
shadowCam.update();
shadowCam.updateViewProjection();
}
@Override
protected GeometryList getOccludersToRender(int shadowMapIndex, GeometryList sceneOccluders, GeometryList sceneReceivers) {
ShadowUtil.getOccludersInCamFrustum(sceneOccluders, shadowCam, mapOccluders);
return mapOccluders;
}
@Override
protected Camera getShadowCam(int shadowMapIndex) {
return shadowCam;
}
@Override
protected void doDisplayFrustumDebug(int shadowMapIndex) {
Vector3f[] points2 = points.clone();
((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex));
ShadowUtil.updateFrustumPoints2(shadowCam, points2);
((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points2, shadowMapIndex));
}
@Override
protected void setMaterialParameters(Material material) {
}
/**
* 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;
}
/**
* 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;
}
}

@ -0,0 +1,154 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jme3test.light;
import com.jme3.asset.AssetManager;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.shadow.AbstractShadowFilter;
import com.jme3.shadow.AbstractShadowRenderer;
import com.jme3.shadow.CompareMode;
import com.jme3.shadow.EdgeFilteringMode;
/**
*
* @author Nehon
*/
public class ShadowTestUIManager implements ActionListener {
private BitmapText shadowTypeText;
private BitmapText shadowCompareText;
private BitmapText shadowFilterText;
private BitmapText shadowIntensityText;
private final static String TYPE_TEXT = "(Space) Shadow type : ";
private final static String COMPARE_TEXT = "(enter) Shadow compare ";
private final static String FILTERING_TEXT = "(f) Edge filtering : ";
private final static String INTENSITY_TEXT = "(t:up, g:down) Shadow intensity : ";
private boolean hardwareShadows = true;
private AbstractShadowRenderer plsr;
private AbstractShadowFilter plsf;
private ViewPort viewPort;
private int filteringIndex = 0;
private int renderModeIndex = 0;
public ShadowTestUIManager(AssetManager assetManager,AbstractShadowRenderer plsr, AbstractShadowFilter plsf,
Node guiNode, InputManager inputManager, ViewPort viewPort) {
this.plsr = plsr;
this.plsf = plsf;
this.viewPort = viewPort;
BitmapFont guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
shadowTypeText = createText(guiFont);
shadowCompareText = createText(guiFont);
shadowFilterText = createText(guiFont);
shadowIntensityText = createText(guiFont);
shadowTypeText.setText(TYPE_TEXT + "Processor");
shadowCompareText.setText(COMPARE_TEXT + (hardwareShadows ? "Hardware" : "Software"));
shadowFilterText.setText(FILTERING_TEXT + plsr.getEdgeFilteringMode().toString());
shadowIntensityText.setText(INTENSITY_TEXT + plsr.getShadowIntensity());
shadowTypeText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 20, 0);
shadowCompareText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 40, 0);
shadowFilterText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 60, 0);
shadowIntensityText.setLocalTranslation(10, viewPort.getCamera().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");
}
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.setShadowCompareMode(hardwareShadows ? CompareMode.Hardware : CompareMode.Software);
plsf.setShadowCompareMode(hardwareShadows ? CompareMode.Hardware : CompareMode.Software);
shadowCompareText.setText(COMPARE_TEXT + (hardwareShadows ? "Hardware" : "Software"));
}
if (name.equals("changeFiltering") && keyPressed) {
filteringIndex = plsr.getEdgeFilteringMode().ordinal();
filteringIndex = (filteringIndex + 1) % EdgeFilteringMode.values().length;
EdgeFilteringMode m = EdgeFilteringMode.values()[filteringIndex];
plsr.setEdgeFilteringMode(m);
plsf.setEdgeFilteringMode(m);
shadowFilterText.setText(FILTERING_TEXT + m.toString());
}
if (name.equals("ShadowUp") && keyPressed) {
plsr.setShadowIntensity(plsr.getShadowIntensity() + 0.1f);
plsf.setShadowIntensity(plsf.getShadowIntensity() + 0.1f);
shadowIntensityText.setText(INTENSITY_TEXT + plsr.getShadowIntensity());
}
if (name.equals("ShadowDown") && keyPressed) {
plsr.setShadowIntensity(plsr.getShadowIntensity() - 0.1f);
plsf.setShadowIntensity(plsf.getShadowIntensity() - 0.1f);
shadowIntensityText.setText(INTENSITY_TEXT + plsr.getShadowIntensity());
}
if (name.equals("ThicknessUp") && keyPressed) {
plsr.setEdgesThickness(plsr.getEdgesThickness() + 1);
plsf.setEdgesThickness(plsf.getEdgesThickness() + 1);
System.out.println("Shadow thickness : " + plsr.getEdgesThickness());
}
if (name.equals("ThicknessDown") && keyPressed) {
plsr.setEdgesThickness(plsr.getEdgesThickness() - 1);
plsf.setEdgesThickness(plsf.getEdgesThickness() - 1);
System.out.println("Shadow thickness : " + plsr.getEdgesThickness());
}
}
private BitmapText createText(BitmapFont guiFont) {
BitmapText t = new BitmapText(guiFont, false);
t.setSize(guiFont.getCharSet().getRenderedSize() * 0.75f);
return t;
}
}

@ -0,0 +1,311 @@
/*
* 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.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.shadow.CompareMode;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.shadow.PssmShadowRenderer.FilterMode;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.SkyFactory;
import com.jme3.util.TangentBinormalGenerator;
public class TestDirectionalLightShadow extends SimpleApplication implements ActionListener {
private Spatial[] obj;
private Material[] mat;
private boolean hardwareShadows = false;
private DirectionalLightShadowRenderer dlsr;
private DirectionalLightShadowFilter dlsf;
private Geometry ground;
private Material matGroundU;
private Material matGroundL;
public static void main(String[] args) {
TestDirectionalLightShadow app = new TestDirectionalLightShadow();
app.start();
}
public void loadScene() {
obj = new Spatial[2];
mat = new Material[2];
mat[0] = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
mat[1].setBoolean("UseMaterialColors", true);
mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f));
mat[1].setColor("Diffuse", ColorRGBA.White.clone());
obj[0] = new Geometry("sphere", new Sphere(30, 30, 2));
obj[0].setShadowMode(ShadowMode.CastAndReceive);
obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f));
obj[1].setShadowMode(ShadowMode.CastAndReceive);
TangentBinormalGenerator.generate(obj[1]);
TangentBinormalGenerator.generate(obj[0]);
for (int i = 0; i < 60; i++) {
Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
t.setLocalScale(FastMath.nextRandomFloat() * 10f);
t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]);
rootNode.attachChild(t);
t.setLocalTranslation(FastMath.nextRandomFloat() * 200f, FastMath.nextRandomFloat() * 30f + 20, 30f * (i + 2f));
}
Box b = new Box(new Vector3f(0, 10, 550), 1000, 2, 1000);
b.scaleTextureCoordinates(new Vector2f(10, 10));
ground = new Geometry("soil", b);
matGroundU = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matGroundU.setColor("Color", ColorRGBA.Green);
matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
grass.setWrap(WrapMode.Repeat);
matGroundL.setTexture("DiffuseMap", grass);
ground.setMaterial(matGroundL);
ground.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(ground);
l = new DirectionalLight();
//new Vector3f(-0.5973172f, -0.56583486f, 0.8846725f).normalizeLocal()
l.setDirection(new Vector3f(-1, -1, -1));
rootNode.addLight(l);
AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(0.5f));
rootNode.addLight(al);
Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false);
sky.setLocalScale(350);
rootNode.attachChild(sky);
}
DirectionalLight l;
@Override
public void simpleInitApp() {
// put the camera in a bad position
cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f));
cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f));
flyCam.setMoveSpeed(100);
loadScene();
dlsr = new DirectionalLightShadowRenderer(assetManager, 1024, 3);
dlsr.setLight(l);
dlsr.setLambda(0.55f);
dlsr.setShadowIntensity(0.6f);
dlsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
//dlsr.displayFrustum();
viewPort.addProcessor(dlsr);
dlsf = new DirectionalLightShadowFilter(assetManager, 1024, 3);
dlsf.setLight(l);
dlsf.setLambda(0.55f);
dlsf.setShadowIntensity(0.6f);
dlsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
dlsf.setEnabled(false);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(dlsf);
viewPort.addProcessor(fpp);
initInputs();
}
private void initInputs() {
inputManager.addMapping("ThicknessUp", new KeyTrigger(KeyInput.KEY_Y));
inputManager.addMapping("ThicknessDown", new KeyTrigger(KeyInput.KEY_H));
inputManager.addMapping("lambdaUp", new KeyTrigger(KeyInput.KEY_U));
inputManager.addMapping("lambdaDown", new KeyTrigger(KeyInput.KEY_J));
inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M));
inputManager.addMapping("splits", new KeyTrigger(KeyInput.KEY_X));
inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_NUMPAD8));
inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_NUMPAD2));
inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_NUMPAD6));
inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_NUMPAD4));
inputManager.addMapping("fwd", new KeyTrigger(KeyInput.KEY_PGUP));
inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_PGDN));
inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown",
"switchGroundMat", "splits", "up", "down", "right", "left", "fwd", "back");
ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort);
}
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("lambdaUp") && keyPressed) {
dlsr.setLambda(dlsr.getLambda() + 0.01f);
dlsf.setLambda(dlsr.getLambda() + 0.01f);
System.out.println("Lambda : " + dlsr.getLambda());
} else if (name.equals("lambdaDown") && keyPressed) {
dlsr.setLambda(dlsr.getLambda() - 0.01f);
dlsf.setLambda(dlsr.getLambda() - 0.01f);
System.out.println("Lambda : " + dlsr.getLambda());
}
if (name.equals("ShadowUp") && keyPressed) {
dlsr.setShadowIntensity(dlsr.getShadowIntensity() + 0.1f);
dlsf.setShadowIntensity(dlsr.getShadowIntensity() + 0.1f);
System.out.println("Shadow intensity : " + dlsr.getShadowIntensity());
}
if (name.equals("ShadowDown") && keyPressed) {
dlsr.setShadowIntensity(dlsr.getShadowIntensity() - 0.1f);
dlsf.setShadowIntensity(dlsr.getShadowIntensity() - 0.1f);
System.out.println("Shadow intensity : " + dlsr.getShadowIntensity());
}
if (name.equals("ThicknessUp") && keyPressed) {
dlsr.setEdgesThickness(dlsr.getEdgesThickness() + 1);
dlsf.setEdgesThickness(dlsr.getEdgesThickness() + 1);
System.out.println("Shadow thickness : " + dlsr.getEdgesThickness());
}
if (name.equals("ThicknessDown") && keyPressed) {
dlsr.setEdgesThickness(dlsr.getEdgesThickness() - 1);
dlsf.setEdgesThickness(dlsr.getEdgesThickness() - 1);
System.out.println("Shadow thickness : " + dlsr.getEdgesThickness());
}
if (name.equals("switchGroundMat") && keyPressed) {
if (ground.getMaterial() == matGroundL) {
ground.setMaterial(matGroundU);
} else {
ground.setMaterial(matGroundL);
}
}
if (name.equals("up")) {
up = keyPressed;
}
if (name.equals("down")) {
down = keyPressed;
}
if (name.equals("right")) {
right = keyPressed;
}
if (name.equals("left")) {
left = keyPressed;
}
if (name.equals("fwd")) {
fwd = keyPressed;
}
if (name.equals("back")) {
back = keyPressed;
}
}
boolean up = false;
boolean down = false;
boolean left = false;
boolean right = false;
boolean fwd = false;
boolean back = false;
float time = 0;
float s = 1f;
@Override
public void simpleUpdate(float tpf) {
if (up) {
Vector3f v = l.getDirection();
v.y += tpf / s;
setDir(v);
}
if (down) {
Vector3f v = l.getDirection();
v.y -= tpf / s;
setDir(v);
}
if (right) {
Vector3f v = l.getDirection();
v.x += tpf / s;
setDir(v);
}
if (left) {
Vector3f v = l.getDirection();
v.x -= tpf / s;
setDir(v);
}
if (fwd) {
Vector3f v = l.getDirection();
v.z += tpf / s;
setDir(v);
}
if (back) {
Vector3f v = l.getDirection();
v.z -= tpf / s;
setDir(v);
}
}
private void setDir(Vector3f v) {
l.setDirection(v);
}
}

@ -32,12 +32,7 @@
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;
@ -46,18 +41,18 @@ 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.EdgeFilteringMode;
import com.jme3.shadow.PointLightShadowFilter;
import com.jme3.shadow.PointLightShadowRenderer;
public class TestPointLightShadows extends SimpleApplication implements ActionListener {
public class TestPointLightShadows extends SimpleApplication {
public static void main(String[] args) {
TestPointLightShadows app = new TestPointLightShadows();
app.start();
}
Node lightNode;
private boolean hardwareShadows = true;
PointLightShadowRenderer plsr ;
PointLightShadowRenderer plsr;
PointLightShadowFilter plsf;
@Override
@ -91,147 +86,44 @@ public class TestPointLightShadows extends SimpleApplication implements ActionLi
plsr = new PointLightShadowRenderer(assetManager, 512);
plsr.setLight((PointLight) scene.getLocalLightList().get(0));
plsr.setFilterMode(PointLightShadowRenderer.FilterMode.Nearest);
// plsr.displayDebug();
plsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
// plsr.setFlushQueues(false);
// plsr.displayDebug();
viewPort.addProcessor(plsr);
// PointLight pl = new PointLight();
// pl.setPosition(new Vector3f(0, 0.5f, 0));
// pl.setRadius(5);
// rootNode.addLight(pl);
// Geometry lightMdl2 = new Geometry("Light2", new Sphere(10, 10, 0.1f));
// //Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f));
// lightMdl2.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
// lightMdl2.setShadowMode(RenderQueue.ShadowMode.Off);
// rootNode.attachChild(lightMdl2);
// lightMdl2.setLocalTranslation(pl.getPosition());
// PointLightShadowRenderer plsr2 = new PointLightShadowRenderer(assetManager, 512);
// plsr2.setLight(pl);
// plsr2.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
// // plsr.displayDebug();
// viewPort.addProcessor(plsr2);
plsf = new PointLightShadowFilter(assetManager, 512);
plsf.setLight((PointLight) scene.getLocalLightList().get(0));
plsf.setFilterMode(PointLightShadowRenderer.FilterMode.Nearest);
plsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
plsf.setEnabled(false);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(plsf);
fpp.addFilter(plsf);
viewPort.addProcessor(fpp);
initUIAndInputs();
ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, plsr, plsf, guiNode, inputManager, viewPort);
}
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;
// lightNode.move(FastMath.cos(tpf) * 0.4f, 0, FastMath.sin(tpf) * 0.4f);
}
}

@ -32,9 +32,13 @@
package jme3test.light;
import com.jme3.app.SimpleApplication;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.Savable;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
@ -63,6 +67,7 @@ import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.SkyFactory;
import com.jme3.util.TangentBinormalGenerator;
import java.io.IOException;
public class TestPssmShadow extends SimpleApplication implements ActionListener {
@ -123,7 +128,7 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener
ground.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(ground);
DirectionalLight l = new DirectionalLight();
l = new DirectionalLight();
l.setDirection(new Vector3f(-1, -1, -1));
rootNode.addLight(l);
@ -136,6 +141,7 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener
rootNode.attachChild(sky);
}
DirectionalLight l;
@Override
public void simpleInitApp() {
@ -148,36 +154,42 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener
loadScene();
pssmRenderer = new PssmShadowRenderer(assetManager, 1024, 3);
pssmRenderer.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
// pssmRenderer.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal());
//pssmRenderer.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
pssmRenderer.setDirection(new Vector3f(-0.5973172f, -0.56583486f, 0.8846725f).normalizeLocal());
pssmRenderer.setLambda(0.55f);
pssmRenderer.setShadowIntensity(0.6f);
pssmRenderer.setCompareMode(CompareMode.Software);
pssmRenderer.setFilterMode(FilterMode.Dither);
// pssmRenderer.displayDebug();
pssmRenderer.displayFrustum();
viewPort.addProcessor(pssmRenderer);
pssmFilter = new PssmShadowFilter(assetManager, 1024, 3);
pssmFilter.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
//pssmRenderer.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal());
//pssmFilter.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
pssmRenderer.setDirection(new Vector3f(-0.5973172f, -0.56583486f, 0.8846725f).normalizeLocal());
pssmFilter.setLambda(0.55f);
pssmFilter.setShadowIntensity(0.6f);
pssmFilter.setCompareMode(CompareMode.Software);
pssmFilter.setFilterMode(FilterMode.Dither);
pssmFilter.setEnabled(false);
// pssmFilter.setShadowZFadeLength(300);
// pssmFilter.setShadowZExtend(500);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
// fpp.setNumSamples(4);
fpp.addFilter(pssmFilter);
viewPort.addProcessor(fpp);
initInputs();
}
BitmapText infoText;
BitmapText infoText;
private void initInputs() {
/** Write text on the screen (HUD) */
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
@ -195,7 +207,20 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener
inputManager.addMapping("lambdaDown", new KeyTrigger(KeyInput.KEY_J));
inputManager.addMapping("toggleHW", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M));
inputManager.addListener(this, "lambdaUp", "lambdaDown", "toggleHW", "toggle", "ShadowUp", "ShadowDown", "ThicknessUp", "ThicknessDown", "changeFiltering", "switchGroundMat");
inputManager.addMapping("splits", new KeyTrigger(KeyInput.KEY_X));
inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_NUMPAD8));
inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_NUMPAD2));
inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_NUMPAD6));
inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_NUMPAD4));
inputManager.addMapping("fwd", new KeyTrigger(KeyInput.KEY_PGUP));
inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_PGDN));
inputManager.addListener(this, "lambdaUp", "lambdaDown", "toggleHW", "toggle", "ShadowUp", "ShadowDown", "ThicknessUp", "ThicknessDown", "changeFiltering",
"switchGroundMat", "splits", "up", "down", "right", "left", "fwd", "back");
}
private void print(String str) {
@ -246,10 +271,10 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener
break;
case 1:
viewPort.removeProcessor(pssmRenderer);
pssmFilter.setEnabled(true);
pssmFilter.setEnabled(true);
break;
case 2:
pssmFilter.setEnabled(false);
pssmFilter.setEnabled(false);
break;
}
@ -311,5 +336,78 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener
}
}
// if (name.equals("splits") && keyPressed) {
// pssmRenderer.displayFrustum();
// }
if (name.equals("up")) {
up = keyPressed;
}
if (name.equals("down")) {
down = keyPressed;
}
if (name.equals("right")) {
right = keyPressed;
}
if (name.equals("left") ) {
left = keyPressed;
}
if (name.equals("fwd")) {
fwd = keyPressed;
}
if (name.equals("back")) {
back = keyPressed;
}
}
boolean up = false;
boolean down = false;
boolean left = false;
boolean right = false;
boolean fwd = false;
boolean back = false;
float time = 0;
float s = 1f;
@Override
public void simpleUpdate(float tpf) {
if (up) {
Vector3f v = l.getDirection();
v.y += tpf / s;
setDir(v);
}
if (down) {
Vector3f v = l.getDirection();
v.y -= tpf / s;
setDir(v);
}
if (right) {
Vector3f v = l.getDirection();
v.x += tpf / s;
setDir(v);
}
if (left) {
Vector3f v = l.getDirection();
v.x -= tpf / s;
setDir(v);
}
if (fwd) {
Vector3f v = l.getDirection();
v.z += tpf / s;
setDir(v);
}
if (back) {
Vector3f v = l.getDirection();
v.z -= tpf / s;
setDir(v);
}
}
private void setDir(Vector3f v) {
l.setDirection(v);
pssmFilter.setDirection(v);
pssmRenderer.setDirection(v);
}
}

@ -0,0 +1,198 @@
/*
* 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.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.light.SpotLight;
import com.jme3.material.Material;
import com.jme3.math.*;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.shader.VarType;
import com.jme3.shadow.CompareMode;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.shadow.SpotLightShadowFilter;
import com.jme3.shadow.SpotLightShadowRenderer;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.TangentBinormalGenerator;
public class TestSpotLightShadows extends SimpleApplication {
private Vector3f lightTarget = new Vector3f(12, 3.5f, 30);
public static void main(String[] args) {
TestSpotLightShadows app = new TestSpotLightShadows();
app.start();
}
SpotLight spot;
Geometry lightMdl;
public void setupLighting() {
AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(0.3f));
rootNode.addLight(al);
rootNode.setShadowMode(ShadowMode.CastAndReceive);
spot = new SpotLight();
spot.setSpotRange(1000);
spot.setSpotInnerAngle(5f * FastMath.DEG_TO_RAD);
spot.setSpotOuterAngle(10 * FastMath.DEG_TO_RAD);
spot.setPosition(new Vector3f(70.70334f, 34.013165f, 27.1017f));
spot.setDirection(lightTarget.subtract(spot.getPosition()));
spot.setColor(ColorRGBA.White.mult(2));
rootNode.addLight(spot);
// PointLight pl=new PointLight();
// pl.setPosition(new Vector3f(77.70334f, 34.013165f, 27.1017f));
// pl.setRadius(1000);
// pl.setColor(ColorRGBA.White.mult(2));
// rootNode.addLight(pl);
lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
lightMdl.setLocalTranslation(new Vector3f(77.70334f, 34.013165f, 27.1017f));
lightMdl.setLocalScale(5);
rootNode.attachChild(lightMdl);
// DirectionalLight dl = new DirectionalLight();
// dl.setDirection(lightTarget.subtract(new Vector3f(77.70334f, 34.013165f, 27.1017f)));
// dl.setColor(ColorRGBA.White.mult(0.7f));
// rootNode.addLight(dl);
final SpotLightShadowRenderer slsr = new SpotLightShadowRenderer(assetManager, 512);
slsr.setLight(spot);
slsr.setShadowIntensity(0.5f);
slsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
viewPort.addProcessor(slsr);
SpotLightShadowFilter slsf = new SpotLightShadowFilter(assetManager, 512);
slsf.setLight(spot);
slsf.setShadowIntensity(0.5f);
slsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
slsf.setEnabled(false);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(slsf);
viewPort.addProcessor(fpp);
ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, slsr, slsf, guiNode, inputManager, viewPort);
inputManager.addListener(new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("stop") && isPressed) {
stop = !stop;
// slsr.displayFrustum();
System.out.println("pos : " + spot.getPosition());
System.out.println("dir : " + spot.getDirection());
}
}
}, "stop");
inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_1));
}
public void setupFloor() {
Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat);
mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat);
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Diffuse", ColorRGBA.White.clone());
mat.setColor("Ambient", ColorRGBA.White.clone());
// mat.setColor("Specular", ColorRGBA.White.clone());
// mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat);
mat.setFloat("Shininess", 0);
// mat.setBoolean("VertexLighting", true);
Box floor = new Box(Vector3f.ZERO, 50, 1f, 50);
TangentBinormalGenerator.generate(floor);
floor.scaleTextureCoordinates(new Vector2f(5, 5));
Geometry floorGeom = new Geometry("Floor", floor);
floorGeom.setMaterial(mat);
floorGeom.setShadowMode(ShadowMode.Receive);
rootNode.attachChild(floorGeom);
}
public void setupSignpost() {
Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml");
Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m");
// mat.setBoolean("VertexLighting", true);
signpost.setMaterial(mat);
signpost.rotate(0, FastMath.HALF_PI, 0);
signpost.setLocalTranslation(12, 3.5f, 30);
signpost.setLocalScale(4);
signpost.setShadowMode(ShadowMode.CastAndReceive);
TangentBinormalGenerator.generate(signpost);
rootNode.attachChild(signpost);
}
@Override
public void simpleInitApp() {
cam.setLocation(new Vector3f(27.492603f, 29.138166f, -13.232513f));
cam.setRotation(new Quaternion(0.25168246f, -0.10547892f, 0.02760565f, 0.96164864f));
flyCam.setMoveSpeed(30);
setupLighting();
setupFloor();
setupSignpost();
}
float angle;
boolean stop = true;
@Override
public void simpleUpdate(float tpf) {
if (!stop) {
super.simpleUpdate(tpf);
angle += tpf;
angle %= FastMath.TWO_PI;
spot.setPosition(new Vector3f(FastMath.cos(angle) * 30f, 34.013165f, FastMath.sin(angle) * 30f));
lightMdl.setLocalTranslation(spot.getPosition());
spot.setDirection(lightTarget.subtract(spot.getPosition()));
}
}
}
Loading…
Cancel
Save