Moved PBR implementation

define_list_fix
Nehon 9 years ago
parent 4b1c61dec1
commit 69f1677649
  1. 351
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  2. 176
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java
  3. 266
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  4. 89
      jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java
  5. 243
      jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java
  6. 947
      jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java

@ -0,0 +1,351 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.environment;
import com.jme3.environment.generation.JobProgressListener;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
/**
* A 360 camera that can capture a cube map of a scene, and then generate the
* Prefiltered Environment cube Map and the Irradiance cube Map needed for PBE
* indirect lighting
*
* @author Nehon
*/
public class EnvironmentCamera extends BaseAppState {
private static Vector3f[] axisX = new Vector3f[6];
private static Vector3f[] axisY = new Vector3f[6];
private static Vector3f[] axisZ = new Vector3f[6];
private Image.Format imageFormat = Image.Format.RGB16F;
//Axis for cameras
static {
//PositiveX axis(left, up, direction)
axisX[0] = Vector3f.UNIT_Z.mult(1f);
axisY[0] = Vector3f.UNIT_Y.mult(-1f);
axisZ[0] = Vector3f.UNIT_X.mult(1f);
//NegativeX
axisX[1] = Vector3f.UNIT_Z.mult(-1f);
axisY[1] = Vector3f.UNIT_Y.mult(-1f);
axisZ[1] = Vector3f.UNIT_X.mult(-1f);
//PositiveY
axisX[2] = Vector3f.UNIT_X.mult(-1f);
axisY[2] = Vector3f.UNIT_Z.mult(1f);
axisZ[2] = Vector3f.UNIT_Y.mult(1f);
//NegativeY
axisX[3] = Vector3f.UNIT_X.mult(-1f);
axisY[3] = Vector3f.UNIT_Z.mult(-1f);
axisZ[3] = Vector3f.UNIT_Y.mult(-1f);
//PositiveZ
axisX[4] = Vector3f.UNIT_X.mult(-1f);
axisY[4] = Vector3f.UNIT_Y.mult(-1f);
axisZ[4] = Vector3f.UNIT_Z;
//NegativeZ
axisX[5] = Vector3f.UNIT_X.mult(1f);
axisY[5] = Vector3f.UNIT_Y.mult(-1f);
axisZ[5] = Vector3f.UNIT_Z.mult(-1f);
}
private Image images[];
ViewPort[] viewports;
FrameBuffer[] framebuffers;
ByteBuffer[] buffers;
private Vector3f position = new Vector3f();
private ColorRGBA backGroundColor = null;
private int size = 128;
private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
// debug to be removed
private Node debugPfemCm;
private Node debugIrrCm;
/**
* Creates an EnvironmentCamera with a size of 128
*/
public EnvironmentCamera() {
}
/**
* Creates an EnvironmentCamera with the given size.
*
* @param size the size of the resulting texture.
*/
public EnvironmentCamera(int size) {
this.size = size;
}
/**
* Creates an EnvironmentCamera with the given size, and the given position
*
* @param size the size of the resulting texture.
* @param position the position of the camera.
*/
public EnvironmentCamera(int size, Vector3f position) {
this.size = size;
this.position.set(position);
}
/**
* Creates an EnvironmentCamera with the given size, and the given position
*
* @param size the size of the resulting texture, and the given ImageFormat.
* @param position the position of the camera.
* @param imageFormat the ImageFormat to use for the resulting texture.
*/
public EnvironmentCamera(int size, Vector3f position, Image.Format imageFormat) {
this.size = size;
this.position.set(position);
this.imageFormat = imageFormat;
}
/**
* Takes a snapshot of the surrounding scene.
*
* @param scene the scene to snapshot.
* @param done a callback to call when the snapshot is done.
*/
public void snapshot(final Node scene, final JobProgressListener<TextureCubeMap> done) {
getApplication().enqueue(new Callable<Void>() {
public Void call() throws Exception {
SnapshotJob job = new SnapshotJob(done, scene);
jobs.add(job);
return null;
}
});
}
@Override
public void render(RenderManager renderManager) {
if (!jobs.isEmpty()) {
SnapshotJob job = jobs.get(0);
for (int i = 0; i < 6; i++) {
viewports[i].clearScenes();
viewports[i].attachScene(job.scene);
renderManager.renderViewPort(viewports[i], 0.16f);
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);
}
TextureCubeMap map = EnvMapUtils.makeCubeMap(images[0], images[1], images[2], images[3], images[4], images[5], imageFormat);
job.callback.done(map);
map.getImage().dispose();
jobs.remove(0);
}
}
public int getSize() {
return size;
}
public Vector3f getPosition() {
return position;
}
// /**
// * Displays or cycles through the generated maps.
// */
// public void toggleDebug() {
// if (debugPfemCm == null) {
// debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(currentEnvProbe.getPrefilteredEnvMap(), getApplication().getAssetManager());
// debugPfemCm.setLocalTranslation(getApplication().getGuiViewPort().getCamera().getWidth() - 532, 20, 0);
// }
// if (debugIrrCm == null) {
// debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(currentEnvProbe.getIrradianceMap(), getApplication().getAssetManager());
// debugIrrCm.setLocalTranslation(getApplication().getGuiViewPort().getCamera().getWidth() - 532, 20, 0);
// }
//
// if (debugIrrCm.getParent() != null) {
// debugIrrCm.removeFromParent();
// ((Node) (getApplication().getGuiViewPort().getScenes().get(0))).attachChild(debugPfemCm);
//
// } else if (debugPfemCm.getParent() != null) {
// debugPfemCm.removeFromParent();
// } else {
// ((Node) (getApplication().getGuiViewPort().getScenes().get(0))).attachChild(debugIrrCm);
// }
//
// }
/**
* Sets the camera position.
*
* @param position
*/
public void setPosition(Vector3f position) {
this.position.set(position);
if (viewports != null) {
for (ViewPort viewPort : viewports) {
viewPort.getCamera().setLocation(position);
}
}
}
@Override
protected void initialize(Application app) {
this.backGroundColor = app.getViewPort().getBackgroundColor();
Camera[] cameras = new Camera[6];
viewports = new ViewPort[6];
framebuffers = new FrameBuffer[6];
buffers = new ByteBuffer[6];
Texture2D[] textures = new Texture2D[6];
images = new Image[6];
for (int i = 0; i < 6; i++) {
cameras[i] = createOffCamera(size, position, axisX[i], axisY[i], axisZ[i]);
viewports[i] = createOffViewPort("EnvView" + i, cameras[i]);
framebuffers[i] = createOffScreenFrameBuffer(size, viewports[i]);
textures[i] = new Texture2D(size, size, imageFormat);
framebuffers[i].setColorTexture(textures[i]);
}
}
@Override
protected void cleanup(Application app) {
this.backGroundColor = null;
for (FrameBuffer frameBuffer : framebuffers) {
frameBuffer.dispose();
}
for (Image image : images) {
if( image != null){
image.dispose();
}
}
}
/**
* returns the images format used for the generated maps.
*
* @return
*/
public Image.Format getImageFormat() {
return imageFormat;
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
/**
* Creates an off camera
*
* @param mapSize the size
* @param worldPos the position
* @param axisX the x axis
* @param axisY the y axis
* @param axisZ tha z axis
* @return
*/
protected final Camera createOffCamera(int mapSize, Vector3f worldPos, Vector3f axisX, Vector3f axisY, Vector3f axisZ) {
Camera offCamera = new Camera(mapSize, mapSize);
offCamera.setLocation(worldPos);
offCamera.setAxes(axisX, axisY, axisZ);
offCamera.setFrustumPerspective(90f, 1f, 1, 1000);
offCamera.setLocation(position);
return offCamera;
}
/**
* creates an offsceen VP
*
* @param name
* @param offCamera
* @return
*/
protected final ViewPort createOffViewPort(String name, Camera offCamera) {
ViewPort offView = new ViewPort(name, offCamera);
offView.setClearFlags(true, true, true);
offView.setBackgroundColor(backGroundColor);
return offView;
}
/**
* create an offscreen frame buffer.
*
* @param mapSize
* @param offView
* @return
*/
protected final FrameBuffer createOffScreenFrameBuffer(int mapSize, ViewPort offView) {
// create offscreen framebuffer
FrameBuffer offBuffer = new FrameBuffer(mapSize, mapSize, 1);
offBuffer.setDepthBuffer(Image.Format.Depth);
offView.setOutputFrameBuffer(offBuffer);
return offBuffer;
}
private class SnapshotJob {
JobProgressListener<TextureCubeMap> callback;
Node scene;
public SnapshotJob(JobProgressListener callback, Node scene) {
this.callback = callback;
this.scene = scene;
}
}
}

@ -0,0 +1,176 @@
/*
* 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,266 @@
/*
* 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 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 com.jme3.math.FastMath;
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.
*
* 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 PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
private final static Logger log = Logger.getLogger(PrefilteredEnvMapFaceGenerator.class.getName());
private int targetMapSize;
private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
private TextureCubeMap sourceMap;
private TextureCubeMap store;
private final Application app;
private int face = 0;
Vector4f Xi = new Vector4f();
Vector3f H = new Vector3f();
Vector3f tmp = new Vector3f();
ColorRGBA c = new ColorRGBA();
Vector3f tmp1 = new Vector3f();
Vector3f tmp2 = new Vector3f();
Vector3f tmp3 = new Vector3f();
/**
* Creates a pem generator for the given face. 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 face the face to generate
* @param listener
*/
public PrefilteredEnvMapFaceGenerator(Application app, int face, JobProgressListener<Integer> listener) {
super(listener);
this.app = app;
this.face = face;
}
/**
* 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;
init();
}
private void init(){
Xi.set(0, 0, 0, 0);
H.set(0, 0, 0);
tmp.set(0, 0, 0);
c.set(1, 1, 1, 1);
tmp1.set(0, 0, 0);
tmp2.set(0, 0, 0);
tmp3.set(0, 0, 0);
reset();
}
@Override
public void run() {
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.start();
return null;
}
});
store = generatePrefilteredEnvMap(sourceMap, targetMapSize, fixSeamsMethod, store);
app.enqueue(new Callable<Void>() {
@Override
public Void call() throws Exception {
listener.done(face);
return null;
}
});
}
/**
* 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
*/
private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
TextureCubeMap pem = store;
int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
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, EnvMapUtils.FixSeamsMethod.Wrap);
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);
}
}
progress();
}
return pem;
}
private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {
Vector3f prefilteredColor = store;
float totalWeight = 0.0f;
// 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);
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;
}
}
return prefilteredColor.divideLocal(totalWeight);
}
public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) {
if (store == null) {
store = new Vector3f();
}
float cosTheta = sqrt((1f - xi.x) / (1f + (a2 - 1f) * xi.x));
float sinTheta = sqrt(1f - cosTheta * cosTheta);
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);
return store;
}
}

@ -0,0 +1,89 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.environment.generation;
/**
*
* Abstract runnable that can report its progress
*
* @author Nehon
*/
public abstract class RunnableWithProgress implements Runnable {
private int progress;
private int end;
protected JobProgressListener listener;
public RunnableWithProgress() {
}
public RunnableWithProgress(JobProgressListener listener) {
this.listener = listener;
}
/**
* set the end step value of the process.
*
* @param end
*/
protected void setEnd(int end) {
this.end = end;
}
/**
* return the curent progress of the process.
*
* @return
*/
public double getProgress() {
return (double) progress / (double) end;
}
/**
* adds one progression step to the process.
*/
protected void progress() {
progress++;
if (listener != null) {
listener.progress(getProgress());
}
}
/**
* resets the progression of the process.
*/
protected void reset() {
progress = 0;
}
}

@ -0,0 +1,243 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.environment.util;
import com.jme3.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.texture.Image;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.DefaultImageRaster;
import com.jme3.texture.image.MipMapImageRaster;
import com.jme3.util.BufferUtils;
/**
* Wraps a Cube map and allows to read from or write pixels into it.
*
* It uses the ImageRaster class to tailor the read write operations.
*
* @author Nehon
*/
public class CubeMapWrapper {
private MipMapImageRaster mipMapRaster;
private final DefaultImageRaster raster;
private int[] sizes;
private final Vector2f uvs = new Vector2f();
private final Image image;
/**
* Creates a CubeMapWrapper for the given cube map
* Note that the cube map must be initialized, and the mipmaps sizes should
* be set if relevant for them to be readable/writable
* @param cubeMap the cubemap to wrap.
*/
public CubeMapWrapper(TextureCubeMap cubeMap) {
image = cubeMap.getImage();
if (image.hasMipmaps()) {
int nbMipMaps = image.getMipMapSizes().length;
sizes = new int[nbMipMaps];
mipMapRaster = new MipMapImageRaster(image, 0);
for (int i = 0; i < nbMipMaps; i++) {
sizes[i] = Math.max(1, image.getWidth() >> i);
}
} else {
sizes = new int[1];
sizes[0] = image.getWidth();
}
raster = new DefaultImageRaster(image, 0,0 , false);
}
/**
* Reads a pixel from the cube map given the coordinate vector
* @param vector the direction vector to fetch the texel
* @param store the color in which to store the pixel color read.
* @return the color of the pixel read.
*/
public ColorRGBA getPixel(Vector3f vector, ColorRGBA store) {
if (store == null) {
store = new ColorRGBA();
}
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
raster.setSlice(face);
return raster.getPixel((int) uvs.x, (int) uvs.y, store);
}
/**
*
* Reads a pixel from the cube map given the coordinate vector
* @param vector the direction vector to fetch the texel
* @param mipLevel the mip level to read from
* @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) {
if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
if (store == null) {
store = new ColorRGBA();
}
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel);
return mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store);
}
/**
* Reads a pixel from the cube map given the 2D coordinates and the face to read from
* @param x the x tex coordinate (from 0 to width)
* @param y the y tex coordinate (from 0 to height)
* @param face the face to read from
* @param store the color where the result is stored.
* @return the color read.
*/
public ColorRGBA getPixel(int x, int y, int face, ColorRGBA store) {
if (store == null) {
store = new ColorRGBA();
}
raster.setSlice(face);
return raster.getPixel((int) x, (int) y, store);
}
/**
* Reads a pixel from the cube map given the 2D coordinates and the face and
* the mip level to read from
* @param x the x tex coordinate (from 0 to width)
* @param y the y tex coordinate (from 0 to height)
* @param face the face to read from
* @param mipLevel the miplevel to read from
* @param store the color where the result is stored.
* @return the color read.
*/
public ColorRGBA getPixel(int x, int y, int face, int mipLevel, ColorRGBA store) {
if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
if (store == null) {
store = new ColorRGBA();
}
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel);
return mipMapRaster.getPixel((int) x, (int) y, store);
}
/**
* writes a pixel given the coordinates vector and the color.
* @param vector the cooredinates where to write the pixel
* @param color the color to write
*/
public void setPixel(Vector3f vector, ColorRGBA color) {
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
raster.setSlice(face);
raster.setPixel((int) uvs.x, (int) uvs.y, color);
}
/**
* writes a pixel given the coordinates vector, the mip level and the color.
* @param vector the cooredinates where to write the pixel
* @param mipLevel the miplevel to write to
* @param color the color to write
*/
public void setPixel(Vector3f vector, int mipLevel, ColorRGBA color) {
if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel);
mipMapRaster.setPixel((int) uvs.x, (int) uvs.y, color);
}
/**
* Writes a pixel given the 2D cordinates and the color
* @param x the x tex coord (from 0 to width)
* @param y the y tex coord (from 0 to height)
* @param face the face to write to
* @param color the color to write
*/
public void setPixel(int x, int y, int face, ColorRGBA color) {
raster.setSlice(face);
raster.setPixel((int) x, (int) y, color);
}
/**
* Writes a pixel given the 2D cordinates, the mip level and the color
* @param x the x tex coord (from 0 to width)
* @param y the y tex coord (from 0 to height)
* @param face the face to write to
* @param mipLevel the mip level to write to
* @param color the color to write
*/
public void setPixel(int x, int y, int face, int mipLevel, ColorRGBA color) {
if (mipMapRaster == null) {
throw new IllegalArgumentException("This cube map has no mip maps");
}
mipMapRaster.setSlice(face);
mipMapRaster.setMipLevel(mipLevel);
mipMapRaster.setPixel((int) x, (int) y, color);
}
/**
* Inits the mip maps of a cube map witht he given number of mip maps
* @param nbMipMaps the number of mip maps to initialize
*/
public void initMipMaps(int nbMipMaps) {
int maxMipMap = (int) (Math.log(image.getWidth()) / Math.log(2) + 1);
if (nbMipMaps > maxMipMap) {
throw new IllegalArgumentException("Max mip map number for a " + image.getWidth() + "x" + image.getHeight() + " cube map is " + maxMipMap);
}
sizes = new int[nbMipMaps];
int totalSize = 0;
for (int i = 0; i < nbMipMaps; i++) {
int size = (int) pow(2, maxMipMap - 1 - i);
sizes[i] = size * size * image.getFormat().getBitsPerPixel() / 8;
totalSize += sizes[i];
}
image.setMipMapSizes(sizes);
image.getData().clear();
for (int i = 0; i < 6; i++) {
image.addData(BufferUtils.createByteBuffer(totalSize));
}
mipMapRaster = new MipMapImageRaster(image, 0);
}
}

@ -0,0 +1,947 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.environment.util;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
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.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.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.util.TempVars;
/**
*
* This class holds several utility method unseful for Physically Based
* Rendering. It alloaws to compute useful pre filtered maps from an env map.
*
* @author Nehon
*/
public class EnvMapUtils {
public final static int NUM_SH_COEFFICIENT = 9;
// See Peter-Pike Sloan paper for these coefficients
//http://www.ppsloan.org/publications/StupidSH36.pdf
public static float[] shBandFactor = {1.0f,
2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f,
1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f};
public static enum FixSeamsMethod {
/**
* wrap texture coordinates
*/
Wrap,
/**
* stretch texture coordinates
*/
Stretch,
/**
* No seams fix
*/
None;
}
/**
* Creates a cube map from 6 images
*
* @param leftImg the west side image, also called negative x (negX) or left
* image
* @param rightImg the east side image, also called positive x (posX) or
* right image
* @param downImg the bottom side image, also called negative y (negY) or
* down image
* @param upImg the up side image, also called positive y (posY) or up image
* @param backImg the south side image, also called positive z (posZ) or
* back image
* @param frontImg the north side image, also called negative z (negZ) or
* front image
* @param format the format of the image
* @return a cube map
*/
public static TextureCubeMap makeCubeMap(Image rightImg, Image leftImg, Image upImg, Image downImg, Image backImg, Image frontImg, Image.Format format) {
Image cubeImage = new Image(format, leftImg.getWidth(), leftImg.getHeight(), null, ColorSpace.Linear);
cubeImage.addData(rightImg.getData(0));
cubeImage.addData(leftImg.getData(0));
cubeImage.addData(upImg.getData(0));
cubeImage.addData(downImg.getData(0));
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);
cubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
cubeMap.setWrap(Texture.WrapMode.EdgeClamp);
return cubeMap;
}
/**
* Make a duplicate of this cube Map. That means that it's another instant
* od TextureCubeMap, but the underlying buffers are duplicates of the
* original ones. see {@link ByteBuffer#duplicate()}
*
* Use this if you need to read from the map from multiple threads, it
* should garanty the thread safety. Note that if you want to write to the
* cube map you have to make sure that the different thread do not write to
* the same area of the buffer. The position, limit and mark are not an
* issue.
*
* @param sourceMap
* @return
*/
public static TextureCubeMap duplicateCubeMap(TextureCubeMap sourceMap) {
Image srcImg = sourceMap.getImage();
Image cubeImage = new Image(srcImg.getFormat(), srcImg.getWidth(), srcImg.getHeight(), null, srcImg.getColorSpace());
for (ByteBuffer d : srcImg.getData()) {
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());
cubeMap.setMinFilter(sourceMap.getMinFilter());
cubeMap.setWrap(sourceMap.getWrap(Texture.WrapAxis.S));
return cubeMap;
}
/**
* Computes the vector coordinates, for the given x,y texture coordinates
* and the given cube map face.
*
* Also computes the solid angle for those coordinates and returns it.
*
* To know what the solid angle is please read this.
* http://www.codinglabs.net/article_physically_based_rendering.aspx
*
*
* Original solid angle calculation code is from Ignacio Castaño. This
* formula is from Manne Öhrström's thesis. It takes two coordiantes in the
* range [-1, 1] that define a portion of a cube face and return the area of
* the projection of that portion on the surface of the sphere.
*
* @param x texture coordinate from 0 to 1 in the given cube map face
* @param y texture coordinate from 0 to 1 in the given cube map face
* @param mapSize the size of the cube map
* @param face the face id of the cube map
* @param store the vector3f where the vector will be stored. don't provide
* null for this param
* @return the solid angle for the give parameters
*/
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");
}
/* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
(+ 0.5f is for texel center addressing) */
float u = (2.0f * ((float) x + 0.5f) / (float) mapSize) - 1.0f;
float v = (2.0f * ((float) y + 0.5f) / (float) mapSize) - 1.0f;
getVectorFromCubemapFaceTexCoord(x, y, mapSize, face, store, fixSeamsMethod);
/* Solid angle weight approximation :
* U and V are the -1..1 texture coordinate on the current face.
* Get projected area for this texel */
float x0, y0, x1, y1;
float invRes = 1.0f / (float) mapSize;
x0 = u - invRes;
y0 = v - invRes;
x1 = u + invRes;
y1 = v + invRes;
return areaElement(x0, y0) - areaElement(x0, y1) - areaElement(x1, y0) + areaElement(x1, y1);
}
/**
* used to compute the solid angle
*
* @param x tex coordinates
* @param y tex coordinates
* @return
*/
private static float areaElement(float x, float y) {
return (float) Math.atan2(x * y, sqrt(x * x + y * y + 1));
}
/**
*
* Computes the 3 component vector coordinates for the given face and coords
*
* @param x the x texture coordinate
* @param y the y texture coordinate
* @param mapSize the size of a face of the cube map
* @param face the face to consider
* @param store a vector3f where the resulting vector will be stored
* @param fixSeamsMethod the method to fix the seams
* @return
*/
public static Vector3f getVectorFromCubemapFaceTexCoord(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) {
if (store == null) {
store = new Vector3f();
}
float u;
float v;
if (fixSeamsMethod == FixSeamsMethod.Stretch) {
/* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
* 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;
} else {
//Done if any other fix method or no fix method is set
/* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
* (+ 0.5f is for texel center addressing) */
u = (2.0f * ((float) x + 0.5f) / (float) (mapSize)) - 1.0f;
v = (2.0f * ((float) y + 0.5f) / (float) (mapSize)) - 1.0f;
}
if (fixSeamsMethod == FixSeamsMethod.Wrap) {
// Warp texel centers in the proximity of the edges.
float a = pow((float) mapSize, 2.0f) / pow(((float) mapSize - 1f), 3.0f);
u = a * pow(u, 3f) + u;
v = a * pow(v, 3f) + v;
}
//compute vector depending on the face
// Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
switch (face) {
case 0:
store.set(1f, -v, -u);
break;
case 1:
store.set(-1f, -v, u);
break;
case 2:
store.set(u, 1f, v);
break;
case 3:
store.set(u, -1f, -v);
break;
case 4:
store.set(u, -v, 1f);
break;
case 5:
store.set(-u, -v, -1.0f);
break;
}
return store.normalizeLocal();
}
/**
*
* Computes the texture coortinates and the face of the cube map from the
* given vector
*
* @param texelVect the vector to fetch texelt from the cube map
* @param fixSeamsMethod the method to fix the seams
* @param mapSize the size of one face of the cube map
* @param store a Vector2f where the texture coordinates will be stored
* @return the face from which to fetch the texel
*/
public static int getCubemapFaceTexCoordFromVector(Vector3f texelVect, int mapSize, Vector2f store, FixSeamsMethod fixSeamsMethod) {
float u = 0, v = 0, bias = 0;
int face;
float absX = abs(texelVect.x);
float absY = abs(texelVect.y);
float absZ = abs(texelVect.z);
float max = Math.max(Math.max(absX, absY), absZ);
if (max == absX) {
face = texelVect.x > 0 ? 0 : 1;
} else if (max == absY) {
face = texelVect.y > 0 ? 2 : 3;
} else {
face = texelVect.z > 0 ? 4 : 5;
}
//compute vector depending on the face
// Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
switch (face) {
case 0:
//store.set(1f, -v, -u, 0);
bias = 1f / texelVect.x;
u = -texelVect.z;
v = -texelVect.y;
break;
case 1:
// store.set(-1f, -v, u, 0);
bias = -1f / texelVect.x;
u = texelVect.z;
v = -texelVect.y;
break;
case 2:
//store.set(u, 1f, v, 0);
bias = 1f / texelVect.y;
u = texelVect.x;
v = texelVect.z;
break;
case 3:
//store.set(u, -1f, -v, 0);
bias = -1f / texelVect.y;
u = texelVect.x;
v = -texelVect.z;
break;
case 4:
//store.set(u, -v, 1f, 0);
bias = 1f / texelVect.z;
u = texelVect.x;
v = -texelVect.y;
break;
case 5:
//store.set(-u, -v, -1.0f, 0);
bias = -1f / texelVect.z;
u = -texelVect.x;
v = -texelVect.y;
break;
}
u *= bias;
v *= bias;
if (fixSeamsMethod == FixSeamsMethod.Stretch) {
/* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp
* transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */
u = Math.round((u + 1.0f) * ((float) mapSize - 1.0f) * 0.5f);
v = Math.round((v + 1.0f) * ((float) mapSize - 1.0f) * 0.5f);
} else {
//Done if any other fix method or no fix method is set
/* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
* (+ 0.5f is for texel center addressing) */
u = Math.round((u + 1.0f) * ((float) mapSize) * 0.5f - 0.5f);
v = Math.round((v + 1.0f) * ((float) mapSize) * 0.5f - 0.5f);
}
store.set(u, v);
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 Math.min(1 << (miptot + mipLevel * 2), 8192);
}
public static float getRoughnessFromMip(int miplevel, int miptot) {
float mipScale = 1.2f;
float mipOffset = 0.0f;
return pow(2, (float) (miplevel - (miptot - 1) + mipOffset) / mipScale);
}
public static float getMipFromRoughness(float roughness, int miptot) {
float mipScale = 1.2f;
float Lod = (float) (Math.log(roughness) / Math.log(2)) * mipScale + miptot - 1.0f;
return (float) Math.max(0.0, Lod);
}
/**
* same as
* {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap, com.jme3.utils.EnvMapUtils.FixSeamsMethod)}
* the fix method used is {@link FixSeamsMethod#Wrap}
*
* @param cubeMap the environment cube map to compute SH for
* @return an array of 9 vector3f representing thos coefficients for each
* r,g,b channnel
*/
public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap) {
return getSphericalHarmonicsCoefficents(cubeMap, FixSeamsMethod.Wrap);
}
/**
* Returns the Spherical Harmonics coefficients for this cube map.
*
* The method used is the one from this article :
* http://graphics.stanford.edu/papers/envmap/envmap.pdf
*
* Also good resources on spherical harmonics
* http://dickyjim.wordpress.com/2013/09/04/spherical-harmonics-for-beginners/
*
* @param cubeMap the environment cube map to compute SH for
* @param fixSeamsMethod method to fix seams when computing the SH
* coefficients
* @return an array of 9 vector3f representing thos coefficients for each
* r,g,b channnel
*/
public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap, FixSeamsMethod fixSeamsMethod) {
Vector3f[] shCoef = new Vector3f[NUM_SH_COEFFICIENT];
float[] shDir = new float[9];
float weightAccum = 0.0f;
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");
}
int width = cubeMap.getImage().getWidth();
int height = cubeMap.getImage().getHeight();
Vector3f texelVect = new Vector3f();
ColorRGBA color = new ColorRGBA();
CubeMapWrapper envMapReader = new CubeMapWrapper(cubeMap);
for (int face = 0; face < 6; face++) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
weight = getSolidAngleAndVector(x, y, width, face, texelVect, fixSeamsMethod);
evalShBasis(texelVect, shDir);
envMapReader.getPixel(x, y, face, color);
for (int i = 0; i < NUM_SH_COEFFICIENT; i++) {
if (shCoef[i] == null) {
shCoef[i] = new Vector3f();
}
shCoef[i].setX(shCoef[i].x + color.r * shDir[i] * weight);
shCoef[i].setY(shCoef[i].y + color.g * shDir[i] * weight);
shCoef[i].setZ(shCoef[i].z + color.b * shDir[i] * weight);
}
weightAccum += weight;
}
}
}
/* Normalization - The sum of solid angle should be equal to the solid angle of the sphere (4 PI), so
* normalize in order our weightAccum exactly match 4 PI. */
for (int i = 0; i < NUM_SH_COEFFICIENT; ++i) {
shCoef[i].multLocal(4.0f * PI / weightAccum);
}
return shCoef;
}
/**
* Computes SH coefficient for a given textel dir The method used is the one
* from this article : http://graphics.stanford.edu/papers/envmap/envmap.pdf
*
* @param texelVect
* @param shDir
*/
public static void evalShBasis(Vector3f texelVect, float[] shDir) {
float xV = texelVect.x;
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;
shDir[0] = (1f / (2f * sqrtPi));
shDir[1] = -(sqrt3Pi * yV) / 2f;
shDir[2] = (sqrt3Pi * zV) / 2f;
shDir[3] = -(sqrt3Pi * xV) / 2f;
shDir[4] = (sqrt15Pi * xV * yV) / 2f;
shDir[5] = -(sqrt15Pi * yV * zV) / 2f;
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 * store.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);
}
//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;
}
/**
* 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);
}
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) {
store = new Vector4f();
}
float phi;
long ui = i;
store.setX((float) i / (float) nbrSample);
/* From http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
* Radical Inverse : Van der Corput */
ui = (ui << 16) | (ui >> 16);
ui = ((ui & 0x55555555) << 1) | ((ui & 0xAAAAAAAA) >>> 1);
ui = ((ui & 0x33333333) << 2) | ((ui & 0xCCCCCCCC) >>> 2);
ui = ((ui & 0x0F0F0F0F) << 4) | ((ui & 0xF0F0F0F0) >>> 4);
ui = ((ui & 0x00FF00FF) << 8) | ((ui & 0xFF00FF00) >>> 8);
ui = ui & 0xffffffff;
store.setY(2.3283064365386963e-10f * (float) (ui)); /* 0x100000000 */
phi = 2.0f * PI * store.y;
store.setZ(cos(phi));
store.setW(sin(phi));
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();
}
float cosTheta = sqrt((1f - xi.x) / (1f + (a2 - 1f) * xi.x));
float sinTheta = sqrt(1f - cosTheta * cosTheta);
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 = vars.vect3.set(upVector).crossLocal(normal).normalizeLocal();
Vector3f tangentY = vars.vect4.set(normal).crossLocal(tangentX);
// Tangent to world space
tangentX.multLocal(sinThetaCosPhi);
tangentY.multLocal(sinThetaSinPhi);
vars.vect5.set(normal).multLocal(cosTheta);
// Tangent to world space
store.set(tangentX).addLocal(tangentY).addLocal(vars.vect5);
return store;
}
/**
* Creates a debug Node of the given cube map to attach to the gui node
*
* the cube map is layered this way :
* <pre>
* _____
* | |
* | +Y |
* _____|_____|_____ _____
* | | | | |
* | -X | +Z | +X | -Z |
* |_____|_____|_____|_____|
* | |
* | -Y |
* |_____|
*
*</pre>
*
* @param cubeMap the cube map
* @param assetManager the asset Manager
* @return
*/
public static Node getCubeMapCrossDebugView(TextureCubeMap cubeMap, AssetManager assetManager) {
Node n = new Node("CubeMapDebug" + cubeMap.getName());
int size = cubeMap.getImage().getWidth();
Picture[] pics = new Picture[6];
float ratio = 128f / (float) size;
for (int i = 0; i < 6; i++) {
pics[i] = new Picture("bla");
Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, cubeMap.getImage().getData(i), cubeMap.getImage().getColorSpace()));
pics[i].setTexture(assetManager, tex, true);
pics[i].setWidth(size);
pics[i].setHeight(size);
n.attachChild(pics[i]);
}
pics[0].setLocalTranslation(size, size * 2, 1);
pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[1].setLocalTranslation(size * 3, size * 2, 1);
pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[2].setLocalTranslation(size * 2, size * 3, 1);
pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[3].setLocalTranslation(size * 2, size, 1);
pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[4].setLocalTranslation(size * 2, size * 2, 1);
pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[5].setLocalTranslation(size * 4, size * 2, 1);
pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
Quad q = new Quad(size * 4, size * 3);
Geometry g = new Geometry("bg", q);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Black);
g.setMaterial(mat);
g.setLocalTranslation(0, 0, 0);
n.attachChild(g);
n.setLocalScale(ratio);
return n;
}
public static Node getCubeMapCrossDebugViewWithMipMaps(TextureCubeMap cubeMap, AssetManager assetManager) {
Node n = new Node("CubeMapDebug" + cubeMap.getName());
int size = cubeMap.getImage().getWidth();
int nbMips = cubeMap.getImage().getMipMapSizes().length;
Picture[] pics = new Picture[6*nbMips];
float ratio = 1f;// 128f / (float) size;
int offset = 0;
int guiOffset = 0;
for (int mipLevel = 0; mipLevel < nbMips; mipLevel++) {
size = Math.max(1, cubeMap.getImage().getWidth() >> mipLevel);
int dataSize = cubeMap.getImage().getMipMapSizes()[mipLevel];
byte[] dataArray = new byte[dataSize];
for (int i = 0; i < 6; i++) {
ByteBuffer bb = cubeMap.getImage().getData(i);
bb.rewind();
bb.position(offset);
bb.get(dataArray, 0, dataSize);
ByteBuffer data = BufferUtils.createByteBuffer(dataArray);
pics[i] = new Picture("bla");
Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, data, cubeMap.getImage().getColorSpace()));
pics[i].setTexture(assetManager, tex, true);
pics[i].setWidth(size);
pics[i].setHeight(size);
n.attachChild(pics[i]);
}
pics[0].setLocalTranslation(guiOffset + size, guiOffset + size * 2, 1);
pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[1].setLocalTranslation(guiOffset + size * 3, guiOffset + size * 2, 1);
pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[2].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 3, 1);
pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[3].setLocalTranslation(guiOffset + size * 2, guiOffset + size, 1);
pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[4].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 2, 1);
pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
pics[5].setLocalTranslation(guiOffset + size * 4, guiOffset + size * 2, 1);
pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
guiOffset+=size *2+1;
offset += dataSize;
}
Quad q = new Quad(cubeMap.getImage().getWidth() * 4 + nbMips, guiOffset + size);
Geometry g = new Geometry("bg", q);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Black);
g.setMaterial(mat);
g.setLocalTranslation(0, 0, 0);
n.attachChild(g);
n.setLocalScale(ratio);
return n;
}
/**
* initialize the Irradiancemap
* @param size the size of the map
* @param imageFormat the format of the image
* @return the initialized Irradiance map
*/
public static TextureCubeMap createIrradianceMap(int size, Image.Format imageFormat) {
TextureCubeMap irrMap = new TextureCubeMap(size, size, imageFormat);
irrMap.setMagFilter(Texture.MagFilter.Bilinear);
irrMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
irrMap.getImage().setColorSpace(ColorSpace.Linear);
return irrMap;
}
/**
* initialize the pem map
* @param size the size of the map
* @param imageFormat the format of the image
* @return the initialized prefiltered env map
*/
public static TextureCubeMap createPrefilteredEnvMap(int size, Image.Format imageFormat) {
TextureCubeMap pem = new TextureCubeMap(size, size, imageFormat);
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);
CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
targetWrapper.initMipMaps(nbMipMap);
return pem;
}
}
Loading…
Cancel
Save