Better PBR env map generation

empirephoenix-patch-1
Nehon 7 years ago
parent 171007693b
commit e4b6bf82a2
  1. 6
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  2. 56
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  3. 227
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  4. 34
      jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java
  5. 22
      jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java
  6. 2
      jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java
  7. 22
      jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java
  8. 21
      jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java
  9. BIN
      jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.bin
  10. 2466
      jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.gltf

@ -48,6 +48,7 @@ import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
import com.jme3.util.MipMapGenerator;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -72,6 +73,8 @@ public class EnvironmentCamera extends BaseAppState {
protected Image.Format imageFormat = Image.Format.RGB16F;
public TextureCubeMap debugEnv;
//Axis for cameras
static {
//PositiveX axis(left, up, direction)
@ -188,10 +191,11 @@ public class EnvironmentCamera extends BaseAppState {
buffers[i] = BufferUtils.createByteBuffer(size * size * imageFormat.getBitsPerPixel() / 8);
renderManager.getRenderer().readFrameBufferWithFormat(framebuffers[i], buffers[i], imageFormat);
images[i] = new Image(imageFormat, size, size, buffers[i], ColorSpace.Linear);
MipMapGenerator.generateMipMaps(images[i]);
}
final TextureCubeMap map = EnvMapUtils.makeCubeMap(images[0], images[1], images[2], images[3], images[4], images[5], imageFormat);
debugEnv = map;
job.callback.done(map);
map.getImage().dispose();
jobs.remove(0);

@ -31,13 +31,14 @@
*/
package com.jme3.environment;
import com.jme3.app.Application;
import com.jme3.environment.generation.*;
import com.jme3.light.LightProbe;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application;
import com.jme3.light.LightProbe;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
@ -106,10 +107,11 @@ public class LightProbeFactory {
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @param genType Fast or HighQuality. Fast may be ok for many types of environment, but you may need high quality when an environment map has very high lighting values.
* @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) {
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final EnvMapUtils.GenerationType genType, final JobProgressListener<LightProbe> listener) {
final LightProbe probe = new LightProbe();
probe.setPosition(envCam.getPosition());
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
@ -117,33 +119,37 @@ public class LightProbeFactory {
@Override
public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener);
generatePbrMaps(map, probe, envCam.getApplication(), genType, listener);
}
});
return probe;
}
/**
* Updates a LightProbe with the giver EnvironmentCamera in the given scene.
*
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
return makeProbe(envCam, scene, EnvMapUtils.GenerationType.Fast, listener);
}
/**
* Updates a LightProbe with the given EnvironmentCamera in the given scene.
* <p>
* 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}.
*
* <p>
* The JobProgressListener will be notified of the progress of the generation.
* Note that you can also use a {@link JobProgressAdapter}.
*
* @param probe the Light probe to update
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @param genType Fast or HighQuality. Fast may be ok for many types of environment, but you may need high quality when an environment map has very high lighting values.
* @param listener the listener of the genration progress.
* @return the created LightProbe
* @see LightProbe
* @see EnvironmentCamera
* @see JobProgressListener
*
* @param probe the Light probe to update
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @param listener the listener of the genration progress.
* @return the created LightProbe
*/
public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final EnvMapUtils.GenerationType genType, final JobProgressListener<LightProbe> listener) {
envCam.setPosition(probe.getPosition());
@ -159,23 +165,27 @@ public class LightProbeFactory {
@Override
public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener);
generatePbrMaps(map, probe, envCam.getApplication(), genType, listener);
}
});
return probe;
}
public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
return updateProbe(probe, envCam, scene, EnvMapUtils.GenerationType.Fast, listener);
}
/**
* Internally called to generate the maps.
* 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
* @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) {
private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, EnvMapUtils.GenerationType genType, final JobProgressListener<LightProbe> listener) {
IrradianceSphericalHarmonicsGenerator irrShGenerator;
PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
@ -189,7 +199,7 @@ public class LightProbeFactory {
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.None, probe.getPrefilteredEnvMap());
pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.None, genType, probe.getPrefilteredEnvMap());
jobState.executor.execute(pemGenerators[i]);
}
}

@ -31,27 +31,19 @@
*/
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.app.Application;
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.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;
import static com.jme3.environment.util.EnvMapUtils.*;
import static com.jme3.math.FastMath.abs;
import static com.jme3.math.FastMath.sqrt;
/**
* Generates one face of the prefiltered environnement map for PBR. This job can
* be lauched from a separate thread.
@ -69,6 +61,7 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
private int targetMapSize;
private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
private EnvMapUtils.GenerationType genType;
private TextureCubeMap sourceMap;
private TextureCubeMap store;
private final Application app;
@ -107,11 +100,12 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
* {@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) {
public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, EnvMapUtils.GenerationType genType, TextureCubeMap store) {
this.sourceMap = sourceMap;
this.targetMapSize = targetMapSize;
this.fixSeamsMethod = fixSeamsMethod;
this.store = store;
this.genType = genType;
init();
}
@ -162,68 +156,105 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
* @return The irradiance cube map for the given coefficients
*/
private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap pem = store;
try {
TextureCubeMap pem = store;
int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
int nbMipMap = store.getImage().getMipMapSizes().length;
setEnd(nbMipMap);
setEnd(nbMipMap);
if (!sourceEnvMap.getImage().hasMipmaps() || sourceEnvMap.getImage().getMipMapSizes().length < nbMipMap) {
throw new IllegalArgumentException("The input cube map must have at least " + nbMipMap + "mip maps");
}
CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
Vector3f texelVect = new Vector3f();
Vector3f color = new Vector3f();
ColorRGBA outColor = new ColorRGBA();
for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
Vector3f texelVect = new Vector3f();
Vector3f color = new Vector3f();
ColorRGBA outColor = new ColorRGBA();
int targetMipMapSize = targetMapSize;
for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
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);
prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
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);
prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, mipLevel, color);
outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y, 0.0001f), Math.max(color.z, 0.0001f), 1);
log.log(Level.FINE, "coords {0},{1}", new Object[]{x, y});
targetWrapper.setPixel(x, y, face, mipLevel, outColor);
outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y, 0.0001f), Math.max(color.z, 0.0001f), 1);
targetWrapper.setPixel(x, y, face, mipLevel, outColor);
}
}
targetMipMapSize /= 2;
progress();
}
progress();
}
return pem;
return pem;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {
private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, int mipLevel, Vector3f store) {
Vector3f prefilteredColor = store;
float totalWeight = 0.0f;
int nbRotations = 1;
if (genType == GenerationType.HighQuality) {
nbRotations = numSamples == 1 ? 1 : 18;
}
float rad = 2f * FastMath.PI / (float) nbRotations;
// offset rotation to avoid sampling pattern
float gi = (float) (FastMath.abs(N.z + N.x) * 256.0);
float offset = rad * (FastMath.cos((gi * 0.5f) % (2f * FastMath.PI)) * 0.5f + 0.5f);
// a = roughness² and a2 = a²
float a2 = roughness * roughness;
a2 *= a2;
//Computing tangent frame
Vector3f upVector = Vector3f.UNIT_X;
if (abs(N.z) < 0.999) {
upVector = Vector3f.UNIT_Y;
}
Vector3f tangentX = tmp1.set(upVector).crossLocal(N).normalizeLocal();
Vector3f tangentY = tmp2.set(N).crossLocal(tangentX);
// https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
// in local space view == normal == 0,0,1
Vector3f V = new Vector3f(0, 0, 1);
Vector3f lWorld = new Vector3f();
for (int i = 0; i < numSamples; i++) {
Xi = getHammersleyPoint(i, numSamples, Xi);
H = importanceSampleGGX(Xi, a2, N, H);
H = importanceSampleGGX(Xi, a2, H);
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;
float VoH = H.z;
Vector3f L = H.multLocal(VoH * 2f).subtractLocal(V);
float NoL = L.z;
float computedMipLevel = mipLevel;
if (mipLevel != 0) {
computedMipLevel = computeMipLevel(roughness, numSamples, this.targetMapSize, VoH);
}
toWorld(L, N, tangentX, tangentY, lWorld);
totalWeight += samplePixel(envMapReader, lWorld, NoL, computedMipLevel, prefilteredColor);
for (int j = 1; j < nbRotations; j++) {
rotateDirection(offset + j * rad, L, lWorld);
L.set(lWorld);
toWorld(L, N, tangentX, tangentY, lWorld);
totalWeight += samplePixel(envMapReader, lWorld, NoL, computedMipLevel, prefilteredColor);
}
}
if (totalWeight > 0) {
prefilteredColor.divideLocal(totalWeight);
@ -232,7 +263,78 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
return prefilteredColor;
}
public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) {
private float samplePixel(CubeMapWrapper envMapReader, Vector3f lWorld, float NoL, float computedMipLevel, Vector3f store) {
if (NoL <= 0) {
return 0;
}
envMapReader.getPixel(lWorld, computedMipLevel, c);
store.setX(store.x + c.r * NoL);
store.setY(store.y + c.g * NoL);
store.setZ(store.z + c.b * NoL);
return NoL;
}
private void toWorld(Vector3f L, Vector3f N, Vector3f tangentX, Vector3f tangentY, Vector3f store) {
store.set(tangentX).multLocal(L.x);
tmp.set(tangentY).multLocal(L.y);
store.addLocal(tmp);
tmp.set(N).multLocal(L.z);
store.addLocal(tmp);
}
private float computeMipLevel(float roughness, int numSamples, float size, float voH) {
// H[2] is NoH in local space
// adds 1.e-5 to avoid ggx / 0.0
float NoH = voH + 1E-5f;
// Probability Distribution Function
float Pdf = ggx(NoH, roughness) * NoH / (4.0f * voH);
// Solid angle represented by this sample
float omegaS = 1.0f / (numSamples * Pdf);
// Solid angle covered by 1 pixel with 6 faces that are EnvMapSize X EnvMapSize
float omegaP = 4.0f * FastMath.PI / (6.0f * size * size);
// Original paper suggest biasing the mip to improve the results
float mipBias = 1.0f; // I tested that the result is better with bias 1
double maxLod = Math.log(size) / Math.log(2f);
double log2 = Math.log(omegaS / omegaP) / Math.log(2);
return Math.min(Math.max(0.5f * (float) log2 + mipBias, 0.0f), (float) maxLod);
}
private float ggx(float NoH, float alpha) {
// use GGX / Trowbridge-Reitz, same as Disney and Unreal 4
// cf http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p3
float tmp = alpha / (NoH * NoH * (alpha * alpha - 1.0f) + 1.0f);
return tmp * tmp * (1f / FastMath.PI);
}
private Vector3f rotateDirection(float angle, Vector3f l, Vector3f store) {
float s, c, t;
s = FastMath.sin(angle);
c = FastMath.cos(angle);
t = 1.f - c;
store.x = l.x * c + l.y * s;
store.y = -l.x * s + l.y * c;
store.z = l.z * (t + c);
return store;
}
/**
* Computes GGX half vector in local space
*
* @param xi
* @param a2
* @param store
* @return
*/
public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f store) {
if (store == null) {
store = new Vector3f();
}
@ -243,22 +345,9 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi)
float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi)
Vector3f upVector = Vector3f.UNIT_X;
if (abs(normal.z) < 0.999) {
upVector = Vector3f.UNIT_Y;
}
Vector3f tangentX = tmp1.set(upVector).crossLocal(normal).normalizeLocal();
Vector3f tangentY = tmp2.set(normal).crossLocal(tangentX);
// Tangent to world space
tangentX.multLocal(sinThetaCosPhi);
tangentY.multLocal(sinThetaSinPhi);
tmp3.set(normal).multLocal(cosTheta);
// Tangent to world space
store.set(tangentX).addLocal(tangentY).addLocal(tmp3);
store.x = sinThetaCosPhi;
store.y = sinThetaSinPhi;
store.z = cosTheta;
return store;
}

@ -31,17 +31,15 @@
*/
package com.jme3.environment.util;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.math.ColorRGBA;
import static com.jme3.math.FastMath.pow;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.math.*;
import com.jme3.texture.Image;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.DefaultImageRaster;
import com.jme3.texture.image.MipMapImageRaster;
import com.jme3.util.BufferUtils;
import static com.jme3.math.FastMath.pow;
/**
* Wraps a Cube map and allows to read from or write pixels into it.
*
@ -57,6 +55,8 @@ public class CubeMapWrapper {
private final Vector2f uvs = new Vector2f();
private final Image image;
private final ColorRGBA tmpColor = new ColorRGBA();
/**
* Creates a CubeMapWrapper for the given cube map
* Note that the cube map must be initialized, and the mipmaps sizes should
@ -105,7 +105,7 @@ public class CubeMapWrapper {
* @param store the color in which to store the pixel color read.
* @return the color of the pixel read.
*/
public ColorRGBA getPixel(Vector3f vector, int mipLevel, ColorRGBA store) {
public ColorRGBA getPixel(Vector3f vector, float mipLevel, ColorRGBA store) {
if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
@ -113,10 +113,26 @@ public class CubeMapWrapper {
store = new ColorRGBA();
}
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
int lowerMipLevel = (int) mipLevel;
int higherMipLevel = (int) FastMath.ceil(mipLevel);
float ratio = mipLevel - lowerMipLevel;
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[lowerMipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel);
return mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store);
mipMapRaster.setMipLevel(lowerMipLevel);
mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store);
face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[higherMipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(higherMipLevel);
mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, tmpColor);
store.r = FastMath.interpolateLinear(ratio, store.r, tmpColor.r);
store.g = FastMath.interpolateLinear(ratio, store.g, tmpColor.g);
store.b = FastMath.interpolateLinear(ratio, store.b, tmpColor.b);
store.a = FastMath.interpolateLinear(ratio, store.a, tmpColor.a);
return store;
}
/**

@ -37,18 +37,15 @@ import com.jme3.math.*;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.*;
import com.jme3.texture.image.ColorSpace;
import com.jme3.ui.Picture;
import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import static com.jme3.math.FastMath.*;
import com.jme3.util.TempVars;
import static com.jme3.math.FastMath.*;
/**
*
@ -88,6 +85,11 @@ public class EnvMapUtils {
None
}
public static enum GenerationType {
Fast,
HighQuality
}
/**
* Creates a cube map from 6 images
*
@ -117,6 +119,8 @@ public class EnvMapUtils {
cubeImage.addData(backImg.getData(0));
cubeImage.addData(frontImg.getData(0));
cubeImage.setMipMapSizes(rightImg.getMipMapSizes());
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
cubeMap.setAnisotropicFilter(0);
cubeMap.setMagFilter(Texture.MagFilter.Bilinear);
@ -148,6 +152,8 @@ public class EnvMapUtils {
cubeImage.addData(d.duplicate());
}
cubeImage.setMipMapSizes(srcImg.getMipMapSizes());
TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
cubeMap.setAnisotropicFilter(sourceMap.getAnisotropicFilter());
cubeMap.setMagFilter(sourceMap.getMagFilter());
@ -730,7 +736,7 @@ public class EnvMapUtils {
pem.setMagFilter(Texture.MagFilter.Bilinear);
pem.setMinFilter(Texture.MinFilter.Trilinear);
pem.getImage().setColorSpace(ColorSpace.Linear);
int nbMipMap = (int) (Math.log(size) / Math.log(2) - 1);
int nbMipMap = Math.min(6, (int) (Math.log(size) / Math.log(2)));
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
targetWrapper.initMipMaps(nbMipMap);
return pem;

@ -110,7 +110,7 @@ class ByteAlignedImageCodec extends ImageCodec {
}
public void readComponents(ByteBuffer buf, int x, int y, int width, int offset, int[] components, byte[] tmp) {
readPixelRaw(buf, (x + y * width + offset) * bpp + offset, bpp, tmp);
readPixelRaw(buf, (x + y * width ) * bpp + offset, bpp, tmp);
components[0] = readComponent(tmp, ap, az);
components[1] = readComponent(tmp, rp, rz);
components[2] = readComponent(tmp, gp, gz);

@ -34,8 +34,8 @@ package com.jme3.util;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.image.ImageRaster;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -58,8 +58,8 @@ public class MipMapGenerator {
float xRatio = ((float)(input.getWidth() - 1)) / output.getWidth();
float yRatio = ((float)(input.getHeight() - 1)) / output.getHeight();
ColorRGBA outputColor = new ColorRGBA();
ColorRGBA outputColor = new ColorRGBA(0, 0, 0, 0);
ColorRGBA bottomLeft = new ColorRGBA();
ColorRGBA bottomRight = new ColorRGBA();
ColorRGBA topLeft = new ColorRGBA();
@ -72,29 +72,21 @@ public class MipMapGenerator {
int x2 = (int)x2f;
int y2 = (int)y2f;
float xDiff = x2f - x2;
float yDiff = y2f - y2;
input.getPixel(x2, y2, bottomLeft);
input.getPixel(x2 + 1, y2, bottomRight);
input.getPixel(x2, y2 + 1, topLeft);
input.getPixel(x2 + 1, y2 + 1, topRight);
bottomLeft.multLocal( (1f - xDiff) * (1f - yDiff) );
bottomRight.multLocal( (xDiff) * (1f - yDiff) );
topLeft.multLocal( (1f - xDiff) * (yDiff) );
topRight.multLocal( (xDiff) * (yDiff) );
outputColor.set(bottomLeft).addLocal(bottomRight)
.addLocal(topLeft).addLocal(topRight);
outputColor.multLocal(1f / 4f);
output.setPixel(x, y, outputColor);
}
}
return outputImage;
}
public static Image resizeToPowerOf2(Image original){
int potWidth = FastMath.nearestPowerOfTwo(original.getWidth());
int potHeight = FastMath.nearestPowerOfTwo(original.getHeight());

@ -11,12 +11,8 @@ import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.LightProbe;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.math.*;
import com.jme3.scene.*;
import com.jme3.ui.Picture;
import com.jme3.util.SkyFactory;
@ -39,11 +35,13 @@ public class RefEnv extends SimpleApplication {
@Override
public void simpleInitApp() {
cam.setLocation(new Vector3f(-17.713732f, 1.8661976f, 17.156784f));
cam.setRotation(new Quaternion(0.021403445f, 0.9428821f, -0.06178002f, 0.32664734f));
cam.setLocation(new Vector3f(-17.95047f, 4.917353f, -17.970531f));
cam.setRotation(new Quaternion(0.11724457f, 0.29356146f, -0.03630452f, 0.94802815f));
// cam.setLocation(new Vector3f(14.790441f, 7.164179f, 19.720007f));
// cam.setRotation(new Quaternion(-0.038261678f, 0.9578362f, -0.15233073f, -0.24058504f));
flyCam.setDragToRotate(true);
flyCam.setMoveSpeed(5);
Spatial sc = assetManager.loadModel("Models/gltf/ref/scene.gltf");
Spatial sc = assetManager.loadModel("Scenes/PBR/ref/scene.gltf");
rootNode.attachChild(sc);
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
rootNode.attachChild(sky);
@ -68,7 +66,7 @@ public class RefEnv extends SimpleApplication {
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("tex") && isPressed) {
if (tex == null) {
return;
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(stateManager.getState(EnvironmentCamera.class).debugEnv, assetManager);
}
if (tex.getParent() == null) {
guiNode.attachChild(tex);
@ -120,13 +118,12 @@ public class RefEnv extends SimpleApplication {
frame++;
if (frame == 2) {
final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, EnvMapUtils.GenerationType.Fast, new JobProgressAdapter<LightProbe>() {
@Override
public void done(LightProbe result) {
System.err.println("Done rendering env maps");
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
// guiNode.attachChild(tex);
rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic);
}
});

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save