Adds support for light porbe blending and Oriented box light probes with corrected parallax
This commit is contained in:
parent
824e99c96e
commit
83aef82d92
@ -34,6 +34,7 @@ package com.jme3.bounding;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Plane;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.util.TempVars;
|
||||
import static java.lang.Math.max;
|
||||
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 axisTestX01(float a, float b, float fa, float fb,
|
||||
// Vector3f center, Vector3f ext,
|
||||
|
@ -38,14 +38,9 @@ import com.jme3.environment.util.EnvMapUtils;
|
||||
import com.jme3.light.LightProbe;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.*;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Texture2D;
|
||||
import com.jme3.texture.TextureCubeMap;
|
||||
import com.jme3.texture.*;
|
||||
import com.jme3.texture.image.ColorSpace;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.MipMapGenerator;
|
||||
@ -119,7 +114,7 @@ public class EnvironmentCamera extends BaseAppState {
|
||||
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() {
|
||||
}
|
||||
@ -322,7 +317,7 @@ public class EnvironmentCamera extends BaseAppState {
|
||||
final Camera offCamera = new Camera(mapSize, mapSize);
|
||||
offCamera.setLocation(worldPos);
|
||||
offCamera.setAxes(axisX, axisY, axisZ);
|
||||
offCamera.setFrustumPerspective(90f, 1f, 1, 1000);
|
||||
offCamera.setFrustumPerspective(90f, 1f, 0.1f, 1000);
|
||||
offCamera.setLocation(position);
|
||||
return offCamera;
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
package com.jme3.environment;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.environment.generation.*;
|
||||
import com.jme3.environment.util.EnvMapUtils;
|
||||
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
|
||||
*/
|
||||
|
@ -34,9 +34,8 @@ package com.jme3.environment.util;
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.light.*;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.light.LightProbe;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
@ -68,7 +67,7 @@ public class LightsDebugState extends BaseAppState {
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
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);
|
||||
debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md");
|
||||
debugGeom.setMaterial(debugMaterial);
|
||||
@ -80,6 +79,16 @@ public class LightsDebugState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
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()) {
|
||||
switch (light.getType()) {
|
||||
|
||||
@ -101,16 +110,18 @@ public class LightsDebugState extends BaseAppState {
|
||||
m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
|
||||
}
|
||||
n.setLocalTranslation(probe.getPosition());
|
||||
n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
|
||||
n.getChild(1).setLocalScale(probe.getArea().getRadius());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
debugNode.updateLogicalState(tpf);
|
||||
debugNode.updateGeometricState();
|
||||
cleanProbes();
|
||||
|
||||
if( scene instanceof Node){
|
||||
Node n = (Node)scene;
|
||||
for (Spatial spatial : n.getChildren()) {
|
||||
updateLights(spatial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,6 +149,9 @@ public class LightsDebugState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
public void render(RenderManager rm) {
|
||||
if(!isEnabled()){
|
||||
return;
|
||||
}
|
||||
rm.renderScene(debugNode, getApplication().getViewPort());
|
||||
}
|
||||
|
||||
|
@ -43,10 +43,10 @@ public final class DefaultLightFilter implements LightFilter {
|
||||
|
||||
private Camera camera;
|
||||
private final HashSet<Light> processedLights = new HashSet<Light>();
|
||||
private final LightProbeBlendingStrategy probeBlendStrat;
|
||||
private LightProbeBlendingStrategy probeBlendStrat;
|
||||
|
||||
public DefaultLightFilter() {
|
||||
probeBlendStrat = new BasicProbeBlendingStrategy();
|
||||
probeBlendStrat = new WeightedProbeBlendingStrategy();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.bounding.BoundingVolume;
|
||||
import com.jme3.bounding.*;
|
||||
import com.jme3.environment.EnvironmentCamera;
|
||||
import com.jme3.environment.LightProbeFactory;
|
||||
import com.jme3.environment.util.EnvMapUtils;
|
||||
import com.jme3.export.InputCapsule;
|
||||
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.export.*;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.texture.TextureCubeMap;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
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)}
|
||||
* 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.
|
||||
* Note that you should never call setReady yourself.
|
||||
*
|
||||
@ -78,15 +70,20 @@ import java.util.logging.Logger;
|
||||
public class LightProbe extends Light implements Savable {
|
||||
|
||||
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 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 Vector3f position = new Vector3f();
|
||||
private Node debugNode;
|
||||
private int nbMipMaps;
|
||||
|
||||
public enum AreaType{
|
||||
Spherical,
|
||||
OrientedBox
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty constructor used for serialization.
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
super.write(ex);
|
||||
@ -118,7 +161,7 @@ public class LightProbe extends Light implements Savable {
|
||||
oc.write(shCoeffs, "shCoeffs", null);
|
||||
oc.write(prefilteredEnvMap, "prefilteredEnvMap", 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(nbMipMaps, "nbMipMaps", 0);
|
||||
}
|
||||
@ -130,7 +173,13 @@ public class LightProbe extends Light implements Savable {
|
||||
|
||||
prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", 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);
|
||||
ready = ic.readBoolean("ready", false);
|
||||
|
||||
@ -146,12 +195,15 @@ public class LightProbe extends Light implements Savable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns the bounding volume of this LightProbe
|
||||
* @return a bounding volume.
|
||||
* @deprecated use {@link LightProbe#getArea()}
|
||||
*/
|
||||
@Deprecated
|
||||
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
|
||||
* throw an UnsupportedOperationException with any other BoundingVolume type
|
||||
* @param bounds the bounds of the LightProbe
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return shCoeffs;
|
||||
}
|
||||
@ -229,7 +281,7 @@ public class LightProbe extends Light implements Savable {
|
||||
*/
|
||||
public void setPosition(Vector3f position) {
|
||||
this.position.set(position);
|
||||
getBounds().setCenter(position);
|
||||
area.setCenter(position);
|
||||
}
|
||||
|
||||
public int getNbMipMaps() {
|
||||
@ -242,12 +294,17 @@ public class LightProbe extends Light implements Savable {
|
||||
|
||||
@Override
|
||||
public boolean intersectsBox(BoundingBox box, TempVars vars) {
|
||||
return getBounds().intersectsBoundingBox(box);
|
||||
return area.intersectsBox(box, vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
@ -267,14 +324,8 @@ public class LightProbe extends Light implements Savable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Light Probe : " + name + " at " + position + " / " + bounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
|
||||
return getBounds().intersectsSphere(sphere);
|
||||
return "Light Probe : " + name + " at " + position + " / " + area;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
249
jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java
Normal file
249
jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java
Normal file
@ -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) {
|
||||
return true;
|
||||
} else {
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
if (camera.getWorldPlane(i).pseudoDistance(position) <= -radius) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return Intersection.intersect(camera, position, radius);
|
||||
}
|
||||
}
|
||||
|
||||
|
33
jme3-core/src/main/java/com/jme3/light/ProbeArea.java
Normal file
33
jme3-core/src/main/java/com/jme3/light/ProbeArea.java
Normal file
@ -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);
|
||||
}
|
102
jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java
Normal file
102
jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java
Normal file
@ -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.util.TempVars;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.*;
|
||||
|
||||
public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic {
|
||||
|
||||
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_INDIRECT_LIGHTING = "INDIRECT_LIGHTING";
|
||||
private static final String DEFINE_NB_PROBES = "NB_PROBES";
|
||||
private static final RenderState ADDITIVE_LIGHT = new RenderState();
|
||||
|
||||
private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
|
||||
private LightProbe lightProbe = null;
|
||||
private List<LightProbe> lightProbes = new ArrayList<>(3);
|
||||
|
||||
static {
|
||||
ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
|
||||
@ -61,13 +61,13 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
|
||||
private final int singlePassLightingDefineId;
|
||||
private final int nbLightsDefineId;
|
||||
private final int indirectLightingDefineId;
|
||||
private final int nbProbesDefineId;
|
||||
|
||||
public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) {
|
||||
super(techniqueDef);
|
||||
singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
|
||||
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
|
||||
@ -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.
|
||||
//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) {
|
||||
lightProbe = extractIndirectLights(lights, false);
|
||||
if (lightProbe == null) {
|
||||
defines.set(indirectLightingDefineId, false);
|
||||
} else {
|
||||
defines.set(indirectLightingDefineId, true);
|
||||
}
|
||||
lightProbes.clear();
|
||||
extractIndirectLights(lights, false);
|
||||
defines.set(nbProbesDefineId, lightProbes.size());
|
||||
}
|
||||
|
||||
return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
|
||||
@ -113,35 +110,44 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
Uniform lightData = shader.getUniform("g_LightData");
|
||||
lightData.setVector4Length(numLights * 3);//8 lights * max 3
|
||||
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
|
||||
Uniform lightProbeData = shader.getUniform("g_LightProbeData");
|
||||
lightProbeData.setVector4Length(1);
|
||||
|
||||
//TODO These 2 uniforms should be packed in an array, to be able to have several probes and blend between them.
|
||||
// Matrix4f
|
||||
Uniform lightProbeData = shader.getUniform("g_LightProbeData");
|
||||
Uniform lightProbeData2 = shader.getUniform("g_LightProbeData2");
|
||||
Uniform lightProbeData3 = shader.getUniform("g_LightProbeData3");
|
||||
|
||||
Uniform shCoeffs = shader.getUniform("g_ShCoeffs");
|
||||
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) {
|
||||
// apply additive blending for 2nd and future passes
|
||||
rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
|
||||
ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
|
||||
}else{
|
||||
lightProbe = extractIndirectLights(lightList,true);
|
||||
extractIndirectLights(lightList,true);
|
||||
ambientColor.setValue(VarType.Vector4, ambientLightColor);
|
||||
}
|
||||
|
||||
//If there is a lightProbe in the list we force its render on the first pass
|
||||
if(lightProbe != null){
|
||||
BoundingSphere s = (BoundingSphere)lightProbe.getBounds();
|
||||
lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / s.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);
|
||||
if (!lightProbes.isEmpty()) {
|
||||
LightProbe lightProbe = lightProbes.get(0);
|
||||
lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData, shCoeffs, lightProbePemMap, lightProbe);
|
||||
if (lightProbes.size() > 1) {
|
||||
lightProbe = lightProbes.get(1);
|
||||
lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData2, shCoeffs2, lightProbePemMap2, lightProbe);
|
||||
}
|
||||
if (lightProbes.size() > 2) {
|
||||
lightProbe = lightProbes.get(2);
|
||||
setProbeData(rm, lastTexUnit, lightProbeData3, shCoeffs3, lightProbePemMap3, lightProbe);
|
||||
}
|
||||
} else {
|
||||
//Disable IBL for this pass
|
||||
lightProbeData.setVector4InArray(0,0,0,-1, 0);
|
||||
lightProbeData.setValue(VarType.Matrix4, LightProbe.FALLBACK_MATRIX);
|
||||
}
|
||||
|
||||
int lightDataIndex = 0;
|
||||
@ -222,6 +228,18 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
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
|
||||
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
|
||||
int nbRenderedLights = 0;
|
||||
@ -241,9 +259,8 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
return;
|
||||
}
|
||||
|
||||
protected LightProbe extractIndirectLights(LightList lightList, boolean removeLights) {
|
||||
protected void extractIndirectLights(LightList lightList, boolean removeLights) {
|
||||
ambientLightColor.set(0, 0, 0, 1);
|
||||
LightProbe probe = null;
|
||||
for (int j = 0; j < lightList.size(); j++) {
|
||||
Light l = lightList.get(j);
|
||||
if (l instanceof AmbientLight) {
|
||||
@ -254,7 +271,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
}
|
||||
}
|
||||
if (l instanceof LightProbe) {
|
||||
probe = (LightProbe)l;
|
||||
lightProbes.add((LightProbe) l);
|
||||
if(removeLights){
|
||||
lightList.remove(l);
|
||||
j--;
|
||||
@ -262,6 +279,5 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
}
|
||||
}
|
||||
ambientLightColor.a = 1.0f;
|
||||
return probe;
|
||||
}
|
||||
}
|
||||
|
@ -42,10 +42,20 @@ import java.nio.FloatBuffer;
|
||||
public class WireFrustum extends Mesh {
|
||||
|
||||
public WireFrustum(Vector3f[] points){
|
||||
if (points != null)
|
||||
setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
|
||||
initGeom(this, points);
|
||||
}
|
||||
|
||||
setBuffer(Type.Index, 2,
|
||||
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)
|
||||
m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
|
||||
|
||||
m.setBuffer(Type.Index, 2,
|
||||
new short[]{
|
||||
0, 1,
|
||||
1, 2,
|
||||
@ -63,8 +73,8 @@ public class WireFrustum extends Mesh {
|
||||
3, 7,
|
||||
}
|
||||
);
|
||||
getBuffer(Type.Index).setUsage(Usage.Static);
|
||||
setMode(Mode.Lines);
|
||||
m.getBuffer(Type.Index).setUsage(Usage.Static);
|
||||
m.setMode(Mode.Lines);
|
||||
}
|
||||
|
||||
public void update(Vector3f[] points){
|
||||
|
@ -3,7 +3,6 @@
|
||||
#import "Common/ShaderLib/Parallax.glsllib"
|
||||
#import "Common/ShaderLib/Lighting.glsllib"
|
||||
|
||||
|
||||
varying vec2 texCoord;
|
||||
#ifdef SEPARATE_TEXCOORD
|
||||
varying vec2 texCoord2;
|
||||
@ -12,7 +11,6 @@ varying vec2 texCoord;
|
||||
varying vec4 Color;
|
||||
|
||||
uniform vec4 g_LightData[NB_LIGHTS];
|
||||
|
||||
uniform vec3 g_CameraPosition;
|
||||
|
||||
uniform float m_Roughness;
|
||||
@ -21,11 +19,20 @@ uniform float m_Metallic;
|
||||
varying vec3 wPosition;
|
||||
|
||||
|
||||
#ifdef INDIRECT_LIGHTING
|
||||
// uniform sampler2D m_IntegrateBRDF;
|
||||
#if NB_PROBES >= 1
|
||||
uniform samplerCube g_PrefEnvMap;
|
||||
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
|
||||
|
||||
#ifdef BASECOLORMAP
|
||||
@ -87,6 +94,91 @@ varying vec3 wNormal;
|
||||
uniform float m_AlphaDiscardThreshold;
|
||||
#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(){
|
||||
vec2 newTexCoord;
|
||||
vec3 viewDir = normalize(g_CameraPosition - wPosition);
|
||||
@ -250,30 +342,45 @@ void main(){
|
||||
gl_FragColor.rgb += directLighting * fallOff;
|
||||
}
|
||||
|
||||
#ifdef INDIRECT_LIGHTING
|
||||
vec3 rv = reflect(-viewDir.xyz, normal.xyz);
|
||||
//prallax fix for spherical bounds from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
|
||||
// g_LightProbeData.w is 1/probe radius + nbMipMaps, g_LightProbeData.xyz is the position of the lightProbe.
|
||||
float invRadius = fract( g_LightProbeData.w);
|
||||
float nbMipMaps = g_LightProbeData.w - invRadius;
|
||||
rv = invRadius * (wPosition - g_LightProbeData.xyz) +rv;
|
||||
#if NB_PROBES >= 1
|
||||
vec3 color1 = vec3(0.0);
|
||||
vec3 color2 = vec3(0.0);
|
||||
vec3 color3 = vec3(0.0);
|
||||
float weight1 = 1.0;
|
||||
float weight2 = 0.0;
|
||||
float weight3 = 0.0;
|
||||
|
||||
//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;
|
||||
float ndf = renderProbe(viewDir, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData, g_ShCoeffs, g_PrefEnvMap, color1);
|
||||
#if NB_PROBES >= 2
|
||||
float ndf2 = renderProbe(viewDir, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData2, g_ShCoeffs2, g_PrefEnvMap2, color2);
|
||||
#endif
|
||||
#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);
|
||||
vec3 indirectSpecular = vec3(0.0);
|
||||
indirectDiffuse = sphericalHarmonics(normal.xyz, g_ShCoeffs) * diffuseColor.rgb;
|
||||
vec3 dominantR = getSpecularDominantDir( normal, rv.xyz, Roughness*Roughness );
|
||||
indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, dominantR, nbMipMaps);
|
||||
indirectSpecular *= vec3(horiz);
|
||||
#if NB_PROBES >= 2
|
||||
float invNdf = max(1.0 - ndf,0.0);
|
||||
float invNdf2 = max(1.0 - ndf2,0.0);
|
||||
float sumNdf = ndf + ndf2;
|
||||
float sumInvNdf = invNdf + invNdf2;
|
||||
#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
|
||||
|
||||
#if defined(EMISSIVE) || defined (EMISSIVEMAP)
|
||||
|
@ -62,6 +62,9 @@ MaterialDef PBR Lighting {
|
||||
//Set to true to activate Steep Parallax mapping
|
||||
Boolean SteepParallax
|
||||
|
||||
//Horizon fade
|
||||
Boolean HorizonFade
|
||||
|
||||
// Set to Use Lightmap
|
||||
Texture2D LightMap
|
||||
|
||||
@ -157,6 +160,7 @@ MaterialDef PBR Lighting {
|
||||
AO_MAP: LightMapAsAOMap
|
||||
NUM_MORPH_TARGETS: NumberOfMorphTargets
|
||||
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
|
||||
HORIZON_FADE: HorizonFade
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ public class TestConeVSFrustum extends SimpleApplication {
|
||||
|
||||
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.setMaterial(new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"));
|
||||
geom.getMaterial().setColor("Diffuse", ColorRGBA.White);
|
||||
|
301
jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java
Normal file
301
jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java
Normal file
@ -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;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.environment.EnvironmentCamera;
|
||||
import com.jme3.environment.LightProbeFactory;
|
||||
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.KeyTrigger;
|
||||
import com.jme3.light.LightProbe;
|
||||
import com.jme3.light.SphereProbeArea;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.scene.*;
|
||||
@ -127,7 +127,7 @@ public class RefEnv extends SimpleApplication {
|
||||
rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic);
|
||||
}
|
||||
});
|
||||
((BoundingSphere) probe.getBounds()).setRadius(100);
|
||||
((SphereProbeArea) probe.getArea()).setRadius(100);
|
||||
rootNode.addLight(probe);
|
||||
|
||||
}
|
||||
|
@ -42,8 +42,7 @@ import com.jme3.input.ChaseCamera;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.LightProbe;
|
||||
import com.jme3.light.*;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
@ -205,7 +204,7 @@ public class TestPBRLighting extends SimpleApplication {
|
||||
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
|
||||
}
|
||||
});
|
||||
((BoundingSphere) probe.getBounds()).setRadius(100);
|
||||
((SphereProbeArea) probe.getArea()).setRadius(100);
|
||||
rootNode.addLight(probe);
|
||||
//getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user