diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java new file mode 100644 index 000000000..66fcce0be --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.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 jobs = new ArrayList(); + + /** + * 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 done) { + getApplication().enqueue(new Callable() { + + @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 callback; + Spatial scene; + + public SnapshotJob(JobProgressListener callback, Spatial scene) { + this.callback = callback; + this.scene = scene; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java b/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java new file mode 100644 index 000000000..0c03b7659 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java @@ -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 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() { + + @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 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() { + + @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 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 { + + JobProgressListener globalListener; + JobState jobState; + LightProbe probe; + + int index; + + public JobListener(JobProgressListener 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(); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java b/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java new file mode 100644 index 000000000..356d53b03 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.environment.generation; + +import com.jme3.environment.util.CubeMapWrapper; +import com.jme3.environment.util.EnvMapUtils; +import com.jme3.app.Application; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.texture.TextureCubeMap; +import static com.jme3.environment.util.EnvMapUtils.shBandFactor; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.concurrent.Callable; + +/** + * + * Generates the Irrafiance map for PBR. This job can be lauched from a separate + * thread. + * + * TODO there is a lot of duplicate code here with the EnvMapUtils. + * + * @author Nehon + */ +//TODO there is a lot of duplicate code here with the EnvMapUtils. We should, +//either leverage the code from the util class either remove it and only allow +//parallel generation using this runnable. +public class IrradianceMapGenerator extends RunnableWithProgress { + + private int targetMapSize; + private EnvMapUtils.FixSeamsMethod fixSeamsMethod; + private TextureCubeMap sourceMap; + private TextureCubeMap store; + private final Application app; + + /** + * Creates an Irradiance map generator. The app is needed to enqueue the + * call to the EnvironmentCamera when the generation is done, so that this + * process is thread safe. + * + * @param app the Application + * @param listener + */ + public IrradianceMapGenerator(Application app, JobProgressListener listener) { + super(listener); + this.app = app; + } + + /** + * Fills all the genration parameters + * + * @param sourceMap the source cube map + * @param targetMapSize the size of the generated map (width or height in + * pixel) + * @param fixSeamsMethod the method used to fix seams as described here + * {@link EnvMapUtils.FixSeamsMethod} + * + * @param store The cube map to store the result in. + */ + public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) { + this.sourceMap = sourceMap; + this.targetMapSize = targetMapSize; + this.fixSeamsMethod = fixSeamsMethod; + this.store = store; + reset(); + } + + @Override + public void run() { + app.enqueue(new Callable() { + + @Override + public Void call() throws Exception { + listener.start(); + return null; + } + }); + try { + Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap); + store = generateIrradianceMap(shCoeffs, targetMapSize, fixSeamsMethod, store); + } catch (Exception e) { + e.printStackTrace(); + } + app.enqueue(new Callable() { + + @Override + public Void call() throws Exception { + listener.done(6); + return null; + } + }); + } + + /** + * Generates the Irradiance map (used for image based difuse lighting) from + * Spherical Harmonics coefficients previously computed with + * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)} + * + * @param shCoeffs the SH coeffs + * @param targetMapSize the size of the irradiance map to generate + * @param fixSeamsMethod the method to fix seams + * @param store + * @return The irradiance cube map for the given coefficients + */ + public TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) { + TextureCubeMap irrCubeMap = store; + + setEnd(6 + 6); + for (int i = 0; i < 6; i++) { + ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * store.getImage().getFormat().getBitsPerPixel() / 8); + irrCubeMap.getImage().setData(i, buf); + progress(); + } + + Vector3f texelVect = new Vector3f(); + ColorRGBA color = new ColorRGBA(ColorRGBA.Black); + float[] shDir = new float[9]; + CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap); + for (int face = 0; face < 6; face++) { + + for (int y = 0; y < targetMapSize; y++) { + for (int x = 0; x < targetMapSize; x++) { + EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod); + EnvMapUtils.evalShBasis(texelVect, shDir); + color.set(0, 0, 0, 0); + for (int i = 0; i < EnvMapUtils.NUM_SH_COEFFICIENT; i++) { + color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i], + color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i], + color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i], + 1.0f); + } + + //clamping the color because very low value close to zero produce artifacts + color.r = Math.max(0.0001f, color.r); + color.g = Math.max(0.0001f, color.g); + color.b = Math.max(0.0001f, color.b); + + envMapWriter.setPixel(x, y, face, color); + + } + } + progress(); + } + return irrCubeMap; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java b/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java new file mode 100644 index 000000000..6b19d6b70 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java @@ -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 + */ +public abstract class JobProgressAdapter implements JobProgressListener{ + + @Override + public void progress(double value) { + } + + @Override + public void start() { + } + + @Override + public void step(String message) { + } + + @Override + public abstract void done(T result); + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java b/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java new file mode 100644 index 000000000..6c5b93d7c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java @@ -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 The type of object generated. + */ +public interface JobProgressListener { + + /** + * 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); + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java b/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java new file mode 100644 index 000000000..c5501a329 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java @@ -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 listener) { + super(listener); + this.app = app; + this.face = face; + } + + + + /** + * Fills all the genration parameters + * + * @param sourceMap the source cube map + * @param targetMapSize the size of the generated map (width or height in + * pixel) + * @param fixSeamsMethod the method used to fix seams as described here + * {@link EnvMapUtils.FixSeamsMethod} + * + * @param store The cube map to store the result in. + */ + public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) { + this.sourceMap = sourceMap; + this.targetMapSize = targetMapSize; + this.fixSeamsMethod = fixSeamsMethod; + this.store = store; + init(); + } + + private void init(){ + Xi.set(0, 0, 0, 0); + H.set(0, 0, 0); + tmp.set(0, 0, 0); + c.set(1, 1, 1, 1); + tmp1.set(0, 0, 0); + tmp2.set(0, 0, 0); + tmp3.set(0, 0, 0); + reset(); + + } + + @Override + public void run() { + + app.enqueue(new Callable() { + + @Override + public Void call() throws Exception { + listener.start(); + return null; + } + }); + store = generatePrefilteredEnvMap(sourceMap, targetMapSize, fixSeamsMethod, store); + app.enqueue(new Callable() { + + @Override + public Void call() throws Exception { + listener.done(face); + return null; + } + }); + } + + /** + * Generates the prefiltered env map (used for image based specular + * lighting) With the GGX/Shlick brdf + * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)} + * Note that the output cube map is in RGBA8 format. + * + * @param sourceEnvMap + * @param targetMapSize the size of the irradiance map to generate + * @param store + * @param fixSeamsMethod the method to fix seams + * @return The irradiance cube map for the given coefficients + */ + private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) { + TextureCubeMap pem = store; + + int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1); + + setEnd(nbMipMap); + + + CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap); + CubeMapWrapper targetWrapper = new CubeMapWrapper(pem); + + Vector3f texelVect = new Vector3f(); + Vector3f color = new Vector3f(); + ColorRGBA outColor = new ColorRGBA(); + for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) { + float roughness = getRoughnessFromMip(mipLevel, nbMipMap); + int nbSamples = getSampleFromMip(mipLevel, nbMipMap); + int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel); + + for (int y = 0; y < targetMipMapSize; y++) { + for (int x = 0; x < targetMipMapSize; x++) { + color.set(0, 0, 0); + getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, EnvMapUtils.FixSeamsMethod.Wrap); + prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color); + + outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y,0.0001f), Math.max(color.z, 0.0001f), 1); + log.log(Level.FINE, "coords {0},{1}", new Object[]{x, y}); + targetWrapper.setPixel(x, y, face, mipLevel, outColor); + + } + } + progress(); + } + + return pem; + } + + private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) { + + Vector3f prefilteredColor = store; + float totalWeight = 0.0f; + + // a = roughness² and a2 = a² + float a2 = roughness * roughness; + a2 *= a2; + a2 *= 10; + for (int i = 0; i < numSamples; i++) { + Xi = getHammersleyPoint(i, numSamples, Xi); + H = importanceSampleGGX(Xi, a2, N, H); + + H.normalizeLocal(); + tmp.set(H); + float NoH = N.dot(tmp); + + Vector3f L = tmp.multLocal(NoH * 2).subtractLocal(N); + float NoL = clamp(N.dot(L), 0.0f, 1.0f); + if (NoL > 0) { + envMapReader.getPixel(L, c); + prefilteredColor.setX(prefilteredColor.x + c.r * NoL); + prefilteredColor.setY(prefilteredColor.y + c.g * NoL); + prefilteredColor.setZ(prefilteredColor.z + c.b * NoL); + + totalWeight += NoL; + } + } + + return prefilteredColor.divideLocal(totalWeight); + } + + public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) { + if (store == null) { + store = new Vector3f(); + } + + float cosTheta = sqrt((1f - xi.x) / (1f + (a2 - 1f) * xi.x)); + float sinTheta = sqrt(1f - cosTheta * cosTheta); + + float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi) + float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi) + + Vector3f upVector = Vector3f.UNIT_X; + + if (abs(normal.z) < 0.999) { + upVector = Vector3f.UNIT_Y; + } + + Vector3f tangentX = tmp1.set(upVector).crossLocal(normal).normalizeLocal(); + Vector3f tangentY = tmp2.set(normal).crossLocal(tangentX); + + // Tangent to world space + tangentX.multLocal(sinThetaCosPhi); + tangentY.multLocal(sinThetaSinPhi); + tmp3.set(normal).multLocal(cosTheta); + + // Tangent to world space + store.set(tangentX).addLocal(tangentY).addLocal(tmp3); + + return store; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java b/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java new file mode 100644 index 000000000..09b495e67 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.environment.generation; + +/** + * + * Abstract runnable that can report its progress + * + * @author Nehon + */ +public abstract class RunnableWithProgress implements Runnable { + + private int progress; + private int end; + protected JobProgressListener listener; + + public RunnableWithProgress() { + } + + public RunnableWithProgress(JobProgressListener listener) { + this.listener = listener; + } + + + /** + * set the end step value of the process. + * + * @param end + */ + protected void setEnd(int end) { + this.end = end; + } + + /** + * return the curent progress of the process. + * + * @return + */ + public double getProgress() { + return (double) progress / (double) end; + } + + /** + * adds one progression step to the process. + */ + protected void progress() { + progress++; + if (listener != null) { + listener.progress(getProgress()); + } + } + + /** + * resets the progression of the process. + */ + protected void reset() { + progress = 0; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java b/jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java new file mode 100644 index 000000000..8cb56536c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java @@ -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; + + } +} diff --git a/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java b/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java new file mode 100644 index 000000000..cdf66a6d1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.environment.util; + +import com.jme3.environment.util.EnvMapUtils; +import com.jme3.math.ColorRGBA; +import static com.jme3.math.FastMath.pow; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.texture.Image; +import com.jme3.texture.TextureCubeMap; +import com.jme3.texture.image.DefaultImageRaster; +import com.jme3.texture.image.MipMapImageRaster; +import com.jme3.util.BufferUtils; + +/** + * Wraps a Cube map and allows to read from or write pixels into it. + * + * It uses the ImageRaster class to tailor the read write operations. + * + * @author Nehon + */ +public class CubeMapWrapper { + + private MipMapImageRaster mipMapRaster; + private final DefaultImageRaster raster; + private int[] sizes; + private final Vector2f uvs = new Vector2f(); + private final Image image; + + /** + * Creates a CubeMapWrapper for the given cube map + * Note that the cube map must be initialized, and the mipmaps sizes should + * be set if relevant for them to be readable/writable + * @param cubeMap the cubemap to wrap. + */ + public CubeMapWrapper(TextureCubeMap cubeMap) { + image = cubeMap.getImage(); + if (image.hasMipmaps()) { + int nbMipMaps = image.getMipMapSizes().length; + sizes = new int[nbMipMaps]; + mipMapRaster = new MipMapImageRaster(image, 0); + + for (int i = 0; i < nbMipMaps; i++) { + sizes[i] = Math.max(1, image.getWidth() >> i); + } + } else { + sizes = new int[1]; + sizes[0] = image.getWidth(); + } + raster = new DefaultImageRaster(image, 0,0 , false); + } + + /** + * Reads a pixel from the cube map given the coordinate vector + * @param vector the direction vector to fetch the texel + * @param store the color in which to store the pixel color read. + * @return the color of the pixel read. + */ + public ColorRGBA getPixel(Vector3f vector, ColorRGBA store) { + + if (store == null) { + store = new ColorRGBA(); + } + + int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch); + raster.setSlice(face); + return raster.getPixel((int) uvs.x, (int) uvs.y, store); + } + + /** + * + * Reads a pixel from the cube map given the coordinate vector + * @param vector the direction vector to fetch the texel + * @param mipLevel the mip level to read from + * @param store the color in which to store the pixel color read. + * @return the color of the pixel read. + */ + public ColorRGBA getPixel(Vector3f vector, int mipLevel, ColorRGBA store) { + if (mipMapRaster == null) { + throw new IllegalArgumentException("This cube map has no mip maps"); + } + if (store == null) { + store = new ColorRGBA(); + } + + int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch); + mipMapRaster.setSlice(face); + mipMapRaster.setMipLevel(mipLevel); + return mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store); + } + + /** + * Reads a pixel from the cube map given the 2D coordinates and the face to read from + * @param x the x tex coordinate (from 0 to width) + * @param y the y tex coordinate (from 0 to height) + * @param face the face to read from + * @param store the color where the result is stored. + * @return the color read. + */ + public ColorRGBA getPixel(int x, int y, int face, ColorRGBA store) { + if (store == null) { + store = new ColorRGBA(); + } + raster.setSlice(face); + return raster.getPixel((int) x, (int) y, store); + } + + /** + * Reads a pixel from the cube map given the 2D coordinates and the face and + * the mip level to read from + * @param x the x tex coordinate (from 0 to width) + * @param y the y tex coordinate (from 0 to height) + * @param face the face to read from + * @param mipLevel the miplevel to read from + * @param store the color where the result is stored. + * @return the color read. + */ + public ColorRGBA getPixel(int x, int y, int face, int mipLevel, ColorRGBA store) { + if (mipMapRaster == null) { + throw new IllegalArgumentException("This cube map has no mip maps"); + } + if (store == null) { + store = new ColorRGBA(); + } + mipMapRaster.setSlice(face); + mipMapRaster.setMipLevel(mipLevel); + return mipMapRaster.getPixel((int) x, (int) y, store); + } + + /** + * writes a pixel given the coordinates vector and the color. + * @param vector the cooredinates where to write the pixel + * @param color the color to write + */ + public void setPixel(Vector3f vector, ColorRGBA color) { + + int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch); + raster.setSlice(face); + raster.setPixel((int) uvs.x, (int) uvs.y, color); + } + /** + * writes a pixel given the coordinates vector, the mip level and the color. + * @param vector the cooredinates where to write the pixel + * @param mipLevel the miplevel to write to + * @param color the color to write + */ + public void setPixel(Vector3f vector, int mipLevel, ColorRGBA color) { + if (mipMapRaster == null) { + throw new IllegalArgumentException("This cube map has no mip maps"); + } + int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch); + mipMapRaster.setSlice(face); + mipMapRaster.setMipLevel(mipLevel); + mipMapRaster.setPixel((int) uvs.x, (int) uvs.y, color); + } + + /** + * Writes a pixel given the 2D cordinates and the color + * @param x the x tex coord (from 0 to width) + * @param y the y tex coord (from 0 to height) + * @param face the face to write to + * @param color the color to write + */ + public void setPixel(int x, int y, int face, ColorRGBA color) { + raster.setSlice(face); + raster.setPixel((int) x, (int) y, color); + } + + /** + * Writes a pixel given the 2D cordinates, the mip level and the color + * @param x the x tex coord (from 0 to width) + * @param y the y tex coord (from 0 to height) + * @param face the face to write to + * @param mipLevel the mip level to write to + * @param color the color to write + */ + public void setPixel(int x, int y, int face, int mipLevel, ColorRGBA color) { + if (mipMapRaster == null) { + throw new IllegalArgumentException("This cube map has no mip maps"); + } + + mipMapRaster.setSlice(face); + mipMapRaster.setMipLevel(mipLevel); + mipMapRaster.setPixel((int) x, (int) y, color); + } + + /** + * Inits the mip maps of a cube map witht he given number of mip maps + * @param nbMipMaps the number of mip maps to initialize + */ + public void initMipMaps(int nbMipMaps) { + int maxMipMap = (int) (Math.log(image.getWidth()) / Math.log(2) + 1); + if (nbMipMaps > maxMipMap) { + throw new IllegalArgumentException("Max mip map number for a " + image.getWidth() + "x" + image.getHeight() + " cube map is " + maxMipMap); + } + + sizes = new int[nbMipMaps]; + + int totalSize = 0; + for (int i = 0; i < nbMipMaps; i++) { + int size = (int) pow(2, maxMipMap - 1 - i); + sizes[i] = size * size * image.getFormat().getBitsPerPixel() / 8; + totalSize += sizes[i]; + } + + image.setMipMapSizes(sizes); + image.getData().clear(); + for (int i = 0; i < 6; i++) { + image.addData(BufferUtils.createByteBuffer(totalSize)); + } + mipMapRaster = new MipMapImageRaster(image, 0); + } +} diff --git a/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java new file mode 100644 index 000000000..9474cb257 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java @@ -0,0 +1,947 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.environment.util; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.texture.TextureCubeMap; +import com.jme3.texture.image.ColorSpace; +import com.jme3.ui.Picture; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import static com.jme3.math.FastMath.*; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.util.TempVars; + +/** + * + * This class holds several utility method unseful for Physically Based + * Rendering. It alloaws to compute useful pre filtered maps from an env map. + * + * @author Nehon + */ +public class EnvMapUtils { + + public final static int NUM_SH_COEFFICIENT = 9; + // See Peter-Pike Sloan paper for these coefficients + //http://www.ppsloan.org/publications/StupidSH36.pdf + public static float[] shBandFactor = {1.0f, + 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f, + 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f}; + + public static enum FixSeamsMethod { + + /** + * wrap texture coordinates + */ + Wrap, + /** + * stretch texture coordinates + */ + Stretch, + /** + * No seams fix + */ + None; + } + + /** + * Creates a cube map from 6 images + * + * @param leftImg the west side image, also called negative x (negX) or left + * image + * @param rightImg the east side image, also called positive x (posX) or + * right image + * @param downImg the bottom side image, also called negative y (negY) or + * down image + * @param upImg the up side image, also called positive y (posY) or up image + * @param backImg the south side image, also called positive z (posZ) or + * back image + * @param frontImg the north side image, also called negative z (negZ) or + * front image + * @param format the format of the image + * @return a cube map + */ + public static TextureCubeMap makeCubeMap(Image rightImg, Image leftImg, Image upImg, Image downImg, Image backImg, Image frontImg, Image.Format format) { + Image cubeImage = new Image(format, leftImg.getWidth(), leftImg.getHeight(), null, ColorSpace.Linear); + + cubeImage.addData(rightImg.getData(0)); + cubeImage.addData(leftImg.getData(0)); + + cubeImage.addData(upImg.getData(0)); + cubeImage.addData(downImg.getData(0)); + + cubeImage.addData(backImg.getData(0)); + cubeImage.addData(frontImg.getData(0)); + + if (leftImg.getEfficentData() != null) { + // also consilidate efficient data + ArrayList efficientData = new ArrayList(6); + efficientData.add(rightImg.getEfficentData()); + efficientData.add(leftImg.getEfficentData()); + efficientData.add(upImg.getEfficentData()); + efficientData.add(downImg.getEfficentData()); + efficientData.add(backImg.getEfficentData()); + efficientData.add(frontImg.getEfficentData()); + cubeImage.setEfficentData(efficientData); + } + + TextureCubeMap cubeMap = new TextureCubeMap(cubeImage); + cubeMap.setAnisotropicFilter(0); + cubeMap.setMagFilter(Texture.MagFilter.Bilinear); + cubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + cubeMap.setWrap(Texture.WrapMode.EdgeClamp); + + return cubeMap; + } + + /** + * Make a duplicate of this cube Map. That means that it's another instant + * od TextureCubeMap, but the underlying buffers are duplicates of the + * original ones. see {@link ByteBuffer#duplicate()} + * + * Use this if you need to read from the map from multiple threads, it + * should garanty the thread safety. Note that if you want to write to the + * cube map you have to make sure that the different thread do not write to + * the same area of the buffer. The position, limit and mark are not an + * issue. + * + * @param sourceMap + * @return + */ + public static TextureCubeMap duplicateCubeMap(TextureCubeMap sourceMap) { + Image srcImg = sourceMap.getImage(); + Image cubeImage = new Image(srcImg.getFormat(), srcImg.getWidth(), srcImg.getHeight(), null, srcImg.getColorSpace()); + + for (ByteBuffer d : srcImg.getData()) { + cubeImage.addData(d.duplicate()); + } + + if (srcImg.getEfficentData() != null) { + // also consilidate efficient data + ArrayList efficientData = new ArrayList(6); + efficientData.add(srcImg.getEfficentData()); + cubeImage.setEfficentData(efficientData); + } + + TextureCubeMap cubeMap = new TextureCubeMap(cubeImage); + cubeMap.setAnisotropicFilter(sourceMap.getAnisotropicFilter()); + cubeMap.setMagFilter(sourceMap.getMagFilter()); + cubeMap.setMinFilter(sourceMap.getMinFilter()); + cubeMap.setWrap(sourceMap.getWrap(Texture.WrapAxis.S)); + + return cubeMap; + } + + /** + * Computes the vector coordinates, for the given x,y texture coordinates + * and the given cube map face. + * + * Also computes the solid angle for those coordinates and returns it. + * + * To know what the solid angle is please read this. + * http://www.codinglabs.net/article_physically_based_rendering.aspx + * + * + * Original solid angle calculation code is from Ignacio Castaño. This + * formula is from Manne Öhrström's thesis. It takes two coordiantes in the + * range [-1, 1] that define a portion of a cube face and return the area of + * the projection of that portion on the surface of the sphere. + * + * @param x texture coordinate from 0 to 1 in the given cube map face + * @param y texture coordinate from 0 to 1 in the given cube map face + * @param mapSize the size of the cube map + * @param face the face id of the cube map + * @param store the vector3f where the vector will be stored. don't provide + * null for this param + * @return the solid angle for the give parameters + */ + static float getSolidAngleAndVector(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) { + + if (store == null) { + throw new IllegalArgumentException("the store parameter ust not be null"); + } + + /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)] + (+ 0.5f is for texel center addressing) */ + float u = (2.0f * ((float) x + 0.5f) / (float) mapSize) - 1.0f; + float v = (2.0f * ((float) y + 0.5f) / (float) mapSize) - 1.0f; + + getVectorFromCubemapFaceTexCoord(x, y, mapSize, face, store, fixSeamsMethod); + + /* Solid angle weight approximation : + * U and V are the -1..1 texture coordinate on the current face. + * Get projected area for this texel */ + float x0, y0, x1, y1; + float invRes = 1.0f / (float) mapSize; + x0 = u - invRes; + y0 = v - invRes; + x1 = u + invRes; + y1 = v + invRes; + + return areaElement(x0, y0) - areaElement(x0, y1) - areaElement(x1, y0) + areaElement(x1, y1); + } + + /** + * used to compute the solid angle + * + * @param x tex coordinates + * @param y tex coordinates + * @return + */ + private static float areaElement(float x, float y) { + return (float) Math.atan2(x * y, sqrt(x * x + y * y + 1)); + } + + /** + * + * Computes the 3 component vector coordinates for the given face and coords + * + * @param x the x texture coordinate + * @param y the y texture coordinate + * @param mapSize the size of a face of the cube map + * @param face the face to consider + * @param store a vector3f where the resulting vector will be stored + * @param fixSeamsMethod the method to fix the seams + * @return + */ + public static Vector3f getVectorFromCubemapFaceTexCoord(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) { + if (store == null) { + store = new Vector3f(); + } + + float u; + float v; + + if (fixSeamsMethod == FixSeamsMethod.Stretch) { + /* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp + * transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */ + u = (2.0f * (float) x / ((float) mapSize - 1.0f)) - 1.0f; + v = (2.0f * (float) y / ((float) mapSize - 1.0f)) - 1.0f; + } else { + //Done if any other fix method or no fix method is set + /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)] + * (+ 0.5f is for texel center addressing) */ + u = (2.0f * ((float) x + 0.5f) / (float) (mapSize)) - 1.0f; + v = (2.0f * ((float) y + 0.5f) / (float) (mapSize)) - 1.0f; + } + + if (fixSeamsMethod == FixSeamsMethod.Wrap) { + // Warp texel centers in the proximity of the edges. + float a = pow((float) mapSize, 2.0f) / pow(((float) mapSize - 1f), 3.0f); + u = a * pow(u, 3f) + u; + v = a * pow(v, 3f) + v; + } + + //compute vector depending on the face + // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp + switch (face) { + case 0: + store.set(1f, -v, -u); + break; + case 1: + store.set(-1f, -v, u); + break; + case 2: + store.set(u, 1f, v); + break; + case 3: + store.set(u, -1f, -v); + break; + case 4: + store.set(u, -v, 1f); + break; + case 5: + store.set(-u, -v, -1.0f); + break; + } + + return store.normalizeLocal(); + } + + /** + * + * Computes the texture coortinates and the face of the cube map from the + * given vector + * + * @param texelVect the vector to fetch texelt from the cube map + * @param fixSeamsMethod the method to fix the seams + * @param mapSize the size of one face of the cube map + * @param store a Vector2f where the texture coordinates will be stored + * @return the face from which to fetch the texel + */ + public static int getCubemapFaceTexCoordFromVector(Vector3f texelVect, int mapSize, Vector2f store, FixSeamsMethod fixSeamsMethod) { + + float u = 0, v = 0, bias = 0; + int face; + float absX = abs(texelVect.x); + float absY = abs(texelVect.y); + float absZ = abs(texelVect.z); + float max = Math.max(Math.max(absX, absY), absZ); + if (max == absX) { + face = texelVect.x > 0 ? 0 : 1; + } else if (max == absY) { + face = texelVect.y > 0 ? 2 : 3; + } else { + face = texelVect.z > 0 ? 4 : 5; + } + + //compute vector depending on the face + // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp + switch (face) { + case 0: + //store.set(1f, -v, -u, 0); + bias = 1f / texelVect.x; + u = -texelVect.z; + v = -texelVect.y; + break; + case 1: + // store.set(-1f, -v, u, 0); + bias = -1f / texelVect.x; + u = texelVect.z; + v = -texelVect.y; + break; + case 2: + //store.set(u, 1f, v, 0); + bias = 1f / texelVect.y; + u = texelVect.x; + v = texelVect.z; + break; + case 3: + //store.set(u, -1f, -v, 0); + bias = -1f / texelVect.y; + u = texelVect.x; + v = -texelVect.z; + break; + case 4: + //store.set(u, -v, 1f, 0); + bias = 1f / texelVect.z; + u = texelVect.x; + v = -texelVect.y; + break; + case 5: + //store.set(-u, -v, -1.0f, 0); + bias = -1f / texelVect.z; + u = -texelVect.x; + v = -texelVect.y; + break; + } + u *= bias; + v *= bias; + + if (fixSeamsMethod == FixSeamsMethod.Stretch) { + /* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp + * transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */ + u = Math.round((u + 1.0f) * ((float) mapSize - 1.0f) * 0.5f); + v = Math.round((v + 1.0f) * ((float) mapSize - 1.0f) * 0.5f); + } else { + //Done if any other fix method or no fix method is set + /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)] + * (+ 0.5f is for texel center addressing) */ + u = Math.round((u + 1.0f) * ((float) mapSize) * 0.5f - 0.5f); + v = Math.round((v + 1.0f) * ((float) mapSize) * 0.5f - 0.5f); + + } + + store.set(u, v); + return face; + } + + /* + public static void main(String... argv) { + +// for (int givenFace = 0; givenFace < 6; givenFace++) { +// +// //int givenFace = 1; +// for (int x = 0; x < 128; x++) { +// for (int y = 0; y < 128; y++) { +// Vector3f v = EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, 128, givenFace, null, FixSeamsMethod.None); +// Vector2f uvs = new Vector2f(); +// int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(v, 128, uvs, FixSeamsMethod.None); +// +// if ((int) uvs.x != x || (int) uvs.y != y) { +// System.err.println("error " + uvs + " should be " + x + "," + y + " vect was " + v); +// } +// if (givenFace != face) { +// System.err.println("error face: " + face + " should be " + givenFace); +// } +// } +// } +// } +// System.err.println("done "); + int total = 0; + for (int i = 0; i < 6; i++) { + int size = (int) pow(2, 7 - i); + int samples = EnvMapUtils.getSampleFromMip(i, 6); + int iterations = (samples * size * size); + total += iterations; + float roughness = EnvMapUtils.getRoughnessFromMip(i, 6); + System.err.println("roughness " + i + " : " + roughness + " , map : " + size + " , samples : " + samples + " , iterations : " + iterations); + System.err.println("reverse " + EnvMapUtils.getMipFromRoughness(roughness, 6)); + + } + System.err.println("total " + total); + System.err.println(128 * 128 * 1024); + System.err.println("test " + EnvMapUtils.getMipFromRoughness(0.9999f, 6)); + System.err.println("nb mip = " + (Math.log(128) / Math.log(2) - 1)); + + }*/ + + public static int getSampleFromMip(int mipLevel, int miptot) { + return 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 : + *
+     *         _____
+     *        |     |
+     *        | +Y  |
+     *   _____|_____|_____ _____
+     *  |     |     |     |     |
+     *  | -X  | +Z  | +X  | -Z  |
+     *  |_____|_____|_____|_____|
+     *        |     |
+     *        | -Y  |
+     *        |_____|
+     *
+     *
+ * + * @param cubeMap the cube map + * @param assetManager the asset Manager + * @return + */ + public static Node getCubeMapCrossDebugView(TextureCubeMap cubeMap, AssetManager assetManager) { + Node n = new Node("CubeMapDebug" + cubeMap.getName()); + int size = cubeMap.getImage().getWidth(); + Picture[] pics = new Picture[6]; + + float ratio = 128f / (float) size; + + for (int i = 0; i < 6; i++) { + pics[i] = new Picture("bla"); + Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, cubeMap.getImage().getData(i), cubeMap.getImage().getColorSpace())); + + pics[i].setTexture(assetManager, tex, true); + pics[i].setWidth(size); + pics[i].setHeight(size); + n.attachChild(pics[i]); + } + + pics[0].setLocalTranslation(size, size * 2, 1); + pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[1].setLocalTranslation(size * 3, size * 2, 1); + pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[2].setLocalTranslation(size * 2, size * 3, 1); + pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[3].setLocalTranslation(size * 2, size, 1); + pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[4].setLocalTranslation(size * 2, size * 2, 1); + pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[5].setLocalTranslation(size * 4, size * 2, 1); + pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + + Quad q = new Quad(size * 4, size * 3); + Geometry g = new Geometry("bg", q); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Black); + g.setMaterial(mat); + g.setLocalTranslation(0, 0, 0); + + n.attachChild(g); + n.setLocalScale(ratio); + return n; + } + + public static Node getCubeMapCrossDebugViewWithMipMaps(TextureCubeMap cubeMap, AssetManager assetManager) { + Node n = new Node("CubeMapDebug" + cubeMap.getName()); + int size = cubeMap.getImage().getWidth(); + int nbMips = cubeMap.getImage().getMipMapSizes().length; + Picture[] pics = new Picture[6*nbMips]; + + float ratio = 1f;// 128f / (float) size; + + int offset = 0; + int guiOffset = 0; + for (int mipLevel = 0; mipLevel < nbMips; mipLevel++) { + size = Math.max(1, cubeMap.getImage().getWidth() >> mipLevel); + int dataSize = cubeMap.getImage().getMipMapSizes()[mipLevel]; + byte[] dataArray = new byte[dataSize]; + for (int i = 0; i < 6; i++) { + + ByteBuffer bb = cubeMap.getImage().getData(i); + + bb.rewind(); + bb.position(offset); + bb.get(dataArray, 0, dataSize); + ByteBuffer data = BufferUtils.createByteBuffer(dataArray); + + pics[i] = new Picture("bla"); + Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, data, cubeMap.getImage().getColorSpace())); + + pics[i].setTexture(assetManager, tex, true); + pics[i].setWidth(size); + pics[i].setHeight(size); + n.attachChild(pics[i]); + } + pics[0].setLocalTranslation(guiOffset + size, guiOffset + size * 2, 1); + pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[1].setLocalTranslation(guiOffset + size * 3, guiOffset + size * 2, 1); + pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[2].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 3, 1); + pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[3].setLocalTranslation(guiOffset + size * 2, guiOffset + size, 1); + pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[4].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 2, 1); + pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + pics[5].setLocalTranslation(guiOffset + size * 4, guiOffset + size * 2, 1); + pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z)); + + guiOffset+=size *2+1; + offset += dataSize; + + } + + Quad q = new Quad(cubeMap.getImage().getWidth() * 4 + nbMips, guiOffset + size); + Geometry g = new Geometry("bg", q); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Black); + g.setMaterial(mat); + g.setLocalTranslation(0, 0, 0); + + n.attachChild(g); + n.setLocalScale(ratio); + return n; + } + + /** + * initialize the Irradiancemap + * @param size the size of the map + * @param imageFormat the format of the image + * @return the initialized Irradiance map + */ + public static TextureCubeMap createIrradianceMap(int size, Image.Format imageFormat) { + + TextureCubeMap irrMap = new TextureCubeMap(size, size, imageFormat); + irrMap.setMagFilter(Texture.MagFilter.Bilinear); + irrMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + irrMap.getImage().setColorSpace(ColorSpace.Linear); + return irrMap; + } + + /** + * initialize the pem map + * @param size the size of the map + * @param imageFormat the format of the image + * @return the initialized prefiltered env map + */ + public static TextureCubeMap createPrefilteredEnvMap(int size, Image.Format imageFormat) { + + TextureCubeMap pem = new TextureCubeMap(size, size, imageFormat); + pem.setMagFilter(Texture.MagFilter.Bilinear); + pem.setMinFilter(Texture.MinFilter.Trilinear); + pem.getImage().setColorSpace(ColorSpace.Linear); + int nbMipMap = (int) (Math.log(size) / Math.log(2) - 1); + CubeMapWrapper targetWrapper = new CubeMapWrapper(pem); + targetWrapper.initMipMaps(nbMipMap); + return pem; + } +} diff --git a/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java b/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java new file mode 100644 index 000000000..962fe85b4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java @@ -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 probeMapping = new HashMap(); + private final List garbage = new ArrayList(); + 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 probes = new ArrayList(); + + /** + * 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() { + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java b/jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java new file mode 100644 index 000000000..e4c16728a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java @@ -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 lightProbes = new ArrayList(); + + @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(); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java index cbd89dc8c..84cdc17fc 100644 --- a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java +++ b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java @@ -43,6 +43,15 @@ public final class DefaultLightFilter implements LightFilter { private Camera camera; private final HashSet processedLights = new HashSet(); + private final LightProbeBlendingStrategy probeBlendStrat; + + public DefaultLightFilter() { + probeBlendStrat = new BasicProbeBlendingStrategy(); + } + + public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) { + this.probeBlendStrat = probeBlendStrat; + } @Override public void setCamera(Camera camera) { @@ -57,6 +66,7 @@ public final class DefaultLightFilter implements LightFilter { TempVars vars = TempVars.get(); try { LightList worldLights = geometry.getWorldLightList(); + for (int i = 0; i < worldLights.size(); 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 { vars.release(); } diff --git a/jme3-core/src/main/java/com/jme3/light/Light.java b/jme3-core/src/main/java/com/jme3/light/Light.java index 9836c7db0..0a66fb729 100644 --- a/jme3-core/src/main/java/com/jme3/light/Light.java +++ b/jme3-core/src/main/java/com/jme3/light/Light.java @@ -78,7 +78,14 @@ public abstract class Light implements Savable, Cloneable { * * @see AmbientLight */ - Ambient(3); + Ambient(3), + + /** + * Light probe + * @see LightProbe + */ + Probe(4); + private int typeId; diff --git a/jme3-core/src/main/java/com/jme3/light/LightProbe.java b/jme3-core/src/main/java/com/jme3/light/LightProbe.java new file mode 100644 index 000000000..66c07342b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/LightProbe.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.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); + } + + + +} diff --git a/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java b/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java new file mode 100644 index 000000000..9e1ec344b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java @@ -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 blendFactors = new ArrayList(); + 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 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{ + + 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; + } + + } +} diff --git a/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java b/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java new file mode 100644 index 000000000..a3a6ac516 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java @@ -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); +} diff --git a/jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java b/jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java new file mode 100644 index 000000000..b991036ae --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java @@ -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 processedLights = new HashSet(); + 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(); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index b8d002932..34e827988 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -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) { @@ -959,13 +961,15 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { renderManager.updateUniformBindings(shader); // 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. resetUniformsNotSetByCurrent(shader); // Delegate rendering to the technique - technique.render(renderManager, shader, geometry, lights); + technique.render(renderManager, shader, geometry, lights, unit); } /** diff --git a/jme3-core/src/main/java/com/jme3/material/Technique.java b/jme3-core/src/main/java/com/jme3/material/Technique.java index 7128747c9..3ae32814e 100644 --- a/jme3-core/src/main/java/com/jme3/material/Technique.java +++ b/jme3-core/src/main/java/com/jme3/material/Technique.java @@ -161,9 +161,9 @@ public final class Technique { * @param geometry The geometry to render * @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(); - logic.render(renderManager, shader, geometry, lights); + logic.render(renderManager, shader, geometry, lights, lastTexUnit); } /** diff --git a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java index 2f9256a80..9ad4ece48 100644 --- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -89,6 +89,16 @@ public class TechniqueDef implements Savable { */ MultiPass, + /** + * Enable light rendering by using a single pass, and also uses Image based lighting for global lighting + * Usually used for PBR + *

+ * 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 */ @@ -112,6 +122,15 @@ public class TechniqueDef implements Savable { InPass, PostPass, } + + /** + * Define in what space the light data should be sent to the shader. + */ + public enum LightSpace { + World, + View, + Legacy + } private final EnumSet requiredCaps = EnumSet.noneOf(Caps.class); private String name; @@ -139,6 +158,8 @@ public class TechniqueDef implements Savable { private TechniqueDefLogic logic; private ArrayList worldBinds; + //The space in which the light should be transposed before sending to the shader. + private LightSpace lightSpace; /** * Creates a new technique definition. @@ -202,6 +223,14 @@ public class TechniqueDef implements Savable { */ public void setLightMode(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) { @@ -714,4 +743,20 @@ public class TechniqueDef implements Savable { + ", renderState=" + renderState + ", 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; + } } diff --git a/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java index ffe72cc00..86ce66391 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java @@ -32,9 +32,7 @@ package com.jme3.material.logic; import com.jme3.asset.AssetManager; -import com.jme3.light.AmbientLight; -import com.jme3.light.Light; -import com.jme3.light.LightList; +import com.jme3.light.*; import com.jme3.material.TechniqueDef; import com.jme3.math.ColorRGBA; import com.jme3.renderer.Caps; @@ -88,8 +86,10 @@ public class DefaultTechniqueDefLogic implements TechniqueDefLogic { return ambientLightColor; } + + @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.setShader(shader); renderMeshFromGeometry(renderer, geometry); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java index 7b5e2f4a5..61e9f26cb 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java @@ -73,7 +73,7 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic { } @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(); Uniform lightDir = shader.getUniform("g_LightDirection"); Uniform lightColor = shader.getUniform("g_LightColor"); @@ -156,6 +156,8 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic { lightDir.setValue(VarType.Vector4, tmpLightDirection); + break; + case Probe: break; default: throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java new file mode 100644 index 000000000..7edf2cba9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java @@ -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 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.

* + *

+ * uniform vec4 g_LightColor[numLights];
// + * g_LightColor.rgb is the diffuse/specular color of the light.
// + * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
// + * 2 = Spot.

+ * uniform vec4 g_LightPosition[numLights];
// + * g_LightPosition.xyz is the position of the light (for point lights)
+ * // or the direction of the light (for directional lights).
// + * g_LightPosition.w is the inverse radius (1/r) of the light (for + * attenuation)

+ */ + 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; + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java index 2b31c7869..015d6b1da 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java @@ -185,6 +185,8 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic { lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); lightDataIndex++; break; + case Probe: + break; default: throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); } @@ -199,7 +201,7 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic { } @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; Renderer renderer = renderManager.getRenderer(); int batchSize = renderManager.getSinglePassLightBatchSize(); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java index 4193b118b..48995ba4f 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java @@ -171,7 +171,7 @@ public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic { } @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(); Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix(); updateLightListUniforms(viewMatrix, shader, lights); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java index 95ab8ccf3..18a805186 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java @@ -93,5 +93,5 @@ public interface TechniqueDefLogic { * @param geometry The geometry to render * @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); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index b07b95092..5d8265fcb 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -804,6 +804,15 @@ public class RenderManager { public void setLightFilter(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. diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java index f403e9c62..fe65adf74 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -319,6 +319,13 @@ public class Geometry extends Spatial { worldLights.sort(true); } + @Override + protected void updateWorldLightList() { + super.updateWorldLightList(); + // geometry requires lights to be sorted + worldLights.sort(true); + } + /** * Associate this Geometry with a {@link GeometryGroupNode}. * diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md index 4d5c36046..32076a815 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -119,7 +119,7 @@ MaterialDef Phong Lighting { Technique { LightMode SinglePass - + VertexShader GLSL100: Common/MatDefs/Light/SPLighting.vert FragmentShader GLSL100: Common/MatDefs/Light/SPLighting.frag diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag new file mode 100644 index 000000000..648769c71 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.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; + + +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md new file mode 100644 index 000000000..dd76c09fe --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md @@ -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 + } + } + +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert new file mode 100644 index 000000000..04e787dcc --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert @@ -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 +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/reflect.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/reflect.j3md new file mode 100644 index 000000000..1b3b94a1f --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/reflect.j3md @@ -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 + } + } + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn new file mode 100644 index 000000000..148c88f1c --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn @@ -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 + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping100.frag new file mode 100644 index 000000000..302461a5c --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping100.frag @@ -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); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping130.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping130.frag new file mode 100644 index 000000000..4b44923b6 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping130.frag @@ -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); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn new file mode 100644 index 000000000..3177743b5 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn @@ -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 + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect100.vert b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect100.vert new file mode 100644 index 000000000..2f0253c4d --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect100.vert @@ -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); + +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib index 7ed91181d..5c1f82698 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib @@ -60,14 +60,16 @@ vec4 TransformWorldViewProjection(vec4 position) return g_ViewProjectionMatrix * TransformWorld(position); } -vec3 TransformNormal(vec3 vec) -{ +vec3 TransformWorldNormal(vec3 vec) { vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].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 (g_ViewMatrix * vec4(worldNormal, 0.0)).xyz; + return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.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. @@ -97,4 +99,9 @@ vec3 TransformNormal(vec3 normal) { return g_NormalMatrix * normal; } +vec3 TransformWorldNormal(vec3 normal) { + return normalize((g_WorldMatrix * vec4(normal,0.0)).xyz); +} + + #endif \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib new file mode 100644 index 000000000..bad4f630a --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib @@ -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); +} + + + + + diff --git a/jme3-core/src/main/resources/Common/Textures/integrateBRDF.ktx b/jme3-core/src/main/resources/Common/Textures/integrateBRDF.ktx new file mode 100644 index 000000000..2fc2e4690 Binary files /dev/null and b/jme3-core/src/main/resources/Common/Textures/integrateBRDF.ktx differ diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index 1f07ccfc0..b77934078 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java @@ -31,9 +31,7 @@ */ package com.jme3.material.plugins; -import com.jme3.material.logic.MultiPassLightingLogic; -import com.jme3.material.logic.SinglePassLightingLogic; -import com.jme3.material.logic.DefaultTechniqueDefLogic; +import com.jme3.material.logic.*; import com.jme3.asset.*; import com.jme3.material.*; import com.jme3.material.RenderState.BlendEquation; @@ -121,9 +119,21 @@ public class J3MLoader implements AssetLoader { if (split.length != 2){ throw new IOException("LightMode statement syntax incorrect"); } + LightMode lm = LightMode.valueOf(split[1]); technique.setLightMode(lm); } + + + // LightMode + 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 private void readShadowMode(String statement) throws IOException{ @@ -543,6 +553,8 @@ public class J3MLoader implements AssetLoader { readShaderStatement(statement.getLine()); }else if (split[0].equals("LightMode")){ readLightMode(statement.getLine()); + }else if (split[0].equals("LightSpace")){ + readLightSpace(statement.getLine()); }else if (split[0].equals("ShadowMode")){ readShadowMode(statement.getLine()); }else if (split[0].equals("WorldParameters")){ @@ -650,6 +662,9 @@ public class J3MLoader implements AssetLoader { case StaticPass: technique.setLogic(new StaticPassLightingLogic(technique)); break; + case SinglePassAndImageBased: + technique.setLogic(new SinglePassAndImageBasedLightingLogic(technique)); + break; default: throw new UnsupportedOperationException(); } diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java index e7ef0bcd9..245591c05 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java @@ -296,6 +296,12 @@ public class DDSLoader implements AssetLoader { // exit here, the rest of the structure is not valid // the real format will be available in the DX10 header return; + + case 113: + compressed = false; + bpp = 64; + pixelFormat = Image.Format.RGBA16F; + break; default: throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc)); } diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java new file mode 100644 index 000000000..376774a0d --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java @@ -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 imageData = new ArrayList(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 = 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; + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java new file mode 100644 index 000000000..fbbdc8972 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java @@ -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 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 = 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); + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/PixelReader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/PixelReader.java new file mode 100644 index 000000000..61eac655c --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/PixelReader.java @@ -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; +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTdRiPixelReader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTdRiPixelReader.java new file mode 100644 index 000000000..50397ff7d --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTdRiPixelReader.java @@ -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; + } + +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTuRoPixelReader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTuRoPixelReader.java new file mode 100644 index 000000000..a1f7751f9 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTuRoPixelReader.java @@ -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; + } + +} diff --git a/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java index 3d88fb408..9430aa368 100644 --- a/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java +++ b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java @@ -60,7 +60,7 @@ public class TestBatchNodeCluster extends SimpleApplication { settingst.setVSync(false); settingst.setFullscreen(false); app.setSettings(settingst); - app.setShowSettings(false); + app.setShowSettings(false); app.start(); } private ActionListener al = new ActionListener() { diff --git a/jme3-examples/src/main/java/jme3test/light/TestColorApp.java b/jme3-examples/src/main/java/jme3test/light/TestColorApp.java new file mode 100644 index 000000000..03bef0275 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestColorApp.java @@ -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; + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/light/TestShadowBug.java b/jme3-examples/src/main/java/jme3test/light/TestShadowBug.java new file mode 100644 index 000000000..cecb53224 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestShadowBug.java @@ -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; + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentCube.java b/jme3-examples/src/main/java/jme3test/light/TestTangentCube.java new file mode 100644 index 000000000..c7c3b6716 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentCube.java @@ -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); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java b/jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java new file mode 100644 index 000000000..f0d100ee2 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java @@ -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{ + + 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)); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java b/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java new file mode 100644 index 000000000..b613bc1f4 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java @@ -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() { + + @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); + + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java new file mode 100644 index 000000000..75fac22c1 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java @@ -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() { + + @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); + } + } + +} + diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java new file mode 100644 index 000000000..e1754f6fa --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java @@ -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); + + } + + } + +} diff --git a/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java b/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java new file mode 100644 index 000000000..15bf25882 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java @@ -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); + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java b/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java new file mode 100644 index 000000000..0514aced5 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java @@ -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"); + + } + +} diff --git a/jme3-examples/src/main/java/jme3test/texture/ktx/TestLoadKtx.java b/jme3-examples/src/main/java/jme3test/texture/ktx/TestLoadKtx.java new file mode 100644 index 000000000..62330bea4 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/ktx/TestLoadKtx.java @@ -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 + } +} diff --git a/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat.j3m b/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat.j3m new file mode 100644 index 000000000..22e50b6ff --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat.j3m @@ -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 { + } +} diff --git a/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat2.j3m b/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat2.j3m new file mode 100644 index 000000000..2414bf358 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat2.j3m @@ -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 { + } +} diff --git a/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat3.j3m b/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat3.j3m new file mode 100644 index 000000000..bdf52aa65 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat3.j3m @@ -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 { + } +} diff --git a/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat4.j3m b/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat4.j3m new file mode 100644 index 000000000..892c9f8bc --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat4.j3m @@ -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 { + } +} diff --git a/jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefDE.png b/jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefDE.png new file mode 100644 index 000000000..ba041cfc3 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefDE.png differ diff --git a/jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefM.png b/jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefM.png new file mode 100644 index 000000000..dadd04d94 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefM.png differ diff --git a/jme3-testdata/src/main/resources/Models/Tank/Tank_Base_Color.png b/jme3-testdata/src/main/resources/Models/Tank/Tank_Base_Color.png new file mode 100644 index 000000000..f8e9fa50a Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tank/Tank_Base_Color.png differ diff --git a/jme3-testdata/src/main/resources/Models/Tank/Tank_Emissive.png b/jme3-testdata/src/main/resources/Models/Tank/Tank_Emissive.png new file mode 100644 index 000000000..b1be607c7 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tank/Tank_Emissive.png differ diff --git a/jme3-testdata/src/main/resources/Models/Tank/Tank_Metallic.png b/jme3-testdata/src/main/resources/Models/Tank/Tank_Metallic.png new file mode 100644 index 000000000..afe233143 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tank/Tank_Metallic.png differ diff --git a/jme3-testdata/src/main/resources/Models/Tank/Tank_Normal.png b/jme3-testdata/src/main/resources/Models/Tank/Tank_Normal.png new file mode 100644 index 000000000..20bb5f64a Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tank/Tank_Normal.png differ diff --git a/jme3-testdata/src/main/resources/Models/Tank/Tank_Roughness.png b/jme3-testdata/src/main/resources/Models/Tank/Tank_Roughness.png new file mode 100644 index 000000000..41f6c0a73 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tank/Tank_Roughness.png differ diff --git a/jme3-testdata/src/main/resources/Models/Tank/tank.j3m b/jme3-testdata/src/main/resources/Models/Tank/tank.j3m new file mode 100644 index 000000000..0bd16814e --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Tank/tank.j3m @@ -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 { + } +} diff --git a/jme3-testdata/src/main/resources/Models/Tank/tank.j3o b/jme3-testdata/src/main/resources/Models/Tank/tank.j3o new file mode 100644 index 000000000..a0d007426 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Tank/tank.j3o differ diff --git a/jme3-testdata/src/main/resources/Models/Tank/tank.j3odata b/jme3-testdata/src/main/resources/Models/Tank/tank.j3odata new file mode 100644 index 000000000..9e0f13099 --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Tank/tank.j3odata @@ -0,0 +1,3 @@ +# +#Sat Apr 11 15:27:27 CEST 2015 +ORIGINAL_PATH=Models/Tank/tank.obj diff --git a/jme3-testdata/src/main/resources/Scenes/PBR/spheres.j3o b/jme3-testdata/src/main/resources/Scenes/PBR/spheres.j3o new file mode 100644 index 000000000..0ad11e36c Binary files /dev/null and b/jme3-testdata/src/main/resources/Scenes/PBR/spheres.j3o differ diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Path.hdr b/jme3-testdata/src/main/resources/Textures/Sky/Path.hdr new file mode 100644 index 000000000..69b878681 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Path.hdr differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR.j3m new file mode 100644 index 000000000..9ef47dd99 --- /dev/null +++ b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR.j3m @@ -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 + } +} \ No newline at end of file diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR2.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR2.j3m new file mode 100644 index 000000000..0eed99a0d --- /dev/null +++ b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR2.j3m @@ -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 + } +} \ No newline at end of file diff --git a/jme3-testdata/src/main/resources/Textures/ktx/down-reference.ktx b/jme3-testdata/src/main/resources/Textures/ktx/down-reference.ktx new file mode 100644 index 000000000..7ebee9111 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ktx/down-reference.ktx differ diff --git a/jme3-testdata/src/main/resources/Textures/ktx/irrMap.ktx b/jme3-testdata/src/main/resources/Textures/ktx/irrMap.ktx new file mode 100644 index 000000000..4779a6200 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ktx/irrMap.ktx differ diff --git a/jme3-testdata/src/main/resources/Textures/ktx/prefilteredMap.ktx b/jme3-testdata/src/main/resources/Textures/ktx/prefilteredMap.ktx new file mode 100644 index 000000000..a39405c39 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ktx/prefilteredMap.ktx differ diff --git a/jme3-testdata/src/main/resources/Textures/ktx/rgb-amg-reference.ktx b/jme3-testdata/src/main/resources/Textures/ktx/rgb-amg-reference.ktx new file mode 100644 index 000000000..aa8587f5b Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ktx/rgb-amg-reference.ktx differ diff --git a/jme3-testdata/src/main/resources/Textures/ktx/rgb-mipmap-reference.ktx b/jme3-testdata/src/main/resources/Textures/ktx/rgb-mipmap-reference.ktx new file mode 100644 index 000000000..77ef3b709 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ktx/rgb-mipmap-reference.ktx differ diff --git a/jme3-testdata/src/main/resources/Textures/ktx/rgba-reference.ktx b/jme3-testdata/src/main/resources/Textures/ktx/rgba-reference.ktx new file mode 100644 index 000000000..bfdce6cf5 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ktx/rgba-reference.ktx differ diff --git a/jme3-testdata/src/main/resources/Textures/ktx/up-reference.ktx b/jme3-testdata/src/main/resources/Textures/ktx/up-reference.ktx new file mode 100644 index 000000000..0166a4d60 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/ktx/up-reference.ktx differ diff --git a/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbe.java b/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbe.java new file mode 100644 index 000000000..b7f86e3e4 --- /dev/null +++ b/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbe.java @@ -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("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 { + } + }; + } +} diff --git a/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeButtonProperty.java b/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeButtonProperty.java new file mode 100644 index 000000000..ade61b4c6 --- /dev/null +++ b/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeButtonProperty.java @@ -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() { + + @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); + } +} diff --git a/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeProgressHandler.java b/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeProgressHandler.java new file mode 100644 index 000000000..af7b539cb --- /dev/null +++ b/sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeProgressHandler.java @@ -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 { + + 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(); + } + +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoControl.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoControl.java new file mode 100644 index 000000000..01b19fb8d --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoControl.java @@ -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); + } + } + + } + +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoFactory.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoFactory.java new file mode 100644 index 000000000..1d4ec1237 --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoFactory.java @@ -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; + } + +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightProbeGizmoControl.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightProbeGizmoControl.java new file mode 100644 index 000000000..ecdabec12 --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightProbeGizmoControl.java @@ -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) { + + } + +} diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed.j3sn b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed.j3sn new file mode 100644 index 000000000..57f32a3e5 --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed.j3sn @@ -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 + } + } +} \ No newline at end of file diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed100.frag b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed100.frag new file mode 100644 index 000000000..09b9caa80 --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed100.frag @@ -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); + +} \ No newline at end of file diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/dashed.j3md b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/dashed.j3md new file mode 100644 index 000000000..f675e8bf0 --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/dashed.j3md @@ -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 + } + } + } + } +} \ No newline at end of file diff --git a/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/shape/ProbeRadiusShape.java b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/shape/ProbeRadiusShape.java new file mode 100644 index 000000000..b052c4952 --- /dev/null +++ b/sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/shape/ProbeRadiusShape.java @@ -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; + + } +}