diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/ToneMapFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/ToneMapFilter.java new file mode 100644 index 000000000..e107f5137 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/ToneMapFilter.java @@ -0,0 +1,125 @@ +/* + * 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 com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * Tone-mapping filter that uses filmic curve. + * + * @author Kirill Vainer + */ +public class ToneMapFilter extends Filter { + + private static final Vector3f DEFAULT_WHITEPOINT = new Vector3f(11.2f, 11.2f, 11.2f); + + private Vector3f whitePoint = DEFAULT_WHITEPOINT.clone(); + + /** + * Creates a tone-mapping filter with the default white-point of 11.2. + */ + public ToneMapFilter() { + super("ToneMapFilter"); + } + + /** + * Creates a tone-mapping filter with the specified white-point. + * + * @param whitePoint The intensity of the brightest part of the scene. + */ + public ToneMapFilter(Vector3f whitePoint) { + this(); + this.whitePoint = whitePoint.clone(); + } + + @Override + protected boolean isRequiresDepthTexture() { + return false; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/ToneMap.j3md"); + material.setVector3("WhitePoint", whitePoint); + } + + @Override + protected Material getMaterial() { + return material; + } + + /** + * Set the scene white point. + * + * @param whitePoint The intensity of the brightest part of the scene. + */ + public void setWhitePoint(Vector3f whitePoint) { + if (material != null) { + material.setVector3("WhitePoint", whitePoint); + } + this.whitePoint = whitePoint; + } + + /** + * Get the scene white point. + * + * @return The intensity of the brightest part of the scene. + */ + public Vector3f getWhitePoint() { + return whitePoint; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(whitePoint, "whitePoint", DEFAULT_WHITEPOINT.clone()); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + whitePoint = (Vector3f) ic.readSavable("whitePoint", DEFAULT_WHITEPOINT.clone()); + } + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.frag new file mode 100644 index 000000000..98ec5c772 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.frag @@ -0,0 +1,38 @@ +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform vec3 m_WhitePoint; + +#if __VERSION__ >= 150 +in vec2 texCoord; +#else +varying vec2 texCoord; +#endif + +vec3 FilmicCurve(in vec3 x) +{ + const float A = 0.22; + const float B = 0.30; + const float C = 0.10; + const float D = 0.20; + const float E = 0.01; + const float F = 0.30; + + return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F; +} + +// whitePoint should be 11.2 + +vec3 ToneMap_Filmic(vec3 color, vec3 whitePoint) +{ + return FilmicCurve(color) / FilmicCurve(whitePoint); +} + +void main() { + // TODO: This is incorrect if multi-sampling is used. + // The tone-mapping should be performed for each sample independently. + + vec4 texVal = getColor(m_Texture, texCoord); + vec3 toneMapped = ToneMap_Filmic(texVal.rgb, m_WhitePoint); + gl_FragColor = vec4(toneMapped, texVal.a); +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.j3md new file mode 100644 index 000000000..d98364130 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.j3md @@ -0,0 +1,31 @@ +MaterialDef Default GUI { + + MaterialParameters { + Int NumSamples + Int NumSamplesDepth + Texture2D Texture + Vector3 WhitePoint + } + + Technique { + VertexShader GLSL150: Common/MatDefs/Post/Post15.vert + FragmentShader GLSL150: Common/MatDefs/Post/ToneMap.frag + + WorldParameters { + } + + Defines { + RESOLVE_MS : NumSamples + } + + } + + Technique { + VertexShader GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL100: Common/MatDefs/Post/ToneMap.frag + + WorldParameters { + } + + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/post/TestToneMapFilter.java b/jme3-examples/src/main/java/jme3test/post/TestToneMapFilter.java new file mode 100644 index 000000000..127e600c2 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestToneMapFilter.java @@ -0,0 +1,141 @@ +/* + * 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.post; + +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.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.HDRRenderer; +import com.jme3.post.filters.ColorOverlayFilter; +import com.jme3.post.filters.RadialBlurFilter; +import com.jme3.post.filters.ToneMapFilter; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.ui.Picture; + +public class TestToneMapFilter extends SimpleApplication { + + private boolean enabled = true; + private FilterPostProcessor fpp; + private ToneMapFilter toneMapFilter; + + public static void main(String[] args){ + TestToneMapFilter app = new TestToneMapFilter(); + AppSettings settings = new AppSettings(true); + + // Must turn on gamma correction, as otherwise it looks too dark. + settings.setGammaCorrection(true); + + app.setSettings(settings); + app.start(); + } + + public Geometry createHDRBox(){ + Box boxMesh = new Box(1, 1, 1); + Geometry box = new Geometry("Box", boxMesh); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Textures/HdrTest/Memorial.hdr")); + box.setMaterial(mat); + return box; + } + + @Override + public void simpleInitApp() { + fpp = new FilterPostProcessor(assetManager); + toneMapFilter = new ToneMapFilter(); + fpp.addFilter(toneMapFilter); + viewPort.addProcessor(fpp); + + rootNode.attachChild(createHDRBox()); + + cam.setLocation(new Vector3f(0f,0f,3f)); + + initInputs(); + } + + private void initInputs() { + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("WhitePointUp", new KeyTrigger(KeyInput.KEY_Y)); + inputManager.addMapping("WhitePointDown", new KeyTrigger(KeyInput.KEY_H)); + + ActionListener acl = new ActionListener() { + + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("toggle") && keyPressed) { + if (enabled) { + enabled = false; + viewPort.removeProcessor(fpp); + } else { + enabled = true; + viewPort.addProcessor(fpp); + } + } + } + }; + + AnalogListener anl = new AnalogListener() { + + public void onAnalog(String name, float isPressed, float tpf) { + float wp = toneMapFilter.getWhitePoint().x; + + if (name.equals("WhitePointUp")) { + wp += tpf * 1.0; + if (wp > 12f) { + wp = 12f; + } + toneMapFilter.setWhitePoint(new Vector3f(wp, wp, wp)); + System.out.println("White point: " + wp); + } + + if (name.equals("WhitePointDown")) { + wp -= tpf * 1.0; + if (wp < 0.01f) { + wp = 0.01f; + } + toneMapFilter.setWhitePoint(new Vector3f(wp, wp, wp)); + System.out.println("White point: " + wp); + } + } + }; + + inputManager.addListener(acl, "toggle"); + inputManager.addListener(anl, "WhitePointUp", "WhitePointDown"); + } +}