Merge branch 'PBRisComing' # Conflicts: # jme3-core/src/main/java/com/jme3/material/Material.java # jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.javadefine_list_fix
@ -0,0 +1,337 @@ |
||||
/* |
||||
* 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.app.Application; |
||||
import com.jme3.app.state.BaseAppState; |
||||
import com.jme3.environment.generation.JobProgressListener; |
||||
import com.jme3.environment.util.EnvMapUtils; |
||||
import com.jme3.light.LightProbe; |
||||
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.Spatial; |
||||
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 PBR |
||||
* indirect lighting |
||||
* |
||||
* @see LightProbeFactory |
||||
* @see LightProbe |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
public class EnvironmentCamera extends BaseAppState { |
||||
|
||||
protected static Vector3f[] axisX = new Vector3f[6]; |
||||
protected static Vector3f[] axisY = new Vector3f[6]; |
||||
protected static Vector3f[] axisZ = new Vector3f[6]; |
||||
|
||||
protected 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); |
||||
|
||||
} |
||||
protected Image images[]; |
||||
protected ViewPort[] viewports; |
||||
protected FrameBuffer[] framebuffers; |
||||
protected ByteBuffer[] buffers; |
||||
|
||||
protected Vector3f position = new Vector3f(); |
||||
protected ColorRGBA backGroundColor; |
||||
|
||||
protected int size = 128; |
||||
|
||||
private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>(); |
||||
|
||||
/** |
||||
* 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 Spatial scene, final JobProgressListener<TextureCubeMap> done) { |
||||
getApplication().enqueue(new Callable<Void>() { |
||||
|
||||
@Override |
||||
public Void call() throws Exception { |
||||
SnapshotJob job = new SnapshotJob(done, scene); |
||||
jobs.add(job); |
||||
return null; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void render(final RenderManager renderManager) { |
||||
|
||||
if (jobs.isEmpty()) { |
||||
return; |
||||
} |
||||
|
||||
final 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); |
||||
} |
||||
|
||||
final 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; |
||||
} |
||||
|
||||
/** |
||||
* Sets the camera position in world space. |
||||
* |
||||
* @param position the position in world space |
||||
*/ |
||||
public void setPosition(final Vector3f position) { |
||||
this.position.set(position); |
||||
|
||||
if (viewports == null) { |
||||
return; |
||||
} |
||||
|
||||
for (final ViewPort viewPort : viewports) { |
||||
viewPort.getCamera().setLocation(position); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void initialize(Application app) { |
||||
this.backGroundColor = app.getViewPort().getBackgroundColor(); |
||||
|
||||
final Camera[] cameras = new Camera[6]; |
||||
|
||||
Texture2D[] textures = new Texture2D[6]; |
||||
|
||||
viewports = new ViewPort[6]; |
||||
framebuffers = new FrameBuffer[6]; |
||||
buffers = new ByteBuffer[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 (final FrameBuffer frameBuffer : framebuffers) { |
||||
frameBuffer.dispose(); |
||||
} |
||||
|
||||
for (final 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 Camera createOffCamera(final int mapSize, final Vector3f worldPos, final Vector3f axisX, final Vector3f axisY, final Vector3f axisZ) { |
||||
final 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 ViewPort createOffViewPort(final String name, final Camera offCamera) { |
||||
final 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 FrameBuffer createOffScreenFrameBuffer(int mapSize, ViewPort offView) { |
||||
// create offscreen framebuffer
|
||||
final FrameBuffer offBuffer = new FrameBuffer(mapSize, mapSize, 1); |
||||
offBuffer.setDepthBuffer(Image.Format.Depth); |
||||
offView.setOutputFrameBuffer(offBuffer); |
||||
return offBuffer; |
||||
} |
||||
|
||||
/** |
||||
* An inner class to keep track on a snapshot job. |
||||
*/ |
||||
protected class SnapshotJob { |
||||
|
||||
JobProgressListener<TextureCubeMap> callback; |
||||
Spatial scene; |
||||
|
||||
public SnapshotJob(JobProgressListener callback, Spatial scene) { |
||||
this.callback = callback; |
||||
this.scene = scene; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,291 @@ |
||||
/* |
||||
* 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.light.LightProbe; |
||||
import com.jme3.environment.generation.JobProgressListener; |
||||
import com.jme3.environment.generation.PrefilteredEnvMapFaceGenerator; |
||||
import com.jme3.environment.generation.IrradianceMapGenerator; |
||||
import com.jme3.environment.util.EnvMapUtils; |
||||
import com.jme3.environment.generation.JobProgressAdapter; |
||||
import com.jme3.app.Application; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.texture.TextureCubeMap; |
||||
import java.util.concurrent.ScheduledThreadPoolExecutor; |
||||
|
||||
/** |
||||
* This Factory allows to create LightProbes within a scene given an EnvironmentCamera. |
||||
* |
||||
* Since the process can be long, you can provide a JobProgressListener that |
||||
* will be notified of the ongoing generation process when calling the makeProbe method. |
||||
* |
||||
* The process is the folowing : |
||||
* 1. Create an EnvironmentCamera |
||||
* 2. give it a position in the scene |
||||
* 3. call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} |
||||
* 4. add the created LightProbe to a node with the {@link Node#addLight(com.jme3.light.Light) } method. |
||||
* |
||||
* Optionally for step 3 call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) } |
||||
* with a {@link JobProgressListener} to be notified of the progress of the generation process. |
||||
* |
||||
* The generation will be split in several threads for faster generation. |
||||
* |
||||
* This class is entirely thread safe and can be called from any thread. |
||||
* |
||||
* Note that in case you are using a {@link JobProgressListener} all the its |
||||
* method will be called inside and app.enqueu callable. |
||||
* This means that it's completely safe to modify the scenegraph within the |
||||
* Listener method, but also means that the even will be delayed until next update loop. |
||||
* |
||||
* @see EnvironmentCamera |
||||
* @author bouquet |
||||
*/ |
||||
public class LightProbeFactory { |
||||
|
||||
/** |
||||
* Creates a LightProbe with the giver EnvironmentCamera in the given scene. |
||||
* |
||||
* Note that this is an assynchronous process that will run on multiple threads. |
||||
* The process is thread safe. |
||||
* The created lightProbe will only be marked as ready when the rendering process is done. |
||||
* |
||||
* If you want to monitor the process use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) } |
||||
* |
||||
* |
||||
* |
||||
* @see LightProbe |
||||
* @see EnvironmentCamera |
||||
* @param envCam the EnvironmentCamera |
||||
* @param scene the Scene |
||||
* @return the created LightProbe |
||||
*/ |
||||
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene) { |
||||
return makeProbe(envCam, scene, null); |
||||
} |
||||
|
||||
/** |
||||
* Creates a LightProbe with the giver EnvironmentCamera in the given scene. |
||||
* |
||||
* Note that this is an assynchronous process that will run on multiple threads. |
||||
* The process is thread safe. |
||||
* The created lightProbe will only be marked as ready when the rendering process is done. |
||||
* |
||||
* The JobProgressListener will be notified of the progress of the generation. |
||||
* Note that you can also use a {@link JobProgressAdapter}. |
||||
* |
||||
* @see LightProbe |
||||
* @see EnvironmentCamera |
||||
* @see JobProgressListener |
||||
|
||||
* @param envCam the EnvironmentCamera |
||||
* @param scene the Scene |
||||
* @param listener the listener of the genration progress. |
||||
* @return the created LightProbe |
||||
*/ |
||||
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) { |
||||
final LightProbe probe = new LightProbe(); |
||||
probe.setPosition(envCam.getPosition()); |
||||
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat())); |
||||
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat())); |
||||
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() { |
||||
|
||||
@Override |
||||
public void done(TextureCubeMap map) { |
||||
generatePbrMaps(map, probe, envCam.getApplication(), listener); |
||||
} |
||||
}); |
||||
return probe; |
||||
} |
||||
|
||||
/** |
||||
* Updates a LightProbe with the giver EnvironmentCamera in the given scene. |
||||
* |
||||
* Note that this is an assynchronous process that will run on multiple threads. |
||||
* The process is thread safe. |
||||
* The created lightProbe will only be marked as ready when the rendering process is done. |
||||
* |
||||
* The JobProgressListener will be notified of the progress of the generation. |
||||
* Note that you can also use a {@link JobProgressAdapter}. |
||||
* |
||||
* @see LightProbe |
||||
* @see EnvironmentCamera |
||||
* @see JobProgressListener |
||||
* |
||||
* @param probe the Light probe to update |
||||
* @param envCam the EnvironmentCamera |
||||
* @param scene the Scene |
||||
* @param listener the listener of the genration progress. |
||||
* @return the created LightProbe |
||||
*/ |
||||
public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) { |
||||
|
||||
envCam.setPosition(probe.getPosition()); |
||||
|
||||
probe.setReady(false); |
||||
|
||||
if(probe.getIrradianceMap() != null) { |
||||
probe.getIrradianceMap().getImage().dispose(); |
||||
probe.getPrefilteredEnvMap().getImage().dispose(); |
||||
} |
||||
|
||||
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat())); |
||||
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat())); |
||||
|
||||
|
||||
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() { |
||||
|
||||
@Override |
||||
public void done(TextureCubeMap map) { |
||||
generatePbrMaps(map, probe, envCam.getApplication(), listener); |
||||
} |
||||
}); |
||||
return probe; |
||||
} |
||||
|
||||
/** |
||||
* Internally called to generate the maps. |
||||
* This method will spawn 7 thread (one for the IrradianceMap, and one for each face of the prefiltered env map). |
||||
* Those threads will be executed in a ScheduledThreadPoolExecutor that will be shutdown when the genration is done. |
||||
* |
||||
* @param envMap the raw env map rendered by the env camera |
||||
* @param probe the LigthProbe to generate maps for |
||||
* @param app the Application |
||||
* @param listener a progress listener. (can be null if no progress reporting is needed) |
||||
*/ |
||||
private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, final JobProgressListener<LightProbe> listener) { |
||||
IrradianceMapGenerator irrMapGenerator; |
||||
PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6]; |
||||
|
||||
final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7)); |
||||
|
||||
irrMapGenerator = new IrradianceMapGenerator(app, new JobListener(listener, jobState, probe, 6)); |
||||
int size = envMap.getImage().getWidth(); |
||||
irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getIrradianceMap()); |
||||
|
||||
jobState.executor.execute(irrMapGenerator); |
||||
|
||||
for (int i = 0; i < pemGenerators.length; i++) { |
||||
pemGenerators[i] = new PrefilteredEnvMapFaceGenerator(app, i, new JobListener(listener, jobState, probe, i)); |
||||
pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getPrefilteredEnvMap()); |
||||
jobState.executor.execute(pemGenerators[i]); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* An inner class to keep the state of a generation process |
||||
*/ |
||||
private static class JobState { |
||||
|
||||
double progress[] = new double[7]; |
||||
boolean done[] = new boolean[7]; |
||||
ScheduledThreadPoolExecutor executor; |
||||
boolean started = false; |
||||
|
||||
public JobState(ScheduledThreadPoolExecutor executor) { |
||||
this.executor = executor; |
||||
} |
||||
|
||||
boolean isDone() { |
||||
for (boolean d : done) { |
||||
if (d == false) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
float getProgress() { |
||||
float mean = 0; |
||||
for (double progres : progress) { |
||||
mean += progres; |
||||
} |
||||
return mean / 7f; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* An inner JobProgressListener to controll the genration process and properly clean up when it's done |
||||
*/ |
||||
private static class JobListener extends JobProgressAdapter<Integer> { |
||||
|
||||
JobProgressListener<LightProbe> globalListener; |
||||
JobState jobState; |
||||
LightProbe probe; |
||||
|
||||
int index; |
||||
|
||||
public JobListener(JobProgressListener<LightProbe> globalListener, JobState jobState, LightProbe probe, int index) { |
||||
this.globalListener = globalListener; |
||||
this.jobState = jobState; |
||||
this.probe = probe; |
||||
this.index = index; |
||||
} |
||||
|
||||
@Override |
||||
public void start() { |
||||
if (globalListener != null && !jobState.started) { |
||||
jobState.started = true; |
||||
globalListener.start(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void progress(double value) { |
||||
jobState.progress[index] = value; |
||||
if (globalListener != null) { |
||||
globalListener.progress(jobState.getProgress()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void done(Integer result) { |
||||
if (globalListener != null) { |
||||
if (result < 6) { |
||||
globalListener.step("Prefiltered env map face " + result + " generated"); |
||||
} else { |
||||
globalListener.step("Irradiance map generated"); |
||||
|
||||
} |
||||
} |
||||
|
||||
jobState.done[index] = true; |
||||
if (jobState.isDone()) { |
||||
probe.setReady(true); |
||||
if (globalListener != null) { |
||||
globalListener.done(probe); |
||||
} |
||||
jobState.executor.shutdownNow(); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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,59 @@ |
||||
/* |
||||
* 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 Adapter class that implements optional methods of JobProgressListener. |
||||
* Extends this class instead of implementing a JobProgressListener if you need |
||||
* only a subset of method implemented. |
||||
* |
||||
* @author nehon |
||||
* @param <T> |
||||
*/ |
||||
public abstract class JobProgressAdapter<T> implements JobProgressListener<T>{ |
||||
|
||||
@Override |
||||
public void progress(double value) { |
||||
} |
||||
|
||||
@Override |
||||
public void start() { |
||||
} |
||||
|
||||
@Override |
||||
public void step(String message) { |
||||
} |
||||
|
||||
@Override |
||||
public abstract void done(T result); |
||||
|
||||
} |
@ -0,0 +1,67 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
/** |
||||
* An interface listener that will be notified of the progress of an asynchronous |
||||
* generation job. |
||||
* |
||||
* |
||||
* @author nehon |
||||
* @param <T> The type of object generated. |
||||
*/ |
||||
public interface JobProgressListener<T> { |
||||
|
||||
/** |
||||
* Called when the process starts. |
||||
*/ |
||||
public void start(); |
||||
|
||||
/** |
||||
* Can be called when a step of the process has been completed with a relevant message. |
||||
* @param message the message stating of the paricular step completion. |
||||
*/ |
||||
public void step(String message); |
||||
|
||||
/** |
||||
* Called when the process has made some progress. |
||||
* @param value a value from 0 to 1 representing the percentage of completion of the process. |
||||
*/ |
||||
public void progress(double value); |
||||
|
||||
/** |
||||
* Called when the process is done. |
||||
* @param result the object generated by the process. |
||||
*/ |
||||
public void done(T result); |
||||
|
||||
} |
@ -0,0 +1,265 @@ |
||||
/* |
||||
* 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 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,177 @@ |
||||
/* |
||||
* 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.FastMath; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Mesh; |
||||
import com.jme3.scene.VertexBuffer.Type; |
||||
import com.jme3.util.BufferUtils; |
||||
import java.nio.FloatBuffer; |
||||
import java.nio.ShortBuffer; |
||||
|
||||
/** |
||||
* |
||||
* A debuging shape for a BoundingSphere |
||||
* Consists of 3 axis aligned circles. |
||||
* |
||||
* @author nehon |
||||
*/ |
||||
public class BoundingSphereDebug extends Mesh { |
||||
|
||||
protected int vertCount; |
||||
protected int triCount; |
||||
protected int radialSamples = 32; |
||||
protected boolean useEvenSlices; |
||||
protected boolean interior; |
||||
/** |
||||
* the distance from the center point each point falls on |
||||
*/ |
||||
public float radius; |
||||
|
||||
public float getRadius() { |
||||
return radius; |
||||
} |
||||
|
||||
public BoundingSphereDebug() { |
||||
setGeometryData(); |
||||
setIndexData(); |
||||
} |
||||
|
||||
/** |
||||
* builds the vertices based on the radius |
||||
*/ |
||||
private void setGeometryData() { |
||||
setMode(Mode.Lines); |
||||
|
||||
FloatBuffer posBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 3); |
||||
FloatBuffer colBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 4); |
||||
|
||||
setBuffer(Type.Position, 3, posBuf); |
||||
setBuffer(Type.Color, 4, colBuf); |
||||
|
||||
// generate geometry
|
||||
float fInvRS = 1.0f / radialSamples; |
||||
|
||||
// Generate points on the unit circle to be used in computing the mesh
|
||||
// points on a sphere slice.
|
||||
float[] afSin = new float[(radialSamples + 1)]; |
||||
float[] afCos = new float[(radialSamples + 1)]; |
||||
for (int iR = 0; iR < radialSamples; iR++) { |
||||
float fAngle = FastMath.TWO_PI * fInvRS * iR; |
||||
afCos[iR] = FastMath.cos(fAngle); |
||||
afSin[iR] = FastMath.sin(fAngle); |
||||
} |
||||
afSin[radialSamples] = afSin[0]; |
||||
afCos[radialSamples] = afCos[0]; |
||||
|
||||
for (int iR = 0; iR <= radialSamples; iR++) { |
||||
posBuf.put(afCos[iR]) |
||||
.put(afSin[iR]) |
||||
.put(0); |
||||
colBuf.put(ColorRGBA.Blue.r) |
||||
.put(ColorRGBA.Blue.g) |
||||
.put(ColorRGBA.Blue.b) |
||||
.put(ColorRGBA.Blue.a); |
||||
|
||||
} |
||||
for (int iR = 0; iR <= radialSamples; iR++) { |
||||
posBuf.put(afCos[iR]) |
||||
.put(0) |
||||
.put(afSin[iR]); |
||||
colBuf.put(ColorRGBA.Green.r) |
||||
.put(ColorRGBA.Green.g) |
||||
.put(ColorRGBA.Green.b) |
||||
.put(ColorRGBA.Green.a); |
||||
} |
||||
for (int iR = 0; iR <= radialSamples; iR++) { |
||||
posBuf.put(0) |
||||
.put(afCos[iR]) |
||||
.put(afSin[iR]); |
||||
colBuf.put(ColorRGBA.Yellow.r) |
||||
.put(ColorRGBA.Yellow.g) |
||||
.put(ColorRGBA.Yellow.b) |
||||
.put(ColorRGBA.Yellow.a); |
||||
} |
||||
|
||||
updateBound(); |
||||
setStatic(); |
||||
} |
||||
|
||||
/** |
||||
* sets the indices for rendering the sphere. |
||||
*/ |
||||
private void setIndexData() { |
||||
|
||||
// allocate connectivity
|
||||
int nbSegments = (radialSamples) * 3; |
||||
|
||||
ShortBuffer idxBuf = BufferUtils.createShortBuffer(2 * nbSegments); |
||||
setBuffer(Type.Index, 2, idxBuf); |
||||
|
||||
int idx = 0; |
||||
int segDone = 0; |
||||
while (segDone < nbSegments) { |
||||
idxBuf.put((short) idx); |
||||
idxBuf.put((short) (idx + 1)); |
||||
idx++; |
||||
segDone++; |
||||
if (segDone == radialSamples || segDone == radialSamples * 2) { |
||||
idx++; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Convenience factory method that creates a debuging bounding sphere geometry |
||||
* @param assetManager the assetManager |
||||
* @return the bounding sphere debug geometry. |
||||
*/ |
||||
public static Geometry createDebugSphere(AssetManager assetManager) { |
||||
BoundingSphereDebug b = new BoundingSphereDebug(); |
||||
Geometry geom = new Geometry("BoundingDebug", b); |
||||
|
||||
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
||||
mat.setBoolean("VertexColor", true); |
||||
mat.getAdditionalRenderState().setWireframe(true); |
||||
|
||||
geom.setMaterial(mat); |
||||
return geom; |
||||
|
||||
} |
||||
} |
@ -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 mipLevel==0?1:Math.min(1 << (miptot - 1 + (mipLevel) * 2 ), 8192); |
||||
} |
||||
|
||||
public static float getRoughnessFromMip(int miplevel, int miptot) { |
||||
float mipScale = 1.0f; |
||||
float mipOffset = -0.3f; |
||||
|
||||
return pow(2, (miplevel - (miptot - 1) + mipOffset) / mipScale); |
||||
} |
||||
|
||||
public static float getMipFromRoughness(float roughness, int miptot) { |
||||
float mipScale = 1.0f; |
||||
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 * irrCubeMap.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; |
||||
} |
||||
} |
@ -0,0 +1,214 @@ |
||||
/* |
||||
* 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.app.Application; |
||||
import com.jme3.app.state.BaseAppState; |
||||
import com.jme3.bounding.BoundingSphere; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.light.LightProbe; |
||||
import com.jme3.light.Light; |
||||
import com.jme3.renderer.RenderManager; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.scene.shape.Sphere; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* A debug state that will display LIght gizmos on screen. |
||||
* Still a wip and for now it only displays light probes. |
||||
* |
||||
* @author nehon |
||||
*/ |
||||
public class LightsDebugState extends BaseAppState { |
||||
|
||||
private Node debugNode; |
||||
private final Map<LightProbe, Node> probeMapping = new HashMap<LightProbe, Node>(); |
||||
private final List<LightProbe> garbage = new ArrayList<LightProbe>(); |
||||
private Geometry debugGeom; |
||||
private Geometry debugBounds; |
||||
private Material debugMaterial; |
||||
private DebugMode debugMode = DebugMode.PrefilteredEnvMap; |
||||
private float probeScale = 1.0f; |
||||
private Spatial scene = null; |
||||
private final List<LightProbe> probes = new ArrayList<LightProbe>(); |
||||
|
||||
/** |
||||
* Debug mode for light probes |
||||
*/ |
||||
public enum DebugMode { |
||||
|
||||
/** |
||||
* Displays the prefiltered env maps on the debug sphere |
||||
*/ |
||||
PrefilteredEnvMap, |
||||
/** |
||||
* displays the Irradiance map on the debug sphere |
||||
*/ |
||||
IrradianceMap |
||||
} |
||||
|
||||
@Override |
||||
protected void initialize(Application app) { |
||||
debugNode = new Node("Environment debug Node"); |
||||
Sphere s = new Sphere(16, 16, 1); |
||||
debugGeom = new Geometry("debugEnvProbe", s); |
||||
debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md"); |
||||
debugGeom.setMaterial(debugMaterial); |
||||
debugBounds = BoundingSphereDebug.createDebugSphere(app.getAssetManager()); |
||||
if (scene == null) { |
||||
scene = app.getViewPort().getScenes().get(0); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void update(float tpf) { |
||||
for (Light light : scene.getWorldLightList()) { |
||||
switch (light.getType()) { |
||||
|
||||
case Probe: |
||||
LightProbe probe = (LightProbe) light; |
||||
probes.add(probe); |
||||
Node n = probeMapping.get(probe); |
||||
if (n == null) { |
||||
n = new Node("DebugProbe"); |
||||
n.attachChild(debugGeom.clone(true)); |
||||
n.attachChild(debugBounds.clone(false)); |
||||
debugNode.attachChild(n); |
||||
probeMapping.put(probe, n); |
||||
} |
||||
Geometry probeGeom = ((Geometry) n.getChild(0)); |
||||
Material m = probeGeom.getMaterial(); |
||||
probeGeom.setLocalScale(probeScale); |
||||
if (probe.isReady()) { |
||||
if (debugMode == DebugMode.IrradianceMap) { |
||||
m.setTexture("CubeMap", probe.getIrradianceMap()); |
||||
} else { |
||||
m.setTexture("CubeMap", probe.getPrefilteredEnvMap()); |
||||
} |
||||
} |
||||
n.setLocalTranslation(probe.getPosition()); |
||||
n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius()); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
debugNode.updateLogicalState(tpf); |
||||
debugNode.updateGeometricState(); |
||||
cleanProbes(); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Set the scenes for wich to render light gizmos. |
||||
* @param scene |
||||
*/ |
||||
public void setScene(Spatial scene) { |
||||
this.scene = scene; |
||||
} |
||||
|
||||
private void cleanProbes() { |
||||
if (probes.size() != probeMapping.size()) { |
||||
for (LightProbe probe : probeMapping.keySet()) { |
||||
if (!probes.contains(probe)) { |
||||
garbage.add(probe); |
||||
} |
||||
} |
||||
for (LightProbe probe : garbage) { |
||||
probeMapping.remove(probe); |
||||
} |
||||
garbage.clear(); |
||||
probes.clear(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void render(RenderManager rm) { |
||||
rm.renderScene(debugNode, getApplication().getViewPort()); |
||||
} |
||||
|
||||
/** |
||||
* |
||||
* @see DebugMode |
||||
* @return the debug mode |
||||
*/ |
||||
public DebugMode getDebugMode() { |
||||
return debugMode; |
||||
} |
||||
|
||||
/** |
||||
* sets the debug mode |
||||
* @see DebugMode |
||||
* @param debugMode the debug mode |
||||
*/ |
||||
public void setDebugMode(DebugMode debugMode) { |
||||
this.debugMode = debugMode; |
||||
|
||||
} |
||||
|
||||
/** |
||||
* returns the scale of the probe's debug sphere |
||||
* @return |
||||
*/ |
||||
public float getProbeScale() { |
||||
return probeScale; |
||||
} |
||||
|
||||
/** |
||||
* sets the scale of the probe's debug sphere |
||||
* @param probeScale |
||||
*/ |
||||
public void setProbeScale(float probeScale) { |
||||
this.probeScale = probeScale; |
||||
} |
||||
|
||||
@Override |
||||
protected void cleanup(Application app) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
protected void onEnable() { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
protected void onDisable() { |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,70 @@ |
||||
/* |
||||
* 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.light; |
||||
|
||||
import com.jme3.scene.Geometry; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* This strategy returns the closest probe from the rendered object. |
||||
* |
||||
* This is the most basic strategy : The fastest and the easiest. |
||||
* Though it has severe graphical draw backs as there might be very visible seams |
||||
* on static object and some "poping" on dynamic objects. |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
public class BasicProbeBlendingStrategy implements LightProbeBlendingStrategy { |
||||
|
||||
List<LightProbe> lightProbes = new ArrayList<LightProbe>(); |
||||
|
||||
@Override |
||||
public void registerProbe(LightProbe probe) { |
||||
lightProbes.add(probe); |
||||
} |
||||
|
||||
@Override |
||||
public void populateProbes(Geometry g, LightList lightList) { |
||||
if (!lightProbes.isEmpty()) { |
||||
//The first probe is actually the closest to the geometry since the
|
||||
//light list is sorted according to the distance to the geom.
|
||||
LightProbe p = lightProbes.get(0); |
||||
if (p.isReady()) { |
||||
lightList.add(p); |
||||
} |
||||
//clearing the list for next pass.
|
||||
lightProbes.clear(); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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.light; |
||||
|
||||
import com.jme3.asset.AssetManager; |
||||
import com.jme3.bounding.BoundingBox; |
||||
import com.jme3.bounding.BoundingSphere; |
||||
import com.jme3.bounding.BoundingVolume; |
||||
import com.jme3.environment.EnvironmentCamera; |
||||
import com.jme3.environment.LightProbeFactory; |
||||
import com.jme3.environment.util.EnvMapUtils; |
||||
import com.jme3.export.InputCapsule; |
||||
import com.jme3.export.JmeExporter; |
||||
import com.jme3.export.JmeImporter; |
||||
import com.jme3.export.OutputCapsule; |
||||
import com.jme3.export.Savable; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.renderer.Camera; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.texture.TextureCubeMap; |
||||
import com.jme3.util.TempVars; |
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* A LightProbe is not exactly a light. It holds environment map information used for Image Based Lighting. |
||||
* This is used for indirect lighting in the Physically Based Rendering pipeline. |
||||
* |
||||
* A light probe has a position in world space. This is the position from where the Environment Map are rendered. |
||||
* There are two environment maps held by the LightProbe : |
||||
* - The irradiance map (used for indirect diffuse lighting in the PBR pipeline). |
||||
* - The prefiltered environment map (used for indirect specular lighting and reflection in the PBE pipeline). |
||||
* Note that when instanciating the LightProbe, both those maps are null. |
||||
* To render them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} |
||||
* and {@link EnvironmentCamera}. |
||||
* |
||||
* The light probe has an area of effect that is a bounding volume centered on its position. (for now only Bounding spheres are supported). |
||||
* |
||||
* A LightProbe will only be taken into account when it's marked as ready. |
||||
* A light probe is ready when it has valid environment map data set. |
||||
* Note that you should never call setReady yourself. |
||||
* |
||||
* @see LightProbeFactory |
||||
* @see EnvironmentCamera |
||||
* @author nehon |
||||
*/ |
||||
public class LightProbe extends Light implements Savable { |
||||
|
||||
private TextureCubeMap irradianceMap; |
||||
private TextureCubeMap prefilteredEnvMap; |
||||
private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO); |
||||
private boolean ready = false; |
||||
private Vector3f position = new Vector3f(); |
||||
private Node debugNode; |
||||
|
||||
/** |
||||
* Empty constructor used for serialization. |
||||
* You should never call it, use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} instead |
||||
*/ |
||||
public LightProbe() { |
||||
} |
||||
|
||||
/** |
||||
* returns the irradiance map texture of this Light probe. |
||||
* Note that this Texture may not have image data yet if the LightProbe is not ready |
||||
* @return the irradiance map |
||||
*/ |
||||
public TextureCubeMap getIrradianceMap() { |
||||
return irradianceMap; |
||||
} |
||||
|
||||
/** |
||||
* Sets the irradiance map |
||||
* @param irradianceMap the irradiance map |
||||
*/ |
||||
public void setIrradianceMap(TextureCubeMap irradianceMap) { |
||||
this.irradianceMap = irradianceMap; |
||||
} |
||||
|
||||
/** |
||||
* returns the prefiltered environment map texture of this light probe |
||||
* Note that this Texture may not have image data yet if the LightProbe is not ready |
||||
* @return the prefiltered environment map |
||||
*/ |
||||
public TextureCubeMap getPrefilteredEnvMap() { |
||||
return prefilteredEnvMap; |
||||
} |
||||
|
||||
/** |
||||
* Sets the prefiltered environment map |
||||
* @param prefileteredEnvMap the prefiltered environment map |
||||
*/ |
||||
public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) { |
||||
this.prefilteredEnvMap = prefileteredEnvMap; |
||||
} |
||||
|
||||
@Override |
||||
public void write(JmeExporter ex) throws IOException { |
||||
super.write(ex); |
||||
OutputCapsule oc = ex.getCapsule(this); |
||||
oc.write(irradianceMap, "irradianceMap", null); |
||||
oc.write(prefilteredEnvMap, "prefilteredEnvMap", null); |
||||
oc.write(position, "position", null); |
||||
oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); |
||||
oc.write(ready, "ready", false); |
||||
} |
||||
|
||||
@Override |
||||
public void read(JmeImporter im) throws IOException { |
||||
super.read(im); |
||||
InputCapsule ic = im.getCapsule(this); |
||||
irradianceMap = (TextureCubeMap) ic.readSavable("irradianceMap", null); |
||||
prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null); |
||||
position = (Vector3f) ic.readSavable("position", this); |
||||
bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); |
||||
ready = ic.readBoolean("ready", false); |
||||
} |
||||
|
||||
/** |
||||
* returns the bounding volume of this LightProbe |
||||
* @return a bounding volume. |
||||
*/ |
||||
public BoundingVolume getBounds() { |
||||
return bounds; |
||||
} |
||||
|
||||
/** |
||||
* Sets the bounds of this LightProbe |
||||
* Note that for now only BoundingSphere is supported and this method will |
||||
* throw an UnsupportedOperationException with any other BoundingVolume type |
||||
* @param bounds the bounds of the LightProbe |
||||
*/ |
||||
public void setBounds(BoundingVolume bounds) { |
||||
if( bounds.getType()!= BoundingVolume.Type.Sphere){ |
||||
throw new UnsupportedOperationException("For not only BoundingSphere are suported for LightProbe"); |
||||
} |
||||
this.bounds = bounds; |
||||
} |
||||
|
||||
/** |
||||
* return true if the LightProbe is ready, meaning the Environment maps have |
||||
* been loaded or rnedered and are ready to be used by a material |
||||
* @return the LightProbe ready state |
||||
*/ |
||||
public boolean isReady() { |
||||
return ready; |
||||
} |
||||
|
||||
/** |
||||
* Don't call this method directly. |
||||
* It's meant to be called by additional systems that will load or render |
||||
* the Environment maps of the LightProbe |
||||
* @param ready the ready state of the LightProbe. |
||||
*/ |
||||
public void setReady(boolean ready) { |
||||
this.ready = ready; |
||||
} |
||||
|
||||
/** |
||||
* For debuging porpose only |
||||
* Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps. |
||||
* |
||||
* @param manager the asset manager |
||||
* @return a debug node |
||||
*/ |
||||
public Node getDebugGui(AssetManager manager) { |
||||
if (!ready) { |
||||
throw new UnsupportedOperationException("This EnvProbeis not ready yet, try to test isReady()"); |
||||
} |
||||
if (debugNode == null) { |
||||
debugNode = new Node("debug gui probe"); |
||||
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager); |
||||
Node debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(getIrradianceMap(), manager); |
||||
|
||||
debugNode.attachChild(debugIrrCm); |
||||
debugNode.attachChild(debugPfemCm); |
||||
debugPfemCm.setLocalTranslation(520, 0, 0); |
||||
} |
||||
|
||||
return debugNode; |
||||
} |
||||
|
||||
/** |
||||
* Returns the position of the LightProbe in world space |
||||
* @return the wolrd space position |
||||
*/ |
||||
public Vector3f getPosition() { |
||||
return position; |
||||
} |
||||
|
||||
/** |
||||
* Sets the position of the LightProbe in world space |
||||
* @param position the wolrd space position |
||||
*/ |
||||
public void setPosition(Vector3f position) { |
||||
this.position.set(position); |
||||
getBounds().setCenter(position); |
||||
} |
||||
|
||||
@Override |
||||
public boolean intersectsBox(BoundingBox box, TempVars vars) { |
||||
return getBounds().intersectsBoundingBox(box); |
||||
} |
||||
|
||||
@Override |
||||
public boolean intersectsFrustum(Camera camera, TempVars vars) { |
||||
return camera.contains(bounds) != Camera.FrustumIntersect.Outside; |
||||
} |
||||
|
||||
@Override |
||||
protected void computeLastDistance(Spatial owner) { |
||||
if (owner.getWorldBound() != null) { |
||||
BoundingVolume bv = owner.getWorldBound(); |
||||
lastDistance = bv.distanceSquaredTo(position); |
||||
} else { |
||||
lastDistance = owner.getWorldTranslation().distanceSquared(position); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Type getType() { |
||||
return Type.Probe; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "Light Probe : " + name + " at " + position + " / " + bounds; |
||||
} |
||||
|
||||
@Override |
||||
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) { |
||||
return getBounds().intersectsSphere(sphere); |
||||
} |
||||
|
||||
|
||||
|
||||
} |
@ -0,0 +1,208 @@ |
||||
/* |
||||
* 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.light; |
||||
|
||||
import com.jme3.bounding.BoundingSphere; |
||||
import com.jme3.post.SceneProcessor; |
||||
import com.jme3.renderer.RenderManager; |
||||
import com.jme3.renderer.ViewPort; |
||||
import com.jme3.renderer.queue.RenderQueue; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.texture.FrameBuffer; |
||||
import com.jme3.util.TempVars; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* this processor allows to blend several light probes maps together according to a Point of Interest. |
||||
* This is all based on this article by Sebastien lagarde |
||||
* https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
|
||||
* @author Nehon |
||||
*/ |
||||
public class LightProbeBlendingProcessor implements SceneProcessor { |
||||
|
||||
private ViewPort viewPort; |
||||
private LightFilter prevFilter; |
||||
private RenderManager renderManager; |
||||
private LightProbe probe = new LightProbe(); |
||||
private Spatial poi; |
||||
|
||||
public LightProbeBlendingProcessor(Spatial poi) { |
||||
this.poi = poi; |
||||
} |
||||
|
||||
@Override |
||||
public void initialize(RenderManager rm, ViewPort vp) { |
||||
viewPort = vp; |
||||
renderManager = rm; |
||||
prevFilter = rm.getLightFilter(); |
||||
rm.setLightFilter(new PoiLightProbeLightFilter(this)); |
||||
} |
||||
|
||||
@Override |
||||
public void reshape(ViewPort vp, int w, int h) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public boolean isInitialized() { |
||||
return viewPort != null; |
||||
} |
||||
|
||||
@Override |
||||
public void preFrame(float tpf) { |
||||
|
||||
} |
||||
|
||||
/** 1. For POI take a spatial in the constructor and make all calculation against its world pos |
||||
* - Alternatively compute an arbitrary POI by casting rays from the camera |
||||
* (one in the center and one for each corner and take the median point) |
||||
* 2. Take the 4 most weighted probes for default. Maybe allow the user to change this |
||||
* 3. For the inner influence radius take half of the radius for a start we'll see then how to change this. |
||||
* |
||||
*/ |
||||
@Override |
||||
public void postQueue(RenderQueue rq) { |
||||
List<BlendFactor> blendFactors = new ArrayList<BlendFactor>(); |
||||
float sumBlendFactors = computeBlendFactors(blendFactors); |
||||
|
||||
//Sort blend factors according to their weight
|
||||
Collections.sort(blendFactors); |
||||
|
||||
//normalize blend factors;
|
||||
float normalizer = 1f / sumBlendFactors; |
||||
for (BlendFactor blendFactor : blendFactors) { |
||||
blendFactor.ndf *= normalizer; |
||||
// System.err.println(blendFactor);
|
||||
} |
||||
|
||||
|
||||
//for now just pick the first probe.
|
||||
if(!blendFactors.isEmpty()){ |
||||
probe = blendFactors.get(0).lightProbe; |
||||
}else{ |
||||
probe = null; |
||||
} |
||||
} |
||||
|
||||
private float computeBlendFactors(List<BlendFactor> blendFactors) { |
||||
float sumBlendFactors = 0; |
||||
for (Spatial scene : viewPort.getScenes()) { |
||||
for (Light light : scene.getWorldLightList()) { |
||||
if(light.getType() == Light.Type.Probe){ |
||||
LightProbe p = (LightProbe)light; |
||||
TempVars vars = TempVars.get(); |
||||
boolean intersect = p.intersectsFrustum(viewPort.getCamera(), vars); |
||||
vars.release(); |
||||
//check if the probe is inside the camera frustum
|
||||
if(intersect){ |
||||
|
||||
//is the poi inside the bounds of this probe
|
||||
if(poi.getWorldBound().intersects(p.getBounds())){ |
||||
|
||||
//computing the distance as we need it to check if th epoi in in the inner radius and later to compute the weight
|
||||
float outerRadius = ((BoundingSphere)p.getBounds()).getRadius(); |
||||
float innerRadius = outerRadius * 0.5f; |
||||
float distance = p.getBounds().getCenter().distance(poi.getWorldTranslation()); |
||||
|
||||
// if the poi in inside the inner range of this probe, then this probe is the only one that matters.
|
||||
if( distance < innerRadius ){ |
||||
blendFactors.clear(); |
||||
blendFactors.add(new BlendFactor(p, 1.0f)); |
||||
return 1.0f; |
||||
} |
||||
//else we need to compute the weight of this probe and collect it for blending
|
||||
float ndf = (distance - innerRadius) / (outerRadius - innerRadius); |
||||
sumBlendFactors += ndf; |
||||
blendFactors.add(new BlendFactor(p, ndf)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return sumBlendFactors; |
||||
} |
||||
|
||||
@Override |
||||
public void postFrame(FrameBuffer out) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void cleanup() { |
||||
viewPort = null; |
||||
renderManager.setLightFilter(prevFilter); |
||||
} |
||||
|
||||
public void populateProbe(LightList lightList){ |
||||
if(probe != null && probe.isReady()){ |
||||
lightList.add(probe); |
||||
} |
||||
} |
||||
|
||||
public Spatial getPoi() { |
||||
return poi; |
||||
} |
||||
|
||||
public void setPoi(Spatial poi) { |
||||
this.poi = poi; |
||||
} |
||||
|
||||
|
||||
private class BlendFactor implements Comparable<BlendFactor>{ |
||||
|
||||
LightProbe lightProbe; |
||||
float ndf; |
||||
|
||||
public BlendFactor(LightProbe lightProbe, float ndf) { |
||||
this.lightProbe = lightProbe; |
||||
this.ndf = ndf; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "BlendFactor{" + "lightProbe=" + lightProbe + ", ndf=" + ndf + '}'; |
||||
} |
||||
|
||||
@Override |
||||
public int compareTo(BlendFactor o) { |
||||
if(o.ndf > ndf){ |
||||
return -1; |
||||
}else if(o.ndf < ndf){ |
||||
return 1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,55 @@ |
||||
/* |
||||
* 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.light; |
||||
|
||||
import com.jme3.scene.Geometry; |
||||
|
||||
/** |
||||
* This is the interface to implement if you want to make your own LightProbe blending strategy. |
||||
* The strategy sets the way multiple LightProbes will be handled for a given object. |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
public interface LightProbeBlendingStrategy { |
||||
|
||||
/** |
||||
* Registers a probe with this strategy |
||||
* @param probe |
||||
*/ |
||||
public void registerProbe(LightProbe probe); |
||||
/** |
||||
* Populates the resulting light probes into the given light list. |
||||
* @param g the geometry for wich the light list is computed |
||||
* @param lightList the result light list |
||||
*/ |
||||
public void populateProbes(Geometry g, LightList lightList); |
||||
} |
@ -0,0 +1,107 @@ |
||||
/* |
||||
* 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.light; |
||||
|
||||
import com.jme3.bounding.BoundingBox; |
||||
import com.jme3.bounding.BoundingSphere; |
||||
import com.jme3.bounding.BoundingVolume; |
||||
import com.jme3.renderer.Camera; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.util.TempVars; |
||||
import java.util.HashSet; |
||||
|
||||
public final class PoiLightProbeLightFilter implements LightFilter { |
||||
|
||||
private Camera camera; |
||||
private final HashSet<Light> processedLights = new HashSet<Light>(); |
||||
private final LightProbeBlendingProcessor processor; |
||||
|
||||
public PoiLightProbeLightFilter(LightProbeBlendingProcessor processor) { |
||||
this.processor = processor; |
||||
} |
||||
|
||||
@Override |
||||
public void setCamera(Camera camera) { |
||||
this.camera = camera; |
||||
for (Light light : processedLights) { |
||||
light.frustumCheckNeeded = true; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void filterLights(Geometry geometry, LightList filteredLightList) { |
||||
TempVars vars = TempVars.get(); |
||||
try { |
||||
LightList worldLights = geometry.getWorldLightList(); |
||||
|
||||
for (int i = 0; i < worldLights.size(); i++) { |
||||
Light light = worldLights.get(i); |
||||
|
||||
if (light.getType() == Light.Type.Probe) { |
||||
continue; |
||||
} |
||||
|
||||
if (light.frustumCheckNeeded) { |
||||
processedLights.add(light); |
||||
light.frustumCheckNeeded = false; |
||||
light.intersectsFrustum = light.intersectsFrustum(camera, vars); |
||||
} |
||||
|
||||
if (!light.intersectsFrustum) { |
||||
continue; |
||||
} |
||||
|
||||
BoundingVolume bv = geometry.getWorldBound(); |
||||
|
||||
if (bv instanceof BoundingBox) { |
||||
if (!light.intersectsBox((BoundingBox) bv, vars)) { |
||||
continue; |
||||
} |
||||
} else if (bv instanceof BoundingSphere) { |
||||
if (!Float.isInfinite(((BoundingSphere) bv).getRadius())) { |
||||
if (!light.intersectsSphere((BoundingSphere) bv, vars)) { |
||||
continue; |
||||
} |
||||
} |
||||
} |
||||
|
||||
filteredLightList.add(light); |
||||
} |
||||
|
||||
processor.populateProbe(filteredLightList); |
||||
|
||||
} finally { |
||||
vars.release(); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,251 @@ |
||||
/* |
||||
* 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.material.logic; |
||||
|
||||
import com.jme3.asset.AssetManager; |
||||
import com.jme3.bounding.BoundingSphere; |
||||
import com.jme3.light.*; |
||||
import com.jme3.material.*; |
||||
import com.jme3.material.RenderState.BlendMode; |
||||
import com.jme3.math.*; |
||||
import com.jme3.renderer.*; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.shader.*; |
||||
import com.jme3.util.TempVars; |
||||
|
||||
import java.util.EnumSet; |
||||
|
||||
public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic { |
||||
|
||||
private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING"; |
||||
private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS"; |
||||
private static final RenderState ADDITIVE_LIGHT = new RenderState(); |
||||
|
||||
private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); |
||||
|
||||
static { |
||||
ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive); |
||||
ADDITIVE_LIGHT.setDepthWrite(false); |
||||
} |
||||
|
||||
private final int singlePassLightingDefineId; |
||||
private final int nbLightsDefineId; |
||||
|
||||
public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) { |
||||
super(techniqueDef); |
||||
singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean); |
||||
nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int); |
||||
} |
||||
|
||||
@Override |
||||
public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, |
||||
EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) { |
||||
defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3); |
||||
defines.set(singlePassLightingDefineId, true); |
||||
return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines); |
||||
} |
||||
|
||||
/** |
||||
* Uploads the lights in the light list as two uniform arrays.<br/><br/> * |
||||
* <p> |
||||
* <code>uniform vec4 g_LightColor[numLights];</code><br/> //
|
||||
* g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
|
||||
* g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
|
||||
* 2 = Spot. <br/> <br/> |
||||
* <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
|
||||
* g_LightPosition.xyz is the position of the light (for point lights)<br/> |
||||
* // or the direction of the light (for directional lights).<br/> //
|
||||
* g_LightPosition.w is the inverse radius (1/r) of the light (for |
||||
* attenuation) <br/> </p> |
||||
*/ |
||||
protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, int lastTexUnit) { |
||||
if (numLights == 0) { // this shader does not do lighting, ignore.
|
||||
return 0; |
||||
} |
||||
|
||||
Uniform lightData = shader.getUniform("g_LightData"); |
||||
lightData.setVector4Length(numLights * 3);//8 lights * max 3
|
||||
Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); |
||||
Uniform lightProbeData = shader.getUniform("g_LightProbeData"); |
||||
lightProbeData.setVector4Length(1); |
||||
Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap"); |
||||
Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap"); |
||||
|
||||
LightProbe lightProbe = null; |
||||
if (startIndex != 0) { |
||||
// apply additive blending for 2nd and future passes
|
||||
rm.getRenderer().applyRenderState(ADDITIVE_LIGHT); |
||||
ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); |
||||
}else{ |
||||
lightProbe = extractIndirectLights(lightList,true); |
||||
ambientColor.setValue(VarType.Vector4, ambientLightColor); |
||||
} |
||||
|
||||
//If there is a lightProbe in the list we force it's render on the first pass
|
||||
if(lightProbe != null){ |
||||
BoundingSphere s = (BoundingSphere)lightProbe.getBounds(); |
||||
lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f/s.getRadius(), 0); |
||||
//assigning new texture indexes
|
||||
int irrUnit = lastTexUnit++; |
||||
int pemUnit = lastTexUnit++; |
||||
|
||||
rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap()); |
||||
lightProbeIrrMap.setValue(VarType.Int, irrUnit); |
||||
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap()); |
||||
lightProbePemMap.setValue(VarType.Int, pemUnit); |
||||
} else { |
||||
//Disable IBL for this pass
|
||||
lightProbeData.setVector4InArray(0,0,0,-1, 0); |
||||
} |
||||
|
||||
int lightDataIndex = 0; |
||||
TempVars vars = TempVars.get(); |
||||
Vector4f tmpVec = vars.vect4f1; |
||||
int curIndex; |
||||
int endIndex = numLights + startIndex; |
||||
boolean useIBL = false; |
||||
for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { |
||||
|
||||
|
||||
Light l = lightList.get(curIndex); |
||||
if(l.getType() == Light.Type.Ambient){ |
||||
endIndex++; |
||||
continue; |
||||
} |
||||
ColorRGBA color = l.getColor(); |
||||
//Color
|
||||
|
||||
if(l.getType() != Light.Type.Probe){ |
||||
lightData.setVector4InArray(color.getRed(), |
||||
color.getGreen(), |
||||
color.getBlue(), |
||||
l.getType().getId(), |
||||
lightDataIndex); |
||||
lightDataIndex++; |
||||
} |
||||
|
||||
switch (l.getType()) { |
||||
case Directional: |
||||
DirectionalLight dl = (DirectionalLight) l; |
||||
Vector3f dir = dl.getDirection(); |
||||
//Data directly sent in view space to avoid a matrix mult for each pixel
|
||||
tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f); |
||||
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex); |
||||
lightDataIndex++; |
||||
//PADDING
|
||||
lightData.setVector4InArray(0,0,0,0, lightDataIndex); |
||||
lightDataIndex++; |
||||
break; |
||||
case Point: |
||||
PointLight pl = (PointLight) l; |
||||
Vector3f pos = pl.getPosition(); |
||||
float invRadius = pl.getInvRadius(); |
||||
tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f); |
||||
|
||||
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex); |
||||
lightDataIndex++; |
||||
//PADDING
|
||||
lightData.setVector4InArray(0,0,0,0, lightDataIndex); |
||||
lightDataIndex++; |
||||
break; |
||||
case Spot: |
||||
SpotLight sl = (SpotLight) l; |
||||
Vector3f pos2 = sl.getPosition(); |
||||
Vector3f dir2 = sl.getDirection(); |
||||
float invRange = sl.getInvSpotRange(); |
||||
float spotAngleCos = sl.getPackedAngleCos(); |
||||
tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f); |
||||
|
||||
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex); |
||||
lightDataIndex++; |
||||
|
||||
tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f); |
||||
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); |
||||
lightDataIndex++; |
||||
break; |
||||
default: |
||||
throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); |
||||
} |
||||
} |
||||
vars.release(); |
||||
|
||||
//Padding of unsued buffer space
|
||||
while(lightDataIndex < numLights * 3) { |
||||
lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); |
||||
lightDataIndex++; |
||||
} |
||||
return curIndex; |
||||
} |
||||
|
||||
@Override |
||||
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { |
||||
int nbRenderedLights = 0; |
||||
Renderer renderer = renderManager.getRenderer(); |
||||
int batchSize = renderManager.getSinglePassLightBatchSize(); |
||||
if (lights.size() == 0) { |
||||
updateLightListUniforms(shader, geometry, lights,batchSize, renderManager, 0, lastTexUnit); |
||||
renderer.setShader(shader); |
||||
renderMeshFromGeometry(renderer, geometry); |
||||
} else { |
||||
while (nbRenderedLights < lights.size()) { |
||||
nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastTexUnit); |
||||
renderer.setShader(shader); |
||||
renderMeshFromGeometry(renderer, geometry); |
||||
} |
||||
} |
||||
return; |
||||
} |
||||
|
||||
protected LightProbe extractIndirectLights(LightList lightList, boolean removeLights) { |
||||
ambientLightColor.set(0, 0, 0, 1); |
||||
LightProbe probe = null; |
||||
for (int j = 0; j < lightList.size(); j++) { |
||||
Light l = lightList.get(j); |
||||
if (l instanceof AmbientLight) { |
||||
ambientLightColor.addLocal(l.getColor()); |
||||
if(removeLights){ |
||||
lightList.remove(l); |
||||
j--; |
||||
} |
||||
} |
||||
if (l instanceof LightProbe) { |
||||
probe = (LightProbe)l; |
||||
if(removeLights){ |
||||
lightList.remove(l); |
||||
j--; |
||||
} |
||||
} |
||||
} |
||||
ambientLightColor.a = 1.0f; |
||||
return probe; |
||||
} |
||||
} |
@ -0,0 +1,238 @@ |
||||
#import "Common/ShaderLib/Parallax.glsllib" |
||||
#import "Common/ShaderLib/PBR.glsllib" |
||||
#import "Common/ShaderLib/Lighting.glsllib" |
||||
|
||||
varying vec2 texCoord; |
||||
#ifdef SEPARATE_TEXCOORD |
||||
varying vec2 texCoord2; |
||||
#endif |
||||
|
||||
varying vec4 Color; |
||||
|
||||
uniform vec4 g_LightData[NB_LIGHTS]; |
||||
|
||||
uniform vec3 g_CameraPosition; |
||||
|
||||
uniform float m_Roughness; |
||||
uniform float m_Metallic; |
||||
|
||||
varying vec3 wPosition; |
||||
|
||||
|
||||
//#ifdef INDIRECT_LIGHTING |
||||
// uniform sampler2D m_IntegrateBRDF; |
||||
uniform samplerCube g_PrefEnvMap; |
||||
uniform samplerCube g_IrradianceMap; |
||||
uniform vec4 g_LightProbeData; |
||||
//#endif |
||||
|
||||
#ifdef BASECOLORMAP |
||||
uniform sampler2D m_BaseColorMap; |
||||
#endif |
||||
#ifdef METALLICMAP |
||||
uniform sampler2D m_MetallicMap; |
||||
#endif |
||||
#ifdef ROUGHNESSMAP |
||||
uniform sampler2D m_RoughnessMap; |
||||
#endif |
||||
|
||||
#ifdef EMISSIVE |
||||
uniform vec4 m_Emissive; |
||||
#endif |
||||
#ifdef EMISSIVEMAP |
||||
uniform sampler2D m_EmissiveMap; |
||||
#endif |
||||
#if defined(EMISSIVE) || defined(EMISSIVEMAP) |
||||
uniform float m_EmissivePower; |
||||
uniform float m_EmissiveIntensity; |
||||
#endif |
||||
|
||||
#ifdef SPECGLOSSPIPELINE |
||||
uniform sampler2D m_SpecularMap; |
||||
uniform sampler2D m_GlossMap; |
||||
#endif |
||||
|
||||
#ifdef PARALLAXMAP |
||||
uniform sampler2D m_ParallaxMap; |
||||
#endif |
||||
#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) |
||||
uniform float m_ParallaxHeight; |
||||
#endif |
||||
|
||||
#ifdef LIGHTMAP |
||||
uniform sampler2D m_LightMap; |
||||
#endif |
||||
|
||||
#if defined(NORMALMAP) || defined(PARALLAXMAP) |
||||
uniform sampler2D m_NormalMap; |
||||
varying vec4 wTangent; |
||||
#endif |
||||
varying vec3 wNormal; |
||||
|
||||
#ifdef DISCARD_ALPHA |
||||
uniform float m_AlphaDiscardThreshold; |
||||
#endif |
||||
|
||||
void main(){ |
||||
vec2 newTexCoord; |
||||
vec3 viewDir = normalize(g_CameraPosition - wPosition); |
||||
|
||||
#if defined(NORMALMAP) || defined(PARALLAXMAP) |
||||
mat3 tbnMat = mat3(wTangent.xyz, wTangent.w * cross( (wNormal), (wTangent.xyz)), wNormal.xyz); |
||||
#endif |
||||
|
||||
#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) |
||||
vec3 vViewDir = viewDir * tbnMat; |
||||
#ifdef STEEP_PARALLAX |
||||
#ifdef NORMALMAP_PARALLAX |
||||
//parallax map is stored in the alpha channel of the normal map |
||||
newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); |
||||
#else |
||||
//parallax map is a texture |
||||
newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); |
||||
#endif |
||||
#else |
||||
#ifdef NORMALMAP_PARALLAX |
||||
//parallax map is stored in the alpha channel of the normal map |
||||
newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); |
||||
#else |
||||
//parallax map is a texture |
||||
newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); |
||||
#endif |
||||
#endif |
||||
#else |
||||
newTexCoord = texCoord; |
||||
#endif |
||||
|
||||
#ifdef BASECOLORMAP |
||||
vec4 albedo = texture2D(m_BaseColorMap, newTexCoord); |
||||
#else |
||||
vec4 albedo = Color; |
||||
#endif |
||||
#ifdef ROUGHNESSMAP |
||||
float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-8); |
||||
#else |
||||
float Roughness = max(m_Roughness, 1e-8); |
||||
#endif |
||||
#ifdef METALLICMAP |
||||
float Metallic = texture2D(m_MetallicMap, newTexCoord).r; |
||||
#else |
||||
float Metallic = max(m_Metallic, 0.0); |
||||
#endif |
||||
|
||||
float alpha = Color.a * albedo.a; |
||||
|
||||
#ifdef DISCARD_ALPHA |
||||
if(alpha < m_AlphaDiscardThreshold){ |
||||
discard; |
||||
} |
||||
#endif |
||||
|
||||
// *********************** |
||||
// Read from textures |
||||
// *********************** |
||||
#if defined(NORMALMAP) |
||||
vec4 normalHeight = texture2D(m_NormalMap, newTexCoord); |
||||
//Note the -2.0 and -1.0. We invert the green channel of the normal map, |
||||
//as it's complient with normal maps generated with blender. |
||||
//see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898 |
||||
//for more explanation. |
||||
vec3 normal = normalize((normalHeight.xyz * vec3(2.0,-2.0,2.0) - vec3(1.0,-1.0,1.0))); |
||||
normal = normalize(tbnMat * normal); |
||||
//normal = normalize(normal * inverse(tbnMat)); |
||||
#else |
||||
vec3 normal = normalize(wNormal); |
||||
#endif |
||||
|
||||
|
||||
#ifdef LIGHTMAP |
||||
vec3 lightMapColor; |
||||
#ifdef SEPARATE_TEXCOORD |
||||
lightMapColor = texture2D(m_LightMap, texCoord2).rgb; |
||||
#else |
||||
lightMapColor = texture2D(m_LightMap, texCoord).rgb; |
||||
#endif |
||||
specularColor.rgb *= lightMapColor; |
||||
albedo.rgb *= lightMapColor; |
||||
#endif |
||||
|
||||
float specular = 0.5; |
||||
#ifdef SPECGLOSSPIPELINE |
||||
vec4 specularColor = texture2D(m_SpecularMap, newTexCoord); |
||||
vec4 diffuseColor = albedo; |
||||
Roughness = 1.0 - texture2D(m_GlossMap, newTexCoord).r; |
||||
#else |
||||
float nonMetalSpec = 0.08 * specular; |
||||
vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic; |
||||
vec4 diffuseColor = albedo - albedo * Metallic; |
||||
#endif |
||||
|
||||
gl_FragColor.rgb = vec3(0.0); |
||||
float ndotv = max( dot( normal, viewDir ),0.0); |
||||
for( int i = 0;i < NB_LIGHTS; i+=3){ |
||||
vec4 lightColor = g_LightData[i]; |
||||
vec4 lightData1 = g_LightData[i+1]; |
||||
vec4 lightDir; |
||||
vec3 lightVec; |
||||
lightComputeDir(wPosition, lightColor.w, lightData1, lightDir, lightVec); |
||||
|
||||
float fallOff = 1.0; |
||||
#if __VERSION__ >= 110 |
||||
// allow use of control flow |
||||
if(lightColor.w > 1.0){ |
||||
#endif |
||||
fallOff = computeSpotFalloff(g_LightData[i+2], lightVec); |
||||
#if __VERSION__ >= 110 |
||||
} |
||||
#endif |
||||
//point light attenuation |
||||
fallOff *= lightDir.w; |
||||
|
||||
lightDir.xyz = normalize(lightDir.xyz); |
||||
vec3 directDiffuse; |
||||
vec3 directSpecular; |
||||
|
||||
PBR_ComputeDirectLight(normal, lightDir.xyz, viewDir, |
||||
lightColor.rgb,specular, Roughness, ndotv, |
||||
directDiffuse, directSpecular); |
||||
|
||||
vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular * specularColor.rgb; |
||||
|
||||
gl_FragColor.rgb += directLighting * fallOff; |
||||
} |
||||
|
||||
vec3 rv = reflect(-viewDir.xyz, normal.xyz); |
||||
//prallax fix for spherical bounds from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ |
||||
// g_LightProbeData.w is 1/probe radius, g_LightProbeData.xyz is the position of the lightProbe. |
||||
rv = g_LightProbeData.w * (wPosition - g_LightProbeData.xyz) +rv; |
||||
|
||||
//horizon fade from http://marmosetco.tumblr.com/post/81245981087 |
||||
float horiz = dot(rv, wNormal.xyz); |
||||
float horizFadePower= 1.0 - Roughness; |
||||
horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 ); |
||||
horiz *= horiz; |
||||
|
||||
vec3 indirectDiffuse = vec3(0.0); |
||||
vec3 indirectSpecular = vec3(0.0); |
||||
indirectDiffuse = textureCube(g_IrradianceMap, normal.xyz).rgb * diffuseColor.rgb; |
||||
|
||||
indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, rv.xyz); |
||||
indirectSpecular *= vec3(horiz); |
||||
|
||||
vec3 indirectLighting = indirectDiffuse + indirectSpecular; |
||||
|
||||
gl_FragColor.rgb = gl_FragColor.rgb + indirectLighting * step( 0.0, g_LightProbeData.w); |
||||
|
||||
#if defined(EMISSIVE) || defined (EMISSIVEMAP) |
||||
#ifdef EMISSIVEMAP |
||||
vec4 emissive = texture2D(m_EmissiveMap, newTexCoord); |
||||
#else |
||||
vec4 emissive = m_Emissive; |
||||
#endif |
||||
gl_FragColor += emissive * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity; |
||||
#endif |
||||
|
||||
gl_FragColor.a = alpha; |
||||
|
||||
|
||||
} |
@ -0,0 +1,307 @@ |
||||
MaterialDef PBR Lighting { |
||||
|
||||
MaterialParameters { |
||||
|
||||
// Alpha threshold for fragment discarding |
||||
Float AlphaDiscardThreshold (AlphaTestFallOff) |
||||
|
||||
//metalness of the material |
||||
Float Metallic : 0.0 |
||||
//Roughness of the material |
||||
Float Roughness : 1.0 |
||||
// Base material color |
||||
Color BaseColor |
||||
// The emissive color of the object |
||||
Color Emissive |
||||
// the emissive power |
||||
Float EmissivePower : 3.0 |
||||
// the emissive intensity |
||||
Float EmissiveIntensity : 1.0 |
||||
|
||||
// BaseColor map |
||||
Texture2D BaseColorMap |
||||
|
||||
// Specular/gloss map |
||||
Texture2D MetallicMap -LINEAR |
||||
|
||||
// Roughness Map |
||||
Texture2D RoughnessMap -LINEAR |
||||
|
||||
// Texture of the emissive parts of the material |
||||
Texture2D EmissiveMap |
||||
|
||||
// Normal map |
||||
Texture2D NormalMap -LINEAR |
||||
|
||||
// For Spec gloss pipeline |
||||
Texture2D SpecularMap |
||||
Texture2D GlossMap |
||||
|
||||
Vector4 ProbeData |
||||
|
||||
// Prefiltered Env Map for indirect specular lighting |
||||
TextureCubeMap PrefEnvMap -LINEAR |
||||
|
||||
// Irradiance map for indirect diffuse lighting |
||||
TextureCubeMap IrradianceMap -LINEAR |
||||
|
||||
//integrate BRDF map for indirect Lighting |
||||
Texture2D IntegrateBRDF -LINEAR |
||||
|
||||
// Parallax/height map |
||||
Texture2D ParallaxMap -LINEAR |
||||
|
||||
//Set to true is parallax map is stored in the alpha channel of the normal map |
||||
Boolean PackedNormalParallax |
||||
|
||||
//Sets the relief height for parallax mapping |
||||
Float ParallaxHeight : 0.05 |
||||
|
||||
//Set to true to activate Steep Parallax mapping |
||||
Boolean SteepParallax |
||||
|
||||
// Set to Use Lightmap |
||||
Texture2D LightMap |
||||
|
||||
// Set to use TexCoord2 for the lightmap sampling |
||||
Boolean SeparateTexCoord |
||||
|
||||
//shadows |
||||
Int FilterMode |
||||
Boolean HardwareShadows |
||||
|
||||
Texture2D ShadowMap0 |
||||
Texture2D ShadowMap1 |
||||
Texture2D ShadowMap2 |
||||
Texture2D ShadowMap3 |
||||
//pointLights |
||||
Texture2D ShadowMap4 |
||||
Texture2D ShadowMap5 |
||||
|
||||
Float ShadowIntensity |
||||
Vector4 Splits |
||||
Vector2 FadeInfo |
||||
|
||||
Matrix4 LightViewProjectionMatrix0 |
||||
Matrix4 LightViewProjectionMatrix1 |
||||
Matrix4 LightViewProjectionMatrix2 |
||||
Matrix4 LightViewProjectionMatrix3 |
||||
//pointLight |
||||
Matrix4 LightViewProjectionMatrix4 |
||||
Matrix4 LightViewProjectionMatrix5 |
||||
Vector3 LightPos |
||||
Vector3 LightDir |
||||
|
||||
Float PCFEdge |
||||
Float ShadowMapSize |
||||
|
||||
// For hardware skinning |
||||
Int NumberOfBones |
||||
Matrix4Array BoneMatrices |
||||
|
||||
//For instancing |
||||
Boolean UseInstancing |
||||
|
||||
//For Vertex Color |
||||
Boolean UseVertexColor |
||||
} |
||||
|
||||
Technique { |
||||
LightMode SinglePassAndImageBased |
||||
|
||||
VertexShader GLSL100: Common/MatDefs/Light/PBRLighting.vert |
||||
FragmentShader GLSL100: Common/MatDefs/Light/PBRLighting.frag |
||||
|
||||
WorldParameters { |
||||
WorldViewProjectionMatrix |
||||
CameraPosition |
||||
WorldMatrix |
||||
} |
||||
|
||||
Defines { |
||||
BASECOLORMAP : BaseColorMap |
||||
NORMALMAP : NormalMap |
||||
METALLICMAP : MetallicMap |
||||
ROUGHNESSMAP : RoughnessMap |
||||
EMISSIVEMAP : EmissiveMap |
||||
EMISSIVE : Emissive |
||||
SPECGLOSSPIPELINE : SpecularMap |
||||
PARALLAXMAP : ParallaxMap |
||||
NORMALMAP_PARALLAX : PackedNormalParallax |
||||
STEEP_PARALLAX : SteepParallax |
||||
LIGHTMAP : LightMap |
||||
SEPARATE_TEXCOORD : SeparateTexCoord |
||||
DISCARD_ALPHA : AlphaDiscardThreshold |
||||
NUM_BONES : NumberOfBones |
||||
INSTANCING : UseInstancing |
||||
//INDIRECT_LIGHTING : IntegrateBRDF |
||||
VERTEX_COLOR : UseVertexColor |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
Technique PreShadow { |
||||
|
||||
VertexShader GLSL100 : Common/MatDefs/Shadow/PreShadow.vert |
||||
FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag |
||||
|
||||
WorldParameters { |
||||
WorldViewProjectionMatrix |
||||
WorldViewMatrix |
||||
ViewProjectionMatrix |
||||
ViewMatrix |
||||
} |
||||
|
||||
Defines { |
||||
COLOR_MAP : ColorMap |
||||
DISCARD_ALPHA : AlphaDiscardThreshold |
||||
NUM_BONES : NumberOfBones |
||||
INSTANCING : UseInstancing |
||||
} |
||||
|
||||
ForcedRenderState { |
||||
FaceCull Off |
||||
DepthTest On |
||||
DepthWrite On |
||||
PolyOffset 5 3 |
||||
ColorWrite Off |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
Technique PostShadow15{ |
||||
VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert |
||||
FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag |
||||
|
||||
WorldParameters { |
||||
WorldViewProjectionMatrix |
||||
WorldMatrix |
||||
ViewProjectionMatrix |
||||
ViewMatrix |
||||
} |
||||
|
||||
Defines { |
||||
HARDWARE_SHADOWS : HardwareShadows |
||||
FILTER_MODE : FilterMode |
||||
PCFEDGE : PCFEdge |
||||
DISCARD_ALPHA : AlphaDiscardThreshold |
||||
COLOR_MAP : ColorMap |
||||
SHADOWMAP_SIZE : ShadowMapSize |
||||
FADE : FadeInfo |
||||
PSSM : Splits |
||||
POINTLIGHT : LightViewProjectionMatrix5 |
||||
NUM_BONES : NumberOfBones |
||||
INSTANCING : UseInstancing |
||||
} |
||||
|
||||
ForcedRenderState { |
||||
Blend Modulate |
||||
DepthWrite Off |
||||
PolyOffset -0.1 0 |
||||
} |
||||
} |
||||
|
||||
Technique PostShadow{ |
||||
VertexShader GLSL100: Common/MatDefs/Shadow/PostShadow.vert |
||||
FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag |
||||
|
||||
WorldParameters { |
||||
WorldViewProjectionMatrix |
||||
WorldMatrix |
||||
ViewProjectionMatrix |
||||
ViewMatrix |
||||
} |
||||
|
||||
Defines { |
||||
HARDWARE_SHADOWS : HardwareShadows |
||||
FILTER_MODE : FilterMode |
||||
PCFEDGE : PCFEdge |
||||
DISCARD_ALPHA : AlphaDiscardThreshold |
||||
COLOR_MAP : ColorMap |
||||
SHADOWMAP_SIZE : ShadowMapSize |
||||
FADE : FadeInfo |
||||
PSSM : Splits |
||||
POINTLIGHT : LightViewProjectionMatrix5 |
||||
NUM_BONES : NumberOfBones |
||||
INSTANCING : UseInstancing |
||||
} |
||||
|
||||
ForcedRenderState { |
||||
Blend Modulate |
||||
DepthWrite Off |
||||
PolyOffset -0.1 0 |
||||
} |
||||
} |
||||
|
||||
Technique PreNormalPass { |
||||
|
||||
VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert |
||||
FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag |
||||
|
||||
WorldParameters { |
||||
WorldViewProjectionMatrix |
||||
WorldViewMatrix |
||||
NormalMatrix |
||||
ViewProjectionMatrix |
||||
ViewMatrix |
||||
} |
||||
|
||||
Defines { |
||||
DIFFUSEMAP_ALPHA : DiffuseMap |
||||
NUM_BONES : NumberOfBones |
||||
INSTANCING : UseInstancing |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
Technique PreNormalPassDerivative { |
||||
|
||||
VertexShader GLSL100 : Common/MatDefs/MSSAO/normal.vert |
||||
FragmentShader GLSL100 : Common/MatDefs/MSSAO/normal.frag |
||||
|
||||
WorldParameters { |
||||
WorldViewProjectionMatrix |
||||
WorldViewMatrix |
||||
NormalMatrix |
||||
ViewProjectionMatrix |
||||
ViewMatrix |
||||
} |
||||
|
||||
Defines { |
||||
DIFFUSEMAP_ALPHA : DiffuseMap |
||||
NUM_BONES : NumberOfBones |
||||
INSTANCING : UseInstancing |
||||
} |
||||
|
||||
} |
||||
|
||||
Technique GBuf { |
||||
|
||||
VertexShader GLSL100: Common/MatDefs/Light/GBuf.vert |
||||
FragmentShader GLSL100: Common/MatDefs/Light/GBuf.frag |
||||
|
||||
WorldParameters { |
||||
WorldViewProjectionMatrix |
||||
NormalMatrix |
||||
WorldViewMatrix |
||||
WorldMatrix |
||||
} |
||||
|
||||
Defines { |
||||
VERTEX_COLOR : UseVertexColor |
||||
MATERIAL_COLORS : UseMaterialColors |
||||
V_TANGENT : VTangent |
||||
MINNAERT : Minnaert |
||||
WARDISO : WardIso |
||||
|
||||
DIFFUSEMAP : DiffuseMap |
||||
NORMALMAP : NormalMap |
||||
SPECULARMAP : SpecularMap |
||||
PARALLAXMAP : ParallaxMap |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,65 @@ |
||||
#import "Common/ShaderLib/Instancing.glsllib" |
||||
#import "Common/ShaderLib/Skinning.glsllib" |
||||
|
||||
uniform vec4 m_BaseColor; |
||||
|
||||
uniform vec4 g_AmbientLightColor; |
||||
varying vec2 texCoord; |
||||
|
||||
#ifdef SEPARATE_TEXCOORD |
||||
varying vec2 texCoord2; |
||||
attribute vec2 inTexCoord2; |
||||
#endif |
||||
|
||||
varying vec4 Color; |
||||
|
||||
attribute vec3 inPosition; |
||||
attribute vec2 inTexCoord; |
||||
attribute vec3 inNormal; |
||||
|
||||
#ifdef VERTEX_COLOR |
||||
attribute vec4 inColor; |
||||
#endif |
||||
|
||||
varying vec3 wNormal; |
||||
varying vec3 wPosition; |
||||
#if defined(NORMALMAP) || defined(PARALLAXMAP) |
||||
attribute vec4 inTangent; |
||||
varying vec4 wTangent; |
||||
#endif |
||||
|
||||
void main(){ |
||||
vec4 modelSpacePos = vec4(inPosition, 1.0); |
||||
vec3 modelSpaceNorm = inNormal; |
||||
|
||||
#if ( defined(NORMALMAP) || defined(PARALLAXMAP)) && !defined(VERTEX_LIGHTING) |
||||
vec3 modelSpaceTan = inTangent.xyz; |
||||
#endif |
||||
|
||||
#ifdef NUM_BONES |
||||
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) |
||||
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); |
||||
#else |
||||
Skinning_Compute(modelSpacePos, modelSpaceNorm); |
||||
#endif |
||||
#endif |
||||
|
||||
gl_Position = TransformWorldViewProjection(modelSpacePos); |
||||
texCoord = inTexCoord; |
||||
#ifdef SEPARATE_TEXCOORD |
||||
texCoord2 = inTexCoord2; |
||||
#endif |
||||
|
||||
wPosition = TransformWorld(modelSpacePos).xyz; |
||||
wNormal = TransformWorldNormal(modelSpaceNorm); |
||||
|
||||
#if defined(NORMALMAP) || defined(PARALLAXMAP) |
||||
wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w); |
||||
#endif |
||||
|
||||
Color = m_BaseColor; |
||||
|
||||
#ifdef VERTEX_COLOR |
||||
Color *= inColor; |
||||
#endif |
||||
} |
@ -0,0 +1,45 @@ |
||||
MaterialDef Simple { |
||||
MaterialParameters { |
||||
TextureCubeMap CubeMap |
||||
} |
||||
Technique { |
||||
WorldParameters { |
||||
WorldViewProjectionMatrix |
||||
WorldMatrix |
||||
CameraPosition |
||||
} |
||||
VertexShaderNodes { |
||||
ShaderNode Reflect { |
||||
Definition : Reflect : Common/MatDefs/ShaderNodes/Environment/reflect.j3sn |
||||
InputMappings { |
||||
normal = Attr.inNormal |
||||
position = Global.position.xyz |
||||
worldMatrix = WorldParam.WorldMatrix |
||||
camPosition = WorldParam.CameraPosition |
||||
} |
||||
} |
||||
ShaderNode CommonVert { |
||||
Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn |
||||
InputMappings { |
||||
worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix |
||||
modelPosition = Global.position.xyz |
||||
} |
||||
OutputMappings { |
||||
Global.position = projPosition |
||||
} |
||||
} |
||||
} |
||||
FragmentShaderNodes { |
||||
ShaderNode EnvMapping { |
||||
Definition : EnvMapping : Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn |
||||
InputMappings { |
||||
refVec = Reflect.refVec |
||||
cubeMap = MatParam.CubeMap |
||||
} |
||||
OutputMappings { |
||||
Global.color = color |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
ShaderNodeDefinitions{ |
||||
ShaderNodeDefinition EnvMapping { |
||||
Type: Fragment |
||||
|
||||
Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/envMapping100.frag |
||||
Shader GLSL130: Common/MatDefs/ShaderNodes/Environment/envMapping130.frag |
||||
|
||||
Documentation{ |
||||
fetches a texel in a cube map |
||||
@input vec3 refVec the reflection vector |
||||
@input samplerCube cubeMap the cube map |
||||
@output vec4 color the output color |
||||
} |
||||
Input { |
||||
vec3 refVec |
||||
samplerCube cubeMap |
||||
} |
||||
Output { |
||||
vec4 color |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
|
||||
void main(){ |
||||
//@input vec3 refVec the reflection vector |
||||
//@input samplerCube cubeMap the cube map |
||||
//@output vec4 color the output color |
||||
|
||||
color = textureCubeLod(cubeMap, refVec, 0.0); |
||||
} |
@ -0,0 +1,8 @@ |
||||
|
||||
void main(){ |
||||
//@input vec3 refVec the reflection vector |
||||
//@input samplerCube cubeMap the cube map |
||||
//@output vec4 color the output color |
||||
|
||||
color = textureLod(cubeMap, refVec, 0.0); |
||||
} |
@ -0,0 +1,25 @@ |
||||
ShaderNodeDefinitions{ |
||||
ShaderNodeDefinition Reflect { |
||||
Type: Vertex |
||||
|
||||
Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/reflect100.vert |
||||
|
||||
Documentation{ |
||||
Computes the relfection vector necessary to do some environment mapping |
||||
@input vec3 position position in model space |
||||
@input vec3 normal the normal of the vertex |
||||
@input vec3 camPosition camera position in world space |
||||
@input mat4 worldMatrix the world matrix |
||||
@output vec3 refVec the reflection vector |
||||
} |
||||
Input { |
||||
vec3 position |
||||
vec3 normal |
||||
vec3 camPosition |
||||
mat4 worldMatrix |
||||
} |
||||
Output { |
||||
vec3 refVec |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,14 @@ |
||||
|
||||
void main(){ |
||||
//@input vec3 position position in model space |
||||
//@input vec3 normal the normal of the vertex |
||||
//@input vec3 camPosition camera position in world space |
||||
//@input mat4 worldMatrix the world view matrix |
||||
//@output vec3 refVec the reflection vector |
||||
|
||||
vec3 worldPos = (worldMatrix * vec4(position, 1.0)).xyz; |
||||
vec3 N = normalize((worldMatrix * vec4(normal, 0.0)).xyz); |
||||
vec3 I = normalize( camPosition - worldPos ).xyz; |
||||
refVec.xyz = reflect(-I, N); |
||||
|
||||
} |
@ -0,0 +1,154 @@ |
||||
|
||||
#ifndef PI |
||||
#define PI 3.14159265358979323846264 |
||||
#endif |
||||
|
||||
//Specular fresnel computation |
||||
vec3 F_Shlick(float vh, vec3 F0){ |
||||
float fresnelFact = pow(2.0, (-5.55473*vh - 6.98316) * vh); |
||||
return mix(F0, vec3(1.0, 1.0, 1.0), fresnelFact); |
||||
} |
||||
|
||||
void PBR_ComputeDirectLightSpecWF(vec3 normal, vec3 lightDir, vec3 viewDir, |
||||
vec3 lightColor, vec3 specColor, float roughness, float ndotv, |
||||
out vec3 outDiffuse, out vec3 outSpecular){ |
||||
// Compute halfway vector. |
||||
vec3 halfVec = normalize(lightDir + viewDir); |
||||
|
||||
// Compute ndotl, ndoth, vdoth terms which are needed later. |
||||
float ndotl = max( dot(normal, lightDir), 0.0); |
||||
float ndoth = max( dot(normal, halfVec), 0.0); |
||||
float hdotv = max( dot(viewDir, halfVec), 0.0); |
||||
|
||||
// Compute diffuse using energy-conserving Lambert. |
||||
// Alternatively, use Oren-Nayar for really rough |
||||
// materials or if you have lots of processing power ... |
||||
outDiffuse = vec3(ndotl) * lightColor; |
||||
|
||||
//cook-torrence, microfacet BRDF : http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf |
||||
|
||||
float alpha = roughness * roughness; |
||||
|
||||
//D, GGX normaal Distribution function |
||||
float alpha2 = alpha * alpha; |
||||
float sum = ((ndoth * ndoth) * (alpha2 - 1.0) + 1.0); |
||||
float denom = PI * sum * sum; |
||||
float D = alpha2 / denom; |
||||
|
||||
// Compute Fresnel function via Schlick's approximation. |
||||
vec3 fresnel = F_Shlick(hdotv, specColor); |
||||
|
||||
//G Shchlick GGX Gometry shadowing term, k = alpha/2 |
||||
float k = alpha * 0.5; |
||||
|
||||
// UE4 way to optimise shlick GGX Gometry shadowing term |
||||
//http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html |
||||
float G_V = ndotv + sqrt( (ndotv - ndotv * k) * ndotv + k ); |
||||
float G_L = ndotl + sqrt( (ndotl - ndotl * k) * ndotl + k ); |
||||
// the max here is to avoid division by 0 that may cause some small glitches. |
||||
float G = 1.0/max( G_V * G_L ,0.01); |
||||
|
||||
float specular = D * G * ndotl; |
||||
|
||||
outSpecular = fresnel * vec3(specular) * lightColor; |
||||
} |
||||
|
||||
void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir, |
||||
vec3 lightColor, float fZero, float roughness, float ndotv, |
||||
out vec3 outDiffuse, out vec3 outSpecular){ |
||||
// Compute halfway vector. |
||||
vec3 halfVec = normalize(lightDir + viewDir); |
||||
|
||||
// Compute ndotl, ndoth, vdoth terms which are needed later. |
||||
float ndotl = max( dot(normal, lightDir), 0.0); |
||||
float ndoth = max( dot(normal, halfVec), 0.0); |
||||
float hdotv = max( dot(viewDir, halfVec), 0.0); |
||||
|
||||
// Compute diffuse using energy-conserving Lambert. |
||||
// Alternatively, use Oren-Nayar for really rough |
||||
// materials or if you have lots of processing power ... |
||||
outDiffuse = vec3(ndotl) * lightColor; |
||||
|
||||
//cook-torrence, microfacet BRDF : http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf |
||||
|
||||
float alpha = roughness * roughness; |
||||
|
||||
//D, GGX normaal Distribution function |
||||
float alpha2 = alpha * alpha; |
||||
float sum = ((ndoth * ndoth) * (alpha2 - 1.0) + 1.0); |
||||
float denom = PI * sum * sum; |
||||
float D = alpha2 / denom; |
||||
|
||||
// Compute Fresnel function via Schlick's approximation. |
||||
float fresnel = fZero + ( 1.0 - fZero ) * pow( 2.0, (-5.55473 * hdotv - 6.98316) * hdotv); |
||||
|
||||
//G Shchlick GGX Gometry shadowing term, k = alpha/2 |
||||
float k = alpha * 0.5; |
||||
|
||||
/* |
||||
//classic Schlick ggx |
||||
float G_V = ndotv / (ndotv * (1.0 - k) + k); |
||||
float G_L = ndotl / (ndotl * (1.0 - k) + k); |
||||
float G = ( G_V * G_L ); |
||||
|
||||
float specular =(D* fresnel * G) /(4 * ndotv); |
||||
*/ |
||||
|
||||
// UE4 way to optimise shlick GGX Gometry shadowing term |
||||
//http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html |
||||
float G_V = ndotv + sqrt( (ndotv - ndotv * k) * ndotv + k ); |
||||
float G_L = ndotl + sqrt( (ndotl - ndotl * k) * ndotl + k ); |
||||
// the max here is to avoid division by 0 that may cause some small glitches. |
||||
float G = 1.0/max( G_V * G_L ,0.01); |
||||
|
||||
float specular = D * fresnel * G * ndotl; |
||||
|
||||
outSpecular = vec3(specular) * lightColor; |
||||
} |
||||
|
||||
//https://knarkowicz.wordpress.com/2014/12/27/analytical-dfg-term-for-ibl/ |
||||
vec3 EnvDFGPolynomial( vec3 specularColor, float roughness, float ndotv ){ |
||||
float x = 1.0 - roughness; |
||||
float y = ndotv; |
||||
|
||||
float b1 = -0.1688; |
||||
float b2 = 1.895; |
||||
float b3 = 0.9903; |
||||
float b4 = -4.853; |
||||
float b5 = 8.404; |
||||
float b6 = -5.069; |
||||
float bias = clamp( min( b1 * x + b2 * x * x, b3 + b4 * y + b5 * y * y + b6 * y * y * y ), 0.0, 1.0 ); |
||||
|
||||
float d0 = 0.6045; |
||||
float d1 = 1.699; |
||||
float d2 = -0.5228; |
||||
float d3 = -3.603; |
||||
float d4 = 1.404; |
||||
float d5 = 0.1939; |
||||
float d6 = 2.661; |
||||
float delta = clamp(( d0 + d1 * x + d2 * y + d3 * x * x + d4 * x * y + d5 * y * y + d6 * x * x * x ), 0.0, 1.0); |
||||
float scale = delta - bias; |
||||
|
||||
bias *= clamp( 2.5 / (roughness) * specularColor.y, 0.0, 1.0 ); |
||||
return specularColor * scale + bias; |
||||
} |
||||
|
||||
vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){ |
||||
//TODO magic values should be replaced by defines. |
||||
float Lod = log2(Roughness) * 1.1 + 6.0 - 2.0; |
||||
vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz,Lod).rgb; |
||||
vec2 EnvBRDF = texture2D(integrateBRDF,vec2(Roughness, ndotv)).rg; |
||||
return PrefilteredColor * ( SpecularColor * EnvBRDF.x+ EnvBRDF.y ); |
||||
} |
||||
|
||||
vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){ |
||||
//TODO magic values should be replaced by defines. |
||||
float Lod = log2(Roughness) * 1.5 + 6.0 - 1.0; |
||||
vec3 PrefilteredColor = textureCubeLod(envMap, refVec.xyz, Lod).rgb; |
||||
return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv); |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,354 @@ |
||||
/* |
||||
* 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.texture.plugins.ktx; |
||||
|
||||
import com.jme3.asset.AssetInfo; |
||||
import com.jme3.asset.AssetLoader; |
||||
import com.jme3.asset.TextureKey; |
||||
import com.jme3.renderer.Caps; |
||||
import com.jme3.renderer.opengl.GLImageFormat; |
||||
import com.jme3.renderer.opengl.GLImageFormats; |
||||
import com.jme3.texture.Image; |
||||
import com.jme3.texture.image.ColorSpace; |
||||
import com.jme3.util.BufferUtils; |
||||
import com.jme3.util.LittleEndien; |
||||
import java.io.DataInput; |
||||
import java.io.DataInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.EnumSet; |
||||
import java.util.logging.Level; |
||||
import java.util.logging.Logger; |
||||
|
||||
/** |
||||
* |
||||
* A KTX file loader |
||||
* KTX file format is an image container defined by the Kronos group |
||||
* See specs here https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
|
||||
* |
||||
* This loader doesn't support compressed files yet. |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
public class KTXLoader implements AssetLoader { |
||||
|
||||
private final static Logger log = Logger.getLogger(KTXLoader.class.getName()); |
||||
|
||||
private final static byte[] fileIdentifier = { |
||||
(byte) 0xAB, (byte) 0x4B, (byte) 0x54, (byte) 0x58, (byte) 0x20, (byte) 0x31, (byte) 0x31, (byte) 0xBB, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A |
||||
}; |
||||
private boolean slicesInside = false; |
||||
|
||||
@Override |
||||
public Object load(AssetInfo info) throws IOException { |
||||
if (!(info.getKey() instanceof TextureKey)) { |
||||
throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); |
||||
} |
||||
|
||||
InputStream in = null; |
||||
try { |
||||
in = info.openStream(); |
||||
Image img = load(in); |
||||
return img; |
||||
} finally { |
||||
if (in != null) { |
||||
in.close(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private Image load(InputStream stream) { |
||||
|
||||
byte[] fileId = new byte[12]; |
||||
|
||||
DataInput in = new DataInputStream(stream); |
||||
try { |
||||
stream.read(fileId, 0, 12); |
||||
if (!checkFileIdentifier(fileId)) { |
||||
throw new IllegalArgumentException("Unrecognized ktx file identifier : " + new String(fileId) + " should be " + new String(fileIdentifier)); |
||||
} |
||||
|
||||
int endianness = in.readInt(); |
||||
//opposite endianness
|
||||
if (endianness == 0x01020304) { |
||||
in = new LittleEndien(stream); |
||||
} |
||||
int glType = in.readInt(); |
||||
int glTypeSize = in.readInt(); |
||||
int glFormat = in.readInt(); |
||||
int glInternalFormat = in.readInt(); |
||||
int glBaseInternalFormat = in.readInt(); |
||||
int pixelWidth = in.readInt(); |
||||
int pixelHeight = in.readInt(); |
||||
int pixelDepth = in.readInt(); |
||||
int numberOfArrayElements = in.readInt(); |
||||
int numberOfFaces = in.readInt(); |
||||
int numberOfMipmapLevels = in.readInt(); |
||||
int bytesOfKeyValueData = in.readInt(); |
||||
|
||||
log.log(Level.FINE, "glType = {0}", glType); |
||||
log.log(Level.FINE, "glTypeSize = {0}", glTypeSize); |
||||
log.log(Level.FINE, "glFormat = {0}", glFormat); |
||||
log.log(Level.FINE, "glInternalFormat = {0}", glInternalFormat); |
||||
log.log(Level.FINE, "glBaseInternalFormat = {0}", glBaseInternalFormat); |
||||
log.log(Level.FINE, "pixelWidth = {0}", pixelWidth); |
||||
log.log(Level.FINE, "pixelHeight = {0}", pixelHeight); |
||||
log.log(Level.FINE, "pixelDepth = {0}", pixelDepth); |
||||
log.log(Level.FINE, "numberOfArrayElements = {0}", numberOfArrayElements); |
||||
log.log(Level.FINE, "numberOfFaces = {0}", numberOfFaces); |
||||
log.log(Level.FINE, "numberOfMipmapLevels = {0}", numberOfMipmapLevels); |
||||
log.log(Level.FINE, "bytesOfKeyValueData = {0}", bytesOfKeyValueData); |
||||
|
||||
if((numberOfFaces >1 && pixelDepth >1) || (numberOfFaces >1 && numberOfArrayElements >1) || (pixelDepth >1 && numberOfArrayElements >1)){ |
||||
throw new UnsupportedOperationException("jME doesn't support cube maps of 3D textures or arrays of 3D texture or arrays of cube map of 3d textures"); |
||||
} |
||||
|
||||
|
||||
PixelReader pixelReader = parseMetaData(bytesOfKeyValueData, in); |
||||
if (pixelReader == null){ |
||||
pixelReader = new SrTuRoPixelReader(); |
||||
} |
||||
|
||||
//some of the values may be 0 we need them at least to be 1
|
||||
pixelDepth = Math.max(1, pixelDepth); |
||||
numberOfArrayElements = Math.max(1, numberOfArrayElements); |
||||
numberOfFaces = Math.max(1, numberOfFaces); |
||||
numberOfMipmapLevels = Math.max(1, numberOfMipmapLevels); |
||||
|
||||
int nbSlices = Math.max(numberOfFaces,numberOfArrayElements); |
||||
|
||||
Image.Format imgFormat = getImageFormat(glFormat, glInternalFormat, glType); |
||||
log.log(Level.FINE, "img format {0}", imgFormat.toString()); |
||||
|
||||
|
||||
int bytePerPixel = imgFormat.getBitsPerPixel() / 8; |
||||
int byteBuffersSize = computeBuffersSize(numberOfMipmapLevels, pixelWidth, pixelHeight, bytePerPixel, pixelDepth); |
||||
log.log(Level.FINE, "data size {0}", byteBuffersSize); |
||||
|
||||
int[] mipMapSizes = new int[numberOfMipmapLevels]; |
||||
|
||||
Image image = createImage(nbSlices, byteBuffersSize, imgFormat, pixelWidth, pixelHeight, pixelDepth); |
||||
|
||||
byte[] pixelData = new byte[bytePerPixel]; |
||||
|
||||
int offset = 0; |
||||
//iterate over data
|
||||
for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) { |
||||
//size of the image in byte.
|
||||
//this value is bogus in many example, when using mipmaps.
|
||||
//instead we compute the theorical size and display a warning when it does not match.
|
||||
int fileImageSize = in.readInt(); |
||||
|
||||
int width = Math.max(1, pixelWidth >> mipLevel); |
||||
int height = Math.max(1, pixelHeight >> mipLevel); |
||||
|
||||
int imageSize = width * height * bytePerPixel; |
||||
mipMapSizes[mipLevel] = imageSize; |
||||
log.log(Level.FINE, "current mip size {0}", imageSize); |
||||
if(fileImageSize != imageSize){ |
||||
log.log(Level.WARNING, "Mip map size is wrong in the file for mip level {0} size is {1} should be {2}", new Object[]{mipLevel, fileImageSize, imageSize}); |
||||
} |
||||
|
||||
for (int arrayElem = 0; arrayElem < numberOfArrayElements; arrayElem++) { |
||||
for (int face = 0; face < numberOfFaces; face++) { |
||||
int nbPixelRead = 0; |
||||
for (int depth = 0; depth < pixelDepth; depth++) { |
||||
ByteBuffer byteBuffer = image.getData(getSlice(face, arrayElem)); |
||||
|
||||
log.log(Level.FINE, "position {0}", byteBuffer.position()); |
||||
byteBuffer.position(offset); |
||||
nbPixelRead = pixelReader.readPixels(width, height, pixelData, byteBuffer, in); |
||||
} |
||||
//cube padding
|
||||
if (numberOfFaces == 6 && numberOfArrayElements == 0) { |
||||
in.skipBytes(3 - ((nbPixelRead + 3) % 4)); |
||||
} |
||||
} |
||||
} |
||||
//mip padding
|
||||
log.log(Level.FINE, "skipping {0}", (3 - ((imageSize + 3) % 4))); |
||||
in.skipBytes(3 - ((imageSize + 3) % 4)); |
||||
offset+=imageSize; |
||||
} |
||||
//there are loaded mip maps we set the sizes
|
||||
if(numberOfMipmapLevels >1){ |
||||
image.setMipMapSizes(mipMapSizes); |
||||
} |
||||
//if 3D texture and slices' orientation is inside, we reverse the data array.
|
||||
if(pixelDepth > 1 && slicesInside){ |
||||
Collections.reverse(image.getData()); |
||||
} |
||||
return image; |
||||
|
||||
} catch (IOException ex) { |
||||
Logger.getLogger(KTXLoader.class.getName()).log(Level.SEVERE, null, ex); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* returns the slice from the face and the array index |
||||
* @param face the face |
||||
* @param arrayElem the array index |
||||
* @return |
||||
*/ |
||||
private static int getSlice(int face, int arrayElem) { |
||||
return Math.max(face, arrayElem); |
||||
} |
||||
|
||||
/** |
||||
* Computes a buffer size from given parameters |
||||
* @param numberOfMipmapLevels |
||||
* @param pixelWidth |
||||
* @param pixelHeight |
||||
* @param bytePerPixel |
||||
* @param pixelDepth |
||||
* @return |
||||
*/ |
||||
private int computeBuffersSize(int numberOfMipmapLevels, int pixelWidth, int pixelHeight, int bytePerPixel, int pixelDepth) { |
||||
int byteBuffersSize = 0; |
||||
for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) { |
||||
int width = Math.max(1, pixelWidth >> mipLevel); |
||||
int height = Math.max(1, pixelHeight >> mipLevel); |
||||
byteBuffersSize += width * height * bytePerPixel; |
||||
log.log(Level.FINE, "mip level size : {0} : {1}", new Object[]{mipLevel, width * height * bytePerPixel}); |
||||
} |
||||
return byteBuffersSize * pixelDepth; |
||||
} |
||||
|
||||
/** |
||||
* Create an image with given parameters |
||||
* @param nbSlices |
||||
* @param byteBuffersSize |
||||
* @param imgFormat |
||||
* @param pixelWidth |
||||
* @param pixelHeight |
||||
* @param depth |
||||
* @return |
||||
*/ |
||||
private Image createImage(int nbSlices, int byteBuffersSize, Image.Format imgFormat, int pixelWidth, int pixelHeight, int depth) { |
||||
ArrayList<ByteBuffer> imageData = new ArrayList<ByteBuffer>(nbSlices); |
||||
for (int i = 0; i < nbSlices; i++) { |
||||
imageData.add(BufferUtils.createByteBuffer(byteBuffersSize)); |
||||
} |
||||
Image image = new Image(imgFormat, pixelWidth, pixelHeight, depth, imageData, ColorSpace.sRGB); |
||||
return image; |
||||
} |
||||
|
||||
/** |
||||
* Parse the file metaData to select the PixelReader that suits the file |
||||
* coordinates orientation |
||||
* @param bytesOfKeyValueData |
||||
* @param in |
||||
* @return |
||||
* @throws IOException |
||||
*/ |
||||
private PixelReader parseMetaData(int bytesOfKeyValueData, DataInput in) throws IOException { |
||||
PixelReader pixelReader = null; |
||||
for (int i = 0; i < bytesOfKeyValueData;) { |
||||
//reading key values
|
||||
int keyAndValueByteSize = in.readInt(); |
||||
byte[] keyValue = new byte[keyAndValueByteSize]; |
||||
in.readFully(keyValue); |
||||
|
||||
|
||||
//parsing key values
|
||||
String[] kv = new String(keyValue).split("\0"); |
||||
for (int j = 0; j < kv.length; j += 2) { |
||||
System.err.println("key : " + kv[j]); |
||||
System.err.println("value : " + kv[j + 1]); |
||||
if(kv[j].equalsIgnoreCase("KTXorientation")){ |
||||
if(kv[j + 1].startsWith("S=r,T=d") ){ |
||||
pixelReader = new SrTdRiPixelReader(); |
||||
}else{ |
||||
pixelReader = new SrTuRoPixelReader(); |
||||
} |
||||
if(kv[j + 1].contains("R=i")){ |
||||
slicesInside = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
//padding
|
||||
int padding = 3 - ((keyAndValueByteSize + 3) % 4); |
||||
if (padding > 0) { |
||||
in.skipBytes(padding); |
||||
} |
||||
i += 4 + keyAndValueByteSize + padding; |
||||
} |
||||
return pixelReader; |
||||
} |
||||
|
||||
/** |
||||
* Chacks the file id |
||||
* @param b |
||||
* @return |
||||
*/ |
||||
private boolean checkFileIdentifier(byte[] b) { |
||||
boolean check = true; |
||||
for (int i = 0; i < 12; i++) { |
||||
if (b[i] != fileIdentifier[i]) { |
||||
check = false; |
||||
} |
||||
} |
||||
return check; |
||||
} |
||||
|
||||
/** |
||||
* returns the JME image format from gl formats and types. |
||||
* @param glFormat |
||||
* @param glInternalFormat |
||||
* @param glType |
||||
* @return |
||||
*/ |
||||
private Image.Format getImageFormat(int glFormat, int glInternalFormat, int glType) { |
||||
EnumSet<Caps> caps = EnumSet.allOf(Caps.class); |
||||
GLImageFormat[][] formats = GLImageFormats.getFormatsForCaps(caps); |
||||
for (GLImageFormat[] format : formats) { |
||||
for (int j = 0; j < format.length; j++) { |
||||
GLImageFormat glImgFormat = format[j]; |
||||
if (glImgFormat != null) { |
||||
if (glImgFormat.format == glFormat && glImgFormat.dataType == glType) { |
||||
if (glFormat == glInternalFormat || glImgFormat.internalFormat == glInternalFormat) { |
||||
return Image.Format.values()[j]; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,279 @@ |
||||
/* |
||||
* 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.texture.plugins.ktx; |
||||
|
||||
import com.jme3.renderer.Caps; |
||||
import com.jme3.renderer.opengl.GLImageFormat; |
||||
import com.jme3.renderer.opengl.GLImageFormats; |
||||
import com.jme3.texture.Image; |
||||
import com.jme3.texture.Texture; |
||||
import com.jme3.texture.Texture2D; |
||||
import com.jme3.texture.Texture3D; |
||||
import com.jme3.texture.TextureArray; |
||||
import com.jme3.texture.TextureCubeMap; |
||||
import java.io.DataOutput; |
||||
import java.io.DataOutputStream; |
||||
import java.io.File; |
||||
import java.io.FileNotFoundException; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.EnumSet; |
||||
import java.util.logging.Level; |
||||
import java.util.logging.Logger; |
||||
|
||||
/** |
||||
* |
||||
* This class allows one to write a KTX file. |
||||
* It doesn't support compressed data yet. |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
public class KTXWriter { |
||||
|
||||
private final static Logger log = Logger.getLogger(KTXWriter.class.getName()); |
||||
|
||||
private final String filePath; |
||||
|
||||
private final static byte[] fileIdentifier = { |
||||
(byte) 0xAB, (byte) 0x4B, (byte) 0x54, (byte) 0x58, (byte) 0x20, (byte) 0x31, (byte) 0x31, (byte) 0xBB, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A |
||||
}; |
||||
|
||||
/** |
||||
* Creates a KTXWriter that will write files in the given path |
||||
* @param path |
||||
*/ |
||||
public KTXWriter(String path) { |
||||
filePath = path; |
||||
} |
||||
|
||||
/** |
||||
* Writes a 2D image from the given image in a KTX file named from the fileName param |
||||
* Note that the fileName should contain the extension (.ktx sounds like a wise choice) |
||||
* @param image the image to write |
||||
* @param fileName the name of the file to write |
||||
*/ |
||||
public void write(Image image, String fileName) { |
||||
write(image, Texture2D.class, fileName); |
||||
} |
||||
|
||||
/** |
||||
* Writes an image with the given params |
||||
* |
||||
* textureType, allows one to write textureArrays, Texture3D, and TextureCubeMaps. |
||||
* Texture2D will write a 2D image. |
||||
* Note that the fileName should contain the extension (.ktx sounds like a wise choice) |
||||
* @param image the image to write |
||||
* @param textureType the texture type |
||||
* @param fileName the name of the file to write |
||||
*/ |
||||
public void write(Image image, Class<? extends Texture> textureType, String fileName) { |
||||
|
||||
FileOutputStream outs = null; |
||||
try { |
||||
File file = new File(filePath + "/" + fileName); |
||||
outs = new FileOutputStream(file); |
||||
|
||||
DataOutput out = new DataOutputStream(outs); |
||||
|
||||
//fileID
|
||||
out.write(fileIdentifier); |
||||
//endianness
|
||||
out.writeInt(0x04030201); |
||||
GLImageFormat format = getGlFormat(image.getFormat()); |
||||
//glType
|
||||
out.writeInt(format.dataType); |
||||
//glTypeSize
|
||||
out.writeInt(1); |
||||
//glFormat
|
||||
out.writeInt(format.format); |
||||
//glInernalFormat
|
||||
out.writeInt(format.internalFormat); |
||||
//glBaseInternalFormat
|
||||
out.writeInt(format.format); |
||||
//pixelWidth
|
||||
out.writeInt(image.getWidth()); |
||||
//pixelHeight
|
||||
out.writeInt(image.getHeight()); |
||||
|
||||
int pixelDepth = 1; |
||||
int numberOfArrayElements = 1; |
||||
int numberOfFaces = 1; |
||||
if (image.getDepth() > 1) { |
||||
//pixelDepth
|
||||
if (textureType == Texture3D.class) { |
||||
pixelDepth = image.getDepth(); |
||||
} |
||||
} |
||||
if(image.getData().size()>1){ |
||||
//numberOfArrayElements
|
||||
if (textureType == TextureArray.class) { |
||||
numberOfArrayElements = image.getData().size(); |
||||
} |
||||
//numberOfFaces
|
||||
if (textureType == TextureCubeMap.class) { |
||||
numberOfFaces = image.getData().size(); |
||||
} |
||||
} |
||||
out.writeInt(pixelDepth); |
||||
out.writeInt(numberOfArrayElements); |
||||
out.writeInt(numberOfFaces); |
||||
|
||||
int numberOfMipmapLevels = 1; |
||||
//numberOfMipmapLevels
|
||||
if (image.hasMipmaps()) { |
||||
numberOfMipmapLevels = image.getMipMapSizes().length; |
||||
} |
||||
out.writeInt(numberOfMipmapLevels); |
||||
|
||||
//bytesOfKeyValueData
|
||||
String keyValues = "KTXorientation\0S=r,T=u\0"; |
||||
int bytesOfKeyValueData = keyValues.length() + 4; |
||||
int padding = 3 - ((bytesOfKeyValueData + 3) % 4); |
||||
bytesOfKeyValueData += padding; |
||||
out.writeInt(bytesOfKeyValueData); |
||||
|
||||
//keyAndValueByteSize
|
||||
out.writeInt(bytesOfKeyValueData - 4 - padding); |
||||
//values
|
||||
out.writeBytes(keyValues); |
||||
pad(padding, out); |
||||
|
||||
int offset = 0; |
||||
//iterate over data
|
||||
for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) { |
||||
|
||||
int width = Math.max(1, image.getWidth() >> mipLevel); |
||||
int height = Math.max(1, image.getHeight() >> mipLevel); |
||||
|
||||
int imageSize; |
||||
|
||||
if (image.hasMipmaps()) { |
||||
imageSize = image.getMipMapSizes()[mipLevel]; |
||||
} else { |
||||
imageSize = width * height * image.getFormat().getBitsPerPixel() / 8; |
||||
} |
||||
out.writeInt(imageSize); |
||||
|
||||
for (int arrayElem = 0; arrayElem < numberOfArrayElements; arrayElem++) { |
||||
for (int face = 0; face < numberOfFaces; face++) { |
||||
int nbPixelWritten = 0; |
||||
for (int depth = 0; depth < pixelDepth; depth++) { |
||||
ByteBuffer byteBuffer = image.getData(getSlice(face, arrayElem)); |
||||
// BufferUtils.ensureLargeEnough(byteBuffer, imageSize);
|
||||
log.log(Level.FINE, "position {0}", byteBuffer.position()); |
||||
byteBuffer.position(offset); |
||||
byte[] b = getByteBufferArray(byteBuffer, imageSize); |
||||
out.write(b); |
||||
|
||||
nbPixelWritten = b.length; |
||||
} |
||||
//cube padding
|
||||
if (numberOfFaces == 6 && numberOfArrayElements == 0) { |
||||
padding = 3 - ((nbPixelWritten + 3) % 4); |
||||
pad(padding, out); |
||||
} |
||||
} |
||||
} |
||||
//mip padding
|
||||
log.log(Level.FINE, "skipping {0}", (3 - ((imageSize + 3) % 4))); |
||||
padding = 3 - ((imageSize + 3) % 4); |
||||
pad(padding, out); |
||||
offset += imageSize; |
||||
} |
||||
|
||||
} catch (FileNotFoundException ex) { |
||||
Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex); |
||||
} catch (IOException ex) { |
||||
Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex); |
||||
} finally { |
||||
try { |
||||
if(outs != null){ |
||||
outs.close(); |
||||
} |
||||
} catch (IOException ex) { |
||||
Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* writes padding data to the output padding times. |
||||
* @param padding |
||||
* @param out |
||||
* @throws IOException |
||||
*/ |
||||
private void pad(int padding, DataOutput out) throws IOException { |
||||
//padding
|
||||
for (int i = 0; i < padding; i++) { |
||||
out.write('\0'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get a byte array from a byte buffer. |
||||
* @param byteBuffer the byte buffer |
||||
* @param size the size of the resulting array |
||||
* @return |
||||
*/ |
||||
private byte[] getByteBufferArray(ByteBuffer byteBuffer, int size) { |
||||
byte[] b; |
||||
if (byteBuffer.hasArray()) { |
||||
b = byteBuffer.array(); |
||||
} else { |
||||
b = new byte[size]; |
||||
byteBuffer.get(b, 0, size); |
||||
} |
||||
return b; |
||||
} |
||||
|
||||
/** |
||||
* get the glformat from JME image Format |
||||
* @param format |
||||
* @return |
||||
*/ |
||||
private GLImageFormat getGlFormat(Image.Format format) { |
||||
EnumSet<Caps> caps = EnumSet.allOf(Caps.class); |
||||
GLImageFormat[][] formats = GLImageFormats.getFormatsForCaps(caps); |
||||
return formats[0][format.ordinal()]; |
||||
} |
||||
|
||||
/** |
||||
* get a slice from the face and the array index |
||||
* @param face |
||||
* @param arrayElem |
||||
* @return |
||||
*/ |
||||
private static int getSlice(int face,int arrayElem) { |
||||
return Math.max(face, arrayElem); |
||||
} |
||||
} |
@ -0,0 +1,46 @@ |
||||
/* |
||||
* 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.texture.plugins.ktx; |
||||
|
||||
import java.io.DataInput; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
/** |
||||
* |
||||
* Interface used to read a set of pixels in a KTX file |
||||
* @author Nehon |
||||
*/ |
||||
public interface PixelReader { |
||||
|
||||
public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException; |
||||
} |
@ -0,0 +1,60 @@ |
||||
/* |
||||
* 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.texture.plugins.ktx; |
||||
|
||||
import java.io.DataInput; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
/** |
||||
* reads the pixels of an image whose origin is the top left corner |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
public class SrTdRiPixelReader implements PixelReader { |
||||
|
||||
@Override |
||||
public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException { |
||||
int pixelRead = 0; |
||||
for (int row = pixelHeight - 1; row >= 0; row--) { |
||||
for (int pixel = 0; pixel < pixelWidth; pixel++) { |
||||
in.readFully(pixelData); |
||||
for (int i = 0; i < pixelData.length; i++) { |
||||
buffer.put((row * pixelWidth + pixel) * pixelData.length + i, pixelData[i]); |
||||
} |
||||
pixelRead += pixelData.length; |
||||
} |
||||
} |
||||
return pixelRead; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,58 @@ |
||||
/* |
||||
* 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.texture.plugins.ktx; |
||||
|
||||
import java.io.DataInput; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
/** |
||||
* reads the pixels of an image whose origin is the bottom left corner |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
public class SrTuRoPixelReader implements PixelReader { |
||||
|
||||
@Override |
||||
public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException { |
||||
int pixelRead = 0; |
||||
for (int row = 0; row < pixelHeight; row++) { |
||||
for (int pixel = 0; pixel < pixelWidth; pixel++) { |
||||
in.readFully(pixelData); |
||||
buffer.put(pixelData); |
||||
pixelRead += pixelData.length; |
||||
} |
||||
} |
||||
return pixelRead; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,90 @@ |
||||
package jme3test.light; |
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.input.ChaseCamera; |
||||
import com.jme3.input.KeyInput; |
||||
import com.jme3.input.controls.AnalogListener; |
||||
import com.jme3.light.DirectionalLight; |
||||
import com.jme3.input.controls.KeyTrigger; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.math.FastMath; |
||||
import com.jme3.math.Quaternion; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.post.FilterPostProcessor; |
||||
import com.jme3.renderer.queue.RenderQueue; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.shape.Box; |
||||
import com.jme3.shadow.DirectionalLightShadowFilter; |
||||
import com.jme3.shadow.DirectionalLightShadowRenderer; |
||||
|
||||
public class TestColorApp extends SimpleApplication { |
||||
public static void main(String[] args) { |
||||
TestColorApp app = new TestColorApp(); |
||||
app.start(); |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
// Lights
|
||||
DirectionalLight sun = new DirectionalLight(); |
||||
Vector3f sunPosition = new Vector3f(1, -1, 1); |
||||
sun.setDirection(sunPosition); |
||||
sun.setColor(new ColorRGBA(1f,1f,1f,1f)); |
||||
rootNode.addLight(sun); |
||||
|
||||
//DirectionalLightShadowFilter sun_renderer = new DirectionalLightShadowFilter(assetManager, 2048, 4);
|
||||
DirectionalLightShadowRenderer sun_renderer = new DirectionalLightShadowRenderer(assetManager, 2048, 1); |
||||
sun_renderer.setLight(sun); |
||||
viewPort.addProcessor(sun_renderer); |
||||
|
||||
// FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
|
||||
// fpp.addFilter(sun_renderer);
|
||||
// viewPort.addProcessor(fpp);
|
||||
|
||||
rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); |
||||
|
||||
// Camera
|
||||
viewPort.setBackgroundColor(new ColorRGBA(.6f, .6f, .6f, 1f)); |
||||
ChaseCamera chaseCam = new ChaseCamera(cam, inputManager); |
||||
|
||||
|
||||
// Objects
|
||||
// Ground Object
|
||||
final Geometry groundBoxWhite = new Geometry("Box", new Box(7.5f, 7.5f, .25f)); |
||||
float[] f = {-FastMath.PI / 2, 3 * FastMath.PI / 2, 0f}; |
||||
groundBoxWhite.setLocalRotation(new Quaternion(f)); |
||||
groundBoxWhite.move(7.5f, -.75f, 7.5f); |
||||
final Material groundMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); |
||||
groundMaterial.setColor("Diffuse", new ColorRGBA(.9f, .9f, .9f, .9f)); |
||||
groundBoxWhite.setMaterial(groundMaterial); |
||||
groundBoxWhite.addControl(chaseCam); |
||||
rootNode.attachChild(groundBoxWhite); |
||||
|
||||
// Planter
|
||||
Geometry planterBox = new Geometry("Box", new Box(.5f, .5f, .5f)); |
||||
final Material planterMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); |
||||
planterMaterial.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg")); |
||||
planterBox.setMaterial(groundMaterial); |
||||
planterBox.setLocalTranslation(10, 0, 9); |
||||
rootNode.attachChild(planterBox); |
||||
|
||||
// Action!
|
||||
inputManager.addMapping("on", new KeyTrigger(KeyInput.KEY_Z)); |
||||
inputManager.addMapping("off", new KeyTrigger(KeyInput.KEY_X)); |
||||
|
||||
inputManager.addListener(new AnalogListener() { |
||||
@Override |
||||
public void onAnalog(String s, float v, float v1) { |
||||
if (s.equals("on")) { |
||||
groundBoxWhite.setMaterial(planterMaterial); |
||||
} |
||||
if (s.equals("off")) { |
||||
groundBoxWhite.setMaterial(groundMaterial); |
||||
} |
||||
} |
||||
}, "on", "off"); |
||||
|
||||
inputEnabled = true; |
||||
} |
||||
} |
@ -0,0 +1,131 @@ |
||||
/* |
||||
* 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 jme3test.light; |
||||
|
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.light.DirectionalLight; |
||||
import com.jme3.light.PointLight; |
||||
import com.jme3.light.SpotLight; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.math.FastMath; |
||||
import com.jme3.math.Quaternion; |
||||
import com.jme3.math.Vector2f; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.scene.shape.Box; |
||||
import com.jme3.shadow.EdgeFilteringMode; |
||||
import com.jme3.shadow.PointLightShadowRenderer; |
||||
import com.jme3.shadow.SpotLightShadowRenderer; |
||||
import com.jme3.texture.Texture; |
||||
import com.jme3.texture.Texture.WrapMode; |
||||
|
||||
|
||||
public class TestShadowBug extends SimpleApplication { |
||||
public static void main(String[] args) { |
||||
TestShadowBug app = new TestShadowBug(); |
||||
app.start(); |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
flyCam.setMoveSpeed(100f); |
||||
rootNode.attachChild(makeFloor()); |
||||
|
||||
Node characters = new Node("Characters"); |
||||
characters.setShadowMode(ShadowMode.Cast); |
||||
rootNode.attachChild(characters); |
||||
|
||||
Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); |
||||
golem.scale(0.5f); |
||||
golem.setLocalTranslation(200.0f, -6f, 200f); |
||||
golem.setShadowMode(ShadowMode.CastAndReceive); |
||||
characters.attachChild(golem); |
||||
|
||||
DirectionalLight sun = new DirectionalLight(); |
||||
sun.setDirection(new Vector3f(-1f, -1f, 1f)); |
||||
sun.setColor(ColorRGBA.White.mult(1.3f)); |
||||
rootNode.addLight(sun); |
||||
characters.addLight(sun); |
||||
|
||||
SpotLight spot = new SpotLight(); |
||||
spot.setSpotRange(13f); // distance
|
||||
spot.setSpotInnerAngle(15f * FastMath.DEG_TO_RAD); // inner light cone (central beam)
|
||||
spot.setSpotOuterAngle(20f * FastMath.DEG_TO_RAD); // outer light cone (edge of the light)
|
||||
spot.setColor(ColorRGBA.White.mult(1.3f)); // light color
|
||||
spot.setPosition(new Vector3f(192.0f, -1f, 192f)); |
||||
spot.setDirection(new Vector3f(1, -0.5f, 1)); |
||||
rootNode.addLight(spot); |
||||
|
||||
PointLight lamp_light = new PointLight(); |
||||
lamp_light.setColor(ColorRGBA.Yellow); |
||||
lamp_light.setRadius(20f); |
||||
lamp_light.setPosition(new Vector3f(210.0f, 0f, 210f)); |
||||
rootNode.addLight(lamp_light); |
||||
|
||||
SpotLightShadowRenderer slsr = new SpotLightShadowRenderer(assetManager, 512); |
||||
slsr.setLight(spot); |
||||
slsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest); |
||||
slsr.setShadowIntensity(0.6f); |
||||
slsr.setFlushQueues(false); |
||||
viewPort.addProcessor(slsr); |
||||
|
||||
PointLightShadowRenderer plsr = new PointLightShadowRenderer(assetManager, 512); |
||||
plsr.setLight(lamp_light); |
||||
plsr.setShadowIntensity(0.6f); |
||||
plsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest); |
||||
plsr.setFlushQueues(false); |
||||
viewPort.addProcessor(plsr); |
||||
|
||||
viewPort.getCamera().setLocation(new Vector3f(192.0f, 10f, 192f)); |
||||
float[] angles = new float[]{3.14f/2, 3.14f/2, 0}; |
||||
viewPort.getCamera().setRotation(new Quaternion(angles)); |
||||
} |
||||
|
||||
protected Geometry makeFloor() { |
||||
Box box = new Box(220, .2f, 220); |
||||
box.scaleTextureCoordinates(new Vector2f(10, 10)); |
||||
Geometry floor = new Geometry("the Floor", box); |
||||
floor.setLocalTranslation(200, -9, 200); |
||||
Material matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); |
||||
Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); |
||||
grass.setWrap(WrapMode.Repeat); |
||||
matGroundL.setTexture("DiffuseMap", grass); |
||||
floor.setMaterial(matGroundL); |
||||
floor.setShadowMode(ShadowMode.CastAndReceive); |
||||
return floor; |
||||
} |
||||
} |
@ -0,0 +1,96 @@ |
||||
/* |
||||
* 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 jme3test.light; |
||||
|
||||
import com.jme3.app.ChaseCameraAppState; |
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.light.AmbientLight; |
||||
import com.jme3.light.PointLight; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.math.FastMath; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.shape.Box; |
||||
import com.jme3.util.TangentBinormalGenerator; |
||||
|
||||
/** |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
public class TestTangentCube extends SimpleApplication { |
||||
|
||||
public static void main(String... args) { |
||||
TestTangentCube app = new TestTangentCube(); |
||||
app.start(); |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
Box aBox = new Box(1, 1, 1); |
||||
Geometry aGeometry = new Geometry("Box", aBox); |
||||
TangentBinormalGenerator.generate(aBox); |
||||
|
||||
Material aMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); |
||||
aMaterial.setTexture("DiffuseMap", |
||||
assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg")); |
||||
aMaterial.setTexture("NormalMap", |
||||
assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall_normal.jpg")); |
||||
aMaterial.setBoolean("UseMaterialColors", false); |
||||
aMaterial.setColor("Diffuse", ColorRGBA.White); |
||||
aMaterial.setColor("Specular", ColorRGBA.White); |
||||
aMaterial.setFloat("Shininess", 64f); |
||||
aGeometry.setMaterial(aMaterial); |
||||
|
||||
// Rotate 45 degrees to see multiple faces
|
||||
aGeometry.rotate(FastMath.QUARTER_PI, FastMath.QUARTER_PI, 0.0f); |
||||
rootNode.attachChild(aGeometry); |
||||
|
||||
/** |
||||
* Must add a light to make the lit object visible! |
||||
*/ |
||||
PointLight aLight = new PointLight(); |
||||
aLight.setPosition(new Vector3f(0, 3, 3)); |
||||
aLight.setColor(ColorRGBA.Red); |
||||
rootNode.addLight(aLight); |
||||
//
|
||||
// AmbientLight bLight = new AmbientLight();
|
||||
// bLight.setColor(ColorRGBA.Gray);
|
||||
// rootNode.addLight(bLight);
|
||||
|
||||
|
||||
ChaseCameraAppState chaser = new ChaseCameraAppState(); |
||||
chaser.setTarget(aGeometry); |
||||
getStateManager().attach(chaser); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,71 @@ |
||||
/* |
||||
* 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 jme3test.light.pbr; |
||||
|
||||
import com.jme3.environment.generation.JobProgressAdapter; |
||||
import com.jme3.light.LightProbe; |
||||
import java.util.logging.Level; |
||||
import java.util.logging.Logger; |
||||
|
||||
/** |
||||
* A basic logger for environment map rendering progress. |
||||
* @author nehon |
||||
*/ |
||||
public class ConsoleProgressReporter extends JobProgressAdapter<LightProbe>{ |
||||
|
||||
private static final Logger logger = Logger.getLogger(ConsoleProgressReporter.class.getName()); |
||||
|
||||
long time; |
||||
|
||||
@Override |
||||
public void start() { |
||||
time = System.currentTimeMillis(); |
||||
logger.log(Level.INFO,"Starting generation"); |
||||
} |
||||
|
||||
@Override |
||||
public void progress(double value) { |
||||
logger.log(Level.INFO, "Progress : {0}%", (value * 100)); |
||||
} |
||||
|
||||
@Override |
||||
public void step(String message) { |
||||
logger.info(message); |
||||
} |
||||
|
||||
@Override |
||||
public void done(LightProbe result) { |
||||
long end = System.currentTimeMillis(); |
||||
logger.log(Level.INFO, "Generation done in {0}", ((float)(end - time) / 1000f)); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,141 @@ |
||||
package jme3test.light.pbr; |
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.bounding.BoundingSphere; |
||||
import com.jme3.environment.EnvironmentCamera; |
||||
import com.jme3.environment.LightProbeFactory; |
||||
import com.jme3.environment.generation.JobProgressAdapter; |
||||
import com.jme3.environment.util.EnvMapUtils; |
||||
import com.jme3.input.KeyInput; |
||||
import com.jme3.input.controls.ActionListener; |
||||
import com.jme3.input.controls.KeyTrigger; |
||||
import com.jme3.light.LightProbe; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.math.Quaternion; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.ui.Picture; |
||||
import com.jme3.util.MaterialDebugAppState; |
||||
|
||||
/** |
||||
* test |
||||
* |
||||
* @author nehon |
||||
*/ |
||||
public class RefEnv extends SimpleApplication { |
||||
|
||||
private Node tex; |
||||
private Node ref; |
||||
private Picture refDE; |
||||
private Picture refM; |
||||
|
||||
public static void main(String[] args) { |
||||
RefEnv app = new RefEnv(); |
||||
app.start(); |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
|
||||
cam.setLocation(new Vector3f(-2.3324413f, 2.9567573f, 4.6054406f)); |
||||
cam.setRotation(new Quaternion(0.06310794f, 0.9321281f, -0.29613864f, 0.1986369f)); |
||||
Spatial sc = assetManager.loadModel("Scenes/PBR/spheres.j3o"); |
||||
rootNode.attachChild(sc); |
||||
rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Always); |
||||
|
||||
ref = new Node("reference pictures"); |
||||
refDE = new Picture("refDE"); |
||||
refDE.setHeight(cam.getHeight()); |
||||
refDE.setWidth(cam.getWidth()); |
||||
refDE.setImage(assetManager,"jme3test/light/pbr/spheresRefDE.png", false); |
||||
refM = new Picture("refM"); |
||||
refM.setImage(assetManager,"jme3test/light/pbr/spheresRefM.png", false); |
||||
refM.setHeight(cam.getHeight()); |
||||
refM.setWidth(cam.getWidth()); |
||||
|
||||
ref.attachChild(refDE); |
||||
|
||||
stateManager.attach(new EnvironmentCamera()); |
||||
|
||||
inputManager.addMapping("tex", new KeyTrigger(KeyInput.KEY_SPACE)); |
||||
inputManager.addMapping("switch", new KeyTrigger(KeyInput.KEY_RETURN)); |
||||
inputManager.addMapping("ref", new KeyTrigger(KeyInput.KEY_R)); |
||||
inputManager.addListener(new ActionListener() { |
||||
|
||||
@Override |
||||
public void onAction(String name, boolean isPressed, float tpf) { |
||||
if (name.equals("tex") && isPressed) { |
||||
if (tex == null) { |
||||
return; |
||||
} |
||||
if (tex.getParent() == null) { |
||||
guiNode.attachChild(tex); |
||||
} else { |
||||
tex.removeFromParent(); |
||||
} |
||||
} |
||||
|
||||
if (name.equals("switch") && isPressed) { |
||||
switchMat(rootNode.getChild("Scene")); |
||||
} |
||||
if (name.equals("ref") && isPressed) { |
||||
if (ref.getParent() == null) { |
||||
guiNode.attachChild(ref); |
||||
} else { |
||||
ref.removeFromParent(); |
||||
} |
||||
} |
||||
} |
||||
}, "tex", "switch", "ref"); |
||||
|
||||
} |
||||
|
||||
private void switchMat(Spatial s) { |
||||
if (s instanceof Node) { |
||||
Node n = (Node) s; |
||||
for (Spatial children : n.getChildren()) { |
||||
switchMat(children); |
||||
} |
||||
} else if (s instanceof Geometry) { |
||||
Geometry g = (Geometry) s; |
||||
Material mat = g.getMaterial(); |
||||
if (((Float) mat.getParam("Metallic").getValue()) == 1f) { |
||||
mat.setFloat("Metallic", 0); |
||||
mat.setColor("BaseColor", ColorRGBA.Black); |
||||
ref.attachChild(refDE); |
||||
refM.removeFromParent(); |
||||
} else { |
||||
mat.setFloat("Metallic", 1); |
||||
mat.setColor("BaseColor", ColorRGBA.White); |
||||
ref.attachChild(refM); |
||||
refDE.removeFromParent(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private int frame = 0; |
||||
|
||||
@Override |
||||
public void simpleUpdate(float tpf) { |
||||
frame++; |
||||
|
||||
if (frame == 2) { |
||||
final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() { |
||||
|
||||
@Override |
||||
public void done(LightProbe result) { |
||||
System.err.println("Done rendering env maps"); |
||||
tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager); |
||||
// guiNode.attachChild(tex);
|
||||
rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Dynamic); |
||||
} |
||||
}); |
||||
((BoundingSphere) probe.getBounds()).setRadius(100); |
||||
rootNode.addLight(probe); |
||||
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,199 @@ |
||||
/* |
||||
* 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 jme3test.light.pbr; |
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.bounding.BoundingSphere; |
||||
import com.jme3.light.LightProbe; |
||||
import com.jme3.environment.LightProbeFactory; |
||||
import com.jme3.environment.EnvironmentCamera; |
||||
import com.jme3.environment.generation.JobProgressAdapter; |
||||
import com.jme3.environment.util.LightsDebugState; |
||||
import com.jme3.input.ChaseCamera; |
||||
import com.jme3.input.KeyInput; |
||||
import com.jme3.input.controls.ActionListener; |
||||
import com.jme3.input.controls.KeyTrigger; |
||||
import com.jme3.light.DirectionalLight; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.math.FastMath; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.post.FilterPostProcessor; |
||||
import com.jme3.post.filters.FXAAFilter; |
||||
import com.jme3.post.filters.ToneMapFilter; |
||||
import com.jme3.post.ssao.SSAOFilter; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.texture.plugins.ktx.KTXLoader; |
||||
import com.jme3.util.MaterialDebugAppState; |
||||
import com.jme3.util.SkyFactory; |
||||
import com.jme3.util.mikktspace.MikktspaceTangentGenerator; |
||||
|
||||
/** |
||||
* A test case for PBR lighting. |
||||
* Still experimental. |
||||
* |
||||
* @author nehon |
||||
*/ |
||||
public class TestPBRLighting extends SimpleApplication { |
||||
|
||||
public static void main(String[] args) { |
||||
TestPBRLighting app = new TestPBRLighting(); |
||||
app.start(); |
||||
} |
||||
private Geometry model; |
||||
private DirectionalLight dl; |
||||
private Node modelNode; |
||||
private int frame = 0; |
||||
private Material pbrMat; |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
assetManager.registerLoader(KTXLoader.class, "ktx"); |
||||
|
||||
viewPort.setBackgroundColor(ColorRGBA.White); |
||||
modelNode = (Node) new Node("modelNode"); |
||||
model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o"); |
||||
MikktspaceTangentGenerator.generate(model); |
||||
modelNode.attachChild(model); |
||||
|
||||
dl = new DirectionalLight(); |
||||
dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); |
||||
rootNode.addLight(dl); |
||||
dl.setColor(ColorRGBA.White); |
||||
rootNode.attachChild(modelNode); |
||||
|
||||
|
||||
FilterPostProcessor fpp = new FilterPostProcessor(assetManager); |
||||
// fpp.addFilter(new FXAAFilter());
|
||||
fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f))); |
||||
// fpp.addFilter(new SSAOFilter(0.5f, 3, 0.2f, 0.2f));
|
||||
viewPort.addProcessor(fpp); |
||||
|
||||
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Sky_Cloudy.hdr", SkyFactory.EnvMapType.EquirectMap);
|
||||
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); |
||||
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap);
|
||||
//Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/road.hdr", SkyFactory.EnvMapType.EquirectMap);
|
||||
rootNode.attachChild(sky); |
||||
|
||||
pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m"); |
||||
model.setMaterial(pbrMat); |
||||
|
||||
|
||||
final EnvironmentCamera envCam = new EnvironmentCamera(128, new Vector3f(0, 3f, 0)); |
||||
stateManager.attach(envCam); |
||||
|
||||
// EnvironmentManager envManager = new EnvironmentManager();
|
||||
// stateManager.attach(envManager);
|
||||
|
||||
// envManager.setScene(rootNode);
|
||||
|
||||
LightsDebugState debugState = new LightsDebugState(); |
||||
stateManager.attach(debugState); |
||||
|
||||
ChaseCamera chaser = new ChaseCamera(cam, modelNode, inputManager); |
||||
chaser.setDragToRotate(true); |
||||
chaser.setMinVerticalRotation(-FastMath.HALF_PI); |
||||
chaser.setMaxDistance(1000); |
||||
chaser.setSmoothMotion(true); |
||||
chaser.setRotationSensitivity(10); |
||||
chaser.setZoomSensitivity(5); |
||||
flyCam.setEnabled(false); |
||||
//flyCam.setMoveSpeed(100);
|
||||
|
||||
inputManager.addListener(new ActionListener() { |
||||
@Override |
||||
public void onAction(String name, boolean isPressed, float tpf) { |
||||
if (name.equals("debug") && isPressed) { |
||||
//envCam.toggleDebug();
|
||||
} |
||||
|
||||
if (name.equals("up") && isPressed) { |
||||
model.move(0, tpf * 100f, 0); |
||||
} |
||||
|
||||
if (name.equals("down") && isPressed) { |
||||
model.move(0, -tpf * 100f, 0); |
||||
} |
||||
if (name.equals("left") && isPressed) { |
||||
model.move(0, 0, tpf * 100f); |
||||
} |
||||
if (name.equals("right") && isPressed) { |
||||
model.move(0, 0, -tpf * 100f); |
||||
} |
||||
if (name.equals("light") && isPressed) { |
||||
dl.setDirection(cam.getDirection().normalize()); |
||||
} |
||||
} |
||||
}, "toggle", "light", "up", "down", "left", "right", "debug"); |
||||
|
||||
inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_RETURN)); |
||||
inputManager.addMapping("light", new KeyTrigger(KeyInput.KEY_F)); |
||||
inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP)); |
||||
inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN)); |
||||
inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT)); |
||||
inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT)); |
||||
inputManager.addMapping("debug", new KeyTrigger(KeyInput.KEY_D)); |
||||
|
||||
|
||||
MaterialDebugAppState debug = new MaterialDebugAppState(); |
||||
debug.registerBinding("Common/MatDefs/Light/PBRLighting.frag", rootNode); |
||||
getStateManager().attach(debug); |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void simpleUpdate(float tpf) { |
||||
frame++; |
||||
|
||||
if (frame == 2) { |
||||
modelNode.removeFromParent(); |
||||
final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() { |
||||
|
||||
@Override |
||||
public void done(LightProbe result) { |
||||
System.err.println("Done rendering env maps"); |
||||
} |
||||
}); |
||||
((BoundingSphere)probe.getBounds()).setRadius(100); |
||||
rootNode.addLight(probe); |
||||
//getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
|
||||
|
||||
} |
||||
if (frame > 10 && modelNode.getParent() == null) { |
||||
rootNode.attachChild(modelNode); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,387 @@ |
||||
/* |
||||
* 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 jme3test.light.pbr; |
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.bounding.BoundingSphere; |
||||
import com.jme3.input.CameraInput; |
||||
import com.jme3.input.KeyInput; |
||||
import com.jme3.input.MouseInput; |
||||
import com.jme3.input.controls.ActionListener; |
||||
import com.jme3.input.controls.KeyTrigger; |
||||
import com.jme3.input.controls.MouseAxisTrigger; |
||||
import com.jme3.light.AmbientLight; |
||||
import com.jme3.light.DirectionalLight; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.math.Quaternion; |
||||
import com.jme3.math.Vector2f; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.scene.shape.Box; |
||||
import com.jme3.scene.shape.Sphere; |
||||
import com.jme3.shadow.DirectionalLightShadowRenderer; |
||||
import com.jme3.shadow.EdgeFilteringMode; |
||||
|
||||
import com.jme3.environment.LightProbeFactory; |
||||
import com.jme3.environment.EnvironmentCamera; |
||||
import com.jme3.environment.util.LightsDebugState; |
||||
import com.jme3.light.LightProbe; |
||||
import com.jme3.material.TechniqueDef; |
||||
import com.jme3.post.FilterPostProcessor; |
||||
import com.jme3.post.filters.BloomFilter; |
||||
import com.jme3.post.filters.FXAAFilter; |
||||
import com.jme3.post.filters.ToneMapFilter; |
||||
import com.jme3.post.ssao.SSAOFilter; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.texture.plugins.ktx.KTXLoader; |
||||
import com.jme3.util.SkyFactory; |
||||
import com.jme3.util.TangentBinormalGenerator; |
||||
|
||||
public class TestPbrEnv extends SimpleApplication implements ActionListener { |
||||
|
||||
public static final int SHADOWMAP_SIZE = 1024; |
||||
private Spatial[] obj; |
||||
private Material[] mat; |
||||
private DirectionalLightShadowRenderer dlsr; |
||||
private LightsDebugState debugState; |
||||
|
||||
private EnvironmentCamera envCam; |
||||
|
||||
private Geometry ground; |
||||
private Material matGroundU; |
||||
private Material matGroundL; |
||||
|
||||
private Geometry camGeom; |
||||
|
||||
public static void main(String[] args) { |
||||
TestPbrEnv app = new TestPbrEnv(); |
||||
app.start(); |
||||
} |
||||
|
||||
|
||||
public void loadScene() { |
||||
|
||||
renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass); |
||||
renderManager.setSinglePassLightBatchSize(3); |
||||
obj = new Spatial[2]; |
||||
// Setup first view
|
||||
|
||||
mat = new Material[2]; |
||||
mat[0] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat.j3m"); |
||||
//mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
|
||||
mat[1] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat2.j3m"); |
||||
// mat[1].setBoolean("UseMaterialColors", true);
|
||||
// mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f));
|
||||
// mat[1].setColor("Diffuse", ColorRGBA.White.clone());
|
||||
|
||||
obj[0] = new Geometry("sphere", new Sphere(30, 30, 2)); |
||||
obj[0].setShadowMode(ShadowMode.CastAndReceive); |
||||
obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f)); |
||||
obj[1].setShadowMode(ShadowMode.CastAndReceive); |
||||
TangentBinormalGenerator.generate(obj[1]); |
||||
TangentBinormalGenerator.generate(obj[0]); |
||||
|
||||
// for (int i = 0; i < 60; i++) {
|
||||
// Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
|
||||
// t.setName("Cube" + i);
|
||||
// t.setLocalScale(FastMath.nextRandomFloat() * 10f);
|
||||
// t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]);
|
||||
// rootNode.attachChild(t);
|
||||
// t.setLocalTranslation(FastMath.nextRandomFloat() * 200f, FastMath.nextRandomFloat() * 30f + 20, 30f * (i + 2f));
|
||||
// }
|
||||
|
||||
for (int i = 0; i < 2; i++) { |
||||
Spatial t = obj[0].clone(false); |
||||
t.setName("Cube" + i); |
||||
t.setLocalScale( 10f); |
||||
t.setMaterial(mat[1].clone()); |
||||
rootNode.attachChild(t); |
||||
t.setLocalTranslation(i * 200f+ 100f, 50, 800f * (i)); |
||||
} |
||||
|
||||
Box b = new Box(1000, 2, 1000); |
||||
b.scaleTextureCoordinates(new Vector2f(20, 20)); |
||||
ground = new Geometry("soil", b); |
||||
TangentBinormalGenerator.generate(ground); |
||||
ground.setLocalTranslation(0, 10, 550); |
||||
matGroundU = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
||||
matGroundU.setColor("Color", ColorRGBA.Green); |
||||
|
||||
// matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
// Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
|
||||
// grass.setWrap(WrapMode.Repeat);
|
||||
// matGroundL.setTexture("DiffuseMap", grass);
|
||||
|
||||
matGroundL = assetManager.loadMaterial("jme3test/light/pbr/pbrMat4.j3m"); |
||||
|
||||
ground.setMaterial(matGroundL); |
||||
|
||||
//ground.setShadowMode(ShadowMode.CastAndReceive);
|
||||
rootNode.attachChild(ground); |
||||
|
||||
l = new DirectionalLight(); |
||||
l.setColor(ColorRGBA.White); |
||||
//l.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal());
|
||||
l.setDirection(new Vector3f(-0.2823181f, -0.41889593f, 0.863031f).normalizeLocal()); |
||||
|
||||
rootNode.addLight(l); |
||||
|
||||
AmbientLight al = new AmbientLight(); |
||||
al.setColor(ColorRGBA.White.mult(0.5f)); |
||||
// rootNode.addLight(al);
|
||||
|
||||
//Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap);
|
||||
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); |
||||
sky.setLocalScale(350); |
||||
|
||||
rootNode.attachChild(sky); |
||||
} |
||||
DirectionalLight l; |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
assetManager.registerLoader(KTXLoader.class, "ktx"); |
||||
|
||||
|
||||
// put the camera in a bad position
|
||||
cam.setLocation(new Vector3f(-52.433647f, 68.69636f, -118.60924f)); |
||||
cam.setRotation(new Quaternion(0.10294232f, 0.25269797f, -0.027049713f, 0.96167296f)); |
||||
|
||||
flyCam.setMoveSpeed(100); |
||||
|
||||
loadScene(); |
||||
|
||||
dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4); |
||||
dlsr.setLight(l); |
||||
//dlsr.setLambda(0.55f);
|
||||
dlsr.setShadowIntensity(0.5f); |
||||
dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); |
||||
//dlsr.displayDebug();
|
||||
// viewPort.addProcessor(dlsr);
|
||||
|
||||
FilterPostProcessor fpp = new FilterPostProcessor(assetManager); |
||||
|
||||
fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(6.0f))); |
||||
SSAOFilter ssao = new SSAOFilter(); |
||||
ssao.setIntensity(5); |
||||
|
||||
fpp.addFilter(ssao); |
||||
|
||||
BloomFilter bloomFilter = new BloomFilter(); |
||||
fpp.addFilter(bloomFilter); |
||||
fpp.addFilter(new FXAAFilter()); |
||||
//viewPort.addProcessor(fpp);
|
||||
|
||||
initInputs(); |
||||
|
||||
// envManager = new EnvironmentManager();
|
||||
// getStateManager().attach(envManager);
|
||||
//
|
||||
envCam = new EnvironmentCamera(); |
||||
getStateManager().attach(envCam); |
||||
|
||||
debugState = new LightsDebugState(); |
||||
debugState.setProbeScale(5); |
||||
getStateManager().attach(debugState); |
||||
|
||||
camGeom = new Geometry("camGeom", new Sphere(16, 16, 2)); |
||||
// Material m = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md");
|
||||
// m.setColor("Color", ColorRGBA.Green);
|
||||
Material m = assetManager.loadMaterial("jme3test/light/pbr/pbrMat3.j3m"); |
||||
camGeom.setMaterial(m); |
||||
camGeom.setLocalTranslation(0, 20, 0); |
||||
camGeom.setLocalScale(5); |
||||
rootNode.attachChild(camGeom); |
||||
|
||||
// envManager.setScene(rootNode);
|
||||
|
||||
// MaterialDebugAppState debug = new MaterialDebugAppState();
|
||||
// debug.registerBinding("MatDefs/PBRLighting.frag", rootNode);
|
||||
// getStateManager().attach(debug);
|
||||
|
||||
flyCam.setDragToRotate(true); |
||||
setPauseOnLostFocus(false); |
||||
|
||||
// cam.lookAt(camGeom.getWorldTranslation(), Vector3f.UNIT_Y);
|
||||
|
||||
} |
||||
|
||||
private void fixFLyCamInputs() { |
||||
inputManager.deleteMapping(CameraInput.FLYCAM_LEFT); |
||||
inputManager.deleteMapping(CameraInput.FLYCAM_RIGHT); |
||||
inputManager.deleteMapping(CameraInput.FLYCAM_UP); |
||||
inputManager.deleteMapping(CameraInput.FLYCAM_DOWN); |
||||
|
||||
inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true)); |
||||
|
||||
inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false)); |
||||
|
||||
inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false)); |
||||
|
||||
inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true)); |
||||
|
||||
inputManager.addListener(flyCam, CameraInput.FLYCAM_LEFT, CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_UP, CameraInput.FLYCAM_DOWN); |
||||
} |
||||
|
||||
private void initInputs() { |
||||
inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M)); |
||||
inputManager.addMapping("snapshot", new KeyTrigger(KeyInput.KEY_SPACE)); |
||||
inputManager.addMapping("fc", new KeyTrigger(KeyInput.KEY_F)); |
||||
inputManager.addMapping("debugProbe", new KeyTrigger(KeyInput.KEY_RETURN)); |
||||
inputManager.addMapping("debugTex", new KeyTrigger(KeyInput.KEY_T)); |
||||
inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP)); |
||||
inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN)); |
||||
inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT)); |
||||
inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT)); |
||||
inputManager.addMapping("delete", new KeyTrigger(KeyInput.KEY_DELETE)); |
||||
|
||||
inputManager.addListener(this, "delete","switchGroundMat", "snapshot", "debugTex", "debugProbe", "fc", "up", "down", "left", "right"); |
||||
} |
||||
|
||||
private LightProbe lastProbe; |
||||
private Node debugGui ; |
||||
|
||||
@Override |
||||
public void onAction(String name, boolean keyPressed, float tpf) { |
||||
|
||||
if (name.equals("switchGroundMat") && keyPressed) { |
||||
if (ground.getMaterial() == matGroundL) { |
||||
ground.setMaterial(matGroundU); |
||||
} else { |
||||
|
||||
ground.setMaterial(matGroundL); |
||||
} |
||||
} |
||||
|
||||
if (name.equals("snapshot") && keyPressed) { |
||||
envCam.setPosition(camGeom.getWorldTranslation()); |
||||
lastProbe = LightProbeFactory.makeProbe(envCam, rootNode, new ConsoleProgressReporter()); |
||||
((BoundingSphere)lastProbe.getBounds()).setRadius(200); |
||||
rootNode.addLight(lastProbe); |
||||
|
||||
} |
||||
|
||||
if (name.equals("delete") && keyPressed) { |
||||
System.err.println(rootNode.getWorldLightList().size()); |
||||
rootNode.removeLight(lastProbe); |
||||
System.err.println("deleted"); |
||||
System.err.println(rootNode.getWorldLightList().size()); |
||||
} |
||||
|
||||
if (name.equals("fc") && keyPressed) { |
||||
|
||||
flyCam.setEnabled(true); |
||||
} |
||||
|
||||
if (name.equals("debugProbe") && keyPressed) { |
||||
//getStateManager().getState(EnvironmentCamera.class).toggleDebug();
|
||||
if (!debugState.isEnabled()) { |
||||
debugState.setEnabled(true); |
||||
debugState.setDebugMode(LightsDebugState.DebugMode.IrradianceMap); |
||||
} else if (debugState.getDebugMode() == LightsDebugState.DebugMode.IrradianceMap) { |
||||
debugState.setDebugMode(LightsDebugState.DebugMode.PrefilteredEnvMap); |
||||
} else if (debugState.getDebugMode() == LightsDebugState.DebugMode.PrefilteredEnvMap) { |
||||
debugState.setEnabled(false); |
||||
} |
||||
|
||||
} |
||||
|
||||
if (name.equals("debugTex") && keyPressed) { |
||||
if(debugGui == null || debugGui.getParent() == null){ |
||||
debugGui = lastProbe.getDebugGui(assetManager); |
||||
debugGui.setLocalTranslation(10, 200, 0); |
||||
guiNode.attachChild(debugGui); |
||||
} else if(debugGui != null){ |
||||
debugGui.removeFromParent(); |
||||
} |
||||
} |
||||
|
||||
if (name.equals("up")) { |
||||
up = keyPressed; |
||||
} |
||||
if (name.equals("down")) { |
||||
down = keyPressed; |
||||
} |
||||
if (name.equals("right")) { |
||||
right = keyPressed; |
||||
} |
||||
if (name.equals("left")) { |
||||
left = keyPressed; |
||||
} |
||||
if (name.equals("fwd")) { |
||||
fwd = keyPressed; |
||||
} |
||||
if (name.equals("back")) { |
||||
back = keyPressed; |
||||
} |
||||
|
||||
} |
||||
boolean up = false; |
||||
boolean down = false; |
||||
boolean left = false; |
||||
boolean right = false; |
||||
boolean fwd = false; |
||||
boolean back = false; |
||||
float time = 0; |
||||
float s = 50f; |
||||
boolean initialized = false; |
||||
|
||||
@Override |
||||
public void simpleUpdate(float tpf) { |
||||
|
||||
if (!initialized) { |
||||
fixFLyCamInputs(); |
||||
initialized = true; |
||||
} |
||||
float val = tpf * s; |
||||
if (up) { |
||||
camGeom.move(0, 0, val); |
||||
} |
||||
if (down) { |
||||
camGeom.move(0, 0, -val); |
||||
|
||||
} |
||||
if (right) { |
||||
camGeom.move(-val, 0, 0); |
||||
|
||||
} |
||||
if (left) { |
||||
camGeom.move(val, 0, 0); |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,157 @@ |
||||
/* |
||||
* Copyright (c) 2009-2012 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 jme3test.material; |
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.input.KeyInput; |
||||
import com.jme3.input.controls.ActionListener; |
||||
import com.jme3.input.controls.AnalogListener; |
||||
import com.jme3.input.controls.KeyTrigger; |
||||
import com.jme3.light.DirectionalLight; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.*; |
||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.scene.shape.Quad; |
||||
import com.jme3.util.SkyFactory; |
||||
import com.jme3.util.TangentBinormalGenerator; |
||||
|
||||
public class TestParallaxPBR extends SimpleApplication { |
||||
|
||||
private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); |
||||
|
||||
public static void main(String[] args) { |
||||
TestParallaxPBR app = new TestParallaxPBR(); |
||||
app.start(); |
||||
} |
||||
|
||||
public void setupSkyBox() { |
||||
rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false)); |
||||
} |
||||
DirectionalLight dl; |
||||
|
||||
public void setupLighting() { |
||||
|
||||
dl = new DirectionalLight(); |
||||
dl.setDirection(lightDir); |
||||
dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1)); |
||||
rootNode.addLight(dl); |
||||
} |
||||
Material mat; |
||||
|
||||
public void setupFloor() { |
||||
mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWallPBR.j3m"); |
||||
//mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWallPBR2.j3m");
|
||||
|
||||
Node floorGeom = new Node("floorGeom"); |
||||
Quad q = new Quad(100, 100); |
||||
q.scaleTextureCoordinates(new Vector2f(10, 10)); |
||||
Geometry g = new Geometry("geom", q); |
||||
g.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); |
||||
floorGeom.attachChild(g); |
||||
|
||||
|
||||
TangentBinormalGenerator.generate(floorGeom); |
||||
floorGeom.setLocalTranslation(-50, 22, 60); |
||||
//floorGeom.setLocalScale(100);
|
||||
|
||||
floorGeom.setMaterial(mat); |
||||
rootNode.attachChild(floorGeom); |
||||
} |
||||
|
||||
public void setupSignpost() { |
||||
Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); |
||||
Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); |
||||
TangentBinormalGenerator.generate(signpost); |
||||
signpost.setMaterial(mat); |
||||
signpost.rotate(0, FastMath.HALF_PI, 0); |
||||
signpost.setLocalTranslation(12, 23.5f, 30); |
||||
signpost.setLocalScale(4); |
||||
signpost.setShadowMode(ShadowMode.CastAndReceive); |
||||
rootNode.attachChild(signpost); |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
cam.setLocation(new Vector3f(-15.445636f, 30.162927f, 60.252777f)); |
||||
cam.setRotation(new Quaternion(0.05173137f, 0.92363626f, -0.13454558f, 0.35513034f)); |
||||
flyCam.setMoveSpeed(30); |
||||
|
||||
|
||||
setupLighting(); |
||||
setupSkyBox(); |
||||
setupFloor(); |
||||
setupSignpost(); |
||||
|
||||
inputManager.addListener(new AnalogListener() { |
||||
|
||||
public void onAnalog(String name, float value, float tpf) { |
||||
if ("heightUP".equals(name)) { |
||||
parallaxHeigh += 0.01; |
||||
mat.setFloat("ParallaxHeight", parallaxHeigh); |
||||
} |
||||
if ("heightDown".equals(name)) { |
||||
parallaxHeigh -= 0.01; |
||||
parallaxHeigh = Math.max(parallaxHeigh, 0); |
||||
mat.setFloat("ParallaxHeight", parallaxHeigh); |
||||
} |
||||
|
||||
} |
||||
}, "heightUP", "heightDown"); |
||||
inputManager.addMapping("heightUP", new KeyTrigger(KeyInput.KEY_I)); |
||||
inputManager.addMapping("heightDown", new KeyTrigger(KeyInput.KEY_K)); |
||||
|
||||
inputManager.addListener(new ActionListener() { |
||||
|
||||
public void onAction(String name, boolean isPressed, float tpf) { |
||||
if (isPressed && "toggleSteep".equals(name)) { |
||||
steep = !steep; |
||||
mat.setBoolean("SteepParallax", steep); |
||||
} |
||||
} |
||||
}, "toggleSteep"); |
||||
inputManager.addMapping("toggleSteep", new KeyTrigger(KeyInput.KEY_SPACE)); |
||||
} |
||||
float parallaxHeigh = 0.05f; |
||||
float time = 0; |
||||
boolean steep = false; |
||||
|
||||
@Override |
||||
public void simpleUpdate(float tpf) { |
||||
// time+=tpf;
|
||||
// lightDir.set(FastMath.sin(time), -1, FastMath.cos(time));
|
||||
// bsr.setDirection(lightDir);
|
||||
// dl.setDirection(lightDir);
|
||||
} |
||||
} |
@ -0,0 +1,180 @@ |
||||
/* |
||||
* 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 jme3test.post; |
||||
|
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.input.KeyInput; |
||||
import com.jme3.input.controls.ActionListener; |
||||
import com.jme3.input.controls.KeyTrigger; |
||||
import com.jme3.light.DirectionalLight; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.math.Quaternion; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.post.FilterPostProcessor; |
||||
import com.jme3.post.filters.BloomFilter; |
||||
import com.jme3.post.filters.BloomFilter.GlowMode; |
||||
import com.jme3.renderer.queue.RenderQueue; |
||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.scene.debug.WireFrustum; |
||||
import com.jme3.scene.shape.Box; |
||||
import com.jme3.util.SkyFactory; |
||||
|
||||
public class TestBloomAlphaThreshold extends SimpleApplication |
||||
{ |
||||
|
||||
float angle; |
||||
Spatial lightMdl; |
||||
Spatial teapot; |
||||
Geometry frustumMdl; |
||||
WireFrustum frustum; |
||||
boolean active = true; |
||||
FilterPostProcessor fpp; |
||||
|
||||
public static void main(String[] args) |
||||
{ |
||||
TestBloomAlphaThreshold app = new TestBloomAlphaThreshold(); |
||||
app.start(); |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() |
||||
{ |
||||
// put the camera in a bad position
|
||||
cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -10)); |
||||
cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f)); |
||||
// cam.setFrustumFar(1000);
|
||||
|
||||
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); |
||||
|
||||
mat.setFloat("Shininess", 15f); |
||||
mat.setBoolean("UseMaterialColors", true); |
||||
mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f)); |
||||
mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); |
||||
mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); |
||||
mat.setColor("GlowColor", ColorRGBA.Green); |
||||
|
||||
Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); |
||||
matSoil.setFloat("Shininess", 15f); |
||||
matSoil.setBoolean("UseMaterialColors", true); |
||||
matSoil.setColor("Ambient", ColorRGBA.Gray); |
||||
matSoil.setColor("Diffuse", ColorRGBA.Black); |
||||
matSoil.setColor("Specular", ColorRGBA.Gray); |
||||
|
||||
teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); |
||||
teapot.setLocalTranslation(0, 0, 10); |
||||
|
||||
teapot.setMaterial(mat); |
||||
teapot.setShadowMode(ShadowMode.CastAndReceive); |
||||
teapot.setLocalScale(10.0f); |
||||
rootNode.attachChild(teapot); |
||||
|
||||
Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700)); |
||||
soil.setMaterial(matSoil); |
||||
soil.setShadowMode(ShadowMode.CastAndReceive); |
||||
rootNode.attachChild(soil); |
||||
|
||||
Material matBox = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
||||
matBox.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png")); |
||||
matBox.setFloat("AlphaDiscardThreshold", 0.5f); |
||||
|
||||
Geometry box = new Geometry("box", new Box(new Vector3f(-3.5f, 10, -2), 2, 2, 2)); |
||||
box.setMaterial(matBox); |
||||
box.setQueueBucket(RenderQueue.Bucket.Translucent); |
||||
// box.setShadowMode(ShadowMode.CastAndReceive);
|
||||
rootNode.attachChild(box); |
||||
|
||||
DirectionalLight light = new DirectionalLight(); |
||||
light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); |
||||
light.setColor(ColorRGBA.White.mult(1.5f)); |
||||
rootNode.addLight(light); |
||||
|
||||
// load sky
|
||||
Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false); |
||||
sky.setCullHint(Spatial.CullHint.Never); |
||||
rootNode.attachChild(sky); |
||||
|
||||
fpp = new FilterPostProcessor(assetManager); |
||||
int numSamples = getContext().getSettings().getSamples(); |
||||
if (numSamples > 0) |
||||
{ |
||||
fpp.setNumSamples(numSamples); |
||||
} |
||||
|
||||
BloomFilter bloom = new BloomFilter(GlowMode.Objects); |
||||
bloom.setDownSamplingFactor(2); |
||||
bloom.setBlurScale(1.37f); |
||||
bloom.setExposurePower(3.30f); |
||||
bloom.setExposureCutOff(0.2f); |
||||
bloom.setBloomIntensity(2.45f); |
||||
BloomUI ui = new BloomUI(inputManager, bloom); |
||||
|
||||
viewPort.addProcessor(fpp); |
||||
fpp.addFilter(bloom); |
||||
initInputs(); |
||||
|
||||
} |
||||
|
||||
private void initInputs() |
||||
{ |
||||
inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); |
||||
|
||||
ActionListener acl = new ActionListener() |
||||
{ |
||||
|
||||
@Override |
||||
public void onAction(String name, boolean keyPressed, float tpf) |
||||
{ |
||||
if (name.equals("toggle") && keyPressed) |
||||
{ |
||||
if (active) |
||||
{ |
||||
active = false; |
||||
viewPort.removeProcessor(fpp); |
||||
} |
||||
else |
||||
{ |
||||
active = true; |
||||
viewPort.addProcessor(fpp); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
inputManager.addListener(acl, "toggle"); |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,112 @@ |
||||
/* |
||||
* 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 jme3test.texture.ktx; |
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.asset.TextureKey; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.renderer.RenderManager; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.texture.Texture; |
||||
import com.jme3.texture.Texture2D; |
||||
import com.jme3.texture.TextureCubeMap; |
||||
import com.jme3.texture.plugins.ktx.KTXLoader; |
||||
import com.jme3.ui.Picture; |
||||
|
||||
/** |
||||
* test |
||||
* @author nehon |
||||
*/ |
||||
public class TestLoadKtx extends SimpleApplication { |
||||
|
||||
public static void main(String[] args) { |
||||
TestLoadKtx app = new TestLoadKtx(); |
||||
//app.setShowSettings(false);
|
||||
app.start(); |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
viewPort.setBackgroundColor(ColorRGBA.DarkGray); |
||||
assetManager.registerLoader(KTXLoader.class, "ktx"); |
||||
|
||||
|
||||
Texture2D t = (Texture2D)assetManager.loadTexture("Textures/ktx/down-reference.ktx"); |
||||
Picture p = new Picture("bla", false); |
||||
p.setTexture(assetManager, t, false); |
||||
p.setLocalTranslation(200, 200, 0); |
||||
p.setWidth(t.getImage().getWidth()); |
||||
p.setHeight(t.getImage().getHeight()); |
||||
guiNode.attachChild(p); |
||||
|
||||
|
||||
Texture2D t2 = (Texture2D)assetManager.loadTexture("Textures/ktx/up-reference.ktx"); |
||||
Picture p2 = new Picture("bla", false); |
||||
p2.setTexture(assetManager, t2, false); |
||||
p2.setLocalTranslation(400, 200, 0); |
||||
p2.setWidth(t2.getImage().getWidth()); |
||||
p2.setHeight(t2.getImage().getHeight()); |
||||
guiNode.attachChild(p2); |
||||
|
||||
Texture2D t3 = (Texture2D)assetManager.loadTexture("Textures/ktx/rgba-reference.ktx"); |
||||
Picture p3 = new Picture("bla", false); |
||||
p3.setTexture(assetManager, t3, false); |
||||
p3.setLocalTranslation(200, 400, 0); |
||||
p3.setWidth(t3.getImage().getWidth()); |
||||
p3.setHeight(t3.getImage().getHeight()); |
||||
guiNode.attachChild(p3); |
||||
|
||||
|
||||
Texture2D t4 = (Texture2D)assetManager.loadTexture("Textures/ktx/rgb-amg-reference.ktx"); |
||||
Picture p4 = new Picture("bla", false); |
||||
p4.setTexture(assetManager, t4, false); |
||||
p4.setLocalTranslation(400, 400, 0); |
||||
p4.setWidth(t4.getImage().getWidth()); |
||||
p4.setHeight(t4.getImage().getHeight()); |
||||
guiNode.attachChild(p4); |
||||
|
||||
|
||||
flyCam.setDragToRotate(true); |
||||
|
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void simpleUpdate(float tpf) { |
||||
//TODO: add update code
|
||||
} |
||||
|
||||
@Override |
||||
public void simpleRender(RenderManager rm) { |
||||
//TODO: add render code
|
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
Material My Material : Common/MatDefs/Light/PBRLighting.j3md { |
||||
MaterialParameters { |
||||
Roughness : 0.1 |
||||
BaseColor : 1.0 0.2 0.2 1.0 |
||||
Metallic : 0.0 |
||||
EmissivePower : 0.0 |
||||
EmissiveIntensity : 0.0 |
||||
Emissive : 1.0 0.8 0.8 1.0 |
||||
ParallaxHeight : 0.0 |
||||
} |
||||
AdditionalRenderState { |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
Material My Material : Common/MatDefs/Light/PBRLighting.j3md { |
||||
MaterialParameters { |
||||
Metallic : 0.0 |
||||
Roughness : 0.0 |
||||
BaseColor : 0.8 0.81960785 0.39607844 1.0 |
||||
EmissiveIntensity : 5.0 |
||||
EmissivePower : 2.0 |
||||
Emissive : 1.0 0.0 0.0 1.0 |
||||
} |
||||
AdditionalRenderState { |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
Material My Material : Common/MatDefs/Light/PBRLighting.j3md { |
||||
MaterialParameters { |
||||
BaseColor : 0.6 0.6 0.6 1.0 |
||||
Metallic : 1.0 |
||||
Roughness : 0.05 |
||||
} |
||||
AdditionalRenderState { |
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
Material My Material : Common/MatDefs/Light/PBRLighting.j3md { |
||||
MaterialParameters { |
||||
BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg |
||||
Roughness : 0.01 |
||||
NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal.jpg |
||||
Metallic : 1.0 |
||||
BaseColor : 1.0 1.0 1.0 1.0 |
||||
} |
||||
AdditionalRenderState { |
||||
} |
||||
} |
After Width: | Height: | Size: 508 KiB |
After Width: | Height: | Size: 555 KiB |
After Width: | Height: | Size: 3.7 MiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 12 MiB |
After Width: | Height: | Size: 1.7 MiB |
@ -0,0 +1,14 @@ |
||||
Material Tank : Common/MatDefs/Light/PBRLighting.j3md { |
||||
MaterialParameters { |
||||
|
||||
MetallicMap : Flip Models/Tank/Tank_Metallic.png |
||||
RoughnessMap : Flip Models/Tank/Tank_Roughness.png |
||||
NormalMap : Flip Models/Tank/Tank_Normal.png |
||||
BaseColorMap : Flip Models/Tank/Tank_Base_Color.png |
||||
EmissiveMap : Flip Models/Tank/Tank_Emissive.png |
||||
EmissiveIntensity : 2.0 |
||||
|
||||
} |
||||
AdditionalRenderState { |
||||
} |
||||
} |
@ -0,0 +1,3 @@ |
||||
# |
||||
#Sat Apr 11 15:27:27 CEST 2015 |
||||
ORIGINAL_PATH=Models/Tank/tank.obj |
@ -0,0 +1,9 @@ |
||||
Material Pong Rock PBR : Common/MatDefs/Light/PBRLighting.j3md { |
||||
MaterialParameters { |
||||
Roughness : 1.0 |
||||
Metallic : 0.0 |
||||
BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg |
||||
NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds |
||||
PackedNormalParallax: true |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
Material Pong Rock PBR : Common/MatDefs/Light/PBRLighting.j3md { |
||||
MaterialParameters { |
||||
Roughness : 1.0 |
||||
Metallic : 0.0 |
||||
BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg |
||||
ParallaxMap : Repeat Textures/Terrain/BrickWall/BrickWall_height.jpg |
||||
} |
||||
} |
@ -0,0 +1,131 @@ |
||||
/* |
||||
* Copyright (c) 2009-2010 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.gde.core.sceneexplorer.nodes; |
||||
|
||||
import com.jme3.light.LightProbe; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.Spatial; |
||||
import java.beans.PropertyEditor; |
||||
import java.lang.reflect.InvocationTargetException; |
||||
import org.openide.nodes.PropertySupport; |
||||
import org.openide.nodes.Sheet; |
||||
|
||||
/** |
||||
* |
||||
* @author normenhansen |
||||
*/ |
||||
@org.openide.util.lookup.ServiceProvider(service=SceneExplorerNode.class) |
||||
@SuppressWarnings({"unchecked", "rawtypes"}) |
||||
public class JmeLightProbe extends JmeLight{ |
||||
protected LightProbe lightProbe; |
||||
|
||||
public JmeLightProbe() { |
||||
} |
||||
|
||||
public JmeLightProbe(Spatial spatial, LightProbe lightProbe) { |
||||
super(spatial, lightProbe); |
||||
this.lightProbe = lightProbe; |
||||
lookupContents.add(lightProbe); |
||||
setName("LightProbe"); |
||||
} |
||||
|
||||
@Override |
||||
protected Sheet createSheet() { |
||||
//TODO: multithreading..
|
||||
Sheet sheet = super.createSheet(); |
||||
Sheet.Set set = Sheet.createPropertiesSet(); |
||||
set.setDisplayName("LightProbe"); |
||||
set.setName(LightProbe.class.getName()); |
||||
LightProbe obj = lightProbe; |
||||
if (obj == null) { |
||||
return sheet; |
||||
} |
||||
|
||||
set.put(makeProperty(obj, Vector3f.class, "getPosition", "setPosition", "Position")); |
||||
set.put(makeEmbedProperty(obj.getBounds(), obj.getBounds().getClass(), float.class, "getRadius", "setRadius", "Radius")); |
||||
set.put(createButtonProperty()); |
||||
sheet.put(set); |
||||
return sheet; |
||||
|
||||
} |
||||
|
||||
public LightProbe getLightProbe() { |
||||
return lightProbe; |
||||
} |
||||
|
||||
@Override |
||||
public Class getExplorerObjectClass() { |
||||
return LightProbe.class; |
||||
} |
||||
|
||||
@Override |
||||
public Class getExplorerNodeClass() { |
||||
return JmeLightProbe.class; |
||||
} |
||||
|
||||
protected void setModified(){ |
||||
java.awt.EventQueue.invokeLater(new Runnable() { |
||||
|
||||
@Override |
||||
public void run() { |
||||
fireSave(true); |
||||
} |
||||
}); |
||||
|
||||
} |
||||
|
||||
private Property createButtonProperty() { |
||||
return new PropertySupport.ReadWrite<Object>("update", Object.class, "Refresh maps", "Click here to refresh environment maps ") { |
||||
JmeLightProbeButtonProperty pe; |
||||
|
||||
@Override |
||||
public Object getValue() throws IllegalAccessException, InvocationTargetException { |
||||
return ""; |
||||
} |
||||
|
||||
@Override |
||||
public PropertyEditor getPropertyEditor() { |
||||
if (pe == null) { |
||||
pe = new JmeLightProbeButtonProperty(JmeLightProbe.this, (Node)getSpatial()); |
||||
pe.attachEnv(pe.env); |
||||
} |
||||
return pe; |
||||
} |
||||
|
||||
@Override |
||||
public void setValue(Object t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { |
||||
} |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,99 @@ |
||||
package com.jme3.gde.core.sceneexplorer.nodes; |
||||
|
||||
import com.jme3.environment.EnvironmentCamera; |
||||
import com.jme3.environment.LightProbeFactory; |
||||
import com.jme3.environment.generation.JobProgressAdapter; |
||||
import com.jme3.gde.core.scene.SceneApplication; |
||||
import com.jme3.gde.core.scene.controller.SceneToolController; |
||||
import com.jme3.gde.core.util.ButtonInplaceEditor; |
||||
import com.jme3.light.LightProbe; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.scene.Node; |
||||
import java.awt.Graphics; |
||||
import java.awt.Rectangle; |
||||
import java.awt.event.ActionEvent; |
||||
import java.awt.event.ActionListener; |
||||
import java.beans.PropertyEditorSupport; |
||||
import java.util.concurrent.Callable; |
||||
import org.netbeans.api.progress.ProgressHandle; |
||||
import org.netbeans.api.progress.ProgressHandleFactory; |
||||
import org.openide.explorer.propertysheet.ExPropertyEditor; |
||||
import org.openide.explorer.propertysheet.InplaceEditor; |
||||
import org.openide.explorer.propertysheet.PropertyEnv; |
||||
|
||||
/** |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
public class JmeLightProbeButtonProperty extends PropertyEditorSupport implements ExPropertyEditor, InplaceEditor.Factory { |
||||
|
||||
JmeLightProbe probe; |
||||
Node node; |
||||
|
||||
public JmeLightProbeButtonProperty(JmeLightProbe pe, Node node) { |
||||
super(); |
||||
this.node = node; |
||||
this.probe = pe; |
||||
} |
||||
PropertyEnv env; |
||||
|
||||
@Override |
||||
public void attachEnv(PropertyEnv env) { |
||||
this.env = env; |
||||
env.registerInplaceEditorFactory(this); |
||||
} |
||||
private ButtonInplaceEditor ed = null; |
||||
|
||||
@Override |
||||
public InplaceEditor getInplaceEditor() { |
||||
if (ed == null) { |
||||
ed = new ButtonInplaceEditor("Refresh"); |
||||
ed.addActionListener(new ActionListener() { |
||||
|
||||
@Override |
||||
public void actionPerformed(ActionEvent e) { |
||||
|
||||
SceneApplication.getApplication().enqueue(new Callable<Object>() { |
||||
|
||||
@Override |
||||
public Object call() throws Exception { |
||||
|
||||
EnvironmentCamera envCam = SceneApplication.getApplication().getStateManager().getState(EnvironmentCamera.class); |
||||
SceneToolController toolController = SceneApplication.getApplication().getStateManager().getState(SceneToolController.class); |
||||
if (toolController != null) { |
||||
envCam.setPosition(toolController.getCursorLocation()); |
||||
} else { |
||||
envCam.setPosition(new Vector3f(0, 0, 0)); |
||||
} |
||||
LightProbeFactory.updateProbe(probe.getLightProbe(), envCam, node, new JmeLightProbeProgressHandler()); |
||||
|
||||
probe.setModified(); |
||||
|
||||
return null; |
||||
} |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
return ed; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isPaintable() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public void paintValue(Graphics gfx, Rectangle box) { |
||||
if (ed == null) { |
||||
getInplaceEditor(); |
||||
} |
||||
ed.setSize(box.width, box.height); |
||||
ed.doLayout(); |
||||
Graphics g = gfx.create(box.x, box.y, box.width, box.height); |
||||
ed.setOpaque(false); |
||||
ed.paint(g); |
||||
g.dispose(); |
||||
probe.refresh(false); |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
|
||||
package com.jme3.gde.core.sceneexplorer.nodes; |
||||
|
||||
import com.jme3.environment.generation.JobProgressAdapter; |
||||
import com.jme3.light.LightProbe; |
||||
import org.netbeans.api.progress.ProgressHandle; |
||||
import org.netbeans.api.progress.ProgressHandleFactory; |
||||
|
||||
/** |
||||
* |
||||
* @author Nehon |
||||
*/ |
||||
|
||||
|
||||
public class JmeLightProbeProgressHandler extends JobProgressAdapter<LightProbe> { |
||||
|
||||
int lastProgress; |
||||
|
||||
ProgressHandle handle = ProgressHandleFactory.createHandle("Generating environment maps"); |
||||
|
||||
@Override |
||||
public void start() { |
||||
handle.start(100); |
||||
} |
||||
|
||||
@Override |
||||
public void progress(double value) { |
||||
lastProgress = (int) (value * 100); |
||||
handle.progress(lastProgress); |
||||
} |
||||
|
||||
@Override |
||||
public void step(String message) { |
||||
handle.progress(message, lastProgress); |
||||
} |
||||
|
||||
@Override |
||||
public void done(LightProbe t) { |
||||
handle.finish(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,57 @@ |
||||
/* |
||||
* To change this license header, choose License Headers in Project Properties. |
||||
* To change this template file, choose Tools | Templates |
||||
* and open the template in the editor. |
||||
*/ |
||||
package com.jme3.gde.scenecomposer.gizmo.light; |
||||
|
||||
import com.jme3.light.Light; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.scene.control.BillboardControl; |
||||
import java.lang.reflect.InvocationTargetException; |
||||
import java.lang.reflect.Method; |
||||
import org.openide.util.Exceptions; |
||||
|
||||
/** |
||||
* Updates the marker's position whenever the light has moved. It is also a |
||||
* BillboardControl, so this marker always faces the camera |
||||
*/ |
||||
public class LightGizmoControl extends BillboardControl { |
||||
|
||||
private final Vector3f lastPos = new Vector3f(); |
||||
private Vector3f lightPos; |
||||
|
||||
LightGizmoControl(Light light) { |
||||
super(); |
||||
|
||||
try { |
||||
Method getPosition = light.getClass().getMethod("getPosition"); |
||||
lightPos = (Vector3f) getPosition.invoke(light); |
||||
} catch (NoSuchMethodException ex) { |
||||
//light type doesn't have a get position method, silancing the exception
|
||||
} catch (SecurityException ex) { |
||||
Exceptions.printStackTrace(ex); |
||||
} catch (IllegalAccessException ex) { |
||||
Exceptions.printStackTrace(ex); |
||||
} catch (IllegalArgumentException ex) { |
||||
Exceptions.printStackTrace(ex); |
||||
} catch (InvocationTargetException ex) { |
||||
Exceptions.printStackTrace(ex); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Override |
||||
protected void controlUpdate(float f) { |
||||
super.controlUpdate(f); |
||||
|
||||
if (!lightPos.equals(lastPos)) { |
||||
if (getSpatial() != null) { |
||||
lastPos.set(lightPos); |
||||
getSpatial().setLocalTranslation(lastPos); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,68 @@ |
||||
/* |
||||
* To change this license header, choose License Headers in Project Properties. |
||||
* To change this template file, choose Tools | Templates |
||||
* and open the template in the editor. |
||||
*/ |
||||
package com.jme3.gde.scenecomposer.gizmo.light; |
||||
|
||||
import com.jme3.asset.AssetManager; |
||||
import com.jme3.environment.util.BoundingSphereDebug; |
||||
import com.jme3.gde.scenecomposer.gizmo.light.shape.ProbeRadiusShape; |
||||
import com.jme3.light.Light; |
||||
import com.jme3.light.LightProbe; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.material.RenderState; |
||||
import com.jme3.renderer.queue.RenderQueue; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.Spatial; |
||||
import com.jme3.scene.control.BillboardControl; |
||||
import com.jme3.scene.shape.Quad; |
||||
import com.jme3.scene.shape.Sphere; |
||||
import com.jme3.texture.Texture; |
||||
|
||||
/** |
||||
* Handles the creation of the appropriate light gizmo according to the light type. |
||||
* @author Nehon |
||||
*/ |
||||
public class LightGizmoFactory { |
||||
|
||||
public static Spatial createGizmo(AssetManager assetManager, Light light){ |
||||
switch (light.getType()){ |
||||
case Probe: |
||||
return createLightProbeGizmo(assetManager, light); |
||||
default: |
||||
return createDefaultGizmo(assetManager, light); |
||||
} |
||||
|
||||
} |
||||
|
||||
private static Spatial createDefaultGizmo(AssetManager assetManager, Light light){ |
||||
Quad q = new Quad(0.5f, 0.5f); |
||||
Geometry g = new Geometry(light.getName(), q); |
||||
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
||||
Texture tex = assetManager.loadTexture("com/jme3/gde/scenecomposer/lightbulb32.png"); |
||||
mat.setTexture("ColorMap", tex); |
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); |
||||
g.setMaterial(mat); |
||||
g.addControl(new LightGizmoControl(light)); |
||||
g.setQueueBucket(RenderQueue.Bucket.Transparent); |
||||
return g; |
||||
} |
||||
|
||||
private static Spatial createLightProbeGizmo(AssetManager assetManager, Light light){ |
||||
Node debugNode = new Node("Environment debug Node"); |
||||
Sphere s = new Sphere(16, 16, 0.5f); |
||||
Geometry debugGeom = new Geometry(light.getName(), s); |
||||
Material debugMaterial = new Material(assetManager, "Common/MatDefs/Misc/reflect.j3md"); |
||||
debugGeom.setMaterial(debugMaterial); |
||||
Spatial debugBounds = ProbeRadiusShape.createShape(assetManager); |
||||
|
||||
debugNode.attachChild(debugGeom); |
||||
debugNode.attachChild(debugBounds); |
||||
debugNode.addControl(new LightProbeGizmoControl((LightProbe)light)); |
||||
|
||||
return debugNode; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,59 @@ |
||||
/* |
||||
* To change this license header, choose License Headers in Project Properties. |
||||
* To change this template file, choose Tools | Templates |
||||
* and open the template in the editor. |
||||
*/ |
||||
package com.jme3.gde.scenecomposer.gizmo.light; |
||||
|
||||
import com.jme3.bounding.BoundingSphere; |
||||
import com.jme3.environment.util.LightsDebugState; |
||||
import com.jme3.light.LightProbe; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.renderer.RenderManager; |
||||
import com.jme3.renderer.ViewPort; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.control.AbstractControl; |
||||
|
||||
/** |
||||
* Updates the marker's position whenever the light probe has moved. |
||||
* Also update the gizmo radius according to the probe radius. |
||||
*/ |
||||
public class LightProbeGizmoControl extends AbstractControl{ |
||||
|
||||
private final Vector3f lastPos = new Vector3f(); |
||||
private final LightProbe lightProbe; |
||||
|
||||
LightProbeGizmoControl(LightProbe light) { |
||||
lightProbe = light; |
||||
|
||||
} |
||||
|
||||
@Override |
||||
protected void controlUpdate(float f) { |
||||
|
||||
if (!lightProbe.getPosition().equals(lastPos)) { |
||||
if (getSpatial() != null) { |
||||
lastPos.set(lightProbe.getPosition()); |
||||
getSpatial().setLocalTranslation(lastPos); |
||||
} |
||||
} |
||||
|
||||
Geometry probeGeom = (Geometry) ((Node) getSpatial()).getChild(0); |
||||
Material m = probeGeom.getMaterial(); |
||||
if (lightProbe.isReady()) { |
||||
m.setTexture("CubeMap", lightProbe.getPrefilteredEnvMap()); |
||||
} |
||||
Geometry probeRadius = (Geometry) ((Node) getSpatial()).getChild(1); |
||||
probeRadius.setLocalScale(((BoundingSphere) lightProbe.getBounds()).getRadius()); |
||||
|
||||
|
||||
} |
||||
|
||||
@Override |
||||
protected void controlRender(RenderManager rm, ViewPort vp) { |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,23 @@ |
||||
ShaderNodeDefinitions{ |
||||
ShaderNodeDefinition Dashed { |
||||
Type: Fragment |
||||
|
||||
Shader GLSL100: com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed100.frag |
||||
|
||||
Documentation{ |
||||
Renders dashed lines |
||||
@input vec2 texCoord The texture coordinates |
||||
@input float size the size of the dashes |
||||
@input vec4 inColor the color of the fragment so far |
||||
@outColor vec4 color the output color |
||||
} |
||||
Input { |
||||
vec2 texCoord |
||||
vec4 inColor |
||||
float size |
||||
} |
||||
Output { |
||||
vec4 outColor |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
|
||||
void main(){ |
||||
//@input vec2 texCoord The texture coordinates |
||||
//@input float size the size of the dashes |
||||
//@output vec4 color the output color |
||||
|
||||
//insert glsl code here |
||||
outColor = inColor; |
||||
outColor.a = step(1.0 - size, texCoord.x); |
||||
|
||||
} |
@ -0,0 +1,37 @@ |
||||
MaterialDef Simple { |
||||
MaterialParameters { |
||||
Float DashSize |
||||
} |
||||
Technique { |
||||
WorldParameters { |
||||
WorldViewProjectionMatrix |
||||
} |
||||
VertexShaderNodes { |
||||
ShaderNode CommonVert { |
||||
Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn |
||||
InputMappings { |
||||
worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix |
||||
modelPosition = Global.position.xyz |
||||
texCoord1 = Attr.inTexCoord |
||||
vertColor = Attr.inColor |
||||
} |
||||
OutputMappings { |
||||
Global.position = projPosition |
||||
} |
||||
} |
||||
} |
||||
FragmentShaderNodes { |
||||
ShaderNode Dashed { |
||||
Definition : Dashed : com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed.j3sn |
||||
InputMappings { |
||||
texCoord = CommonVert.texCoord1 |
||||
inColor = CommonVert.vertColor |
||||
size = MatParam.DashSize |
||||
} |
||||
OutputMappings { |
||||
Global.color = outColor |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,168 @@ |
||||
/* |
||||
* 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.gde.scenecomposer.gizmo.light.shape; |
||||
|
||||
import com.jme3.asset.AssetManager; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.material.RenderState; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.math.FastMath; |
||||
import com.jme3.renderer.queue.RenderQueue; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Mesh; |
||||
import com.jme3.scene.VertexBuffer.Type; |
||||
import com.jme3.scene.control.BillboardControl; |
||||
import com.jme3.util.BufferUtils; |
||||
import java.nio.FloatBuffer; |
||||
import java.nio.ShortBuffer; |
||||
|
||||
/** |
||||
* |
||||
* A debuging shape for a BoundingSphere |
||||
* Consists of 3 axis aligned circles. |
||||
* |
||||
* @author nehon |
||||
*/ |
||||
public class ProbeRadiusShape extends Mesh { |
||||
|
||||
protected int vertCount; |
||||
protected int triCount; |
||||
protected int radialSamples = 256; |
||||
protected boolean useEvenSlices; |
||||
protected boolean interior; |
||||
/** |
||||
* the distance from the center point each point falls on |
||||
*/ |
||||
public float radius; |
||||
|
||||
public float getRadius() { |
||||
return radius; |
||||
} |
||||
|
||||
public ProbeRadiusShape() { |
||||
setGeometryData(); |
||||
setIndexData(); |
||||
} |
||||
|
||||
/** |
||||
* builds the vertices based on the radius |
||||
*/ |
||||
private void setGeometryData() { |
||||
setMode(Mode.Lines); |
||||
|
||||
FloatBuffer posBuf = BufferUtils.createVector3Buffer((radialSamples + 1)); |
||||
FloatBuffer colBuf = BufferUtils.createFloatBuffer((radialSamples + 1) * 4); |
||||
FloatBuffer texBuf = BufferUtils.createVector2Buffer(radialSamples + 1); |
||||
|
||||
|
||||
setBuffer(Type.Position, 3, posBuf); |
||||
setBuffer(Type.Color, 4, colBuf); |
||||
setBuffer(Type.TexCoord, 2, texBuf); |
||||
|
||||
// generate geometry
|
||||
float fInvRS = 1.0f / radialSamples; |
||||
|
||||
// Generate points on the unit circle to be used in computing the mesh
|
||||
// points on a sphere slice.
|
||||
float[] afSin = new float[(radialSamples + 1)]; |
||||
float[] afCos = new float[(radialSamples + 1)]; |
||||
for (int iR = 0; iR < radialSamples; iR++) { |
||||
float fAngle = FastMath.TWO_PI * fInvRS * iR; |
||||
afCos[iR] = FastMath.cos(fAngle); |
||||
afSin[iR] = FastMath.sin(fAngle); |
||||
} |
||||
afSin[radialSamples] = afSin[0]; |
||||
afCos[radialSamples] = afCos[0]; |
||||
|
||||
for (int iR = 0; iR <= radialSamples; iR++) { |
||||
posBuf.put(afCos[iR]) |
||||
.put(afSin[iR]) |
||||
.put(0); |
||||
colBuf.put(ColorRGBA.Orange.r) |
||||
.put(ColorRGBA.Orange.g) |
||||
.put(ColorRGBA.Orange.b) |
||||
.put(ColorRGBA.Orange.a); |
||||
texBuf.put(iR % 2f) |
||||
.put(iR % 2f); |
||||
|
||||
} |
||||
|
||||
updateBound(); |
||||
setStatic(); |
||||
} |
||||
|
||||
/** |
||||
* sets the indices for rendering the sphere. |
||||
*/ |
||||
private void setIndexData() { |
||||
// allocate connectivity
|
||||
int nbSegments = (radialSamples);// * 3;
|
||||
|
||||
ShortBuffer idxBuf = BufferUtils.createShortBuffer(2 * nbSegments); |
||||
setBuffer(Type.Index, 2, idxBuf); |
||||
|
||||
int idx = 0; |
||||
int segDone = 0; |
||||
while (segDone < nbSegments) { |
||||
idxBuf.put((short) idx); |
||||
idxBuf.put((short) (idx + 1)); |
||||
idx++; |
||||
segDone++; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Convenience factory method that creates a debuging bounding sphere geometry |
||||
* @param assetManager the assetManager |
||||
* @return the bounding sphere debug geometry. |
||||
*/ |
||||
public static Geometry createShape(AssetManager assetManager) { |
||||
ProbeRadiusShape b = new ProbeRadiusShape(); |
||||
Geometry geom = new Geometry("BoundingDebug", b); |
||||
|
||||
Material mat = new Material(assetManager, "com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/dashed.j3md"); |
||||
mat.getAdditionalRenderState().setWireframe(true); |
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); |
||||
mat.getAdditionalRenderState().setDepthWrite(false); |
||||
mat.getAdditionalRenderState().setDepthTest(false); |
||||
mat.setFloat("DashSize", 0.5f); |
||||
geom.setQueueBucket(RenderQueue.Bucket.Transparent); |
||||
geom.addControl(new BillboardControl()); |
||||
|
||||
|
||||
geom.setMaterial(mat); |
||||
return geom; |
||||
|
||||
} |
||||
} |