From 653746f018b80201f2c1f2c3ffea7c35c8d9ccdf Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Sun, 10 Aug 2025 09:23:01 +0200 Subject: [PATCH 1/2] KHR pbr neutral tonemap --- .../resources/Common/ShaderLib/Hdr.glsllib | 26 ++++ .../jme3/post/filters/KHRToneMapFilter.java | 131 ++++++++++++++++++ .../Common/MatDefs/Post/KHRToneMap.frag | 43 ++++++ .../Common/MatDefs/Post/KHRToneMap.j3md | 24 ++++ 4 files changed, 224 insertions(+) create mode 100644 jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java create mode 100644 jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag create mode 100644 jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.j3md diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib index 5db1423ef5..9688ad71ec 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib @@ -62,4 +62,30 @@ vec3 HDR_ToneMap(in vec3 color, in float lumAvg, in float a, in float white){ vec3 HDR_ToneMap2(in vec3 color, in float lumAvg, in float a, in float white){ float scale = a / (lumAvg + 0.001); return (vec3(scale) * color) / (color + vec3(1.0)); +} + +// Based on https://github.com/KhronosGroup/ToneMapping/blob/main/PBR_Neutral/pbrNeutral.glsl +// Input color is non-negative and resides in the Linear Rec. 709 color space. +// Output color is also Linear Rec. 709, but in the [0, 1] range. +vec3 HDR_KHRToneMap(in vec3 color, in vec3 exposure, in vec3 gamma) { + color *= pow(vec3(2.0), exposure); + + const float startCompression = 0.8 - 0.04; + const float desaturation = 0.15; + + float x = min(color.r, min(color.g, color.b)); + float offset = x < 0.08 ? x - 6.25 * x * x : 0.04; + color -= offset; + + float peak = max(color.r, max(color.g, color.b)); + if (peak < startCompression) return color; + + const float d = 1. - startCompression; + float newPeak = 1. - d * d / (peak + d - startCompression); + color *= newPeak / peak; + + float g = 1. - 1. / (desaturation * (peak - newPeak) + 1.); + color = mix(color, newPeak * vec3(1, 1, 1), g); + color = pow(color, gamma); + return color; } \ No newline at end of file diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java new file mode 100644 index 0000000000..0a57d79ec7 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java @@ -0,0 +1,131 @@ +/* + * 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 khronos neutral pbr tone mapping curve. + */ +public class KHRToneMapFilter extends Filter { + + private static final float DEFAULT_EXPOSURE = 0.0f; + private static final float DEFAULT_GAMMA = 1.0f; + + private final Vector3f exposure = new Vector3f(DEFAULT_EXPOSURE, DEFAULT_EXPOSURE, DEFAULT_EXPOSURE); + private final Vector3f gamma = new Vector3f(DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA); + + /** + * Creates a tone-mapping filter with the default white-point. + */ + public KHRToneMapFilter() { + super("KHRToneMapFilter"); + } + + + @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/KHRToneMap.j3md"); + material.setVector3("Exposure", exposure); + material.setVector3("Gamma", gamma); + } + + @Override + protected Material getMaterial() { + return material; + } + + /** + * Set the exposure for the tone mapping. + */ + public void setExposure(Vector3f whitePoint) { + this.exposure.set(whitePoint); + } + + /** + * Get the exposure for the tone mapping. + * + * @return The exposure vector. + */ + public Vector3f getExposure() { + return exposure; + } + + + /** + * Set the gamma for the tone mapping. + */ + public void setGamma(Vector3f gamma) { + this.gamma.set(gamma); + } + + /** + * Get the gamma for the tone mapping. + * + * @return The gamma vector. + */ + public Vector3f getGamma() { + return gamma; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(exposure, "exposure", new Vector3f(DEFAULT_EXPOSURE, DEFAULT_EXPOSURE, DEFAULT_EXPOSURE)); + oc.write(gamma, "gamma", new Vector3f(DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA)); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + exposure.set((Vector3f)ic.readSavable("exposure", new Vector3f(DEFAULT_EXPOSURE, DEFAULT_EXPOSURE, DEFAULT_EXPOSURE))); + gamma.set((Vector3f)ic.readSavable("gamma", new Vector3f(DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA))); + } + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag new file mode 100644 index 0000000000..ba09268efa --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag @@ -0,0 +1,43 @@ +#extension GL_ARB_texture_multisample : enable +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Hdr.glsllib" + + +uniform vec3 m_Exposure; +uniform vec3 m_Gamma; +varying vec2 texCoord; + +vec3 applyCurve(in vec3 x) { + return HDR_KHRToneMap(x, m_Exposure, m_Gamma); +} + + +#ifdef NUM_SAMPLES + +uniform sampler2DMS m_Texture; + +vec4 applyToneMap() { + ivec2 iTexC = ivec2(texCoord * vec2(textureSize(m_Texture))); + vec4 color = vec4(0.0); + for (int i = 0; i < NUM_SAMPLES; i++) { + vec4 hdrColor = texelFetch(m_Texture, iTexC, i); + vec3 ldrColor = applyCurve(hdrColor.rgb); + color += vec4(ldrColor, hdrColor.a); + } + return color / float(NUM_SAMPLES); +} + +#else + +uniform sampler2D m_Texture; + +vec4 applyToneMap() { + vec4 texVal = texture2D(m_Texture, texCoord); + return vec4(applyCurve(texVal.rgb) , texVal.a); +} + +#endif + +void main() { + gl_FragColor = applyToneMap(); +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.j3md new file mode 100644 index 0000000000..90b81d075e --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.j3md @@ -0,0 +1,24 @@ +MaterialDef KHRToneMap { + + MaterialParameters { + Int BoundDrawBuffer + Int NumSamples + Int NumSamplesDepth + Texture2D Texture + Vector3 Gamma + Vector3 Exposure + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/KHRToneMap.frag + + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + NUM_SAMPLES : NumSamples + } + } +} \ No newline at end of file From bbbb43482b3a118c9b404715b358d36bf3e685d0 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Sun, 10 Aug 2025 10:03:17 +0200 Subject: [PATCH 2/2] fix javadoc --- .../main/java/com/jme3/post/filters/KHRToneMapFilter.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java index 0a57d79ec7..a93add502d 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java @@ -55,7 +55,7 @@ public class KHRToneMapFilter extends Filter { private final Vector3f gamma = new Vector3f(DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA); /** - * Creates a tone-mapping filter with the default white-point. + * Creates a tone-mapping filter with the default exposure and gamma. */ public KHRToneMapFilter() { super("KHRToneMapFilter"); @@ -81,6 +81,8 @@ protected Material getMaterial() { /** * Set the exposure for the tone mapping. + * + * @param whitePoint The exposure vector. */ public void setExposure(Vector3f whitePoint) { this.exposure.set(whitePoint); @@ -98,6 +100,8 @@ public Vector3f getExposure() { /** * Set the gamma for the tone mapping. + * + * @param gamma The gamma vector. */ public void setGamma(Vector3f gamma) { this.gamma.set(gamma);