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