Adds support for light porbe blending and Oriented box light probes with corrected parallax

shader-nodes-enhancement
Nehon 7 years ago
parent 824e99c96e
commit 83aef82d92
  1. 10
      jme3-core/src/main/java/com/jme3/bounding/Intersection.java
  2. 13
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  3. 21
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  4. 30
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  5. 8
      jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java
  6. 157
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  7. 214
      jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java
  8. 249
      jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java
  9. 107
      jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java
  10. 7
      jme3-core/src/main/java/com/jme3/light/PointLight.java
  11. 33
      jme3-core/src/main/java/com/jme3/light/ProbeArea.java
  12. 102
      jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java
  13. 77
      jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java
  14. 72
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  15. 18
      jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java
  16. 157
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  17. 4
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  18. 0
      jme3-examples/src/main/java/jme3test/collision/Main.java
  19. 0
      jme3-examples/src/main/java/jme3test/light/DlsfError.java
  20. 2
      jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java
  21. 301
      jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java
  22. 4
      jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java
  23. 5
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java

@ -34,6 +34,7 @@ package com.jme3.bounding;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.math.Plane; import com.jme3.math.Plane;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -107,6 +108,15 @@ public final class Intersection {
} }
} }
public static boolean intersect(Camera camera, Vector3f center,float radius){
for (int i = 5; i >= 0; i--) {
if (camera.getWorldPlane(i).pseudoDistance(center) <= -radius) {
return false;
}
}
return true;
}
// private boolean axisTest(float a, float b, float fa, float fb, Vector3f v0, Vector3f v1, ) // private boolean axisTest(float a, float b, float fa, float fb, Vector3f v0, Vector3f v1, )
// private boolean axisTestX01(float a, float b, float fa, float fb, // private boolean axisTestX01(float a, float b, float fa, float fb,
// Vector3f center, Vector3f ext, // Vector3f center, Vector3f ext,

@ -38,14 +38,9 @@ import com.jme3.environment.util.EnvMapUtils;
import com.jme3.light.LightProbe; import com.jme3.light.LightProbe;
import com.jme3.math.ColorRGBA; import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.*;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.texture.FrameBuffer; import com.jme3.texture.*;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace; import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.MipMapGenerator; import com.jme3.util.MipMapGenerator;
@ -119,7 +114,7 @@ public class EnvironmentCamera extends BaseAppState {
private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>(); private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
/** /**
* Creates an EnvironmentCamera with a size of 128 * Creates an EnvironmentCamera with a size of 256
*/ */
public EnvironmentCamera() { public EnvironmentCamera() {
} }
@ -322,7 +317,7 @@ public class EnvironmentCamera extends BaseAppState {
final Camera offCamera = new Camera(mapSize, mapSize); final Camera offCamera = new Camera(mapSize, mapSize);
offCamera.setLocation(worldPos); offCamera.setLocation(worldPos);
offCamera.setAxes(axisX, axisY, axisZ); offCamera.setAxes(axisX, axisY, axisZ);
offCamera.setFrustumPerspective(90f, 1f, 1, 1000); offCamera.setFrustumPerspective(90f, 1f, 0.1f, 1000);
offCamera.setLocation(position); offCamera.setLocation(position);
return offCamera; return offCamera;
} }

@ -32,6 +32,7 @@
package com.jme3.environment; package com.jme3.environment;
import com.jme3.app.Application; import com.jme3.app.Application;
import com.jme3.asset.AssetManager;
import com.jme3.environment.generation.*; import com.jme3.environment.generation.*;
import com.jme3.environment.util.EnvMapUtils; import com.jme3.environment.util.EnvMapUtils;
import com.jme3.light.LightProbe; import com.jme3.light.LightProbe;
@ -204,6 +205,26 @@ public class LightProbeFactory {
} }
} }
/**
* For debuging porpose only
* Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
*
* @param manager the asset manager
* @return a debug node
*/
public static Node getDebugGui(AssetManager manager, LightProbe probe) {
if (!probe.isReady()) {
throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
}
Node debugNode = new Node("debug gui probe");
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(probe.getPrefilteredEnvMap(), manager);
debugNode.attachChild(debugPfemCm);
debugPfemCm.setLocalTranslation(520, 0, 0);
return debugNode;
}
/** /**
* An inner class to keep the state of a generation process * An inner class to keep the state of a generation process
*/ */

@ -34,9 +34,8 @@ package com.jme3.environment.util;
import com.jme3.app.Application; import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState; import com.jme3.app.state.BaseAppState;
import com.jme3.bounding.BoundingSphere; import com.jme3.bounding.BoundingSphere;
import com.jme3.light.*;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.light.LightProbe;
import com.jme3.light.Light;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Node; import com.jme3.scene.Node;
@ -68,7 +67,7 @@ public class LightsDebugState extends BaseAppState {
@Override @Override
protected void initialize(Application app) { protected void initialize(Application app) {
debugNode = new Node("Environment debug Node"); debugNode = new Node("Environment debug Node");
Sphere s = new Sphere(16, 16, 1); Sphere s = new Sphere(16, 16, 0.15f);
debugGeom = new Geometry("debugEnvProbe", s); debugGeom = new Geometry("debugEnvProbe", s);
debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md"); debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md");
debugGeom.setMaterial(debugMaterial); debugGeom.setMaterial(debugMaterial);
@ -80,6 +79,16 @@ public class LightsDebugState extends BaseAppState {
@Override @Override
public void update(float tpf) { public void update(float tpf) {
if(!isEnabled()){
return;
}
updateLights(scene);
debugNode.updateLogicalState(tpf);
debugNode.updateGeometricState();
cleanProbes();
}
public void updateLights(Spatial scene) {
for (Light light : scene.getWorldLightList()) { for (Light light : scene.getWorldLightList()) {
switch (light.getType()) { switch (light.getType()) {
@ -101,16 +110,18 @@ public class LightsDebugState extends BaseAppState {
m.setTexture("CubeMap", probe.getPrefilteredEnvMap()); m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
} }
n.setLocalTranslation(probe.getPosition()); n.setLocalTranslation(probe.getPosition());
n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius()); n.getChild(1).setLocalScale(probe.getArea().getRadius());
break; break;
default: default:
break; break;
} }
} }
debugNode.updateLogicalState(tpf); if( scene instanceof Node){
debugNode.updateGeometricState(); Node n = (Node)scene;
cleanProbes(); for (Spatial spatial : n.getChildren()) {
updateLights(spatial);
}
}
} }
/** /**
@ -138,6 +149,9 @@ public class LightsDebugState extends BaseAppState {
@Override @Override
public void render(RenderManager rm) { public void render(RenderManager rm) {
if(!isEnabled()){
return;
}
rm.renderScene(debugNode, getApplication().getViewPort()); rm.renderScene(debugNode, getApplication().getViewPort());
} }

@ -43,10 +43,10 @@ public final class DefaultLightFilter implements LightFilter {
private Camera camera; private Camera camera;
private final HashSet<Light> processedLights = new HashSet<Light>(); private final HashSet<Light> processedLights = new HashSet<Light>();
private final LightProbeBlendingStrategy probeBlendStrat; private LightProbeBlendingStrategy probeBlendStrat;
public DefaultLightFilter() { public DefaultLightFilter() {
probeBlendStrat = new BasicProbeBlendingStrategy(); probeBlendStrat = new WeightedProbeBlendingStrategy();
} }
public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) { public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) {
@ -114,4 +114,8 @@ public final class DefaultLightFilter implements LightFilter {
} }
} }
public void setLightProbeBlendingStrategy(LightProbeBlendingStrategy strategy){
probeBlendStrat = strategy;
}
} }

@ -31,24 +31,16 @@
*/ */
package com.jme3.light; package com.jme3.light;
import com.jme3.asset.AssetManager; import com.jme3.bounding.*;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume;
import com.jme3.environment.EnvironmentCamera; import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory; import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.util.EnvMapUtils; import com.jme3.export.*;
import com.jme3.export.InputCapsule; import com.jme3.math.*;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap; import com.jme3.texture.TextureCubeMap;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -65,9 +57,9 @@ import java.util.logging.Logger;
* To compute them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} * To compute them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
* and {@link EnvironmentCamera}. * and {@link EnvironmentCamera}.
* *
* The light probe has an area of effect that is a bounding volume centered on its position. (for now only Bounding spheres are supported). * The light probe has an area of effect centered on its position. It can have a Spherical area or an Oriented Box area
* *
* A LightProbe will only be taken into account when it's marked as ready. * A LightProbe will only be taken into account when it's marked as ready and enabled.
* A light probe is ready when it has valid environment map data set. * A light probe is ready when it has valid environment map data set.
* Note that you should never call setReady yourself. * Note that you should never call setReady yourself.
* *
@ -78,15 +70,20 @@ import java.util.logging.Logger;
public class LightProbe extends Light implements Savable { public class LightProbe extends Light implements Savable {
private static final Logger logger = Logger.getLogger(LightProbe.class.getName()); private static final Logger logger = Logger.getLogger(LightProbe.class.getName());
public static final Matrix4f FALLBACK_MATRIX = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1);
private Vector3f[] shCoeffs; private Vector3f[] shCoeffs;
private TextureCubeMap prefilteredEnvMap; private TextureCubeMap prefilteredEnvMap;
private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO); private ProbeArea area = new SphereProbeArea(Vector3f.ZERO, 1.0f);
private boolean ready = false; private boolean ready = false;
private Vector3f position = new Vector3f(); private Vector3f position = new Vector3f();
private Node debugNode;
private int nbMipMaps; private int nbMipMaps;
public enum AreaType{
Spherical,
OrientedBox
}
/** /**
* Empty constructor used for serialization. * Empty constructor used for serialization.
* You should never call it, use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} instead * You should never call it, use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} instead
@ -111,6 +108,52 @@ public class LightProbe extends Light implements Savable {
this.prefilteredEnvMap = prefileteredEnvMap; this.prefilteredEnvMap = prefileteredEnvMap;
} }
/**
* Returns the data to send to the shader.
* This is a column major matrix that is not a classic transform matrix, it's laid out in a particular way
// 3x3 rot mat|
// 0 1 2 | 3
// 0 | ax bx cx | px | )
// 1 | ay by cy | py | probe position
// 2 | az bz cz | pz | )
// --|----------|
// 3 | sx sy sz sp | -> 1/probe radius + nbMipMaps
// --scale--
* <p>
* (ax, ay, az) is the pitch rotation axis
* (bx, by, bz) is the yaw rotation axis
* (cx, cy, cz) is the roll rotation axis
* Like in a standard 3x3 rotation matrix.
* It's also the valid rotation matrix of the probe in world space.
* Note that for the Spherical Probe area this part is a 3x3 identity matrix.
* <p>
* (px, py, pz) is the position of the center of the probe in world space
* Like in a valid 4x4 transform matrix.
* <p>
* (sx, sy, sy) is the extent of the probe ( the scale )
* In a standard transform matrix the scale is applied to the rotation matrix part.
* In the shader we need the rotation and the scale to be separated, doing this avoid to extract
* the scale from a classic transform matrix in the shader
* <p>
* (sp) is a special entry, it contains the packed number of mip maps of the probe and the inverse radius for the probe.
* since the inverse radius in lower than 1, it's packed in the decimal part of the float.
* The number of mip maps is packed in the integer part of the float.
* (ie: for 6 mip maps and a radius of 3, sp= 6.3333333)
* <p>
* The radius is obvious for a SphereProbeArea,
* but in the case of a OrientedBoxProbeArea it's the max of the extent vector's components.
*/
public Matrix4f getUniformMatrix(){
Matrix4f mat = area.getUniformMatrix();
// setting the (sp) entry of the matrix
mat.m33 = nbMipMaps + 1f / area.getRadius();
return mat;
}
@Override @Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);
@ -118,7 +161,7 @@ public class LightProbe extends Light implements Savable {
oc.write(shCoeffs, "shCoeffs", null); oc.write(shCoeffs, "shCoeffs", null);
oc.write(prefilteredEnvMap, "prefilteredEnvMap", null); oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
oc.write(position, "position", null); oc.write(position, "position", null);
oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); oc.write(area, "area", new SphereProbeArea(Vector3f.ZERO, 1.0f));
oc.write(ready, "ready", false); oc.write(ready, "ready", false);
oc.write(nbMipMaps, "nbMipMaps", 0); oc.write(nbMipMaps, "nbMipMaps", 0);
} }
@ -130,7 +173,13 @@ public class LightProbe extends Light implements Savable {
prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null); prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
position = (Vector3f) ic.readSavable("position", null); position = (Vector3f) ic.readSavable("position", null);
bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); area = (ProbeArea)ic.readSavable("area", null);
if(area == null) {
// retro compat
BoundingSphere bounds = (BoundingSphere) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
area = new SphereProbeArea(bounds.getCenter(), bounds.getRadius());
}
area.setCenter(position);
nbMipMaps = ic.readInt("nbMipMaps", 0); nbMipMaps = ic.readInt("nbMipMaps", 0);
ready = ic.readBoolean("ready", false); ready = ic.readBoolean("ready", false);
@ -146,12 +195,15 @@ public class LightProbe extends Light implements Savable {
} }
} }
/** /**
* returns the bounding volume of this LightProbe * returns the bounding volume of this LightProbe
* @return a bounding volume. * @return a bounding volume.
* @deprecated use {@link LightProbe#getArea()}
*/ */
@Deprecated
public BoundingVolume getBounds() { public BoundingVolume getBounds() {
return bounds; return new BoundingSphere(((SphereProbeArea)area).getRadius(), ((SphereProbeArea)area).getCenter());
} }
/** /**
@ -159,12 +211,33 @@ public class LightProbe extends Light implements Savable {
* Note that for now only BoundingSphere is supported and this method will * Note that for now only BoundingSphere is supported and this method will
* throw an UnsupportedOperationException with any other BoundingVolume type * throw an UnsupportedOperationException with any other BoundingVolume type
* @param bounds the bounds of the LightProbe * @param bounds the bounds of the LightProbe
* @deprecated
*/ */
@Deprecated
public void setBounds(BoundingVolume bounds) { public void setBounds(BoundingVolume bounds) {
if( bounds.getType()!= BoundingVolume.Type.Sphere){
throw new UnsupportedOperationException("For not only BoundingSphere are suported for LightProbe");
} }
this.bounds = bounds;
public ProbeArea getArea() {
return area;
}
public void setAreaType(AreaType type){
switch (type){
case Spherical:
area = new SphereProbeArea(Vector3f.ZERO, 1.0f);
break;
case OrientedBox:
area = new OrientedBoxProbeArea(new Transform());
area.setCenter(position);
break;
}
}
public AreaType getAreaType(){
if(area instanceof SphereProbeArea){
return AreaType.Spherical;
}
return AreaType.OrientedBox;
} }
/** /**
@ -186,27 +259,6 @@ public class LightProbe extends Light implements Savable {
this.ready = ready; this.ready = ready;
} }
/**
* For debuging porpose only
* Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
*
* @param manager the asset manager
* @return a debug node
*/
public Node getDebugGui(AssetManager manager) {
if (!ready) {
throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
}
if (debugNode == null) {
debugNode = new Node("debug gui probe");
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager);
debugNode.attachChild(debugPfemCm);
debugPfemCm.setLocalTranslation(520, 0, 0);
}
return debugNode;
}
public Vector3f[] getShCoeffs() { public Vector3f[] getShCoeffs() {
return shCoeffs; return shCoeffs;
} }
@ -229,7 +281,7 @@ public class LightProbe extends Light implements Savable {
*/ */
public void setPosition(Vector3f position) { public void setPosition(Vector3f position) {
this.position.set(position); this.position.set(position);
getBounds().setCenter(position); area.setCenter(position);
} }
public int getNbMipMaps() { public int getNbMipMaps() {
@ -242,12 +294,17 @@ public class LightProbe extends Light implements Savable {
@Override @Override
public boolean intersectsBox(BoundingBox box, TempVars vars) { public boolean intersectsBox(BoundingBox box, TempVars vars) {
return getBounds().intersectsBoundingBox(box); return area.intersectsBox(box, vars);
} }
@Override @Override
public boolean intersectsFrustum(Camera camera, TempVars vars) { public boolean intersectsFrustum(Camera camera, TempVars vars) {
return camera.contains(bounds) != Camera.FrustumIntersect.Outside; return area.intersectsFrustum(camera, vars);
}
@Override
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
return area.intersectsSphere(sphere, vars);
} }
@Override @Override
@ -267,14 +324,8 @@ public class LightProbe extends Light implements Savable {
@Override @Override
public String toString() { public String toString() {
return "Light Probe : " + name + " at " + position + " / " + bounds; return "Light Probe : " + name + " at " + position + " / " + area;
} }
@Override
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
return getBounds().intersectsSphere(sphere);
}
} }

@ -1,214 +0,0 @@
/*
* Copyright (c) 2009-2015 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.light;
import com.jme3.bounding.BoundingSphere;
import com.jme3.post.SceneProcessor;
import com.jme3.profile.AppProfiler;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.texture.FrameBuffer;
import com.jme3.util.TempVars;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* this processor allows to blend several light probes maps together according to a Point of Interest.
* This is all based on this article by Sebastien lagarde
* https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
* @author Nehon
*/
public class LightProbeBlendingProcessor implements SceneProcessor {
private ViewPort viewPort;
private LightFilter prevFilter;
private RenderManager renderManager;
private LightProbe probe = new LightProbe();
private Spatial poi;
private AppProfiler prof;
public LightProbeBlendingProcessor(Spatial poi) {
this.poi = poi;
}
@Override
public void initialize(RenderManager rm, ViewPort vp) {
viewPort = vp;
renderManager = rm;
prevFilter = rm.getLightFilter();
rm.setLightFilter(new PoiLightProbeLightFilter(this));
}
@Override
public void reshape(ViewPort vp, int w, int h) {
}
@Override
public boolean isInitialized() {
return viewPort != null;
}
@Override
public void preFrame(float tpf) {
}
/** 1. For POI take a spatial in the constructor and make all calculation against its world pos
* - Alternatively compute an arbitrary POI by casting rays from the camera
* (one in the center and one for each corner and take the median point)
* 2. Take the 4 most weighted probes for default. Maybe allow the user to change this
* 3. For the inner influence radius take half of the radius for a start we'll see then how to change this.
*
*/
@Override
public void postQueue(RenderQueue rq) {
List<BlendFactor> blendFactors = new ArrayList<BlendFactor>();
float sumBlendFactors = computeBlendFactors(blendFactors);
//Sort blend factors according to their weight
Collections.sort(blendFactors);
//normalize blend factors;
float normalizer = 1f / sumBlendFactors;
for (BlendFactor blendFactor : blendFactors) {
blendFactor.ndf *= normalizer;
// System.err.println(blendFactor);
}
//for now just pick the first probe.
if(!blendFactors.isEmpty()){
probe = blendFactors.get(0).lightProbe;
}else{
probe = null;
}
}
private float computeBlendFactors(List<BlendFactor> blendFactors) {
float sumBlendFactors = 0;
for (Spatial scene : viewPort.getScenes()) {
for (Light light : scene.getWorldLightList()) {
if(light.getType() == Light.Type.Probe){
LightProbe p = (LightProbe)light;
TempVars vars = TempVars.get();
boolean intersect = p.intersectsFrustum(viewPort.getCamera(), vars);
vars.release();
//check if the probe is inside the camera frustum
if(intersect){
//is the poi inside the bounds of this probe
if(poi.getWorldBound().intersects(p.getBounds())){
//computing the distance as we need it to check if th epoi in in the inner radius and later to compute the weight
float outerRadius = ((BoundingSphere)p.getBounds()).getRadius();
float innerRadius = outerRadius * 0.5f;
float distance = p.getBounds().getCenter().distance(poi.getWorldTranslation());
// if the poi in inside the inner range of this probe, then this probe is the only one that matters.
if( distance < innerRadius ){
blendFactors.clear();
blendFactors.add(new BlendFactor(p, 1.0f));
return 1.0f;
}
//else we need to compute the weight of this probe and collect it for blending
float ndf = (distance - innerRadius) / (outerRadius - innerRadius);
sumBlendFactors += ndf;
blendFactors.add(new BlendFactor(p, ndf));
}
}
}
}
}
return sumBlendFactors;
}
@Override
public void postFrame(FrameBuffer out) {
}
@Override
public void cleanup() {
viewPort = null;
renderManager.setLightFilter(prevFilter);
}
public void populateProbe(LightList lightList){
if(probe != null && probe.isReady()){
lightList.add(probe);
}
}
public Spatial getPoi() {
return poi;
}
public void setPoi(Spatial poi) {
this.poi = poi;
}
@Override
public void setProfiler(AppProfiler profiler) {
this.prof = profiler;
}
private class BlendFactor implements Comparable<BlendFactor>{
LightProbe lightProbe;
float ndf;
public BlendFactor(LightProbe lightProbe, float ndf) {
this.lightProbe = lightProbe;
this.ndf = ndf;
}
@Override
public String toString() {
return "BlendFactor{" + "lightProbe=" + lightProbe + ", ndf=" + ndf + '}';
}
@Override
public int compareTo(BlendFactor o) {
if(o.ndf > ndf){
return -1;
}else if(o.ndf < ndf){
return 1;
}
return 0;
}
}
}

@ -0,0 +1,249 @@
package com.jme3.light;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.export.*;
import com.jme3.math.*;
import com.jme3.renderer.Camera;
import com.jme3.util.TempVars;
import java.io.IOException;
public class OrientedBoxProbeArea implements ProbeArea {
private Transform transform = new Transform();
/**
* @see LightProbe#getUniformMatrix()
* for this Area type, the matrix is updated when the probe is transformed,
* and its data is used for bound checks in the light culling process.
*/
private Matrix4f uniformMatrix = new Matrix4f();
public OrientedBoxProbeArea() {
}
public OrientedBoxProbeArea(Transform transform) {
transform.set(transform);
updateMatrix();
}
@Override
public boolean intersectsBox(BoundingBox box, TempVars vars) {
Vector3f axis1 = getScaledAxis(0, vars.vect1);
Vector3f axis2 = getScaledAxis(1, vars.vect2);
Vector3f axis3 = getScaledAxis(2, vars.vect3);
Vector3f tn = vars.vect4;
Plane p = vars.plane;
Vector3f c = box.getCenter();
p.setNormal(0, 0, -1);
p.setConstant(-(c.z + box.getZExtent()));
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
p.setNormal(0, 0, 1);
p.setConstant(c.z - box.getZExtent());
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
p.setNormal(0, -1, 0);
p.setConstant(-(c.y + box.getYExtent()));
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
p.setNormal(0, 1, 0);
p.setConstant(c.y - box.getYExtent());
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
p.setNormal(-1, 0, 0);
p.setConstant(-(c.x + box.getXExtent()));
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
p.setNormal(1, 0, 0);
p.setConstant(c.x - box.getXExtent());
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
return true;
}
@Override
public float getRadius() {
return Math.max(Math.max(transform.getScale().x, transform.getScale().y), transform.getScale().z);
}
@Override
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
Vector3f closestPoint = getClosestPoint(vars, sphere.getCenter());
// check if the point intersects with the sphere bound
if (sphere.intersects(closestPoint)) {
return true;
}
return false;
}
@Override
public boolean intersectsFrustum(Camera camera, TempVars vars) {
// extract the scaled axis
// this allows a small optimization.
Vector3f axis1 = getScaledAxis(0, vars.vect1);
Vector3f axis2 = getScaledAxis(1, vars.vect2);
Vector3f axis3 = getScaledAxis(2, vars.vect3);
Vector3f tn = vars.vect4;
for (int i = 5; i >= 0; i--) {
Plane p = camera.getWorldPlane(i);
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
}
return true;
}
private Vector3f getScaledAxis(int index, Vector3f store) {
Matrix4f u = uniformMatrix;
float x = 0, y = 0, z = 0, s = 1;
switch (index) {
case 0:
x = u.m00;
y = u.m10;
z = u.m20;
s = u.m30;
break;
case 1:
x = u.m01;
y = u.m11;
z = u.m21;
s = u.m31;
break;
case 2:
x = u.m02;
y = u.m12;
z = u.m22;
s = u.m32;
}
return store.set(x, y, z).multLocal(s);
}
private boolean insidePlane(Plane p, Vector3f axis1, Vector3f axis2, Vector3f axis3, Vector3f tn) {
// transform the plane normal in the box local space.
tn.set(axis1.dot(p.getNormal()), axis2.dot(p.getNormal()), axis3.dot(p.getNormal()));
// distance check
float radius = FastMath.abs(tn.x) +
FastMath.abs(tn.y) +
FastMath.abs(tn.z);
float distance = p.pseudoDistance(transform.getTranslation());
if (distance < -radius) {
return false;
}
return true;
}
private Vector3f getClosestPoint(TempVars vars, Vector3f point) {
// non normalized direction
Vector3f dir = vars.vect2.set(point).subtractLocal(transform.getTranslation());
// initialize the closest point with box center
Vector3f closestPoint = vars.vect3.set(transform.getTranslation());
//store extent in an array
float[] r = vars.fWdU;
r[0] = transform.getScale().x;
r[1] = transform.getScale().y;
r[2] = transform.getScale().z;
// computing closest point to sphere center
for (int i = 0; i < 3; i++) {
// extract the axis from the 3x3 matrix
Vector3f axis = getScaledAxis(i, vars.vect1);
// nomalize (here we just divide by the extent
axis.divideLocal(r[i]);
// distance to the closest point on this axis.
float d = FastMath.clamp(dir.dot(axis), -r[i], r[i]);
closestPoint.addLocal(vars.vect4.set(axis).multLocal(d));
}
return closestPoint;
}
private void updateMatrix() {
TempVars vars = TempVars.get();
Matrix3f r = vars.tempMat3;
Matrix4f u = uniformMatrix;
transform.getRotation().toRotationMatrix(r);
u.m00 = r.get(0,0);
u.m10 = r.get(1,0);
u.m20 = r.get(2,0);
u.m01 = r.get(0,1);
u.m11 = r.get(1,1);
u.m21 = r.get(2,1);
u.m02 = r.get(0,2);
u.m12 = r.get(1,2);
u.m22 = r.get(2,2);
//scale
u.m30 = transform.getScale().x;
u.m31 = transform.getScale().y;
u.m32 = transform.getScale().z;
//position
u.m03 = transform.getTranslation().x;
u.m13 = transform.getTranslation().y;
u.m23 = transform.getTranslation().z;
vars.release();
}
public Matrix4f getUniformMatrix() {
return uniformMatrix;
}
public Vector3f getExtent() {
return transform.getScale();
}
public void setExtent(Vector3f extent) {
transform.setScale(extent);
updateMatrix();
}
public Vector3f getCenter() {
return transform.getTranslation();
}
public void setCenter(Vector3f center) {
transform.setTranslation(center);
updateMatrix();
}
public Quaternion getRotation() {
return transform.getRotation();
}
public void setRotation(Quaternion rotation) {
transform.setRotation(rotation);
updateMatrix();
}
@Override
protected OrientedBoxProbeArea clone() throws CloneNotSupportedException {
return new OrientedBoxProbeArea(transform);
}
@Override
public void write(JmeExporter e) throws IOException {
OutputCapsule oc = e.getCapsule(this);
oc.write(transform, "transform", new Transform());
}
@Override
public void read(JmeImporter i) throws IOException {
InputCapsule ic = i.getCapsule(this);
transform = (Transform) ic.readSavable("transform", new Transform());
updateMatrix();
}
}

@ -1,107 +0,0 @@
/*
* Copyright (c) 2009-2015 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.light;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume;
import com.jme3.renderer.Camera;
import com.jme3.scene.Geometry;
import com.jme3.util.TempVars;
import java.util.HashSet;
public final class PoiLightProbeLightFilter implements LightFilter {
private Camera camera;
private final HashSet<Light> processedLights = new HashSet<Light>();
private final LightProbeBlendingProcessor processor;
public PoiLightProbeLightFilter(LightProbeBlendingProcessor processor) {
this.processor = processor;
}
@Override
public void setCamera(Camera camera) {
this.camera = camera;
for (Light light : processedLights) {
light.frustumCheckNeeded = true;
}
}
@Override
public void filterLights(Geometry geometry, LightList filteredLightList) {
TempVars vars = TempVars.get();
try {
LightList worldLights = geometry.getWorldLightList();
for (int i = 0; i < worldLights.size(); i++) {
Light light = worldLights.get(i);
if (light.getType() == Light.Type.Probe) {
continue;
}
if (light.frustumCheckNeeded) {
processedLights.add(light);
light.frustumCheckNeeded = false;
light.intersectsFrustum = light.intersectsFrustum(camera, vars);
}
if (!light.intersectsFrustum) {
continue;
}
BoundingVolume bv = geometry.getWorldBound();
if (bv instanceof BoundingBox) {
if (!light.intersectsBox((BoundingBox) bv, vars)) {
continue;
}
} else if (bv instanceof BoundingSphere) {
if (!Float.isInfinite(((BoundingSphere) bv).getRadius())) {
if (!light.intersectsSphere((BoundingSphere) bv, vars)) {
continue;
}
}
}
filteredLightList.add(light);
}
processor.populateProbe(filteredLightList);
} finally {
vars.release();
}
}
}

@ -212,12 +212,7 @@ public class PointLight extends Light {
if (this.radius == 0) { if (this.radius == 0) {
return true; return true;
} else { } else {
for (int i = 5; i >= 0; i--) { return Intersection.intersect(camera, position, radius);
if (camera.getWorldPlane(i).pseudoDistance(position) <= -radius) {
return false;
}
}
return true;
} }
} }

@ -0,0 +1,33 @@
package com.jme3.light;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.export.Savable;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.util.TempVars;
public interface ProbeArea extends Savable, Cloneable{
public void setCenter(Vector3f center);
public float getRadius();
public Matrix4f getUniformMatrix();
/**
* @see Light#intersectsBox(BoundingBox, TempVars)
*/
public boolean intersectsBox(BoundingBox box, TempVars vars);
/**
* @see Light#intersectsSphere(BoundingSphere, TempVars)
*/
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars);
/**
* @see Light#intersectsFrustum(Camera, TempVars)
*/
public abstract boolean intersectsFrustum(Camera camera, TempVars vars);
}

@ -0,0 +1,102 @@
package com.jme3.light;
import com.jme3.bounding.*;
import com.jme3.export.*;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.util.logging.Level;
public class SphereProbeArea implements ProbeArea {
private Vector3f center = new Vector3f();
private float radius = 1;
private Matrix4f uniformMatrix = new Matrix4f();
public SphereProbeArea() {
}
public SphereProbeArea(Vector3f center, float radius) {
this.center.set(center);
this.radius = radius;
updateMatrix();
}
public Vector3f getCenter() {
return center;
}
public void setCenter(Vector3f center) {
this.center.set(center);
updateMatrix();
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
updateMatrix();
}
@Override
public Matrix4f getUniformMatrix() {
return uniformMatrix;
}
private void updateMatrix(){
//position
uniformMatrix.m03 = center.x;
uniformMatrix.m13 = center.y;
uniformMatrix.m23 = center.z;
}
@Override
public boolean intersectsBox(BoundingBox box, TempVars vars) {
return Intersection.intersect(box, center, radius);
}
@Override
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
return Intersection.intersect(sphere, center, radius);
}
@Override
public boolean intersectsFrustum(Camera camera, TempVars vars) {
return Intersection.intersect(camera, center, radius);
}
@Override
public String toString() {
return "SphereProbeArea{" +
"center=" + center +
", radius=" + radius +
'}';
}
@Override
protected SphereProbeArea clone() throws CloneNotSupportedException {
return new SphereProbeArea(center, radius);
}
@Override
public void write(JmeExporter e) throws IOException {
OutputCapsule oc = e.getCapsule(this);
oc.write(center, "center", new Vector3f());
oc.write(radius, "radius", 1);
}
@Override
public void read(JmeImporter i) throws IOException {
InputCapsule ic = i.getCapsule(this);
center = (Vector3f) ic.readSavable("center", new Vector3f());
radius = ic.readFloat("radius", 1);
updateMatrix();
}
}

@ -0,0 +1,77 @@
/*
* Copyright (c) 2009-2015 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.light;
import com.jme3.scene.Geometry;
import java.util.ArrayList;
import java.util.List;
/**
* This strategy returns the 3 closest probe from the rendered object.
* <p>
* Image based lighting will be blended between those probes in the shader according to their distance and range.
*
* @author Nehon
*/
public class WeightedProbeBlendingStrategy implements LightProbeBlendingStrategy {
private final static int MAX_PROBES = 3;
List<LightProbe> lightProbes = new ArrayList<LightProbe>();
@Override
public void registerProbe(LightProbe probe) {
lightProbes.add(probe);
}
@Override
public void populateProbes(Geometry g, LightList lightList) {
if (!lightProbes.isEmpty()) {
//The 3 first probes are the closest to the geometry since the
//light list is sorted according to the distance to the geom.
int addedProbes = 0;
for (LightProbe p : lightProbes) {
if (p.isReady() && p.isEnabled()) {
lightList.add(p);
addedProbes ++;
}
if (addedProbes == MAX_PROBES) {
break;
}
}
//clearing the list for next pass.
lightProbes.clear();
}
}
}

@ -42,17 +42,17 @@ import com.jme3.scene.Geometry;
import com.jme3.shader.*; import com.jme3.shader.*;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.util.EnumSet; import java.util.*;
public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic { public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic {
private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING"; private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING";
private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS"; private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS";
private static final String DEFINE_INDIRECT_LIGHTING = "INDIRECT_LIGHTING"; private static final String DEFINE_NB_PROBES = "NB_PROBES";
private static final RenderState ADDITIVE_LIGHT = new RenderState(); private static final RenderState ADDITIVE_LIGHT = new RenderState();
private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
private LightProbe lightProbe = null; private List<LightProbe> lightProbes = new ArrayList<>(3);
static { static {
ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive); ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
@ -61,13 +61,13 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
private final int singlePassLightingDefineId; private final int singlePassLightingDefineId;
private final int nbLightsDefineId; private final int nbLightsDefineId;
private final int indirectLightingDefineId; private final int nbProbesDefineId;
public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) { public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) {
super(techniqueDef); super(techniqueDef);
singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean); singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int); nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
indirectLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_INDIRECT_LIGHTING, VarType.Boolean); nbProbesDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_PROBES, VarType.Int);
} }
@Override @Override
@ -81,12 +81,9 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
//Though the second pass should not render IBL as it is taken care of on first pass like ambient light in phong lighting. //Though the second pass should not render IBL as it is taken care of on first pass like ambient light in phong lighting.
//We cannot change the define between passes and the old technique, and for some reason the code fails on mac (renders nothing). //We cannot change the define between passes and the old technique, and for some reason the code fails on mac (renders nothing).
if(lights != null) { if(lights != null) {
lightProbe = extractIndirectLights(lights, false); lightProbes.clear();
if (lightProbe == null) { extractIndirectLights(lights, false);
defines.set(indirectLightingDefineId, false); defines.set(nbProbesDefineId, lightProbes.size());
} else {
defines.set(indirectLightingDefineId, true);
}
} }
return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines); return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
@ -113,35 +110,44 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
Uniform lightData = shader.getUniform("g_LightData"); Uniform lightData = shader.getUniform("g_LightData");
lightData.setVector4Length(numLights * 3);//8 lights * max 3 lightData.setVector4Length(numLights * 3);//8 lights * max 3
Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
// Matrix4f
Uniform lightProbeData = shader.getUniform("g_LightProbeData"); Uniform lightProbeData = shader.getUniform("g_LightProbeData");
lightProbeData.setVector4Length(1); Uniform lightProbeData2 = shader.getUniform("g_LightProbeData2");
Uniform lightProbeData3 = shader.getUniform("g_LightProbeData3");
//TODO These 2 uniforms should be packed in an array, to be able to have several probes and blend between them.
Uniform shCoeffs = shader.getUniform("g_ShCoeffs"); Uniform shCoeffs = shader.getUniform("g_ShCoeffs");
Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap"); Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
Uniform shCoeffs2 = shader.getUniform("g_ShCoeffs2");
Uniform lightProbePemMap2 = shader.getUniform("g_PrefEnvMap2");
Uniform shCoeffs3 = shader.getUniform("g_ShCoeffs3");
Uniform lightProbePemMap3 = shader.getUniform("g_PrefEnvMap3");
lightProbe = null; lightProbes.clear();
if (startIndex != 0) { if (startIndex != 0) {
// apply additive blending for 2nd and future passes // apply additive blending for 2nd and future passes
rm.getRenderer().applyRenderState(ADDITIVE_LIGHT); rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
}else{ }else{
lightProbe = extractIndirectLights(lightList,true); extractIndirectLights(lightList,true);
ambientColor.setValue(VarType.Vector4, ambientLightColor); ambientColor.setValue(VarType.Vector4, ambientLightColor);
} }
//If there is a lightProbe in the list we force its render on the first pass //If there is a lightProbe in the list we force its render on the first pass
if(lightProbe != null){ if (!lightProbes.isEmpty()) {
BoundingSphere s = (BoundingSphere)lightProbe.getBounds(); LightProbe lightProbe = lightProbes.get(0);
lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / s.getRadius() + lightProbe.getNbMipMaps(), 0); lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData, shCoeffs, lightProbePemMap, lightProbe);
shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs()); if (lightProbes.size() > 1) {
//assigning new texture indexes lightProbe = lightProbes.get(1);
int pemUnit = lastTexUnit++; lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData2, shCoeffs2, lightProbePemMap2, lightProbe);
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap()); }
lightProbePemMap.setValue(VarType.Int, pemUnit); if (lightProbes.size() > 2) {
lightProbe = lightProbes.get(2);
setProbeData(rm, lastTexUnit, lightProbeData3, shCoeffs3, lightProbePemMap3, lightProbe);
}
} else { } else {
//Disable IBL for this pass //Disable IBL for this pass
lightProbeData.setVector4InArray(0,0,0,-1, 0); lightProbeData.setValue(VarType.Matrix4, LightProbe.FALLBACK_MATRIX);
} }
int lightDataIndex = 0; int lightDataIndex = 0;
@ -222,6 +228,18 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
return curIndex; return curIndex;
} }
private int setProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeData, Uniform shCoeffs, Uniform lightProbePemMap, LightProbe lightProbe) {
lightProbeData.setValue(VarType.Matrix4, lightProbe.getUniformMatrix());
//setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / area.getRadius() + lightProbe.getNbMipMaps(), 0);
shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs());
//assigning new texture indexes
int pemUnit = lastTexUnit++;
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
lightProbePemMap.setValue(VarType.Int, pemUnit);
return lastTexUnit;
}
@Override @Override
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
int nbRenderedLights = 0; int nbRenderedLights = 0;
@ -241,9 +259,8 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
return; return;
} }
protected LightProbe extractIndirectLights(LightList lightList, boolean removeLights) { protected void extractIndirectLights(LightList lightList, boolean removeLights) {
ambientLightColor.set(0, 0, 0, 1); ambientLightColor.set(0, 0, 0, 1);
LightProbe probe = null;
for (int j = 0; j < lightList.size(); j++) { for (int j = 0; j < lightList.size(); j++) {
Light l = lightList.get(j); Light l = lightList.get(j);
if (l instanceof AmbientLight) { if (l instanceof AmbientLight) {
@ -254,7 +271,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
} }
} }
if (l instanceof LightProbe) { if (l instanceof LightProbe) {
probe = (LightProbe)l; lightProbes.add((LightProbe) l);
if(removeLights){ if(removeLights){
lightList.remove(l); lightList.remove(l);
j--; j--;
@ -262,6 +279,5 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
} }
} }
ambientLightColor.a = 1.0f; ambientLightColor.a = 1.0f;
return probe;
} }
} }

@ -42,10 +42,20 @@ import java.nio.FloatBuffer;
public class WireFrustum extends Mesh { public class WireFrustum extends Mesh {
public WireFrustum(Vector3f[] points){ public WireFrustum(Vector3f[] points){
initGeom(this, points);
}
public static Mesh makeFrustum(Vector3f[] points){
Mesh m = new Mesh();
initGeom(m, points);
return m;
}
private static void initGeom(Mesh m, Vector3f[] points) {
if (points != null) if (points != null)
setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points)); m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
setBuffer(Type.Index, 2, m.setBuffer(Type.Index, 2,
new short[]{ new short[]{
0, 1, 0, 1,
1, 2, 1, 2,
@ -63,8 +73,8 @@ public class WireFrustum extends Mesh {
3, 7, 3, 7,
} }
); );
getBuffer(Type.Index).setUsage(Usage.Static); m.getBuffer(Type.Index).setUsage(Usage.Static);
setMode(Mode.Lines); m.setMode(Mode.Lines);
} }
public void update(Vector3f[] points){ public void update(Vector3f[] points){

@ -3,7 +3,6 @@
#import "Common/ShaderLib/Parallax.glsllib" #import "Common/ShaderLib/Parallax.glsllib"
#import "Common/ShaderLib/Lighting.glsllib" #import "Common/ShaderLib/Lighting.glsllib"
varying vec2 texCoord; varying vec2 texCoord;
#ifdef SEPARATE_TEXCOORD #ifdef SEPARATE_TEXCOORD
varying vec2 texCoord2; varying vec2 texCoord2;
@ -12,7 +11,6 @@ varying vec2 texCoord;
varying vec4 Color; varying vec4 Color;
uniform vec4 g_LightData[NB_LIGHTS]; uniform vec4 g_LightData[NB_LIGHTS];
uniform vec3 g_CameraPosition; uniform vec3 g_CameraPosition;
uniform float m_Roughness; uniform float m_Roughness;
@ -21,11 +19,20 @@ uniform float m_Metallic;
varying vec3 wPosition; varying vec3 wPosition;
#ifdef INDIRECT_LIGHTING #if NB_PROBES >= 1
// uniform sampler2D m_IntegrateBRDF;
uniform samplerCube g_PrefEnvMap; uniform samplerCube g_PrefEnvMap;
uniform vec3 g_ShCoeffs[9]; uniform vec3 g_ShCoeffs[9];
uniform vec4 g_LightProbeData; uniform mat4 g_LightProbeData;
#endif
#if NB_PROBES >= 2
uniform samplerCube g_PrefEnvMap2;
uniform vec3 g_ShCoeffs2[9];
uniform mat4 g_LightProbeData2;
#endif
#if NB_PROBES == 3
uniform samplerCube g_PrefEnvMap3;
uniform vec3 g_ShCoeffs3[9];
uniform mat4 g_LightProbeData3;
#endif #endif
#ifdef BASECOLORMAP #ifdef BASECOLORMAP
@ -87,6 +94,91 @@ varying vec3 wNormal;
uniform float m_AlphaDiscardThreshold; uniform float m_AlphaDiscardThreshold;
#endif #endif
float renderProbe(vec3 viewDir, vec3 normal, vec3 norm, float Roughness, vec4 diffuseColor, vec4 specularColor, float ndotv, vec3 ao, mat4 lightProbeData,vec3 shCoeffs[9],samplerCube prefEnvMap, inout vec3 color ){
// lightProbeData is a mat4 with this layout
// 3x3 rot mat|
// 0 1 2 | 3
// 0 | ax bx cx | px | )
// 1 | ay by cy | py | probe position
// 2 | az bz cz | pz | )
// --|----------|
// 3 | sx sy sz sp | -> 1/probe radius + nbMipMaps
// --scale--
// parallax fix for spherical / obb bounds and probe blending from
// from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
vec3 rv = reflect(-viewDir, normal);
vec4 probePos = lightProbeData[3];
float invRadius = fract( probePos.w);
float nbMipMaps = probePos.w - invRadius;
vec3 direction = wPosition - probePos.xyz;
float ndf = 0.0;
if(lightProbeData[0][3] != 0.0){
// oriented box probe
mat3 wToLocalRot = mat3(lightProbeData);
wToLocalRot = inverse(wToLocalRot);
vec3 scale = vec3(lightProbeData[0][3], lightProbeData[1][3], lightProbeData[2][3]);
#if NB_PROBES >= 2
// probe blending
// compute fragment position in probe local space
vec3 localPos = wToLocalRot * wPosition;
localPos -= probePos.xyz;
// compute normalized distance field
vec3 localDir = abs(localPos);
localDir /= scale;
ndf = max(max(localDir.x, localDir.y), localDir.z);
#endif
// parallax fix
vec3 rayLs = wToLocalRot * rv;
rayLs /= scale;
vec3 positionLs = wPosition - probePos.xyz;
positionLs = wToLocalRot * positionLs;
positionLs /= scale;
vec3 unit = vec3(1.0);
vec3 firstPlaneIntersect = (unit - positionLs) / rayLs;
vec3 secondPlaneIntersect = (-unit - positionLs) / rayLs;
vec3 furthestPlane = max(firstPlaneIntersect, secondPlaneIntersect);
float distance = min(min(furthestPlane.x, furthestPlane.y), furthestPlane.z);
vec3 intersectPositionWs = wPosition + rv * distance;
rv = intersectPositionWs - probePos.xyz;
} else {
// spherical probe
// paralax fix
rv = invRadius * direction + rv;
#if NB_PROBES >= 2
// probe blending
float dist = sqrt(dot(direction, direction));
ndf = dist * invRadius;
#endif
}
vec3 indirectDiffuse = vec3(0.0);
vec3 indirectSpecular = vec3(0.0);
indirectDiffuse = sphericalHarmonics(normal.xyz, shCoeffs) * diffuseColor.rgb;
vec3 dominantR = getSpecularDominantDir( normal, rv.xyz, Roughness * Roughness );
indirectSpecular = ApproximateSpecularIBLPolynomial(prefEnvMap, specularColor.rgb, Roughness, ndotv, dominantR, nbMipMaps);
#ifdef HORIZON_FADE
//horizon fade from http://marmosetco.tumblr.com/post/81245981087
float horiz = dot(rv, norm);
float horizFadePower = 1.0 - Roughness;
horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 );
horiz *= horiz;
indirectSpecular *= vec3(horiz);
#endif
vec3 indirectLighting = (indirectDiffuse + indirectSpecular) * ao;
color = indirectLighting * step( 0.0, probePos.w);
return ndf;
}
void main(){ void main(){
vec2 newTexCoord; vec2 newTexCoord;
vec3 viewDir = normalize(g_CameraPosition - wPosition); vec3 viewDir = normalize(g_CameraPosition - wPosition);
@ -250,30 +342,45 @@ void main(){
gl_FragColor.rgb += directLighting * fallOff; gl_FragColor.rgb += directLighting * fallOff;
} }
#ifdef INDIRECT_LIGHTING #if NB_PROBES >= 1
vec3 rv = reflect(-viewDir.xyz, normal.xyz); vec3 color1 = vec3(0.0);
//prallax fix for spherical bounds from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ vec3 color2 = vec3(0.0);
// g_LightProbeData.w is 1/probe radius + nbMipMaps, g_LightProbeData.xyz is the position of the lightProbe. vec3 color3 = vec3(0.0);
float invRadius = fract( g_LightProbeData.w); float weight1 = 1.0;
float nbMipMaps = g_LightProbeData.w - invRadius; float weight2 = 0.0;
rv = invRadius * (wPosition - g_LightProbeData.xyz) +rv; float weight3 = 0.0;
//horizon fade from http://marmosetco.tumblr.com/post/81245981087 float ndf = renderProbe(viewDir, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData, g_ShCoeffs, g_PrefEnvMap, color1);
float horiz = dot(rv, norm); #if NB_PROBES >= 2
float horizFadePower = 1.0 - Roughness; float ndf2 = renderProbe(viewDir, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData2, g_ShCoeffs2, g_PrefEnvMap2, color2);
horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 ); #endif
horiz *= horiz; #if NB_PROBES == 3
float ndf3 = renderProbe(viewDir, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData3, g_ShCoeffs3, g_PrefEnvMap3, color3);
#endif
vec3 indirectDiffuse = vec3(0.0); #if NB_PROBES >= 2
vec3 indirectSpecular = vec3(0.0); float invNdf = max(1.0 - ndf,0.0);
indirectDiffuse = sphericalHarmonics(normal.xyz, g_ShCoeffs) * diffuseColor.rgb; float invNdf2 = max(1.0 - ndf2,0.0);
vec3 dominantR = getSpecularDominantDir( normal, rv.xyz, Roughness*Roughness ); float sumNdf = ndf + ndf2;
indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, dominantR, nbMipMaps); float sumInvNdf = invNdf + invNdf2;
indirectSpecular *= vec3(horiz); #if NB_PROBES == 3
float invNdf3 = max(1.0 - ndf3,0.0);
sumNdf += ndf3;
sumInvNdf += invNdf3;
weight3 = ((1.0 - (ndf3 / sumNdf)) / (NB_PROBES - 1)) * (invNdf3 / sumInvNdf);
#endif
vec3 indirectLighting = (indirectDiffuse + indirectSpecular) * ao; weight1 = ((1.0 - (ndf / sumNdf)) / (NB_PROBES - 1)) * (invNdf / sumInvNdf);
weight2 = ((1.0 - (ndf2 / sumNdf)) / (NB_PROBES - 1)) * (invNdf2 / sumInvNdf);
float weightSum = weight1 + weight2 + weight3;
weight1 /= weightSum;
weight2 /= weightSum;
weight3 /= weightSum;
#endif
gl_FragColor.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0);
gl_FragColor.rgb = gl_FragColor.rgb + indirectLighting * step( 0.0, g_LightProbeData.w);
#endif #endif
#if defined(EMISSIVE) || defined (EMISSIVEMAP) #if defined(EMISSIVE) || defined (EMISSIVEMAP)

@ -62,6 +62,9 @@ MaterialDef PBR Lighting {
//Set to true to activate Steep Parallax mapping //Set to true to activate Steep Parallax mapping
Boolean SteepParallax Boolean SteepParallax
//Horizon fade
Boolean HorizonFade
// Set to Use Lightmap // Set to Use Lightmap
Texture2D LightMap Texture2D LightMap
@ -157,6 +160,7 @@ MaterialDef PBR Lighting {
AO_MAP: LightMapAsAOMap AO_MAP: LightMapAsAOMap
NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
HORIZON_FADE: HorizonFade
} }
} }

@ -102,7 +102,7 @@ public class TestConeVSFrustum extends SimpleApplication {
float radius = FastMath.tan(spotLight.getSpotOuterAngle()) * spotLight.getSpotRange(); float radius = FastMath.tan(spotLight.getSpotOuterAngle()) * spotLight.getSpotRange();
Cylinder cylinder = new Cylinder(5, 16, 0, radius, spotLight.getSpotRange(), true, false); Cylinder cylinder = new Cylinder(5, 16, 0.01f, radius, spotLight.getSpotRange(), true, false);
geom = new Geometry("light", cylinder); geom = new Geometry("light", cylinder);
geom.setMaterial(new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md")); geom.setMaterial(new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"));
geom.getMaterial().setColor("Diffuse", ColorRGBA.White); geom.getMaterial().setColor("Diffuse", ColorRGBA.White);

@ -0,0 +1,301 @@
/*
* 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.ChaseCameraAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.export.binary.BinaryExporter;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.*;
import com.jme3.light.*;
import com.jme3.material.Material;
import com.jme3.math.*;
import com.jme3.renderer.Camera;
import com.jme3.scene.*;
import com.jme3.scene.debug.Grid;
import com.jme3.scene.debug.WireFrustum;
import com.jme3.scene.shape.*;
import com.jme3.shadow.ShadowUtil;
import com.jme3.util.TempVars;
import java.io.File;
import java.io.IOException;
public class TestObbVsBounds extends SimpleApplication {
private Node ln;
private BoundingBox aabb = new BoundingBox();
private BoundingSphere sphere = new BoundingSphere(10, new Vector3f(-30, 0, -60));
private final static float MOVE_SPEED = 60;
private Vector3f tmp = new Vector3f();
private Quaternion tmpQuat = new Quaternion();
private boolean moving, shift;
private boolean panning;
private OrientedBoxProbeArea area = new OrientedBoxProbeArea();
private Camera frustumCam;
private Geometry areaGeom;
private Geometry frustumGeom;
private Geometry aabbGeom;
private Geometry sphereGeom;
public static void main(String[] args) {
TestObbVsBounds app = new TestObbVsBounds();
app.start();
}
@Override
public void simpleInitApp() {
viewPort.setBackgroundColor(ColorRGBA.DarkGray);
frustumCam = cam.clone();
frustumCam.setFrustumFar(25);
makeCamFrustum();
aabb.setCenter(20, 10, -60);
aabb.setXExtent(10);
aabb.setYExtent(5);
aabb.setZExtent(3);
makeBoxWire(aabb);
makeSphereWire(sphere);
rootNode.addLight(new DirectionalLight());
AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(0.2f));
rootNode.addLight(al);
Grid grid = new Grid(50, 50, 5);
Geometry gridGeom = new Geometry("grid", grid);
gridGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
gridGeom.getMaterial().setColor("Color", ColorRGBA.Gray);
rootNode.attachChild(gridGeom);
gridGeom.setLocalTranslation(-125, -25, -125);
area.setCenter(Vector3f.ZERO);
area.setExtent(new Vector3f(4, 8, 5));
makeAreaGeom();
ln = new Node("lb");
ln.setLocalRotation(new Quaternion(-0.18826798f, -0.38304946f, -0.12780227f, 0.895261f));
ln.attachChild(areaGeom);
ln.setLocalScale(4,8,5);
rootNode.attachChild(ln);
inputManager.addMapping("click", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addMapping("shift", new KeyTrigger(KeyInput.KEY_LSHIFT), new KeyTrigger(KeyInput.KEY_RSHIFT));
inputManager.addMapping("middleClick", new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE));
inputManager.addMapping("up", new MouseAxisTrigger(MouseInput.AXIS_Y, false));
inputManager.addMapping("down", new MouseAxisTrigger(MouseInput.AXIS_Y, true));
inputManager.addMapping("left", new MouseAxisTrigger(MouseInput.AXIS_X, true));
inputManager.addMapping("right", new MouseAxisTrigger(MouseInput.AXIS_X, false));
final Node camTarget = new Node("CamTarget");
rootNode.attachChild(camTarget);
ChaseCameraAppState chaser = new ChaseCameraAppState();
chaser.setTarget(camTarget);
chaser.setMaxDistance(150);
chaser.setDefaultDistance(70);
chaser.setDefaultHorizontalRotation(FastMath.HALF_PI);
chaser.setMinVerticalRotation(-FastMath.PI);
chaser.setMaxVerticalRotation(FastMath.PI * 2);
chaser.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
stateManager.attach(chaser);
flyCam.setEnabled(false);
inputManager.addListener(new AnalogListener() {
public void onAnalog(String name, float value, float tpf) {
Spatial s = null;
float mult = 1;
if (moving) {
s = ln;
}
if (panning) {
s = camTarget;
mult = -1;
}
if ((moving || panning) && s != null) {
if (shift) {
if (name.equals("left")) {
tmp.set(cam.getDirection());
s.rotate(tmpQuat.fromAngleAxis(value, tmp));
}
if (name.equals("right")) {
tmp.set(cam.getDirection());
s.rotate(tmpQuat.fromAngleAxis(-value, tmp));
}
} else {
value *= MOVE_SPEED * mult;
if (name.equals("up")) {
tmp.set(cam.getUp()).multLocal(value);
s.move(tmp);
}
if (name.equals("down")) {
tmp.set(cam.getUp()).multLocal(-value);
s.move(tmp);
}
if (name.equals("left")) {
tmp.set(cam.getLeft()).multLocal(value);
s.move(tmp);
}
if (name.equals("right")) {
tmp.set(cam.getLeft()).multLocal(-value);
s.move(tmp);
}
}
}
}
}, "up", "down", "left", "right");
inputManager.addListener(new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("click")) {
if (isPressed) {
moving = true;
} else {
moving = false;
}
}
if (name.equals("middleClick")) {
if (isPressed) {
panning = true;
} else {
panning = false;
}
}
if (name.equals("shift")) {
if (isPressed) {
shift = true;
} else {
shift = false;
}
}
}
}, "click", "middleClick", "shift");
}
public void makeAreaGeom() {
Vector3f[] points = new Vector3f[8];
for (int i = 0; i < points.length; i++) {
points[i] = new Vector3f();
}
points[0].set(-1, -1, 1);
points[1].set(-1, 1, 1);
points[2].set(1, 1, 1);
points[3].set(1, -1, 1);
points[4].set(-1, -1, -1);
points[5].set(-1, 1, -1);
points[6].set(1, 1, -1);
points[7].set(1, -1, -1);
Mesh box = WireFrustum.makeFrustum(points);
areaGeom = new Geometry("light", (Mesh)box);
areaGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
areaGeom.getMaterial().setColor("Color", ColorRGBA.White);
}
public void makeCamFrustum() {
Vector3f[] points = new Vector3f[8];
for (int i = 0; i < 8; i++) {
points[i] = new Vector3f();
}
ShadowUtil.updateFrustumPoints2(frustumCam, points);
WireFrustum frustumShape = new WireFrustum(points);
frustumGeom = new Geometry("frustum", frustumShape);
frustumGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
rootNode.attachChild(frustumGeom);
}
public void makeBoxWire(BoundingBox box) {
Vector3f[] points = new Vector3f[8];
for (int i = 0; i < 8; i++) {
points[i] = new Vector3f();
}
points[0].set(-1, -1, 1);
points[1].set(-1, 1, 1);
points[2].set(1, 1, 1);
points[3].set(1, -1, 1);
points[4].set(-1, -1, -1);
points[5].set(-1, 1, -1);
points[6].set(1, 1, -1);
points[7].set(1, -1, -1);
WireFrustum frustumShape = new WireFrustum(points);
aabbGeom = new Geometry("box", frustumShape);
aabbGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
aabbGeom.getMaterial().getAdditionalRenderState().setWireframe(true);
aabbGeom.setLocalTranslation(box.getCenter());
aabbGeom.setLocalScale(box.getXExtent(), box.getYExtent(), box.getZExtent());
rootNode.attachChild(aabbGeom);
}
public void makeSphereWire(BoundingSphere sphere) {
sphereGeom = new Geometry("box", new Sphere(16, 16, 10));
sphereGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
sphereGeom.getMaterial().getAdditionalRenderState().setWireframe(true);
sphereGeom.setLocalTranslation(sphere.getCenter());
rootNode.attachChild(sphereGeom);
}
@Override
public void simpleUpdate(float tpf) {
area.setCenter(ln.getLocalTranslation());
area.setRotation(ln.getLocalRotation());
TempVars vars = TempVars.get();
boolean intersectBox = area.intersectsBox(aabb, vars);
boolean intersectFrustum = area.intersectsFrustum(frustumCam, vars);
boolean intersectSphere = area.intersectsSphere(sphere, vars);
vars.release();
boolean intersect = intersectBox || intersectFrustum || intersectSphere;
areaGeom.getMaterial().setColor("Color", intersect ? ColorRGBA.Green : ColorRGBA.White);
sphereGeom.getMaterial().setColor("Color", intersectSphere ? ColorRGBA.Cyan : ColorRGBA.White);
frustumGeom.getMaterial().setColor("Color", intersectFrustum ? ColorRGBA.Cyan : ColorRGBA.White);
aabbGeom.getMaterial().setColor("Color", intersectBox ? ColorRGBA.Cyan : ColorRGBA.White);
}
}

@ -1,7 +1,6 @@
package jme3test.light.pbr; package jme3test.light.pbr;
import com.jme3.app.SimpleApplication; import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingSphere;
import com.jme3.environment.EnvironmentCamera; import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.LightProbeFactory; import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.generation.JobProgressAdapter; import com.jme3.environment.generation.JobProgressAdapter;
@ -10,6 +9,7 @@ import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger; import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.LightProbe; import com.jme3.light.LightProbe;
import com.jme3.light.SphereProbeArea;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.math.*; import com.jme3.math.*;
import com.jme3.scene.*; import com.jme3.scene.*;
@ -127,7 +127,7 @@ public class RefEnv extends SimpleApplication {
rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic); rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic);
} }
}); });
((BoundingSphere) probe.getBounds()).setRadius(100); ((SphereProbeArea) probe.getArea()).setRadius(100);
rootNode.addLight(probe); rootNode.addLight(probe);
} }

@ -42,8 +42,7 @@ import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput; import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger; import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight; import com.jme3.light.*;
import com.jme3.light.LightProbe;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.math.*; import com.jme3.math.*;
import com.jme3.post.FilterPostProcessor; import com.jme3.post.FilterPostProcessor;
@ -205,7 +204,7 @@ public class TestPBRLighting extends SimpleApplication {
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager); tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
} }
}); });
((BoundingSphere) probe.getBounds()).setRadius(100); ((SphereProbeArea) probe.getArea()).setRadius(100);
rootNode.addLight(probe); rootNode.addLight(probe);
//getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe); //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);

Loading…
Cancel
Save