From 69f1677649399f8db4cf7783924be6aef579f797 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sat, 8 Aug 2015 09:25:09 +0200 Subject: [PATCH] Moved PBR implementation --- .../jme3/environment/EnvironmentCamera.java | 351 +++++++ .../generation/IrradianceMapGenerator.java | 176 ++++ .../PrefilteredEnvMapFaceGenerator.java | 266 +++++ .../generation/RunnableWithProgress.java | 89 ++ .../jme3/environment/util/CubeMapWrapper.java | 243 +++++ .../jme3/environment/util/EnvMapUtils.java | 947 ++++++++++++++++++ 6 files changed, 2072 insertions(+) create mode 100644 jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java create mode 100644 jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java create mode 100644 jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java create mode 100644 jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java create mode 100644 jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java create mode 100644 jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java new file mode 100644 index 000000000..bd6efb61c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.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 jobs = new ArrayList(); + + // 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 done) { + getApplication().enqueue(new Callable() { + + 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 callback; + Node scene; + + public SnapshotJob(JobProgressListener callback, Node scene) { + this.callback = callback; + this.scene = scene; + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java b/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java new file mode 100644 index 000000000..356d53b03 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java @@ -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 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() { + + @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() { + + @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; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java b/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java new file mode 100644 index 000000000..9dd6f6962 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java @@ -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 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() { + + @Override + public Void call() throws Exception { + listener.start(); + return null; + } + }); + store = generatePrefilteredEnvMap(sourceMap, targetMapSize, fixSeamsMethod, store); + app.enqueue(new Callable() { + + @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; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java b/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java new file mode 100644 index 000000000..09b495e67 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java @@ -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; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java b/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java new file mode 100644 index 000000000..cdf66a6d1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java @@ -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); + } +} diff --git a/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java new file mode 100644 index 000000000..cb1618f37 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java @@ -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 efficientData = new ArrayList(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 efficientData = new ArrayList(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 : + *
+     *         _____
+     *        |     |
+     *        | +Y  |
+     *   _____|_____|_____ _____
+     *  |     |     |     |     |
+     *  | -X  | +Z  | +X  | -Z  |
+     *  |_____|_____|_____|_____|
+     *        |     |
+     *        | -Y  |
+     *        |_____|
+     *
+     *
+ * + * @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; + } +}