Merge remote-tracking branch 'origin/master' into in-pass-shadows

in-pass-shadows
Kirill Vainer 7 years ago
commit 8d125a30ba
  1. 2
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  2. 24
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  3. 176
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java
  4. 117
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceSphericalHarmonicsGenerator.java
  5. 21
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  6. 290
      jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java
  7. 38
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  8. 59
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  9. 13
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  10. 17
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  11. 79
      jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib
  12. 41
      jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java
  13. 136
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRDirectLighting.java
  14. 27
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
  15. 11
      jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java
  16. 50
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  17. BIN
      jme3-examples/src/main/resources/jme3test/light/pbr/ref.png
  18. BIN
      jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefDE.png
  19. BIN
      jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefM.png

@ -111,7 +111,7 @@ public class EnvironmentCamera extends BaseAppState {
/**
* The size of environment cameras.
*/
protected int size = 128;
protected int size = 256;
private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();

@ -31,12 +31,9 @@
*/
package com.jme3.environment;
import com.jme3.environment.generation.*;
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;
@ -115,7 +112,6 @@ public class LightProbeFactory {
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>() {
@ -153,15 +149,12 @@ public class LightProbeFactory {
probe.setReady(false);
if(probe.getIrradianceMap() != null) {
probe.getIrradianceMap().getImage().dispose();
if (probe.getPrefilteredEnvMap() != null) {
probe.getPrefilteredEnvMap().getImage().dispose();
}
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override
@ -174,7 +167,7 @@ public class LightProbeFactory {
/**
* 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).
* This method will spawn 7 thread (one for the Irradiance spherical harmonics generator, 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
@ -183,20 +176,20 @@ public class LightProbeFactory {
* @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;
IrradianceSphericalHarmonicsGenerator irrShGenerator;
PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7));
irrMapGenerator = new IrradianceMapGenerator(app, new JobListener(listener, jobState, probe, 6));
irrShGenerator = new IrradianceSphericalHarmonicsGenerator(app, new JobListener(listener, jobState, probe, 6));
int size = envMap.getImage().getWidth();
irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getIrradianceMap());
irrShGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), probe);
jobState.executor.execute(irrMapGenerator);
jobState.executor.execute(irrShGenerator);
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());
pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.None, probe.getPrefilteredEnvMap());
jobState.executor.execute(pemGenerators[i]);
}
}
@ -280,6 +273,7 @@ public class LightProbeFactory {
jobState.done[index] = true;
if (jobState.isDone()) {
probe.setNbMipMaps(probe.getPrefilteredEnvMap().getImage().getMipMapSizes().length);
probe.setReady(true);
if (globalListener != null) {
globalListener.done(probe);

@ -1,176 +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.environment.generation;
import com.jme3.environment.util.CubeMapWrapper;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.texture.TextureCubeMap;
import static com.jme3.environment.util.EnvMapUtils.shBandFactor;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
/**
*
* Generates the Irrafiance map for PBR. This job can be lauched from a separate
* thread.
*
* TODO there is a lot of duplicate code here with the EnvMapUtils.
*
* @author Nehon
*/
//TODO there is a lot of duplicate code here with the EnvMapUtils. We should,
//either leverage the code from the util class either remove it and only allow
//parallel generation using this runnable.
public class IrradianceMapGenerator extends RunnableWithProgress {
private int targetMapSize;
private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
private TextureCubeMap sourceMap;
private TextureCubeMap store;
private final Application app;
/**
* Creates an Irradiance map generator. The app is needed to enqueue the
* call to the EnvironmentCamera when the generation is done, so that this
* process is thread safe.
*
* @param app the Application
* @param listener
*/
public IrradianceMapGenerator(Application app, JobProgressListener<Integer> listener) {
super(listener);
this.app = app;
}
/**
* Fills all the genration parameters
*
* @param sourceMap the source cube map
* @param targetMapSize the size of the generated map (width or height in
* pixel)
* @param fixSeamsMethod the method used to fix seams as described here
* {@link EnvMapUtils.FixSeamsMethod}
*
* @param store The cube map to store the result in.
*/
public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
this.sourceMap = sourceMap;
this.targetMapSize = targetMapSize;
this.fixSeamsMethod = fixSeamsMethod;
this.store = store;
reset();
}
@Override
public void run() {
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.start();
return null;
}
});
try {
Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap);
store = generateIrradianceMap(shCoeffs, targetMapSize, fixSeamsMethod, store);
} catch (Exception e) {
e.printStackTrace();
}
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.done(6);
return null;
}
});
}
/**
* Generates the Irradiance map (used for image based difuse lighting) from
* Spherical Harmonics coefficients previously computed with
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
*
* @param shCoeffs the SH coeffs
* @param targetMapSize the size of the irradiance map to generate
* @param fixSeamsMethod the method to fix seams
* @param store
* @return The irradiance cube map for the given coefficients
*/
public TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap irrCubeMap = store;
setEnd(6 + 6);
for (int i = 0; i < 6; i++) {
ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * store.getImage().getFormat().getBitsPerPixel() / 8);
irrCubeMap.getImage().setData(i, buf);
progress();
}
Vector3f texelVect = new Vector3f();
ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
float[] shDir = new float[9];
CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
for (int face = 0; face < 6; face++) {
for (int y = 0; y < targetMapSize; y++) {
for (int x = 0; x < targetMapSize; x++) {
EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
EnvMapUtils.evalShBasis(texelVect, shDir);
color.set(0, 0, 0, 0);
for (int i = 0; i < EnvMapUtils.NUM_SH_COEFFICIENT; i++) {
color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
1.0f);
}
//clamping the color because very low value close to zero produce artifacts
color.r = Math.max(0.0001f, color.r);
color.g = Math.max(0.0001f, color.g);
color.b = Math.max(0.0001f, color.b);
envMapWriter.setPixel(x, y, face, color);
}
}
progress();
}
return irrCubeMap;
}
}

@ -0,0 +1,117 @@
/*
* 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;
import com.jme3.app.Application;
import com.jme3.environment.util.CubeMapWrapper;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.light.LightProbe;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.texture.TextureCubeMap;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
import static com.jme3.environment.util.EnvMapUtils.shBandFactor;
/**
* Generates the Irradiance map for PBR. This job can be launched from a separate
* thread.
* <p>
* This is not used as we use spherical harmonics directly now, but we may need this code again at some point
*
* @author Nehon
*/
public class IrradianceSphericalHarmonicsGenerator extends RunnableWithProgress {
private TextureCubeMap sourceMap;
private LightProbe store;
private final Application app;
/**
* Creates an Irradiance map generator. The app is needed to enqueue the
* call to the EnvironmentCamera when the generation is done, so that this
* process is thread safe.
*
* @param app the Application
* @param listener
*/
public IrradianceSphericalHarmonicsGenerator(Application app, JobProgressListener<Integer> listener) {
super(listener);
this.app = app;
}
/**
* Fills all the genration parameters
*
* @param sourceMap the source cube map
* {@link EnvMapUtils.FixSeamsMethod}
* @param store The cube map to store the result in.
*/
public void setGenerationParam(TextureCubeMap sourceMap, LightProbe store) {
this.sourceMap = sourceMap;
this.store = store;
reset();
}
@Override
public void run() {
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.start();
return null;
}
});
try {
Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap);
EnvMapUtils.prepareShCoefs(shCoeffs);
store.setShCoeffs(shCoeffs);
} catch (Exception e) {
e.printStackTrace();
}
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.done(6);
return null;
}
});
}
}

@ -34,27 +34,28 @@ package com.jme3.environment.generation;
import com.jme3.environment.util.CubeMapWrapper;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application;
import com.jme3.math.ColorRGBA;
import com.jme3.math.*;
import static com.jme3.math.FastMath.abs;
import static com.jme3.math.FastMath.clamp;
import static com.jme3.math.FastMath.pow;
import static com.jme3.math.FastMath.sqrt;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.texture.TextureCubeMap;
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 java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* Generates one face of the prefiltered environnement map for PBR. This job can
* be lauched from a separate thread.
*
* <p>
* TODO there is a lot of duplicate code here with the EnvMapUtils.
*
* @author Nehon
@ -96,7 +97,6 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
}
/**
* Fills all the genration parameters
*
@ -105,7 +105,6 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
* pixel)
* @param fixSeamsMethod the method used to fix seams as described here
* {@link EnvMapUtils.FixSeamsMethod}
*
* @param store The cube map to store the result in.
*/
public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
@ -184,7 +183,7 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
for (int y = 0; y < targetMipMapSize; y++) {
for (int x = 0; x < targetMipMapSize; x++) {
color.set(0, 0, 0);
getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, EnvMapUtils.FixSeamsMethod.Wrap);
getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, fixSeamsMethod);
prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y, 0.0001f), Math.max(color.z, 0.0001f), 1);
@ -207,7 +206,6 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
// a = roughness² and a2 = a²
float a2 = roughness * roughness;
a2 *= a2;
a2 *= 10;
for (int i = 0; i < numSamples; i++) {
Xi = getHammersleyPoint(i, numSamples, Xi);
H = importanceSampleGGX(Xi, a2, N, H);
@ -227,8 +225,11 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
totalWeight += NoL;
}
}
if (totalWeight > 0) {
prefilteredColor.divideLocal(totalWeight);
}
return prefilteredColor.divideLocal(totalWeight);
return prefilteredColor;
}
public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) {

@ -33,9 +33,7 @@ package com.jme3.environment.util;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.math.*;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
@ -49,8 +47,7 @@ import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import static com.jme3.math.FastMath.*;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.util.TempVars;
/**
@ -62,6 +59,12 @@ import com.jme3.util.TempVars;
*/
public class EnvMapUtils {
private static final float sqrtPi = sqrt(PI);
private static final float sqrt3Pi = sqrt(3f / PI);
private static final float sqrt5Pi = sqrt(5f / PI);
private static final float sqrt15Pi = sqrt(15f / PI);
public final static int NUM_SH_COEFFICIENT = 9;
// See Peter-Pike Sloan paper for these coefficients
//http://www.ppsloan.org/publications/StupidSH36.pdf
@ -82,7 +85,7 @@ public class EnvMapUtils {
/**
* No seams fix
*/
None;
None
}
/**
@ -114,18 +117,6 @@ public class EnvMapUtils {
cubeImage.addData(backImg.getData(0));
cubeImage.addData(frontImg.getData(0));
if (leftImg.getEfficentData() != null) {
// also consilidate efficient data
ArrayList<Object> efficientData = new ArrayList<Object>(6);
efficientData.add(rightImg.getEfficentData());
efficientData.add(leftImg.getEfficentData());
efficientData.add(upImg.getEfficentData());
efficientData.add(downImg.getEfficentData());
efficientData.add(backImg.getEfficentData());
efficientData.add(frontImg.getEfficentData());
cubeImage.setEfficentData(efficientData);
}
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
cubeMap.setAnisotropicFilter(0);
cubeMap.setMagFilter(Texture.MagFilter.Bilinear);
@ -157,13 +148,6 @@ public class EnvMapUtils {
cubeImage.addData(d.duplicate());
}
if (srcImg.getEfficentData() != null) {
// also consilidate efficient data
ArrayList<Object> efficientData = new ArrayList<Object>(6);
efficientData.add(srcImg.getEfficentData());
cubeImage.setEfficentData(efficientData);
}
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
cubeMap.setAnisotropicFilter(sourceMap.getAnisotropicFilter());
cubeMap.setMagFilter(sourceMap.getMagFilter());
@ -199,7 +183,7 @@ public class EnvMapUtils {
static float getSolidAngleAndVector(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) {
if (store == null) {
throw new IllegalArgumentException("the store parameter ust not be null");
throw new IllegalArgumentException("the store parameter must not be null");
}
/* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
@ -254,7 +238,7 @@ public class EnvMapUtils {
float v;
if (fixSeamsMethod == FixSeamsMethod.Stretch) {
/* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
/* Code from Nvtt : https://github.com/castano/nvidia-texture-tools/blob/master/src/nvtt/CubeSurface.cpp#L77
* transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */
u = (2.0f * (float) x / ((float) mapSize - 1.0f)) - 1.0f;
v = (2.0f * (float) y / ((float) mapSize - 1.0f)) - 1.0f;
@ -274,7 +258,7 @@ public class EnvMapUtils {
}
//compute vector depending on the face
// Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
// Code from Nvtt : https://github.com/castano/nvidia-texture-tools/blob/master/src/nvtt/CubeSurface.cpp#L101
switch (face) {
case 0:
store.set(1f, -v, -u);
@ -387,62 +371,21 @@ public class EnvMapUtils {
return face;
}
/*
public static void main(String... argv) {
// for (int givenFace = 0; givenFace < 6; givenFace++) {
//
// //int givenFace = 1;
// for (int x = 0; x < 128; x++) {
// for (int y = 0; y < 128; y++) {
// Vector3f v = EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, 128, givenFace, null, FixSeamsMethod.None);
// Vector2f uvs = new Vector2f();
// int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(v, 128, uvs, FixSeamsMethod.None);
//
// if ((int) uvs.x != x || (int) uvs.y != y) {
// System.err.println("error " + uvs + " should be " + x + "," + y + " vect was " + v);
// }
// if (givenFace != face) {
// System.err.println("error face: " + face + " should be " + givenFace);
// }
// }
// }
// }
// System.err.println("done ");
int total = 0;
for (int i = 0; i < 6; i++) {
int size = (int) pow(2, 7 - i);
int samples = EnvMapUtils.getSampleFromMip(i, 6);
int iterations = (samples * size * size);
total += iterations;
float roughness = EnvMapUtils.getRoughnessFromMip(i, 6);
System.err.println("roughness " + i + " : " + roughness + " , map : " + size + " , samples : " + samples + " , iterations : " + iterations);
System.err.println("reverse " + EnvMapUtils.getMipFromRoughness(roughness, 6));
}
System.err.println("total " + total);
System.err.println(128 * 128 * 1024);
System.err.println("test " + EnvMapUtils.getMipFromRoughness(0.9999f, 6));
System.err.println("nb mip = " + (Math.log(128) / Math.log(2) - 1));
}*/
public static int getSampleFromMip(int mipLevel, int miptot) {
return mipLevel == 0 ? 1 : Math.min(1 << (miptot - 1 + (mipLevel) * 2), 8192);
}
public static float getRoughnessFromMip(int miplevel, int miptot) {
float mipScale = 1.0f;
float mipOffset = -0.3f;
return pow(2, (miplevel - (miptot - 1) + mipOffset) / mipScale);
//see lagarde's paper https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
//linear roughness
public static float getRoughnessFromMip(int miplevel, int miptot) {
float step = 1f / ((float) miptot - 1);
step *= miplevel;
return step * step;
}
public static float getMipFromRoughness(float roughness, int miptot) {
float mipScale = 1.0f;
float Lod = (float) (Math.log(roughness) / Math.log(2)) * mipScale + miptot - 1.0f;
return (float) Math.max(0.0, Lod);
return FastMath.sqrt(roughness) * (miptot - 1);
}
/**
@ -482,7 +425,7 @@ public class EnvMapUtils {
float weight;
if (cubeMap.getImage().getData(0) == null) {
throw new IllegalStateException("The cube map must contain Efficient data, if you rendered the cube map on the GPU plase use renderer.readFrameBuffer, to create a CPU image");
throw new IllegalStateException("The cube map must contain Efficient data, if you rendered the cube map on the GPU please use renderer.readFrameBuffer, to create a CPU image");
}
int width = cubeMap.getImage().getWidth();
@ -539,12 +482,6 @@ public class EnvMapUtils {
float yV = texelVect.y;
float zV = texelVect.z;
float pi = PI;
float sqrtPi = sqrt(pi);
float sqrt3Pi = sqrt(3f / pi);
float sqrt5Pi = sqrt(5f / pi);
float sqrt15Pi = sqrt(15f / pi);
float x2 = xV * xV;
float y2 = yV * yV;
float z2 = zV * zV;
@ -558,140 +495,31 @@ public class EnvMapUtils {
shDir[6] = (sqrt5Pi * (-1f + 3f * z2)) / 4f;
shDir[7] = -(sqrt15Pi * xV * zV) / 2f;
shDir[8] = sqrt15Pi * (x2 - y2) / 4f;
// shDir[0] = (1f/(2.f*sqrtPi));
//
// shDir[1] = -(sqrt(3f/pi)*yV)/2.f;
// shDir[2] = (sqrt(3/pi)*zV)/2.f;
// shDir[3] = -(sqrt(3/pi)*xV)/2.f;
//
// shDir[4] = (sqrt(15f/pi)*xV*yV)/2.f;
// shDir[5] = -(sqrt(15f/pi)*yV*zV)/2.f;
// shDir[6] = (sqrt(5f/pi)*(-1 + 3f*z2))/4.f;
// shDir[7] = -(sqrt(15f/pi)*xV*zV)/2.f;
// shDir[8] = sqrt(15f/pi)*(x2 - y2)/4.f;
}
/**
* {@link EnvMapUtils#generateIrradianceMap(com.jme3.math.Vector3f[], com.jme3.texture.TextureCubeMap, int, com.jme3.utils.EnvMapUtils.FixSeamsMethod)
* }
*
* @param shCoeffs the spherical harmonics coefficients to use
* @param targetMapSize the size of the target map
* @return the irradiance map.
*/
public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize) {
return generateIrradianceMap(shCoeffs, targetMapSize, FixSeamsMethod.Wrap, null);
}
/**
* Generates the Irradiance map (used for image based difuse lighting) from
* Spherical Harmonics coefficients previously computed with
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
* Note that the output cube map is in RGBA8 format.
*
* @param shCoeffs the SH coeffs
* @param targetMapSize the size of the irradiance map to generate
* @param fixSeamsMethod the method to fix seams
* @param store
* @return The irradiance cube map for the given coefficients
*/
public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap irrCubeMap = store;
if (irrCubeMap == null) {
irrCubeMap = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
irrCubeMap.setMagFilter(Texture.MagFilter.Bilinear);
irrCubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
irrCubeMap.getImage().setColorSpace(ColorSpace.Linear);
}
for (int i = 0; i < 6; i++) {
ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * irrCubeMap.getImage().getFormat().getBitsPerPixel()/8);
irrCubeMap.getImage().setData(i, buf);
}
Vector3f texelVect = new Vector3f();
ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
float[] shDir = new float[9];
CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
for (int face = 0; face < 6; face++) {
for (int y = 0; y < targetMapSize; y++) {
for (int x = 0; x < targetMapSize; x++) {
getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
evalShBasis(texelVect, shDir);
color.set(0, 0, 0, 0);
for (int i = 0; i < NUM_SH_COEFFICIENT; i++) {
color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
1.0f);
}
public static void prepareShCoefs(Vector3f[] shCoefs) {
//clamping the color because very low value close to zero produce artifacts
color.r = Math.max(0.0001f, color.r);
color.g = Math.max(0.0001f, color.g);
color.b = Math.max(0.0001f, color.b);
envMapWriter.setPixel(x, y, face, color);
}
}
}
return irrCubeMap;
}
float coef0 = (1f / (2f * sqrtPi));
float coef1 = -sqrt3Pi / 2f;
float coef2 = -coef1;
float coef3 = coef1;
float coef4 = sqrt15Pi / 2f;
float coef5 = -coef4;
float coef6 = sqrt5Pi / 4f;
float coef7 = coef5;
float coef8 = sqrt15Pi / 4f;
/**
* Generates the prefiltered env map (used for image based specular
* lighting) With the GGX/Shlick brdf
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
* Note that the output cube map is in RGBA8 format.
*
* @param sourceEnvMap
* @param targetMapSize the size of the irradiance map to generate
* @param store
* @param fixSeamsMethod the method to fix seams
* @return The irradiance cube map for the given coefficients
*/
public static TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap pem = store;
if (pem == null) {
pem = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
pem.setMagFilter(Texture.MagFilter.Bilinear);
pem.setMinFilter(Texture.MinFilter.Trilinear);
pem.getImage().setColorSpace(ColorSpace.Linear);
shCoefs[0].multLocal(coef0);
shCoefs[1].multLocal(coef1);
shCoefs[2].multLocal(coef2);
shCoefs[3].multLocal(coef3);
shCoefs[4].multLocal(coef4);
shCoefs[5].multLocal(coef5);
shCoefs[6].multLocal(coef6);
shCoefs[7].multLocal(coef7);
shCoefs[8].multLocal(coef8);
}
int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
targetWrapper.initMipMaps(nbMipMap);
Vector3f texelVect = new Vector3f();
Vector3f color = new Vector3f();
ColorRGBA outColor = new ColorRGBA();
for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
System.err.println("mip level " + mipLevel);
float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
for (int face = 0; face < 6; face++) {
System.err.println("face " + face);
for (int y = 0; y < targetMipMapSize; y++) {
for (int x = 0; x < targetMipMapSize; x++) {
color.set(0, 0, 0);
getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, FixSeamsMethod.Wrap);
prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
outColor.set(color.x, color.y, color.z, 1.0f);
// System.err.println("coords " + x + "," + y);
targetWrapper.setPixel(x, y, face, mipLevel, outColor);
}
}
}
}
return pem;
}
public static Vector4f getHammersleyPoint(int i, final int nbrSample, Vector4f store) {
if (store == null) {
@ -719,43 +547,6 @@ public class EnvMapUtils {
return store;
}
private static Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {
Vector3f prefilteredColor = store;
float totalWeight = 0.0f;
TempVars vars = TempVars.get();
Vector4f Xi = vars.vect4f1;
Vector3f H = vars.vect1;
Vector3f tmp = vars.vect2;
ColorRGBA c = vars.color;
// a = roughness² and a2 = a²
float a2 = roughness * roughness;
a2 *= a2;
a2 *= 10;
for (int i = 0; i < numSamples; i++) {
Xi = getHammersleyPoint(i, numSamples, Xi);
H = importanceSampleGGX(Xi, a2, N, H, vars);
H.normalizeLocal();
tmp.set(H);
float NoH = N.dot(tmp);
Vector3f L = tmp.multLocal(NoH * 2).subtractLocal(N);
float NoL = clamp(N.dot(L), 0.0f, 1.0f);
if (NoL > 0) {
envMapReader.getPixel(L, c);
prefilteredColor.setX(prefilteredColor.x + c.r * NoL);
prefilteredColor.setY(prefilteredColor.y + c.g * NoL);
prefilteredColor.setZ(prefilteredColor.z + c.b * NoL);
totalWeight += NoL;
}
}
vars.release();
return prefilteredColor.divideLocal(totalWeight);
}
public static Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store, TempVars vars) {
if (store == null) {
store = new Vector3f();
@ -945,3 +736,6 @@ public class EnvMapUtils {
return pem;
}
}

@ -61,26 +61,10 @@ public class LightsDebugState extends BaseAppState {
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");
@ -114,12 +98,8 @@ public class LightsDebugState extends BaseAppState {
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;
@ -161,24 +141,6 @@ public class LightsDebugState extends BaseAppState {
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

@ -50,17 +50,19 @@ 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;
/**
* 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).
* There are two environment data structure held by the LightProbe :
* - The irradiance spherical harmonics factors (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)}
* Note that when instantiating the LightProbe, both of those structures are null.
* 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).
@ -75,12 +77,15 @@ import java.io.IOException;
*/
public class LightProbe extends Light implements Savable {
private TextureCubeMap irradianceMap;
private static final Logger logger = Logger.getLogger(LightProbe.class.getName());
private Vector3f[] shCoeffs;
private TextureCubeMap prefilteredEnvMap;
private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
private boolean ready = false;
private Vector3f position = new Vector3f();
private Node debugNode;
private int nbMipMaps;
/**
* Empty constructor used for serialization.
@ -89,23 +94,6 @@ public class LightProbe extends Light implements Savable {
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
@ -127,7 +115,7 @@ public class LightProbe extends Light implements Savable {
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(irradianceMap, "irradianceMap", null);
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));
@ -138,11 +126,15 @@ public class LightProbe extends Light implements Savable {
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
irradianceMap = (TextureCubeMap) ic.readSavable("irradianceMap", null);
shCoeffs = (Vector3f[]) ic.readSavableArray("shCoeffs", null);
prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
position = (Vector3f) ic.readSavable("position", this);
bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
ready = ic.readBoolean("ready", false);
if (shCoeffs == null) {
ready = false;
logger.log(Level.WARNING, "LightProbe is missing parameters, it should be recomputed. Please use lightProbeFactory.updateProbe()");
}
}
/**
@ -199,9 +191,6 @@ public class LightProbe extends Light implements Savable {
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);
}
@ -209,6 +198,14 @@ public class LightProbe extends Light implements Savable {
return debugNode;
}
public Vector3f[] getShCoeffs() {
return shCoeffs;
}
public void setShCoeffs(Vector3f[] shCoeffs) {
this.shCoeffs = shCoeffs;
}
/**
* Returns the position of the LightProbe in world space
* @return the wolrd space position
@ -226,6 +223,14 @@ public class LightProbe extends Light implements Savable {
getBounds().setCenter(position);
}
public int getNbMipMaps() {
return nbMipMaps;
}
public void setNbMipMaps(int nbMipMaps) {
this.nbMipMaps = nbMipMaps;
}
@Override
public boolean intersectsBox(BoundingBox box, TempVars vars) {
return getBounds().intersectsBoundingBox(box);

@ -118,7 +118,9 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
Uniform lightProbeData = shader.getUniform("g_LightProbeData");
lightProbeData.setVector4Length(1);
Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap");
//TODO These 2 uniforms should be packed in an array, to ba able to have several probes and blend between them.
Uniform shCoeffs = shader.getUniform("g_ShCoeffs");
Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
lightProbe = null;
@ -131,16 +133,13 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
ambientColor.setValue(VarType.Vector4, ambientLightColor);
}
//If there is a lightProbe in the list we force it's render on the first pass
//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(), 0);
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 irrUnit = lastTexUnit++;
int pemUnit = lastTexUnit++;
rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap());
lightProbeIrrMap.setValue(VarType.Int, irrUnit);
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
lightProbePemMap.setValue(VarType.Int, pemUnit);
} else {

@ -24,7 +24,7 @@ varying vec3 wPosition;
#ifdef INDIRECT_LIGHTING
// uniform sampler2D m_IntegrateBRDF;
uniform samplerCube g_PrefEnvMap;
uniform samplerCube g_IrradianceMap;
uniform vec3 g_ShCoeffs[9];
uniform vec4 g_LightProbeData;
#endif
@ -236,7 +236,7 @@ void main(){
lightColor.rgb,specular, Roughness, ndotv,
directDiffuse, directSpecular);
vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular * specularColor.rgb;
vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular;
gl_FragColor.rgb += directLighting * fallOff;
}
@ -244,8 +244,10 @@ void main(){
#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, g_LightProbeData.xyz is the position of the lightProbe.
rv = g_LightProbeData.w * (wPosition - g_LightProbeData.xyz) +rv;
// 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;
//horizon fade from http://marmosetco.tumblr.com/post/81245981087
float horiz = dot(rv, wNormal.xyz);
@ -255,9 +257,9 @@ void main(){
vec3 indirectDiffuse = vec3(0.0);
vec3 indirectSpecular = vec3(0.0);
indirectDiffuse = textureCube(g_IrradianceMap, normal.xyz).rgb * diffuseColor.rgb;
indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, rv.xyz);
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);
vec3 indirectLighting = indirectDiffuse + indirectSpecular;
@ -273,7 +275,6 @@ void main(){
#endif
gl_FragColor += emissive * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity;
#endif
gl_FragColor.a = alpha;
}

@ -53,6 +53,29 @@ void PBR_ComputeDirectLightSpecWF(vec3 normal, vec3 lightDir, vec3 viewDir,
outSpecular = fresnel * vec3(specular) * lightColor;
}
vec3 sphericalHarmonics( const in vec3 normal, const vec3 sph[9] ){
float x = normal.x;
float y = normal.y;
float z = normal.z;
vec3 result = (
sph[0] +
sph[1] * y +
sph[2] * z +
sph[3] * x +
sph[4] * y * x +
sph[5] * y * z +
sph[6] * (3.0 * z * z - 1.0) +
sph[7] * (z * x) +
sph[8] * (x*x - y*y)
);
return max(result, vec3(0.0));
}
void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir,
vec3 lightColor, float fZero, float roughness, float ndotv,
out vec3 outDiffuse, out vec3 outSpecular){
@ -106,46 +129,38 @@ void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir,
outSpecular = vec3(specular) * lightColor;
}
//https://knarkowicz.wordpress.com/2014/12/27/analytical-dfg-term-for-ibl/
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( 2.5 / (roughness) * specularColor.y, 0.0, 1.0 );
return specularColor * scale + bias;
vec3 integrateBRDFApprox( const in vec3 specular, float roughness, float NoV ){
const vec4 c0 = vec4( -1, -0.0275, -0.572, 0.022 );
const vec4 c1 = vec4( 1, 0.0425, 1.04, -0.04 );
vec4 r = roughness * c0 + c1;
float a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;
vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;
return specular * AB.x + AB.y;
}
// from Sebastien Lagarde https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf page 69
vec3 getSpecularDominantDir(const in vec3 N, const in vec3 R, const in float realRoughness){
vec3 dominant;
float smoothness = 1.0 - realRoughness;
float lerpFactor = smoothness * (sqrt(smoothness) + realRoughness);
// The result is not normalized as we fetch in a cubemap
dominant = mix(N, R, lerpFactor);
return dominant;
}
vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
//TODO magic values should be replaced by defines.
float Lod = log2(Roughness) * 1.1 + 6.0 - 2.0;
vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec, float nbMipMaps){
float Lod = sqrt( Roughness ) * (nbMipMaps - 1.0);
vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz,Lod).rgb;
vec2 EnvBRDF = texture2D(integrateBRDF,vec2(Roughness, ndotv)).rg;
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.6 + 5.0;
vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec, float nbMipMaps){
float Lod = sqrt( Roughness ) * (nbMipMaps - 1.0);
vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz, Lod).rgb;
return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv);
return PrefilteredColor * integrateBRDFApprox(SpecularColor, Roughness, ndotv);
}

@ -18,7 +18,7 @@ import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.ui.Picture;
import com.jme3.util.MaterialDebugAppState;
import com.jme3.util.SkyFactory;
/**
* test
@ -29,8 +29,7 @@ public class RefEnv extends SimpleApplication {
private Node tex;
private Node ref;
private Picture refDE;
private Picture refM;
private Picture refImg;
public static void main(String[] args) {
RefEnv app = new RefEnv();
@ -40,25 +39,25 @@ public class RefEnv extends SimpleApplication {
@Override
public void simpleInitApp() {
cam.setLocation(new Vector3f(-2.3324413f, 2.9567573f, 4.6054406f));
cam.setRotation(new Quaternion(0.06310794f, 0.9321281f, -0.29613864f, 0.1986369f));
Spatial sc = assetManager.loadModel("Scenes/PBR/spheres.j3o");
cam.setLocation(new Vector3f(-17.713732f, 1.8661976f, 17.156784f));
cam.setRotation(new Quaternion(0.021403445f, 0.9428821f, -0.06178002f, 0.32664734f));
flyCam.setDragToRotate(true);
flyCam.setMoveSpeed(5);
Spatial sc = assetManager.loadModel("Models/gltf/ref/scene.gltf");
rootNode.attachChild(sc);
rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Always);
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
rootNode.attachChild(sky);
rootNode.getChild(0).setCullHint(Spatial.CullHint.Always);
ref = new Node("reference pictures");
refDE = new Picture("refDE");
refDE.setHeight(cam.getHeight());
refDE.setWidth(cam.getWidth());
refDE.setImage(assetManager,"jme3test/light/pbr/spheresRefDE.png", false);
refM = new Picture("refM");
refM.setImage(assetManager,"jme3test/light/pbr/spheresRefM.png", false);
refM.setHeight(cam.getHeight());
refM.setWidth(cam.getWidth());
refImg = new Picture("refImg");
refImg.setHeight(cam.getHeight());
refImg.setWidth(cam.getWidth());
refImg.setImage(assetManager, "jme3test/light/pbr/ref.png", false);
ref.attachChild(refDE);
ref.attachChild(refImg);
stateManager.attach(new EnvironmentCamera());
stateManager.attach(new EnvironmentCamera(256, Vector3f.ZERO));
inputManager.addMapping("tex", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addMapping("switch", new KeyTrigger(KeyInput.KEY_RETURN));
@ -105,13 +104,11 @@ public class RefEnv extends SimpleApplication {
if (((Float) mat.getParam("Metallic").getValue()) == 1f) {
mat.setFloat("Metallic", 0);
mat.setColor("BaseColor", ColorRGBA.Black);
ref.attachChild(refDE);
refM.removeFromParent();
ref.attachChild(refImg);
} else {
mat.setFloat("Metallic", 1);
mat.setColor("BaseColor", ColorRGBA.White);
ref.attachChild(refM);
refDE.removeFromParent();
refImg.removeFromParent();
}
}
}
@ -130,7 +127,7 @@ public class RefEnv extends SimpleApplication {
System.err.println("Done rendering env maps");
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
// guiNode.attachChild(tex);
rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Dynamic);
rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic);
}
});
((BoundingSphere) probe.getBounds()).setRadius(100);

@ -0,0 +1,136 @@
/*
* 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.ChaseCameraAppState;
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;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.environment.util.LightsDebugState;
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.material.Material;
import com.jme3.math.*;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.ToneMapFilter;
import com.jme3.scene.*;
import com.jme3.scene.shape.Sphere;
import com.jme3.texture.plugins.ktx.KTXLoader;
import com.jme3.util.MaterialDebugAppState;
import com.jme3.util.SkyFactory;
import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
/**
* A test case for PBR lighting.
* Still experimental.
*
* @author nehon
*/
public class TestPBRDirectLighting extends SimpleApplication {
public static void main(String[] args) {
TestPBRDirectLighting app = new TestPBRDirectLighting();
app.start();
}
private DirectionalLight dl;
private float roughness = 0.0f;
@Override
public void simpleInitApp() {
viewPort.setBackgroundColor(ColorRGBA.DarkGray);
dl = new DirectionalLight();
dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
rootNode.addLight(dl);
dl.setColor(ColorRGBA.White);
ChaseCameraAppState chaser = new ChaseCameraAppState();
chaser.setDragToRotate(true);
chaser.setMinVerticalRotation(-FastMath.HALF_PI);
chaser.setMaxDistance(1000);
chaser.setInvertVerticalAxis(true);
getStateManager().attach(chaser);
chaser.setTarget(rootNode);
flyCam.setEnabled(false);
Geometry sphere = new Geometry("sphere", new Sphere(32, 32, 1));
final Material m = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
m.setColor("BaseColor", ColorRGBA.Black);
m.setFloat("Metallic", 0f);
m.setFloat("Roughness", roughness);
sphere.setMaterial(m);
rootNode.attachChild(sphere);
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("rup") && isPressed) {
roughness = FastMath.clamp(roughness + 0.1f, 0.0f, 1.0f);
m.setFloat("Roughness", roughness);
}
if (name.equals("rdown") && isPressed) {
roughness = FastMath.clamp(roughness - 0.1f, 0.0f, 1.0f);
m.setFloat("Roughness", roughness);
}
if (name.equals("light") && isPressed) {
dl.setDirection(cam.getDirection().normalize());
}
}
}, "light", "rup", "rdown");
inputManager.addMapping("light", new KeyTrigger(KeyInput.KEY_F));
inputManager.addMapping("rup", new KeyTrigger(KeyInput.KEY_UP));
inputManager.addMapping("rdown", new KeyTrigger(KeyInput.KEY_DOWN));
}
@Override
public void simpleUpdate(float tpf) {
}
}

@ -73,13 +73,13 @@ public class TestPBRLighting extends SimpleApplication {
}
private Node tex;
private Node tex2;
private Geometry model;
private DirectionalLight dl;
private Node modelNode;
private int frame = 0;
private Material pbrMat;
private float roughness = 1.0f;
@Override
public void simpleInitApp() {
@ -114,7 +114,7 @@ public class TestPBRLighting extends SimpleApplication {
model.setMaterial(pbrMat);
final EnvironmentCamera envCam = new EnvironmentCamera(128, new Vector3f(0, 3f, 0));
final EnvironmentCamera envCam = new EnvironmentCamera(256, new Vector3f(0, 3f, 0));
stateManager.attach(envCam);
// EnvironmentManager envManager = new EnvironmentManager();
@ -142,16 +142,23 @@ public class TestPBRLighting extends SimpleApplication {
if (tex == null) {
return;
}
if (tex.getParent() == null && tex2.getParent() == null) {
if (tex.getParent() == null) {
guiNode.attachChild(tex);
} else if (tex2.getParent() == null){
tex.removeFromParent();
guiNode.attachChild(tex2);
} else {
tex2.removeFromParent();
tex.removeFromParent();
}
}
if (name.equals("rup") && isPressed) {
roughness = FastMath.clamp(roughness + 0.1f, 0.0f, 1.0f);
pbrMat.setFloat("Roughness", roughness);
}
if (name.equals("rdown") && isPressed) {
roughness = FastMath.clamp(roughness - 0.1f, 0.0f, 1.0f);
pbrMat.setFloat("Roughness", roughness);
}
if (name.equals("up") && isPressed) {
model.move(0, tpf * 100f, 0);
}
@ -169,7 +176,7 @@ public class TestPBRLighting extends SimpleApplication {
dl.setDirection(cam.getDirection().normalize());
}
}
}, "toggle", "light", "up", "down", "left", "right", "debug");
}, "toggle", "light", "up", "down", "left", "right", "debug", "rup", "rdown");
inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addMapping("light", new KeyTrigger(KeyInput.KEY_F));
@ -178,10 +185,13 @@ 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));
inputManager.addMapping("rup", new KeyTrigger(KeyInput.KEY_T));
inputManager.addMapping("rdown", new KeyTrigger(KeyInput.KEY_G));
MaterialDebugAppState debug = new MaterialDebugAppState();
debug.registerBinding("Common/MatDefs/Light/PBRLighting.frag", rootNode);
debug.registerBinding("Common/ShaderLib/PBR.glsllib", rootNode);
getStateManager().attach(debug);
}
@ -198,7 +208,6 @@ public class TestPBRLighting extends SimpleApplication {
public void done(LightProbe result) {
System.err.println("Done rendering env maps");
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
tex2 = EnvMapUtils.getCubeMapCrossDebugView(result.getIrradianceMap(), assetManager);
}
});
((BoundingSphere) probe.getBounds()).setRadius(100);

@ -306,16 +306,7 @@ public class TestPbrEnv extends SimpleApplication implements ActionListener {
}
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);
}
debugState.setEnabled(!debugState.isEnabled());
}
if (name.equals("debugTex") && keyPressed) {

@ -54,7 +54,7 @@ public class TestGltfLoading extends SimpleApplication {
Node probeNode;
float time = 0;
int assetIndex = 0;
boolean useAutoRotate = true;
boolean useAutoRotate = false;
private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
int duration = 2;
boolean playAnim = true;
@ -105,25 +105,27 @@ public class TestGltfLoading extends SimpleApplication {
// PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
// rootNode.addLight(pl1);
loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1);
loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1);
loadModel("Models/gltf/damagedHelmet/damagedHelmet.gltf", Vector3f.ZERO, 1);
loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f);
// loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f);
loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f);
loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f);
//TODO need to pad tracks that doesn't have the same length than the animation.
//loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f);
loadModel("Models/gltf/RiggedFigure/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f);
loadModel("Models/gltf/RiggedFigure/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f);
loadModel("Models/gltf/CesiumMan/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f);
loadModel("Models/gltf/BrainStem/BrainStem.gltf", new Vector3f(0, -1, 0), 1f);
// loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1);
// loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1);
// loadModel("Models/gltf/damagedHelmet/damagedHelmet.gltf", Vector3f.ZERO, 1);
// loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f);
//// loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f);
// loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f);
// loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f);
//
// //loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f);
//
// loadModel("Models/gltf/RiggedFigure/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f);
//loadModel("Models/gltf/RiggedFigure/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f);
//loadModel("Models/gltf/CesiumMan/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f);
//loadModel("Models/gltf/BrainStem/BrainStem.gltf", new Vector3f(0, -1, 0), 1f);
//loadModel("Models/gltf/Jaime/Jaime.gltf", new Vector3f(0, -1, 0), 1f);
// loadModel("Models/gltf/GiantWorm/GiantWorm.gltf", new Vector3f(0, -1, 0), 1f);
//loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f);
loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f);
// //loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f);
// loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f);
// loadModel("Models/gltf/corset/Corset.gltf", new Vector3f(0, -1, 0), 20f);
loadModel("Models/gltf/boxInter/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f);
probeNode.attachChild(assets.get(0));
@ -193,12 +195,14 @@ public class TestGltfLoading extends SimpleApplication {
playFirstAnim(s);
}
// SkeletonControl ctrl = findControl(s, SkeletonControl.class);
SkeletonControl ctrl = findControl(s, SkeletonControl.class);
// //ctrl.getSpatial().removeControl(ctrl);
// if (ctrl == null) {
// return;
// }
// getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
if (ctrl == null) {
return;
}
ctrl.setHardwareSkinningPreferred(false);
getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
// AnimControl aCtrl = findControl(s, AnimControl.class);
// //ctrl.getSpatial().removeControl(ctrl);
// if (aCtrl == null) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 KiB

Loading…
Cancel
Save