Big refactoring to how PBR is handled.

- Introduced a new Light type : LightProbes that are lights holding Image based Lighting information that are sent to the shader. For now, only the closest LightProbe from a geometry is sent to the shader. This will be enhanced later as it's obviously not the best way to handle this.
- Added a LightProbeFactory for easy creation and rendering of LightPorbes and associated maps. The maps generation process can also be monitored through a Listener class.
- Added various utility classses for debuging purpose.
- Added a new test case for environment with multiple LightProbes.
- Adapted the previous test case to the new system.
define_list_fix
Nehon 10 years ago
parent 7b7c6951ad
commit a35b499ee7
  1. 53
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  2. 246
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  3. 59
      jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java
  4. 67
      jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java
  5. 1
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  6. 177
      jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java
  7. 214
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  8. 12
      jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java
  9. 9
      jme3-core/src/main/java/com/jme3/light/Light.java
  10. 251
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  11. 62
      jme3-core/src/main/java/com/jme3/material/Material.java
  12. 23
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  13. 4
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  14. 45
      jme3-core/src/main/resources/Common/MatDefs/Misc/reflect.j3md
  15. 22
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn
  16. 8
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping100.frag
  17. 8
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping130.frag
  18. 25
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn
  19. 14
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect100.vert
  20. 33
      jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib
  21. 90
      jme3-examples/src/main/java/jme3test/light/TestColorApp.java
  22. 96
      jme3-examples/src/main/java/jme3test/light/TestTangentCube.java
  23. 71
      jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java
  24. 121
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
  25. 378
      jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java
  26. 180
      jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java
  27. 13
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat.j3m
  28. 12
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat2.j3m
  29. 9
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat3.j3m
  30. 11
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat4.j3m

@ -35,12 +35,13 @@ import com.jme3.environment.generation.JobProgressListener;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
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.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
@ -54,8 +55,11 @@ import java.util.concurrent.Callable;
/**
* A 360 camera that can capture a cube map of a scene, and then generate the
* Prefiltered Environment cube Map and the Irradiance cube Map needed for PBE
* Prefiltered Environment cube Map and the Irradiance cube Map needed for PBR
* indirect lighting
*
* @see LightProbeFactory
* @see LightProbe
*
* @author Nehon
*/
@ -105,10 +109,6 @@ public class EnvironmentCamera extends BaseAppState {
private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
// debug to be removed
private Node debugPfemCm;
private Node debugIrrCm;
/**
* Creates an EnvironmentCamera with a size of 128
*/
@ -154,9 +154,10 @@ public class EnvironmentCamera extends BaseAppState {
* @param scene the scene to snapshot.
* @param done a callback to call when the snapshot is done.
*/
public void snapshot(final Node scene, final JobProgressListener<TextureCubeMap> done) {
public void snapshot(final Spatial scene, final JobProgressListener<TextureCubeMap> done) {
getApplication().enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
SnapshotJob job = new SnapshotJob(done, scene);
jobs.add(job);
@ -195,37 +196,10 @@ public class EnvironmentCamera extends BaseAppState {
return position;
}
// /**
// * Displays or cycles through the generated maps.
// */
// public void toggleDebug() {
// if (debugPfemCm == null) {
// debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(currentEnvProbe.getPrefilteredEnvMap(), getApplication().getAssetManager());
// debugPfemCm.setLocalTranslation(getApplication().getGuiViewPort().getCamera().getWidth() - 532, 20, 0);
// }
// if (debugIrrCm == null) {
// debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(currentEnvProbe.getIrradianceMap(), getApplication().getAssetManager());
// debugIrrCm.setLocalTranslation(getApplication().getGuiViewPort().getCamera().getWidth() - 532, 20, 0);
// }
//
// if (debugIrrCm.getParent() != null) {
// debugIrrCm.removeFromParent();
// ((Node) (getApplication().getGuiViewPort().getScenes().get(0))).attachChild(debugPfemCm);
//
// } else if (debugPfemCm.getParent() != null) {
// debugPfemCm.removeFromParent();
// } else {
// ((Node) (getApplication().getGuiViewPort().getScenes().get(0))).attachChild(debugIrrCm);
// }
//
// }
/**
* Sets the camera position.
* Sets the camera position in world space.
*
* @param position
* @param position the position in world space
*/
public void setPosition(Vector3f position) {
this.position.set(position);
@ -336,12 +310,15 @@ public class EnvironmentCamera extends BaseAppState {
return offBuffer;
}
/**
* An inner class to keep track on a snapshot job.
*/
private class SnapshotJob {
JobProgressListener<TextureCubeMap> callback;
Node scene;
Spatial scene;
public SnapshotJob(JobProgressListener callback, Node scene) {
public SnapshotJob(JobProgressListener callback, Spatial scene) {
this.callback = callback;
this.scene = scene;
}

@ -0,0 +1,246 @@
/*
* 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.environment;
import com.jme3.light.LightProbe;
import com.jme3.environment.generation.JobProgressListener;
import com.jme3.environment.generation.PrefilteredEnvMapFaceGenerator;
import com.jme3.environment.generation.IrradianceMapGenerator;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.app.Application;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* This Factory allows to create LightProbes within a scene given an EnvironmentCamera.
*
* Since the process can be long, you can provide a JobProgressListener that
* will be notified of the ongoing generation process when calling the makeProbe method.
*
* The process is the folowing :
* 1. Create an EnvironmentCamera
* 2. give it a position in the scene
* 3. call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
* 4. add the created LightProbe to a node with the {@link Node#addLight(com.jme3.light.Light) } method.
*
* Optionally for step 3 call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
* with a {@link JobProgressListener} to be notified of the progress of the generation process.
*
* The generation will be split in several threads for faster generation.
*
* This class is entirely thread safe and can be called from any thread.
*
* Note that in case you are using a {@link JobProgressListener} all the its
* method will be called inside and app.enqueu callable.
* This means that it's completely safe to modify the scenegraph within the
* Listener method, but also means that the even will be delayed until next update loop.
*
* @see EnvironmentCamera
* @author bouquet
*/
public class LightProbeFactory {
/**
* Creates a LightProbe with the giver EnvironmentCamera in the given scene.
*
* Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done.
*
* If you want to monitor the process use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
*
*
*
* @see LightProbe
* @see EnvironmentCamera
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @return the created LightProbe
*/
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene) {
return makeProbe(envCam, scene, null);
}
/**
* Creates a LightProbe with the giver EnvironmentCamera in the given scene.
*
* Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done.
*
* The JobProgressListener will be notified of the progress of the generation.
* Note that you can also use a {@link JobProgressAdapter}.
*
* @see LightProbe
* @see EnvironmentCamera
* @see JobProgressListener
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @param listener the listener of the genration progress.
* @return the created LightProbe
*/
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
final LightProbe probe = new LightProbe();
probe.setPosition(envCam.getPosition());
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override
public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener);
}
});
return probe;
}
/**
* Internally called to generate the maps.
* This method will spawn 7 thread (one for the IrradianceMap, and one for each face of the prefiltered env map).
* Those threads will be executed in a ScheduledThreadPoolExecutor that will be shutdown when the genration is done.
*
* @param envMap the raw env map rendered by the env camera
* @param probe the LigthProbe to generate maps for
* @param app the Application
* @param listener a progress listener. (can be null if no progress reporting is needed)
*/
private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, final JobProgressListener<LightProbe> listener) {
IrradianceMapGenerator irrMapGenerator;
PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7));
irrMapGenerator = new IrradianceMapGenerator(app, new JobListener(listener, jobState, probe, 6));
int size = envMap.getImage().getWidth();
irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getIrradianceMap());
jobState.executor.execute(irrMapGenerator);
for (int i = 0; i < pemGenerators.length; i++) {
pemGenerators[i] = new PrefilteredEnvMapFaceGenerator(app, i, new JobListener(listener, jobState, probe, i));
pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getPrefilteredEnvMap());
jobState.executor.execute(pemGenerators[i]);
}
}
/**
* An inner class to keep the state of a generation process
*/
private static class JobState {
double progress[] = new double[7];
boolean done[] = new boolean[7];
ScheduledThreadPoolExecutor executor;
boolean started = false;
public JobState(ScheduledThreadPoolExecutor executor) {
this.executor = executor;
}
boolean isDone() {
for (boolean d : done) {
if (d == false) {
return false;
}
}
return true;
}
float getProgress() {
float mean = 0;
for (double progres : progress) {
mean += progres;
}
return mean / 7f;
}
}
/**
* An inner JobProgressListener to controll the genration process and properly clean up when it's done
*/
private static class JobListener extends JobProgressAdapter<Integer> {
JobProgressListener<LightProbe> globalListener;
JobState jobState;
LightProbe probe;
int index;
public JobListener(JobProgressListener<LightProbe> globalListener, JobState jobState, LightProbe probe, int index) {
this.globalListener = globalListener;
this.jobState = jobState;
this.probe = probe;
this.index = index;
}
@Override
public void start() {
if (globalListener != null && !jobState.started) {
jobState.started = true;
globalListener.start();
}
}
@Override
public void progress(double value) {
jobState.progress[index] = value;
if (globalListener != null) {
globalListener.progress(jobState.getProgress());
}
}
@Override
public void done(Integer result) {
if (globalListener != null) {
if (result < 6) {
globalListener.step("Prefiltered env map face " + result + " generated");
} else {
globalListener.step("Irradiance map generated");
}
}
jobState.done[index] = true;
if (jobState.isDone()) {
probe.setReady(true);
if (globalListener != null) {
globalListener.done(probe);
}
jobState.executor.shutdownNow();
}
}
}
}

@ -0,0 +1,59 @@
/*
* 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.environment.generation;
/**
* Abstract Adapter class that implements optional methods of JobProgressListener.
* Extends this class instead of implementing a JobProgressListener if you need
* only a subset of method implemented.
*
* @author nehon
* @param <T>
*/
public abstract class JobProgressAdapter<T> implements JobProgressListener<T>{
@Override
public void progress(double value) {
}
@Override
public void start() {
}
@Override
public void step(String message) {
}
@Override
public abstract void done(T result);
}

@ -0,0 +1,67 @@
/*
* 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.environment.generation;
/**
* An interface listener that will be notified of the progress of an asynchronous
* generation job.
*
*
* @author nehon
* @param <T> The type of object generated.
*/
public interface JobProgressListener<T> {
/**
* Called when the process starts.
*/
public void start();
/**
* Can be called when a step of the process has been completed with a relevant message.
* @param message the message stating of the paricular step completion.
*/
public void step(String message);
/**
* Called when the process has made some progress.
* @param value a value from 0 to 1 representing the percentage of completion of the process.
*/
public void progress(double value);
/**
* Called when the process is done.
* @param result the object generated by the process.
*/
public void done(T result);
}

@ -46,7 +46,6 @@ import static com.jme3.environment.util.EnvMapUtils.getHammersleyPoint;
import static com.jme3.environment.util.EnvMapUtils.getRoughnessFromMip;
import static com.jme3.environment.util.EnvMapUtils.getSampleFromMip;
import static com.jme3.environment.util.EnvMapUtils.getVectorFromCubemapFaceTexCoord;
import com.jme3.math.FastMath;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;

@ -0,0 +1,177 @@
/*
* 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.environment.util;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
/**
*
* A debuging shape for a BoundingSphere
* Consists of 3 axis aligned circles.
*
* @author nehon
*/
public class BoundingSphereDebug extends Mesh {
protected int vertCount;
protected int triCount;
protected int radialSamples = 32;
protected boolean useEvenSlices;
protected boolean interior;
/**
* the distance from the center point each point falls on
*/
public float radius;
public float getRadius() {
return radius;
}
public BoundingSphereDebug() {
setGeometryData();
setIndexData();
}
/**
* builds the vertices based on the radius
*/
private void setGeometryData() {
setMode(Mode.Lines);
FloatBuffer posBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 3);
FloatBuffer colBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 4);
setBuffer(Type.Position, 3, posBuf);
setBuffer(Type.Color, 4, colBuf);
// generate geometry
float fInvRS = 1.0f / radialSamples;
// Generate points on the unit circle to be used in computing the mesh
// points on a sphere slice.
float[] afSin = new float[(radialSamples + 1)];
float[] afCos = new float[(radialSamples + 1)];
for (int iR = 0; iR < radialSamples; iR++) {
float fAngle = FastMath.TWO_PI * fInvRS * iR;
afCos[iR] = FastMath.cos(fAngle);
afSin[iR] = FastMath.sin(fAngle);
}
afSin[radialSamples] = afSin[0];
afCos[radialSamples] = afCos[0];
for (int iR = 0; iR <= radialSamples; iR++) {
posBuf.put(afCos[iR])
.put(afSin[iR])
.put(0);
colBuf.put(ColorRGBA.Blue.r)
.put(ColorRGBA.Blue.g)
.put(ColorRGBA.Blue.b)
.put(ColorRGBA.Blue.a);
}
for (int iR = 0; iR <= radialSamples; iR++) {
posBuf.put(afCos[iR])
.put(0)
.put(afSin[iR]);
colBuf.put(ColorRGBA.Green.r)
.put(ColorRGBA.Green.g)
.put(ColorRGBA.Green.b)
.put(ColorRGBA.Green.a);
}
for (int iR = 0; iR <= radialSamples; iR++) {
posBuf.put(0)
.put(afCos[iR])
.put(afSin[iR]);
colBuf.put(ColorRGBA.Yellow.r)
.put(ColorRGBA.Yellow.g)
.put(ColorRGBA.Yellow.b)
.put(ColorRGBA.Yellow.a);
}
updateBound();
setStatic();
}
/**
* sets the indices for rendering the sphere.
*/
private void setIndexData() {
// allocate connectivity
int nbSegments = (radialSamples) * 3;
ShortBuffer idxBuf = BufferUtils.createShortBuffer(2 * nbSegments);
setBuffer(Type.Index, 2, idxBuf);
int idx = 0;
int segDone = 0;
while (segDone < nbSegments) {
idxBuf.put((short) idx);
idxBuf.put((short) (idx + 1));
idx++;
segDone++;
if (segDone == radialSamples || segDone == radialSamples * 2) {
idx++;
}
}
}
/**
* Convenience factory method that creates a debuging bounding sphere geometry
* @param assetManager the assetManager
* @return the bounding sphere debug geometry.
*/
public static Geometry createDebugSphere(AssetManager assetManager) {
BoundingSphereDebug b = new BoundingSphereDebug();
Geometry geom = new Geometry("BoundingDebug", b);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setBoolean("VertexColor", true);
mat.getAdditionalRenderState().setWireframe(true);
geom.setMaterial(mat);
return geom;
}
}

@ -0,0 +1,214 @@
/*
* 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.environment.util;
import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.bounding.BoundingSphere;
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;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Sphere;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A debug state that will display LIght gizmos on screen.
* Still a wip and for now it only displays light probes.
*
* @author nehon
*/
public class LightsDebugState extends BaseAppState {
private Node debugNode;
private final Map<LightProbe, Node> probeMapping = new HashMap<LightProbe, Node>();
private final List<LightProbe> garbage = new ArrayList<LightProbe>();
private Geometry debugGeom;
private Geometry debugBounds;
private Material debugMaterial;
private DebugMode debugMode = DebugMode.PrefilteredEnvMap;
private float probeScale = 1.0f;
private Spatial scene = null;
private final List<LightProbe> probes = new ArrayList<LightProbe>();
/**
* Debug mode for light probes
*/
public enum DebugMode {
/**
* Displays the prefiltered env maps on the debug sphere
*/
PrefilteredEnvMap,
/**
* displays the Irradiance map on the debug sphere
*/
IrradianceMap
}
@Override
protected void initialize(Application app) {
debugNode = new Node("Environment debug Node");
Sphere s = new Sphere(16, 16, 1);
debugGeom = new Geometry("debugEnvProbe", s);
debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md");
debugGeom.setMaterial(debugMaterial);
debugBounds = BoundingSphereDebug.createDebugSphere(app.getAssetManager());
if (scene == null) {
scene = app.getViewPort().getScenes().get(0);
}
}
@Override
public void update(float tpf) {
for (Light light : scene.getWorldLightList()) {
switch (light.getType()) {
case Probe:
LightProbe probe = (LightProbe) light;
probes.add(probe);
Node n = probeMapping.get(probe);
if (n == null) {
n = new Node("DebugProbe");
n.attachChild(debugGeom.clone(true));
n.attachChild(debugBounds.clone(false));
debugNode.attachChild(n);
probeMapping.put(probe, n);
}
Geometry probeGeom = ((Geometry) n.getChild(0));
Material m = probeGeom.getMaterial();
probeGeom.setLocalScale(probeScale);
if (probe.isReady()) {
if (debugMode == DebugMode.IrradianceMap) {
m.setTexture("CubeMap", probe.getIrradianceMap());
} else {
m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
}
}
n.setLocalTranslation(probe.getPosition());
n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
break;
default:
break;
}
}
debugNode.updateLogicalState(tpf);
debugNode.updateGeometricState();
cleanProbes();
}
/**
* Set the scenes for wich to render light gizmos.
* @param scene
*/
public void setScene(Spatial scene) {
this.scene = scene;
}
private void cleanProbes() {
if (probes.size() != probeMapping.size()) {
for (LightProbe probe : probeMapping.keySet()) {
if (!probes.contains(probe)) {
garbage.add(probe);
}
}
for (LightProbe probe : garbage) {
probeMapping.remove(probe);
}
garbage.clear();
probes.clear();
}
}
@Override
public void render(RenderManager rm) {
rm.renderScene(debugNode, getApplication().getViewPort());
}
/**
*
* @see DebugMode
* @return the debug mode
*/
public DebugMode getDebugMode() {
return debugMode;
}
/**
* sets the debug mode
* @see DebugMode
* @param debugMode the debug mode
*/
public void setDebugMode(DebugMode debugMode) {
this.debugMode = debugMode;
}
/**
* returns the scale of the probe's debug sphere
* @return
*/
public float getProbeScale() {
return probeScale;
}
/**
* sets the scale of the probe's debug sphere
* @param probeScale
*/
public void setProbeScale(float probeScale) {
this.probeScale = probeScale;
}
@Override
protected void cleanup(Application app) {
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}

@ -55,6 +55,7 @@ public final class DefaultLightFilter implements LightFilter {
TempVars vars = TempVars.get();
try {
LightList worldLights = geometry.getWorldLightList();
boolean probeAdded = false;
for (int i = 0; i < worldLights.size(); i++) {
Light light = worldLights.get(i);
@ -80,8 +81,17 @@ public final class DefaultLightFilter implements LightFilter {
throw new UnsupportedOperationException("Only AABB supported for now");
}
}
if (light.getType() == Light.Type.Probe) {
if (!probeAdded && ((LightProbe)light).isReady()) {
//only adding the first probe (the closest to the geom as lights are sorted by the distance to the geom
probeAdded = true;
filteredLightList.add(light);
}
filteredLightList.add(light);
} else {
filteredLightList.add(light);
}
}
} finally {
vars.release();

@ -77,7 +77,14 @@ public abstract class Light implements Savable, Cloneable {
*
* @see AmbientLight
*/
Ambient(3);
Ambient(3),
/**
* Light probe
* @see LightProbe
*/
Probe(4);
private int typeId;

@ -0,0 +1,251 @@
/*
* 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.asset.AssetManager;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume;
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.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;
/**
* A LightProbe is not exactly a light. It holds environment map information used for Image Based Lighting.
* This is used for indirect lighting in the Physically Based Rendering pipeline.
*
* A light probe has a position in world space. This is the position from where the Environment Map are rendered.
* There are two environment maps held by the LightProbe :
* - The irradiance map (used for indirect diffuse lighting in the PBR pipeline).
* - The prefiltered environment map (used for indirect specular lighting and reflection in the PBE pipeline).
* Note that when instanciating the LightProbe, both those maps are null.
* To render 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).
*
* A LightProbe will only be taken into account when it's marked as ready.
* A light probe is ready when it has valid environment map data set.
* Note that you should never call setReady yourself.
*
* @see LightProbeFactory
* @see EnvironmentCamera
* @author nehon
*/
public class LightProbe extends Light implements Savable {
private TextureCubeMap irradianceMap;
private TextureCubeMap prefilteredEnvMap;
private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
private boolean ready = false;
private Vector3f position = new Vector3f();
private Node debugNode;
/**
* Empty constructor used for serialization.
* You should never call it, use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} instead
*/
public LightProbe() {
}
/**
* returns the irradiance map texture of this Light probe.
* Note that this Texture may not have image data yet if the LightProbe is not ready
* @return the irradiance map
*/
public TextureCubeMap getIrradianceMap() {
return irradianceMap;
}
/**
* Sets the irradiance map
* @param irradianceMap the irradiance map
*/
public void setIrradianceMap(TextureCubeMap irradianceMap) {
this.irradianceMap = irradianceMap;
}
/**
* returns the prefiltered environment map texture of this light probe
* Note that this Texture may not have image data yet if the LightProbe is not ready
* @return the prefiltered environment map
*/
public TextureCubeMap getPrefilteredEnvMap() {
return prefilteredEnvMap;
}
/**
* Sets the prefiltered environment map
* @param prefileteredEnvMap the prefiltered environment map
*/
public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) {
this.prefilteredEnvMap = prefileteredEnvMap;
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(irradianceMap, "irradianceMap", null);
oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
oc.write(position, "position", null);
oc.write(bounds, "bounds", bounds);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
irradianceMap = (TextureCubeMap) ic.readSavable("irradianceMap", null);
prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
position = (Vector3f) ic.readSavable("position", this);
bounds = (BoundingVolume) ic.readSavable("bounds", bounds);
}
/**
* returns the bounding volume of this LightProbe
* @return a bounding volume.
*/
public BoundingVolume getBounds() {
return bounds;
}
/**
* Sets the bounds of this LightProbe
* 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
*/
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;
}
/**
* return true if the LightProbe is ready, meaning the Environment maps have
* been loaded or rnedered and are ready to be used by a material
* @return the LightProbe ready state
*/
public boolean isReady() {
return ready;
}
/**
* Don't call this method directly.
* It's meant to be called by additional systems that will load or render
* the Environment maps of the LightProbe
* @param ready the ready state of the LightProbe.
*/
public void setReady(boolean 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 EnvProbeis not ready yet, try to test isReady()");
}
if (debugNode == null) {
debugNode = new Node("debug gui probe");
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager);
Node debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(getIrradianceMap(), manager);
debugNode.attachChild(debugIrrCm);
debugNode.attachChild(debugPfemCm);
debugPfemCm.setLocalTranslation(520, 0, 0);
}
return debugNode;
}
/**
* Returns the position of the LightProbe in world space
* @return the wolrd space position
*/
public Vector3f getPosition() {
return position;
}
/**
* Sets the position of the LightProbe in world space
* @param position the wolrd space position
*/
public void setPosition(Vector3f position) {
this.position.set(position);
}
@Override
public boolean intersectsBox(BoundingBox box, TempVars vars) {
return getBounds().intersectsBoundingBox(box);
}
@Override
public boolean intersectsFrustum(Camera camera, TempVars vars) {
return camera.contains(bounds) != Camera.FrustumIntersect.Outside;
}
@Override
protected void computeLastDistance(Spatial owner) {
if (owner.getWorldBound() != null) {
BoundingVolume bv = owner.getWorldBound();
lastDistance = bv.distanceSquaredTo(position);
} else {
lastDistance = owner.getWorldTranslation().distanceSquared(position);
}
}
@Override
public Type getType() {
return Type.Probe;
}
}

@ -34,6 +34,7 @@ package com.jme3.material;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetManager;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.bounding.BoundingSphere;
import com.jme3.export.*;
import com.jme3.light.*;
import com.jme3.material.RenderState.BlendMode;
@ -104,6 +105,10 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
private int sortingId = -1;
private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
//Env textures units
int irrUnit = -1;
int pemUnit = -1;
public Material(MaterialDef def) {
if (def == null) {
throw new NullPointerException("Material definition cannot be null");
@ -505,22 +510,26 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
paramValues.remove(name);
if (matParam instanceof MatParamTexture) {
int texUnit = ((MatParamTexture) matParam).getUnit();
nextTexUnit--;
for (MatParam param : paramValues.values()) {
if (param instanceof MatParamTexture) {
MatParamTexture texParam = (MatParamTexture) param;
if (texParam.getUnit() > texUnit) {
texParam.setUnit(texParam.getUnit() - 1);
}
}
}
sortingId = -1;
removeTexUnit(texUnit);
}
if (technique != null) {
technique.notifyParamChanged(name, null, null);
}
}
protected void removeTexUnit(int texUnit) {
nextTexUnit--;
for (MatParam param : paramValues.values()) {
if (param instanceof MatParamTexture) {
MatParamTexture texParam = (MatParamTexture) param;
if (texParam.getUnit() > texUnit) {
texParam.setUnit(texParam.getUnit() - 1);
}
}
}
sortingId = -1;
}
/**
* Set a texture parameter.
*
@ -750,8 +759,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
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");
Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap");
Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
if (startIndex != 0) {
// apply additive blending for 2nd and future passes
rm.getRenderer().applyRenderState(additiveLight);
@ -825,6 +837,32 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
lightDataIndex++;
break;
case Probe:
//There should be a better way to handle these texture units
//for now they are removed and reassign on every frame which is a waste.
if (irrUnit != -1) {
lightProbeIrrMap.clearValue();
lightProbePemMap.clearValue();
removeTexUnit(irrUnit);
removeTexUnit(pemUnit);
irrUnit = -1;
pemUnit = -1;
}
endIndex++;
LightProbe probe = (LightProbe)l;
BoundingSphere s = (BoundingSphere)probe.getBounds();
tmpVec.set(probe.getPosition().x, probe.getPosition().y, probe.getPosition().z, 1f/s.getRadius());
lightProbeData.setValue(VarType.Vector4, tmpVec);
if( irrUnit == -1 ){
irrUnit = nextTexUnit++;
pemUnit = nextTexUnit++;
}
rm.getRenderer().setTexture(irrUnit, probe.getIrradianceMap());
lightProbeIrrMap.setValue(VarType.Int, irrUnit);
rm.getRenderer().setTexture(pemUnit, probe.getPrefilteredEnvMap());
lightProbePemMap.setValue(VarType.Int, pemUnit);
break;
default:
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
}

@ -19,11 +19,12 @@ uniform float m_Metallic;
varying vec3 wPosition;
#ifdef INDIRECT_LIGHTING
uniform sampler2D m_IntegrateBRDF;
uniform samplerCube m_PrefEnvMap;
uniform samplerCube m_IrradianceMap;
#endif
//#ifdef INDIRECT_LIGHTING
// uniform sampler2D m_IntegrateBRDF;
uniform samplerCube g_PrefEnvMap;
uniform samplerCube g_IrradianceMap;
uniform vec4 g_ProbeData;
//#endif
#ifdef BASECOLORMAP
uniform sampler2D m_BaseColorMap;
@ -201,8 +202,10 @@ void main(){
gl_FragColor.rgb += directLighting * fallOff;
}
#ifdef INDIRECT_LIGHTING
vec3 rv = reflect(-viewDir.xyz, normal.xyz);
// #ifdef INDIRECT_LIGHTING
vec3 rv = reflect(-viewDir.xyz, normal.xyz);
//prallax fix for spherical bounds.
rv = g_ProbeData.w * (wPosition - g_ProbeData.xyz) +rv;
//horizon fade from http://marmosetco.tumblr.com/post/81245981087
float horiz = dot(rv, wNormal.xyz);
@ -212,15 +215,15 @@ void main(){
vec3 indirectDiffuse = vec3(0.0);
vec3 indirectSpecular = vec3(0.0);
indirectDiffuse = textureCube(m_IrradianceMap, rv.xyz).rgb * albedo.rgb;
indirectDiffuse = textureCube(g_IrradianceMap, rv.xyz).rgb * albedo.rgb;
indirectSpecular = ApproximateSpecularIBL(m_PrefEnvMap,m_IntegrateBRDF, specularColor.rgb, Roughness, ndotv, rv.xyz);
indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, rv.xyz);
indirectSpecular *= vec3(horiz);
vec3 indirectLighting = indirectDiffuse + indirectSpecular;
gl_FragColor.rgb = gl_FragColor.rgb + indirectLighting ;
#endif
// #endif
#if defined(EMISSIVE) || defined (EMISSIVEMAP)
#ifdef EMISSIVEMAP

@ -37,6 +37,8 @@ MaterialDef PBR Lighting {
Texture2D SpecularMap
Texture2D GlossMap
Vector4 ProbeData
// Prefiltered Env Map for indirect specular lighting
TextureCubeMap PrefEnvMap -LINEAR
@ -132,7 +134,7 @@ MaterialDef PBR Lighting {
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
INDIRECT_LIGHTING : IntegrateBRDF
//INDIRECT_LIGHTING : IntegrateBRDF
VERTEX_COLOR : UseVertexColor
}
}

@ -0,0 +1,45 @@
MaterialDef Simple {
MaterialParameters {
TextureCubeMap CubeMap
}
Technique {
WorldParameters {
WorldViewProjectionMatrix
WorldMatrix
CameraPosition
}
VertexShaderNodes {
ShaderNode Reflect {
Definition : Reflect : Common/MatDefs/ShaderNodes/Environment/reflect.j3sn
InputMappings {
normal = Attr.inNormal
position = Global.position.xyz
worldMatrix = WorldParam.WorldMatrix
camPosition = WorldParam.CameraPosition
}
}
ShaderNode CommonVert {
Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn
InputMappings {
worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
modelPosition = Global.position.xyz
}
OutputMappings {
Global.position = projPosition
}
}
}
FragmentShaderNodes {
ShaderNode EnvMapping {
Definition : EnvMapping : Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn
InputMappings {
refVec = Reflect.refVec
cubeMap = MatParam.CubeMap
}
OutputMappings {
Global.color = color
}
}
}
}
}

@ -0,0 +1,22 @@
ShaderNodeDefinitions{
ShaderNodeDefinition EnvMapping {
Type: Fragment
Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/envMapping100.frag
Shader GLSL130: Common/MatDefs/ShaderNodes/Environment/envMapping130.frag
Documentation{
fetches a texel in a cube map
@input vec3 refVec the reflection vector
@input samplerCube cubeMap the cube map
@output vec4 color the output color
}
Input {
vec3 refVec
samplerCube cubeMap
}
Output {
vec4 color
}
}
}

@ -0,0 +1,8 @@
void main(){
//@input vec3 refVec the reflection vector
//@input samplerCube cubeMap the cube map
//@output vec4 color the output color
color = textureCube(cubeMap, refVec, 0.0);
}

@ -0,0 +1,8 @@
void main(){
//@input vec3 refVec the reflection vector
//@input samplerCube cubeMap the cube map
//@output vec4 color the output color
color = texture(cubeMap, refVec, 0.0);
}

@ -0,0 +1,25 @@
ShaderNodeDefinitions{
ShaderNodeDefinition Reflect {
Type: Vertex
Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/reflect100.vert
Documentation{
Computes the relfection vector necessary to do some environment mapping
@input vec3 position position in model space
@input vec3 normal the normal of the vertex
@input vec3 camPosition camera position in world space
@input mat4 worldMatrix the world matrix
@output vec3 refVec the reflection vector
}
Input {
vec3 position
vec3 normal
vec3 camPosition
mat4 worldMatrix
}
Output {
vec3 refVec
}
}
}

@ -0,0 +1,14 @@
void main(){
//@input vec3 position position in model space
//@input vec3 normal the normal of the vertex
//@input vec3 camPosition camera position in world space
//@input mat4 worldMatrix the world view matrix
//@output vec3 refVec the reflection vector
vec3 worldPos = (worldMatrix * vec4(position, 1.0)).xyz;
vec3 N = normalize((worldMatrix * vec4(normal, 0.0)).xyz);
vec3 I = normalize( camPosition - worldPos ).xyz;
refVec.xyz = reflect(-I, N);
}

@ -106,6 +106,31 @@ void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir,
outSpecular = vec3(specular) * lightColor;
}
vec3 EnvDFGPolynomial( vec3 specularColor, float roughness, float ndotv ){
float x = 1.0 - roughness;
float y = ndotv;
float b1 = -0.1688;
float b2 = 1.895;
float b3 = 0.9903;
float b4 = -4.853;
float b5 = 8.404;
float b6 = -5.069;
float bias = clamp( min( b1 * x + b2 * x * x, b3 + b4 * y + b5 * y * y + b6 * y * y * y ), 0.0, 1.0 );
float d0 = 0.6045;
float d1 = 1.699;
float d2 = -0.5228;
float d3 = -3.603;
float d4 = 1.404;
float d5 = 0.1939;
float d6 = 2.661;
float delta = clamp(( d0 + d1 * x + d2 * y + d3 * x * x + d4 * x * y + d5 * y * y + d6 * x * x * x ), 0.0, 1.0);
float scale = delta - bias;
bias *= clamp( 50.0 * specularColor.y, 0.0, 1.0 );
return specularColor * scale + bias;
}
vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
//TODO magic values should be replaced by defines.
@ -115,6 +140,14 @@ vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 Spe
return PrefilteredColor * ( SpecularColor * EnvBRDF.x+ EnvBRDF.y );
}
vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
//TODO magic values should be replaced by defines.
float Lod = log2(Roughness) * 1.2 + 6.0 - 1.0;
vec3 PrefilteredColor = textureCube(envMap, refVec.xyz,Lod).rgb;
return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv);
}

@ -0,0 +1,90 @@
package jme3test.light;
import com.jme3.app.SimpleApplication;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.light.DirectionalLight;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.DirectionalLightShadowRenderer;
public class TestColorApp extends SimpleApplication {
public static void main(String[] args) {
TestColorApp app = new TestColorApp();
app.start();
}
@Override
public void simpleInitApp() {
// Lights
DirectionalLight sun = new DirectionalLight();
Vector3f sunPosition = new Vector3f(1, -1, 1);
sun.setDirection(sunPosition);
sun.setColor(new ColorRGBA(1f,1f,1f,1f));
rootNode.addLight(sun);
//DirectionalLightShadowFilter sun_renderer = new DirectionalLightShadowFilter(assetManager, 2048, 4);
DirectionalLightShadowRenderer sun_renderer = new DirectionalLightShadowRenderer(assetManager, 2048, 1);
sun_renderer.setLight(sun);
viewPort.addProcessor(sun_renderer);
// FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
// fpp.addFilter(sun_renderer);
// viewPort.addProcessor(fpp);
rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
// Camera
viewPort.setBackgroundColor(new ColorRGBA(.6f, .6f, .6f, 1f));
ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
// Objects
// Ground Object
final Geometry groundBoxWhite = new Geometry("Box", new Box(7.5f, 7.5f, .25f));
float[] f = {-FastMath.PI / 2, 3 * FastMath.PI / 2, 0f};
groundBoxWhite.setLocalRotation(new Quaternion(f));
groundBoxWhite.move(7.5f, -.75f, 7.5f);
final Material groundMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
groundMaterial.setColor("Diffuse", new ColorRGBA(.9f, .9f, .9f, .9f));
groundBoxWhite.setMaterial(groundMaterial);
groundBoxWhite.addControl(chaseCam);
rootNode.attachChild(groundBoxWhite);
// Planter
Geometry planterBox = new Geometry("Box", new Box(.5f, .5f, .5f));
final Material planterMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
planterMaterial.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
planterBox.setMaterial(groundMaterial);
planterBox.setLocalTranslation(10, 0, 9);
rootNode.attachChild(planterBox);
// Action!
inputManager.addMapping("on", new KeyTrigger(KeyInput.KEY_Z));
inputManager.addMapping("off", new KeyTrigger(KeyInput.KEY_X));
inputManager.addListener(new AnalogListener() {
@Override
public void onAnalog(String s, float v, float v1) {
if (s.equals("on")) {
groundBoxWhite.setMaterial(planterMaterial);
}
if (s.equals("off")) {
groundBoxWhite.setMaterial(groundMaterial);
}
}
}, "on", "off");
inputEnabled = true;
}
}

@ -0,0 +1,96 @@
/*
* 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 jme3test.light;
import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.util.TangentBinormalGenerator;
/**
*
* @author Nehon
*/
public class TestTangentCube extends SimpleApplication {
public static void main(String... args) {
TestTangentCube app = new TestTangentCube();
app.start();
}
@Override
public void simpleInitApp() {
Box aBox = new Box(1, 1, 1);
Geometry aGeometry = new Geometry("Box", aBox);
TangentBinormalGenerator.generate(aBox);
Material aMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
aMaterial.setTexture("DiffuseMap",
assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
aMaterial.setTexture("NormalMap",
assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall_normal.jpg"));
aMaterial.setBoolean("UseMaterialColors", false);
aMaterial.setColor("Diffuse", ColorRGBA.White);
aMaterial.setColor("Specular", ColorRGBA.White);
aMaterial.setFloat("Shininess", 64f);
aGeometry.setMaterial(aMaterial);
// Rotate 45 degrees to see multiple faces
aGeometry.rotate(FastMath.QUARTER_PI, FastMath.QUARTER_PI, 0.0f);
rootNode.attachChild(aGeometry);
/**
* Must add a light to make the lit object visible!
*/
PointLight aLight = new PointLight();
aLight.setPosition(new Vector3f(0, 3, 3));
aLight.setColor(ColorRGBA.Red);
rootNode.addLight(aLight);
//
// AmbientLight bLight = new AmbientLight();
// bLight.setColor(ColorRGBA.Gray);
// rootNode.addLight(bLight);
ChaseCameraAppState chaser = new ChaseCameraAppState();
chaser.setTarget(aGeometry);
getStateManager().attach(chaser);
}
}

@ -0,0 +1,71 @@
/*
* 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 jme3test.light.pbr;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.light.LightProbe;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A basic logger for environment map rendering progress.
* @author nehon
*/
public class ConsoleProgressReporter extends JobProgressAdapter<LightProbe>{
private static final Logger logger = Logger.getLogger(ConsoleProgressReporter.class.getName());
long time;
@Override
public void start() {
time = System.currentTimeMillis();
logger.log(Level.INFO,"Starting generation");
}
@Override
public void progress(double value) {
logger.log(Level.INFO, "Progress : {0}%", (value * 100));
}
@Override
public void step(String message) {
logger.info(message);
}
@Override
public void done(LightProbe result) {
long end = System.currentTimeMillis();
logger.log(Level.INFO, "Generation done in {0}", ((float)(end - time) / 1000f));
}
}

@ -1,6 +1,43 @@
/*
* 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 jme3test.light.pbr;
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingSphere;
import com.jme3.light.LightProbe;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.environment.util.LightsDebugState;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
@ -13,11 +50,12 @@ import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.FXAAFilter;
import com.jme3.post.filters.ToneMapFilter;
import com.jme3.post.ssao.SSAOFilter;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.pbr.EnvironmentCamera;
import com.jme3.texture.plugins.ktx.KTXLoader;
import com.jme3.util.MaterialDebugAppState;
import com.jme3.util.SkyFactory;
/**
@ -35,10 +73,8 @@ public class TestPBRLighting extends SimpleApplication {
private Geometry model;
private DirectionalLight dl;
private Node modelNode;
private int frame = 0;
private boolean indirectLighting = true;
private Material pbrMat;
private Material adHocMat;
private int frame = 0;
private Material pbrMat;
@Override
public void simpleInitApp() {
@ -55,22 +91,34 @@ public class TestPBRLighting extends SimpleApplication {
dl.setColor(ColorRGBA.White);
rootNode.attachChild(modelNode);
final EnvironmentCamera envCam = new EnvironmentCamera(128, new Vector3f(0, 3f, 0));
stateManager.attach(envCam);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(new FXAAFilter());
fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(2.0f)));
fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f)));
fpp.addFilter(new SSAOFilter(0.5f, 3, 0.2f, 0.2f));
viewPort.addProcessor(fpp);
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Sky_Cloudy.hdr", SkyFactory.EnvMapType.EquirectMap);
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Stonewall.hdr", SkyFactory.EnvMapType.EquirectMap);
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap);
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/road.hdr", SkyFactory.EnvMapType.EquirectMap);
rootNode.attachChild(sky);
pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
model.setMaterial(pbrMat);
final EnvironmentCamera envCam = new EnvironmentCamera(128, new Vector3f(0, 3f, 0));
stateManager.attach(envCam);
// EnvironmentManager envManager = new EnvironmentManager();
// stateManager.attach(envManager);
// envManager.setScene(rootNode);
LightsDebugState debugState = new LightsDebugState();
stateManager.attach(debugState);
ChaseCamera chaser = new ChaseCamera(cam, modelNode, inputManager);
chaser.setDragToRotate(true);
chaser.setMinVerticalRotation(-FastMath.HALF_PI);
@ -84,26 +132,8 @@ public class TestPBRLighting extends SimpleApplication {
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("toggle") && isPressed) {
if (!indirectLighting) {
toggleIBL();
} else {
pbrMat.clearParam("IntegrateBRDF");
indirectLighting = false;
}
}
if (name.equals("switchMats") && isPressed) {
if (model.getMaterial() == pbrMat) {
model.setMaterial(adHocMat);
} else {
model.setMaterial(pbrMat);
}
}
if (name.equals("debug") && isPressed) {
envCam.toggleDebug();
//envCam.toggleDebug();
}
if (name.equals("up") && isPressed) {
@ -132,41 +162,36 @@ public class TestPBRLighting extends SimpleApplication {
inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT));
inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT));
inputManager.addMapping("debug", new KeyTrigger(KeyInput.KEY_D));
MaterialDebugAppState debug = new MaterialDebugAppState();
debug.registerBinding("Common/MatDefs/Light/PBRLighting.frag", rootNode);
getStateManager().attach(debug);
}
private void toggleIBL() {
ensurePbrMat();
pbrMat.setTexture("IrradianceMap", stateManager.getState(EnvironmentCamera.class).getIrradianceMap());
pbrMat.setTexture("PrefEnvMap", stateManager.getState(EnvironmentCamera.class).getPrefilteredEnvMap());
pbrMat.setTexture("IntegrateBRDF", assetManager.loadTexture("Common/Textures/integrateBRDF.ktx"));
indirectLighting = true;
}
private void ensurePbrMat() {
if (model.getMaterial() != pbrMat && model.getMaterial() != adHocMat) {
pbrMat = model.getMaterial();
}
}
@Override
public void simpleUpdate(float tpf) {
frame++;
if (frame == 2) {
modelNode.removeFromParent();
stateManager.getState(EnvironmentCamera.class).snapshot(rootNode, new Runnable() {
//this code is ensured to be called in the update loop, the run method is called by the EnvCamera app state in it's update cycle
final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
@Override
public void run() {
toggleIBL();
public void done(LightProbe result) {
System.err.println("Done rendering env maps");
}
});
((BoundingSphere)probe.getBounds()).setRadius(100);
rootNode.addLight(probe);
//getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
}
if (frame > 2 && modelNode.getParent() == null) {
if (frame > 10 && modelNode.getParent() == null) {
rootNode.attachChild(modelNode);
}
}
}

@ -0,0 +1,378 @@
/*
* 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 jme3test.light.pbr;
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingSphere;
import com.jme3.input.CameraInput;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.environment.LightProbeFactory;
import com.jme3.environment.EnvironmentCamera;
import com.jme3.environment.util.LightsDebugState;
import com.jme3.light.LightProbe;
import com.jme3.material.TechniqueDef;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.post.filters.FXAAFilter;
import com.jme3.post.filters.ToneMapFilter;
import com.jme3.post.ssao.SSAOFilter;
import com.jme3.scene.Node;
import com.jme3.texture.plugins.ktx.KTXLoader;
import com.jme3.util.SkyFactory;
import com.jme3.util.TangentBinormalGenerator;
public class TestPbrEnv extends SimpleApplication implements ActionListener {
public static final int SHADOWMAP_SIZE = 1024;
private Spatial[] obj;
private Material[] mat;
private DirectionalLightShadowRenderer dlsr;
private LightsDebugState debugState;
private EnvironmentCamera envCam;
private Geometry ground;
private Material matGroundU;
private Material matGroundL;
private Geometry camGeom;
public static void main(String[] args) {
TestPbrEnv app = new TestPbrEnv();
app.start();
}
public void loadScene() {
renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass);
renderManager.setSinglePassLightBatchSize(3);
obj = new Spatial[2];
// Setup first view
mat = new Material[2];
mat[0] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat.j3m");
//mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
mat[1] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat2.j3m");
// mat[1].setBoolean("UseMaterialColors", true);
// mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f));
// mat[1].setColor("Diffuse", ColorRGBA.White.clone());
obj[0] = new Geometry("sphere", new Sphere(30, 30, 2));
obj[0].setShadowMode(ShadowMode.CastAndReceive);
obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f));
obj[1].setShadowMode(ShadowMode.CastAndReceive);
TangentBinormalGenerator.generate(obj[1]);
TangentBinormalGenerator.generate(obj[0]);
// for (int i = 0; i < 60; i++) {
// Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
// t.setName("Cube" + i);
// t.setLocalScale(FastMath.nextRandomFloat() * 10f);
// t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]);
// rootNode.attachChild(t);
// t.setLocalTranslation(FastMath.nextRandomFloat() * 200f, FastMath.nextRandomFloat() * 30f + 20, 30f * (i + 2f));
// }
for (int i = 0; i < 2; i++) {
Spatial t = obj[0].clone(false);
t.setName("Cube" + i);
t.setLocalScale( 10f);
t.setMaterial(mat[1].clone());
rootNode.attachChild(t);
t.setLocalTranslation(i * 200f+ 100f, 50, 800f * (i));
}
Box b = new Box(1000, 2, 1000);
b.scaleTextureCoordinates(new Vector2f(20, 20));
ground = new Geometry("soil", b);
TangentBinormalGenerator.generate(ground);
ground.setLocalTranslation(0, 10, 550);
matGroundU = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matGroundU.setColor("Color", ColorRGBA.Green);
// matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
// Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
// grass.setWrap(WrapMode.Repeat);
// matGroundL.setTexture("DiffuseMap", grass);
matGroundL = assetManager.loadMaterial("jme3test/light/pbr/pbrMat4.j3m");
ground.setMaterial(matGroundL);
//ground.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(ground);
l = new DirectionalLight();
l.setColor(ColorRGBA.White);
//l.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal());
l.setDirection(new Vector3f(-0.2823181f, -0.41889593f, 0.863031f).normalizeLocal());
rootNode.addLight(l);
AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(0.5f));
// rootNode.addLight(al);
//Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap);
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
sky.setLocalScale(350);
rootNode.attachChild(sky);
}
DirectionalLight l;
@Override
public void simpleInitApp() {
assetManager.registerLoader(KTXLoader.class, "ktx");
// put the camera in a bad position
cam.setLocation(new Vector3f(-52.433647f, 68.69636f, -118.60924f));
cam.setRotation(new Quaternion(0.10294232f, 0.25269797f, -0.027049713f, 0.96167296f));
flyCam.setMoveSpeed(100);
loadScene();
dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4);
dlsr.setLight(l);
//dlsr.setLambda(0.55f);
dlsr.setShadowIntensity(0.5f);
dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
//dlsr.displayDebug();
// viewPort.addProcessor(dlsr);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(6.0f)));
SSAOFilter ssao = new SSAOFilter();
ssao.setIntensity(5);
fpp.addFilter(ssao);
BloomFilter bloomFilter = new BloomFilter();
fpp.addFilter(bloomFilter);
fpp.addFilter(new FXAAFilter());
//viewPort.addProcessor(fpp);
initInputs();
// envManager = new EnvironmentManager();
// getStateManager().attach(envManager);
//
envCam = new EnvironmentCamera();
getStateManager().attach(envCam);
debugState = new LightsDebugState();
debugState.setProbeScale(5);
getStateManager().attach(debugState);
camGeom = new Geometry("camGeom", new Sphere(16, 16, 2));
// Material m = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md");
// m.setColor("Color", ColorRGBA.Green);
Material m = assetManager.loadMaterial("jme3test/light/pbr/pbrMat3.j3m");
camGeom.setMaterial(m);
camGeom.setLocalTranslation(0, 20, 0);
camGeom.setLocalScale(5);
rootNode.attachChild(camGeom);
// envManager.setScene(rootNode);
// MaterialDebugAppState debug = new MaterialDebugAppState();
// debug.registerBinding("MatDefs/PBRLighting.frag", rootNode);
// getStateManager().attach(debug);
flyCam.setDragToRotate(true);
setPauseOnLostFocus(false);
// cam.lookAt(camGeom.getWorldTranslation(), Vector3f.UNIT_Y);
}
private void fixFLyCamInputs() {
inputManager.deleteMapping(CameraInput.FLYCAM_LEFT);
inputManager.deleteMapping(CameraInput.FLYCAM_RIGHT);
inputManager.deleteMapping(CameraInput.FLYCAM_UP);
inputManager.deleteMapping(CameraInput.FLYCAM_DOWN);
inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true));
inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false));
inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
inputManager.addListener(flyCam, CameraInput.FLYCAM_LEFT, CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_UP, CameraInput.FLYCAM_DOWN);
}
private void initInputs() {
inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M));
inputManager.addMapping("snapshot", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addMapping("fc", new KeyTrigger(KeyInput.KEY_F));
inputManager.addMapping("debugProbe", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addMapping("debugTex", new KeyTrigger(KeyInput.KEY_T));
inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP));
inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN));
inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT));
inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT));
inputManager.addListener(this, "switchGroundMat", "snapshot", "debugTex", "debugProbe", "fc", "up", "down", "left", "right");
}
private LightProbe lastProbe;
private Node debugGui ;
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("switchGroundMat") && keyPressed) {
if (ground.getMaterial() == matGroundL) {
ground.setMaterial(matGroundU);
} else {
ground.setMaterial(matGroundL);
}
}
if (name.equals("snapshot") && keyPressed) {
envCam.setPosition(camGeom.getWorldTranslation());
lastProbe = LightProbeFactory.makeProbe(envCam, rootNode, new ConsoleProgressReporter());
((BoundingSphere)lastProbe.getBounds()).setRadius(200);
rootNode.addLight(lastProbe);
}
if (name.equals("fc") && keyPressed) {
flyCam.setEnabled(true);
}
if (name.equals("debugProbe") && keyPressed) {
//getStateManager().getState(EnvironmentCamera.class).toggleDebug();
if (!debugState.isEnabled()) {
debugState.setEnabled(true);
debugState.setDebugMode(LightsDebugState.DebugMode.IrradianceMap);
} else if (debugState.getDebugMode() == LightsDebugState.DebugMode.IrradianceMap) {
debugState.setDebugMode(LightsDebugState.DebugMode.PrefilteredEnvMap);
} else if (debugState.getDebugMode() == LightsDebugState.DebugMode.PrefilteredEnvMap) {
debugState.setEnabled(false);
}
}
if (name.equals("debugTex") && keyPressed) {
if(debugGui == null || debugGui.getParent() == null){
debugGui = lastProbe.getDebugGui(assetManager);
debugGui.setLocalTranslation(10, 200, 0);
guiNode.attachChild(debugGui);
} else if(debugGui != null){
debugGui.removeFromParent();
}
}
if (name.equals("up")) {
up = keyPressed;
}
if (name.equals("down")) {
down = keyPressed;
}
if (name.equals("right")) {
right = keyPressed;
}
if (name.equals("left")) {
left = keyPressed;
}
if (name.equals("fwd")) {
fwd = keyPressed;
}
if (name.equals("back")) {
back = keyPressed;
}
}
boolean up = false;
boolean down = false;
boolean left = false;
boolean right = false;
boolean fwd = false;
boolean back = false;
float time = 0;
float s = 50f;
boolean initialized = false;
@Override
public void simpleUpdate(float tpf) {
if (!initialized) {
fixFLyCamInputs();
initialized = true;
}
float val = tpf * s;
if (up) {
camGeom.move(0, 0, val);
}
if (down) {
camGeom.move(0, 0, -val);
}
if (right) {
camGeom.move(-val, 0, 0);
}
if (left) {
camGeom.move(val, 0, 0);
}
}
}

@ -0,0 +1,180 @@
/*
* 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 jme3test.post;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.post.filters.BloomFilter.GlowMode;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.WireFrustum;
import com.jme3.scene.shape.Box;
import com.jme3.util.SkyFactory;
public class TestBloomAlphaThreshold extends SimpleApplication
{
float angle;
Spatial lightMdl;
Spatial teapot;
Geometry frustumMdl;
WireFrustum frustum;
boolean active = true;
FilterPostProcessor fpp;
public static void main(String[] args)
{
TestBloomAlphaThreshold app = new TestBloomAlphaThreshold();
app.start();
}
@Override
public void simpleInitApp()
{
// put the camera in a bad position
cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -10));
cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f));
// cam.setFrustumFar(1000);
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.setFloat("Shininess", 15f);
mat.setBoolean("UseMaterialColors", true);
mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f));
mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f));
mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f));
mat.setColor("GlowColor", ColorRGBA.Green);
Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
matSoil.setFloat("Shininess", 15f);
matSoil.setBoolean("UseMaterialColors", true);
matSoil.setColor("Ambient", ColorRGBA.Gray);
matSoil.setColor("Diffuse", ColorRGBA.Black);
matSoil.setColor("Specular", ColorRGBA.Gray);
teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
teapot.setLocalTranslation(0, 0, 10);
teapot.setMaterial(mat);
teapot.setShadowMode(ShadowMode.CastAndReceive);
teapot.setLocalScale(10.0f);
rootNode.attachChild(teapot);
Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700));
soil.setMaterial(matSoil);
soil.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(soil);
Material matBox = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matBox.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
matBox.setFloat("AlphaDiscardThreshold", 0.5f);
Geometry box = new Geometry("box", new Box(new Vector3f(-3.5f, 10, -2), 2, 2, 2));
box.setMaterial(matBox);
box.setQueueBucket(RenderQueue.Bucket.Translucent);
// box.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(box);
DirectionalLight light = new DirectionalLight();
light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
light.setColor(ColorRGBA.White.mult(1.5f));
rootNode.addLight(light);
// load sky
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false);
sky.setCullHint(Spatial.CullHint.Never);
rootNode.attachChild(sky);
fpp = new FilterPostProcessor(assetManager);
int numSamples = getContext().getSettings().getSamples();
if (numSamples > 0)
{
fpp.setNumSamples(numSamples);
}
BloomFilter bloom = new BloomFilter(GlowMode.Objects);
bloom.setDownSamplingFactor(2);
bloom.setBlurScale(1.37f);
bloom.setExposurePower(3.30f);
bloom.setExposureCutOff(0.2f);
bloom.setBloomIntensity(2.45f);
BloomUI ui = new BloomUI(inputManager, bloom);
viewPort.addProcessor(fpp);
fpp.addFilter(bloom);
initInputs();
}
private void initInputs()
{
inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
ActionListener acl = new ActionListener()
{
@Override
public void onAction(String name, boolean keyPressed, float tpf)
{
if (name.equals("toggle") && keyPressed)
{
if (active)
{
active = false;
viewPort.removeProcessor(fpp);
}
else
{
active = true;
viewPort.addProcessor(fpp);
}
}
}
};
inputManager.addListener(acl, "toggle");
}
}

@ -0,0 +1,13 @@
Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
Roughness : 0.1
BaseColor : 1.0 0.2 0.2 1.0
Metallic : 0.0
EmissivePower : 0.0
EmissiveIntensity : 0.0
Emissive : 1.0 0.8 0.8 1.0
ParallaxHeight : 0.0
}
AdditionalRenderState {
}
}

@ -0,0 +1,12 @@
Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
Metallic : 0.0
Roughness : 0.0
BaseColor : 0.8 0.81960785 0.39607844 1.0
EmissiveIntensity : 5.0
EmissivePower : 2.0
Emissive : 1.0 0.0 0.0 1.0
}
AdditionalRenderState {
}
}

@ -0,0 +1,9 @@
Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
BaseColor : 0.6 0.6 0.6 1.0
Metallic : 1.0
Roughness : 0.05
}
AdditionalRenderState {
}
}

@ -0,0 +1,11 @@
Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
MaterialParameters {
BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg
Roughness : 0.01
NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal.jpg
Metallic : 1.0
BaseColor : 1.0 1.0 1.0 1.0
}
AdditionalRenderState {
}
}
Loading…
Cancel
Save