Better PBR env map generation
This commit is contained in:
parent
171007693b
commit
e4b6bf82a2
@ -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.environment.generation.*;
|
||||
import com.jme3.light.LightProbe;
|
||||
import com.jme3.environment.util.EnvMapUtils;
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.environment.generation.*;
|
||||
import com.jme3.environment.util.EnvMapUtils;
|
||||
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);
|
||||
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
if (!sourceEnvMap.getImage().hasMipmaps() || sourceEnvMap.getImage().getMipMapSizes().length < nbMipMap) {
|
||||
throw new IllegalArgumentException("The input cube map must have at least " + nbMipMap + "mip maps");
|
||||
}
|
||||
progress();
|
||||
}
|
||||
|
||||
return pem;
|
||||
CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
|
||||
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
|
||||
|
||||
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, mipLevel, color);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
float VoH = H.z;
|
||||
Vector3f L = H.multLocal(VoH * 2f).subtractLocal(V);
|
||||
float NoL = L.z;
|
||||
|
||||
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 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,19 +37,16 @@ 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 java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import static com.jme3.math.FastMath.*;
|
||||
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.jme3.math.FastMath.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* This class holds several utility method unseful for Physically Based
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
BIN
jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.bin
Normal file
BIN
jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.bin
Normal file
Binary file not shown.
2466
jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.gltf
Normal file
2466
jme3-testdata/src/main/resources/Scenes/PBR/ref/scene.gltf
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user