PBR has come
Merge branch 'PBRisComing' # Conflicts: # jme3-core/src/main/java/com/jme3/material/Material.java # jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -43,6 +43,15 @@ public final class DefaultLightFilter implements LightFilter {
|
|||||||
|
|
||||||
private Camera camera;
|
private Camera camera;
|
||||||
private final HashSet<Light> processedLights = new HashSet<Light>();
|
private final HashSet<Light> processedLights = new HashSet<Light>();
|
||||||
|
private final LightProbeBlendingStrategy probeBlendStrat;
|
||||||
|
|
||||||
|
public DefaultLightFilter() {
|
||||||
|
probeBlendStrat = new BasicProbeBlendingStrategy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) {
|
||||||
|
this.probeBlendStrat = probeBlendStrat;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCamera(Camera camera) {
|
public void setCamera(Camera camera) {
|
||||||
@ -57,6 +66,7 @@ public final class DefaultLightFilter implements LightFilter {
|
|||||||
TempVars vars = TempVars.get();
|
TempVars vars = TempVars.get();
|
||||||
try {
|
try {
|
||||||
LightList worldLights = geometry.getWorldLightList();
|
LightList worldLights = geometry.getWorldLightList();
|
||||||
|
|
||||||
for (int i = 0; i < worldLights.size(); i++) {
|
for (int i = 0; i < worldLights.size(); i++) {
|
||||||
Light light = worldLights.get(i);
|
Light light = worldLights.get(i);
|
||||||
|
|
||||||
@ -88,9 +98,17 @@ public final class DefaultLightFilter implements LightFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredLightList.add(light);
|
if (light.getType() == Light.Type.Probe) {
|
||||||
|
probeBlendStrat.registerProbe((LightProbe) light);
|
||||||
|
} else {
|
||||||
|
filteredLightList.add(light);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
probeBlendStrat.populateProbes(geometry, filteredLightList);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
vars.release();
|
vars.release();
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,14 @@ public abstract class Light implements Savable, Cloneable {
|
|||||||
*
|
*
|
||||||
* @see AmbientLight
|
* @see AmbientLight
|
||||||
*/
|
*/
|
||||||
Ambient(3);
|
Ambient(3),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Light probe
|
||||||
|
* @see LightProbe
|
||||||
|
*/
|
||||||
|
Probe(4);
|
||||||
|
|
||||||
|
|
||||||
private int typeId;
|
private int typeId;
|
||||||
|
|
||||||
|
266
jme3-core/src/main/java/com/jme3/light/LightProbe.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -807,6 +807,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO HACKY HACK remove this when texture unit is handled by the uniform.
|
||||||
|
return unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
|
private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
|
||||||
@ -959,13 +961,15 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
|
|||||||
renderManager.updateUniformBindings(shader);
|
renderManager.updateUniformBindings(shader);
|
||||||
|
|
||||||
// Set material parameters
|
// Set material parameters
|
||||||
updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
|
|
||||||
|
//TODO RRemove the unit when texture units are handled in the Uniform
|
||||||
|
int unit = updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
|
||||||
|
|
||||||
// Clear any uniforms not changed by material.
|
// Clear any uniforms not changed by material.
|
||||||
resetUniformsNotSetByCurrent(shader);
|
resetUniformsNotSetByCurrent(shader);
|
||||||
|
|
||||||
// Delegate rendering to the technique
|
// Delegate rendering to the technique
|
||||||
technique.render(renderManager, shader, geometry, lights);
|
technique.render(renderManager, shader, geometry, lights, unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,9 +161,9 @@ public final class Technique {
|
|||||||
* @param geometry The geometry to render
|
* @param geometry The geometry to render
|
||||||
* @param lights Lights which influence the geometry.
|
* @param lights Lights which influence the geometry.
|
||||||
*/
|
*/
|
||||||
void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
|
void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
|
||||||
TechniqueDefLogic logic = def.getLogic();
|
TechniqueDefLogic logic = def.getLogic();
|
||||||
logic.render(renderManager, shader, geometry, lights);
|
logic.render(renderManager, shader, geometry, lights, lastTexUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,6 +89,16 @@ public class TechniqueDef implements Savable {
|
|||||||
*/
|
*/
|
||||||
MultiPass,
|
MultiPass,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable light rendering by using a single pass, and also uses Image based lighting for global lighting
|
||||||
|
* Usually used for PBR
|
||||||
|
* <p>
|
||||||
|
* An array of light positions and light colors is passed to the shader
|
||||||
|
* containing the world light list for the geometry being rendered.
|
||||||
|
* Also Light probes are passed to the shader.
|
||||||
|
*/
|
||||||
|
SinglePassAndImageBased,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated OpenGL1 is not supported anymore
|
* @deprecated OpenGL1 is not supported anymore
|
||||||
*/
|
*/
|
||||||
@ -112,6 +122,15 @@ public class TechniqueDef implements Savable {
|
|||||||
InPass,
|
InPass,
|
||||||
PostPass,
|
PostPass,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define in what space the light data should be sent to the shader.
|
||||||
|
*/
|
||||||
|
public enum LightSpace {
|
||||||
|
World,
|
||||||
|
View,
|
||||||
|
Legacy
|
||||||
|
}
|
||||||
|
|
||||||
private final EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
|
private final EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
|
||||||
private String name;
|
private String name;
|
||||||
@ -139,6 +158,8 @@ public class TechniqueDef implements Savable {
|
|||||||
private TechniqueDefLogic logic;
|
private TechniqueDefLogic logic;
|
||||||
|
|
||||||
private ArrayList<UniformBinding> worldBinds;
|
private ArrayList<UniformBinding> worldBinds;
|
||||||
|
//The space in which the light should be transposed before sending to the shader.
|
||||||
|
private LightSpace lightSpace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new technique definition.
|
* Creates a new technique definition.
|
||||||
@ -202,6 +223,14 @@ public class TechniqueDef implements Savable {
|
|||||||
*/
|
*/
|
||||||
public void setLightMode(LightMode lightMode) {
|
public void setLightMode(LightMode lightMode) {
|
||||||
this.lightMode = lightMode;
|
this.lightMode = lightMode;
|
||||||
|
//if light space is not specified we set it to Legacy
|
||||||
|
if(lightSpace == null){
|
||||||
|
if(lightMode== LightMode.MultiPass){
|
||||||
|
lightSpace = LightSpace.Legacy;
|
||||||
|
}else{
|
||||||
|
lightSpace = LightSpace.World;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLogic(TechniqueDefLogic logic) {
|
public void setLogic(TechniqueDefLogic logic) {
|
||||||
@ -714,4 +743,20 @@ public class TechniqueDef implements Savable {
|
|||||||
+ ", renderState=" + renderState
|
+ ", renderState=" + renderState
|
||||||
+ ", forcedRenderState=" + forcedRenderState + "]";
|
+ ", forcedRenderState=" + forcedRenderState + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the space in which the light data should be passed to the shader.
|
||||||
|
* @return the light space
|
||||||
|
*/
|
||||||
|
public LightSpace getLightSpace() {
|
||||||
|
return lightSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the space in which the light data should be passed to the shader.
|
||||||
|
* @param lightSpace the light space
|
||||||
|
*/
|
||||||
|
public void setLightSpace(LightSpace lightSpace) {
|
||||||
|
this.lightSpace = lightSpace;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,7 @@
|
|||||||
package com.jme3.material.logic;
|
package com.jme3.material.logic;
|
||||||
|
|
||||||
import com.jme3.asset.AssetManager;
|
import com.jme3.asset.AssetManager;
|
||||||
import com.jme3.light.AmbientLight;
|
import com.jme3.light.*;
|
||||||
import com.jme3.light.Light;
|
|
||||||
import com.jme3.light.LightList;
|
|
||||||
import com.jme3.material.TechniqueDef;
|
import com.jme3.material.TechniqueDef;
|
||||||
import com.jme3.math.ColorRGBA;
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.renderer.Caps;
|
import com.jme3.renderer.Caps;
|
||||||
@ -88,8 +86,10 @@ public class DefaultTechniqueDefLogic implements TechniqueDefLogic {
|
|||||||
return ambientLightColor;
|
return ambientLightColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
|
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
|
||||||
Renderer renderer = renderManager.getRenderer();
|
Renderer renderer = renderManager.getRenderer();
|
||||||
renderer.setShader(shader);
|
renderer.setShader(shader);
|
||||||
renderMeshFromGeometry(renderer, geometry);
|
renderMeshFromGeometry(renderer, geometry);
|
||||||
|
@ -73,7 +73,7 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
|
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
|
||||||
Renderer r = renderManager.getRenderer();
|
Renderer r = renderManager.getRenderer();
|
||||||
Uniform lightDir = shader.getUniform("g_LightDirection");
|
Uniform lightDir = shader.getUniform("g_LightDirection");
|
||||||
Uniform lightColor = shader.getUniform("g_LightColor");
|
Uniform lightColor = shader.getUniform("g_LightColor");
|
||||||
@ -156,6 +156,8 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
|
|||||||
|
|
||||||
lightDir.setValue(VarType.Vector4, tmpLightDirection);
|
lightDir.setValue(VarType.Vector4, tmpLightDirection);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case Probe:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
|
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -185,6 +185,8 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
|
|||||||
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
|
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
|
||||||
lightDataIndex++;
|
lightDataIndex++;
|
||||||
break;
|
break;
|
||||||
|
case Probe:
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
|
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
|
||||||
}
|
}
|
||||||
@ -199,7 +201,7 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
|
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
|
||||||
int nbRenderedLights = 0;
|
int nbRenderedLights = 0;
|
||||||
Renderer renderer = renderManager.getRenderer();
|
Renderer renderer = renderManager.getRenderer();
|
||||||
int batchSize = renderManager.getSinglePassLightBatchSize();
|
int batchSize = renderManager.getSinglePassLightBatchSize();
|
||||||
|
@ -171,7 +171,7 @@ public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
|
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
|
||||||
Renderer renderer = renderManager.getRenderer();
|
Renderer renderer = renderManager.getRenderer();
|
||||||
Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix();
|
Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix();
|
||||||
updateLightListUniforms(viewMatrix, shader, lights);
|
updateLightListUniforms(viewMatrix, shader, lights);
|
||||||
|
@ -93,5 +93,5 @@ public interface TechniqueDefLogic {
|
|||||||
* @param geometry The geometry to render
|
* @param geometry The geometry to render
|
||||||
* @param lights Lights which influence the geometry.
|
* @param lights Lights which influence the geometry.
|
||||||
*/
|
*/
|
||||||
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights);
|
public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit);
|
||||||
}
|
}
|
||||||
|
@ -804,6 +804,15 @@ public class RenderManager {
|
|||||||
public void setLightFilter(LightFilter lightFilter) {
|
public void setLightFilter(LightFilter lightFilter) {
|
||||||
this.lightFilter = lightFilter;
|
this.lightFilter = lightFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current LightFilter.
|
||||||
|
*
|
||||||
|
* @return the current light filter
|
||||||
|
*/
|
||||||
|
public LightFilter getLightFilter() {
|
||||||
|
return this.lightFilter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines what light mode will be selected when a technique offers several light modes.
|
* Defines what light mode will be selected when a technique offers several light modes.
|
||||||
|
@ -319,6 +319,13 @@ public class Geometry extends Spatial {
|
|||||||
worldLights.sort(true);
|
worldLights.sort(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateWorldLightList() {
|
||||||
|
super.updateWorldLightList();
|
||||||
|
// geometry requires lights to be sorted
|
||||||
|
worldLights.sort(true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associate this <code>Geometry</code> with a {@link GeometryGroupNode}.
|
* Associate this <code>Geometry</code> with a {@link GeometryGroupNode}.
|
||||||
*
|
*
|
||||||
|
@ -119,7 +119,7 @@ MaterialDef Phong Lighting {
|
|||||||
|
|
||||||
Technique {
|
Technique {
|
||||||
LightMode SinglePass
|
LightMode SinglePass
|
||||||
|
|
||||||
VertexShader GLSL100: Common/MatDefs/Light/SPLighting.vert
|
VertexShader GLSL100: Common/MatDefs/Light/SPLighting.vert
|
||||||
FragmentShader GLSL100: Common/MatDefs/Light/SPLighting.frag
|
FragmentShader GLSL100: Common/MatDefs/Light/SPLighting.frag
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -60,14 +60,16 @@ vec4 TransformWorldViewProjection(vec4 position)
|
|||||||
return g_ViewProjectionMatrix * TransformWorld(position);
|
return g_ViewProjectionMatrix * TransformWorld(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 TransformNormal(vec3 vec)
|
vec3 TransformWorldNormal(vec3 vec) {
|
||||||
{
|
|
||||||
vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
|
vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
|
||||||
inInstanceData[2].w, inInstanceData[3].w);
|
inInstanceData[2].w, inInstanceData[3].w);
|
||||||
|
|
||||||
vec3 worldNormal = vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
|
return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
|
||||||
|
}
|
||||||
return (g_ViewMatrix * vec4(worldNormal, 0.0)).xyz;
|
|
||||||
|
vec3 TransformNormal(vec3 vec)
|
||||||
|
{
|
||||||
|
return (g_ViewMatrix * vec4(TransformWorldNormal(vec), 0.0)).xyz;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent user from using g_** matrices which will have invalid data in this case.
|
// Prevent user from using g_** matrices which will have invalid data in this case.
|
||||||
@ -97,4 +99,9 @@ vec3 TransformNormal(vec3 normal) {
|
|||||||
return g_NormalMatrix * normal;
|
return g_NormalMatrix * normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec3 TransformWorldNormal(vec3 normal) {
|
||||||
|
return normalize((g_WorldMatrix * vec4(normal,0.0)).xyz);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
154
jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
jme3-core/src/main/resources/Common/Textures/integrateBRDF.ktx
Normal file
@ -31,9 +31,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.jme3.material.plugins;
|
package com.jme3.material.plugins;
|
||||||
|
|
||||||
import com.jme3.material.logic.MultiPassLightingLogic;
|
import com.jme3.material.logic.*;
|
||||||
import com.jme3.material.logic.SinglePassLightingLogic;
|
|
||||||
import com.jme3.material.logic.DefaultTechniqueDefLogic;
|
|
||||||
import com.jme3.asset.*;
|
import com.jme3.asset.*;
|
||||||
import com.jme3.material.*;
|
import com.jme3.material.*;
|
||||||
import com.jme3.material.RenderState.BlendEquation;
|
import com.jme3.material.RenderState.BlendEquation;
|
||||||
@ -121,9 +119,21 @@ public class J3MLoader implements AssetLoader {
|
|||||||
if (split.length != 2){
|
if (split.length != 2){
|
||||||
throw new IOException("LightMode statement syntax incorrect");
|
throw new IOException("LightMode statement syntax incorrect");
|
||||||
}
|
}
|
||||||
|
|
||||||
LightMode lm = LightMode.valueOf(split[1]);
|
LightMode lm = LightMode.valueOf(split[1]);
|
||||||
technique.setLightMode(lm);
|
technique.setLightMode(lm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// LightMode <SPACE>
|
||||||
|
private void readLightSpace(String statement) throws IOException{
|
||||||
|
String[] split = statement.split(whitespacePattern);
|
||||||
|
if (split.length != 2){
|
||||||
|
throw new IOException("LightSpace statement syntax incorrect");
|
||||||
|
}
|
||||||
|
TechniqueDef.LightSpace ls = TechniqueDef.LightSpace.valueOf(split[1]);
|
||||||
|
technique.setLightSpace(ls);
|
||||||
|
}
|
||||||
|
|
||||||
// ShadowMode <MODE>
|
// ShadowMode <MODE>
|
||||||
private void readShadowMode(String statement) throws IOException{
|
private void readShadowMode(String statement) throws IOException{
|
||||||
@ -543,6 +553,8 @@ public class J3MLoader implements AssetLoader {
|
|||||||
readShaderStatement(statement.getLine());
|
readShaderStatement(statement.getLine());
|
||||||
}else if (split[0].equals("LightMode")){
|
}else if (split[0].equals("LightMode")){
|
||||||
readLightMode(statement.getLine());
|
readLightMode(statement.getLine());
|
||||||
|
}else if (split[0].equals("LightSpace")){
|
||||||
|
readLightSpace(statement.getLine());
|
||||||
}else if (split[0].equals("ShadowMode")){
|
}else if (split[0].equals("ShadowMode")){
|
||||||
readShadowMode(statement.getLine());
|
readShadowMode(statement.getLine());
|
||||||
}else if (split[0].equals("WorldParameters")){
|
}else if (split[0].equals("WorldParameters")){
|
||||||
@ -650,6 +662,9 @@ public class J3MLoader implements AssetLoader {
|
|||||||
case StaticPass:
|
case StaticPass:
|
||||||
technique.setLogic(new StaticPassLightingLogic(technique));
|
technique.setLogic(new StaticPassLightingLogic(technique));
|
||||||
break;
|
break;
|
||||||
|
case SinglePassAndImageBased:
|
||||||
|
technique.setLogic(new SinglePassAndImageBasedLightingLogic(technique));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
@ -296,6 +296,12 @@ public class DDSLoader implements AssetLoader {
|
|||||||
// exit here, the rest of the structure is not valid
|
// exit here, the rest of the structure is not valid
|
||||||
// the real format will be available in the DX10 header
|
// the real format will be available in the DX10 header
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case 113:
|
||||||
|
compressed = false;
|
||||||
|
bpp = 64;
|
||||||
|
pixelFormat = Image.Format.RGBA16F;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc));
|
throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc));
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -60,7 +60,7 @@ public class TestBatchNodeCluster extends SimpleApplication {
|
|||||||
settingst.setVSync(false);
|
settingst.setVSync(false);
|
||||||
settingst.setFullscreen(false);
|
settingst.setFullscreen(false);
|
||||||
app.setSettings(settingst);
|
app.setSettings(settingst);
|
||||||
app.setShowSettings(false);
|
app.setShowSettings(false);
|
||||||
app.start();
|
app.start();
|
||||||
}
|
}
|
||||||
private ActionListener al = new ActionListener() {
|
private ActionListener al = new ActionListener() {
|
||||||
|
90
jme3-examples/src/main/java/jme3test/light/TestColorApp.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
131
jme3-examples/src/main/java/jme3test/light/TestShadowBug.java
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
141
jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
387
jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java
Normal file
@ -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 |
BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Base_Color.png
Normal file
After Width: | Height: | Size: 3.7 MiB |
BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Emissive.png
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Metallic.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Normal.png
Normal file
After Width: | Height: | Size: 12 MiB |
BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Roughness.png
Normal file
After Width: | Height: | Size: 1.7 MiB |
14
jme3-testdata/src/main/resources/Models/Tank/tank.j3m
Normal file
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
BIN
jme3-testdata/src/main/resources/Models/Tank/tank.j3o
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#
|
||||||
|
#Sat Apr 11 15:27:27 CEST 2015
|
||||||
|
ORIGINAL_PATH=Models/Tank/tank.obj
|
BIN
jme3-testdata/src/main/resources/Scenes/PBR/spheres.j3o
Normal file
BIN
jme3-testdata/src/main/resources/Textures/Sky/Path.hdr
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
BIN
jme3-testdata/src/main/resources/Textures/ktx/down-reference.ktx
Normal file
BIN
jme3-testdata/src/main/resources/Textures/ktx/irrMap.ktx
Normal file
BIN
jme3-testdata/src/main/resources/Textures/ktx/prefilteredMap.ktx
Normal file
BIN
jme3-testdata/src/main/resources/Textures/ktx/rgba-reference.ktx
Normal file
BIN
jme3-testdata/src/main/resources/Textures/ktx/up-reference.ktx
Normal file
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|