parent
4b1c61dec1
commit
69f1677649
@ -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…
Reference in new issue