diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java index 344a69b216..55c2db1e08 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java @@ -38,13 +38,13 @@ /** * Wraps an array of Tween actions into an action object. - * *

* Notes : - *

  • The sequence of tweens is determined by {@link com.jme3.anim.tween.Tweens} utility class and the {@link BaseAction} interpolates that sequence.
  • - *
  • This implementation mimics the {@link com.jme3.anim.tween.AbstractTween}, but it delegates the interpolation method {@link Tween#interpolate(double)} + * * * Created by Nehon. * @@ -66,10 +66,11 @@ public abstract class Action implements JmeCloneable, Tween { * Instantiates an action object that wraps a tween actions array by extracting their actions to the collection {@link Action#actions}. *

    * Notes : - *

  • If intentions are to wrap some tween actions, then subclasses have to call this constructor, examples : {@link BlendableAction} and {@link BlendAction}.
  • - *
  • If intentions are to make an implementation of {@link Action} that shouldn't wrap tweens of actions, then subclasses shouldn't call this + * * * @param tweens the tween actions to be wrapped (not null). */ @@ -119,11 +120,12 @@ public double getSpeed() { * Alters the speedup factor applied by the layer running this action. *

    * Notes: - *

  • This factor controls the animation direction, if the speed is a positive value then the animation will run forward and vice versa.
  • - *
  • The speed factor gets applied, inside the {@link com.jme3.anim.AnimLayer}, on each interpolation step by this formula : time += tpf * action.getSpeed() * composer.globalSpeed.
  • - *
  • Default speed is 1.0, it plays the animation clips at their normal speed.
  • - *
  • Setting the speed factor to Zero will stop the animation, while setting it to a negative number will play the animation in a backward fashion.
  • - *

    + * * * @param speed the speed of frames. */ diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java index edc819f7ff..928c168bc0 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java @@ -55,7 +55,6 @@ * //run the action within this layer * animComposer.setCurrentAction("basicAction", ActionState.class.getSimpleName()); * - *

    * Created by Nehon. */ public class BaseAction extends Action { diff --git a/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java b/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java index 1c95730bbc..bbacb524a4 100644 --- a/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java +++ b/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java @@ -35,6 +35,7 @@ import com.jme3.profile.AppProfiler; import com.jme3.profile.AppStep; +import com.jme3.profile.FgStep; import com.jme3.profile.SpStep; import com.jme3.profile.VpStep; import com.jme3.renderer.ViewPort; @@ -204,4 +205,5 @@ public void vpStep(VpStep step, ViewPort vp, Bucket bucket) { @Override public void spStep(SpStep step, String... additionalInfo) { } + } diff --git a/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java b/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java index 2936a30072..dad7c538cd 100644 --- a/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java +++ b/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java @@ -57,6 +57,7 @@ public class DetailedProfiler implements AppProfiler { private String curAppPath = null; private String curVpPath = null; private String curSpPath = null; + private String curFgPath = null; private VpStep lastVpStep = null; private final StringBuilder path = new StringBuilder(256); @@ -185,6 +186,16 @@ public void spStep(SpStep step, String... additionalInfo) { } } + + @Override + public void fgStep(FgStep step, String... additionalInfo) { + if (data != null) { + curFgPath = getPath("", additionalInfo); + path.setLength(0); + path.append(curAppPath).append("/").append(curVpPath).append(curFgPath); + addStep(path.toString(), System.nanoTime()); + } + } public Map getStats() { if (data != null) { diff --git a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java index ef2a550ee9..33073b432d 100644 --- a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2022 jMonkeyEngine + * Copyright (c) 2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -336,6 +336,7 @@ private void initCamera() { Camera guiCam = new Camera(settings.getWidth(), settings.getHeight()); guiViewPort = renderManager.createPostView("Gui Default", guiCam); guiViewPort.setClearFlags(false, false, false); + //guiViewPort.setUseFrameGraphs(false); } /** diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java index 73762a1234..16cf9c8d01 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java @@ -39,6 +39,8 @@ import com.jme3.material.Material; import com.jme3.post.FilterPostProcessor; import com.jme3.renderer.Caps; +import com.jme3.renderer.framegraph.export.FrameGraphData; +import com.jme3.renderer.framegraph.export.ModuleGraphData; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.OBJLoader; import com.jme3.shader.ShaderGenerator; @@ -364,6 +366,24 @@ public default List getClassLoaders() { */ public FilterPostProcessor loadFilter(String name); + /** + * Loads a framegraph *.j3g file with a FrameGraphKey. + * + * @param key asset key of the framegraph file to load + * @return loaded framegraph + * @see #loadAsset(com.jme3.asset.AssetKey) + */ + public FrameGraphData loadFrameGraph(FrameGraphKey key); + + /** + * Loads a framegraph *.j3g file with a FrameGraphKey. + * + * @param name asset name of the framegraph file to load + * @return loaded framegraph + * @see #loadAsset(com.jme3.asset.AssetKey) + */ + public FrameGraphData loadFrameGraph(String name); + /** * Sets the shaderGenerator to generate shaders based on shaderNodes. * @param generator the shaderGenerator diff --git a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java index a8ff73f2c4..218105369b 100644 --- a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java @@ -38,6 +38,8 @@ import com.jme3.material.Material; import com.jme3.post.FilterPostProcessor; import com.jme3.renderer.Caps; +import com.jme3.renderer.framegraph.export.FrameGraphData; +import com.jme3.renderer.framegraph.export.ModuleGraphData; import com.jme3.scene.Spatial; import com.jme3.shader.Glsl100ShaderGenerator; import com.jme3.shader.Glsl150ShaderGenerator; @@ -455,6 +457,16 @@ public FilterPostProcessor loadFilter(FilterKey key) { public FilterPostProcessor loadFilter(String name) { return loadFilter(new FilterKey(name)); } + + @Override + public FrameGraphData loadFrameGraph(FrameGraphKey key) { + return loadAsset(key); + } + + @Override + public FrameGraphData loadFrameGraph(String name) { + return loadAsset(new FrameGraphKey(name)); + } /** * {@inheritDoc} diff --git a/jme3-core/src/main/java/com/jme3/asset/FrameGraphKey.java b/jme3-core/src/main/java/com/jme3/asset/FrameGraphKey.java new file mode 100644 index 0000000000..92b3d80668 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/FrameGraphKey.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 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.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.renderer.framegraph.export.FrameGraphData; + +/** + * + * @author codex + */ +public class FrameGraphKey extends AssetKey { + + public FrameGraphKey(String name) { + super(name); + } + public FrameGraphKey() { + super(); + } + + @Override + public Class getCacheType() { + return null; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/asset/ModuleGraphKey.java b/jme3-core/src/main/java/com/jme3/asset/ModuleGraphKey.java new file mode 100644 index 0000000000..04b27e95e3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/asset/ModuleGraphKey.java @@ -0,0 +1,28 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.asset; + +import com.jme3.asset.cache.AssetCache; +import com.jme3.renderer.framegraph.export.ModuleGraphData; + +/** + * + * @author codex + */ +public class ModuleGraphKey extends AssetKey { + + public ModuleGraphKey(String name) { + super(name); + } + public ModuleGraphKey() { + super(); + } + + @Override + public Class getCacheType() { + return null; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java index d07a039995..9f685d5987 100644 --- a/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java +++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java @@ -65,7 +65,7 @@ * ec1.setPosition(new Vector3f(0,0,0)); * // ec2.setPosition(new Vector3f(0,0,10)); * 3. Tag the spatials that are part of the environment - * scene.deepFirstTraversal(s->{ + * scene.deepFirstTraversal(s -> { * if(s.getUserData("isEnvNode")!=null){ * EnvironmentProbeControl.tagGlobal(s); * // or ec1.tag(s); diff --git a/jme3-core/src/main/java/com/jme3/export/InputCapsule.java b/jme3-core/src/main/java/com/jme3/export/InputCapsule.java index 133ea34678..55b5ea0a66 100644 --- a/jme3-core/src/main/java/com/jme3/export/InputCapsule.java +++ b/jme3-core/src/main/java/com/jme3/export/InputCapsule.java @@ -39,6 +39,7 @@ import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.BitSet; +import java.util.Collection; import java.util.Map; /** @@ -109,14 +110,32 @@ public interface InputCapsule { // BinarySavable - + public Savable readSavable(String name, Savable defVal) throws IOException; public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException; public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException; - + public default T readSavable(String name, Class type, T defVal) throws IOException { + Savable s = readSavable(name, defVal); + if (s != defVal && type.isAssignableFrom(s.getClass())) { + return (T)s; + } else { + return defVal; + } + } + public default SavableObject readSavableObject(String name, SavableObject defVal) throws IOException { + return readSavable(name, SavableObject.class, defVal); + } // ArrayLists + public default Collection readToCollection(String name, Collection target) throws IOException { + ArrayList list = readSavableArrayList(name, new ArrayList()); + for (Object obj : list) { + target.add(obj); + } + return target; + } + public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException; public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException; public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException; diff --git a/jme3-core/src/main/java/com/jme3/export/NullSavable.java b/jme3-core/src/main/java/com/jme3/export/NullSavable.java index e3384a4ca7..4893d9e9ba 100644 --- a/jme3-core/src/main/java/com/jme3/export/NullSavable.java +++ b/jme3-core/src/main/java/com/jme3/export/NullSavable.java @@ -41,10 +41,12 @@ * @author Kirill Vainer */ public class NullSavable implements Savable { + + public static final NullSavable INSTANCE = new NullSavable(); + @Override - public void write(JmeExporter ex) throws IOException { - } + public void write(JmeExporter ex) throws IOException {} @Override - public void read(JmeImporter im) throws IOException { - } + public void read(JmeImporter im) throws IOException {} + } diff --git a/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java b/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java index 1612a61c13..35ac81b8d3 100644 --- a/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java +++ b/jme3-core/src/main/java/com/jme3/export/OutputCapsule.java @@ -39,13 +39,14 @@ import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.BitSet; +import java.util.Collection; import java.util.Map; /** * @author Joshua Slack */ public interface OutputCapsule { - + // byte primitive public void write(byte value, String name, byte defVal) throws IOException; @@ -115,7 +116,17 @@ public interface OutputCapsule { // ArrayLists - + + public default void writeFromCollection(Collection collection, String name, boolean checkSavable) throws IOException { + ArrayList list = new ArrayList(collection.size()); + for (Object obj : collection) { + if (!checkSavable || obj instanceof Savable) { + list.add(obj); + } + } + writeSavableArrayList(list, name, new ArrayList()); + } + public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException; public void writeSavableArrayListArray(ArrayList[] array, String name, ArrayList[] defVal) throws IOException; public void writeSavableArrayListArray2D(ArrayList[][] array, String name, ArrayList[][] defVal) throws IOException; diff --git a/jme3-core/src/main/java/com/jme3/export/Savable.java b/jme3-core/src/main/java/com/jme3/export/Savable.java index 85957fc24f..004f381a12 100644 --- a/jme3-core/src/main/java/com/jme3/export/Savable.java +++ b/jme3-core/src/main/java/com/jme3/export/Savable.java @@ -40,6 +40,8 @@ * @author Kirill Vainer */ public interface Savable { + void write(JmeExporter ex) throws IOException; void read(JmeImporter im) throws IOException; + } diff --git a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java index 2c63050a4d..42d5bc896c 100644 --- a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java +++ b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java @@ -252,4 +252,5 @@ private static Constructor findNoArgConstructor(String className) return result; } + } diff --git a/jme3-core/src/main/java/com/jme3/export/SavableObject.java b/jme3-core/src/main/java/com/jme3/export/SavableObject.java new file mode 100644 index 0000000000..4cdf4ac425 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/SavableObject.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2024 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.export; + +import com.jme3.util.IntMap; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Saves the internal object if it is a supported savable type. + *

    + * All types supported by Input/Output capsules are supported here, + * except all arrays. + * + * @author codex + */ +public class SavableObject implements Savable { + + public static final SavableObject NULL = new SavableObject(); + private static final String NAME = "name"; + private static final String OBJECT = "object"; + private static final String TYPE = "type"; + private static final String NULL_TYPE = "Null"; + private static final Logger LOG = Logger.getLogger(SavableObject.class.getName()); + + private String name; + private Object object; + + public SavableObject() {} + public SavableObject(String name) { + this(name, null); + } + public SavableObject(Object object) { + this(null, object); + } + public SavableObject(String name, Object object) { + this.name = name; + this.object = object; + } + + @Override + public void write(JmeExporter ex) throws IOException { + if (object == null) { + return; + } + OutputCapsule out = ex.getCapsule(this); + out.write(name, NAME, null); + if (object instanceof Savable) { + out.write((Savable)object, OBJECT, null); + out.write("Savable", TYPE, NULL_TYPE); + } else if (object instanceof Savable[]) { + out.write((Savable[])object, OBJECT, new Savable[0]); + out.write("Savable[]", TYPE, NULL_TYPE); + } else if (object instanceof Savable[][]) { + out.write((Savable[][])object, OBJECT, new Savable[0][0]); + out.write("Savable[][]", TYPE, NULL_TYPE); + } else if (object instanceof Integer) { + out.write((int)object, OBJECT, 0); + out.write("Integer", TYPE, NULL_TYPE); + } else if (object instanceof Integer[]) { + out.write((int[])object, OBJECT, new int[0]); + out.write("Integer[]", TYPE, NULL_TYPE); + } else if (object instanceof Integer[][]) { + out.write((int[][])object, OBJECT, new int[0][0]); + out.write("Integer[][]", TYPE, NULL_TYPE); + } else if (object instanceof Float) { + out.write((float)object, OBJECT, 0); + out.write("Float", TYPE, NULL_TYPE); + } else if (object instanceof Float[]) { + out.write((float[])object, OBJECT, new float[0]); + out.write("Float[]", TYPE, NULL_TYPE); + } else if (object instanceof Float[][]) { + out.write((float[][])object, OBJECT, new float[0][0]); + out.write("Float[][]", TYPE, NULL_TYPE); + } else if (object instanceof Double) { + out.write((double)object, OBJECT, 0); + out.write("Double", TYPE, NULL_TYPE); + } else if (object instanceof Double[]) { + out.write((double[])object, OBJECT, new double[0]); + out.write("Double[]", TYPE, NULL_TYPE); + } else if (object instanceof Double[][]) { + out.write((double[][])object, OBJECT, new double[0][0]); + out.write("Double[][]", TYPE, NULL_TYPE); + } else if (object instanceof Boolean) { + out.write((boolean)object, OBJECT, false); + out.write("Boolean", TYPE, NULL_TYPE); + } else if (object instanceof Boolean[]) { + out.write((boolean[])object, OBJECT, new boolean[0]); + out.write("Boolean[]", TYPE, NULL_TYPE); + } else if (object instanceof Boolean[][]) { + out.write((boolean[][])object, OBJECT, new boolean[0][0]); + out.write("Boolean[][]", TYPE, NULL_TYPE); + } else if (object instanceof Byte) { + out.write((byte)object, OBJECT, (byte)0); + out.write("Byte", TYPE, NULL_TYPE); + } else if (object instanceof Byte[]) { + out.write((byte[])object, OBJECT, new byte[0]); + out.write("Byte[]", TYPE, NULL_TYPE); + } else if (object instanceof Byte[][]) { + out.write((byte[][])object, OBJECT, new byte[0][0]); + out.write("Byte[][]", TYPE, NULL_TYPE); + } else if (object instanceof String) { + out.write((String)object, OBJECT, null); + out.write("String", TYPE, NULL_TYPE); + } else if (object instanceof String[]) { + out.write((String[])object, OBJECT, new String[0]); + out.write("String[]", TYPE, NULL_TYPE); + } else if (object instanceof String[][]) { + out.write((String[][])object, OBJECT, new String[0][0]); + out.write("String[][]", TYPE, NULL_TYPE); + } else if (object instanceof Long) { + out.write((Long)object, OBJECT, 0); + out.write("Long", TYPE, NULL_TYPE); + } else if (object instanceof Long[]) { + out.write((long[])object, OBJECT, new long[0]); + out.write("Long[]", TYPE, NULL_TYPE); + } else if (object instanceof Long[][]) { + out.write((long[][])object, OBJECT, new long[0][0]); + out.write("Long[][]", TYPE, NULL_TYPE); + } else if (object instanceof Short) { + out.write((short)object, OBJECT, (short)0); + out.write("Short", TYPE, NULL_TYPE); + } else if (object instanceof Short[]) { + out.write((short[])object, OBJECT, new short[0]); + out.write("Short[]", TYPE, NULL_TYPE); + } else if (object instanceof Short[][]) { + out.write((short[][])object, OBJECT, new short[0][0]); + out.write("Short[][]", TYPE, NULL_TYPE); + } else if (object instanceof BitSet) { + out.write((BitSet)object, OBJECT, null); + out.write("BitSet", TYPE, NULL_TYPE); + } else if (object instanceof FloatBuffer) { + out.write((FloatBuffer)object, OBJECT, null); + out.write("FloatBuffer", TYPE, NULL_TYPE); + } else if (object instanceof IntBuffer) { + out.write((IntBuffer)object, OBJECT, null); + out.write("IntBuffer", TYPE, NULL_TYPE); + } else if (object instanceof ByteBuffer) { + out.write((ByteBuffer)object, OBJECT, null); + out.write("ByteBuffer", TYPE, NULL_TYPE); + } else if (object instanceof ShortBuffer) { + out.write((ShortBuffer)object, OBJECT, null); + out.write("ShortBuffer", TYPE, NULL_TYPE); + } else if (object instanceof ArrayList) { + out.writeSavableArrayList((ArrayList)object, OBJECT, null); + out.write("ArrayList", TYPE, NULL_TYPE); + } else if (object instanceof ArrayList[]) { + out.writeSavableArrayListArray((ArrayList[])object, OBJECT, new ArrayList[0]); + out.write("ArrayList[]", TYPE, NULL_TYPE); + } else if (object instanceof ArrayList[][]) { + out.writeSavableArrayListArray2D((ArrayList[][])object, OBJECT, new ArrayList[0][0]); + out.write("ArrayList[][]", TYPE, NULL_TYPE); + } else if (object instanceof Map) { + out.writeStringSavableMap((Map)object, OBJECT, null); + out.write("StringSavableMap", TYPE, NULL_TYPE); + } else if (object instanceof IntMap) { + out.writeIntSavableMap((IntMap)object, OBJECT, null); + out.write("IntMap", TYPE, NULL_TYPE); + } else { + String type = object.getClass().getName(); + LOG.log(Level.WARNING, "Attempted to save unsupported type {0}", type); + out.write(type, TYPE, NULL_TYPE); + } + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + name = in.readString(NAME, null); + String type = in.readString(TYPE, NULL_TYPE); + switch (type) { + case "Savable": + object = in.readSavable(OBJECT, null); + break; + case "Savable[]": + object = in.readSavableArray(OBJECT, new Savable[0]); + break; + case "Savable[][]": + object = in.readSavableArray2D(OBJECT, new Savable[0][0]); + break; + case "Integer": + object = in.readInt(OBJECT, 0); + break; + case "Integer[]": + object = in.readIntArray(OBJECT, new int[0]); + break; + case "Integer[][]": + object = in.readIntArray2D(OBJECT, new int[0][0]); + break; + case "Float": + object = in.readFloat(OBJECT, 0); + break; + case "Float[]": + object = in.readFloatArray(OBJECT, new float[0]); + break; + case "Float[][]": + object = in.readFloatArray2D(OBJECT, new float[0][0]); + break; + case "Double": + object = in.readDouble(OBJECT, 0); + break; + case "Double[]": + object = in.readDoubleArray(OBJECT, new double[0]); + break; + case "Double[][]": + object = in.readDoubleArray2D(OBJECT, new double[0][0]); + break; + case "Boolean": + object = in.readBoolean(OBJECT, false); + break; + case "Boolean[]": + object = in.readBooleanArray(OBJECT, new boolean[0]); + break; + case "Boolean[][]": + object = in.readBooleanArray2D(OBJECT, new boolean[0][0]); + break; + case "Byte": + object = in.readByte(OBJECT, (byte)0); + break; + case "Byte[]": + object = in.readByteArray(OBJECT, new byte[0]); + break; + case "Byte[][]": + object = in.readByteArray2D(OBJECT, new byte[0][0]); + break; + case "String": + object = in.readString(OBJECT, null); + break; + case "String[]": + object = in.readStringArray(OBJECT, new String[0]); + break; + case "String[][]": + object = in.readStringArray2D(OBJECT, new String[0][0]); + break; + case "Long": + object = in.readLong(OBJECT, 0); + break; + case "Long[]": + object = in.readLongArray(OBJECT, new long[0]); + break; + case "Long[][]": + object = in.readLongArray2D(OBJECT, new long[0][0]); + break; + case "Short": + object = in.readShort(OBJECT, (short)0); + break; + case "Short[]": + object = in.readShortArray(OBJECT, new short[0]); + break; + case "Short[][]": + object = in.readShortArray2D(OBJECT, new short[0][0]); + break; + case "BitSet": + object = in.readBitSet(OBJECT, null); + break; + case "FloatBuffer": + object = in.readFloatBuffer(OBJECT, null); + break; + case "IntBuffer": + object = in.readIntBuffer(OBJECT, null); + break; + case "ByteBuffer": + object = in.readByteBuffer(OBJECT, null); + break; + case "ShortBuffer": + object = in.readShortBuffer(OBJECT, null); + break; + case "ArrayList": + object = in.readSavableArrayList(OBJECT, null); + break; + case "ArrayList[]": + object = in.readSavableArrayListArray(OBJECT, new ArrayList[0]); + break; + case "ArrayList[][]": + object = in.readSavableArrayListArray2D(OBJECT, new ArrayList[0][0]); + break; + case "StringSavableMap": + object = in.readStringSavableMap(OBJECT, null); + break; + case "IntMap": + object = in.readIntSavableMap(OBJECT, null); + break; + case NULL_TYPE: + object = null; + break; + default: + LOG.log(Level.WARNING, "Attempted to load unsupported type {0}", type); + break; + } + } + + public void setName(String name) { + this.name = name; + } + public void setObject(Object object) { + this.object = object; + } + + public String getName() { + return name; + } + public Object getObject() { + return object; + } + public T getObject(Class type) { + return (T)object; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/material/MaterialDef.java b/jme3-core/src/main/java/com/jme3/material/MaterialDef.java index 9cbd482e00..8c3755548c 100644 --- a/jme3-core/src/main/java/com/jme3/material/MaterialDef.java +++ b/jme3-core/src/main/java/com/jme3/material/MaterialDef.java @@ -185,6 +185,24 @@ public void addTechniqueDef(TechniqueDef technique) { public List getTechniqueDefs(String name) { return techniques.get(name); } + + /** + * Gets the first TechniqueDef under the name of the given light mode. + * + * @param name + * @param mode + * @return first matching TechniqueDef, or null + */ + public TechniqueDef getTechniqueDef(String name, TechniqueDef.LightMode mode) { + List list = techniques.get(name); + if (list == null) return null; + for (TechniqueDef d : list) { + if (d.getLightMode() == mode) { + return d; + } + } + return null; + } /** * 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 5091028a85..437ef785b5 100644 --- a/jme3-core/src/main/java/com/jme3/material/Technique.java +++ b/jme3-core/src/main/java/com/jme3/material/Technique.java @@ -164,8 +164,7 @@ Shader makeCurrent(RenderManager renderManager, SafeArrayList * @param lastTexUnit the index of the most recently used texture unit */ void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { - TechniqueDefLogic logic = def.getLogic(); - logic.render(renderManager, shader, geometry, lights, lastBindUnits); + def.getLogic().render(renderManager, shader, geometry, lights, lastBindUnits); } /** 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 b620c9ceaf..40d1df9612 100644 --- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -41,6 +41,7 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.*; +import java.util.function.Function; /** * Describes a technique definition. @@ -66,6 +67,7 @@ public class TechniqueDef implements Savable, Cloneable { * Describes light rendering mode. */ public enum LightMode { + /** * Disable light-based rendering */ @@ -105,6 +107,7 @@ public enum LightMode { */ @Deprecated FixedPipeline, + /** * Similar to {@link #SinglePass} except the type of each light is known * at shader compile time. @@ -115,7 +118,13 @@ public enum LightMode { * shaders used balloons because of the variations in the number of * lights used by objects. */ - StaticPass + StaticPass, + + /** + * + */ + Custom, + } public enum ShadowMode { @@ -157,6 +166,7 @@ public enum LightSpace { private LightMode lightMode = LightMode.Disable; private ShadowMode shadowMode = ShadowMode.Disable; private TechniqueDefLogic logic; + private boolean logicInit = false; private ArrayList worldBinds; //The space in which the light should be transposed before sending to the shader. @@ -242,10 +252,36 @@ public void setLightMode(LightMode lightMode) { public void setLogic(TechniqueDefLogic logic) { this.logic = logic; } - + public TechniqueDefLogic getLogic() { return logic; } + + public T getLogic(Class type) { + if (logic == null) return null; + if (!type.isAssignableFrom(logic.getClass())) { + throw new ClassCastException("TechniqueDefLogic is not of "+type.getName()); + } + return (T)logic; + } + + /** + * Sets the technique def logic if the current logic is null or of a different type. + * + * @param + * @param type + * @param func + * @return + */ + public T setLogicOrElse(Class type, Function func) { + if (logic == null || !type.isAssignableFrom(logic.getClass())) { + T l = func.apply(this); + setLogic(l); + return l; + } else { + return (T)logic; + } + } /** * Returns the shadow mode. @@ -439,7 +475,6 @@ public void addShaderParamDefine(String paramName, VarType paramType, String def */ public int addShaderUnmappedDefine(String defineName, VarType defineType) { int defineId = defineNames.size(); - defineNames.add(defineName); defineTypes.add(defineType); return defineId; @@ -514,13 +549,13 @@ private Shader loadShader(AssetManager assetManager, EnumSet rendererCaps, } public Shader getShader(AssetManager assetManager, EnumSet rendererCaps, DefineList defines) { - Shader shader = definesToShaderMap.get(defines); - if (shader == null) { - shader = loadShader(assetManager, rendererCaps, defines); - definesToShaderMap.put(defines.deepClone(), shader); - } - return shader; - } + Shader shader = definesToShaderMap.get(defines); + if (shader == null) { + shader = loadShader(assetManager, rendererCaps, defines); + definesToShaderMap.put(defines.deepClone(), shader); + } + return shader; + } /** * Sets the shaders that this technique definition will use. @@ -529,7 +564,7 @@ public Shader getShader(AssetManager assetManager, EnumSet rendererCaps, D * @param shaderLanguages EnumMap containing all shader languages for this stage */ public void setShaderFile(EnumMap shaderNames, - EnumMap shaderLanguages) { + EnumMap shaderLanguages) { requiredCaps.clear(); weight = 0; @@ -815,7 +850,7 @@ public TechniqueDef clone() throws CloneNotSupportedException { try { clone.logic = logic.getClass().getConstructor(TechniqueDef.class).newInstance(clone); } catch (InstantiationException | IllegalAccessException - | NoSuchMethodException | InvocationTargetException e) { + | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } 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 ca8f7d1efa..55a76b7c99 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 @@ -59,7 +59,15 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager EnumSet rendererCaps, LightList lights, DefineList defines) { return techniqueDef.getShader(assetManager, rendererCaps, defines); } - + + /** + * Renders mesh from geometry. + * + * @param renderer + * @param geom + * @deprecated use {@link com.jme3.renderer.TechniqueDefLogic#renderMeshFromGeometry(com.jme3.renderer.Renderer, com.jme3.scene.Geometry)} instead. + */ + @Deprecated public static void renderMeshFromGeometry(Renderer renderer, Geometry geom) { Mesh mesh = geom.getMesh(); int lodLevel = geom.getLodLevel(); @@ -95,6 +103,6 @@ protected static ColorRGBA getAmbientColor(LightList lightList, boolean removeLi public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { Renderer renderer = renderManager.getRenderer(); renderer.setShader(shader); - renderMeshFromGeometry(renderer, geometry); + TechniqueDefLogic.renderMeshFromGeometry(renderer, geometry); } } diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SkyLightAndReflectionProbeRender.java b/jme3-core/src/main/java/com/jme3/material/logic/SkyLightAndReflectionProbeRender.java new file mode 100644 index 0000000000..4972452df3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/logic/SkyLightAndReflectionProbeRender.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2023 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.light.AmbientLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.LightProbe; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.TextureUnitException; +import com.jme3.shader.Uniform; +import com.jme3.shader.VarType; +import com.jme3.texture.TextureCubeMap; + +import java.util.List; + +/** + * Rendering logic for handling SkyLight and ReflectionProbe. + *

    + * todo:The functionality of this tool class is not yet complete, because LightProbe has not yet been split into SkyLight and ReflectionProbe. So the code here is just to be compatible with the extraction of LightProbe and lay the groundwork for subsequent work. + * @author JohnKkk + */ +public class SkyLightAndReflectionProbeRender { + + /** + * Update lightProbe data to current shader (currently data comes from lightProbe, later this method will be used for skyLight and reflectionProbe) + * @param rm + * @param lastTexUnit + * @param lightProbeData + * @param shCoeffs + * @param lightProbePemMap + * @param lightProbe + * @return + */ + public static int setSkyLightAndReflectionProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeData, Uniform shCoeffs, Uniform lightProbePemMap, LightProbe lightProbe) { + + lightProbeData.setValue(VarType.Matrix4, lightProbe.getUniformMatrix()); + //setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / area.getRadius() + lightProbe.getNbMipMaps(), 0); + shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs()); + /* + * Assign the prefiltered env map to the next available texture unit. + */ + int pemUnit = lastTexUnit++; + Renderer renderer = rm.getRenderer(); + TextureCubeMap pemTexture = lightProbe.getPrefilteredEnvMap(); + try { + renderer.setTexture(pemUnit, pemTexture); + } catch (TextureUnitException exception) { + String message = "Can't assign texture unit for SkyLightAndReflectionProbe." + + " lastTexUnit=" + lastTexUnit; + throw new IllegalArgumentException(message); + } + lightProbePemMap.setValue(VarType.Int, pemUnit); + return lastTexUnit; + } + + /** + * Extract which lightProbes should affect the currently rendered object. Currently, this method is only used in deferredPath and only works for the first three collected lightProbes, so it is problematic, but I put it here to prepare for future functionality (and compatibilty with current lightProbes). + * @param lightList + * @param ambientLightColor + * @param skyLightAndReflectionProbes + * @param removeLights + * @return hasAmbientLight + */ + public static boolean extractSkyLightAndReflectionProbes(LightList lightList, ColorRGBA ambientLightColor, List skyLightAndReflectionProbes, boolean removeLights) { + ambientLightColor.set(0, 0, 0, 1); + boolean hasAmbientLight = false; + skyLightAndReflectionProbes.clear(); + for (int j = 0; j < lightList.size(); j++) { + Light l = lightList.get(j); + if (l instanceof AmbientLight) { + ambientLightColor.addLocal(l.getColor()); + hasAmbientLight = true; + if(removeLights){ + lightList.remove(j); + j--; + } + } + if (l instanceof LightProbe) { + skyLightAndReflectionProbes.add((LightProbe) l); + if(removeLights){ + lightList.remove(j); + j--; + } + } + } + // todo:For reflection probes, only top three in view frustum are processed per frame (but scene can contain large amount of reflection probes) + if(skyLightAndReflectionProbes.size() > 3){ + + } + ambientLightColor.a = 1.0f; + return hasAmbientLight; + } + +} 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 31f970a176..475d13ffae 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 @@ -34,9 +34,13 @@ import com.jme3.asset.AssetManager; import com.jme3.light.LightList; import com.jme3.material.Material.BindUnits; +import com.jme3.material.TechniqueDef; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.instancing.InstancedGeometry; import com.jme3.shader.DefineList; import com.jme3.shader.Shader; import com.jme3.shader.Uniform; @@ -91,7 +95,27 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager * {@link #makeCurrent(com.jme3.asset.AssetManager, com.jme3.renderer.RenderManager, java.util.EnumSet, com.jme3.light.LightList, com.jme3.shader.DefineList)}. * @param geometry The geometry to render * @param lights Lights which influence the geometry. - * @param lastTexUnit the index of the most recently used texture unit + * @param lastBindUnits the index of the most recently used units */ public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits); + + /** + * + * @param renderer + * @param geom + */ + public static void renderMeshFromGeometry(Renderer renderer, Geometry geom) { + Mesh mesh = geom.getMesh(); + int lodLevel = geom.getLodLevel(); + if (geom instanceof InstancedGeometry) { + InstancedGeometry instGeom = (InstancedGeometry) geom; + int numVisibleInstances = instGeom.getNumVisibleInstances(); + if (numVisibleInstances > 0) { + renderer.renderMesh(mesh, lodLevel, numVisibleInstances, instGeom.getAllInstanceData()); + } + } else { + renderer.renderMesh(mesh, lodLevel, 1, null); + } + } + } diff --git a/jme3-core/src/main/java/com/jme3/math/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java index 2993c7eed0..c0eacfdd5d 100644 --- a/jme3-core/src/main/java/com/jme3/math/FastMath.java +++ b/jme3-core/src/main/java/com/jme3/math/FastMath.java @@ -843,6 +843,18 @@ public static float determinant(double m00, double m01, double m02, * (m10 * det12 - m11 * det02 + m12 * det01)); } + /** + * Returns a random float between min and max. + * + * @param min the desired minimum value + * @param max the desired maximum value + * @return A random float between min (inclusive) to max (inclusive). + */ + public static float nextRandomFloat(float min, float max) { + float f = (nextRandomFloat() * (max - min + 1.0f)) + min; + return Math.min(Math.max(f, min), max); + } + /** * Returns a random float between 0 and 1. * diff --git a/jme3-core/src/main/java/com/jme3/post/Filter.java b/jme3-core/src/main/java/com/jme3/post/Filter.java index efdb19b04a..dc71f0deae 100644 --- a/jme3-core/src/main/java/com/jme3/post/Filter.java +++ b/jme3-core/src/main/java/com/jme3/post/Filter.java @@ -248,7 +248,7 @@ protected Filter() { * @param w the width * @param h the height */ - protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + public final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { // cleanup(renderManager.getRenderer()); defaultPass = new Pass(); defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat()); @@ -260,7 +260,7 @@ protected final void init(AssetManager manager, RenderManager renderManager, Vie * * @param r the Renderer */ - protected final void cleanup(Renderer r) { + public final void cleanup(Renderer r) { processor = null; if (defaultPass != null) { defaultPass.cleanup(r); @@ -301,6 +301,15 @@ protected void cleanUpFilter(Renderer r) { */ protected abstract Material getMaterial(); + /** + * Public mirror of {@link #getMaterial()}. + * + * @return + */ + public Material getPassMaterial() { + return getMaterial(); + } + /** * Override if you want to do something special with the depth texture; * @@ -326,7 +335,16 @@ protected void postQueue(RenderQueue queue) { */ protected void preFrame(float tpf) { } - + + /** + * Public mirror of {@link #preFrame(float)}. + * + * @param tpf + */ + public void filterPreFrame(float tpf) { + + } + /** * Override this method if you want to make a pass just after the frame has been rendered and just before the filter rendering * @@ -445,11 +463,38 @@ protected boolean isRequiresBilinear() { return false; } + /** + * Returns true if this filter can be used by framegraph passes. + * + * @return + */ + public boolean isFrameGraphCompatible() { + return false; + } + + /** + * Public mirror of {@link #isRequiresSceneTexture()} + * + * @return + */ + public boolean isReqSceneTex() { + return isRequiresSceneTexture(); + } + + /** + * Public mirror of {@link #isRequiresDepthTexture()} + * + * @return + */ + public boolean isReqDepthTex() { + return isRequiresDepthTexture(); + } + /** * returns the list of the postRender passes * @return the pre-existing List */ - protected List getPostRenderPasses() { + public List getPostRenderPasses() { return postRenderPasses; } @@ -492,4 +537,15 @@ protected void setProcessor(FilterPostProcessor proc) { */ protected void postFilter(Renderer r, FrameBuffer buffer){ } + + /** + * Public mirror of {@link #postFilter(com.jme3.renderer.Renderer, com.jme3.texture.FrameBuffer)} + * + * @param r + * @param buffer + */ + public void filterPostRender(Renderer r, FrameBuffer buffer) { + postFilter(r, buffer); + } + } diff --git a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java index 7da3453b8f..c775240c54 100644 --- a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java @@ -658,4 +658,5 @@ private void setupViewPortFrameBuffer() { viewPort.setOutputFrameBuffer(renderFrameBuffer); } } - } + +} diff --git a/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java b/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java index e3efc8e3af..e761c530d7 100644 --- a/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java +++ b/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java @@ -73,12 +73,28 @@ public interface AppProfiler { /** * Called at the beginning of the specified SpStep (SceneProcessor step). - * For more detailed steps it is possible to provide additional information as strings, like the name of the processor. + *

    + * For more detailed steps it is possible to provide additional information as strings, + * like the name of the processor. * * @param step the SceneProcessor step that's about to begin * @param additionalInfo information about the SceneProcessor step */ public void spStep(SpStep step, String... additionalInfo); + + /** + * Called at the beginning of the specified FgStep (FrameGraph step). + *

    + * For more detailed steps it is possible to provide additional information as strings, + * such as the name of the pass. + * + * @param step + * @param additionalInfo + */ + public default void fgStep(FgStep step, String... additionalInfo) { + // implementation is optional + } + } diff --git a/jme3-core/src/main/java/com/jme3/profile/FgStep.java b/jme3-core/src/main/java/com/jme3/profile/FgStep.java new file mode 100644 index 0000000000..245a518a85 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/profile/FgStep.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 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.profile; + +/** + * Defines steps in executing framegraphs. + * + * @author codex + */ +public enum FgStep { + Prepare, + Execute, + Reset, +} diff --git a/jme3-core/src/main/java/com/jme3/profile/VpStep.java b/jme3-core/src/main/java/com/jme3/profile/VpStep.java index 9dff853681..fa78f82efe 100644 --- a/jme3-core/src/main/java/com/jme3/profile/VpStep.java +++ b/jme3-core/src/main/java/com/jme3/profile/VpStep.java @@ -48,6 +48,10 @@ public enum VpStep { PostFrame, ProcEndRender, RenderBucket, + FrameGraphSetup, + FrameGraphCull, + FrameGraphExecute, + FrameGraphReset, EndRender } diff --git a/jme3-core/src/main/java/com/jme3/renderer/AbstractPipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/AbstractPipelineContext.java new file mode 100644 index 0000000000..47773fa363 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/AbstractPipelineContext.java @@ -0,0 +1,45 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer; + +import java.util.LinkedList; + +/** + * + * @author codex + */ +public abstract class AbstractPipelineContext implements PipelineContext { + + private final LinkedList usedPipelines = new LinkedList<>(); + + @Override + public boolean addPipeline(RenderManager rm, RenderPipeline pipeline) { + boolean firstCall = false; + if (!pipeline.hasRenderedThisFrame()) { + if (firstCall = usedPipelines.isEmpty()) { + beginRenderFrame(rm); + } + pipeline.beginRenderFrame(rm); + usedPipelines.add(pipeline); + } else if (usedPipelines.isEmpty()) { + throw new IllegalStateException( + "Pipeline cannot have rendered at this point, but claims it did."); + } + return firstCall; + } + @Override + public void flushPipelineStack(RenderManager rm) { + for (RenderPipeline p : usedPipelines) { + p.endRenderFrame(rm); + } + usedPipelines.clear(); + endRenderFrame(rm); + } + + protected abstract void beginRenderFrame(RenderManager rm); + + protected abstract void endRenderFrame(RenderManager rm); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/Camera.java b/jme3-core/src/main/java/com/jme3/renderer/Camera.java index eb7cca3ae4..af48217267 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Camera.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Camera.java @@ -518,6 +518,25 @@ public void resize(int width, int height, boolean fixAspect) { onFrustumChange(); } } + + /** + * Resizes the camera's view to the width and height only if the + * that would change the camera's view size. + * + * @param width + * @param height + * @param fixAspect + * @param force + * @return true if camera was resized (or forced) + * @see #resize(int, int, boolean) + */ + public boolean resize(int width, int height, boolean fixAspect, boolean force) { + if (force || this.width != width || this.height != height) { + resize(width, height, fixAspect); + return true; + } + return false; + } /** * Returns the value of the bottom frustum diff --git a/jme3-core/src/main/java/com/jme3/renderer/DefaultPipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/DefaultPipelineContext.java new file mode 100644 index 0000000000..2ca510f2d3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/DefaultPipelineContext.java @@ -0,0 +1,18 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer; + +/** + * + * @author codex + */ +public class DefaultPipelineContext extends AbstractPipelineContext { + + @Override + protected void beginRenderFrame(RenderManager rm) {} + @Override + protected void endRenderFrame(RenderManager rm) {} + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/DepthRange.java b/jme3-core/src/main/java/com/jme3/renderer/DepthRange.java new file mode 100644 index 0000000000..c9ad3c705c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/DepthRange.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2024 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.renderer; + +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 java.io.IOException; + +/** + * Defines a range within 0 and 1 that render depth values are clamped to. + * + * @author codex + */ +public class DepthRange implements Savable { + + /** + * Range between 0 and 1. + */ + public static final DepthRange IDENTITY = new DepthRange(); + /** + * Range that clamps to zero. + */ + public static final DepthRange FRONT = new DepthRange(0, 0); + /** + * Range that clamps to one. + */ + public static final DepthRange REAR = new DepthRange(1, 1); + + private float start, end; + + /** + * Creates a new range between 0 and 1. + */ + public DepthRange() { + set(0, 1); + } + /** + * + * @param start + * @param end + */ + public DepthRange(float start, float end) { + set(start, end); + } + /** + * + * @param range + */ + public DepthRange(DepthRange range) { + set(range); + } + + /** + * Sets the range. + * + * @param start lower bound + * @param end upper bound + * @return this instance + */ + public final DepthRange set(float start, float end) { + validateRange(start, end); + this.start = start; + this.end = end; + return this; + } + + /** + * Sets the range. + * + * @param range + * @return this instance + */ + public final DepthRange set(DepthRange range) { + // no need to validate range here + start = range.start; + end = range.end; + return this; + } + + /** + * Sets the start (lower) bound. + * + * @param start + * @return this instance + */ + public final DepthRange setStart(float start) { + validateRange(start, end); + this.start = start; + return this; + } + + /** + * Sets the end (upper) bound. + * + * @param end + * @return + */ + public final DepthRange setEnd(float end) { + validateRange(start, end); + this.end = end; + return this; + } + + /** + * Gets the start (lower) bound. + * + * @return + */ + public float getStart() { + return start; + } + + /** + * Gets the end (upper) bound. + * + * @return + */ + public float getEnd() { + return end; + } + + private void validateRange(float start, float end) { + if (start > end) { + throw new IllegalStateException("Depth start cannot be beyond depth end."); + } + if (start > 1 || start < 0 || end > 1 || end < 0) { + throw new IllegalStateException("Depth parameters must be between 0 and 1 (inclusive)."); + } + } + + @Override + public boolean equals(Object object) { + if (object == null || !(object instanceof DepthRange)) { + return false; + } + DepthRange obj = (DepthRange)object; + return start == obj.start && end == obj.end; + } + + public boolean equals(DepthRange range) { + return range != null && start == range.start && end == range.end; + } + + public boolean equals(float start, float end) { + return this.start != start && this.end != end; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 79 * hash + Float.floatToIntBits(this.start); + hash = 79 * hash + Float.floatToIntBits(this.end); + return hash; + } + @Override + public String toString() { + return "DepthRange["+start+" -> "+end+"]"; + } + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(start, "start", 0); + out.write(end, "end", 1); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + start = in.readFloat("start", 0); + end = in.readFloat("end", 1); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/ForwardPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/ForwardPipeline.java new file mode 100644 index 0000000000..85be771c1c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/ForwardPipeline.java @@ -0,0 +1,125 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer; + +import com.jme3.post.SceneProcessor; +import com.jme3.profile.AppProfiler; +import com.jme3.profile.SpStep; +import com.jme3.profile.VpStep; +import com.jme3.scene.Spatial; +import com.jme3.util.SafeArrayList; +import java.util.List; + +/** + * + * @author codex + */ +public class ForwardPipeline implements RenderPipeline { + + private boolean rendered = false; + + @Override + public PipelineContext fetchPipelineContext(RenderManager rm) { + return rm.getDefaultContext(); + } + @Override + public boolean hasRenderedThisFrame() { + return rendered; + } + @Override + public void beginRenderFrame(RenderManager rm) {} + @Override + public void pipelineRender(RenderManager rm, PipelineContext context, ViewPort vp, float tpf) { + + AppProfiler prof = rm.getProfiler(); + + SafeArrayList processors = vp.getProcessors(); + if (processors.isEmpty()) { + processors = null; + } + + if (processors != null) { + if (prof != null) { + prof.vpStep(VpStep.PreFrame, vp, null); + } + for (SceneProcessor p : processors.getArray()) { + if (!p.isInitialized()) { + p.initialize(rm, vp); + } + p.setProfiler(prof); + if (prof != null) { + prof.spStep(SpStep.ProcPreFrame, p.getClass().getSimpleName()); + } + p.preFrame(tpf); + } + } + + rm.applyViewPort(vp); + + if (prof != null) { + prof.vpStep(VpStep.RenderScene, vp, null); + } + // flatten scenes into render queue + List scenes = vp.getScenes(); + for (int i = scenes.size() - 1; i >= 0; i--) { + rm.renderScene(scenes.get(i), vp); + } + if (processors != null) { + if (prof != null) { + prof.vpStep(VpStep.PostQueue, vp, null); + } + for (SceneProcessor p : processors.getArray()) { + if (prof != null) { + prof.spStep(SpStep.ProcPostQueue, p.getClass().getSimpleName()); + } + p.postQueue(vp.getQueue()); + } + } + + if (prof != null) { + prof.vpStep(VpStep.FlushQueue, vp, null); + } + rm.flushQueue(vp); + + if (processors != null) { + if (prof != null) { + prof.vpStep(VpStep.PostFrame, vp, null); + } + for (SceneProcessor proc : processors.getArray()) { + if (prof != null) { + prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName()); + } + proc.postFrame(vp.getOutputFrameBuffer()); + } + if (prof != null) { + prof.vpStep(VpStep.ProcEndRender, vp, null); + } + } + + // render the translucent objects queue after processors have been rendered + rm.renderTranslucentQueue(vp); + + // clear any remaining spatials that were not rendered. + rm.clearQueue(vp); + + rendered = true; + + /* + * the call to setCamera will indirectly cause a clipRect to be set, must be cleared to avoid surprising results + * if renderer#copyFrameBuffer is used later + */ + rm.getRenderer().clearClipRect(); + + if (prof != null) { + prof.vpStep(VpStep.EndRender, vp, null); + } + + } + @Override + public void endRenderFrame(RenderManager rm) { + rendered = false; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/GeometryRenderHandler.java b/jme3-core/src/main/java/com/jme3/renderer/GeometryRenderHandler.java new file mode 100644 index 0000000000..c1f35b8cea --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/GeometryRenderHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 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.renderer; + +import com.jme3.scene.Geometry; + +/** + * Handles rendering of individual geometries. + * + * @author codex + */ +public interface GeometryRenderHandler { + + public static final GeometryRenderHandler DEFAULT = (rm, geom) -> { + rm.renderGeometry(geom); + return true; + }; + + /** + * Renders the given geometry, or returns false. + * + * @param rm renderGeometry manager + * @param geom geometry to renderGeometry + * @return true if the geometry was rendered + */ + public boolean renderGeometry(RenderManager rm, Geometry geom); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/PipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/PipelineContext.java new file mode 100644 index 0000000000..27007c3499 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/PipelineContext.java @@ -0,0 +1,18 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer; + +/** + * Handles objects globally for a single type of RenderPipeline. + * + * @author codex + */ +public interface PipelineContext { + + public boolean addPipeline(RenderManager rm, RenderPipeline pipeline); + + public void flushPipelineStack(RenderManager rm); + +} 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 df1c42f442..9aeeb8c8c3 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,9 @@ import com.jme3.profile.AppStep; import com.jme3.profile.SpStep; import com.jme3.profile.VpStep; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.RenderObjectMap; +import com.jme3.renderer.framegraph.debug.GraphEventCapture; import com.jme3.renderer.queue.GeometryList; import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.Bucket; @@ -63,17 +66,22 @@ import com.jme3.system.Timer; import com.jme3.texture.FrameBuffer; import com.jme3.util.SafeArrayList; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.logging.Level; import java.util.logging.Logger; /** - * A high-level rendering interface that is - * above the Renderer implementation. RenderManager takes care - * of rendering the scene graphs attached to each viewport and - * handling SceneProcessors. + * A high-level rendering interface that is above the Renderer implementation. + *

    + * RenderManager takes care of rendering the scene graphs attached to each + * viewport and handling SceneProcessors. * * @see SceneProcessor * @see ViewPort @@ -81,18 +89,21 @@ */ public class RenderManager { - private static final Logger logger = Logger.getLogger(RenderManager.class.getName()); private final Renderer renderer; private final UniformBindingManager uniformBindingManager = new UniformBindingManager(); private final ArrayList preViewPorts = new ArrayList<>(); private final ArrayList viewPorts = new ArrayList<>(); private final ArrayList postViewPorts = new ArrayList<>(); + private final HashMap contexts = new HashMap<>(); + private final LinkedList usedContexts = new LinkedList<>(); + private RenderPipeline defaultPipeline = new ForwardPipeline(); private Camera prevCam = null; private Material forcedMaterial = null; private String forcedTechnique = null; private RenderState forcedRenderState = null; private final SafeArrayList forcedOverrides = new SafeArrayList<>(MatParamOverride.class); + private GeometryRenderHandler renderGeometry; private int viewX; private int viewY; private int viewWidth; @@ -104,10 +115,9 @@ public class RenderManager { private LightFilter lightFilter = new DefaultLightFilter(); private TechniqueDef.LightMode preferredLightMode = TechniqueDef.LightMode.MultiPass; private int singlePassLightBatchSize = 1; - private MatParamOverride boundDrawBufferId=new MatParamOverride(VarType.Int,"BoundDrawBuffer",0); + private MatParamOverride boundDrawBufferId = new MatParamOverride(VarType.Int, "BoundDrawBuffer", 0); private Predicate renderFilter; - /** * Creates a high-level rendering interface over the * low-level rendering interface. @@ -117,6 +127,109 @@ public class RenderManager { public RenderManager(Renderer renderer) { this.renderer = renderer; this.forcedOverrides.add(boundDrawBufferId); + contexts.put(PipelineContext.class, new DefaultPipelineContext()); + } + + /** + * Gets the default pipeline used when a ViewPort does not have a + * pipeline already assigned to it. + * + * @return + */ + public RenderPipeline getPipeline() { + return defaultPipeline; + } + + /** + * Sets the default pipeline used when a ViewPort does not have a + * pipeline already assigned to it. + *

    + * default={@link ForwardPipeline} + * + * @param pipeline default pipeline (not null) + */ + public void setPipeline(RenderPipeline pipeline) { + this.defaultPipeline = pipeline; + } + + /** + * Gets the default pipeline context registered under + * {@link PipelineContext#getClass()}. + * + * @return + */ + public PipelineContext getDefaultContext() { + return getContext(PipelineContext.class); + } + + /** + * Gets the pipeline context registered under the class. + * + * @param + * @param type + * @return + */ + public T getContext(Class type) { + return (T)contexts.get(type); + } + + /** + * Gets the pipeline context registered under the class or creates + * and registers a new context supplied by the supplier. + * + * @param + * @param type + * @param supplier + * @return + */ + public T getOrCreateContext(Class type, Supplier supplier) { + T c = getContext(type); + if (c == null) { + c = supplier.get(); + contexts.put(type, c); + } + return c; + } + + /** + * Registers the pipeline context under the class. + * + * @param + * @param type + * @param context + */ + public void registerContext(Class type, T context) { + contexts.put(type, context); + } + + /** + * Gets the application profiler. + * + * @return + */ + public AppProfiler getProfiler() { + return prof; + } + + /** + * Sets the GeometryRenderHandler used to render geometry. + *

    + * default=null + * + * @param renderGeometry geometry render handler, or null to not use one for rendering + * @see GeometryRenderHandler + */ + public void setGeometryRenderHandler(GeometryRenderHandler renderGeometry){ + this.renderGeometry = renderGeometry; + } + + /** + * Gest the GeometryRenderHandler used to render geometry. + * + * @return geometry render handler, or null of none is used for rendering + */ + public GeometryRenderHandler getGeometryRenderHandler() { + return renderGeometry; } /** @@ -402,7 +515,7 @@ public void notifyRescale(float x, float y) { for (ViewPort vp : preViewPorts) { notifyRescale(vp, x, y); } - for (ViewPort vp : viewPorts) { + for (ViewPort vp : viewPorts) { notifyRescale(vp, x, y); } for (ViewPort vp : postViewPorts) { @@ -422,6 +535,15 @@ public void notifyRescale(float x, float y) { public void setForcedMaterial(Material mat) { forcedMaterial = mat; } + + /** + * Gets the forced material. + * + * @return + */ + public Material getForcedMaterial() { + return forcedMaterial; + } /** * Returns the forced render state previously set with @@ -503,7 +625,7 @@ public void setForcedTechnique(String forcedTechnique) { * material or any overrides that exist in the scene graph that have the * same name. * - * @param override The override to add + * @param override The override to addUserEvent * @see MatParamOverride * @see #removeForcedMatParam(com.jme3.material.MatParamOverride) */ @@ -628,7 +750,33 @@ public void updateUniformBindings(Shader shader) { * @see com.jme3.material.Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) */ public void renderGeometry(Geometry geom) { - if (renderFilter != null && !renderFilter.test(geom)) return; + + if (renderFilter != null && !renderFilter.test(geom)) { + return; + } + + LightList lightList = geom.getWorldLightList(); + if (lightFilter != null) { + filteredLightList.clear(); + lightFilter.filterLights(geom, filteredLightList); + lightList = filteredLightList; + } + + renderGeometry(geom, lightList); + + } + + /** + * + * @param geom + * @param lightList + */ + public void renderGeometry(Geometry geom, LightList lightList) { + + if (renderFilter != null && !renderFilter.test(geom)) { + return; + } + this.renderer.pushDebugGroup(geom.getName()); if (geom.isIgnoreTransform()) { setWorldMatrix(Matrix4f.IDENTITY); @@ -636,23 +784,18 @@ public void renderGeometry(Geometry geom) { setWorldMatrix(geom.getWorldMatrix()); } - // Use material override to pass the current target index (used in api such as GL ES that do not support glDrawBuffer) + // Use material override to pass the current target index (used in api such as GL ES + // that do not support glDrawBuffer) FrameBuffer currentFb = this.renderer.getCurrentFrameBuffer(); if (currentFb != null && !currentFb.isMultiTarget()) { this.boundDrawBufferId.setValue(currentFb.getTargetIndex()); } - - // Perform light filtering if we have a light filter. - LightList lightList = geom.getWorldLightList(); - - if (lightFilter != null) { - filteredLightList.clear(); - lightFilter.filterLights(geom, filteredLightList); - lightList = filteredLightList; - } + + // updateFilterLight + geom.setFilterLight(lightList); Material material = geom.getMaterial(); - + // If forcedTechnique exists, we try to force it for the render. // If it does not exist in the mat def, we check for forcedMaterial and render the geom if not null. // Otherwise, the geometry is not rendered. @@ -736,7 +879,7 @@ public void preloadScene(Spatial scene) { preloadScene(children.get(i)); } } else if (scene instanceof Geometry) { - // add to the render queue + // addUserEvent to the render queue Geometry gm = (Geometry) scene; if (gm.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); @@ -755,7 +898,7 @@ public void preloadScene(Spatial scene) { } } } - + /** * Flattens the given scene graph into the ViewPort's RenderQueue, * checking for culling as the call goes down the graph recursively. @@ -786,11 +929,11 @@ public void preloadScene(Spatial scene) { * contain the flattened scene graph. */ public void renderScene(Spatial scene, ViewPort vp) { - //reset of the camera plane state for proper culling - //(must be 0 for the first note of the scene to be rendered) + // reset of the camera plane state for proper culling + // (must be 0 for the first note of the scene to be rendered) vp.getCamera().setPlaneState(0); - //rendering the scene - renderSubScene(scene, vp); + // queue the scene for rendering + queueSubScene(scene, vp); } /** @@ -799,13 +942,11 @@ public void renderScene(Spatial scene, ViewPort vp) { * @param scene the scene to be rendered (not null) * @param vp the ViewPort to render in (not null) */ - private void renderSubScene(Spatial scene, ViewPort vp) { - - // check culling first. + private void queueSubScene(Spatial scene, ViewPort vp) { + // check culling first if (!scene.checkCulling(vp.getCamera())) { return; } - scene.runControlRender(this, vp); if (scene instanceof Node) { // Recurse for all children @@ -816,15 +957,14 @@ private void renderSubScene(Spatial scene, ViewPort vp) { for (int i = 0; i < children.size(); i++) { // Restoring cam state before proceeding children recursively vp.getCamera().setPlaneState(camState); - renderSubScene(children.get(i), vp); + queueSubScene(children.get(i), vp); } } else if (scene instanceof Geometry) { - // add to the render queue + // addUserEvent to the render queue Geometry gm = (Geometry) scene; if (gm.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); } - vp.getQueue().addToQueue(gm, scene.getQueueBucket()); } } @@ -1038,7 +1178,7 @@ public void renderTranslucentQueue(ViewPort vp) { } private void setViewPort(Camera cam) { - // this will make sure to update viewport only if needed + // this will make sure to clearReservations viewport only if needed if (cam != prevCam || cam.isViewportChanged()) { viewX = (int) (cam.getViewPortLeft() * cam.getWidth()); viewY = (int) (cam.getViewPortBottom() * cam.getHeight()); @@ -1119,6 +1259,23 @@ public void renderViewPortRaw(ViewPort vp) { flushQueue(vp); } + /** + * Applies the ViewPort's Camera and FrameBuffer in preparation + * for rendering. + * + * @param vp + */ + public void applyViewPort(ViewPort vp) { + renderer.setFrameBuffer(vp.getOutputFrameBuffer()); + setCamera(vp.getCamera(), false); + if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) { + if (vp.isClearColor()) { + renderer.setBackgroundColor(vp.getBackgroundColor()); + } + renderer.clearBuffers(vp.isClearColor(), vp.isClearDepth(), vp.isClearStencil()); + } + } + /** * Renders the {@link ViewPort}. * @@ -1165,96 +1322,16 @@ public void renderViewPortRaw(ViewPort vp) { public void renderViewPort(ViewPort vp, float tpf) { if (!vp.isEnabled()) { return; + } + RenderPipeline pipeline = vp.getFrameGraph(); + if (pipeline == null) { + pipeline = defaultPipeline; } - if (prof != null) { - prof.vpStep(VpStep.BeginRender, vp, null); - } - - SafeArrayList processors = vp.getProcessors(); - if (processors.isEmpty()) { - processors = null; - } - - if (processors != null) { - if (prof != null) { - prof.vpStep(VpStep.PreFrame, vp, null); - } - for (SceneProcessor proc : processors.getArray()) { - if (!proc.isInitialized()) { - proc.initialize(this, vp); - } - proc.setProfiler(this.prof); - if (prof != null) { - prof.spStep(SpStep.ProcPreFrame, proc.getClass().getSimpleName()); - } - proc.preFrame(tpf); - } - } - - renderer.setFrameBuffer(vp.getOutputFrameBuffer()); - setCamera(vp.getCamera(), false); - if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) { - if (vp.isClearColor()) { - renderer.setBackgroundColor(vp.getBackgroundColor()); - } - renderer.clearBuffers(vp.isClearColor(), - vp.isClearDepth(), - vp.isClearStencil()); - } - - if (prof != null) { - prof.vpStep(VpStep.RenderScene, vp, null); - } - List scenes = vp.getScenes(); - for (int i = scenes.size() - 1; i >= 0; i--) { - renderScene(scenes.get(i), vp); - } - - if (processors != null) { - if (prof != null) { - prof.vpStep(VpStep.PostQueue, vp, null); - } - for (SceneProcessor proc : processors.getArray()) { - if (prof != null) { - prof.spStep(SpStep.ProcPostQueue, proc.getClass().getSimpleName()); - } - proc.postQueue(vp.getQueue()); - } - } - - if (prof != null) { - prof.vpStep(VpStep.FlushQueue, vp, null); - } - flushQueue(vp); - - if (processors != null) { - if (prof != null) { - prof.vpStep(VpStep.PostFrame, vp, null); - } - for (SceneProcessor proc : processors.getArray()) { - if (prof != null) { - prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName()); - } - proc.postFrame(vp.getOutputFrameBuffer()); - } - if (prof != null) { - prof.vpStep(VpStep.ProcEndRender, vp, null); - } - } - //renders the translucent objects queue after processors have been rendered - renderTranslucentQueue(vp); - // clear any remaining spatials that were not rendered. - clearQueue(vp); - - /* - * the call to setCamera will indirectly cause a clipRect to be set, must be cleared to avoid surprising results - * if renderer#copyFrameBuffer is used later - */ - renderer.clearClipRect(); - - if (prof != null) { - prof.vpStep(VpStep.EndRender, vp, null); + PipelineContext context = pipeline.fetchPipelineContext(this); + if (context.addPipeline(this, pipeline)) { + usedContexts.add(context); } + pipeline.pipelineRender(this, context, vp, tpf); } /** @@ -1276,7 +1353,7 @@ public void render(float tpf, boolean mainFrameBufferActive) { if (renderer instanceof NullRenderer) { return; } - + uniformBindingManager.newFrame(); if (prof != null) { @@ -1308,12 +1385,20 @@ public void render(float tpf, boolean mainFrameBufferActive) { renderViewPort(vp, tpf); } } + + // cleanup for used pipeline contexts only + System.out.println("flush pipelines"); + for (PipelineContext c : usedContexts) { + System.out.println("flush pipeline stack for "+c); + c.flushPipelineStack(this); + } + usedContexts.clear(); + } - /** * Returns true if the draw buffer target id is passed to the shader. - * + * * @return True if the draw buffer target id is passed to the shaders. */ public boolean getPassDrawBufferTargetIdToShaders() { @@ -1324,7 +1409,7 @@ public boolean getPassDrawBufferTargetIdToShaders() { * Enable or disable passing the draw buffer target id to the shaders. This * is needed to handle FrameBuffer.setTargetIndex correctly in some * backends. - * + * * @param v * True to enable, false to disable (default is true) */ @@ -1337,11 +1422,12 @@ public void setPassDrawBufferTargetIdToShaders(boolean v) { this.forcedOverrides.remove(boundDrawBufferId); } } + /** * Set a render filter. Every geometry will be tested against this filter * before rendering and will only be rendered if the filter returns true. * - * @param filter + * @param filter the render filter */ public void setRenderFilter(Predicate filter) { renderFilter = filter; @@ -1350,7 +1436,7 @@ public void setRenderFilter(Predicate filter) { /** * Returns the render filter that the RenderManager is currently using * - * @return the render filter + * @return the render filter */ public Predicate getRenderFilter() { return renderFilter; diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/RenderPipeline.java new file mode 100644 index 0000000000..3aa658016a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderPipeline.java @@ -0,0 +1,58 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer; + +/** + * Renders a ViewPort. + * + * @author codex + * @param + */ +public interface RenderPipeline { + + /** + * Fetches the PipelineContext this pipeline requires for rendering + * from the RenderManager. + * + * @param rm + * @return pipeline context (not null) + */ + public T fetchPipelineContext(RenderManager rm); + + /** + * Returns true if this pipeline has rendered a viewport this render frame. + * + * @return + */ + public boolean hasRenderedThisFrame(); + + /** + * Called before this pipeline is rendered for the first time this frame. + *

    + * Only called if the pipeline will actually be rendered. + * + * @param rm + */ + public void beginRenderFrame(RenderManager rm); + + /** + * Renders the pipeline. + * + * @param rm + * @param context + * @param vp + * @param tpf + */ + public void pipelineRender(RenderManager rm, T context, ViewPort vp, float tpf); + + /** + * Called after all rendering is complete in a rendering frame this + * pipeline participated in. + * + * @param rm + */ + public void endRenderFrame(RenderManager rm); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java index c92de923ae..f553980a8f 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -128,6 +128,16 @@ public interface Renderer { * @param end The range end */ public void setDepthRange(float start, float end); + + /** + * Sets the range of depth values for objects. + * + * @param range + * @see #setDepthRange(float, float) + */ + public default void setDepthRange(DepthRange range) { + setDepthRange(range.getStart(), range.getEnd()); + } /** * Called when a new frame has been rendered. diff --git a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java index 6f0424fb30..3756dfb588 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java +++ b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,7 @@ import com.jme3.math.ColorRGBA; import com.jme3.post.SceneProcessor; +import com.jme3.renderer.framegraph.FrameGraph; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; @@ -84,6 +85,10 @@ public class ViewPort { * Scene processors currently applied. */ protected final SafeArrayList processors = new SafeArrayList<>(SceneProcessor.class); + /** + * Dedicated framegraph. + */ + protected FrameGraph framegraph; /** * FrameBuffer for output. */ @@ -424,5 +429,29 @@ public void setEnabled(boolean enable) { public boolean isEnabled() { return enabled; } + + /** + * Sets the framegraph used by this viewport for rendering. + *

    + * If null, the render manager's default framegraph, if not null, will be + * used to render this viewport. If all else fails, the default forward + * renderer will be used. + *

    + * default=null + * + * @param framegraph framegraph, or null + */ + public void setFrameGraph(FrameGraph framegraph) { + this.framegraph = framegraph; + } + + /** + * Gets the framegraph used by this viewport for rendering. + * + * @return + */ + public FrameGraph getFrameGraph() { + return framegraph; + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/Connectable.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/Connectable.java new file mode 100644 index 0000000000..6ec9f5c812 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/Connectable.java @@ -0,0 +1,75 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Interface.java to edit this template + */ +package com.jme3.renderer.framegraph; + +/** + * + * @author codex + */ +public interface Connectable { + + public ResourceTicket getInput(String name); + + public ResourceTicket getOutput(String name); + + public TicketGroup getGroup(String name); + + public ResourceTicket addListEntry(String groupName); + + public default ResourceTicket getInput(String name, boolean failOnMiss) { + ResourceTicket t = getInput(name); + if (t == null && failOnMiss) { + throw new NullPointerException("Input ticket \""+name+"\" does not exist."); + } + return t; + } + + public default ResourceTicket getOutput(String name, boolean failOnMiss) { + ResourceTicket t = getOutput(name); + if (t == null && failOnMiss) { + throw new NullPointerException("Output ticket \""+name+"\" does not exist."); + } + return t; + } + + public default TicketGroup getGroup(String name, boolean failOnMiss) { + TicketGroup g = getGroup(name); + if (g == null && failOnMiss) { + throw new NullPointerException("Group \""+name+"\" does not exist."); + } + return g; + } + + public default void makeInput(Connectable source, String sourceTicket, String targetTicket) { + ResourceTicket out = source.getOutput(sourceTicket, true); + if (TicketGroup.isListTicket(targetTicket)) { + ResourceTicket t = addListEntry(TicketGroup.extractGroupName(targetTicket)); + t.setSource(out); + } else { + ResourceTicket target = getInput(targetTicket, true); + target.setSource(out); + } + } + + public default void makeGroupInput(Connectable source, String sourceGroup, String targetGroup, int sourceStart, int targetStart, int length) { + ResourceTicket[] sourceArray = source.getGroup(sourceGroup, true).getArray(); + ResourceTicket[] targetArray = getGroup(targetGroup, true).getArray(); + int n = Math.min(sourceStart+length, sourceArray.length); + int m = Math.min(targetStart+length, targetArray.length); + for (; sourceStart < n && targetStart < m; sourceStart++, targetStart++) { + targetArray[targetStart].setSource(sourceArray[sourceStart]); + } + } + + public default void makeGroupInput(Connectable source, String sourceGroup, String targetGroup) { + makeGroupInput(source, sourceGroup, targetGroup, 0, 0, Integer.MAX_VALUE); + } + + public default void makeInputToList(Connectable source, String sourceTicket, String targetGroup) { + ResourceTicket t = addListEntry(targetGroup); + t.setSource(source.getOutput(sourceTicket)); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGPipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGPipelineContext.java new file mode 100644 index 0000000000..aeabc75a22 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGPipelineContext.java @@ -0,0 +1,64 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph; + +import com.jme3.renderer.AbstractPipelineContext; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.framegraph.debug.GraphEventCapture; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Manages global pipeline context for rendering with FrameGraphs. + * + * @author codex + */ +public class FGPipelineContext extends AbstractPipelineContext { + + private static final Logger LOG = Logger.getLogger(FGPipelineContext.class.getName()); + + private final RenderObjectMap renderObjects; + private GraphEventCapture eventCapture; + + public FGPipelineContext(RenderManager rm) { + renderObjects = new RenderObjectMap(this, true); + } + + @Override + public void beginRenderFrame(RenderManager rm) { + if (eventCapture != null) { + eventCapture.beginRenderFrame(); + } + renderObjects.newFrame(); + } + @Override + public void endRenderFrame(RenderManager rm) { + if (eventCapture != null) { + eventCapture.endRenderFrame(); + } + renderObjects.flushMap(); + if (eventCapture != null && eventCapture.isComplete()) { + try { + eventCapture.export(); + } catch (IOException ex) { + LOG.log(Level.SEVERE, "Error exporting captured event data.", ex); + } + eventCapture = null; + } + } + + public void setEventCapture(GraphEventCapture eventCapture) { + this.eventCapture = eventCapture; + } + + public RenderObjectMap getRenderObjects() { + return renderObjects; + } + public GraphEventCapture getEventCapture() { + return eventCapture; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java new file mode 100644 index 0000000000..2987c0279c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.opencl.CommandQueue; +import com.jme3.opencl.Context; +import com.jme3.profile.AppProfiler; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.debug.GraphEventCapture; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture2D; +import java.util.function.Predicate; +import com.jme3.renderer.GeometryRenderHandler; +import com.jme3.renderer.RendererException; + +/** + * Context for FrameGraph rendering. + *

    + * Provides RenderPasses with access to important objects such as the RenderManager, + * ViewPort, profiler, and fullscreen quad. Utility methods are provided for + * fullscreen quad rendering and camera management. + *

    + * Additionally, the following render settings are handled to ensure settings + * do not leak between renders. + *

      + *
    • Forced technique
    • + *
    • Forced material
    • + *
    • Geometry render handler
    • + *
    • Geometry filter
    • + *
    • Forced render state
    • + *
    + * After each pass execution on the main render thread, {@link #popRenderSettings()} is + * called to reset these settings to what they were before rendering began. + * + * @author codex + */ +public class FGRenderContext { + + private final FrameGraph frameGraph; + private RenderManager renderManager; + private FGPipelineContext context; + private ViewPort viewPort; + private AppProfiler profiler; + private float tpf; + private final FullScreenQuad screen; + private Context clContext; + private CommandQueue clQueue; + + private String forcedTechnique; + private Material forcedMat; + private FrameBuffer frameBuffer; + private GeometryRenderHandler geomRender; + private Predicate geomFilter; + private RenderState renderState; + private int camWidth, camHeight; + + public FGRenderContext(FrameGraph frameGraph) { + this(frameGraph, null); + } + public FGRenderContext(FrameGraph frameGraph, Context clContext) { + this.frameGraph = frameGraph; + this.clContext = clContext; + this.screen = new FullScreenQuad(this.frameGraph.getAssetManager()); + } + + /** + * Targets this context to the viewport. + * + * @param rm + * @param context + * @param vp + * @param profiler + * @param tpf + */ + public void target(RenderManager rm, FGPipelineContext context, ViewPort vp, AppProfiler profiler, float tpf) { + this.renderManager = rm; + this.context = context; + this.viewPort = vp; + this.profiler = profiler; + this.tpf = tpf; + if (viewPort == null) { + throw new NullPointerException("ViewPort cannot be null."); + } + } + /** + * Returns true if the context is ready for rendering. + * + * @return + */ + public boolean isReady() { + return renderManager != null && viewPort != null; + } + + /** + * Saves the current render settings. + */ + public void pushRenderSettings() { + forcedTechnique = renderManager.getForcedTechnique(); + forcedMat = renderManager.getForcedMaterial(); + frameBuffer = renderManager.getRenderer().getCurrentFrameBuffer(); + geomRender = renderManager.getGeometryRenderHandler(); + geomFilter = renderManager.getRenderFilter(); + renderState = renderManager.getForcedRenderState(); + camWidth = viewPort.getCamera().getWidth(); + camHeight = viewPort.getCamera().getHeight(); + } + /** + * Applies saved render settings, except the framebuffer. + */ + public void popRenderSettings() { + renderManager.setForcedTechnique(forcedTechnique); + renderManager.setForcedMaterial(forcedMat); + renderManager.getRenderer().setFrameBuffer(frameBuffer); + renderManager.setGeometryRenderHandler(geomRender); + renderManager.setRenderFilter(geomFilter); + renderManager.setForcedRenderState(renderState); + renderManager.getRenderer().setDepthRange(0, 1); + resizeCamera(camWidth, camHeight, true, false, false); + if (viewPort.isClearColor()) { + renderManager.getRenderer().setBackgroundColor(viewPort.getBackgroundColor()); + } + } + /** + * Applies the saved framebuffer. + */ + public void popFrameBuffer() { + renderManager.getRenderer().setFrameBuffer(frameBuffer); + } + + /** + * Renders the given geometry list with the camera and render handler. + * + * @param queue queue of geometry to render (not null) + * @param cam camera to render with (or null to render with the current viewport camera) + * @param handler handler to render with (or null to render with {@link GeometryRenderHandler#DEFAULT}) + */ + public void renderGeometry(GeometryQueue queue, Camera cam, GeometryRenderHandler handler) { + if (cam == null) { + cam = viewPort.getCamera(); + } + queue.setCamera(cam); + queue.sort(); + queue.render(renderManager, handler); + } + /** + * Renders the material on a fullscreen quad. + * + * @param mat + */ + public void renderFullscreen(Material mat) { + screen.render(renderManager, mat); + } + /** + * Renders the color and depth textures on a fullscreen quad, where + * the color texture informs the color, and the depth texture informs + * the depth. + *

    + * If both color and depth are null, no rendering will be performed + * + * @param color color texture, or null + * @param depth depth texture, or null + */ + public void renderTextures(Texture2D color, Texture2D depth) { + if (color != null) { + //resizeCamera(color.getImage().getWidth(), color.getImage().getHeight(), false, false); + } else if (depth != null) { + //resizeCamera(depth.getImage().getWidth(), depth.getImage().getHeight(), false, false); + } + screen.render(renderManager, color, depth); + } + + /** + * Resizes the camera to the width and height. + * + * @param w new camera width + * @param h new camera height + * @param fixAspect true to fix camera aspect + * @param ortho true to use parallel projection + * @param force true to force setting the width and height + */ + public void resizeCamera(int w, int h, boolean fixAspect, boolean ortho, boolean force) { + Camera cam = viewPort.getCamera(); + if (cam.resize(w, h, fixAspect, force)) { + renderManager.setCamera(cam, ortho); + } + } + + /** + * Sets the OpenCL context for compute shading. + * + * @param clContext + */ + public void setCLContext(Context clContext) { + this.clContext = clContext; + } + /** + * Sets the OpenCL command queue for compute shading. + * + * @param clQueue + */ + public void setCLQueue(CommandQueue clQueue) { + this.clQueue = clQueue; + } + + /** + * Gets the resource list belonging to the framegraph. + * + * @return + */ + public ResourceList getResources() { + return frameGraph.getResources(); + } + /** + * Gets the render manager. + * + * @return + */ + public RenderManager getRenderManager() { + return renderManager; + } + /** + * Gets the context for the FrameGraph pipeline. + * + * @return + */ + public FGPipelineContext getPipelineContext() { + return context; + } + /** + * Gets the viewport currently being rendered. + * + * @return + */ + public ViewPort getViewPort() { + return viewPort; + } + /** + * Gets the profiler. + * + * @return app profiler, or null + */ + public AppProfiler getProfiler() { + return profiler; + } + /** + * Gets the renderer held by the render manager. + * + * @return + */ + public Renderer getRenderer() { + return renderManager.getRenderer(); + } + /** + * Gets the render queue held by the viewport. + * + * @return + */ + public RenderQueue getRenderQueue() { + if (viewPort != null) { + return viewPort.getQueue(); + } else { + return null; + } + } + /** + * Gets the fullscreen quad used for fullscreen renders. + * + * @return + */ + public FullScreenQuad getScreen() { + return screen; + } + /** + * Gets the debug frame capture if one is assigned. + * + * @return + */ + public GraphEventCapture getGraphCapture() { + return context.getEventCapture(); + } + /** + * Gets the OpenCL context for compute shading. + * + * @return + */ + public Context getCLContext() { + return clContext; + } + /** + * Gets the OpenCL command queue assigned to this context. + * + * @return + */ + public CommandQueue getCLQueue() { + return clQueue; + } + /** + * Gets the time per frame. + * + * @return + */ + public float getTpf() { + return tpf; + } + /** + * Gets the camera width. + * + * @return + */ + public int getWidth() { + return viewPort.getCamera().getWidth(); + } + /** + * Gets the camera height. + * + * @return + */ + public int getHeight() { + return viewPort.getCamera().getHeight(); + } + /** + * Returns true if the FrameGraph is asynchronous. + * + * @return + */ + public boolean isAsync() { + return frameGraph.isAsync(); + } + + /** + * Returns true if the app profiler is not null. + * + * @return + */ + public boolean isProfilerAvailable() { + return profiler != null; + } + /** + * Returns true if a debug frame snapshot is assigned. + * + * @return + */ + public boolean isGraphCaptureActive() { + return context.getEventCapture() != null; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java new file mode 100644 index 0000000000..f21f910af5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java @@ -0,0 +1,623 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import com.jme3.renderer.framegraph.modules.ModuleLocator; +import com.jme3.renderer.framegraph.modules.RenderThread; +import com.jme3.renderer.framegraph.modules.ThreadLauncher; +import com.jme3.renderer.framegraph.modules.RenderModule; +import com.jme3.renderer.framegraph.export.ModuleGraphData; +import com.jme3.renderer.framegraph.passes.RenderPass; +import com.jme3.asset.AssetManager; +import com.jme3.asset.FrameGraphKey; +import com.jme3.export.SavableObject; +import com.jme3.opencl.CommandQueue; +import com.jme3.opencl.Context; +import com.jme3.profile.AppProfiler; +import com.jme3.profile.VpStep; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.RenderPipeline; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.client.GraphSetting; +import com.jme3.renderer.framegraph.debug.GraphEventCapture; +import com.jme3.renderer.framegraph.export.FrameGraphData; +import java.util.HashMap; +import java.util.function.Function; +import java.util.logging.Logger; + +/** + * Manages render passes, dependencies, and resources in a node-based parameter system. + *

    + * Rendering is a complicated task, involving many parameters and resources. The framegraph + * aims to simplify rendering from the user's perspective, and limit the creation, binding, + * and destruction of resources wherever possible. + *

    + * Passes are expected to declare and describe beforehand the resources they plan on using + * during execution. Passes can also reference resources declared by other passes. The resource + * manager can determine from these "promises" which passes can be culled, as their contributions + * would ultimately go unused. + *

    + * During execution, passes expected ask the resource manager for the resource the declared or referenced + * earlier. If the resource does not already exist (is virtual) the manager will either create a new + * resource or allocate an existing, unused resource that qualifies based on the description provided + * on declaration. Reallocation is usually preferred to reduce memory footprint. + *

    + * FrameGraph execution occurs in four steps: + *

      + *
    1. Preparation. Passes declare, reserve, and reference resources + * during this step.
    2. + *
    3. Culling. The resource manager determines which resources and + * passes are unused, and culls them. This can often save loads of resources, as many + * passes may not used for large parts of the application.
    4. + *
    5. Execution. Passes that were not culled acquire the resources + * they need, and perform rendering operations. All passes are expected to release + * all resources they declared or referenced in the first step, however, this is done + * automatically by {@link RenderPass}.
    6. + *
    7. Reset. Passes perform whatever post-rendering cleanup is necessary.
    8. + *
    + *

    + * Each step begins only after every qualifying pass has completed the previous step. + *

    + * Passes are executed in the order they appear in the queue. This can sometimes lead + * to unintended consequences, as a pass may use resources generated by a later queue. + * + * @author codex + */ +public class FrameGraph implements RenderPipeline { + + private static final Logger LOG = Logger.getLogger(FrameGraph.class.getName()); + + private final AssetManager assetManager; + private final ResourceList resources; + private final FGRenderContext context; + private final HashMap settings = new HashMap<>(); + private ThreadLauncher launcher = new ThreadLauncher(); + private String name = "FrameGraph"; + private String docAsset = null; + private boolean rendered = false; + private boolean debugPrint = false; + private boolean interrupted = false; + private int nextModuleId = 0; + + /** + * Creates a new blank framegraph. + * + * @param assetManager asset manager (not null) + */ + public FrameGraph(AssetManager assetManager) { + this.assetManager = assetManager; + this.resources = new ResourceList(this); + this.context = new FGRenderContext(this); + launcher.initializeModule(this); + launcher.getOrCreate(PassIndex.MAIN_THREAD); + } + /** + * Creates a new framegraph from the given data. + * + * @param assetManager + * @param data + */ + public FrameGraph(AssetManager assetManager, FrameGraphData data) { + this(assetManager); + applyData(data); + } + /** + * Creates a new framegraph from data obtained by the given asset key. + * + * @param assetManager + * @param key + */ + public FrameGraph(AssetManager assetManager, FrameGraphKey key) { + this(assetManager, assetManager.loadFrameGraph(key)); + } + /** + * Creates a new framegraph from data obtained by the given asset name. + * + * @param assetManager + * @param dataAsset + */ + public FrameGraph(AssetManager assetManager, String dataAsset) { + this(assetManager, assetManager.loadFrameGraph(dataAsset)); + } + + @Override + public FGPipelineContext fetchPipelineContext(RenderManager rm) { + return rm.getOrCreateContext(FGPipelineContext.class, () -> new FGPipelineContext(rm)); + } + @Override + public void beginRenderFrame(RenderManager rm) {} + @Override + public void pipelineRender(RenderManager rm, FGPipelineContext pContext, ViewPort vp, float tpf) { + + if (interrupted) { + throw new IllegalStateException("Cannot render because FrameGraph crashed."); + } + + AppProfiler prof = rm.getProfiler(); + if (prof != null) { + prof.vpStep(VpStep.BeginRender, vp, null); + } + + // prepare + rm.applyViewPort(vp); + context.target(rm, pContext, vp, prof, tpf); + GraphEventCapture cap = context.getGraphCapture(); + if (cap != null) { + cap.renderViewPort(context.getViewPort()); + } + if (prof != null) prof.vpStep(VpStep.FrameGraphSetup, vp, null); + if (!rendered) { + resources.beginRenderFrame( + pContext.getRenderObjects(), pContext.getEventCapture()); + } + launcher.prepareModuleRender(context, new PassIndex(0, 0)); + resources.applyFutureReferences(); + + // cull modules and resources + if (prof != null) prof.vpStep(VpStep.FrameGraphCull, vp, null); + launcher.countReferences(); + resources.cullUnreferenced(); + + // execute + if (prof != null) prof.vpStep(VpStep.FrameGraphExecute, vp, null); + context.pushRenderSettings(); + // execute threads in reverse order so that the 0th thread is executed + // last on JME's main thread. + launcher.executeModuleRender(context); + if (interrupted) { + throw new RuntimeException("FrameGraph execution was interrupted."); + } + context.popFrameBuffer(); + + // reset + if (prof != null) prof.vpStep(VpStep.FrameGraphReset, vp, null); + launcher.resetRender(context); + pContext.getRenderObjects().clearReservations(); + resources.clear(); + rm.getRenderer().clearClipRect(); + rendered = true; + + if (prof != null) { + prof.vpStep(VpStep.EndRender, vp, null); + } + + } + @Override + public boolean hasRenderedThisFrame() { + return rendered; + } + @Override + public void endRenderFrame(RenderManager rm) { + launcher.renderingComplete(); + rendered = false; + } + @Override + public String toString() { + return "FrameGraph ("+name+")"; + } + + /** + * Adds the pass to end of the {@link PassThread} running on the main render thread. + * + * @param + * @param module + * @return given pass + */ + public T add(T module) { + return launcher.getOrCreate(PassIndex.MAIN_THREAD).add(module); + } + /** + * Adds the pass at the index. + *

    + * If the thread index is >= the total number of {@link PassThreads}s, + * a new PassThread will be created for this to be added to. + *

    + * If the queue index is >= the current queue size, the pass will + * be added to the end of the queue. Passes above the added pass + * will have their indexes shifted. + * + * @param + * @param pass + * @param index + * @return + */ + public T add(T pass, PassIndex index) { + return launcher.getOrCreate(index.getThreadIndex()).add(pass, index.queueIndex); + } + + /** + * Adds an array of passes connected in series to the framegraph. + *

    + * The named input ticket on each pass (except the first) is connected to + * the named output ticket on the previous pass, creating a series of connected + * passes. The array length determines the number of passes that will be added + * and connected. + *

    + * Null elements of the array are replaced using the Function. + * + * @param + * @param array array of passes (elements may be null) + * @param factory creates passes where array elements are null (may be null) + * @param inTicket name of the input ticket on each pass + * @param outTicket name of the output ticket on each pass + * @return array of passes + * @see PassThread#addLoop(T[], int, java.util.function.Supplier, java.lang.String, java.lang.String) + */ + public T[] addLoop(T[] array, Function factory, + String inTicket, String outTicket) { + return launcher.getOrCreate(PassIndex.MAIN_THREAD).addLoop(array, -1, factory, inTicket, outTicket); + } + /** + * Adds an array of passes connected in series to the framegraph. + *

    + * The named input ticket on each pass (except the first) is connected to + * the named output ticket on the previous pass. + * + * @param + * @param array array of passes (elements may be null) + * @param index index that passes are added to + * @param function creates passes where array elements are null (may be null) + * @param inTicket name of the input ticket on each pass + * @param outTicket name of the output ticket on each pass + * @return array of passes + * @see PassThread#addLoop(T[], int, java.util.function.Supplier, java.lang.String, java.lang.String) + */ + public T[] addLoop(T[] array, PassIndex index, + Function function, String inTicket, String outTicket) { + return launcher.getOrCreate(index.getThreadIndex()).addLoop( + array, index.queueIndex, function, inTicket, outTicket); + } + + /** + * Gets the first pass that qualifies. + * + * @param + * @param by + * @return first qualifying pass, or null + */ + public T get(ModuleLocator by) { + return launcher.get(by); + } + + /** + * Registers the object under the name in the settings map. + *

    + * Registered objects can be referenced by passes by name. Any existing + * object already registered under the name will be replaced. + * + * @param + * @param name + * @param object + * @return given object + */ + public T setSetting(String name, T object) { + settings.put(name, object); + return object; + } + /** + * Registers the object under the name in the settings map, and creates + * a {@link GraphSetting} with the same name. + * + * @param + * @param name + * @param object + * @param defaultValue + * @param create true to create a GraphSetting, otherwise one will not be created and null returned + * @return created graph setting + * @see #setSetting(java.lang.String, java.lang.Object) + */ + public GraphSetting setSetting(String name, T object, T defaultValue) { + setSetting(name, object); + return new GraphSetting<>(name, defaultValue); + } + /** + * Sets an integer in the settings map based on a boolean value. + *

    + * If the boolean is true, 0 is written, otherwise -1 is written. Commonly + * used to enable/disable features by switching a Junction to 0 or -1. + * + * @param name + * @param enable + * @return + * @see #setSetting(java.lang.String, java.lang.Object) + */ + public int enableFeature(String name, boolean enable) { + return setSetting(name, enable ? 0 : -1); + } + /** + * Gets the object registered under the name in the settings map, + * or null if none is registered. + * + * @param + * @param name + * @return registered object, or null + */ + public T getSetting(String name) { + Object obj = settings.get(name); + if (obj != null) { + return (T)obj; + } else { + return null; + } + } + /** + * Removes the object registered under the name in the settings map. + * + * @param + * @param name + * @return removed object, or null + */ + public T removeSetting(String name) { + Object obj = settings.remove(name); + if (obj != null) { + return (T)obj; + } else { + return null; + } + } + /** + * Gets the settings map. + *

    + * The returned map may be modified. + * + * @return + */ + public HashMap getSettingsMap() { + return settings; + } + + /** + * Sets the name of this FrameGraph. + * + * @param name + */ + public void setName(String name) { + this.name = name; + } + /** + * Sets the asset path corresponding to a documentation file for + * this FrameGraph. + * + * @param docs asset path, or null for no documentation + */ + public void setDocumentationAsset(String docs) { + this.docAsset = docs; + } + /** + * Sets the OpenCL context used for compute shading. + * + * @param clContext + */ + public void setCLContext(Context clContext) { + context.setCLContext(clContext); + } + /** + * Assigns this framegraph to the OpenCL command queue. + *

    + * Passes do not need to use the assigned command queue, but are encouraged to. + * + * @param clQueue + */ + public void setCLQueue(CommandQueue clQueue) { + context.setCLQueue(clQueue); + } + /** + * Enables printing of information useful for debugging. + *

    + * default=false + * + * @param debugPrint + */ + public void enableDebugPrint(boolean debugPrint) { + this.debugPrint = debugPrint; + } + + /** + * Called automatically to notify the FrameGraph that a {@link RenderThread} has completed execution. + * + * @param thread + */ + public void notifyThreadComplete(RenderThread thread) { + launcher.notifyThreadComplete(thread); + } + /** + * Called automatically when a rendering exception occurs. + * + * @param ex + */ + public void interruptRendering() { + if (!interrupted) { + interrupted = true; + launcher.interrupt(); + } + } + + /** + * Gets the {@link AssetManager} assigned to this FrameGraph. + * + * @return + */ + public AssetManager getAssetManager() { + return assetManager; + } + /** + * Gets the {@link ResourceList} that manages resources for this FrameGraph. + * + * @return + */ + public ResourceList getResources() { + return resources; + } + /** + * Gets the rendering context. + * + * @return + */ + public FGRenderContext getContext() { + return context; + } + /** + * Gets the RenderManager. + * + * @return + */ + public RenderManager getRenderManager() { + return context.getRenderManager(); + } + /** + * Gets the OpenCL context used for compute shading, or null if not set. + * + * @return + */ + public Context getCLContext() { + return context.getCLContext(); + } + /** + * Gets the name of this framegraph. + * + * @return + */ + public String getName() { + return name; + } + /** + * Returns an asset path corresponding to a documentation file for + * this FrameGraph. + * + * @return asset path, or null if no documentation is available + */ + public String getDocumantationAsset() { + return docAsset; + } + /** + * Returns true if this framegraph is running asynchronous {@link PassThread}s. + * + * @return + */ + public boolean isAsync() { + return launcher.isAsync(); + } + /** + * + * @return + */ + public boolean isDebugPrintEnabled() { + return debugPrint; + } + + /** + * Applies the {@link FrameGraphData} to this FrameGraph. + * + * @param data + * @return + */ + public final FrameGraph applyData(FrameGraphData data) { + name = data.getName(); + for (SavableObject obj : data.getSettings()) { + setSetting(obj.getName(), obj.getObject()); + } + return applyData(data.getModules()); + } + /** + * Applies the {@link ModuleGraphData} to this FrameGraph. + * + * @param data + * @return this instance + * @throws IllegalStateException if the tree root is already a member of a FrameGraph + */ + public final FrameGraph applyData(ModuleGraphData data) { + launcher.cleanupModule(); + launcher = data.getRootModule(ThreadLauncher.class); + if (launcher.isAssigned()) { + throw new IllegalStateException("Cannot apply tree root that is already a member of a FrameGraph."); + } + launcher.initializeModule(this); + return this; + } + /** + * Applies the {@link ModuleGraphData} to this FrameGraph. + * + * @param data + * @return this instance + * @throws ClassCastException if the object is not an instance of {@link ModuleGraphData}. + * @throws NullPointerException if the object is null + */ + public FrameGraph applyData(Object data) { + if (data != null) { + if (data instanceof FrameGraphData) { + return applyData((FrameGraphData)data); + } else if (data instanceof ModuleGraphData) { + return applyData((ModuleGraphData)data); + } else { + throw new ClassCastException(data.getClass()+" cannot be accepted as usable data."); + } + } else { + throw new NullPointerException("Data cannot be null."); + } + } + /** + * Loads and applies {@link ModuleGraphData} from the key. + * + * @param key + * @return + */ + public FrameGraph loadData(FrameGraphKey key) { + return applyData(assetManager.loadFrameGraph(key)); + } + /** + * Loads and applies {@link ModuleGraphData} at the specified asset path. + * + * @param assetPath + * @return + */ + public FrameGraph loadData(String assetPath) { + return applyData(assetManager.loadFrameGraph(assetPath)); + } + /** + * Creates exportable snapshot of this FrameGraph as {@link ModuleGraphData}. + * + * @return + */ + public ModuleGraphData createModuleData() { + return new ModuleGraphData(launcher); + } + + /** + * Returns the next unique module id for this FrameGraph. + * + * @return + */ + public final int getNextId() { + return nextModuleId++; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java new file mode 100644 index 0000000000..df6dca5873 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import com.jme3.asset.AssetManager; +import com.jme3.renderer.framegraph.light.TiledRenderGrid; +import com.jme3.renderer.framegraph.client.GraphSetting; +import com.jme3.renderer.framegraph.passes.*; + +/** + * Utility class for constructing common {@link FrameGraph}s in code. + * + * @author codex + */ +public class FrameGraphFactory { + + /** + * Constructs a standard forward FrameGraph, with no controllable settings. + * + * @param assetManager + * @return forward framegraph + */ + public static FrameGraph forward(AssetManager assetManager) { + + FrameGraph fg = new FrameGraph(assetManager); + fg.setName("Forward"); + + SceneEnqueuePass enqueue = fg.add(new SceneEnqueuePass(true, true)); + QueueMergePass merge = fg.add(new QueueMergePass(5)); + OutputGeometryPass out = fg.add(new OutputGeometryPass()); + + merge.makeInput(enqueue, "Opaque", "Queues[0]"); + merge.makeInput(enqueue, "Sky", "Queues[1]"); + merge.makeInput(enqueue, "Transparent", "Queues[2]"); + merge.makeInput(enqueue, "Gui", "Queues[3]"); + merge.makeInput(enqueue, "Translucent", "Queues[4]"); + + out.makeInput(merge, "Result", "Geometry"); + + return fg; + + } + + /** + * Constructs a deferred FrameGraph. + * + * @param assetManager + * @param tiled true to enable tiled lighting + * @return deferred framegraph + */ + public static FrameGraph deferred(AssetManager assetManager, boolean tiled) { + return deferred(assetManager, tiled, false); + } + + /** + * Constructs a deferred FrameGraph. + * + * @param assetManager + * @param tiled true to enable tiled lighting + * @param async true to enable multithreading optimizations + * @return deferred framegraph + */ + public static FrameGraph deferred(AssetManager assetManager, boolean tiled, boolean async) { + + FrameGraph fg = new FrameGraph(assetManager); + fg.setName(tiled ? "TiledDeferred" : "Deferred"); + + int asyncThread = async ? 1 : PassIndex.MAIN_THREAD; + + SceneEnqueuePass enqueue = fg.add(new SceneEnqueuePass(true, true)); + Attribute tileInfoAttr = fg.add(new Attribute()); + Junction tileJunct1 = fg.add(new Junction(1, 1)); + GBufferPass gbuf = fg.add(new GBufferPass()); + LightImagePass lightImg = fg.add(new LightImagePass(), new PassIndex(asyncThread, -1)); + Junction lightJunct = fg.add(new Junction(1, 6)); + Junction tileJunct2 = fg.add(new Junction(1, 2)); + DeferredPass deferred = fg.add(new DeferredPass()); + OutputPass defOut = fg.add(new OutputPass(0f)); + QueueMergePass merge = fg.add(new QueueMergePass(4), new PassIndex(asyncThread, -1)); + OutputGeometryPass geometry = fg.add(new OutputGeometryPass()); + + gbuf.makeInput(enqueue, "Opaque", "Geometry"); + + GraphSetting tileInfo = new GraphSetting<>("TileInfo", new TiledRenderGrid()); + tileInfoAttr.setName("TileInfo"); + tileInfoAttr.setSource(tileInfo); + + GraphSetting tileToggle = fg.setSetting("EnableLightTiles", tiled ? 0 : -1, -1); + tileJunct1.makeInput(tileInfoAttr, Attribute.OUTPUT, Junction.getInput(0)); + tileJunct1.setIndexSource(tileToggle); + + lightImg.makeInput(enqueue, "OpaqueLights", "Lights"); + lightImg.makeInput(tileJunct1, Junction.getOutput(), "TileInfo"); + + GraphSetting lightPackMethod = fg.setSetting("EnableLightTextures", tiled ? 0 : -1, -1); + lightJunct.setName("LightPackMethod"); + lightJunct.makeGroupInput(lightImg, "Textures", Junction.getInput(0), 0, 0, 3); + lightJunct.makeInput(lightImg, "NumLights", Junction.getInput(0, 3)); + lightJunct.makeInput(lightImg, "Ambient", Junction.getInput(0, 4)); + lightJunct.makeInput(lightImg, "Probes", Junction.getInput(0, 5)); + lightJunct.setIndexSource(lightPackMethod); + + tileJunct2.makeGroupInput(lightImg, "TileTextures", Junction.getInput(0)); + tileJunct2.setIndexSource(tileToggle); + + deferred.makeGroupInput(gbuf, "GBufferData", "GBufferData"); + deferred.makeInput(enqueue, "OpaqueLights", "Lights"); + deferred.makeGroupInput(lightJunct, Junction.getOutput(), "LightTextures", 0, 0, 3); + deferred.makeInput(lightJunct, Junction.getOutput(3), "NumLights"); + deferred.makeInput(lightJunct, Junction.getOutput(4), "Ambient"); + deferred.makeInput(lightJunct, Junction.getOutput(5), "Probes"); + deferred.makeGroupInput(tileJunct2, Junction.getOutput(), "TileTextures"); + + defOut.makeInput(deferred, "Color", "Color"); + defOut.makeInput(gbuf, "GBufferData[4]", "Depth"); + + merge.makeInput(enqueue, "Sky", "Queues[0]"); + merge.makeInput(enqueue, "Transparent", "Queues[1]"); + merge.makeInput(enqueue, "Gui", "Queues[2]"); + merge.makeInput(enqueue, "Translucent", "Queues[3]"); + + geometry.makeInput(merge, "Result", "Geometry"); + + return fg; + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FullScreenQuad.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FullScreenQuad.java new file mode 100644 index 0000000000..648ceac898 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FullScreenQuad.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import com.jme3.asset.AssetManager; +import com.jme3.light.LightList; +import com.jme3.material.Material; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; + +/** + * Renders materials and textures on a static fullscreen quad geometry. + *

    + * Compatible with Filter shaders. + * + * @author codex + */ +public class FullScreenQuad { + + private final Geometry geometry; + private final Material transferMat; + + /** + * + * @param assetManager + */ + public FullScreenQuad(AssetManager assetManager) { + Mesh mesh = new Mesh(); + mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer( + 0, 0, 0, + 1, 0, 0, + 0, 1, 0, + 1, 1, 0 + )); + mesh.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer( + 0, 1, 2, + 1, 3, 2 + )); + mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer( + 1, 0, + 1, 1, + 0, 0, + 0, 1 + )); + mesh.updateBound(); + mesh.updateCounts(); + mesh.setStatic(); + geometry = new Geometry("Screen", mesh); + transferMat = new Material(assetManager, "Common/MatDefs/ShadingCommon/TextureTransfer.j3md"); + } + + /** + * Renders the material on the quad. + * + * @param rm + * @param material + */ + public void render(RenderManager rm, Material material) { + geometry.setMaterial(material); + geometry.updateGeometricState(); + rm.renderGeometry(geometry); + } + /** + * Renders the material with the light list on the quad. + * + * @param rm + * @param material + * @param lights + */ + public void render(RenderManager rm, Material material, LightList lights) { + geometry.setMaterial(material); + geometry.updateGeometricState(); + rm.renderGeometry(geometry, lights); + } + /** + * Renders the color and depth textures on the quad, where the depth texture + * informs the depth value. + * + * @param rm + * @param color color texture, or null + * @param depth depth texture, or null + */ + public void render(RenderManager rm, Texture2D color, Texture2D depth) { + boolean writeDepth = depth != null; + if (color != null || writeDepth) { + transferMat.setTexture("ColorMap", color); + transferMat.setTexture("DepthMap", depth); + transferMat.getAdditionalRenderState().setDepthTest(writeDepth); + transferMat.getAdditionalRenderState().setDepthWrite(writeDepth); + render(rm, transferMat); + setAlphaDiscard(null); + } + } + + /** + * Sets the alpha discard threshold for next texture render. + *

    + * Fragments with alpha values below or equal to the threshold will be discarded. + * + * @param alphaDiscard + */ + public void setAlphaDiscard(Float alphaDiscard) { + if (alphaDiscard == null) { + transferMat.clearParam("AlphaDiscard"); + } else { + transferMat.setFloat("AlphaDiscard", alphaDiscard); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/GeometryQueue.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/GeometryQueue.java new file mode 100644 index 0000000000..a2b793d91c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/GeometryQueue.java @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import com.jme3.renderer.DepthRange; +import com.jme3.renderer.Camera; +import com.jme3.renderer.GeometryRenderHandler; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.queue.GeometryComparator; +import com.jme3.renderer.queue.NullComparator; +import com.jme3.scene.Geometry; +import com.jme3.util.ListSort; +import java.util.ArrayList; + +/** + * Queue of ordered geometries for rendering. + *

    + * Similar to {@link GeometryList}, but designed for use in FrameGraphs. Specifically, + * this can store other GeometryQueues internally, essentially making queues able + * to merge very quickly and still maintain geometry order. + * + * @author codex + */ +public class GeometryQueue { + + private static final int DEFAULT_SIZE = 32; + + private Geometry[] geometries; + private GeometryComparator comparator; + private Camera cam; + private final ListSort listSort; + private final ArrayList internalQueues = new ArrayList<>(); + private final DepthRange depth = new DepthRange(); + private boolean updateFlag = true; + private boolean perspective = true; + private int size; + + /** + * Geometry queue with default settings and a {@link NullComparator}. + */ + public GeometryQueue() { + this(new NullComparator()); + } + /** + * Geometry queue with default settings and the given comparator. + * + * @param comparator + */ + public GeometryQueue(GeometryComparator comparator) { + size = 0; + geometries = new Geometry[DEFAULT_SIZE]; + this.comparator = comparator; + listSort = new ListSort(); + } + + /** + * Sorts this queue and all internal queues. + */ + public void sort() { + if (updateFlag && size > 1) { + // sort the spatial list using the comparator + if (listSort.getLength() != size) { + listSort.allocateStack(size); + } + listSort.sort(geometries, comparator); + updateFlag = false; + } + for (GeometryQueue q : internalQueues) { + q.sort(); + } + } + /** + * Renders this queue and all internal queues. + * + * @param renderManager + * @param handler + */ + public void render(RenderManager renderManager, GeometryRenderHandler handler) { + GeometryRenderHandler h; + if (handler == null) { + h = GeometryRenderHandler.DEFAULT; + } else { + h = handler; + } + renderManager.getRenderer().setDepthRange(depth); + if (!perspective) { + renderManager.setCamera(cam, true); + } + for (Geometry g : geometries) { + if (g == null) continue; + h.renderGeometry(renderManager, g); + g.queueDistance = Float.NEGATIVE_INFINITY; + } + if (!perspective) { + renderManager.setCamera(cam, false); + } + renderManager.getRenderer().setDepthRange(DepthRange.IDENTITY); + for (GeometryQueue q : internalQueues) { + q.render(renderManager, handler); + } + } + + /** + * Adds the geometry to the queue. + * + * @param g + */ + public void add(Geometry g) { + if (size == geometries.length) { + Geometry[] temp = new Geometry[size * 2]; + System.arraycopy(geometries, 0, temp, 0, size); + geometries = temp; // original list replaced by double-size list + } + geometries[size++] = g; + updateFlag = true; + } + /** + * Sets the element at the given index. + * + * @param index The index to set + * @param value The value + */ + public void set(int index, Geometry value) { + geometries[index] = value; + updateFlag = true; + } + /** + * Adds the geometry queue. + * + * @param q + */ + public void add(GeometryQueue q) { + internalQueues.add(q); + } + /** + * Adds the geometry queue at the index. + * + * @param q + * @param index + */ + public void add(GeometryQueue q, int index) { + internalQueues.add(index, q); + } + + /** + * Resets list size to 0. + *

    + * Clears internal queue list, but does not clear internal queues. + */ + public void clear() { + for (int i = 0; i < size; i++) { + geometries[i] = null; + } + internalQueues.clear(); + updateFlag = true; + size = 0; + } + + /** + * Marks this list as requiring sorting. + */ + public void setUpdateNeeded() { + updateFlag = true; + } + /** + * Sets the comparator used to sort geometries. + * + * @param comparator + */ + public void setComparator(GeometryComparator comparator) { + if (this.comparator != comparator) { + this.comparator = comparator; + updateFlag = true; + } + } + /** + * Set the camera that will be set on the geometry comparators + * via {@link GeometryComparator#setCamera(com.jme3.renderer.Camera)}. + * + * @param cam Camera to use for sorting. + */ + public void setCamera(Camera cam) { + if (this.cam != cam) { + this.cam = cam; + comparator.setCamera(this.cam); + updateFlag = true; + } + for (GeometryQueue q : internalQueues) { + q.setCamera(cam); + } + } + /** + * Sets the depth range geometries in this queue (not internal queues) + * are rendered at. + * + * @param depth + */ + public void setDepth(DepthRange depth) { + this.depth.set(depth); + } + /** + * Sets this queue (not internal queues) to render in perspective mode + * (as opposed to parallel projection or orthogonal). + * + * @param perspective + */ + public void setPerspective(boolean perspective) { + this.perspective = perspective; + } + + /** + * Returns the GeometryComparator that this Geometry list uses + * for sorting. + * + * @return the pre-existing instance + */ + public GeometryComparator getComparator() { + return comparator; + } + /** + * Returns the number of elements in this GeometryList. + * + * @return Number of elements in the list + */ + public int size() { + return size; + } + /** + * Returns the element at the given index. + * + * @param index The index to lookup + * @return Geometry at the index + */ + public Geometry get(int index) { + return geometries[index]; + } + /** + * + * @return + */ + public DepthRange getDepth() { + return depth; + } + /** + * + * @return + */ + public boolean isPerspective() { + return perspective; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/GraphRenderException.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/GraphRenderException.java new file mode 100644 index 0000000000..633a0f0cc3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/GraphRenderException.java @@ -0,0 +1,18 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph; + +/** + * Raised when a fatal error occurs during FrameGraph processes. + * + * @author codex + */ +public class GraphRenderException extends RuntimeException { + + public GraphRenderException(String message) { + super(message); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java new file mode 100644 index 0000000000..290a32d8ed --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +/** + * Holds indices pointing to a render pass held within a framegraph. + *

    + * The FrameGraph system uses indices to schedule resources, particularly + * with reservations. + *

    + * Negative indices denote using defaults assigned by the FrameGraph. + * + * @author codex + */ +public final class PassIndex { + + /** + * Index of the main render thread. + */ + public static final int MAIN_THREAD = 0; + + /** + * Index that conforms to defaults only. + */ + public static final PassIndex DEFAULT = new PassIndex(); + + /** + * Index of the thread the pass is executed on. + */ + public int threadIndex; + /** + * Index in the thread the pass is executed at. + */ + public int queueIndex; + + /** + * Creates a pass index with all defaults (negative indices). + */ + public PassIndex() { + this(MAIN_THREAD, -1); + } + /** + * + * @param queueIndex + */ + public PassIndex(int queueIndex) { + this(MAIN_THREAD, queueIndex); + } + /** + * + * @param threadIndex + * @param queueIndex + */ + public PassIndex(int threadIndex, int queueIndex) { + this.threadIndex = threadIndex; + this.queueIndex = queueIndex; + } + /** + * + * @param index + */ + public PassIndex(PassIndex index) { + this(index.threadIndex, index.queueIndex); + } + + /** + * + * @param index index to set to (not null) + * @return this + */ + public PassIndex set(PassIndex index) { + threadIndex = index.threadIndex; + queueIndex = index.queueIndex; + return this; + } + /** + * + * @param threadIndex + * @param queueIndex + * @return this + */ + public PassIndex set(int threadIndex, int queueIndex) { + this.threadIndex = threadIndex; + this.queueIndex = queueIndex; + return this; + } + /** + * + * @param threadIndex + * @return this + */ + public PassIndex setThreadIndex(int threadIndex) { + this.threadIndex = threadIndex; + return this; + } + /** + * + * @param queueIndex + * @return this + */ + public PassIndex setQueueIndex(int queueIndex) { + this.queueIndex = queueIndex; + return this; + } + + /** + * Shifts the thread index up or down one if the thread index is greater + * than the given index. + * + * @param i + * @param pos + * @return new thread index + */ + public int shiftThread(int i, boolean pos) { + if (threadIndex > i) { + threadIndex += pos ? 1 : -1; + } + return threadIndex; + } + /** + * Shifts the queue index up or down one if the thread index is greater than + * the given index. + * + * @param i + * @param pos + * @return new queue index + */ + public int shiftQueue(int i, boolean pos) { + if (queueIndex > i) { + queueIndex += pos ? 1 : -1; + } + return queueIndex; + } + + /** + * Gets the index of the thread the pass is executed on. + * + * @return + */ + public int getThreadIndex() { + return threadIndex; + } + /** + * Gets the index of the pass in a queue. + * + * @return + */ + public int getQueueIndex() { + return queueIndex; + } + + /** + * Returns true if this thread index matches the main thread index + * + * @return + */ + public boolean isMainThread() { + return threadIndex == MAIN_THREAD; + } + /** + * Returns true if the default queue index is used (queue index is negative). + * + * @return + */ + public boolean useDefaultQueueIndex() { + return queueIndex < 0; + } + + /** + * Throws an exception if either index is less than zero. + */ + public void requirePositive() { + if (threadIndex < 0) { + throw new IndexOutOfBoundsException("Thread index cannot be negative in this context."); + } + if (queueIndex < 0) { + throw new IndexOutOfBoundsException("Queue index cannot be negative in this context."); + } + } + + @Override + public int hashCode() { + int hash = 7; + hash = 19 * hash + this.threadIndex; + hash = 19 * hash + this.queueIndex; + return hash; + } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PassIndex other = (PassIndex) obj; + if (this.threadIndex != other.threadIndex) { + return false; + } + return this.queueIndex == other.queueIndex; + } + @Override + public String toString() { + return PassIndex.class.getSimpleName()+"[thread="+threadIndex+", queue="+queueIndex+']'; + } + @Override + public PassIndex clone() { + return new PassIndex(threadIndex, queueIndex); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java new file mode 100644 index 0000000000..a1b5ec8e8b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import com.jme3.renderer.framegraph.definitions.ResourceDef; +import com.jme3.util.NativeObject; +import java.util.LinkedList; +import java.util.function.Consumer; + +/** + * Handles a raw object used for rendering processes within a FrameGraph. + * + * @author codex + * @param + */ +public class RenderObject { + + private static final Consumer DEFAULT = object -> {}; + private static final Consumer NATIVE = object -> object.dispose(); + + private static long nextId = 0; + + private final long id; + private final T object; + private final LinkedList reservations = new LinkedList<>(); + private int timeoutDuration; + private int timeout = 0; + private boolean acquired = false; + private boolean constant = false; + private boolean inspect = false; + private boolean prioritized = false; + private Consumer disposer; + + /** + * + * @param def + * @param object + * @param timeout + */ + public RenderObject(ResourceDef def, T object, int timeout) { + this.id = nextId++; + if (object == null) { + throw new NullPointerException("Object cannot be null."); + } + this.object = object; + this.timeoutDuration = def.getStaticTimeout(); + if (this.timeoutDuration < 0) { + this.timeoutDuration = timeout; + } + disposer = def.getDisposalMethod(); + if (disposer != null); + else if (object instanceof NativeObject) { + this.disposer = NATIVE; + } else { + this.disposer = DEFAULT; + } + } + + /** + * Starts an inspection of this object. + *

    + * This blocks other threads from inspecting this object at the same time. + * Often, other threads will save this object for later, and continue inspecting + * other objects in the meantime. + *

    + * Note that this is not a threadsafe operation, meaning some threads may "escape" + * this check. A synchronized block should be used to catch these exceptions. + * + * @see #endInspect() + */ + public void startInspect() { + inspect = true; + } + /** + * Ends an inspection of this object. + * + * @see #startInspect() + */ + public void endInspect() { + inspect = false; + } + /** + * Returns true if this object is currently being inspected. + * + * @return + */ + public boolean isInspect() { + return inspect; + } + + /** + * Marks this RenderObject as priorized, but not officially claimed, by a thread. + *

    + * This will block threads from attempting to reallocate this as an indirect + * resource. + * + * @param prioritized + */ + public void setPrioritized(boolean prioritized) { + this.prioritized = prioritized; + } + /** + * Returns true if this RenderObject is prioritized. + * + * @return + */ + public boolean isPrioritized() { + return prioritized; + } + + /** + * Acquires this render object for use. + */ + public void acquire() { + if (acquired) { + throw new IllegalStateException("Already acquired."); + } + timeout = timeoutDuration; + acquired = true; + } + /** + * Releases this render object from use. + */ + public void release() { + if (!acquired) { + throw new IllegalStateException("Already released."); + } + acquired = false; + } + /** + * Reserves this render object for use at the specified render pass index. + * + * @param index + */ + public void reserve(PassIndex index) { + reservations.add(new Reservation(index)); + } + /** + * Disposes the internal object. + */ + public void dispose() { + // ensure this cannot be acquired + acquired = true; + disposer.accept(object); + } + + /** + * Claims the reservation pertaining to the index. + * + * @param index + * @return true if a reservation was claimed. + */ + public boolean claimReservation(PassIndex index) { + for (Reservation r : reservations) { + if (r.claim(index)) return true; + } + return false; + } + /** + * Determine if reallocating this object with the context would + * result in a violation of a reservation. + * + * @param frame + * @return true if this object is reserved within the timeframe + */ + public boolean isReservedWithin(TimeFrame frame) { + for (Reservation r : reservations) { + if (r.violates(frame)) return true; + } + return false; + } + /** + * Clears all reservations. + */ + public void clearReservations() { + reservations.clear(); + } + + /** + * Decrements the integer tracking frames until the object is deemed + * abandoned. + *

    + * Abandoned objects are removed and disposed. + * + * @return true if the object is not considered abandoned + */ + public boolean tickTimeout() { + return timeout-- > 0; + } + + /** + * Sets this as constant, which blocks reallocations until the rendering ends. + * + * @param constant + */ + public void setConstant(boolean constant) { + this.constant = constant; + } + + /** + * Gets the id of this render object. + * + * @return + */ + public long getId() { + return id; + } + /** + * Gets the internal object. + * + * @return + */ + public T getObject() { + return object; + } + /** + * Returns true if this render object is acquired (and not yet released). + * + * @return + */ + public boolean isAcquired() { + return acquired; + } + /** + * Returns true if this render object is constant. + * + * @return + */ + public boolean isConstant() { + return constant; + } + + /** + * Gets the next unique id of RenderObjects. + * + * @return + */ + public static long getNextId() { + return nextId; + } + + private static class Reservation { + + private final PassIndex index; + private boolean claimed = false; + + public Reservation(PassIndex index) { + this.index = index; + } + + public boolean claim(PassIndex index) { + if (this.index.equals(index)) { + claimed = true; + return true; + } + return false; + } + public boolean violates(TimeFrame frame) { + return !claimed && (frame.isAsync() || frame.getThreadIndex() != index.getThreadIndex()); + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObjectMap.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObjectMap.java new file mode 100644 index 0000000000..3120cffbfb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObjectMap.java @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.framegraph.debug.GraphEventCapture; +import com.jme3.renderer.framegraph.definitions.ResourceDef; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages creation, reallocation, and disposal of {@link RenderObject}s. + * + * @author codex + */ +public class RenderObjectMap { + + private final FGPipelineContext context; + private final Map objectMap; + private int staticTimeout = 1; + + // statistics + private int totalAllocations = 0; + private int officialReservations = 0; + private int completedReservations = 0; + private int failedReservations = 0; + private int objectsCreated = 0; + private int objectsReallocated = 0; + private int totalObjects = 0; + private int flushedObjects = 0; + + /** + * + * @param context + * @param async + */ + public RenderObjectMap(FGPipelineContext context, boolean async) { + this.context = context; + objectMap = new ConcurrentHashMap<>(); + } + + private RenderObject create(ResourceDef def) { + return create(def, def.createResource()); + } + private RenderObject create(ResourceDef def, T value) { + RenderObject obj = new RenderObject(def, value, staticTimeout); + objectMap.put(obj.getId(), obj); + return obj; + } + private boolean isAvailable(RenderObject object) { + return !object.isAcquired() && !object.isConstant(); + } + + /** + * Allocates a render object to the ResourceView. + *

    + * First, if this resource holds an object id, then corresponding render object, + * if it still exists, will be tried for reallocation. If that fails, each render object + * will be tried for reallocation. Finally, if that fails, a new render object + * will be created and allocated to the resource. + * + * @param + * @param resource + * @param async true to execute asynchronous methods, otherwise synchronous methods will + * be used in the interest of efficiency + */ + public void allocate(ResourceView resource, boolean async) { + if (async) { + allocateAsync(resource); + } else { + allocateSync(resource); + } + } + + private void allocateSync(ResourceView resource) { + if (resource.isUndefined()) { + throw new IllegalArgumentException("Cannot allocate object to an undefined resource."); + } + GraphEventCapture cap = context.getEventCapture(); + totalAllocations++; + ResourceDef def = resource.getDefinition(); + if (def.isUseExisting()) { + // first try allocating a specific object, which is much faster + if (allocateSpecificSync(resource)) { + return; + } + // find object to allocate + T indirectRes = null; + RenderObject indirectObj = null; + for (RenderObject obj : objectMap.values()) { + if (isAvailable(obj) && !obj.isReservedWithin(resource.getLifeTime())) { + // try applying a direct resource + T r = def.applyDirectResource(obj.getObject()); + if (r != null) { + resource.setObject(obj, r); + if (cap != null) cap.reallocateObject(obj.getId(), resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + objectsReallocated++; + return; + } + // then try applying an indirect resource, which is not as desirable + if (indirectObj == null) { + indirectRes = def.applyIndirectResource(obj.getObject()); + if (indirectRes != null) { + indirectObj = obj; + } + } + } + } + // allocate indirect object + if (indirectObj != null) { + resource.setObject(indirectObj, indirectRes); + if (cap != null) cap.reallocateObject(indirectObj.getId(), resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + objectsReallocated++; + return; + } + } + // create new object + resource.setObject(create(def)); + if (cap != null) cap.createObject(resource.getObject().getId(), + resource.getIndex(), resource.getResource().getClass().getSimpleName()); + objectsCreated++; + } + private boolean allocateSpecificSync(ResourceView resource) { + GraphEventCapture cap = context.getEventCapture(); + ResourceDef def = resource.getDefinition(); + long id = resource.getTicket().getObjectId(); + if (id < 0) return false; + // allocate reserved object + RenderObject obj = objectMap.get(id); + if (obj != null) { + if (cap != null) cap.attemptReallocation(id, resource.getIndex()); + if (isAvailable(obj) && (obj.claimReservation(resource.getProducer().getIndex()) + || !obj.isReservedWithin(resource.getLifeTime()))) { + // reserved object is only applied if it is accepted by the definition + T r = def.applyDirectResource(obj.getObject()); + if (r == null) { + r = def.applyIndirectResource(obj.getObject()); + } + if (r != null) { + resource.setObject(obj, r); + if (cap != null) cap.reallocateObject(id, resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + completedReservations++; + objectsReallocated++; + return true; + } + } + } + failedReservations++; + return false; + } + private void allocateAsync(ResourceView resource) { + if (resource.isUndefined()) { + throw new IllegalArgumentException("Cannot allocate object to an undefined resource."); + } + GraphEventCapture cap = context.getEventCapture(); + totalAllocations++; + ResourceDef def = resource.getDefinition(); + if (def.isUseExisting()) { + // first try allocating a specific object, which is much faster + if (allocateSpecificAsync(resource)) { + return; + } + // find object to allocate + T indirectRes = null; + RenderObject indirectObj = null; + LinkedList skipped = new LinkedList<>(); + Iterator it = objectMap.values().iterator(); + boolean next; + while ((next = it.hasNext()) || !skipped.isEmpty()) { + RenderObject obj; + if (next) obj = it.next(); + else obj = skipped.removeFirst(); + if (isAvailable(obj)) { + if ((next || !skipped.isEmpty()) && obj.isInspect()) { + // Inspect this object later, because something else is inspecting it. + // This makes this thread try other objects first, instead of waiting + // for a synchronized block to be available. + skipped.addLast(obj); + continue; + } + // If multiple threads do happen to be here at the same time, ensure only one + // will inspect at a time. + synchronized (obj) { + // The thread we were waiting on may have claimed to object, so check again + // if it is available. + if (!isAvailable(obj)) { + continue; + } + obj.startInspect(); + if (!obj.isReservedWithin(resource.getLifeTime())) { + // try applying a direct resource + T r = def.applyDirectResource(obj.getObject()); + if (r != null) { + resource.setObject(obj, r); + if (cap != null) cap.reallocateObject(obj.getId(), resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + objectsReallocated++; + obj.endInspect(); + return; + } + // then try applying an indirect resource, which is not as desirable + if (!obj.isPrioritized() && indirectObj == null) { + indirectRes = def.applyIndirectResource(obj.getObject()); + if (indirectRes != null) { + indirectObj = obj; + // make sure no other thread attempts to apply this indirectly at the same time + obj.setPrioritized(true); + obj.endInspect(); + continue; + } + } + } + obj.endInspect(); + } + } + } + // allocate indirect object + if (indirectObj != null) synchronized (indirectObj) { + // disable priority flag + indirectObj.setPrioritized(false); + // check again if object is available + if (isAvailable(indirectObj)) { + indirectObj.startInspect(); + resource.setObject(indirectObj, indirectRes); + if (cap != null) cap.reallocateObject(indirectObj.getId(), resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + objectsReallocated++; + indirectObj.endInspect(); + } else { + // In the unlikely event that another thread "steals" this object + // from this thread, try allocating again. + allocateAsync(resource); + } + return; + } + } + // create new object + resource.setObject(create(def)); + if (cap != null) cap.createObject(resource.getObject().getId(), + resource.getIndex(), resource.getResource().getClass().getSimpleName()); + objectsCreated++; + } + private boolean allocateSpecificAsync(ResourceView resource) { + GraphEventCapture cap = context.getEventCapture(); + ResourceDef def = resource.getDefinition(); + long id = resource.getTicket().getObjectId(); + if (id < 0) return false; + // allocate reserved object + RenderObject obj = objectMap.get(id); + if (obj != null) { + if (cap != null) cap.attemptReallocation(id, resource.getIndex()); + if (isAvailable(obj)) synchronized (obj) { + obj.startInspect(); + if (obj.claimReservation(resource.getProducer().getIndex()) + || !obj.isReservedWithin(resource.getLifeTime())) { + // reserved object is only applied if it is accepted by the definition + T r = def.applyDirectResource(obj.getObject()); + if (r == null) { + r = def.applyIndirectResource(obj.getObject()); + } + if (r != null) { + resource.setObject(obj, r); + if (cap != null) cap.reallocateObject(id, resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + completedReservations++; + objectsReallocated++; + obj.endInspect(); + return true; + } + } + obj.endInspect(); + } + } + failedReservations++; + return false; + } + + /** + * Makes a reservation of render object holding the specified id at the render + * pass index. + *

    + * A reservation blocks other reallocation requests for the remainder of the frame. + * It is not strictly guaranteed to block all other requests, so it is not considered + * good practice to rely on a reservation blocking all such requests. + * + * @param objectId id of the object to reserve + * @param index index to reserve the object at + * @return true if the referenced object exists + */ + public boolean reserve(long objectId, PassIndex index) { + RenderObject obj = objectMap.get(objectId); + if (obj != null) { + obj.reserve(index); + officialReservations++; + if (context.getEventCapture() != null) { + context.getEventCapture().reserveObject(objectId, index); + } + return true; + } + return false; + } + /** + * Disposes the render object pointed to by the ResourceView's internal ticket. + * + * @param resource + */ + public void dispose(ResourceView resource) { + long id = resource.getTicket().getObjectId(); + if (id >= 0) { + RenderObject obj = objectMap.remove(id); + if (obj != null) { + obj.dispose(); + if (context.getEventCapture() != null) { + context.getEventCapture().disposeObject(id); + } + } + } + } + + /** + * Should be called only when a new rendering frame begins (before rendering). + */ + public void newFrame() { + totalAllocations = 0; + officialReservations = 0; + completedReservations = 0; + failedReservations = 0; + objectsCreated = 0; + objectsReallocated = 0; + flushedObjects = 0; + } + /** + * Clears reservations of all tracked render objects. + */ + public void clearReservations() { + for (RenderObject obj : objectMap.values()) { + obj.clearReservations(); + } + } + /** + * Flushes the map. + *

    + * Any render objects that have not been used for a number of frames are disposed. + */ + public void flushMap() { + totalObjects = objectMap.size(); + GraphEventCapture cap = context.getEventCapture(); + if (cap != null) cap.flushObjects(totalObjects); + for (Iterator it = objectMap.values().iterator(); it.hasNext();) { + RenderObject obj = it.next(); + if (!obj.tickTimeout()) { + if (cap != null) cap.disposeObject(obj.getId()); + obj.dispose(); + it.remove(); + flushedObjects++; + continue; + } + obj.setConstant(false); + } + if (cap != null) { + cap.value("totalAllocations", totalAllocations); + cap.value("officialReservations", officialReservations); + cap.value("completedReservations", completedReservations); + cap.value("failedReservations", failedReservations); + cap.value("objectsCreated", objectsCreated); + cap.value("objectsReallocated", objectsReallocated); + cap.value("flushedObjects", flushedObjects); + } + } + /** + * Clears the map. + *

    + * All tracked render objects are disposed. + */ + public void clearMap() { + GraphEventCapture cap = context.getEventCapture(); + for (RenderObject obj : objectMap.values()) { + if (cap != null) cap.disposeObject(obj.getId()); + obj.dispose(); + } + objectMap.clear(); + } + + /** + * Sets the default number of frame boundaries an object can experience without + * being used before being disposed. + *

    + * default=1 (can survive one frame boundary) + * + * @param staticTimeout + */ + public void setStaticTimeout(int staticTimeout) { + this.staticTimeout = staticTimeout; + } + + /** + * Gets the default number of frame boundaries an object can experience without + * being used before being disposed. + * + * @return + */ + public int getStaticTimeout() { + return staticTimeout; + } + /** + * Get the total number of allocations that occured during the last render frame. + * + * @return + */ + public int getTotalAllocations() { + return totalAllocations; + } + /** + * Gets the number of official reservations that occured during the last + * render frame. + *

    + * An official reservation is one made using {@link #reserve(long, int)}. + * + * @return + */ + public int getOfficialReservations() { + return officialReservations; + } + /** + * Gets the number of completed reservations that occured during the + * last render frame. + *

    + * A completed reservation is declared and allocated. + * + * @return + */ + public int getCompletedReservations() { + return completedReservations; + } + /** + * Gets the number of incomplete or failed reservations that occured + * during the last render frame. + * + * @return + */ + public int getFailedReservations() { + return failedReservations; + } + /** + * Gets the number of render objects created during the last render frame. + * + * @return + */ + public int getObjectsCreated() { + return objectsCreated; + } + /** + * Gets the number of reallocations that occured during the last render frame. + * + * @return + */ + public int getObjectsReallocated() { + return objectsReallocated; + } + /** + * Gets the number of render objects present before flushing. + * + * @return + */ + public int getTotalObjects() { + return totalObjects; + } + /** + * Gets the number of render objects disposed during flushing. + * + * @return + */ + public int getFlushedObjects() { + return flushedObjects; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceList.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceList.java new file mode 100644 index 0000000000..45e903c9f4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceList.java @@ -0,0 +1,779 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.framegraph.debug.GraphEventCapture; +import com.jme3.renderer.framegraph.definitions.ResourceDef; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture; +import java.util.ArrayList; +import java.util.LinkedList; + +/** + * Manages {@link ResourceView} declarations, references, and + * releases for a single framegraph. + * + * @author codex + */ +public class ResourceList { + + /** + * Initial size of the resource ArrayList. + */ + private static final int INITIAL_SIZE = 20; + + /** + * Maximum time to wait in milliseconds before throwing an exception. + */ + public static final long WAIT_TIMEOUT = 5000; + + private final FrameGraph frameGraph; + private RenderManager renderManager; + private RenderObjectMap map; + private GraphEventCapture cap; + private ArrayList resources = new ArrayList<>(INITIAL_SIZE); + private final LinkedList futureRefs = new LinkedList<>(); + private int nextSlot = 0; + private int textureBinds = 0; + + /** + * + * @param frameGraph + */ + public ResourceList(FrameGraph frameGraph) { + this.frameGraph = frameGraph; + } + + private ResourceView create(ResourceUser producer, ResourceDef def, String name) { + ResourceView res = new ResourceView<>(producer, def, new ResourceTicket<>(name)); + res.getTicket().setLocalIndex(add(res)); + return res; + } + private ResourceView locate(ResourceTicket ticket) { + return locate(ticket, true); + } + private ResourceView locate(ResourceTicket ticket, boolean failOnMiss) { + if (ticket == null) { + if (failOnMiss) { + throw new NullPointerException("Ticket cannot be null."); + } + return null; + } + final int i = ticket.getWorldIndex(); + if (i < 0) { + if (failOnMiss) { + throw new NullPointerException(ticket+" does not point to any resource (negative index)."); + } + return null; + } + if (i < resources.size()) { + ResourceView res = resources.get(i); + if (res != null) { + return res; + } + if (failOnMiss) { + throw new NullPointerException(ticket+" points to null resource."); + } + } + if (failOnMiss) { + throw new IndexOutOfBoundsException(ticket+" is out of bounds for size "+resources.size()); + } + return null; + } + private ResourceView fastLocate(ResourceTicket ticket) { + return resources.get(ticket.getWorldIndex()); + } + private int add(ResourceView res) { + assert res != null; + if (nextSlot >= resources.size()) { + // addUserEvent resource to end of list + resources.add(res); + nextSlot++; + return resources.size()-1; + } else { + // insert resource into available slot + int i = nextSlot; + resources.set(i, res); + // find next available slot + while (++nextSlot < resources.size()) { + if (resources.get(nextSlot) == null) { + break; + } + } + return i; + } + } + private ResourceView remove(int index) { + ResourceView prev = resources.set(index, null); + if (prev != null && prev.isReferenced()) { + throw new IllegalStateException("Cannot remove "+prev+" because it is referenced."); + } + nextSlot = Math.min(nextSlot, index); + return prev; + } + + /** + * Returns true if the ticket can be used to locate a resource. + *

    + * Use {@link ResourceTicket#validate(com.jme3.renderer.framegraph.ResourceTicket)} instead. + * + * @param ticket + * @return + */ + public boolean validate(ResourceTicket ticket) { + return ResourceTicket.validate(ticket); + } + + /** + * Declares a new resource. + * + * @param + * @param producer + * @param def + * @param store + * @return + */ + public ResourceTicket declare(ResourceUser producer, ResourceDef def, ResourceTicket store) { + String name = (store != null ? store.getName() : null); + ResourceView resource = create(producer, def, name); + if (cap != null) cap.declareResource(resource.getIndex(), name); + return resource.getTicket().copyIndexTo(store); + } + + /** + * Declares a temporary resource with an unregistered ticket. + *

    + * Temporary resources do not participate in culling. + * + * @param + * @param producer + * @param def + * @param store + * @return + */ + public ResourceTicket declareTemporary(ResourceUser producer, ResourceDef def, ResourceTicket store) { + store = declare(producer, def, store); + locate(store).setTemporary(true); + return store; + } + + /** + * If the ticket contains a valid object ID, that render object will be reserved + * at the index. + *

    + * Reserved objects cannot be allocated to another resource before the indexed + * pass occurs, unless that object is also reserved by another resource. + * + * @param passIndex + * @param ticket + */ + public void reserve(PassIndex passIndex, ResourceTicket ticket) { + if (ticket.getObjectId() >= 0) { + map.reserve(ticket.getObjectId(), passIndex); + ticket.copyObjectTo(locate(ticket).getTicket()); + } + } + + /** + * Makes reservations at the index for each {@link RenderObject} referenced by the tickets. + * + * @param passIndex + * @param tickets + * @see RenderObjectMap#reserve(long, com.jme3.renderer.framegraph.PassIndex) + */ + public void reserve(PassIndex passIndex, ResourceTicket... tickets) { + for (ResourceTicket t : tickets) { + reserve(passIndex, t); + } + } + + private void reference(PassIndex index, ResourceTicket ticket, boolean optional) { + boolean sync = !frameGraph.isAsync(); + if (optional && sync && !ResourceTicket.validate(ticket)) { + return; + } + ResourceView resource = locate(ticket, sync); + if (resource != null) { + resource.reference(index); + if (cap != null) cap.referenceResource(resource.getIndex(), ticket.getName()); + } else { + // save for later, since the resource hasn't been declared yet + futureRefs.add(new FutureReference(index, ticket, optional)); + } + } + + /** + * References the resource associated with the ticket. + *

    + * The pass index indicates when the resource will be acquired by the entity + * which is referencing the resource, which is important for determining resource + * lifetime. + * + * @param passIndex render pass index + * @param ticket + */ + public void reference(PassIndex passIndex, ResourceTicket ticket) { + reference(passIndex, ticket, false); + } + + /** + * References the resource associated with the ticket if the ticket + * is not null and does not have a negative world index. + * + * @param passIndex render pass index + * @param ticket + */ + public void referenceOptional(PassIndex passIndex, ResourceTicket ticket) { + reference(passIndex, ticket, true); + } + + /** + * References resources associated with the tickets. + * + * @param passIndex render pass index + * @param tickets + */ + public void reference(PassIndex passIndex, ResourceTicket... tickets) { + for (ResourceTicket t : tickets) { + reference(passIndex, t, false); + } + } + + /** + * Optionally references resources associated with the tickets. + * + * @param passIndex render pass index + * @param tickets + */ + public void referenceOptional(PassIndex passIndex, ResourceTicket... tickets) { + for (ResourceTicket t : tickets) { + reference(passIndex, t, true); + } + } + + /** + * Gets the definition of the resource associated with the ticket. + * + * @param + * @param + * @param type + * @param ticket + * @return + */ + public > R getDefinition(Class type, ResourceTicket ticket) { + ResourceDef def = locate(ticket).getDefinition(); + if (type.isAssignableFrom(def.getClass())) { + return (R)def; + } + return null; + } + + /** + * Marks the resource associated with the ticket as undefined. + *

    + * Undefined resources cannot hold objects. If an undefined resource is acquired acquired (unless with + * {@link #acquireOrElse(com.jme3.renderer.framegraph.ResourceTicket, java.lang.Object) acquireOrElse}), + * an exception will occur. + * + * @param ticket + */ + public void setUndefined(ResourceTicket ticket) { + ResourceView resource = locate(ticket); + resource.setUndefined(); + if (cap != null) cap.setResourceUndefined(resource.getIndex(), ticket.getName()); + } + + /** + * Marks the existing object held be the resource associated with the ticket as constant. + *

    + * Constant objects cannot be reallocated until the end of the frame. + * + * @param ticket + */ + public void setConstant(ResourceTicket ticket) { + RenderObject obj = locate(ticket).getObject(); + if (obj != null) { + obj.setConstant(true); + if (cap != null) cap.setObjectConstant(obj.getId()); + } + } + + /** + * Marks the resource associated with the ticket if the ticket is not + * null and does not have a negative world index. + * + * @param ticket + */ + public void setConstantOptional(ResourceTicket ticket) { + if (validate(ticket)) { + setConstant(ticket); + } + } + + /** + * Returns true if the resource associated with the ticket is virtual. + *

    + * A resource is virtual if it does not contain a concrete object and is + * not marked as undefined. + * + * @param ticket + * @param optional + * @return + */ + public boolean isVirtual(ResourceTicket ticket, boolean optional) { + if (!optional || validate(ticket)) { + return locate(ticket).isVirtual(); + } + return true; + } + + /** + * Forces the current thread to wait until the resource at the ticket is + * available for reading or a timeout occurs. + *

    + * A resource becomes available for reading after being released by the declaring pass. + * Then all waiting passes may access it for reading only. + *

    + * The operation is skipped if the ticket is invalid. + * + * @param ticket ticket to locate resource with + * @param thread current thread + */ + public void waitForResource(ResourceTicket ticket, int thread) { + if (ResourceTicket.validate(ticket)) { + // wait for resource to become available to this context + long start = System.currentTimeMillis(); + ResourceView res; + // TODO: determine why not locating the resource on each try results in timeouts. + while (!(res = fastLocate(ticket)).isReadAvailable()) { + if (System.currentTimeMillis()-start >= WAIT_TIMEOUT) { + throw new IllegalStateException("Thread "+thread+": Resource at "+ticket+" was assumed " + + "unreachable after "+WAIT_TIMEOUT+" milliseconds."); + } + } + // claim read permisions + // for resources that are read concurrent, this won't matter + if (!res.claimReadPermissions()) { + waitForResource(ticket, thread); + } + } + } + + /** + * Returns true if the resource at the ticket is asynchronous. + * + * @param ticket + * @return + */ + public boolean isAsync(ResourceTicket ticket) { + if (ResourceTicket.validate(ticket)) { + return locate(ticket).getLifeTime().isAsync(); + } + return false; + } + + /** + * Acquires the object held by the given resource. + *

    + * If the object does have an object associated with it (virtual), one will either + * be created or reallocated by the {@link RenderObjectMap}. + *

    + * The object's id is written to the ticket. + * + * @param + * @param resource + * @param ticket + * @return + */ + protected T acquire(ResourceView resource, ResourceTicket ticket) { + if (!resource.isUsed()) { + throw new IllegalStateException(resource+" was unexpectedly acquired."); + } + if (resource.isVirtual()) { + map.allocate(resource, frameGraph.isAsync()); + } + if (cap != null) cap.acquireResource(resource.getIndex(), ticket.getName()); + resource.getTicket().copyObjectTo(ticket); + return resource.getResource(); + } + + /** + * Acquires and returns the value associated with the resource at the ticket. + *

    + * If the resource is virtual (not holding a object), then either an existing + * object will be reallocated to the resource or a new object will be created. + * + * @param + * @param ticket + * @return + */ + public T acquire(ResourceTicket ticket) { + ResourceView resource = locate(ticket); + if (resource.isUndefined()) { + throw new NullPointerException("Cannot acquire undefined resource."); + } + return acquire(resource, ticket); + } + + /** + * If the ticket is not null and has a positive or zero world index, an object + * will be acquired for the resource and returned. + *

    + * Otherwise, the given default value will be returned. + * + * @param + * @param ticket + * @param value default value (may be null) + * @return + */ + public T acquireOrElse(ResourceTicket ticket, T value) { + if (validate(ticket)) { + ResourceView resource = locate(ticket); + if (!resource.isUndefined()) { + return acquire(resource, ticket); + } + } + return value; + } + + /** + * Acquires and assigns textures as color targets to the framebuffer. + *

    + * If a texture is already assigned to the framebuffer at the same color target index, + * then nothing will be changed at that index. + *

    + * Existing texture targets beyond the number of tickets passed will be removed. + * + * @param fbo + * @param tickets + */ + public void acquireColorTargets(FrameBuffer fbo, ResourceTicket... tickets) { + acquireColorTargets(fbo, null, tickets); + } + + /** + * Acquires and assigns textures as color targets to the framebuffer. + * + * @param fbo + * @param texArray array to populate with acquired textures, or null + * @param tickets + * @return populated texture array + */ + public Texture[] acquireColorTargets(FrameBuffer fbo, Texture[] texArray, ResourceTicket[] tickets) { + if (tickets.length == 0) { + fbo.clearColorTargets(); + fbo.setUpdateNeeded(); + return texArray; + } + if (tickets.length < fbo.getNumColorTargets()) { + fbo.trimColorTargetsTo(tickets.length-1); + fbo.setUpdateNeeded(); + } + int i = 0; + for (int n = Math.min(fbo.getNumColorTargets(), tickets.length); i < n; i++) { + Texture t = replaceColorTarget(fbo, tickets[i], i); + if (texArray != null) { + texArray[i] = t; + } + } + for (; i < tickets.length; i++) { + Texture t = acquire(tickets[i]); + if (texArray != null) { + texArray[i] = t; + } + fbo.addColorTarget(FrameBuffer.target(t)); + fbo.setUpdateNeeded(); + if (cap != null) cap.bindTexture(tickets[i].getWorldIndex(), tickets[i].getName()); + textureBinds++; + } + return texArray; + } + + /** + * Acquires the texture associated with the ticket and assigns it to the framebuffer. + * + * @param + * @param fbo + * @param ticket + * @return acquired texture + */ + public T acquireColorTarget(FrameBuffer fbo, ResourceTicket ticket) { + if (ticket == null) { + if (fbo.getNumColorTargets() > 0) { + fbo.clearColorTargets(); + fbo.setUpdateNeeded(); + } + return null; + } + if (fbo.getNumColorTargets() > 1) { + fbo.trimColorTargetsTo(0); + fbo.setUpdateNeeded(); + } + return replaceColorTarget(fbo, ticket, 0); + } + + private T replaceColorTarget(FrameBuffer fbo, ResourceTicket ticket, int i) { + if (i < fbo.getNumColorTargets()) { + Texture existing = fbo.getColorTarget(i).getTexture(); + T acquired = acquire(ticket); + if (acquired != existing) { + fbo.setColorTarget(i, FrameBuffer.target(acquired)); + fbo.setUpdateNeeded(); + if (cap != null) cap.bindTexture(ticket.getWorldIndex(), ticket.getName()); + textureBinds++; + } + return acquired; + } else { + T acquired = acquire(ticket); + fbo.addColorTarget(FrameBuffer.target(acquired)); + fbo.setUpdateNeeded(); + return acquired; + } + } + + /** + * Acquires and assigns a texture as the depth target to the framebuffer. + *

    + * If the texture is already assigned to the framebuffer as the depth target, + * the nothing changes. + * + * @param + * @param fbo + * @param ticket + * @return + */ + public T acquireDepthTarget(FrameBuffer fbo, ResourceTicket ticket) { + T acquired = acquire(ticket); + FrameBuffer.RenderBuffer target = fbo.getDepthTarget(); + if (target == null || acquired != target.getTexture()) { + fbo.setDepthTarget(FrameBuffer.target(acquired)); + fbo.setUpdateNeeded(); + if (cap != null) cap.bindTexture(ticket.getWorldIndex(), ticket.getName()); + textureBinds++; + } + return acquired; + } + + /** + * Directly assign the resource associated with the ticket to the value. + *

    + * A render object is not created to store the value. Instead, the render resource + * will directly store the value. Note that the value will be lost when the + * render resource is destroyed. + *

    + * This is intended to called in place of {@link #acquire(com.jme3.renderer.framegraph.ResourceTicket)}. + * + * @param + * @param ticket + * @param value + */ + public void setPrimitive(ResourceTicket ticket, T value) { + locate(ticket).setPrimitive(value); + } + + /** + * Releases the resource from use. + * + * @param ticket + */ + public void release(ResourceTicket ticket) { + ResourceView resource = locate(ticket); + if (cap != null) cap.releaseResource(resource.getIndex(), ticket.getName()); + if (!resource.release()) { + if (cap != null && resource.getObject() != null) { + cap.releaseObject(resource.getObject().getId()); + } + remove(ticket.getWorldIndex()); + if (!resource.isVirtual() && !resource.isUndefined()) { + ResourceDef def = resource.getDefinition(); + if (def != null && def.isDisposeOnRelease()) { + if (!resource.isPrimitive()) { + map.dispose(resource); + } else { + resource.getDefinition().dispose(resource.getResource()); + } + } + resource.setObject(null); + } + } + } + + /** + * Releases the ticket if the ticket is not null and contains a non-negative + * world index. + * + * @param ticket + * @return + */ + public boolean releaseOptional(ResourceTicket ticket) { + if (ResourceTicket.validate(ticket)) { + release(ticket); + return true; + } + return false; + } + + /** + * Releases the resources obtained by the tickets from use. + * + * @param tickets + */ + public void release(ResourceTicket... tickets) { + for (ResourceTicket t : tickets) { + release(t); + } + } + + /** + * Optionally releases the resources obtained by the tickets from use. + * + * @param tickets + */ + public void releaseOptional(ResourceTicket... tickets) { + for (ResourceTicket t : tickets) { + releaseOptional(t); + } + } + + /** + * Prepares this for rendering. + *

    + * This should only be called once per frame. + * @param map + * @param cap + */ + public void beginRenderFrame(RenderObjectMap map, GraphEventCapture cap) { + this.map = map; + this.cap = cap; + textureBinds = 0; + } + + /** + * Applies all missed references. + */ + public void applyFutureReferences() { + for (FutureReference ref : futureRefs) { + if (!ref.optional || ResourceTicket.validate(ref.ticket)) { + locate(ref.ticket).reference(ref.index); + } + } + futureRefs.clear(); + } + + /** + * Culls all resources and resource producers found to be unused. + *

    + * This should only be called after producers have fully counted their + * references, and prior to execution. + */ + public void cullUnreferenced() { + LinkedList cull = new LinkedList<>(); + for (ResourceView r : resources) { + if (r != null && !r.isReferenced() && !r.isTemporary()) { + cull.add(r); + } + } + ResourceView resource; + while ((resource = cull.pollFirst()) != null) { + // dereference producer of resource + ResourceUser producer = resource.getProducer(); + if (producer == null) { + remove(resource.getIndex()); + continue; + } + if (!producer.isUsed()) { + continue; + } + producer.dereference(); + if (!producer.isUsed()) { + for (ResourceTicket t : producer.getInputTickets()) { + if (!validate(t)) { + continue; + } + ResourceView r = locate(t); + r.release(); + if (!r.isReferenced()) { + cull.addLast(r); + } + } + for (ResourceTicket t : producer.getOutputTickets()) { + if (!t.hasSource()) { + remove(t.getLocalIndex()); + } + } + } + } + } + + /** + * Clears the resource list. + */ + public void clear() { + // TODO: throw exceptions for unreleased resources. + int size = resources.size(); + resources = new ArrayList<>(size); + nextSlot = 0; + if (cap != null) { + cap.clearResources(size); + cap.value("framebufferTextureBinds", textureBinds); + } + } + + /** + * Gets the number of known texture binds that occured during + * the last render frame. + * + * @return + */ + public int getTextureBinds() { + return textureBinds; + } + + /** + * Represents a reference to a resource that will exist in the future. + */ + private static class FutureReference { + + public final PassIndex index; + public final ResourceTicket ticket; + public final boolean optional; + + public FutureReference(PassIndex index, ResourceTicket ticket, boolean optional) { + this.index = index; + this.ticket = ticket; + this.optional = optional; + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceTicket.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceTicket.java new file mode 100644 index 0000000000..6a36eea9aa --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceTicket.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import java.util.Collection; +import java.util.LinkedList; + +/** + * References a {@link RenderResource} by index. + *

    + * Can reference another ticket as a source, which makes this point to the same + * resource as the source ticket. This mechanism allows RenderPasses to share + * resources. Also vaguely tracks the last seen render object, which is used to + * prioritize that render object, especially for reservations. + * + * @author codex + * @param + */ +public class ResourceTicket { + + public static final String RESERVED = "#"; + + private String name; + private int localIndex; + private long objectId = -1; + private ResourceTicket source; + private final LinkedList> targets = new LinkedList<>(); + private int exportGroupId = -1; + + /** + * + */ + public ResourceTicket() { + this(null, -1); + } + /** + * Creates a ticket with the name and a negative local index. + * + * @param name + */ + public ResourceTicket(String name) { + this(name, -1); + } + /** + * Creates a ticket with the name and local index. + * + * @param name + * @param index + */ + public ResourceTicket(String name, int index) { + this.name = name; + this.localIndex = index; + } + + /** + * Clears all target tickets. + */ + public void clearAllTargets() { + for (ResourceTicket t : targets) { + t.source = null; + } + targets.clear(); + } + + /** + * Copies this ticket's resource index to the target ticket. + * + * @param target + * @return + */ + public ResourceTicket copyIndexTo(ResourceTicket target) { + if (target == null) { + target = new ResourceTicket(); + } + return target.setLocalIndex(localIndex); + } + /** + * Copies this ticket's object ID to the target ticket. + * + * @param target + * @return + */ + public ResourceTicket copyObjectTo(ResourceTicket target) { + if (target == null) { + target = new ResourceTicket(); + } + target.setObjectId(objectId); + return target; + } + + /** + * Sets the source ticket. + * + * @param source + */ + public void setSource(ResourceTicket source) { + if (this.source != null) { + this.source.targets.remove(this); + } + this.source = source; + if (this.source != null) { + this.source.targets.add(this); + } + } + /** + * Sets the name of this ticket. + * + * @param name + * @return + */ + public ResourceTicket setName(String name) { + this.name = name; + return this; + } + /** + * Sets the local index. + *

    + * The local index is overriden if the source ticket is not null and + * the source's world index is not negative. + * + * @param index + * @return + */ + protected ResourceTicket setLocalIndex(int index) { + this.localIndex = index; + return this; + } + /** + * Sets the object ID. + * + * @param objectId + */ + public void setObjectId(long objectId) { + this.objectId = objectId; + } + /** + * Sets the id of the group this ticket is exported with. + *

    + * Called internally. Do not use. + * + * @param exportGroupId + */ + public void setExportGroupId(int exportGroupId) { + this.exportGroupId = exportGroupId; + } + + /** + * + * @return + */ + public String getName() { + return name; + } + /** + * Gets the world index. + *

    + * If the source ticket is null or its world index is negative, this ticket's + * local index will be returned. + * + * @return + */ + public int getWorldIndex() { + if (source != null) { + int i = source.getWorldIndex(); + if (i >= 0) return i; + } + return localIndex; + } + /** + * + * @return + */ + public int getLocalIndex() { + return localIndex; + } + /** + * + * @return + */ + public long getObjectId() { + return objectId; + } + /** + * + * @return + */ + public ResourceTicket getSource() { + return source; + } + /** + * Returns true if this source ticket is not null. + * + * @return + */ + public boolean hasSource() { + return source != null; + } + /** + * Gets all tickets depending on this ticket. + * + * @return + */ + public Collection> getTargets() { + return targets; + } + public int getExportGroupId() { + return exportGroupId; + } + + @Override + public String toString() { + return "Ticket[name="+name+", worldIndex="+getWorldIndex()+"]"; + } + + + /** + * Returns true if the ticket is valid for locating a resource. + *

    + * A ticket is only valid if it is not null and its world index + * is greater than or equal to zero. + * + * @param ticket + * @return true if ticket is valid + */ + public static boolean validate(ResourceTicket ticket) { + return ticket != null && ticket.getWorldIndex() >= 0; + } + + public static void validateUserTicketName(String name) { + if (name.startsWith(RESERVED)) { + throw new IllegalArgumentException("Cannot start ticket name with reserved \""+RESERVED+"\"."); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceUser.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceUser.java new file mode 100644 index 0000000000..16407c6af6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceUser.java @@ -0,0 +1,28 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Interface.java to edit this template + */ +package com.jme3.renderer.framegraph; + +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author codex + */ +public interface ResourceUser { + + public LinkedList getInputTickets(); + + public LinkedList getOutputTickets(); + + public PassIndex getIndex(); + + public void countReferences(); + + public void dereference(); + + public boolean isUsed(); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceView.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceView.java new file mode 100644 index 0000000000..09fbe09b02 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceView.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +import com.jme3.renderer.framegraph.definitions.ResourceDef; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Represents an existing or future resource used for rendering. + *

    + * Unlike {@link RenderObject}, this is only loosely connected to a raw + * rendering resource. In fact, the creation of a ResourceView does not + * necessarily predict the creation of a raw resource, due to culling. + * + * @author codex + * @param + */ +public class ResourceView { + + private final ResourceUser producer; + private final ResourceDef def; + private final ResourceTicket ticket; + private final TimeFrame lifetime; + private RenderObject object; + private T resource; + private int refs = 0; + private boolean temporary = false; + private boolean undefined = false; + private final AtomicBoolean released = new AtomicBoolean(false); + + /** + * + * @param producer + * @param def + * @param ticket + */ + public ResourceView(ResourceUser producer, ResourceDef def, ResourceTicket ticket) { + this.producer = producer; + this.def = def; + this.ticket = ticket; + this.lifetime = new TimeFrame(this.producer.getIndex(), 0); + } + + /** + * Reference this resource from the specified render pass index. + * + * @param index + */ + public void reference(PassIndex index) { + lifetime.extendTo(index); + refs++; + } + /** + * Releases this resource from one user. + * + * @return true if this resource is used after the release + */ + public boolean release() { + refs--; + released.set(true); + return refs >= 0; + } + /** + * Claims the resource for reading. + * + * @return + */ + public boolean claimReadPermissions() { + return (def.isReadConcurrent() && released.get()) || released.getAndSet(false); + } + + /** + * Sets the render object held by this resource. + * + * @param object + */ + public void setObject(RenderObject object) { + if (object != null) { + setObject(object, object.getObject()); + } else { + if (this.object != null) { + this.object.release(); + } + this.object = null; + resource = null; + } + } + /** + * Sets the render object and concrete resource held by this render resource. + * + * @param object + * @param resource + */ + public void setObject(RenderObject object, T resource) { + Objects.requireNonNull(object, "Object cannot be null."); + Objects.requireNonNull(resource, "Object resource cannot be null."); + if (undefined) { + throw new IllegalStateException("Resource is already undefined."); + } + if (object.isAcquired()) { + throw new IllegalStateException("Object is already acquired."); + } + this.object = object; + this.resource = resource; + this.object.acquire(); + ticket.setObjectId(this.object.getId()); + } + /** + * Directly sets the raw resource held by this render resource. + * + * @param resource + */ + public void setPrimitive(T resource) { + if (undefined) { + throw new IllegalStateException("Resource is already marked as undefined."); + } + object = null; + this.resource = resource; + } + /** + * Marks this resource as undefined. + */ + public void setUndefined() { + if (resource != null) { + throw new IllegalStateException("Resource is already defined."); + } + undefined = true; + } + /** + * Returns true if this resource always survives cull by reference. + * + * @param temporary + */ + public void setTemporary(boolean temporary) { + this.temporary = temporary; + } + + /** + * Gets this resource's producer. + * + * @return + */ + public ResourceUser getProducer() { + return producer; + } + /** + * Gets the resource definition. + * + * @return + */ + public ResourceDef getDefinition() { + return def; + } + /** + * Gets the resource ticket. + * + * @return + */ + public ResourceTicket getTicket() { + return ticket; + } + /** + * Gets the lifetime of this resource in render pass indices. + * + * @return + */ + public TimeFrame getLifeTime() { + return lifetime; + } + /** + * Gets the render object. + * + * @return + */ + public RenderObject getObject() { + return object; + } + /** + * Gets the concrete resource. + * + * @return + */ + public T getResource() { + return resource; + } + /** + * Gets the index of this resource. + * + * @return + */ + public int getIndex() { + return ticket.getWorldIndex(); + } + /** + * Gets the number of references to this resource. + * + * @return + */ + public int getNumReferences() { + return refs; + } + + /** + * Returns true if this resource is virtual. + *

    + * A resource is virtual when it does not hold a concrete resource + * and is not set as undefined. + * + * @return + */ + public boolean isVirtual() { + return resource == null && !undefined; + } + /** + * Returns true if this resource is primitive. + *

    + * A resource is primitive when it holds a concrete resource without a + * corresponding render object. Primitive resources are handled niavely, + * because they are not directly associated with a render object. + * + * @return + */ + public boolean isPrimitive() { + return resource != null && object == null; + } + /** + * Returns true if this resource is referenced by users other than the + * producer. + * + * @return + */ + public boolean isReferenced() { + return refs > 0; + } + /** + * Returns true if this resource is used (including the producer). + * + * @return + */ + public boolean isUsed() { + return refs >= 0; + } + /** + * Returns true if this resource is marked as undefined. + * + * @return + */ + public boolean isUndefined() { + return undefined; + } + /** + * + * @return + */ + public boolean isTemporary() { + return temporary; + } + /** + * Return true if this resource is available for reading. + *

    + * This is true typically after the first release occurs. + * + * @return + */ + public boolean isReadAvailable() { + return released.get(); + } + + @Override + public String toString() { + return "RenderResource["+producer+", "+ticket+"]"; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/TicketGroup.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/TicketGroup.java new file mode 100644 index 0000000000..dc6dfe8b55 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/TicketGroup.java @@ -0,0 +1,118 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph; + +/** + * + * @author codex + * @param + */ +public class TicketGroup { + + /** + * Prefix for tickets that are members of a list. + */ + public static final String LIST = "#list:"; + + private final String name; + private ResourceTicket[] array; + private boolean list = false; + + public TicketGroup(String name) { + this.name = name; + this.array = new ResourceTicket[0]; + this.list = true; + } + public TicketGroup(String name, int length) { + this.name = name; + this.array = new ResourceTicket[length]; + } + + public ResourceTicket create(int i) { + String tName; + if (!list) { + tName = arrayTicketName(name, i); + } else { + tName = listTicketName(name); + } + return new ResourceTicket<>(tName); + } + + public ResourceTicket add() { + ResourceTicket[] temp = new ResourceTicket[array.length+1]; + System.arraycopy(array, 0, temp, 0, array.length); + array = temp; + return (array[array.length-1] = create(array.length-1)); + } + public int remove(ResourceTicket t) { + requireAsList(true); + int i = array.length-1; + for (; i >= 0; i--) { + if (array[i] == t) break; + } + if (i >= 0) { + ResourceTicket[] temp = new ResourceTicket[array.length-1]; + if (i > 0) { + System.arraycopy(array, 0, temp, 0, i); + } + if (i < array.length-1) { + System.arraycopy(array, i+1, temp, i, array.length-i-1); + } + array = temp; + } + return i; + } + + public void requireAsList(boolean list) { + if (this.list != list) { + throw new IllegalStateException("Group must be "+(list ? "a list" : "an array")+" in this context."); + } + } + + public String getName() { + return name; + } + public ResourceTicket[] getArray() { + return array; + } + public boolean isList() { + return list; + } + + /** + * + * @param group + * @param i + * @return + */ + public static String arrayTicketName(String group, int i) { + return group+'['+i+']'; + } + /** + * + * @param group + * @return + */ + public static String listTicketName(String group) { + return LIST+group; + } + /** + * + * @param name + * @return + */ + public static boolean isListTicket(String name) { + return name.startsWith(LIST); + } + /** + * + * @param name + * @return + */ + public static String extractGroupName(String name) { + return name.substring(LIST.length()); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/TimeFrame.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/TimeFrame.java new file mode 100644 index 0000000000..a50fe49fff --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/TimeFrame.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph; + +/** + * Represents a period of time starting at the start of the indexed pass, and + * lasting for the duration of a number of following passes. + *

    + * Used primarily to track the lifetime of ResourceViews, which is then used + * to determine if a ResourceView violates any reservations. + *

    + * This would rather overestimate that underestimate, so asynchronous resources + * are tracked as surviving from first inception to frame end. + * + * @author codex + */ +public class TimeFrame { + + private int thread; + private int queue, length; + private boolean async = false; + + /** + * + */ + private TimeFrame() {} + /** + * + * @param index + * @param length + */ + public TimeFrame(PassIndex index, int length) { + this.thread = index.getThreadIndex(); + this.queue = index.getQueueIndex(); + this.length = length; + if (this.queue < 0) { + throw new IllegalArgumentException("Pass index cannot be negative."); + } + if (this.length < 0) { + throw new IllegalArgumentException("Length cannot be negative."); + } + } + + /** + * Extends the length so that this time frame includes the given index. + * + * @param passIndex + */ + public void extendTo(PassIndex passIndex) { + if (passIndex.getThreadIndex() != thread) { + async = true; + } else { + length = Math.max(length, passIndex.getQueueIndex()-this.queue); + } + } + /** + * Copies this to the target time frame. + * + * @param target + * @return + */ + public TimeFrame copyTo(TimeFrame target) { + if (target == null) { + target = new TimeFrame(); + } + target.thread = thread; + target.queue = queue; + target.length = length; + target.async = async; + return target; + } + + /** + * Gets the index of the thread this timeframe is based from. + * + * @return + */ + public int getThreadIndex() { + return thread; + } + /** + * Gets index of the first pass this time frame includes. + * + * @return + */ + public int getStartQueueIndex() { + return queue; + } + /** + * Gets the length. + * + * @return + */ + public int getLength() { + return length; + } + /** + * Gets index of the last pass this time frame includes. + * + * @return + */ + public int getEndQueueIndex() { + return queue+length; + } + /** + * Returns true if this timeframe is asynchronous. + *

    + * An asynchronous timeframe's end index is unreliable. + * + * @return + */ + public boolean isAsync() { + return async; + } + + /** + * Returns true if this time frame overlaps the given time frame. + * + * @param time + * @return + */ + public boolean overlaps(TimeFrame time) { + return queue <= time.queue+time.length && queue+length >= time.queue; + } + /** + * Returns true if this time frame includes the given index. + * + * @param index + * @return + */ + public boolean includes(int index) { + return queue <= index && queue+length >= index; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphSetting.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphSetting.java new file mode 100644 index 0000000000..32351c9264 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphSetting.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.client; + +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.export.SavableObject; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.FrameGraph; +import java.io.IOException; + +/** + * Shares settings from the FrameGraph settings map with the FrameGraph. + * + * @author codex + * @param + */ +public class GraphSetting implements GraphSource, GraphTarget, Savable { + + private String name; + private ViewPortFilter filter; + private T defValue; + + /** + * Serialization only. + */ + public GraphSetting() { + this("", null); + } + /** + * Graph setting that interacts with the named setting + * stored in the FrameGraph. + * + * @param name + */ + public GraphSetting(String name) { + this(name, null); + } + /** + * Graph setting that interacts with the named setting + * stored in the FrameGraph. + * + * @param name + * @param defValue + */ + public GraphSetting(String name, T defValue) { + this.name = name; + this.defValue = defValue; + } + + @Override + public T getGraphValue(FrameGraph frameGraph, ViewPort viewPort) { + assert name != null : "Setting name cannot be null."; + if (filter == null || filter.accept(viewPort)) { + T value = frameGraph.getSetting(name); + if (value != null) { + return value; + } else { + return defValue; + } + } + return null; + } + @Override + public boolean setGraphValue(FrameGraph frameGraph, ViewPort viewPort, T value) { + assert name != null : "Setting name cannot be null."; + if (filter == null || filter.accept(viewPort)) { + frameGraph.setSetting(name, value); + return true; + } + return false; + } + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(name, "name", null); + if (filter != null && filter instanceof Savable) { + out.write((Savable)filter, "filter", new DefaultSavableFilter()); + } + out.write(new SavableObject(defValue), "defValue", SavableObject.NULL); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + name = in.readString("name", null); + filter = (ViewPortFilter)in.readSavable("filter", new DefaultSavableFilter()); + defValue = (T)in.readSavableObject("defValue", SavableObject.NULL).getObject(); + } + + /** + * Sets the viewport filter. + *

    + * Rejected viewports are not able to change or read from the settings map via + * this object. + * + * @param filter + */ + public void setFilter(ViewPortFilter filter) { + this.filter = filter; + } + + /** + * Sets the default value used if no value is available in + * the FrameGraph settings. + *

    + * default=null + * + * @param defValue + */ + public void setDefaultValue(T defValue) { + this.defValue = defValue; + } + + /** + * Gets the name of the setting this interacts with. + * + * @return + */ + public String getName() { + return name; + } + + /** + * Gets the viewport filter. + * + * @return + */ + public ViewPortFilter getFilter() { + return filter; + } + + /** + * + * @return + */ + public T getDefaultValue() { + return defValue; + } + + public static class DefaultSavableFilter implements ViewPortFilter, Savable { + + @Override + public boolean accept(ViewPort vp) { + return true; + } + @Override + public void write(JmeExporter ex) throws IOException {} + @Override + public void read(JmeImporter im) throws IOException {} + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphSource.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphSource.java new file mode 100644 index 0000000000..7ea188d1d6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphSource.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.client; + +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.FrameGraph; + +/** + * Provides values to a FrameGraph from game logic. + * + * @author codex + * @param + */ +public interface GraphSource { + + /** + * Gets the value provided to the framegraph. + * + * @param frameGraph framegraph currently rendering + * @param viewPort viewport currently being rendered + * @return value (may be null in some circumstances) + */ + public T getGraphValue(FrameGraph frameGraph, ViewPort viewPort); + + /** + * Returns the value from provided by the GraphSource. + *

    + * If the source is null, the default value will be returned instead. + * + * @param + * @param source graph source + * @param defValue default value (used if graph source is null) + * @param frameGraph framegraph + * @param viewPort viewport + * @return value from source (or default value if source is null) + */ + public static T get(GraphSource source, T defValue, FrameGraph frameGraph, ViewPort viewPort) { + if (source != null) { + return source.getGraphValue(frameGraph, viewPort); + } + return defValue; + } + + /** + * Returns a source that returns the given value. + * + * @param + * @param value + * @return + */ + public static GraphValue value(T value) { + return new GraphValue<>(value); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphTarget.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphTarget.java new file mode 100644 index 0000000000..2d4089b4bf --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphTarget.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.client; + +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.FrameGraph; + +/** + * Receives values from a FrameGraph. + * + * @author codex + * @param + */ +public interface GraphTarget { + + /** + * Recieves a value from the FrameGraph. + * + * @param frameGraph FrameGraph currently rendering + * @param viewPort viewport currently being rendered + * @param value value from framegraph + * @return true if value is used, which may affect how resources are + * handled by the FrameGraph internally + */ + public boolean setGraphValue(FrameGraph frameGraph, ViewPort viewPort, T value); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphValue.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphValue.java new file mode 100644 index 0000000000..5688e852b6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/GraphValue.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.client; + +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.export.SavableObject; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.FrameGraph; +import java.io.IOException; + +/** + * Implementation of GraphSource and GraphTarget that manages a value. + * + * @author codex + * @param + */ +public class GraphValue implements GraphSource, GraphTarget, Savable { + + private T value; + + public GraphValue() {} + public GraphValue(T value) { + this.value = value; + } + + @Override + public T getGraphValue(FrameGraph frameGraph, ViewPort viewPort) { + return value; + } + @Override + public boolean setGraphValue(FrameGraph frameGraph, ViewPort viewPort, T value) { + this.value = value; + return true; + } + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(new SavableObject(value), "value", SavableObject.NULL); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + value = (T)in.readSavableObject("value", SavableObject.NULL).getObject(); + } + + /** + * Sets the held value. + *

    + * Subject to change if values are being recieved from the FrameGraph. + * + * @param value + */ + public void setValue(T value) { + this.value = value; + } + + /** + * Gets the held value. + * + * @return + */ + public T getValue() { + return value; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/MatParamTargetControl.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/MatParamTargetControl.java new file mode 100644 index 0000000000..3986dc2d58 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/MatParamTargetControl.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.client; + +import com.jme3.material.Material; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.scene.Geometry; +import com.jme3.scene.SceneGraphIterator; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; +import com.jme3.shader.VarType; + +/** + * Applies values from a FrameGraph to a material parameter. + *

    + * The target material is the first located material in the controlled spatial's + * hierarchy, however, this is mainly intended to control geometries. + * + * @author codex + * @param + */ +public class MatParamTargetControl extends AbstractControl implements GraphTarget { + + private final String name; + private final VarType type; + private ViewPort[] viewPorts; + private Material material; + private T value; + + public MatParamTargetControl(String name, VarType type) { + this.name = name; + this.type = type; + } + + @Override + protected void controlUpdate(float tpf) {} + @Override + protected void controlRender(RenderManager rm, ViewPort vp) {} + @Override + public void setSpatial(Spatial spat) { + if (spatial == spat) { + return; + } + super.setSpatial(spat); + if (spatial != null) { + for (Spatial s : new SceneGraphIterator(spatial)) { + if (s instanceof Geometry) { + material = ((Geometry)s).getMaterial(); + break; + } + } + } else { + material = null; + } + } + @Override + public boolean setGraphValue(FrameGraph frameGraph, ViewPort viewPort, T value) { + if (containsViewPort(viewPort)) { + this.value = value; + if (this.value != null) { + material.setParam(name, type, this.value); + } else { + material.clearParam(name); + } + return true; + } + return false; + } + + private boolean containsViewPort(ViewPort vp) { + if (viewPorts == null) return true; + for (ViewPort p : viewPorts) { + if (p == vp) return true; + } + return false; + } + + /** + * Registers the ViewPorts that are able to affect the internal value. + *

    + * ViewPorts not included in the array cannot affect the internal value. + * + * @param viewPorts + */ + public void setViewPorts(ViewPort... viewPorts) { + this.viewPorts = viewPorts; + } + /** + * Sets the ViewPort filter to allow ViewPorts to affect the internal value. + */ + public void includeAllViewPorts() { + viewPorts = null; + } + + /** + * Gets the internal value. + * + * @return + */ + public T getValue() { + return value; + } + /** + * Gets the array of ViewPorts that are able to affect the internal value. + * + * @return array of ViewPorts, or null if all ViewPorts are accepted + */ + public ViewPort[] getViewPorts() { + return viewPorts; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/ViewPortFilter.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/ViewPortFilter.java new file mode 100644 index 0000000000..8184897b6b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/client/ViewPortFilter.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.client; + +import com.jme3.renderer.ViewPort; + +/** + * Filter that accepts or denies ViewPorts. + *

    + * Used primarily by {@link GraphSource} and {@link GraphTarget} implementation + * that only function for certain registered ViewPorts. + * + * @author codex + */ +public interface ViewPortFilter { + + /** + * Filter that accepts all viewports. + */ + public static final ViewPortFilter ANY = vp -> true; + + /** + * Returns true if the viewport is accepted by this filter. + * + * @param vp + * @return + */ + public boolean accept(ViewPort vp); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/debug/GraphEventCapture.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/debug/GraphEventCapture.java new file mode 100644 index 0000000000..d188d1702a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/debug/GraphEventCapture.java @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.debug; + +import com.jme3.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.PassIndex; +import com.jme3.texture.FrameBuffer; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.LinkedList; +import java.util.function.Supplier; + +/** + * Logs a list of FrameGraph events that occures over a number of frames. + *

    + * Event logs can be written to a file, so that it can be analyzed by external + * programs. + * + * @author codex + */ +public class GraphEventCapture { + + private final File target; + private final LinkedList events = new LinkedList<>(); + private int frame = 0, targetFrames = 10; + private boolean includeNanos = true; + private long userNanos = 0; + + /** + * Graph event capture that exports the the file. + * + * @param target + */ + public GraphEventCapture(File target) { + this.target = target; + } + + private void add(Event c) { + events.addLast(c); + } + /** + * Adds an event triggered by the user. + * + * @param operation + * @param arguments + */ + public void addUserEvent(String operation, Object... arguments) { + add(new Event("USEREVENT", operation, arguments)); + } + /** + * Logs the object. + *

    + * Changes to object afterward affect this. + * + * @param name + * @param value + */ + public void value(String name, Object value) { + add(new Value(name, value)); + } + + /** + * Logs a render frame start event. + */ + public void beginRenderFrame() { + add(new Event("SUPEREVENT", "BeginRenderFrame", frame)); + } + /** + * Logs a render frame end event. + */ + public void endRenderFrame() { + add(new Event("SUPEREVENT", "EndRenderFrame", frame++)); + } + /** + * Logs a viewport render event. + * + * @param vp + */ + public void renderViewPort(ViewPort vp) { + Camera cam = vp.getCamera(); + add(new Event("SUPEREVENT", "StartViewPort", vp.getName(), cam.getWidth(), cam.getHeight())); + } + /** + * Logs a RenderPass preperation event. + * + * @param index + * @param name + */ + public void prepareRenderPass(PassIndex index, String name) { + events.add(new Event("PrepareRenderPass", index.clone(), name)); + } + /** + * Logs a RenderPass execution event. + * + * @param index + * @param name + */ + public void executeRenderPass(PassIndex index, String name) { + add(new Event("ExecuteRenderPass", index.clone(), name)); + } + /** + * Logs a FrameBuffer creation event. + * + * @param fb + */ + public void createFrameBuffer(FrameBuffer fb) { + add(new Event("CreateFrameBuffer", fb.getWidth(), fb.getHeight(), fb.getSamples())); + } + + /** + * Logs a ResourceView declaration event. + * + * @param index + * @param ticket + */ + public void declareResource(int index, String ticket) { + add(new Event("DeclareResource", index, ticket)); + } + /** + * Logs a ResourceView reference event. + * + * @param index + * @param ticket + */ + public void referenceResource(int index, String ticket) { + add(new Event("ReferenceResource", index, ticket)); + } + /** + * Logs a resource acquire event. + * + * @param index + * @param ticket + */ + public void acquireResource(int index, String ticket) { + add(new Event("AcquireResource", index, ticket)); + } + /** + * Logs an event in which a ResourceView is marked as undefined. + * + * @param index + * @param ticket + */ + public void setResourceUndefined(int index, String ticket) { + add(new Event("SetResourceUndefined", index, ticket)); + } + /** + * Logs a ResourceView release event. + * + * @param index + * @param ticket + */ + public void releaseResource(int index, String ticket) { + add(new Event("ReleaseResource", index, ticket)); + } + /** + * Logs a ResourceView clearing event. + * + * @param size number of resources cleared + */ + public void clearResources(int size) { + add(new Event("ClearResources", size)); + } + /** + * Logs a texture bind event. + * + * @param index + * @param ticket + */ + public void bindTexture(int index, String ticket) { + add(new Event("BindTexture", index, ticket)); + } + + /** + * Logs a RenderObject reservation event. + * + * @param id + * @param index + */ + public void reserveObject(long id, PassIndex index) { + add(new Event("ReserveObject", id, index.clone())); + } + /** + * Logs a ResourceObject creation event. + * + * @param id + * @param index + * @param type + */ + public void createObject(long id, int index, String type) { + add(new Event("CreateObject", id, index, type)); + } + /** + * Logs a RenderObject reallocation event. + * + * @param id + * @param index + * @param type + */ + public void reallocateObject(long id, int index, String type) { + add(new Event("ReallocateObject", id, index, type)); + } + /** + * Logs an attempt at reallocating a specific RenderObject. + * + * @param id + * @param index + */ + public void attemptReallocation(long id, int index) { + add(new Event("AttemptSpecificReallocation", id, index)); + } + /** + * Logs an event in which a RenderObject is marked as constant. + * + * @param id + */ + public void setObjectConstant(long id) { + add(new Event("SetObjectConstant", id)); + } + /** + * Logs a RenderObject release event. + * + * @param id + */ + public void releaseObject(long id) { + add(new Event("ReleaseObject", id)); + } + /** + * Logs a RenderObject dispose event. + * + * @param id + */ + public void disposeObject(long id) { + add(new Event("DisposeObject", id)); + } + /** + * Logs an event in which RenderObjectMap is flushed. + * + * @param size number of RenderObjects in the map. + */ + public void flushObjects(int size) { + add(new Event("FlushObjects", size)); + } + + /** + * Begins a nanos count. + * + * @param subject + */ + public void startNanos(String subject) { + userNanos = System.nanoTime(); + add(new Event("PROFILE", "StartNanos", subject)); + } + /** + * Marks the beginning and end of a nanos "lap". + * + * @param subject + * @return + */ + public long lapNanos(String subject) { + return breakNanos(subject, subject); + } + /** + * Ends the current nanos count and begins a new nanos count. + * + * @param endSubject + * @param startSubject + * @return + */ + public long breakNanos(String endSubject, String startSubject) { + long duration = endNanos(endSubject); + startNanos(startSubject); + return duration; + } + /** + * Ends the current nanos count. + * + * @param subject + * @return + */ + public long endNanos(String subject) { + long duration = Math.abs(System.nanoTime()-userNanos); + add(new Event("PROFILE", "EndNanos", subject, duration+"ns", (duration/1000000)+"ms")); + return duration; + } + + /** + * Exports the current event log to this capture's target file. + * + * @throws IOException + */ + public void export() throws IOException { + if (target.exists()) { + target.delete(); + } + target.createNewFile(); + FileWriter writer = new FileWriter(target); + writer.write(events.size()+" framegraph events over "+frame+" frames\n"); + for (Event e : events) { + writer.write(e.toExportString(includeNanos)); + writer.write('\n'); + } + writer.close(); + } + + /** + * + * @param targetFrames + */ + public void setTargetFrames(int targetFrames) { + this.targetFrames = targetFrames; + } + /** + * Set the export file to include nanos with each event. + * + * @param includeNanos + */ + public void setIncludeNanos(boolean includeNanos) { + this.includeNanos = includeNanos; + } + + public int getTargetFrames() { + return targetFrames; + } + /** + * + * @return + */ + public boolean isIncludeNanos() { + return includeNanos; + } + + public boolean isComplete() { + return frame >= targetFrames; + } + + public void resetFrameCount() { + frame = 0; + } + + private static class Event { + + protected String eventType; + protected final String operation; + protected final Object[] arguments; + protected final long startNanos; + + public Event(String operation, Object... arguments) { + this("EVENT", operation, arguments); + } + public Event(String eventType, String operation, Object... arguments) { + this.eventType = eventType; + this.operation = operation; + this.arguments = arguments; + this.startNanos = System.nanoTime(); + } + + public String getOperation() { + return operation; + } + public Object[] getArguments() { + return arguments; + } + + public String toExportString(boolean nanos) { + StringBuilder builder = new StringBuilder(); + builder.append(getEventType()) + .append(" ") + .append(operation) + .append('('); + if (nanos) { + builder.append(nanos); + } + for (Object a : arguments) { + if (nanos) { + builder.append(','); + } + builder.append(a.toString()); + nanos = true; + } + return builder.append(')').toString(); + } + public String getEventType() { + return eventType; + } + + } + private static class Value extends Event { + + public Value(String field, Object value) { + super(field, value); + } + + @Override + public String toExportString(boolean nanos) { + StringBuilder builder = new StringBuilder(); + return builder.append(getEventType()).append(" ") + .append(operation).append('=') + .append(arguments[0]).toString(); + } + @Override + public String getEventType() { + return "VALUE"; + } + + } + + private static class Check { + + private final String name; + private final Supplier check; + private final boolean terminal; + + public Check(String name, Supplier check) { + this(name, check, false); + } + public Check(String name, Supplier check, boolean terminal) { + this.name = name; + this.check = check; + this.terminal = terminal; + } + + public boolean run() { + return check.get(); + } + public String asArgument(boolean b) { + return name+"="+b; + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/AbstractResourceDef.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/AbstractResourceDef.java new file mode 100644 index 0000000000..2dbc372d02 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/AbstractResourceDef.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.definitions; + +import java.util.function.Consumer; + +/** + * Abstract implementation of ResourceDef. + * + * @author codex + * @param + */ +public abstract class AbstractResourceDef implements ResourceDef { + + private Consumer disposalMethod; + private int staticTimeout = -1; + private boolean useExisting = true; + private boolean disposeOnRelease = false; + private boolean readConcurrent = true; + + @Override + public int getStaticTimeout() { + return staticTimeout; + } + + @Override + public Consumer getDisposalMethod() { + return disposalMethod; + } + + @Override + public boolean isUseExisting() { + return useExisting; + } + + @Override + public boolean isDisposeOnRelease() { + return disposeOnRelease; + } + + @Override + public boolean isReadConcurrent() { + return readConcurrent; + } + + /** + * Sets the Consumer responsible for disposing the resource. + *

    + * default=null + * + * @param disposalMethod disposal consumer, or null to use defaults + */ + public void setDisposalMethod(Consumer disposalMethod) { + this.disposalMethod = disposalMethod; + } + + /** + * Sets the number of frames the resource can be static before being + * disposed. + *

    + * If less than zero, the default value provided by + * {@link com.jme3.renderer.framegraph.RenderObjectMap RenderObjectMap} will + * be used instead. + * + * @param staticTimout + */ + public void setStaticTimeout(int staticTimout) { + this.staticTimeout = staticTimout; + } + + /** + * Allows for reallocation of existing resources. + *

    + * default=true + * + * @param useExisting + */ + public void setUseExisting(boolean useExisting) { + this.useExisting = useExisting; + } + + /** + * Sets the resource to be disposed when it is first unused. + *

    + * default=false + * + * @param disposeOnRelease + */ + public void setDisposeOnRelease(boolean disposeOnRelease) { + this.disposeOnRelease = disposeOnRelease; + } + + /** + * Sets the resource as able to be read concurrently. + *

    + * default=true + * + * @param readConcurrent + */ + public void setReadConcurrent(boolean readConcurrent) { + this.readConcurrent = readConcurrent; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/ResourceDef.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/ResourceDef.java new file mode 100644 index 0000000000..2df32d21b7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/ResourceDef.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.definitions; + +import java.util.function.Consumer; +import com.jme3.renderer.framegraph.ResourceView; + +/** + * Manages the behavior of a {@link ResourceView}, especially for creation, + * reallocation, and disposal of related raw resources. + * + * @author codex + * @param + */ +public interface ResourceDef { + + /** + * Creates a new resources from scratch. + * + * @return + */ + public T createResource(); + + /** + * Checks if the resource can be directly allocated to this definition's + * {@link ResourceView} as is. + *

    + * For reallocation, direct "as is" resources are preferred over indirect + * resources. Usually because direct resources are specifically designed + * for whatever task. + * + * @param resource + * @return the resource if approved, otherwise null + */ + public T applyDirectResource(Object resource); + + /** + * Repurposes the given resource for allocation to this definition's + * {@link ResourceView}. + *

    + * An indirect resource usually does not exactly match the type of this + * definition, but does contain the necessary components. + * + * @param resource + * @return repurposed resource, or null if the given resource is not usable. + */ + public T applyIndirectResource(Object resource); + + /** + * Returns the number of frames which the resource must be + * static (unused throughout rendering) before it is disposed. + *

    + * If negative, the default timeout value will be used instead. + * + * @return static timeout duration + */ + public default int getStaticTimeout() { + return -1; + } + + /** + * Gets the Consumer used to dispose of a resource. + * + * @return resource disposer, or null + */ + public default Consumer getDisposalMethod() { + return null; + } + + /** + * Returns true if resources can be reallocated to this definition. + * + * @return + */ + public default boolean isUseExisting() { + return true; + } + + /** + * Returns true if the resource should be disposed after being + * released and having no users. + * + * @return + */ + public default boolean isDisposeOnRelease() { + return false; + } + + /** + * Returns true if the resource can be read concurrently. + * + * @return + */ + public default boolean isReadConcurrent() { + return true; + } + + /** + * Disposes the resource using the disposal method, if not null. + * + * @param resource + */ + public default void dispose(T resource) { + Consumer d = getDisposalMethod(); + if (d != null) d.accept(resource); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/TextureDef.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/TextureDef.java new file mode 100644 index 0000000000..82ee5eafe5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/TextureDef.java @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.definitions; + +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.texture.Texture3D; +import com.jme3.texture.image.ColorSpace; +import java.util.ArrayList; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * General resource definition for textures. + *

    + * Able to indirectly reallocate any object that contains a locatable {@link Image} that + * matches the properties given in this definition. + * + * @author codex + * @param + */ +public class TextureDef extends AbstractResourceDef implements Consumer { + + public static final Function TEXTURE_2D = img -> new Texture2D(img); + public static final Function TEXTURE_3D = img -> new Texture3D(img); + + private final Class type; + private Function textureBuilder; + private Function imageExtractor; + private int width = 128; + private int height = 128; + private int depth = 0; + private int samples = 1; + private Image.Format format; + private ColorSpace colorSpace = ColorSpace.Linear; + private Texture.MagFilter magFilter = Texture.MagFilter.Bilinear; + private Texture.MinFilter minFilter = Texture.MinFilter.BilinearNoMipMaps; + private Texture.ShadowCompareMode shadowCompare = Texture.ShadowCompareMode.Off; + private Texture.WrapMode wrapS, wrapT, wrapR; + private boolean formatFlexible = false; + private boolean colorSpaceFlexible = false; + + /** + * + * @param type + * @param textureBuilder + */ + public TextureDef(Class type, Function textureBuilder) { + this(type, textureBuilder, Image.Format.RGBA8); + } + /** + * + * @param type + * @param textureBuilder + * @param format + */ + public TextureDef(Class type, Function textureBuilder, Image.Format format) { + Objects.requireNonNull(type); + Objects.requireNonNull(textureBuilder); + Objects.requireNonNull(format); + this.type = type; + this.textureBuilder = textureBuilder; + this.format = format; + if (!Texture2D.class.isAssignableFrom(type)) { + depth = 1; + } + } + + @Override + public T createResource() { + Image img; + if (depth > 0) { + img = new Image(format, width, height, depth, new ArrayList<>(depth), colorSpace); + } else { + img = new Image(format, width, height, null, colorSpace); + } + return createTexture(img); + } + @Override + public T applyDirectResource(Object resource) { + if (type.isAssignableFrom(resource.getClass())) { + T tex = (T)resource; + if (validateImage(tex.getImage())) { + setupTexture(tex); + return tex; + } + } + return null; + } + @Override + public T applyIndirectResource(Object resource) { + Image img; + if (imageExtractor != null) { + if ((img = imageExtractor.apply(resource)) == null) { + return null; + } + } else if (resource instanceof Texture) { + img = ((Texture)resource).getImage(); + } else if (resource instanceof Image) { + img = (Image)resource; + } else { + return null; + } + if (validateImage(img)) { + return textureBuilder.apply(img); + } + return null; + } + @Override + public Consumer getDisposalMethod() { + return this; + } + @Override + public boolean isDisposeOnRelease() { + return false; + } + @Override + public void accept(T t) { + t.getImage().dispose(); + } + + protected T createTexture(Image img) { + T tex = textureBuilder.apply(img); + tex.getImage().setMultiSamples(samples); + setupTexture(tex); + return tex; + } + protected void setupTexture(Texture tex) { + if (magFilter != null) { + tex.setMagFilter(magFilter); + } + if (minFilter != null) { + tex.setMinFilter(minFilter); + } + if (shadowCompare != null) { + tex.setShadowCompareMode(shadowCompare); + } + if (wrapS != null) { + tex.setWrap(Texture.WrapAxis.S, wrapS); + } + if (wrapT != null) { + tex.setWrap(Texture.WrapAxis.T, wrapT); + } + if (wrapR != null && depth > 0) { + tex.setWrap(Texture.WrapAxis.R, wrapR); + } + } + protected boolean validateImage(Image img) { + return validateImageSize(img) + && (samples <= 0 || img.getMultiSamples() == samples) + && validateImageFormat(img) + && (colorSpaceFlexible || img.getColorSpace() == colorSpace); + } + protected boolean validateImageSize(Image img) { + return img.getWidth() == width && img.getHeight() == height && (depth == 0 || img.getDepth() == depth); + } + protected boolean validateImageFormat(Image img) { + return img.getFormat() == format || (formatFlexible && img.getFormat().isDepthFormat() == format.isDepthFormat()); + } + + /** + * Sets the function that constructs a texture from an image. + * + * @param textureBuilder + */ + public void setTextureBuilder(Function textureBuilder) { + Objects.requireNonNull(textureBuilder); + this.textureBuilder = textureBuilder; + } + /** + * Sets the function that extracts an Image from an object. + * + * @param imageExtractor + */ + public void setImageExtractor(Function imageExtractor) { + this.imageExtractor = imageExtractor; + } + /** + * Sets the texture width. + * + * @param width texture width greater than zero + */ + public void setWidth(int width) { + if (width <= 0) { + throw new IllegalArgumentException("Width must be greater than zero."); + } + this.width = width; + } + /** + * Sets the texture height. + * + * @param height texture height greater than zero + */ + public void setHeight(int height) { + if (height <= 0) { + throw new IllegalArgumentException("Height must be greater than zero."); + } + this.height = height; + } + /** + * Sets the texture depth. + *

    + * Values less than or equal to zero indicate a 2D texture. + * + * @param depth texture depth (or less or equal to than zero) + */ + public void setDepth(int depth) { + if (depth < 0) { + throw new IllegalArgumentException("Depth cannot be less than zero."); + } + this.depth = depth; + } + /** + * Sets the width and height of the texture to the length. + * + * @param length + */ + public void setSquare(int length) { + width = height = length; + } + /** + * Sets the width, height, and depth of the texture to the length. + * + * @param length + */ + public void setCube(int length) { + width = height = depth = length; + } + /** + * Sets the width and height of the texture. + * + * @param width + * @param height + */ + public void setSize(int width, int height) { + setWidth(width); + setHeight(height); + } + /** + * Sets the width, height, and depth of the texture. + * + * @param width + * @param height + * @param depth + */ + public void setSize(int width, int height, int depth) { + setWidth(width); + setHeight(height); + setDepth(depth); + } + /** + * Sets the given texture demensions to contain the specified number of pixels. + * + * @param pixels + * @param w true to set width + * @param h true to set height + * @param d true to set depth + */ + public void setNumPixels(int pixels, boolean w, boolean h, boolean d) { + width = height = 1; + if (depth > 0) depth = 1; + else depth = 0; + int n = 0; + if (w) n++; + if (h) n++; + if (d) n++; + int length; + switch (n) { + case 3: length = (int)Math.ceil(Math.cbrt(pixels)); break; + case 2: length = (int)Math.ceil(Math.sqrt(pixels)); break; + default: length = pixels; + } + if (w) width = length; + if (h) height = length; + if (d) depth = length; + } + /** + * Sets the number of samples of the texture's image. + * + * @param samples + */ + public void setSamples(int samples) { + if (samples <= 0) { + throw new IllegalArgumentException("Image samples must be greater than zero."); + } + this.samples = samples; + } + /** + * Sets the format of the image. + * + * @param format + */ + public void setFormat(Image.Format format) { + Objects.requireNonNull(format); + this.format = format; + } + /** + * Sets reallocation so that the target image only needs to have the same + * format type (color versus depth) as this definition. + *

    + * default=false + * + * @param formatFlexible + */ + public void setFormatFlexible(boolean formatFlexible) { + this.formatFlexible = formatFlexible; + } + /** + * Sets the color space of the texture. + * + * @param colorSpace + */ + public void setColorSpace(ColorSpace colorSpace) { + this.colorSpace = colorSpace; + } + /** + * Sets the magnification filter of the texture. + * + * @param magFilter mag filter, or null to use default + */ + public void setMagFilter(Texture.MagFilter magFilter) { + this.magFilter = magFilter; + } + /** + * Sets the minification filter of the texture. + * + * @param minFilter min filter, or null to use default + */ + public void setMinFilter(Texture.MinFilter minFilter) { + this.minFilter = minFilter; + } + /** + * Sets reallocation so that the target image does not need the same + * color space as this definition. + * + * @param colorSpaceFlexible + */ + public void setColorSpaceFlexible(boolean colorSpaceFlexible) { + this.colorSpaceFlexible = colorSpaceFlexible; + } + /** + * Sets the wrap mode on all axis. + * + * @param mode + */ + public void setWrap(Texture.WrapMode mode) { + wrapS = wrapT = wrapR = mode; + } + /** + * Sets the wrap mode on the specified axis. + * + * @param axis + * @param mode + */ + public void setWrap(Texture.WrapAxis axis, Texture.WrapMode mode) { + switch (axis) { + case S: wrapS = mode; break; + case T: wrapT = mode; break; + case R: wrapR = mode; break; + } + } + + /** + * Gets the texture type handled by this definition. + * + * @return + */ + public Class getType() { + return type; + } + /** + * + * @return + */ + public Function getTextureBuilder() { + return textureBuilder; + } + /** + * + * @return + */ + public Function getImageExtractor() { + return imageExtractor; + } + /** + * + * @return + */ + public int getWidth() { + return width; + } + /** + * + * @return + */ + public int getHeight() { + return height; + } + /** + * + * @return + */ + public int getDepth() { + return depth; + } + /** + * Returns the number of pixels contained in the texture. + * + * @return + */ + public int getNumPixels() { + if (depth > 0) return width*height*depth; + else return width*height; + } + /** + * + * @return + */ + public int getSamples() { + return samples; + } + /** + * + * @return + */ + public Image.Format getFormat() { + return format; + } + /** + * + * @return + */ + public boolean isFormatFlexible() { + return formatFlexible; + } + /** + * + * @return + */ + public ColorSpace getColorSpace() { + return colorSpace; + } + /** + * + * @return + */ + public Texture.MagFilter getMagFilter() { + return magFilter; + } + /** + * + * @return + */ + public Texture.MinFilter getMinFilter() { + return minFilter; + } + /** + * + * @return + */ + public boolean isColorSpaceFlexible() { + return colorSpaceFlexible; + } + /** + * Gets the wrap mode on the specified axis. + * + * @param axis + * @return + */ + public Texture.WrapMode getWrap(Texture.WrapAxis axis) { + switch (axis) { + case S: return wrapS; + case T: return wrapT; + case R: return wrapR; + default: throw new IllegalArgumentException(); + } + } + + /** + * Creates a general-purpose definition for {@link Texture2D}s. + * + * @return + */ + public static TextureDef texture2D() { + return new TextureDef<>(Texture2D.class, TEXTURE_2D); + } + /** + * Creates a general-purpose definition for {@link Texture3D}s. + * + * @return + */ + public static TextureDef texture3D() { + return new TextureDef<>(Texture3D.class, TEXTURE_3D); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/ValueDef.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/ValueDef.java new file mode 100644 index 0000000000..b75b10b825 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/definitions/ValueDef.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.definitions; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * General resource definition implementation. + * + * @author codex + * @param + */ +public class ValueDef extends AbstractResourceDef { + + private final Class type; + private Function builder; + private Consumer reviser; + + public ValueDef(Class type, Function create) { + this.type = type; + this.builder = create; + } + + @Override + public T createResource() { + return builder.apply(null); + } + @Override + public T applyDirectResource(Object resource) { + if (reviser != null && type.isAssignableFrom(resource.getClass())) { + T res = (T)resource; + reviser.accept(res); + return res; + } + return null; + } + @Override + public T applyIndirectResource(Object resource) { + return null; + } + + /** + * Sets the builder function that constructs new objects. + * + * @param builder + */ + public void setBuilder(Function builder) { + this.builder = builder; + } + /** + * Sets the consumer that alters objects for reallocation. + * + * @param reviser + */ + public void setReviser(Consumer reviser) { + this.reviser = reviser; + } + + /** + * Gets the object type handled by this definition. + * + * @return + */ + public Class getType() { + return type; + } + /** + * + * @return + */ + public Function getBuilder() { + return builder; + } + /** + * + * @return + */ + public Consumer getReviser() { + return reviser; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/export/FrameGraphData.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/export/FrameGraphData.java new file mode 100644 index 0000000000..2c6df70960 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/export/FrameGraphData.java @@ -0,0 +1,64 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph.export; + +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.export.SavableObject; +import com.jme3.renderer.framegraph.FrameGraph; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Holds savable data for a FrameGraph. + * + * @author codex + */ +public class FrameGraphData implements Savable { + + private String name; + private ModuleGraphData modules; + private ArrayList settings = new ArrayList<>(); + + public FrameGraphData() {} + public FrameGraphData(FrameGraph frameGraph) { + this.name = frameGraph.getName(); + this.modules = frameGraph.createModuleData(); + HashMap settingsMap = frameGraph.getSettingsMap(); + for (String key : settingsMap.keySet()) { + settings.add(new SavableObject(key, settingsMap.get(key))); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(name, "name", "FrameGraph"); + out.write(modules, "modules", null); + out.writeSavableArrayList(settings, "settings", new ArrayList<>()); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + name = in.readString("name", "FrameGraph"); + modules = in.readSavable("modules", ModuleGraphData.class, null); + settings = in.readSavableArrayList("settings", new ArrayList<>()); + } + + public String getName() { + return name; + } + public ModuleGraphData getModules() { + return modules; + } + public ArrayList getSettings() { + return settings; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/export/ModuleGraphData.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/export/ModuleGraphData.java new file mode 100644 index 0000000000..b01f3e7209 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/export/ModuleGraphData.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.export; + +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.renderer.framegraph.Connectable; +import com.jme3.renderer.framegraph.modules.RenderModule; +import com.jme3.renderer.framegraph.ResourceTicket; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.function.Consumer; + +/** + * Holds savable data for a root module and its descendents. + * + * @author codex + */ +public class ModuleGraphData implements Savable { + + private static final ArrayList DEF_CONNECTIONS = new ArrayList<>(); + + private RenderModule rootModule; + + public ModuleGraphData() {} + public ModuleGraphData(RenderModule root) { + this.rootModule = root; + } + + @Override + public void write(JmeExporter ex) throws IOException { + if (rootModule == null) { + throw new NullPointerException("Root module cannot be null."); + } + // extract connections + final ArrayList connections = new ArrayList<>(); + final LinkedList members = new LinkedList<>(); + rootModule.traverse(new ModuleTreeExtractor(members)); + // descend the queue, so that output ids can be reset in the same pass + for (Iterator it = members.descendingIterator(); it.hasNext();) { + RenderModule m = it.next(); + for (ResourceTicket t : m.getInputTickets()) { + if (t.hasSource()) { + int sourceId = t.getSource().getExportGroupId(); + if (sourceId < 0) { + connections.add(new SavableConnection(m.getId(), sourceId, + t.getName(), t.getSource().getName())); + } + } + } + // reset output ids, since they will no longer be used + for (ResourceTicket t : m.getOutputTickets()) { + t.setExportGroupId(-1); + } + } + // write + OutputCapsule out = ex.getCapsule(this); + out.write(rootModule, "root", null); + out.writeSavableArrayList(connections, "connections", DEF_CONNECTIONS); + connections.clear(); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + rootModule = in.readSavable("root", RenderModule.class, null); + final ArrayList connections = in.readSavableArrayList("connections", DEF_CONNECTIONS); + final HashMap registry = new HashMap<>(); + rootModule.traverse(m -> { if (registry.put(m.getId(), m) != null) + throw new IllegalStateException("Modules with duplicate ids imported."); }); + for (SavableConnection c : connections) { + Connectable source = registry.get(c.getSourceId()); + if (source == null) { + throw new NullPointerException("Source of connection not found."); + } + Connectable target = registry.get(c.getTargetId()); + if (target == null) { + throw new NullPointerException("Target of connection not found."); + } + target.makeInput(source, c.getSourceTicket(), c.getTargetTicket()); + } + connections.clear(); + registry.clear(); + } + + public void setRootModule(RenderModule rootModule) { + this.rootModule = rootModule; + } + public RenderModule getRootModule() { + return rootModule; + } + public T getRootModule(Class requiredType) { + if (rootModule != null && !requiredType.isAssignableFrom(rootModule.getClass())) { + throw new ClassCastException("Module tree root is a "+rootModule.getClass().getName() + + " when required as a "+requiredType.getName()); + } + return (T)rootModule; + } + + private static class ModuleTreeExtractor implements Consumer { + + private final LinkedList members; + + public ModuleTreeExtractor(LinkedList members) { + this.members = members; + } + + @Override + public void accept(RenderModule m) { + members.add(m); + // only need to apply id to export tickets, since we will have the + // correct id handy for input tickets when we extract connections. + for (ResourceTicket t : m.getOutputTickets()) { + t.setExportGroupId(m.getId()); + } + } + + public LinkedList getMembers() { + return members; + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/export/SavableConnection.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/export/SavableConnection.java new file mode 100644 index 0000000000..13eb2a73f2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/export/SavableConnection.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.export; + +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 java.io.IOException; + +/** + * Represents a connection of tickets between render passes. + * + * @author codex + */ +public class SavableConnection implements Savable { + + private int targetId, sourceId; + private String targetTicket, sourceTicket; + + /** + * Serialization only. + */ + public SavableConnection() {} + /** + * + * @param targetId + * @param sourceId + * @param targetTicket + * @param sourceTicket + */ + public SavableConnection(int targetId, int sourceId, String targetTicket, String sourceTicket) { + this.targetId = targetId; + this.sourceId = sourceId; + this.targetTicket = targetTicket; + this.sourceTicket = sourceTicket; + } + + /** + * + * @param targetId + */ + public void setTargetId(int targetId) { + this.targetId = targetId; + } + /** + * + * @param sourceId + */ + public void setSourceId(int sourceId) { + this.sourceId = sourceId; + } + /** + * + * @param targetTicket + */ + public void setTargetTicket(String targetTicket) { + this.targetTicket = targetTicket; + } + /** + * + * @param sourceTicket + */ + public void setSourceTicket(String sourceTicket) { + this.sourceTicket = sourceTicket; + } + + /** + * + * @return + */ + public int getTargetId() { + return targetId; + } + /** + * + * @return + */ + public int getSourceId() { + return sourceId; + } + /** + * + * @return + */ + public String getTargetTicket() { + return targetTicket; + } + /** + * + * @return + */ + public String getSourceTicket() { + return sourceTicket; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(targetId, "inputId", -1); + out.write(sourceId, "outputId", -1); + out.write(targetTicket, "inputTicket", ""); + out.write(sourceTicket, "outputTicket", ""); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + targetId = in.readInt("inputId", -1); + sourceId = in.readInt("outputId", -1); + targetTicket = in.readString("inputTicket", ""); + sourceTicket = in.readString("outputTicket", ""); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/light/LightFrustum.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/light/LightFrustum.java new file mode 100644 index 0000000000..11ecac19c2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/light/LightFrustum.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.light; + +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.Camera; +import java.util.ArrayList; +import java.util.LinkedList; + +/** + * A screenspace rectangle that contains a light's influence. + * + * @author codex + */ +public class LightFrustum { + + private float left, right, top, bottom; + + private Matrix4f vp; + private float camLeftCoeff = -1; + private float camTopCoeff = -1; + private float viewPortWidth = -1; + private float viewPortHeight = -1; + private final float[] matArray1 = new float[16]; + private final Vector3f tempVec3 = new Vector3f(); + private final Vector3f tempVec3_2 = new Vector3f(); + private final Vector4f tempVec4 = new Vector4f(); + private final Vector4f tempvec4_2 = new Vector4f(); + private final Vector4f tempVec4_3 = new Vector4f(); + private final Vector4f camUp = new Vector4f(); + private final Vector4f camLeft = new Vector4f(); + private final Vector4f lightLeft = new Vector4f(); + private final Vector4f lightUp = new Vector4f(); + private final Vector4f lightCenter = new Vector4f(); + private boolean fullscreen = false; + + /** + * Calculates important values from the camera. + * + * @param cam + * @return + */ + public LightFrustum calculateCamera(Camera cam) { + viewPortWidth = cam.getWidth() * 0.5f; + viewPortHeight = cam.getHeight() * 0.5f; + vp = cam.getViewProjectionMatrix(); + Matrix4f v = cam.getViewMatrix(); + v.get(matArray1); + tempVec3.set(matArray1[0], matArray1[1], matArray1[2]); + camLeftCoeff = 1.0f / cam.getWorldPlane(1).getNormal().dot(tempVec3); + tempVec3.set(matArray1[4], matArray1[5], matArray1[6]); + camTopCoeff = 1.0f / cam.getWorldPlane(2).getNormal().dot(tempVec3); + camLeft.set(matArray1[0], matArray1[1], matArray1[2], -1.0f).multLocal(-1.0f); + camUp.set(matArray1[4], matArray1[5], matArray1[6], 1.0f); + return this; + } + + /** + * Calculates the frustum from the light. + * + * @param l + * @return + */ + public LightFrustum fromLight(Light l) { + switch (l.getType()) { + case Directional: + return fromDirectional((DirectionalLight)l); + case Point: + return fromPoint((PointLight)l); + case Spot: + return fromSpot((SpotLight)l); + case Ambient: + return fromAmbient((AmbientLight)l); + default: + throw new UnsupportedOperationException("Light type "+l.getType()+" is not supported."); + } + } + /** + * Calculates the frustum from the DirectionalLight. + * + * @param dl + * @return + */ + public LightFrustum fromDirectional(DirectionalLight dl) { + return fullscreen(); + } + /** + * Calculates the frustum from the point light. + * + * @param pl + * @return + */ + public LightFrustum fromPoint(PointLight pl) { + + //return fullscreen(); + + float r = Math.abs(pl.getRadius()); + if (r == 0) { + throw new IllegalStateException("PointLight radius cannot be zero in this context."); + } + float lr = r * camLeftCoeff; + float tr = r * camTopCoeff; + tempVec4.set(pl.getPosition().x, pl.getPosition().y, pl.getPosition().z, 1.0f); + Vector4f center = tempVec4; + tempvec4_2.w = 1.0f; + tempVec4_3.w = 1.0f; + + camLeft.mult(lr, tempvec4_2); + tempvec4_2.addLocal(center); + Vector4f lightFrustumLeft = tempvec4_2; + lightFrustumLeft.w = 1.0f; + + camUp.mult(tr, tempVec4_3); + tempVec4_3.addLocal(center); + Vector4f lightFrustumUp = tempVec4_3; + lightFrustumUp.w = 1.0f; + + vp.mult(lightFrustumLeft, lightLeft); + vp.mult(lightFrustumUp, lightUp); + vp.mult(center, lightCenter); + + lightLeft.x /= lightLeft.w; + lightLeft.y /= lightLeft.w; + + lightUp.x /= lightUp.w; + lightUp.y /= lightUp.w; + + lightCenter.x /= lightCenter.w; + lightCenter.y /= lightCenter.w; + + lightLeft.x = viewPortWidth * (1.0f + lightLeft.x); + lightUp.x = viewPortWidth * (1.0f + lightUp.x); + lightCenter.x = viewPortWidth * (1.0f + lightCenter.x); + + lightLeft.y = viewPortHeight * (1.0f - lightLeft.y); + lightUp.y = viewPortHeight * (1.0f - lightUp.y); + lightCenter.y = viewPortHeight * (1.0f - lightCenter.y); + + // light frustum rect + float lw = Math.abs(lightLeft.x - lightCenter.x); + float lh = Math.abs(lightCenter.y - lightUp.y); + float l, b; + if(lightCenter.z < -lightCenter.w){ + l = -lightCenter.x - lw; + b = -lightCenter.y + lh; + } else { + l = lightCenter.x - lw; + b = lightCenter.y + lh; + } + b = viewPortHeight * 2.0f - b; + this.left = l; + this.right = lw * 2.0f + l; + this.top = lh * 2.0f + b; + this.bottom = b; + + return this; + + } + /** + * Calculates the frustum from the SpotLight. + * + * @param sl + * @return + */ + public LightFrustum fromSpot(SpotLight sl) { + return fullscreen(); + } + /** + * Calculates the frustum from the AmbientLight. + * + * @param al + * @return + */ + public LightFrustum fromAmbient(AmbientLight al) { + return fullscreen(); + } + /** + * Calculates a fullscreen frustum. + * + * @return + */ + public LightFrustum fullscreen() { + fullscreen = true; + left = bottom = 0; + right = viewPortWidth*2; + top = viewPortHeight*2; + return this; + } + + /** + * Writes the calculated frustum of the indexed light to a set of screenspace tiles. + * + * @param tileIndices 2D lists containing light indices for each tile + * @param tileInfo information about tile demensions + * @param lightIndex index of light + */ + public void write(ArrayList> tileIndices, TiledRenderGrid tileInfo, int lightIndex) { + if (!fullscreen) { + int width = tileInfo.getGridWidth(); + int numTiles = tileInfo.getNumTiles(); + int tileLeft = (int)Math.max(Math.floor(left / tileInfo.getTileSize()), 0); + int tileRight = (int)Math.min(Math.ceil(right / tileInfo.getTileSize()), width); + int tileBottom = (int)Math.max(Math.floor(bottom / tileInfo.getTileSize()), 0); + int tileTop = (int)Math.min(Math.ceil(top / tileInfo.getTileSize()), tileInfo.getGridHeight()); + for (int b = tileBottom; b < tileTop; b++) { + int base = b*width; + for (int l = tileLeft; l < tileRight; l++) { + int tileId = l+base; + if (tileId >= 0 && tileId < numTiles) { + tileIndices.get(tileId).add(lightIndex); + } + } + } + } else for (LinkedList l : tileIndices) { + l.add(lightIndex); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/light/LightImagePacker.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/light/LightImagePacker.java new file mode 100644 index 0000000000..ed3fb82cd8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/light/LightImagePacker.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.light; + +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.LightProbe; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.texture.Image; +import com.jme3.texture.Texture2D; +import com.jme3.texture.image.ImageRaster; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * Rasters light info to a set of textures. + * + * @author codex + */ +public class LightImagePacker { + + private final Texture2D[] textures = new Texture2D[5]; + private final ImageRaster[] rasters = new ImageRaster[5]; + private final ColorRGBA tempColor = new ColorRGBA(); + private final LightFrustum frustum = new LightFrustum(); + private boolean hasAmbient = false; + private ArrayList> tileIndices = new ArrayList<>(); + + public LightImagePacker() {} + + /** + * Sets the textures to raster light information to. + * + * @param tex1 texture to contain light color and type + * @param tex2 texture to contain light position/direction and coefficients + * @param tex3 texture to contain spot coefficients + * @param tiles texture to contain start indices for each tile (or null to not calculate tiles) + * @param indices texture to contain light indices referenced by tiles (or null to not calculate tiles) + */ + public void setTextures(Texture2D tex1, Texture2D tex2, Texture2D tex3, Texture2D tiles, Texture2D indices) { + validateSize(tex1, tex2); + validateSize(tex1, tex3); + updateTexture(0, tex1); + updateTexture(1, tex2); + updateTexture(2, tex3); + updateTexture(3, tiles); + updateTexture(4, indices); + } + /** + * Gets the array of textures to raster to. + * + * @return + */ + public Texture2D[] getTextures() { + return textures; + } + /** + * Gets the texture at the index. + * + * @param i + * @return + */ + public Texture2D getTexture(int i) { + return textures[i]; + } + + /** + * Rasters the lights in the LightList into the current textures. + * + * @param lights lights to pack + * @param ambient stores the accumulated ambient light + * @param probes stores all LightProbes + * @param cam camera for tile calculations + * @param tileInfo tile demensions for tile calculations + * @return number of directional, point, and spot lights + */ + public int packLights(LightList lights, ColorRGBA ambient, + List probes, Camera cam, TiledRenderGrid tileInfo) { + ambient.set(0, 0, 0, 0); + probes.clear(); + if (lights.size() == 0) { + return 0; + } + int i = 0; + final int limit = textures[0].getImage().getWidth(); + final boolean useTiles = textures[3] != null && textures[4] != null && cam != null && tileInfo != null; + if (useTiles) { + tempColor.set(ColorRGBA.BlackNoAlpha); + frustum.calculateCamera(cam); + // match tile index lists to number of tiles + int n = tileInfo.getNumTiles(); + while (tileIndices.size() > n) { + tileIndices.remove(tileIndices.size()-1); + } + while (tileIndices.size() < n) { + tileIndices.add(new LinkedList<>()); + } + } + boolean packedLight = false; + boolean spotlight = false; + hasAmbient = false; + for (Light l : lights) { + if (l.getType() == Light.Type.Ambient) { + ambient.addLocal(l.getColor()); + hasAmbient = true; + continue; + } + if (l.getType() == Light.Type.Probe) { + probes.add((LightProbe)l); + continue; + } + packedLight = true; + tempColor.set(l.getColor()).setAlpha(l.getType().getId()); + rasters[0].setPixel(i, 0, tempColor); + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight)l; + vectorToColor(dl.getDirection(), tempColor).a = 0; + rasters[1].setPixel(i, 0, tempColor); + if (useTiles) { + frustum.fromDirectional(dl).write(tileIndices, tileInfo, i); + } + break; + case Point: + PointLight pl = (PointLight)l; + vectorToColor(pl.getPosition(), tempColor); + tempColor.a = pl.getInvRadius(); + rasters[1].setPixel(i, 0, tempColor); + if (useTiles) { + frustum.fromPoint(pl).write(tileIndices, tileInfo, i); + } + break; + case Spot: + SpotLight sl = (SpotLight)l; + vectorToColor(sl.getPosition(), tempColor); + tempColor.a = sl.getInvSpotRange(); + rasters[1].setPixel(i, 0, tempColor); + vectorToColor(sl.getDirection(), tempColor); + tempColor.a = sl.getPackedAngleCos(); + rasters[2].setPixel(i, 0, tempColor); + spotlight = true; + if (useTiles) { + frustum.fromSpot(sl).write(tileIndices, tileInfo, i); + } + break; + } + if (++i >= limit) { + break; + } + } + if (packedLight) { + textures[0].getImage().setUpdateNeeded(); + textures[1].getImage().setUpdateNeeded(); + if (spotlight) { + textures[2].getImage().setUpdateNeeded(); + } + if (useTiles) { + packLightIndices(); + } + } + return i; + } + private void packLightIndices() { + int componentIndex = 0; + int xIndex = 0, yIndex = 0; + int tileX = 0, tileY = 0; + final int indexWidth = textures[4].getImage().getWidth(); + final int tileWidth = textures[3].getImage().getWidth(); + final ColorRGBA tileInfoColor = new ColorRGBA(); + tempColor.set(0, 0, 0, 0); + for (LinkedList l : tileIndices) { + // raster tile info to texture + tileInfoColor.r = xIndex; + tileInfoColor.g = yIndex; + tileInfoColor.b = componentIndex; + tileInfoColor.a = l.size(); + rasters[3].setPixel(tileX, tileY, tileInfoColor); + if (++tileX >= tileWidth) { + tileX = 0; + tileY++; + } + // raster light indices to texture + for (int index : l) { + // pack 4 indices per pixel + switch (componentIndex) { + case 0: tempColor.r = index; break; + case 1: tempColor.g = index; break; + case 2: tempColor.b = index; break; + case 3: tempColor.a = index; break; + } + if (++componentIndex > 3) { + componentIndex = 0; + rasters[4].setPixel(xIndex, yIndex, tempColor); + if (++xIndex >= indexWidth) { + xIndex = 0; + yIndex++; + } + } + } + l.clear(); + } + // if the index color is incomplete, raster it to the texture + if (componentIndex != 0) { + rasters[4].setPixel(xIndex, yIndex, tempColor); + } + textures[3].getImage().setUpdateNeeded(); + textures[4].getImage().setUpdateNeeded(); + } + + public boolean hasAmbientLight() { + return hasAmbient; + } + + private void validateSamples(Texture2D tex) { + if (tex.getImage().getMultiSamples() != 1) { + throw new IllegalArgumentException("Texture cannot be multisampled."); + } + } + private void validateSize(Texture2D base, Texture2D target) { + Image baseImg = base.getImage(); + Image targetImg = target.getImage(); + if (baseImg.getWidth() != targetImg.getWidth() || baseImg.getHeight() != targetImg.getHeight()) { + throw new IllegalArgumentException("Texture has incorrect demensions."); + } + } + private void updateTexture(int i, Texture2D tex) { + if (tex == null) { + textures[i] = null; + rasters[i] = null; + } else { + validateSamples(tex); + if (textures[i] != tex) { + textures[i] = tex; + rasters[i] = ImageRaster.create(tex.getImage()); + } + } + } + private ColorRGBA vectorToColor(Vector3f vec, ColorRGBA color) { + if (color == null) { + color = new ColorRGBA(); + } + color.r = vec.x; + color.g = vec.y; + color.b = vec.z; + return color; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/light/TiledRenderGrid.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/light/TiledRenderGrid.java new file mode 100644 index 0000000000..5916f47ebe --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/light/TiledRenderGrid.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.light; + +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.renderer.Camera; +import java.io.IOException; + +/** + * Calculates screenspace tile demensions for rendering + * techniques such as tiled deferred. + * + * @author johnkkk, codex + */ +public class TiledRenderGrid implements Savable { + + private int divisions = 4; + private int forcedTileSize = 64; //64 + private int tileSize = 0; + private int gridWidth = 0; + private int gridHeight = 0; + + public TiledRenderGrid() {} + public TiledRenderGrid(int divisions) { + this.divisions = divisions; + } + public TiledRenderGrid(int divisions, int forcedTileSize) { + this.divisions = divisions; + this.forcedTileSize = forcedTileSize; + } + + public void update(Camera cam) { + tileSize = (forcedTileSize > 0 ? forcedTileSize : cam.getWidth()/divisions); + int x1 = (int)(cam.getViewPortLeft()*cam.getWidth()); + int y1 = (int)(cam.getViewPortBottom()*cam.getHeight()); + int x2 = (int)(cam.getViewPortRight()*cam.getWidth()); + int y2 = (int)(cam.getViewPortTop()*cam.getHeight()); + gridWidth = (x2-x1)/tileSize; + gridHeight = (y2-y1)/tileSize; + } + + public void setNumDivisions(int divisions) { + assert divisions > 0 : "Number of divisions must be greater than zero."; + this.divisions = divisions; + } + public void setForcedTileSize(int forcedTileSize) { + this.forcedTileSize = forcedTileSize; + } + public void copyFrom(TiledRenderGrid src) { + setNumDivisions(src.divisions); + setForcedTileSize(src.forcedTileSize); + tileSize = src.tileSize; + gridWidth = src.gridWidth; + gridHeight = src.gridHeight; + } + + public int getNumDivisions() { + return divisions; + } + public int getForcedTileSize() { + return forcedTileSize; + } + public int getTileSize() { + return tileSize; + } + public int getGridWidth() { + return gridWidth; + } + public int getGridHeight() { + return gridHeight; + } + public int getNumTiles() { + return gridWidth*gridHeight; + } + + public boolean gridPropertiesDiffer(TiledRenderGrid grid) { + return grid.tileSize != tileSize || gridDemensionsDiffer(grid); + } + public boolean gridDemensionsDiffer(TiledRenderGrid grid) { + return grid.gridWidth != gridWidth || grid.gridHeight != gridHeight; + } + public boolean numTilesDiffer(TiledRenderGrid grid) { + return grid.getNumTiles() != getNumTiles(); + } + + public void verifyUpdated() { + if (needsUpdate()) { + throw new IllegalStateException("Update is required before use."); + } + } + public boolean needsUpdate() { + return tileSize <= 0 || gridWidth <= 0 || gridHeight <= 0; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(divisions, "divisions", 4); + out.write(forcedTileSize, "forcedTileSize", 64); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + divisions = in.readInt("divisions", 4); + forcedTileSize = in.readInt("forcedTileSize", 64); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/ModuleLocator.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/ModuleLocator.java new file mode 100644 index 0000000000..685bc2d95a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/ModuleLocator.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.modules; + +import com.jme3.renderer.framegraph.modules.RenderModule; + +/** + * Locates a pass. + * + * @author codex + * @param + */ +public interface ModuleLocator { + + /** + * Determines if the module qualifies for this locator. + * + * @param module + * @return pass, or null if not accepted + */ + public T accept(RenderModule module); + + /** + * Locates a pass by its type. + * + * @param + * @param type + * @return + */ + public static ModuleLocator by(Class type) { + return pass -> { + if (type.isAssignableFrom(pass.getClass())) { + return (R)pass; + } else { + return null; + } + }; + } + + /** + * Locates a pass by its type and name. + * + * @param + * @param type + * @param name + * @return + */ + public static ModuleLocator by(Class type, String name) { + return pass -> { + if (name.equals(pass.getName()) && type.isAssignableFrom(pass.getClass())) { + return (R)pass; + } else { + return null; + } + }; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/RenderContainer.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/RenderContainer.java new file mode 100644 index 0000000000..4c2e2b0289 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/RenderContainer.java @@ -0,0 +1,204 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph.modules; + +import com.jme3.renderer.framegraph.modules.RenderModule; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.PassIndex; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * + * @author codex + * @param + */ +public class RenderContainer extends RenderModule implements Iterable { + + protected final ArrayList queue = new ArrayList<>(); + + @Override + public void initModule(FrameGraph frameGraph) { + for (RenderModule m : queue) { + m.initializeModule(frameGraph); + } + } + @Override + public void cleanupModule(FrameGraph frameGraph) { + for (RenderModule m : queue) { + m.cleanupModule(); + } + } + @Override + public void prepareModuleRender(FGRenderContext context, PassIndex index) { + super.prepareModuleRender(context, index); + for (RenderModule m : queue) { + index.queueIndex++; + m.prepareModuleRender(context, index); + } + } + @Override + public void prepareRender(FGRenderContext context) {} + @Override + public void executeRender(FGRenderContext context) { + for (RenderModule m : queue) { + if (isInterrupted()) { + break; + } + m.executeModuleRender(context); + } + } + @Override + public void resetRender(FGRenderContext context) { + for (RenderModule m : queue) { + m.resetRender(context); + } + } + @Override + public void renderingComplete() { + for (RenderModule m : queue) { + m.renderingComplete(); + } + } + @Override + public void countReferences() { + for (RenderModule m : queue) { + m.countReferences(); + } + } + @Override + public boolean isUsed() { + // if executing a container becomes heavy on its own, change this to + // check isUsed() for each contained module. + return !queue.isEmpty(); + } + @Override + public void interrupt() { + super.interrupt(); + for (RenderModule m : queue) { + m.interrupt(); + } + } + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + ArrayList array = new ArrayList<>(); + array.addAll(queue); + out.writeSavableArrayList(array, "queue", new ArrayList<>(0)); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + ArrayList array = in.readSavableArrayList("queue", new ArrayList<>(0)); + queue.addAll(array); + } + @Override + public Iterator iterator() { + return queue.iterator(); + } + @Override + public void traverse(Consumer traverser) { + traverser.accept(this); + for (RenderModule m : queue) { + m.traverse(traverser); + } + } + + public T add(T module, int index) { + assert module != null : "Cannot add null module."; + assert this != module : "Cannot add container to itself."; + if (module.getParent() != null) { + module.getParent().remove(module); + } + if (index < 0) { + index = queue.size(); + } + queue.add(index, module); + if (module.setParent(this)) { + if (isAssigned()) { + module.initializeModule(frameGraph); + } + return module; + } + throw new IllegalArgumentException(module+" cannot be added to this container."); + } + public T add(T module) { + return add(module, queue.size()); + } + public T[] addLoop(T[] array, int start, Function factory, String source, String target) { + for (int i = 0; i < array.length; i++) { + T module = array[i]; + if (module == null) { + if (factory == null) { + throw new NullPointerException("Module to add cannot be null."); + } + module = array[i] = factory.apply(i); + } + if (start >= 0) { + add(module, start++); + } else { + add(module); + } + if (i > 0) { + array[i].makeInput(array[i-1], source, target); + } + } + return array; + } + public R get(int index) { + return queue.get(index); + } + public T get(ModuleLocator by) { + for (RenderModule m : queue) { + T module = by.accept(m); + if (module != null) { + return module; + } else if (m instanceof RenderContainer) { + module = (T)((RenderContainer)m).get(by); + if (module != null) { + return module; + } + } + } + return null; + } + public boolean remove(R module) { + if (queue.remove(module)) { + module.cleanupModule(); + return true; + } + return false; + } + public R remove(int index) { + if (index < 0 || index >= queue.size()) { + return null; + } + R m = queue.remove(index); + m.cleanupModule(); + return m; + } + public void clear() { + for (RenderModule m : queue) { + m.cleanupModule(); + } + queue.clear(); + } + + public int size() { + return queue.size(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/RenderModule.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/RenderModule.java new file mode 100644 index 0000000000..87d10e9ac4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/RenderModule.java @@ -0,0 +1,324 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph.modules; + +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.renderer.framegraph.Connectable; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.PassIndex; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.ResourceUser; +import com.jme3.renderer.framegraph.TicketGroup; +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * + * @author codex + */ +public abstract class RenderModule implements Connectable, ResourceUser, Savable { + + protected FrameGraph frameGraph; + protected String name = "RenderModule"; + protected RenderContainer parent; + protected final PassIndex index = new PassIndex(); + protected final LinkedList inputs = new LinkedList<>(); + protected final LinkedList outputs = new LinkedList<>(); + protected final HashMap groups = new HashMap<>(); + private boolean interrupted = false; + private int refs = 0; + private int id = -1; + + @Override + public LinkedList getInputTickets() { + return inputs; + } + @Override + public LinkedList getOutputTickets() { + return outputs; + } + @Override + public PassIndex getIndex() { + return index; + } + @Override + public void countReferences() { + refs = outputs.size(); + } + @Override + public void dereference() { + refs--; + } + @Override + public boolean isUsed() { + return refs > 0; + } + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(name, "name", "RenderModule"); + out.write(id, "exportId", -1); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + name = in.readString("name", "RenderModule"); + id = in.readInt("exportId", -1); + } + + @Override + public ResourceTicket getInput(String name) { + return getTicketFromStream(inputs.stream(), name); + } + @Override + public ResourceTicket getOutput(String name) { + return getTicketFromStream(outputs.stream(), name); + } + @Override + public TicketGroup getGroup(String name) { + return groups.get(name); + } + @Override + public ResourceTicket addListEntry(String groupName) { + TicketGroup g = getGroup(name, true); + g.requireAsList(true); + return addInput(g.add()); + } + + public int getId() { + return id; + } + + /** + * Adds the ticket as input. + * + * @param + * @param input + * @return given ticket + */ + public ResourceTicket addInput(ResourceTicket input) { + getInputTickets().add(input); + return input; + } + /** + * Adds the ticket as output. + * + * @param + * @param output + * @return given ticket + */ + public ResourceTicket addOutput(ResourceTicket output) { + getOutputTickets().add(output); + return output; + } + /** + * Creates and registers a new ticket as input. + * + * @param + * @param name name assigned to the new ticket + * @return created ticket + */ + public ResourceTicket addInput(String name) { + ResourceTicket.validateUserTicketName(name); + return addInput(new ResourceTicket<>(name)); + } + /** + * Creates and registers a new ticket as output. + * + * @param + * @param name name assigned to the new ticket + * @return created ticket + */ + public ResourceTicket addOutput(String name) { + ResourceTicket.validateUserTicketName(name); + return addOutput(new ResourceTicket<>(name)); + } + /** + * Creates and adds a ticket array as a group input of the specified length under the given name. + *

    + * A group bundles several tickets together so that they can easily be used together. + * Each individual ticket is handled just like any other, it is just registered with the group as well. + * Names are formatted as {@code groupName+"["+index+"]"}. + * + * @param + * @param name + * @param length + * @return created ticket array + */ + public ResourceTicket[] addInputGroup(String name, int length) { + ResourceTicket.validateUserTicketName(name); + TicketGroup group = new TicketGroup(name, length); + for (int i = 0; i < length; i++) { + group.getArray()[i] = addInput(group.create(i)); + } + groups.put(name, group); + return group.getArray(); + } + /** + * Creates and adds a ticket array as a group output of the specified length under the given name. + *

    + * A group bundles several tickets together so that they can easily be used together. + * Each individual ticket is handled just like any other, it is just registered with the group as well. + * Names are formatted as {@code groupName+"["+index+"]"}. + * + * @param + * @param name + * @param length + * @return create ticket array + */ + public ResourceTicket[] addOutputGroup(String name, int length) { + ResourceTicket.validateUserTicketName(name); + TicketGroup group = new TicketGroup(name, length); + for (int i = 0; i < length; i++) { + group.getArray()[i] = addOutput(group.create(i)); + } + groups.put(name, group); + return group.getArray(); + } + /** + * Creates an input ticket list. + *

    + * A ticket list is an extension of a ticket group where the size is indefinite, meaning connections + * can be added or removed at will. The order of connections is not guaranteed, especially + * when a ticket list is loaded from a save file. + *

    + * Each addition or removal from a ticket list requires an array resize, so it a ticket + * list should remain static where possible. + * + * @param name + */ + public void addInputList(String name) { + ResourceTicket.validateUserTicketName(name); + groups.put(name, new TicketGroup(name)); + } + + private static ResourceTicket getTicketFromStream(Stream stream, String name) { + return stream.filter(t -> name.equals(t.getName())).findFirst().orElse(null); + } + public ResourceTicket[] getGroupArray(String name) { + return getGroup(name, true).getArray(); + } + + /** + * Interrupts execution of this module. + */ + public void interrupt() { + interrupted = true; + } + + public void initializeModule(FrameGraph frameGraph) { + if (this.frameGraph != null) { + throw new IllegalStateException("Module already initialized."); + } + this.frameGraph = frameGraph; + id = this.frameGraph.getNextId(); + initModule(this.frameGraph); + } + public void prepareModuleRender(FGRenderContext context, PassIndex index) { + this.index.set(index); + prepareRender(context); + } + public void executeModuleRender(FGRenderContext context) { + if (!isUsed()) { + return; + } + executeRender(context); + } + public void cleanupModule() { + id = -1; + if (frameGraph != null) { + cleanupModule(frameGraph); + frameGraph = null; + } + } + + /** + * Initializes the RenderModule implementation. + *

    + * For most cases, use {@link #initializeModule(com.jme3.renderer.framegraph.FrameGraph)} + * instead. + * + * @param frameGraph + */ + protected abstract void initModule(FrameGraph frameGraph); + /** + * Prepares the RenderModule implementation. + *

    + * For most cases, use {@link #prepareModuleRender(com.jme3.renderer.framegraph.FGRenderContext, com.jme3.renderer.framegraph.PassIndex)} + * instead. + * + * @param context + */ + protected abstract void prepareRender(FGRenderContext context); + /** + * Executes the RenderModule implementation. + *

    + * For most cases, use {@link #executeModuleRender(com.jme3.renderer.framegraph.FGRenderContext)} + * instead. + * + * @param context + */ + protected abstract void executeRender(FGRenderContext context); + /** + * Resets the RenderModule after execution. + * + * @param context + */ + protected abstract void resetRender(FGRenderContext context); + /** + * Cleans up the RenderModule implementation. + *

    + * For most cases, use {@link #cleanupModule()} instead. + * + * @param frameGraph + */ + protected abstract void cleanupModule(FrameGraph frameGraph); + + protected abstract void renderingComplete(); + public abstract void traverse(Consumer traverser); + + public void setName(String name) { + this.name = name; + } + protected boolean setParent(RenderContainer parent) { + this.parent = parent; + return true; + } + + public String getName() { + return name; + } + public RenderContainer getParent() { + return parent; + } + public boolean isAssigned() { + return frameGraph != null; + } + public boolean isInterrupted() { + return interrupted; + } + public boolean isAsync() { + return !index.isMainThread(); + } + public boolean isAncenstor(RenderModule ancestor) { + RenderModule p = parent; + while (p != null) { + if (p == ancestor) { + return true; + } + p = p.parent; + } + return false; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/RenderThread.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/RenderThread.java new file mode 100644 index 0000000000..50fa0b13cc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/RenderThread.java @@ -0,0 +1,61 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph.modules; + +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.modules.RenderContainer; +import com.jme3.renderer.framegraph.modules.RenderModule; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author codex + */ +public class RenderThread extends RenderContainer implements Runnable { + + private static final Logger LOG = Logger.getLogger(RenderThread.class.getName()); + + private FGRenderContext context; + + @Override + public void run() { + try { + executeModuleRender(context); + } catch (Exception ex) { + LOG.log(Level.SEVERE, "An exception occured while executing RenderThread at index "+index.threadIndex+'.', ex); + frameGraph.interruptRendering(); + } finally { + frameGraph.notifyThreadComplete(this); + } + } + + /** + * Starts running the group of modules contained by this RenderThread. + *

    + * If the thread index of this module is asynchronous (index != + * {@link PassIndex#MAIN_THREAD}), a new thread is spawned for this + * to execute on. + *

    + * All exceptions that occur under this module are caught and + * the FrameGraph notified to ensure graceful interruption of other threads. + * + * @param context + */ + public void startThreadExecution(FGRenderContext context) { + this.context = context; + if (isInterrupted()) { + frameGraph.notifyThreadComplete(this); + return; + } + if (!isAsync()) { + run(); + } else { + Thread t = new Thread(this); + t.start(); + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/ThreadLauncher.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/ThreadLauncher.java new file mode 100644 index 0000000000..34c38c5f7b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/modules/ThreadLauncher.java @@ -0,0 +1,75 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph.modules; + +import com.jme3.renderer.RendererException; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.PassIndex; + +/** + * Container that launches renderering threads. + *

    + * This module cannot be added to another container. It is designed to be + * handled by the FrameGraph directly. + * + * @author codex + */ +public class ThreadLauncher extends RenderContainer { + + private static final long threadTimeoutMillis = 2000; + + private int activeThreads = 0; + + public ThreadLauncher() {} + + @Override + public void prepareModuleRender(FGRenderContext context, PassIndex index) { + super.prepareModuleRender(context, index); + for (RenderThread t : queue) { + t.prepareModuleRender(context, index); + index.threadIndex++; + index.queueIndex = 0; + } + } + @Override + public void executeRender(FGRenderContext context) { + activeThreads = queue.size(); + for (int i = queue.size()-1; i >= 0; i--) { + if (isInterrupted()) { + break; + } + queue.get(i).startThreadExecution(context); + } + long start = System.currentTimeMillis(); + while (activeThreads > 0) { + // wait for all threads to complete + if (System.currentTimeMillis()-start > threadTimeoutMillis) { + throw new RendererException("Timeout occured waiting for threads to complete."); + } + } + } + @Override + protected boolean setParent(RenderContainer parent) { + return false; + } + + public void notifyThreadComplete(RenderThread thread) { + activeThreads--; + } + + public RenderThread getOrCreate(int i) { + if (i >= queue.size()) { + return add(); + } else if (i >= 0) { + return queue.get(i); + } else { + return queue.get(PassIndex.MAIN_THREAD); + } + } + public RenderThread add() { + return add(new RenderThread()); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/Attribute.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/Attribute.java new file mode 100644 index 0000000000..9158d226e2 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/Attribute.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.NullSavable; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.renderer.framegraph.client.GraphTarget; +import com.jme3.renderer.framegraph.client.GraphSource; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import java.io.IOException; +import java.util.LinkedList; + +/** + * Interface pass between the framegraph and game logic, allowing them to communicate. + *

    + * Game logic can listen to framegraph parameters via {@link GraphTarget}s, and/or game logic + * can communicate parameters to the framegraph via a {@link GraphSource}. + *

    + * Objects handled by this pass are automatically marked as constant, so that future changes + * do not taint the game logic's resource view. + *

    + * Inputs: + *

      + *
    • {@link #INPUT} ({@link Object}): the value to share with game logic via registered GraphTargets (optional).
    • + *
    + * Outputs: + *
      + *
    • {@link #OUTPUT} ({@link Object}): the value to share with the FrameGraph from game logic using the registered GraphSource
    • + *
    + * + * @author codex + * @param + */ +public class Attribute extends RenderPass { + + public static final String INPUT = "Input", OUTPUT = "Output"; + + private ResourceTicket in, out; + private T defaultValue = null; + private GraphSource source; + private final LinkedList> targets = new LinkedList<>(); + + @Override + protected void initialize(FrameGraph frameGraph) { + this. + in = addInput(INPUT); + out = addOutput(OUTPUT); + } + @Override + protected void prepare(FGRenderContext context) { + declare(null, out); + referenceOptional(in); + } + @Override + protected void execute(FGRenderContext context) { + T inVal = resources.acquireOrElse(in, null); + if (inVal != null && !targets.isEmpty()) { + boolean used = false; + for (GraphTarget t : targets) { + if (t.setGraphValue(frameGraph, context.getViewPort(), inVal)) { + used = true; + } + } + if (used) { + resources.setConstant(in); + } + } + T outVal; + if (source != null) { + outVal = source.getGraphValue(frameGraph, context.getViewPort()); + } else { + outVal = defaultValue; + } + if (outVal != null) { + resources.setPrimitive(out, outVal); + } else { + resources.setUndefined(out); + } + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) { + targets.clear(); + source = null; + } + @Override + public boolean isUsed() { + return super.isUsed() || in.hasSource(); + } + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + out.writeFromCollection(targets, "targets", true); + if (source != null && source instanceof Savable) { + out.write((Savable)source, "source", NullSavable.INSTANCE); + } + if (defaultValue != null && defaultValue instanceof Savable) { + out.write((Savable)defaultValue, "default", NullSavable.INSTANCE); + } + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + in.readToCollection("targets", targets); + source = (GraphSource)in.readSavable("source", null); + defaultValue = (T)in.readSavable("default", null); + } + + /** + * Adds the graph target. + *

    + * If any targets are recieving from this Attribute, the incoming + * object will be marked as constant. + * + * @param target target to add (not null) + */ + public void addTarget(GraphTarget target) { + targets.add(target); + } + /** + * Removes the graph target. + * + * @param target target to remove (not null) + */ + public void removeTarget(GraphTarget target) { + targets.remove(target); + } + /** + * Sets the graph source. + * + * @param source + */ + public void setSource(GraphSource source) { + this.source = source; + } + /** + * + * @param defaultValue + */ + public void setDefaultValue(T defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * + * @return + */ + public T getDefaultValue() { + return defaultValue; + } + + /** + * + * @return + */ + public static String getInput() { + return "Value"; + } + /** + * + * @param i + * @return + */ + public static String getInput(int i) { + return "Value["+i+"]"; + } + /** + * + * @return + */ + public static String getOutput() { + return "Value"; + } + /** + * + * @param i + * @return + */ + public static String getOutput(int i) { + return "Value["+i+"]"; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/BlitPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/BlitPass.java new file mode 100644 index 0000000000..b1f4f7738d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/BlitPass.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.material.Material; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture2D; + +/** + * + * @author codex + */ +public class BlitPass extends RenderPass { + + private ResourceTicket source; + private ResourceTicket color, depth; + private final TextureDef colorDef = TextureDef.texture2D(); + private final TextureDef depthDef = TextureDef.texture2D(); + private Material nullMat; + + @Override + protected void initialize(FrameGraph frameGraph) { + source = addInput("Source"); + color = addOutput("Color"); + depth = addOutput("Depth"); + colorDef.setFormatFlexible(true); + depthDef.setFormat(Image.Format.Depth); + depthDef.setFormatFlexible(true); + nullMat = new Material(frameGraph.getAssetManager(), "Common/MatDefs/Misc/Null.j3md"); + } + @Override + protected void prepare(FGRenderContext context) { + declare(colorDef, color); + declare(depthDef, depth); + reserve(color, depth); + referenceOptional(source); + } + @Override + protected void execute(FGRenderContext context) { + FrameBuffer sourceBuf = resources.acquireOrElse(source, null); + FrameBuffer targetBuf; + if (sourceBuf != null) { + targetBuf = getFrameBuffer(sourceBuf.getWidth(), sourceBuf.getHeight(), sourceBuf.getSamples()); + colorDef.setSize(sourceBuf.getWidth(), sourceBuf.getHeight()); + depthDef.setSize(sourceBuf.getWidth(), sourceBuf.getHeight()); + } else { + targetBuf = getFrameBuffer(context, 1); + colorDef.setSize(context.getWidth(), context.getHeight()); + depthDef.setSize(context.getWidth(), context.getHeight()); + } + context.getRenderer().copyFrameBuffer(sourceBuf, targetBuf, true, true); + resources.acquireColorTarget(targetBuf, color); + resources.acquireDepthTarget(targetBuf, depth); + context.getRenderer().setFrameBuffer(targetBuf); + context.renderFullscreen(nullMat); + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/DeferredPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/DeferredPass.java new file mode 100644 index 0000000000..496538d3c7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/DeferredPass.java @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +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.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.LightProbe; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.Material; +import com.jme3.material.TechniqueDef; +import com.jme3.material.logic.SkyLightAndReflectionProbeRender; +import com.jme3.material.logic.TechniqueDefLogic; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.scene.Geometry; +import com.jme3.shader.DefineList; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.shader.VarType; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture2D; +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * Renders GBuffer information using the deferred lighting technique to a color texture. + *

    + * Inputs: + *

      + *
    • GBufferData[5] ({@link Texture2D}): Textures containing the necessary geometry information.
    • + *
    • Lights ({@link LightList}): List of lights in the scene (optional).
    • + *
    • LightTextures[3] ({@link Texture2D}): Textures containing light data (optional).
    • + *
    • TileTextures[2] ({@link Texture2D}): Textures sorting lights by screenspace tile (optional).
    • + *
    • NumLights (int): Number of lights stored in LightTextures (optional).
    • + *
    • Ambient ({@link ColorRGBA}): Accumulated color of ambient lights (optional).
    • + *
    • Probes (List<{@link LightProbe}>): List of light probes in the scene (optional)
    • + *
    + * Outputs: + *
      + *
    • Color ({@link Texture2D}): Result of deferred rendering.
    • + *
    + * There are three different ways to inject lighting information: + *
      + *
    1. Provide raw light list ("Lights"). Requires "LightTextures" and "Ambient" be undefined.
    2. + *
    3. Provide preprocessed light list ("Lights"), with "Ambient" and "Probes" defined, and "LightTextures" undefined.
    4. + *
    5. Provide lights packed into "LightTextures", with "NumLights", "Ambient", and "Probes" defined.
    6. + *
    + * With light textures, "TileTextures" can also be defined to use tiled lighting techniques, + * which generally makes the process more efficient, especially with a large number of lights. + * + * @author codex + */ +public class DeferredPass extends RenderPass implements TechniqueDefLogic { + + /** + * Indicates the maximum number of directional, point, and spot lights + * that can be handled using buffers. + *

    + * Excess lights will be discarded if using buffers (instead of textures). + *

    + * Development Note: if more uniforms are added to the + * shader, this value may need to be decreased. + */ + public static final int MAX_BUFFER_LIGHTS = 320; + + /** + * Indicates the maximum number of light probes that can be handled. + *

    + * Excess light probes will be discarded. + */ + public static final int MAX_PROBES = 3; + + private static final List localProbeList = new LinkedList<>(); + + private boolean tiled = false; + private AssetManager assetManager; + private ResourceTicket outColor; + private ResourceTicket lights; + private ResourceTicket numLights; + private ResourceTicket ambient; + private ResourceTicket> probes; + private TextureDef colorDef; + private Material material; + private final Texture2D[] lightTextures = new Texture2D[3]; + private final Texture2D[] tileTextures = new Texture2D[2]; + private final ColorRGBA ambientColor = new ColorRGBA(); + private List probeList; + + public DeferredPass() {} + public DeferredPass(boolean tiled) { + this.tiled = tiled; + } + + @Override + protected void initialize(FrameGraph frameGraph) { + addInputGroup("GBufferData", 5); + lights = addInput("Lights"); + addInputGroup("LightTextures", 3); + addInputGroup("TileTextures", 2); + numLights = addInput("NumLights"); + ambient = addInput("Ambient"); + probes = addInput("Probes"); + outColor = addOutput("Color"); + colorDef = new TextureDef<>(Texture2D.class, img -> new Texture2D(img)); + colorDef.setFormatFlexible(true); + assetManager = frameGraph.getAssetManager(); + material = new Material(assetManager, "Common/MatDefs/ShadingCommon/DeferredShading.j3md"); + for (TechniqueDef t : material.getMaterialDef().getTechniqueDefs("DeferredPass")) { + Defines.config(t); + } + } + @Override + protected void prepare(FGRenderContext context) { + colorDef.setSize(context.getWidth(), context.getHeight()); + declare(colorDef, outColor); + reserve(outColor); + // groups are stored by hashmap, which makes it absolutely fine to fetch every frame + reference(getGroupArray("GBufferData")); + referenceOptional(lights, numLights, ambient, probes); + referenceOptional(getGroupArray("LightTextures")); + referenceOptional(getGroupArray("TileTextures")); + } + @Override + protected void execute(FGRenderContext context) { + + // setup framebuffer + FrameBuffer fb = getFrameBuffer(context, 1); + resources.acquireColorTargets(fb, outColor); + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + context.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha); + + // apply gbuffer textures + ResourceTicket[] gbuffers = getGroupArray("GBufferData"); + for (int i = 0; i < gbuffers.length; i++) { + material.setTexture("GBuffer"+i, resources.acquire(gbuffers[i])); + } + + // setup technique + material.selectTechnique("DeferredPass", context.getRenderManager()); + TechniqueDef active = material.getActiveTechnique().getDef(); + active.setLogic(this); + Defines.config(active); + + // render + acquireArrayOrElse("LightTextures", lightTextures, null); + if (lightTextures[0] == null) { + context.getScreen().render(context.getRenderManager(), material, resources.acquire(lights)); + } else { + for (int i = 1; i <= lightTextures.length; i++) { + material.setTexture("LightTex"+i, lightTextures[i-1]); + } + // get textures used for screenspace light tiling + acquireArrayOrElse("TileTextures", tileTextures, null); + material.setTexture("Tiles", tileTextures[0]); + material.setTexture("LightIndex", tileTextures[1]); + context.renderFullscreen(material); + } + active.setLogic(null); + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, + EnumSet rendererCaps, LightList lights, DefineList defines) { + TechniqueDef active = material.getActiveTechnique().getDef(); + Defines defs = Defines.get(active); + if (lightTextures[0] == null) { + ColorRGBA amb = resources.acquireOrElse(ambient, null); + if (amb == null) { + probeList = localProbeList; + // extract ambient and probes from light list + SkyLightAndReflectionProbeRender.extractSkyLightAndReflectionProbes( + lights, ambientColor, probeList, true); + } else { + // lights are already processed: get ambient and probes from resources + ambientColor.set(amb); + probeList = resources.acquire(probes); + } + defines.set(defs.numLights, Math.min(lights.size(), MAX_BUFFER_LIGHTS)*3); + } else { + // get resources for lighting with textures + ambientColor.set(resources.acquire(ambient)); + probeList = resources.acquire(probes); + defines.set(defs.useTextures, true); + defines.set(defs.numLights, resources.acquire(numLights)); + if (tileTextures[0] != null) { + defines.set(defs.useTiles, true); + } + } + // this may need to be changed to only be enabled when there is an ambient light present + defines.set(defs.useAmbientLight, true); + defines.set(defs.numProbes, getNumReadyProbes(probeList)); + return active.getShader(assetManager, rendererCaps, defines); + } + @Override + public void render(RenderManager rm, Shader shader, Geometry geometry, + LightList lights, Material.BindUnits lastBindUnits) { + Renderer renderer = rm.getRenderer(); + injectShaderGlobals(rm, shader, lastBindUnits.textureUnit); + if (lightTextures[0] == null) { + injectLightBuffers(shader, lights); + } else { + injectLightTextures(shader); + } + renderer.setShader(shader); + TechniqueDefLogic.renderMeshFromGeometry(renderer, geometry); + probeList = null; + } + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + out.write(tiled, "tiled", false); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + tiled = in.readBoolean("tiled", false); + } + + private int getNumReadyProbes(List probes) { + int n = 0; + if (probes != null) for (LightProbe p : probes) { + if (p.isEnabled() && p.isReady() && ++n == MAX_PROBES) { + break; + } + } + return n; + } + + private void injectShaderGlobals(RenderManager rm, Shader shader, int lastTexUnit) { + shader.getUniform("g_AmbientLightColor").setValue(VarType.Vector4, ambientColor); + if (probeList != null && !probeList.isEmpty()) { + int i = 0; + // inject light probes + for (LightProbe p : probeList) { + if (!p.isEnabled() || !p.isReady()) { + continue; + } + String num = (++i > 1 ? String.valueOf(i) : ""); + Uniform sky = shader.getUniform("g_SkyLightData"+num); + Uniform coeffs = shader.getUniform("g_ShCoeffs"+num); + Uniform env = shader.getUniform("g_ReflectionEnvMap"+num); + lastTexUnit = SkyLightAndReflectionProbeRender.setSkyLightAndReflectionProbeData( + rm, lastTexUnit, sky, coeffs, env, p); + if (i == MAX_PROBES) break; + } + } else { + // disable light probes + shader.getUniform("g_SkyLightData").setValue(VarType.Matrix4, LightProbe.FALLBACK_MATRIX); + } + } + private void injectLightBuffers(Shader shader, LightList lights) { + // ambient lights and probes should already have been extracted at this point + Uniform data = shader.getUniform("g_LightData"); + int n = Math.min(lights.size(), MAX_BUFFER_LIGHTS)*3; + data.setVector4Length(n); + int i = 0, lightCount = 0; + for (Light l : lights) { + if (lightCount++ >= MAX_BUFFER_LIGHTS) { + break; + } + Light.Type type = l.getType(); + writeColorToUniform(data, l.getColor(), type.getId(), i++); + switch (type) { + case Directional: + DirectionalLight dl = (DirectionalLight)l; + writeVectorToUniform(data, dl.getDirection(), -1, i++); + data.setVector4InArray(0, 0, 0, 0, i++); + break; + case Point: + PointLight pl = (PointLight)l; + writeVectorToUniform(data, pl.getPosition(), pl.getInvRadius(), i++); + data.setVector4InArray(0, 0, 0, 0, i++); + break; + case Spot: + SpotLight sl = (SpotLight)l; + writeVectorToUniform(data, sl.getPosition(), sl.getInvSpotRange(), i++); + writeVectorToUniform(data, sl.getDirection(), sl.getPackedAngleCos(), i++); + break; + default: + throw new UnsupportedOperationException("Light "+type+" not supported."); + } + } + // just in case, fill in the remaining elements + while (i < n) { + data.setVector4InArray(0, 0, 0, 0, i++); + } + } + private void injectLightTextures(Shader shader) { + int w = lightTextures[0].getImage().getWidth(); + shader.getUniform("m_LightTexInv").setValue(VarType.Float, 1f/w); + if (tileTextures[0] != null) { + w = tileTextures[1].getImage().getWidth(); + int h = tileTextures[1].getImage().getHeight(); + shader.getUniform("m_LightIndexSize").setValue(VarType.Vector3, new Vector3f(w-0.5f, 1f/w, 1f/h)); + } + } + + private void writeVectorToUniform(Uniform uniform, Vector3f vec, float w, int i) { + uniform.setVector4InArray(vec.x, vec.y, vec.z, w, i); + } + private void writeColorToUniform(Uniform uniform, ColorRGBA color, float a, int i) { + uniform.setVector4InArray(color.r, color.g, color.b, a, i); + } + + /** + * Registers and tracks necessary define IDs for all deferred passes. + */ + private static class Defines { + + private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS"; + private static final String DEFINE_NB_PROBES = "NB_PROBES"; + private static final String DEFINE_USE_LIGHT_TEXTURES = "USE_LIGHT_TEXTURES"; + private static final String DEFINE_USE_AMBIENT_LIGHT = "USE_AMBIENT_LIGHT"; + private static final String DEFINE_TILED_LIGHTS = "TILED_LIGHTS"; + + private static final HashMap defMap = new HashMap<>(); + public final int numLights, useTextures, numProbes, useAmbientLight, useTiles; + + public Defines(TechniqueDef def) { + numLights = def.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int); + numProbes = def.addShaderUnmappedDefine(DEFINE_NB_PROBES, VarType.Int); + useTextures = def.addShaderUnmappedDefine(DEFINE_USE_LIGHT_TEXTURES, VarType.Boolean); + useAmbientLight = def.addShaderUnmappedDefine(DEFINE_USE_AMBIENT_LIGHT, VarType.Boolean); + useTiles = def.addShaderUnmappedDefine(DEFINE_TILED_LIGHTS, VarType.Boolean); + } + + public static Defines config(TechniqueDef technique) { + Defines defs = defMap.get(technique); + if (defs == null) { + defs = new Defines(technique); + defMap.put(technique, defs); + } + return defs; + } + + public static Defines get(TechniqueDef technique) { + Defines defs = defMap.get(technique); + if (defs == null) { + throw new NullPointerException("Attempted to use unconfigured TechniqueDef."); + } + return defs; + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GBufferPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GBufferPass.java new file mode 100644 index 0000000000..da23efb9d7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GBufferPass.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.scene.Geometry; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture2D; +import java.util.function.Function; +import com.jme3.renderer.GeometryRenderHandler; +import com.jme3.renderer.framegraph.GeometryQueue; + +/** + * Renders information about a queue of geometries to a set of textures. + *

    + * Inputs: + *

      + *
    • Geometry ({@link GeometryQueue}: queue of geometries to extract information from.
    • + *
    + * Outputs: + *
      + *
    • GBufferData[5] ({@link Texture2D}): textures containing geometry information.
    • + *
    • NumRenders (int): number of geometries rendered, since not all geometries are guaranteed to be rendered.
    • + *
    + * Geometries that do not have a material with a "GBuffer" technique are not rendered. + * + * @author codex + */ +public class GBufferPass extends RenderPass implements GeometryRenderHandler { + + private final static String GBUFFER_PASS = "GBufferPass"; + + private ResourceTicket geometry; + private ResourceTicket[] gbuffers; + private ResourceTicket numRendersTicket; + private final TextureDef[] texDefs = new TextureDef[5]; + private int numRenders = 0; + + @Override + protected void initialize(FrameGraph frameGraph) { + geometry = addInput("Geometry"); + gbuffers = addOutputGroup("GBufferData", 5); + numRendersTicket = addOutput("NumRenders"); + Function tex = img -> new Texture2D(img); + texDefs[0] = new TextureDef<>(Texture2D.class, tex, Image.Format.RGBA16F); + texDefs[1] = new TextureDef<>(Texture2D.class, tex, Image.Format.RGBA16F); + texDefs[2] = new TextureDef<>(Texture2D.class, tex, Image.Format.RGBA16F); + texDefs[3] = new TextureDef<>(Texture2D.class, tex, Image.Format.RGBA32F); + texDefs[4] = new TextureDef<>(Texture2D.class, tex, Image.Format.Depth); + } + @Override + protected void prepare(FGRenderContext context) { + int w = context.getWidth(), h = context.getHeight(); + for (int i = 0; i < gbuffers.length; i++) { + texDefs[i].setSize(w, h); + declare(texDefs[i], gbuffers[i]); + } + declare(null, numRendersTicket); + reserve(gbuffers); + reference(geometry); + numRenders = 0; + } + @Override + protected void execute(FGRenderContext context) { + // acquire texture targets + FrameBuffer fb = getFrameBuffer(context, 1); + fb.setMultiTarget(true); + //resources.setPrimitive(diffuse, diffuseTex); + resources.acquireColorTargets(fb, gbuffers[0], gbuffers[1], gbuffers[2], gbuffers[3]); + resources.acquireDepthTarget(fb, gbuffers[4]); + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + context.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha); + context.getRenderManager().setForcedTechnique(GBUFFER_PASS); + context.getRenderManager().setGeometryRenderHandler(this); + GeometryQueue bucket = resources.acquire(geometry); + context.renderGeometry(bucket, null, this); + resources.setPrimitive(numRendersTicket, numRenders); + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public boolean renderGeometry(RenderManager rm, Geometry geom) { + Material material = geom.getMaterial(); + if(material.getMaterialDef().getTechniqueDefs(rm.getForcedTechnique()) == null) { + return false; + } + rm.renderGeometry(geom); + numRenders++; + return false; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GeometryPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GeometryPass.java new file mode 100644 index 0000000000..ffee861d66 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GeometryPass.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.DepthRange; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.GeometryQueue; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture2D; +import java.io.IOException; + +/** + * Renders a queue bucket to a set of color and depth textures. + *

    + * Inputs: + *

      + *
    • Geometry ({@link GeometryQueue}): queue of geometries to render.
    • + *
    • Color ({@link Texture2D}): Color texture to combine (by depth comparison) with the result of this render (optional).
    • + *
    • Depth ({@link Texture2D}): Depth texture to combine (by depth comparison) with the result of this render (optional).
    • + *
    + * Outputs: + *
      + *
    • Color ({@link Texture2D}): Resulting color texture.
    • + *
    • Depth ({@link Texture2D}): Resulting depth texture.
    • + *
    + * + * @author codex + */ +public class GeometryPass extends RenderPass { + + private final DepthRange depth = new DepthRange(); + private ResourceTicket inColor, inDepth, outColor, outDepth; + private ResourceTicket geometry; + private TextureDef colorDef, depthDef; + private boolean perspective; + + public GeometryPass() { + this(DepthRange.IDENTITY, true); + } + public GeometryPass(DepthRange depth) { + this(depth, true); + } + public GeometryPass(DepthRange depth, boolean perspective) { + this.depth.set(depth); + this.perspective = perspective; + } + + @Override + protected void initialize(FrameGraph frameGraph) { + inColor = addInput("Color"); + inDepth = addInput("Depth"); + geometry = addInput("Geometry"); + outColor = addOutput("Color"); + outDepth = addOutput("Depth"); + colorDef = new TextureDef<>(Texture2D.class, img -> new Texture2D(img)); + depthDef = new TextureDef<>(Texture2D.class, img -> new Texture2D(img), Image.Format.Depth); + colorDef.setFormatFlexible(true); + depthDef.setFormatFlexible(true); + } + @Override + protected void prepare(FGRenderContext context) { + int w = context.getWidth(); + int h = context.getHeight(); + colorDef.setSize(w, h); + depthDef.setSize(w, h); + declare(colorDef, outColor); + declare(depthDef, outDepth); + reserve(outColor, outDepth); + reference(geometry); + referenceOptional(inColor, inDepth); + } + @Override + protected void execute(FGRenderContext context) { + FrameBuffer fb = getFrameBuffer(context, 1); + resources.acquireColorTarget(fb, outColor); + resources.acquireDepthTarget(fb, outDepth); + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + context.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha); + context.renderTextures(resources.acquireOrElse(inColor, null), resources.acquireOrElse(inDepth, null)); + //context.getRenderer().setDepthRange(depth); + //if (!perspective) { + // context.getRenderManager().setCamera(context.getViewPort().getCamera(), true); + //} + context.renderGeometry(resources.acquire(geometry), null, null); + //if (!perspective) { + // context.getRenderManager().setCamera(context.getViewPort().getCamera(), false); + //} + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + out.write(depth, "depth", DepthRange.IDENTITY); + out.write(perspective, "perspective", true); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + depth.set(in.readSavable("depth", DepthRange.class, DepthRange.IDENTITY)); + perspective = in.readBoolean("perspective", true); + } + + /** + * Sets the depth range objects are rendered within. + * + * @param depth depth range (not null, unaffected) + */ + public void setDepthRange(DepthRange depth) { + this.depth.set(depth); + } + + /** + * Gets the depth range objects are rendered within. + * + * @return + */ + public DepthRange getDepthRange() { + return depth; + } + + /** + * + * @param perspective + */ + public void setPerspective(boolean perspective) { + this.perspective = perspective; + } + + /** + * + * @return + */ + public boolean isPerspective() { + return perspective; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GroupAttribute.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GroupAttribute.java new file mode 100644 index 0000000000..565e540e80 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GroupAttribute.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.client.GraphSource; +import com.jme3.renderer.framegraph.client.GraphTarget; +import java.io.IOException; +import java.util.ArrayList; + +/** + * Accepts and produces and group of inputs and outputs to/from game logic. + *

    + * The size of each group is determine by the specified group size. Each given + * {@link GraphSource} and {@link GraphTarget} map index-to-index to an individual + * output or input. Surplus sources and targets are not used. + *

    + * Inputs: + *

      + *
    • {@link #INPUT}[n] ({@link Object}): input group of a specified size (optional). + *
    + * Outputs: + *
      + *
    • {@link #OUTPUT}[n] ({@link Object)}: output group of a specified size. + *
    + * + * @author codex + */ +public class GroupAttribute extends RenderPass { + + public static final String INPUT = "Input", OUTPUT = "Output"; + + private int groupSize = 2; + private final ArrayList sources = new ArrayList<>(5); + private final ArrayList targets = new ArrayList<>(5); + + public GroupAttribute() {} + public GroupAttribute(int groupSize) { + this.groupSize = groupSize; + } + + @Override + protected void initialize(FrameGraph frameGraph) { + addInputGroup(INPUT, groupSize); + addOutputGroup(OUTPUT, groupSize); + } + @Override + protected void prepare(FGRenderContext context) { + for (ResourceTicket t : getGroupArray(OUTPUT)) { + declare(null, t); + } + referenceOptional(getGroupArray(INPUT)); + } + @Override + protected void execute(FGRenderContext context) { + ViewPort vp = context.getViewPort(); + ResourceTicket[] inTickets = getGroupArray(INPUT); + for (int i = 0, n = Math.min(groupSize, targets.size()); i < n; i++) { + Object value = resources.acquireOrElse(inTickets[i], null); + GraphTarget t = targets.get(i); + if (t != null && t.setGraphValue(frameGraph, vp, value)) { + resources.setConstant(inTickets[i]); + } + } + int i = 0; + ResourceTicket[] outTickets = getGroupArray(OUTPUT); + for (int n = Math.min(groupSize, sources.size()); i < n; i++) { + GraphSource s = sources.get(i); + if (s != null) { + Object value = s.getGraphValue(frameGraph, vp); + if (value != null) { + resources.setPrimitive(outTickets[i], value); + continue; + } + } + resources.setUndefined(outTickets[i]); + } + for (; i < groupSize; i++) { + resources.setUndefined(outTickets[i]); + } + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + out.write(groupSize, "groupSize", 2); + out.writeFromCollection(sources, "sources", true); + out.writeFromCollection(targets, "targets", true); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + groupSize = in.readInt("groupSize", 2); + in.readToCollection("sources", sources); + in.readToCollection("targets", targets); + } + + /** + * Sets the size of the input and output groups. + * + * @param groupSize + * @throws IllegalStateException if called while pass is assigned to a framegraph + */ + public void setGroupSize(int groupSize) { + if (isAssigned()) { + throw new IllegalStateException("Cannot alter group size while assigned to a framegraph."); + } + this.groupSize = groupSize; + } + /** + * Sets the source that provides values for the output at the index + * within the output group. + * + * @param i + * @param source + */ + public void setSource(int i, GraphSource source) { + while (sources.size() <= i) { + sources.add(null); + } + sources.set(i, source); + } + /** + * Sets the target that recieves values from the input at the index + * within the input group. + * + * @param i + * @param target + */ + public void setTarget(int i, GraphTarget target) { + while (targets.size() <= i) { + targets.add(null); + } + targets.set(i, target); + } + + /** + * + * @return + */ + public int getGroupSize() { + return groupSize; + } + /** + * Gets the source at the index. + * + * @param i + * @return source at the index, or null if no source is assigned at the index + */ + public GraphSource getSource(int i) { + if (i < sources.size()) { + return sources.get(i); + } else { + return null; + } + } + /** + * Gets the target at the index. + * + * @param i + * @return target at the index, or null if no target is assigned at the index. + */ + public GraphTarget getTarget(int i) { + if (i < targets.size()) { + return targets.get(i); + } else { + return null; + } + } + + /** + * Gets the name of the input at the index. + * + * @param i + * @return + */ + public static String getInput(int i) { + return INPUT+'['+i+']'; + } + /** + * Gets the name of the output at the index. + * + * @param i + * @return + */ + public static String getOutput(int i) { + return OUTPUT+'['+i+']'; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/Junction.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/Junction.java new file mode 100644 index 0000000000..30074567c0 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/Junction.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.NullSavable; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.renderer.framegraph.client.GraphSource; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import java.io.IOException; + +/** + * Merges several inputs into one output by choosing one input to connect to + * the output using a controllable index. + *

    + * Junction can either function as a set of individual inputs (group size is 1), or as a set of + * group inputs (group size is greater than 1). + *

    + * Static methods should be used to correctly reference inputs and outputs. + * + * @author codex + * @param + */ +public class Junction extends RenderPass { + + private static final int EXTRA_INPUTS = 0; + + private int length; + private int groupSize; + private ResourceTicket output; + private GraphSource source; + private int defaultIndex = 0; + + public Junction() { + this(2); + } + public Junction(int length) { + setLength(length); + } + public Junction(int length, int groupSize) { + setLength(length); + setGroupSize(groupSize); + } + + @Override + protected void initialize(FrameGraph frameGraph) { + for (int i = 0; i < length; i++) { + if (groupSize > 1) { + addInputGroup(getInput(i), groupSize); + } else { + addInput(getInput(i)); + } + } + if (groupSize > 1) { + addOutputGroup(getOutput(), groupSize); + } else { + output = addOutput(getOutput()); + } + } + @Override + protected void prepare(FGRenderContext context) { + int size; + if (groupSize > 1) { + size = groups.size()-1; + } else { + size = inputs.size()-EXTRA_INPUTS; + } + // remove excess tickets + while (size > length) { + size--; + if (groupSize > 1) { + ResourceTicket[] array = removeGroup(getInput(size)); + for (ResourceTicket t : array) { + t.setSource(null); + } + } else { + inputs.removeLast().setSource(null); + } + } + // add deficit tickets + while (size < length) { + if (groupSize > 1) { + addInputGroup(getInput(size), groupSize); + } else { + addInput(getInput(size)); + } + size++; + } + // connect output to input + if (source != null) { + connect(source.getGraphValue(frameGraph, context.getViewPort())); + } else { + connect(defaultIndex); + } + } + @Override + protected void execute(FGRenderContext context) {} + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public boolean isUsed() { + // This pass will never execute + return false; + } + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + out.write(length, "length", 2); + out.write(groupSize, "groupSize", 1); + out.write(defaultIndex, "defaultIndex", 0); + if (source != null && source instanceof Savable) { + out.write((Savable)source, "source", NullSavable.INSTANCE); + } + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + length = in.readInt("length", 2); + groupSize = in.readInt("groupSize", 1); + defaultIndex = in.readInt("defaultIndex", 0); + source = (GraphSource)in.readSavable("source", null); + } + + private void connect(int i) { + boolean assignNull = i < 0 || i >= length; + if (groupSize > 1) { + ResourceTicket[] outArray = getGroupArray(getOutput()); + if (!assignNull) { + ResourceTicket[] inArray = getGroupArray(getInput(i)); + for (int j = 0; j < groupSize; j++) { + outArray[j].setSource(inArray[j]); + } + } else for (ResourceTicket t : outArray) { + t.setSource(null); + } + } else { + output.setSource(assignNull ? null : inputs.get(i)); + } + } + + public final void setLength(int length) { + if (length <= 0) { + throw new IllegalArgumentException("Length must be greater than zero."); + } + this.length = length; + } + public final void setGroupSize(int groupSize) { + if (isAssigned()) { + throw new IllegalStateException("Cannot alter group size while assigned to a framegraph."); + } + if (groupSize <= 0) { + throw new IllegalArgumentException("Group length must be greater than zero."); + } + this.groupSize = groupSize; + } + public void setIndexSource(GraphSource source) { + this.source = source; + } + public void setDefaultIndex(int defaultIndex) { + this.defaultIndex = defaultIndex; + } + + public int getLength() { + return length; + } + public int getGroupSize() { + return groupSize; + } + public GraphSource getIndexSource() { + return source; + } + public int getDefaultIndex() { + return defaultIndex; + } + + /** + * Returns a string referencing an individual input (groupSize = 1) or + * an input group (groupSize > 1). + * + * @param i index of input or input group + * @return + */ + public static String getInput(int i) { + return "Input["+i+"]"; + } + /** + * Returns a string referencing an individual input that is part of + * a group (groupSize > 1 only). + * + * @param i index of group + * @param j index of input in group + * @return + */ + public static String getInput(int i, int j) { + return "Input["+i+"]["+j+"]"; + } + /** + * Returns a string referencing the individual output (groupSize = 1) + * or the output group (groupSize > 1). + * + * @return + */ + public static String getOutput() { + return "Value"; + } + /** + * Returns a string referencing an individual output in the output group + * (groupSize > 1 only). + * + * @param i index of output in group + * @return + */ + public static String getOutput(int i) { + return "Value["+i+"]"; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/LightImagePass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/LightImagePass.java new file mode 100644 index 0000000000..a47bccd7cc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/LightImagePass.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.LightList; +import com.jme3.light.LightProbe; +import com.jme3.renderer.framegraph.light.TiledRenderGrid; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.Camera; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.renderer.framegraph.light.LightImagePacker; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; + +/** + * Packs light data into a set of textures. + *

    + * Inputs: + *

      + *
    • Lights ({@link LightList}): list of lights to pack.
    • + *
    • TileInfo ({@link TileInfo}): settings for packing tile information (optional).
    • + *
    + * Outputs: + *
      + *
    • Textures[3] ({@link Texture2D}): textures containing packed light information.
    • + *
    • TileTextures[2] ({@link Texture2D}): textures containing tile and index information.
    • + *
    • NumLights (int): number of lights that are not AmbientLights or LightProbes.
    • + *
    • Ambient ({@link ColorRGBA}): accumulated color of all AmbientLights.
    • + *
    • Probes (LinkedList<{@link LightProbe}>): list of all LightProbes.
    • + *
    + * If "TileInfo" is defined, tile textures will additionally be generated, which can be used + * in algorithms such as tiled deferred. Otherwise, "TileTextures" will be undefined. + * + * @author codex + */ +public class LightImagePass extends RenderPass { + + private final LightImagePacker packer = new LightImagePacker(); + private ResourceTicket lights; + private ResourceTicket tileInfo; + private ResourceTicket[] textures; + private ResourceTicket[] tileTextures; + private ResourceTicket numLights; + private ResourceTicket ambientColor; + private ResourceTicket> probes; + private final LightTextureDef lightTexDef = new LightTextureDef(512); + private final TileTextureDef tileDef = new TileTextureDef(); + private final TileTextureDef indexDef = new TileTextureDef(); + private final ColorRGBA ambient = new ColorRGBA(0, 0, 0, 0); + private final LinkedList probeList = new LinkedList<>(); + + @Override + protected void initialize(FrameGraph frameGraph) { + lights = addInput("Lights"); + tileInfo = addInput("TileInfo"); + textures = addOutputGroup("Textures", 3); + tileTextures = addOutputGroup("TileTextures", 2); + numLights = addOutput("NumLights"); + ambientColor = addOutput("Ambient"); + probes = addOutput("Probes"); + lightTexDef.setFormat(Image.Format.RGBA32F); + lightTexDef.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + lightTexDef.setMagFilter(Texture.MagFilter.Nearest); + lightTexDef.setWrap(Texture.WrapMode.EdgeClamp); + tileDef.setFormat(Image.Format.RGBA32F); + tileDef.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + tileDef.setMagFilter(Texture.MagFilter.Nearest); + tileDef.setWrap(Texture.WrapMode.EdgeClamp); + indexDef.setFormat(Image.Format.RGBA32F); + indexDef.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + indexDef.setMagFilter(Texture.MagFilter.Nearest); + indexDef.setWrap(Texture.WrapMode.EdgeClamp); + } + @Override + protected void prepare(FGRenderContext context) { + for (ResourceTicket t : textures) { + declare(lightTexDef, t); + reserve(t); + } + declare(tileDef, tileTextures[0]); + declare(indexDef, tileTextures[1]); + declare(null, numLights); + declare(null, ambientColor); + declare(null, probes); + reference(lights); + referenceOptional(tileInfo); + } + @Override + protected void execute(FGRenderContext context) { + LightList lightList = resources.acquire(lights); + TiledRenderGrid grid = resources.acquireOrElse(tileInfo, null); + Camera cam = context.getViewPort().getCamera(); + Texture2D tiles = null; + Texture2D indices = null; + if (grid != null) { + grid.update(cam); + tileDef.setSize(grid.getGridWidth(), grid.getGridHeight()); + // four indices are stored per pixel + int reqPixels = lightTexDef.getWidth()*grid.getNumTiles()/4; + if (indexDef.getNumPixels() < reqPixels) { + indexDef.setNumPixels(reqPixels, true, true, false); + } + tiles = resources.acquire(tileTextures[0]); + indices = resources.acquire(tileTextures[1]); + } else { + resources.setUndefined(tileTextures[0]); + resources.setUndefined(tileTextures[1]); + } + packer.setTextures(resources.acquire(textures[0]), + resources.acquire(textures[1]), + resources.acquire(textures[2]), + tiles, indices); + int n = packer.packLights(lightList, ambient, probeList, cam, grid); + resources.setPrimitive(numLights, n); + resources.setPrimitive(ambientColor, ambient); + resources.setPrimitive(probes, probeList); + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + out.write(lightTexDef.getWidth(), "maxLights", 512); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + lightTexDef.setWidth(in.readInt("maxLights", 512)); + } + + public int getMaxLights() { + return lightTexDef.getWidth(); + } + + public void setMaxLights(int maxLights) { + lightTexDef.setWidth(maxLights); + } + + private static class LightTextureDef extends TextureDef { + + public LightTextureDef(int maxLights) { + super(Texture2D.class, img -> new Texture2D(img)); + setWidth(maxLights); + super.setHeight(1); + super.setDepth(0); + } + + @Override + public Texture2D createResource() { + Image.Format format = getFormat(); + int width = getWidth(); + ByteBuffer data = BufferUtils.createByteBuffer((int)(format.getBitsPerPixel()/8)*width); + Image img = new Image(format, width, 1, data, null, getColorSpace()); + return createTexture(img); + } + @Override + public void setHeight(int height) {} + @Override + public void setDepth(int depth) {} + + } + private static class TileTextureDef extends TextureDef { + + public TileTextureDef() { + super(Texture2D.class, img -> new Texture2D(img)); + } + + @Override + public Texture2D createResource() { + Image.Format format = getFormat(); + int width = getWidth(); + int height = getHeight(); + ByteBuffer data = BufferUtils.createByteBuffer((int)(format.getBitsPerPixel()/8)*width*height); + Image img = new Image(format, width, height, data, null, getColorSpace()); + return createTexture(img); + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/LightListExtractPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/LightListExtractPass.java new file mode 100644 index 0000000000..90f74dbb94 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/LightListExtractPass.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.LightProbe; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import java.util.LinkedList; +import java.util.List; + +/** + * Extracts ambient light and light probes from a LightList. + * + * @author codex + */ +public class LightListExtractPass extends RenderPass { + + private ResourceTicket inLights, outLights; + private ResourceTicket ambient; + private ResourceTicket> probes; + private final LightList outLightList = new LightList(null); + private final ColorRGBA ambColor = new ColorRGBA(0, 0, 0, 1); + private final List probeList = new LinkedList<>(); + + @Override + protected void initialize(FrameGraph frameGraph) { + inLights = addInput("Lights"); + outLights = addOutput("Lights"); + ambient = addOutput("Ambient"); + probes = addOutput("Probes"); + } + @Override + protected void prepare(FGRenderContext context) { + referenceOptional(inLights); + declare(null, outLights); + declare(null, ambient); + declare(null, probes); + } + @Override + protected void execute(FGRenderContext context) { + LightList list = resources.acquireOrElse(inLights, null); + if (list != null) { + ambColor.set(0, 0, 0, 1); + for (Light l : list) switch (l.getType()) { + case Ambient: + ambColor.addLocal(l.getColor()); + break; + case Probe: + probeList.add((LightProbe)l); + break; + default: + outLightList.add(l); + } + resources.setPrimitive(outLights, outLightList); + resources.setPrimitive(ambient, ambColor); + resources.setPrimitive(probes, probeList); + } else { + resources.setUndefined(outLights); + resources.setUndefined(ambient); + resources.setUndefined(probes); + } + } + @Override + protected void reset(FGRenderContext context) { + outLightList.clear(); + probeList.clear(); + } + @Override + protected void cleanup(FrameGraph frameGraph) {} + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/OutputGeometryPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/OutputGeometryPass.java new file mode 100644 index 0000000000..55d52cd368 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/OutputGeometryPass.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.DepthRange; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.GeometryQueue; +import com.jme3.renderer.framegraph.ResourceTicket; +import java.io.IOException; + +/** + * Renders a queue bucket to the viewport's output framebuffer. + * + * @author codex + */ +public class OutputGeometryPass extends RenderPass { + + private ResourceTicket geometry; + + @Override + protected void initialize(FrameGraph frameGraph) { + geometry = addInput("Geometry"); + } + @Override + protected void prepare(FGRenderContext context) { + reference(geometry); + } + @Override + protected void execute(FGRenderContext context) { + context.popFrameBuffer(); + context.renderGeometry(resources.acquire(geometry), null, null); + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public boolean isUsed() { + return geometry.hasSource(); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/OutputPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/OutputPass.java new file mode 100644 index 0000000000..b7c5257e9c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/OutputPass.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.texture.Texture2D; +import java.io.IOException; + +/** + * Renders a set of color and depth textures on a fullscreen quad to the + * viewport's output framebuffer. + * + * @author codex + */ +public class OutputPass extends RenderPass { + + private ResourceTicket color, depth; + private Float alphaDiscard; + + public OutputPass() { + this(null); + } + public OutputPass(Float alphaDiscard) { + this.alphaDiscard = alphaDiscard; + } + + @Override + protected void initialize(FrameGraph frameGraph) { + color = addInput("Color"); + depth = addInput("Depth"); + } + @Override + protected void prepare(FGRenderContext context) { + referenceOptional(color, depth); + } + @Override + protected void execute(FGRenderContext context) { + context.popFrameBuffer(); + Texture2D colorTex = resources.acquireOrElse(color, null); + Texture2D depthTex = resources.acquireOrElse(depth, null); + if (alphaDiscard != null) { + context.getScreen().setAlphaDiscard(alphaDiscard); + } + //System.out.println("camera: "+context.getWidth()+" "+context.getHeight()); + //System.out.println("texture: "+colorTex.getImage().getWidth()+" "+colorTex.getImage().getHeight()); + //context.resizeCamera(context.getWidth(), context.getHeight(), true, false, true); + context.renderTextures(colorTex, depthTex); + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public boolean isUsed() { + return color.hasSource() || depth.hasSource(); + } + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(alphaDiscard, "AlphaDiscard", -1); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + alphaDiscard = in.readFloat("AlphaDiscard", -1); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/QueueMergePass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/QueueMergePass.java new file mode 100644 index 0000000000..8bcc4150b1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/QueueMergePass.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.GeometryQueue; +import com.jme3.renderer.framegraph.ResourceTicket; +import java.io.IOException; + +/** + * Merges a specified number of {@link GeometryQueue}s into one output queue. + *

    + * Inputs: + *

      + *
    • Queues[n] ({@link GeometryQueue}: queues to merge into one.
    • + *
    + * Outputs: + *
      + *
    • Result ({@link GeometryQueue}): resulting geometry queue.
    • + *
    + * + * @author codex + */ +public class QueueMergePass extends RenderPass { + + private int groupSize = 2; + private ResourceTicket result; + private final GeometryQueue target = new GeometryQueue(); + + public QueueMergePass() {} + public QueueMergePass(int groupSize) { + this.groupSize = groupSize; + } + + @Override + protected void initialize(FrameGraph frameGraph) { + addInputGroup("Queues", groupSize); + result = addOutput("Result"); + } + @Override + protected void prepare(FGRenderContext context) { + declare(null, result); + referenceOptional(getGroupArray("Queues")); + } + @Override + protected void execute(FGRenderContext context) { + GeometryQueue[] queues = acquireArray("Queues", n -> new GeometryQueue[n]); + for (GeometryQueue q : queues) { + target.add(q); + } + resources.setPrimitive(result, target); + } + @Override + protected void reset(FGRenderContext context) { + target.clear(); + } + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + out.write(groupSize, "groupSize", 2); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + groupSize = in.readInt("groupSize", 2); + } + + public void setGroupSize(int groupSize) { + if (isAssigned()) { + throw new IllegalStateException("Cannot alter group size while assigned to a framegraph."); + } + this.groupSize = groupSize; + } + + public int getGroupSize() { + return groupSize; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/RenderPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/RenderPass.java new file mode 100644 index 0000000000..9d54f8e066 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/RenderPass.java @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +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.renderer.framegraph.modules.RenderModule; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceList; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.TicketGroup; +import com.jme3.renderer.framegraph.debug.GraphEventCapture; +import com.jme3.renderer.framegraph.definitions.ResourceDef; +import com.jme3.texture.FrameBuffer; +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Modular rendering process for a {@link FrameGraph}. + * + * @author codex + */ +public abstract class RenderPass extends RenderModule implements Savable { + + private final LinkedList frameBuffers = new LinkedList<>(); + protected ResourceList resources; + protected boolean autoTicketRelease = true; + + @Override + public String toString() { + return getClass().getSimpleName(); + } + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + write(ex.getCapsule(this)); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + read(im.getCapsule(this)); + } + @Override + public void traverse(Consumer traverser) { + traverser.accept(this); + } + + @Override + public void initModule(FrameGraph frameGraph) { + initialize(frameGraph); + } + @Override + public void prepareRender(FGRenderContext context) { + resources = context.getResources(); + prepare(context); + } + @Override + public void executeRender(FGRenderContext context) { + if (context.isAsync()) { + waitToExecute(); + } + execute(context); + if (autoTicketRelease) { + releaseAll(); + } + if (index.isMainThread()) { + context.popRenderSettings(); + } + } + @Override + public void resetRender(FGRenderContext context) { + reset(context); + } + @Override + public void cleanupModule(FrameGraph frameGraph) { + cleanup(frameGraph); + inputs.clear(); + outputs.clear(); + groups.clear(); + this.frameGraph = null; + } + + /** + * Initializes the pass. + *

    + * Tickets should be created add registered here. + * + * @param frameGraph + */ + protected abstract void initialize(FrameGraph frameGraph); + /** + * Prepares the pass. + *

    + * Resource should be declared, referenced, and reserved here. + * + * @param context + */ + protected abstract void prepare(FGRenderContext context); + /** + * Executes the pass. + *

    + * All declared and referenced resources should be acquired here. Resources + * must also be released, but that occurs automatically. + * + * @param context + */ + protected abstract void execute(FGRenderContext context); + /** + * Resets the pass. + * + * @param context + */ + protected abstract void reset(FGRenderContext context); + /** + * Cleans up the pass. + * + * @param frameGraph + */ + protected abstract void cleanup(FrameGraph frameGraph); + + /** + * Called when all rendering is complete in a rendering frame this pass + * participated in. + */ + @Override + public void renderingComplete() { + for (Iterator it = frameBuffers.iterator(); it.hasNext();) { + PassFrameBuffer fb = it.next(); + if (!fb.used) { + fb.dispose(); + it.remove(); + } else { + fb.used = false; + } + } + } + + /** + * Declares a new resource using a registered ticket. + * + * @param + * @param def definition for new resource + * @param ticket ticket to store resulting index + * @return given ticket + * @see #declareTemporary(com.jme3.renderer.framegraph.definitions.ResourceDef, com.jme3.renderer.framegraph.ResourceTicket) + */ + protected ResourceTicket declare(ResourceDef def, ResourceTicket ticket) { + return resources.declare(this, def, ticket); + } + /** + * Declares a resource using an unregistered ticket. + * + * @param + * @param def + * @param ticket + * @return + * @see #declare(com.jme3.renderer.framegraph.definitions.ResourceDef, com.jme3.renderer.framegraph.ResourceTicket) + */ + protected ResourceTicket declareTemporary(ResourceDef def, ResourceTicket ticket) { + return resources.declareTemporary(this, def, ticket); + } + /** + * Reserves the {@link com.jme3.renderer.framegraph.RenderObject RenderObject} associated with the ticket. + * + * @param ticket + */ + protected void reserve(ResourceTicket ticket) { + resources.reserve(index, ticket); + } + /** + * Reserves each RenderObject associated with the tickets. + * + * @param tickets + */ + protected void reserve(ResourceTicket... tickets) { + resources.reserve(index, tickets); + } + /** + * References the resource associated with the ticket. + * + * @param ticket + */ + protected void reference(ResourceTicket ticket) { + resources.reference(index, ticket); + } + /** + * References each resource associated with the tickets. + * + * @param tickets + */ + protected void reference(ResourceTicket... tickets) { + resources.reference(index, tickets); + } + /** + * References the resource associated with the ticket if the ticket is not + * null and contains a non-negative world index. + * + * @param ticket + */ + protected void referenceOptional(ResourceTicket ticket) { + resources.referenceOptional(index, ticket); + } + /** + * Optionally references each resource associated with the tickets. + * + * @param tickets + */ + protected void referenceOptional(ResourceTicket... tickets) { + for (ResourceTicket t : tickets) { + referenceOptional(t); + } + } + + /** + * Forces this thread to wait until all inputs are available for this pass. + *

    + * An incoming resource is deemed ready when {@link com.jme3.renderer.framegraph.ResourceView#claimReadPermissions() read + * permissions are claimed}. + */ + public void waitToExecute() { + for (ResourceTicket t : inputs) { + resources.waitForResource(t, index.threadIndex); + } + } + + /** + * Acquires a set of resources from a ticket group and stores them in the array. + * + * @param + * @param name + * @param array + * @return + */ + protected T[] acquireArray(String name, T[] array) { + ResourceTicket[] tickets = getGroup(name).getArray(); + int n = Math.min(array.length, tickets.length); + for (int i = 0; i < n; i++) { + array[i] = resources.acquire(tickets[i]); + } + return array; + } + /** + * Acquires a set of resources from a ticket group and stores them in an + * array created by the function. + * + * @param + * @param name + * @param func + * @return + */ + protected T[] acquireArray(String name, Function func) { + ResourceTicket[] tickets = getGroup(name).getArray(); + T[] array = func.apply(tickets.length); + int n = Math.min(array.length, tickets.length); + for (int i = 0; i < n; i++) { + array[i] = resources.acquire(tickets[i]); + } + return array; + } + /** + * Acquires a set of resources from a ticket group and stores them in + * the array. + *

    + * Tickets that are invalid will acquire {@code val}. + * + * @param + * @param name + * @param array + * @param val + * @return + */ + protected T[] acquireArrayOrElse(String name, T[] array, T val) { + ResourceTicket[] tickets = getGroup(name).getArray(); + int n = Math.min(array.length, tickets.length); + for (int i = 0; i < n; i++) { + array[i] = resources.acquireOrElse(tickets[i], val); + } + return array; + } + /** + * Acquires a set of resources from a ticket group and stores them in an + * array created by the function. + *

    + * Tickets that are invalid will acquire {@code val}. + * + * @param + * @param name + * @param func + * @param val + * @return + */ + protected T[] acquireArrayOrElse(String name, Function func, T val) { + ResourceTicket[] tickets = getGroup(name).getArray(); + T[] array = func.apply(tickets.length); + int n = Math.min(array.length, tickets.length); + for (int i = 0; i < n; i++) { + array[i] = resources.acquireOrElse(tickets[i], val); + } + return array; + } + /** + * Acquires a list of resources from a ticket group and stores them in the given list. + * + * @param + * @param name group name + * @param list list to store resources in (or null to create a new {@link LinkedList}). + * @return given list + */ + protected List acquireList(String name, List list) { + if (list == null) { + list = new LinkedList<>(); + } + ResourceTicket[] tickets = getGroup(name).getArray(); + for (ResourceTicket t : tickets) { + T res = resources.acquireOrElse(t, null); + if (res != null) list.add(res); + } + return list; + } + + /** + * Releases all reasources associated with any registered ticket. + *

    + * Called automatically. + */ + protected void releaseAll() { + for (ResourceTicket t : inputs) { + resources.releaseOptional(t); + } + for (ResourceTicket t : outputs) { + resources.releaseOptional(t); + } + } + + /** + * Removes all members of the named group from the input and output lists. + * + * @param + * @param name + * @return + */ + protected ResourceTicket[] removeGroup(String name) { + TicketGroup group = groups.remove(name); + if (group == null) { + return null; + } + // Once we determine which list group members were added to, we only + // need to remove from that list for future members. + byte state = 0; + if (group.isList()) state = 1; + for (ResourceTicket t : group.getArray()) { + if (state >= 0 && inputs.remove(t)) { + state = 1; + } + if (state <= 0 && outputs.remove(t)) { + state = -1; + } + } + return group.getArray(); + } + + /** + * Gets an existing {@link FrameBuffer} that matches the given properties. + *

    + * If no existing FrameBuffer matches, a new framebuffer will be created + * and returned. FrameBuffers that are not used during pass execution + * are disposed. + *

    + * If the event capturer is not null, an event will be logged for debugging. + * + * @param cap graph event capturer for debugging (may be null) + * @param tag tag (name) requirement for returned FrameBuffer (may be null) + * @param width width requirement for returned FrameBuffer + * @param height height requirement for returned FrameBuffer + * @param samples samples requirement for returned FrameBuffer + * @return FrameBuffer matching given width, height, and samples + */ + protected FrameBuffer getFrameBuffer(GraphEventCapture cap, String tag, int width, int height, int samples) { + if (tag == null) { + tag = PassFrameBuffer.DEF_TAG; + } + for (PassFrameBuffer fb : frameBuffers) { + if (fb.qualifies(tag, width, height, samples)) { + return fb.use(); + } + } + PassFrameBuffer fb = new PassFrameBuffer(tag, width, height, samples); + frameBuffers.add(fb); + if (cap != null) cap.createFrameBuffer(fb.frameBuffer); + return fb.use(); + } + /** + * + * @param cap + * @param width + * @param height + * @param samples + * @return + * @see #getFrameBuffer(com.jme3.renderer.framegraph.debug.GraphEventCapture, java.lang.String, int, int, int) + */ + protected FrameBuffer getFrameBuffer(GraphEventCapture cap, int width, int height, int samples) { + return getFrameBuffer(cap, null, width, height, samples); + } + /** + * + * @param width + * @param height + * @param samples + * @return + * @see #getFrameBuffer(com.jme3.renderer.framegraph.debug.GraphEventCapture, java.lang.String, int, int, int) + */ + protected FrameBuffer getFrameBuffer(int width, int height, int samples) { + return getFrameBuffer(null, null, width, height, samples); + } + /** + * + * @param tag + * @param width + * @param height + * @param samples + * @return + * @see #getFrameBuffer(com.jme3.renderer.framegraph.debug.GraphEventCapture, java.lang.String, int, int, int) + */ + protected FrameBuffer getFrameBuffer(String tag, int width, int height, int samples) { + return getFrameBuffer(null, tag, width, height, samples); + } + /** + * Creates a FrameBuffer matching the width and height presented by the {@link FGRenderContext}. + * + * @param context + * @param samples + * @return + * @see #getFrameBuffer(com.jme3.renderer.framegraph.debug.GraphEventCapture, java.lang.String, int, int, int) + */ + protected FrameBuffer getFrameBuffer(FGRenderContext context, int samples) { + return getFrameBuffer(context.getGraphCapture(), context.getWidth(), context.getHeight(), samples); + } + /** + * Creates a FrameBuffer matching the width and height presented by the {@link FGRenderContext}. + * + * @param context + * @param tag + * @param samples + * @return + * @see #getFrameBuffer(com.jme3.renderer.framegraph.debug.GraphEventCapture, java.lang.String, int, int, int) + */ + protected FrameBuffer getFrameBuffer(FGRenderContext context, String tag, int samples) { + return getFrameBuffer(context.getGraphCapture(), tag, context.getWidth(), context.getHeight(), samples); + } + + /** + * Gets the name given to a profiler, which may be more compact or informative. + * + * @return + */ + public String getProfilerName() { + return getName(); + } + + /** + * Convenience method for writing pass properties to the output capsule. + * + * @param out + * @throws IOException + */ + protected void write(OutputCapsule out) throws IOException {} + /** + * Convenience method for reading pass properties from the input capsule. + * + * @param in + * @throws IOException + */ + protected void read(InputCapsule in) throws IOException {} + + private static class PassFrameBuffer { + + public static final String DEF_TAG = "#DefaultTag"; + + public final String tag; + public final FrameBuffer frameBuffer; + public boolean used = false; + + public PassFrameBuffer(int width, int height, int samples) { + this(DEF_TAG, width, height, samples); + } + public PassFrameBuffer(String tag, int width, int height, int samples) { + this.tag = tag; + frameBuffer = new FrameBuffer(width, height, samples); + } + + public FrameBuffer use() { + used = true; + return frameBuffer; + } + + public boolean qualifies(String tag, int width, int height, int samples) { + return frameBuffer.getWidth() == width + && frameBuffer.getHeight() == height + && frameBuffer.getSamples() == samples + && this.tag.equals(tag); + } + + public void dispose() { + frameBuffer.dispose(); + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/ScalingPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/ScalingPass.java new file mode 100644 index 0000000000..1c2f4953fd --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/ScalingPass.java @@ -0,0 +1,128 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph.passes; + +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture2D; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author codex + */ +public class ScalingPass extends RenderPass { + + private static final Logger logger = Logger.getLogger(ScalingPass.class.getName()); + + private ResourceTicket inTex, outTex, tweenTex; + private TextureDef outTexDef, tweenTexDef; + private Material material; + private int renders = 5; + private boolean downsample = true; + private final Vector2f tempTexelSize = new Vector2f(); + + public ScalingPass() { + autoTicketRelease = false; + } + + @Override + protected void initialize(FrameGraph frameGraph) { + inTex = addInput("Texture"); + outTex = addOutput("Texture"); + tweenTex = new ResourceTicket<>(); + outTexDef = new TextureDef<>(Texture2D.class, img -> new Texture2D(img)); + tweenTexDef = new TextureDef<>(Texture2D.class, img -> new Texture2D(img)); + } + @Override + protected void prepare(FGRenderContext context) { + declare(outTexDef, outTex); + reserve(outTex); + reference(inTex); + } + @Override + protected void execute(FGRenderContext context) { + context.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha); + Texture2D prev = resources.acquire(inTex); + int w = prev.getImage().getWidth(); + int h = prev.getImage().getHeight(); + if (downsample) { + capNumRenders(w, h); + } + for (int i = 0; i < renders-1; i++) { + w = increment(w); + h = increment(h); + declare(tweenTexDef, tweenTex); + prev = render(context, tweenTex, tweenTexDef, prev, w, h); + resources.release(tweenTex); + } + w = increment(w); + h = increment(h); + render(context, outTex, outTexDef, prev, w, h); + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + + private int increment(int n) { + if (downsample) return n >> 1; + else return n << 1; + } + private void capNumRenders(int w, int h) { + int limit = Math.min(w, h); + for (int i = 0; i < renders; i++) { + limit = limit >> 1; + if (limit <= 2) { + renders = i; + logger.log(Level.INFO, "Number of renders capped at {0} due to texture size.", i); + break; + } + } + } + private Texture2D render(FGRenderContext context, ResourceTicket ticket, + TextureDef def, Texture2D prev, int w, int h) { + def.setSize(w, h); + def.setFormat(prev.getImage().getFormat()); + FrameBuffer fb = getFrameBuffer(w, h, 1); + Texture2D tex = resources.acquireColorTarget(fb, ticket); + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + if (material != null) { + renderMaterial(context, prev, w, h); + } else { + context.renderTextures(prev, null); + } + return tex; + } + + protected void renderMaterial(FGRenderContext context, Texture2D texture, int w, int h) { + material.setTexture("Texture", texture); + material.setVector2("TexelSize", tempTexelSize.set(1f/w, 1f/h)); + context.getScreen().render(context.getRenderManager(), material); + } + + public void setMaterial(Material material) { + this.material = material; + } + public void setDownsample(boolean downsample) { + this.downsample = downsample; + } + + public Material getMaterial() { + return material; + } + public boolean isDownsample() { + return downsample; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/SceneEnqueuePass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/SceneEnqueuePass.java new file mode 100644 index 0000000000..08bb7cd9e3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/SceneEnqueuePass.java @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2024 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.renderer.framegraph.passes; + +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.light.Light; +import com.jme3.light.LightList; +import com.jme3.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.DepthRange; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.GeometryQueue; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.queue.GeometryComparator; +import com.jme3.renderer.queue.GuiComparator; +import com.jme3.renderer.queue.NullComparator; +import com.jme3.renderer.queue.OpaqueComparator; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.TransparentComparator; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Enqueues geometries into different {@link GeometryQueue}s based on world + * render bucket value. + *

    + * Outputs vary based on what GeometryQueues are added. If default queues are + * added (via {@link #SceneEnqueuePass(boolean, boolean)}), then the outputs + * include: "Opaque", "Sky", "Transparent", "Gui", and "Translucent". All outputs + * are GeometryQueues. + *

    + * A geometry is placed in queues according to the userdata found at + * {@link #QUEUE} (expected as String) according to ancestor inheritance, or the + * value returned by {@link Geometry#getQueueBucket()} (converted to String). + * Userdata value (if found) trumps queue bucket value. + * + * @author codex + */ +public class SceneEnqueuePass extends RenderPass { + + /** + * Userdata key for denoting the queue the spatial should be sorted into. + */ + public static final String QUEUE = "SceneEnqueuePass.RenderQueue"; + + /** + * Userdata value for inheriting the queue of the spatial's parent. + */ + public static final String INHERIT = RenderQueue.Bucket.Inherit.name(); + + public static final String + OPAQUE = "Opaque", + SKY = "Sky", + TRANSPARENT = "Transparent", + GUI = "Gui", + TRANSLUCENT = "Translucent"; + + private boolean runControlRender = true; + private final HashMap queues = new HashMap<>(); + private String defaultBucket = OPAQUE; + + /** + * Initialize an instance with default settings. + *

    + * Default queues are not added. + */ + public SceneEnqueuePass() {} + /** + * + * @param runControlRender true to have this pass run {@link com.jme3.scene.control.Control} renders + * @param useDefaultBuckets true to have default queues registered + */ + public SceneEnqueuePass(boolean runControlRender, boolean useDefaultBuckets) { + this.runControlRender = runControlRender; + if (useDefaultBuckets) { + add(OPAQUE, new OpaqueComparator()); + add(SKY, null, DepthRange.REAR, true); + add(TRANSPARENT, new TransparentComparator()); + add(GUI, new GuiComparator(), DepthRange.FRONT, false); + add(TRANSLUCENT, new TransparentComparator()); + } + } + + @Override + protected void initialize(FrameGraph frameGraph) { + for (Queue b : queues.values()) { + b.geometry = addOutput(b.name); + b.lights = addOutput(b.name+"Lights"); + } + } + @Override + protected void prepare(FGRenderContext context) { + for (Queue b : queues.values()) { + declare(null, b.geometry); + declare(null, b.lights); + } + } + @Override + protected void execute(FGRenderContext context) { + ViewPort vp = context.getViewPort(); + List scenes = vp.getScenes(); + for (int i = scenes.size()-1; i >= 0; i--) { + vp.getCamera().setPlaneState(0); + queueSubScene(context, scenes.get(i), null); + } + for (Queue b : queues.values()) { + resources.setPrimitive(b.geometry, b.queue); + resources.setPrimitive(b.lights, b.lightList); + } + } + @Override + protected void reset(FGRenderContext context) { + for (Queue b : queues.values()) { + b.queue.clear(); + b.lightList.clear(); + } + } + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + out.write(runControlRender, "runControlRender", true); + ArrayList list = new ArrayList<>(); + list.addAll(queues.values()); + out.writeSavableArrayList(list, "buckets", new ArrayList<>()); + out.write(defaultBucket, "defaultBucket", OPAQUE); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + runControlRender = in.readBoolean("runControlRender", true); + ArrayList list = in.readSavableArrayList("buckets", new ArrayList<>()); + for (Savable s : list) { + Queue b = (Queue)s; + queues.put(b.name, b); + } + defaultBucket = in.readString("defaultBucket", OPAQUE); + } + + private void queueSubScene(FGRenderContext context, Spatial spatial, String parentBucket) { + // check culling + Camera cam = context.getViewPort().getCamera(); + if (!spatial.checkCulling(cam)) { + return; + } + // render controls + if (runControlRender) { + spatial.runControlRender(context.getRenderManager(), context.getViewPort()); + } + // get target bucket + String value = getSpatialBucket(spatial, parentBucket); + Queue queue = (value != null ? queues.get(value) : null); + // accumulate lights + if (queue != null) for (Light l : spatial.getLocalLightList()) { + queue.lightList.add(l); + } + if (spatial instanceof Node) { + int camState = cam.getPlaneState(); + for (Spatial s : ((Node)spatial).getChildren()) { + // restore cam state before queueing children + cam.setPlaneState(camState); + queueSubScene(context, s, value); + } + } else if (queue != null && spatial instanceof Geometry) { + // add to the render queue + Geometry g = (Geometry)spatial; + if (g.getMaterial() == null) { + throw new IllegalStateException("No material is set for Geometry: " + g.getName()); + } + queue.queue.add(g); + } + } + private String getSpatialBucket(Spatial spatial, String parentValue) { + String value = spatial.getUserData(QUEUE); + if (value == null) { + value = spatial.getLocalQueueBucket().name(); + } + if (value.equals(INHERIT)) { + if (parentValue != null) { + value = parentValue; + } else if (spatial.getParent() != null) { + value = getSpatialBucket(spatial.getParent(), null); + } else { + value = defaultBucket; + } + } + return value; + } + + /** + * Adds a queue with the name and comparator. + *

    + * If a bucket already exists under the name, it will be replaced. + * + * @param name name of the queue corresponding to the output name + * @param comparator sorts geometries within the queue + * @return this instance + * @throws IllegalStateException if called while assigned to a framegraph + */ + public final SceneEnqueuePass add(String name, GeometryComparator comparator) { + return add(name, comparator, DepthRange.IDENTITY, true); + } + /** + * Adds a queue with the name, comparator, depth range, and perspective mode. + * + * @param name name of the queue corresponding to the output name. + * @param comparator sorts geometries within the queue + * @param depth range in which geometries in the bucket will be rendered within + * @param perspective true to render geometries in the bucket in perspective mode (versus orthogonal) + * @return this instance + * @throws IllegalStateException if called while assigned to a framegraph + */ + public final SceneEnqueuePass add(String name, GeometryComparator comparator, DepthRange depth, boolean perspective) { + if (isAssigned()) { + throw new IllegalStateException("Cannot add buckets while assigned to a framegraph."); + } + queues.put(name, new Queue(name, comparator, depth, perspective)); + return this; + } + + /** + * Sets this pass to render controls when traversing the scene. + *

    + * default=true + * + * @param runControlRender + */ + public void setRunControlRender(boolean runControlRender) { + this.runControlRender = runControlRender; + } + /** + * Sets the default bucket geometries are added to if their + * hierarchy only calls for {@link #INHERIT}. + *

    + * default={@link #OPAQUE} + * + * @param defaultBucket + */ + public void setDefaultBucket(String defaultBucket) { + this.defaultBucket = defaultBucket; + } + + /** + * + * @return + */ + public boolean isRunControlRender() { + return runControlRender; + } + /** + * + * @return + */ + public String getDefaultBucket() { + return defaultBucket; + } + + private static class Queue implements Savable { + + public static final NullComparator NULL_COMPARATOR = new NullComparator(); + + public String name; + public GeometryQueue queue; + public final LightList lightList = new LightList(null); + public ResourceTicket geometry; + public ResourceTicket lights; + + public Queue() {} + public Queue(String name, GeometryComparator comparator, DepthRange depth, boolean perspective) { + if (comparator == null) { + comparator = Queue.NULL_COMPARATOR; + } + this.name = name; + this.queue = new GeometryQueue(comparator); + this.queue.setDepth(depth); + this.queue.setPerspective(perspective); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + out.write(name, "name", "Opaque"); + out.write(queue.getComparator(), "comparator", NULL_COMPARATOR); + out.write(queue.getDepth(), "depth", DepthRange.IDENTITY); + out.write(queue.isPerspective(), "perspective", true); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + name = in.readString("name", "Opaque"); + queue = new GeometryQueue(in.readSavable("comparator", GeometryComparator.class, NULL_COMPARATOR)); + queue.setDepth(in.readSavable("depth", DepthRange.class, DepthRange.IDENTITY)); + queue.setPerspective(in.readBoolean("perspective", true)); + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 60e6b30265..1c7a9ce10a 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -1434,6 +1434,7 @@ assert isValidNumber(fb) : "Invalid Matrix4f array value " gl.glUniform1i(loc, i.intValue()); break; default: + System.out.println("uniform value = "+uniform.getValue().toString()); throw new UnsupportedOperationException( "Unsupported uniform type: " + uniform.getVarType() + " for " + uniform.getBinding()); } @@ -1831,6 +1832,7 @@ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyColor, src = mainFbOverride; } if (dst == null) { + System.out.println("assign mainfpOverride to destination framebuffer"); dst = mainFbOverride; } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryComparator.java index 39a8fdf82c..ed5a0e2b48 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryComparator.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryComparator.java @@ -31,8 +31,12 @@ */ package com.jme3.renderer.queue; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; import com.jme3.renderer.Camera; import com.jme3.scene.Geometry; +import java.io.IOException; import java.util.Comparator; /** @@ -41,7 +45,7 @@ * * @author Kirill Vainer */ -public interface GeometryComparator extends Comparator { +public interface GeometryComparator extends Comparator, Savable { /** * Set the camera to use for sorting. @@ -49,4 +53,11 @@ public interface GeometryComparator extends Comparator { * @param cam The camera to use for sorting */ public void setCamera(Camera cam); + + @Override + public default void write(JmeExporter ex) throws IOException {} + + @Override + public default void read(JmeImporter im) throws IOException {} + } diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java index 5836c87614..e5f02e1007 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java @@ -32,11 +32,12 @@ package com.jme3.renderer.queue; import java.util.Iterator; -import java.util.NoSuchElementException; import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; import com.jme3.scene.Geometry; import com.jme3.util.ListSort; +import java.util.ArrayList; /** * This class is a special purpose list of {@link Geometry} objects for render @@ -49,11 +50,14 @@ public class GeometryList implements Iterable{ private static final int DEFAULT_SIZE = 32; - + private Geometry[] geometries; final private ListSort listSort; private int size; private GeometryComparator comparator; + private Camera cam; + private boolean updateFlag = true; + private ArrayList lists = new ArrayList<>(); /** * Initializes the GeometryList to use the given {@link GeometryComparator} @@ -68,8 +72,18 @@ public GeometryList(GeometryComparator comparator) { listSort = new ListSort(); } + /** + * Marks this list as requiring sorting. + */ + public void setUpdateNeeded() { + updateFlag = true; + } + public void setComparator(GeometryComparator comparator) { - this.comparator = comparator; + if (this.comparator != comparator) { + this.comparator = comparator; + updateFlag = true; + } } /** @@ -89,7 +103,14 @@ public GeometryComparator getComparator() { * @param cam Camera to use for sorting. */ public void setCamera(Camera cam) { - this.comparator.setCamera(cam); + if (this.cam != cam) { + this.cam = cam; + comparator.setCamera(this.cam); + updateFlag = true; + } + for (GeometryList l : lists) { + l.setCamera(cam); + } } /** @@ -97,7 +118,7 @@ public void setCamera(Camera cam) { * * @return Number of elements in the list */ - public int size(){ + public int size() { return size; } @@ -109,6 +130,7 @@ public int size(){ */ public void set(int index, Geometry value) { geometries[index] = value; + updateFlag = true; } /** @@ -135,6 +157,16 @@ public void add(Geometry g) { geometries = temp; // original list replaced by double-size list } geometries[size++] = g; + updateFlag = true; + } + + /** + * + * + * @param l + */ + public void addList(GeometryList l) { + lists.add(l); } /** @@ -144,47 +176,81 @@ public void clear() { for (int i = 0; i < size; i++) { geometries[i] = null; } - + lists.clear(); + updateFlag = true; size = 0; } /** * Sorts the elements in the list according to their Comparator. */ - @SuppressWarnings("unchecked") public void sort() { - if (size > 1) { + if (updateFlag && size > 1) { // sort the spatial list using the comparator if (listSort.getLength() != size) { listSort.allocateStack(size); } - listSort.sort(geometries,comparator); + listSort.sort(geometries, comparator); + updateFlag = false; + } + for (GeometryList l : lists) { + l.sort(); + } + } + + /** + * Renders the geometries in this list and in internal lists. + * + * @param rm + */ + public void render(RenderManager rm) { + for (Geometry g : geometries) { + rm.renderGeometry(g); + } + for (GeometryList l : lists) { + l.render(rm); } } @Override public Iterator iterator() { - return new Iterator() { - int index = 0; - - @Override - public boolean hasNext() { - return index < size(); + return new ListIterator(); + } + + private class ListIterator implements Iterator { + + private int arrayIndex = 0; + private int listIndex = 0; + private Iterator it = null; + + @Override + public boolean hasNext() { + if (arrayIndex < size) { + return true; } - - - @Override - public Geometry next() { - if (index >= size()) { - throw new NoSuchElementException("Geometry list has only " + size() + " elements"); + // iterate until a populated list is found + while (it == null || !it.hasNext()) { + if (listIndex >= lists.size()) { + return false; + } + GeometryList l = lists.get(listIndex++); + if (l.size > 0) { + it = l.iterator(); } - return get(index++); } - - @Override - public void remove() { - throw new UnsupportedOperationException("Geometry list doesn't support iterator removal"); + return true; + } + @Override + public Geometry next() { + if (it != null) { + return it.next(); } - }; + return get(arrayIndex++); + } + @Override + public void remove() { + throw new UnsupportedOperationException("Geometry list doesn't support iterator removal"); + } } + } \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java index 86c5bc5280..0326708aa1 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java @@ -36,11 +36,13 @@ import com.jme3.renderer.RenderManager; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; +import java.util.LinkedList; +import com.jme3.renderer.GeometryRenderHandler; /** - * RenderQueue is used to queue up and sort + * RenderQueue is used to queue up and sort * {@link Geometry geometries} for rendering. - * + * * @author Kirill Vainer */ public class RenderQueue { @@ -64,50 +66,50 @@ public RenderQueue() { } /** - * The render queue Bucket specifies the bucket - * to which the spatial will be placed when rendered. + * The renderGeometry queue Bucket specifies the bucket + * to which the spatial will be placed when rendered. *

    - * The behavior of the rendering will differ depending on which + * The behavior of the rendering will differ depending on which * bucket the spatial is placed. A spatial's queue bucket can be set * via {@link Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket) }. */ public enum Bucket { /** - * The renderer will try to find the optimal order for rendering all + * The renderer will try to find the optimal order for rendering all * objects using this mode. * You should use this mode for most normal objects, except transparent * ones, as it could give a nice performance boost to your application. */ Opaque, - + /** * This is the mode you should use for object with * transparency in them. It will ensure the objects furthest away are * rendered first. That ensures when another transparent object is drawn on * top of previously drawn objects, you can see those (and the object drawn * using Opaque) through the transparent parts of the newly drawn - * object. + * object. */ Transparent, - + /** - * A special mode used for rendering really far away, flat objects - - * e.g. skies. In this mode, the depth is set to infinity so + * A special mode used for rendering really far away, flat objects - + * e.g. skies. In this mode, the depth is set to infinity so * spatials in this bucket will appear behind everything, the downside * to this bucket is that 3D objects will not be rendered correctly * due to lack of depth testing. */ Sky, - + /** * A special mode used for rendering transparent objects that - * should not be affected by {@link SceneProcessor}. + * should not be affected by {@link SceneProcessor}. * Generally this would contain translucent objects, and * also objects that do not write to the depth buffer such as * particle emitters. */ Translucent, - + /** * This is a special mode, for drawing 2D object * without perspective (such as GUI or HUD parts). @@ -117,7 +119,7 @@ public enum Bucket { * outside of that range are culled. */ Gui, - + /** * A special mode, that will ensure that this spatial uses the same * mode as the parent Node does. @@ -135,22 +137,22 @@ public enum ShadowMode { * Generally used for special effects like particle emitters. */ Off, - + /** - * Enable casting of shadows but not receiving them. + * Enable casting of shadows but not receiving them. */ Cast, - + /** * Enable receiving of shadows but not casting them. */ Receive, - + /** * Enable both receiving and casting of shadows. */ CastAndReceive, - + /** * Inherit the ShadowMode from the parent node. */ @@ -236,11 +238,10 @@ public GeometryComparator getGeometryComparator(Bucket bucket) { * Adds a geometry to the given bucket. * The {@link RenderManager} automatically handles this task * when flattening the scene graph. The bucket to add - * the geometry is determined by {@link Geometry#getQueueBucket() }. - * + * the geometry is determined by {@link Geometry#getQueueBucket()}. + * * @param g The geometry to add - * @param bucket The bucket to add to, usually - * {@link Geometry#getQueueBucket() }. + * @param bucket The bucket to add to, usually {@link Geometry#getQueueBucket()}. */ public void addToQueue(Geometry g, Bucket bucket) { switch (bucket) { @@ -265,16 +266,35 @@ public void addToQueue(Geometry g, Bucket bucket) { } private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + GeometryRenderHandler handler = rm.getGeometryRenderHandler(); list.setCamera(cam); // select camera for sorting list.sort(); - for (int i = 0; i < list.size(); i++) { - Geometry obj = list.get(i); - assert obj != null; - rm.renderGeometry(obj); - obj.queueDistance = Float.NEGATIVE_INFINITY; - } - if (clear) { + if (handler != null && clear) { + LinkedList saved = new LinkedList<>(); + for (Geometry g : list) { + assert g != null; + if (!handler.renderGeometry(rm, g)) { + saved.add(g); + } + g.queueDistance = Float.NEGATIVE_INFINITY; + } list.clear(); + for (Geometry g : saved) { + list.add(g); + } + } else { + for (Geometry g : list) { + assert g != null; + if (handler != null) { + handler.renderGeometry(rm, g); + } else { + rm.renderGeometry(g); + } + g.queueDistance = Float.NEGATIVE_INFINITY; + } + if (clear) { + list.clear(); + } } } diff --git a/jme3-core/src/main/java/com/jme3/scene/ParentIterator.java b/jme3-core/src/main/java/com/jme3/scene/ParentIterator.java new file mode 100644 index 0000000000..c36ad20c72 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/ParentIterator.java @@ -0,0 +1,62 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.scene; + +import java.util.Iterator; +import java.util.LinkedList; + +/** + * Iterates through the given spatials and their ancestors. + * + * @author codex + */ +public class ParentIterator implements Iterable, Iterator { + + private static final String VISITED_TAG = ParentIterator.class.getName()+":visited"; + + private final LinkedList spatials = new LinkedList<>(); + private final LinkedList current = new LinkedList<>(); + private final Iterator iterator; + + public ParentIterator(Iterable startingPoints) { + for (Spatial s : startingPoints) { + if (!isVisited(s)) { + visit(s); + } + } + while (!current.isEmpty()) { + Spatial parent = current.removeFirst().getParent(); + if (parent != null && !isVisited(parent)) { + visit(parent); + } + } + iterator = spatials.iterator(); + } + + private boolean isVisited(Spatial s) { + return s.getUserData(VISITED_TAG) != null; + } + private void visit(Spatial s) { + s.setUserData(VISITED_TAG, true); + current.addFirst(s); + spatials.add(s); + } + + @Override + public Iterator iterator() { + return this; + } + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + @Override + public Spatial next() { + Spatial s = iterator.next(); + s.setUserData(VISITED_TAG, null); + return s; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index 2fe1775d3e..1037325887 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -137,6 +137,7 @@ public enum BatchHint { */ protected LightList localLights; protected transient LightList worldLights; + protected transient LightList filterWorldLights; protected SafeArrayList localOverrides; protected SafeArrayList worldOverrides; @@ -247,7 +248,7 @@ boolean requiresUpdates() { * call setRequiresUpdate(false) in their constructors to receive * optimal behavior if they don't require updateLogicalState() to be * called even if there are no controls. - * + * * @param f true→require updates, false→don't require updates */ protected void setRequiresUpdates(boolean f) { @@ -434,6 +435,18 @@ public LightList getWorldLightList() { return worldLights; } + /** + * Set Current filterLight. + * @param filterLight + */ + public void setFilterLight(LightList filterLight){ + filterWorldLights = filterLight; + } + + public LightList getFilterWorldLights() { + return filterWorldLights; + } + /** * Get the local material parameter overrides. * @@ -1595,7 +1608,7 @@ public Collection getUserDataKeys() { * @see java.util.regex.Pattern */ public boolean matches(Class spatialSubclass, - String nameRegex) { + String nameRegex) { if (spatialSubclass != null && !spatialSubclass.isInstance(this)) { return false; } diff --git a/jme3-core/src/main/java/com/jme3/shader/DefineList.java b/jme3-core/src/main/java/com/jme3/shader/DefineList.java index d4ee84fc51..b527730564 100644 --- a/jme3-core/src/main/java/com/jme3/shader/DefineList.java +++ b/jme3-core/src/main/java/com/jme3/shader/DefineList.java @@ -43,7 +43,7 @@ public final class DefineList { private final BitSet isSet; - private final int[] values; + private int[] values; public DefineList(int numValues) { if (numValues < 0) { @@ -60,7 +60,13 @@ private DefineList(DefineList original) { } private void rangeCheck(int id) { - assert 0 <= id && id < values.length; + //assert 0 <= id && id < values.length; + assert id >= 0 : "Define id cannot be less than zero."; + if (id > values.length) { + int[] temp = new int[id+1]; + System.arraycopy(values, 0, temp, 0, values.length); + values = temp; + } } public boolean isSet(int id) { @@ -139,6 +145,10 @@ public float getFloat(int id) { public int getInt(int id) { return values[id]; } + + public int size() { + return values.length; + } @Override public int hashCode() { diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java index cb7a87b89d..d85081c1dc 100644 --- a/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java @@ -306,7 +306,7 @@ public AccessHint getAccessHint() { /** * Set AccessHint to hint the renderer on how to access this data. * - * @param natureHint + * @param accessHint */ public void setAccessHint(AccessHint accessHint) { this.accessHint = accessHint; diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java index 78198ad025..db8310ad66 100644 --- a/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java @@ -85,7 +85,7 @@ protected ObjectSerializer getSerializer(Object obj) { /** * Register a serializer * - * @param type + * @param serializer */ protected void registerSerializer(ObjectSerializer serializer) { serializers.add(serializer); diff --git a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java index 4cefa6ab6f..4403a758c9 100644 --- a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java @@ -249,6 +249,16 @@ public static FrameBufferTextureTarget newTarget(Texture tx, TextureCubeMap.Face return t; } } + + public static FrameBufferTextureTarget target(Texture tx) { + return FrameBufferTarget.newTarget(tx); + } + public static FrameBufferBufferTarget target(Format format) { + return FrameBufferTarget.newTarget(format); + } + public static FrameBufferTextureTarget target(Texture tx, TextureCubeMap.Face face) { + return FrameBufferTarget.newTarget(tx, face); + } /** * A private constructor to inhibit instantiation of this class. @@ -260,12 +270,32 @@ public void addColorTarget(FrameBufferBufferTarget colorBuf){ colorBuf.slot=colorBufs.size(); colorBufs.add(colorBuf); } - + public void addColorTarget(FrameBufferTextureTarget colorBuf){ // checkSetTexture(colorBuf.getTexture(), false); // TODO: this won't work for levels. colorBuf.slot=colorBufs.size(); colorBufs.add(colorBuf); } + + /** + * Sets the color target at the index. + * + * @param i + * @param colorBuf + */ + public void setColorTarget(int i, FrameBufferTextureTarget colorBuf) { + colorBuf.slot = i; + colorBufs.set(i, colorBuf); + } + + /** + * Removes all color targets stored at indices above the specified index. + * + * @param i + */ + public void trimColorTargetsTo(int i) { + colorBufs.removeIf(rb -> rb.slot > i); + } /** * Adds a texture to one of the color Buffers Array. It uses {@link TextureCubeMap} ordinal number for the diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index 25518966a2..c136af4a7f 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -737,7 +737,7 @@ public Image clone(){ */ public Image() { super(); - data = new ArrayList(1); + data = new ArrayList<>(1); } protected Image(int id){ @@ -837,7 +837,7 @@ public Image(Format format, int width, int height, ByteBuffer data, this.width = width; this.height = height; if (data != null){ - this.data = new ArrayList(1); + this.data = new ArrayList<>(1); this.data.add(data); } this.mipMapSizes = mipMapSizes; diff --git a/jme3-core/src/main/java/com/jme3/util/TempVars.java b/jme3-core/src/main/java/com/jme3/util/TempVars.java index fcc8c8071d..42d195399f 100644 --- a/jme3-core/src/main/java/com/jme3/util/TempVars.java +++ b/jme3-core/src/main/java/com/jme3/util/TempVars.java @@ -164,6 +164,7 @@ public void release() { * Color */ public final ColorRGBA color = new ColorRGBA(); + public final ColorRGBA color2 = new ColorRGBA(); /** * General vectors. */ diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java b/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java index 4529239865..269009a960 100644 --- a/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java @@ -56,15 +56,15 @@ public class StructStd140BufferObject extends BufferObject { private final Std140Layout std140 = new Std140Layout(); /** - * Create an empty Struct buffer - * - * @param str + * Create an empty Struct buffer. */ public StructStd140BufferObject() { } /** - * Internal only + * Internal only. + * + * @param id */ public StructStd140BufferObject(int id) { super(id); diff --git a/jme3-core/src/main/resources/Common/FrameGraphs/Deferred.j3g b/jme3-core/src/main/resources/Common/FrameGraphs/Deferred.j3g new file mode 100644 index 0000000000..ab430aae62 Binary files /dev/null and b/jme3-core/src/main/resources/Common/FrameGraphs/Deferred.j3g differ diff --git a/jme3-core/src/main/resources/Common/FrameGraphs/Forward.j3g b/jme3-core/src/main/resources/Common/FrameGraphs/Forward.j3g new file mode 100644 index 0000000000..98f0faecd3 Binary files /dev/null and b/jme3-core/src/main/resources/Common/FrameGraphs/Forward.j3g differ 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 fd32933738..70991f2da3 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -131,7 +131,6 @@ MaterialDef Phong Lighting { Vector2 LinearFog Float ExpFog Float ExpSqFog - } Technique { @@ -262,6 +261,50 @@ MaterialDef Phong Lighting { } + Technique GBufferPass { + + VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/LightingGBufferPack.vert + FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/LightingGBufferPack.frag + + WorldParameters { + WorldViewProjectionMatrix + NormalMatrix + WorldNormalMatrix + WorldViewMatrix + ViewMatrix + CameraPosition + WorldMatrix + ViewProjectionMatrix + } + + Defines { + VERTEX_COLOR : UseVertexColor + MATERIAL_COLORS : UseMaterialColors + DIFFUSEMAP : DiffuseMap + NORMALMAP : NormalMap + SPECULARMAP : SpecularMap + PARALLAXMAP : ParallaxMap + NORMALMAP_PARALLAX : PackedNormalParallax + STEEP_PARALLAX : SteepParallax + ALPHAMAP : AlphaMap + COLORRAMP : ColorRamp + LIGHTMAP : LightMap + SEPARATE_TEXCOORD : SeparateTexCoord + DISCARD_ALPHA : AlphaDiscardThreshold + USE_REFLECTION : EnvMap + SPHERE_MAP : EnvMapAsSphereMap + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + + // fog - jayfella + USE_FOG : UseFog + FOG_LINEAR : LinearFog + FOG_EXP : ExpFog + FOG_EXPSQ : ExpSqFog + } + } Technique PostShadow { VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/LightingGBufferPack.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/LightingGBufferPack.frag new file mode 100644 index 0000000000..a7a8b89eab --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/LightingGBufferPack.frag @@ -0,0 +1,212 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" +#import "Common/ShaderLib/Parallax.glsllib" +#import "Common/ShaderLib/Optics.glsllib" +#ifndef VERTEX_LIGHTING + #import "Common/ShaderLib/BlinnPhongLighting.glsllib" + #import "Common/ShaderLib/Lighting.glsllib" +#endif +// shading model +#import "Common/ShaderLib/ShadingModel.glsllib" + +// fog - jayfella +#ifdef USE_FOG +#import "Common/ShaderLib/MaterialFog.glsllib" +varying float fog_distance; +uniform vec4 m_FogColor; + +#ifdef FOG_LINEAR +uniform vec2 m_LinearFog; +#endif + +#ifdef FOG_EXP +uniform float m_ExpFog; +#endif + +#ifdef FOG_EXPSQ +uniform float m_ExpSqFog; +#endif + +#endif // end fog + +varying vec2 texCoord; +#ifdef SEPARATE_TEXCOORD + varying vec2 texCoord2; +#endif + +varying vec3 AmbientSum; +varying vec4 DiffuseSum; +varying vec3 SpecularSum; + +#ifdef DIFFUSEMAP + uniform sampler2D m_DiffuseMap; +#endif + +#ifdef SPECULARMAP + uniform sampler2D m_SpecularMap; +#endif + +#ifdef PARALLAXMAP + uniform sampler2D m_ParallaxMap; +#endif +#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + uniform float m_ParallaxHeight; + varying vec3 vPos; + uniform vec3 g_CameraPosition; +#endif + +#ifdef LIGHTMAP + uniform sampler2D m_LightMap; +#endif + +#ifdef NORMALMAP + uniform sampler2D m_NormalMap; + varying vec4 vTangent; +#endif +varying vec3 vNormal; + +#ifdef ALPHAMAP + uniform sampler2D m_AlphaMap; +#endif + +#ifdef COLORRAMP + uniform sampler2D m_ColorRamp; +#endif + +uniform float m_AlphaDiscardThreshold; + +#ifndef VERTEX_LIGHTING +uniform float m_Shininess; + + #ifdef USE_REFLECTION + uniform float m_ReflectionPower; + uniform float m_ReflectionIntensity; + varying vec4 refVec; + + uniform ENVMAP m_EnvMap; + #endif +#endif + +void main(){ + vec2 newTexCoord; + #if defined(NORMALMAP) || defined(PARALLAXMAP) + vec3 norm = normalize(vNormal); + vec3 tan = normalize(vTangent.xyz); + mat3 tbnMat = mat3(tan, vTangent.w * cross( (norm), (tan)), norm); + #endif + + #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) && !defined(VERTEX_LIGHTING) + vec3 viewDir = normalize(g_CameraPosition - vPos); + 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 DIFFUSEMAP + vec4 diffuseColor = texture2D(m_DiffuseMap, newTexCoord); + #else + vec4 diffuseColor = vec4(1.0); + #endif + + float alpha = DiffuseSum.a * diffuseColor.a; + + #ifdef ALPHAMAP + alpha = alpha * texture2D(m_AlphaMap, newTexCoord).r; + #endif + + #ifdef DISCARD_ALPHA + if(alpha < m_AlphaDiscardThreshold){ + discard; + } + #endif + + // *********************** + // Read from textures + // *********************** + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + 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 compliant with normal maps generated with blender. + //see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898 + //for more explanation. + //mat3 tbnMat = mat3(vTangent.xyz, vTangent.w * cross( (vNormal), (vTangent.xyz)), vNormal.xyz); + + if (!gl_FrontFacing) + { + tbnMat[2] = -tbnMat[2]; + } + vec3 normal = tbnMat * normalize((normalHeight.xyz * vec3(2.0,-2.0,2.0) - vec3(1.0,-1.0,1.0))); + normal = normalize(normal); + #elif !defined(VERTEX_LIGHTING) + vec3 normal = normalize(vNormal); + + if (!gl_FrontFacing) + { + normal = -normal; + } + #endif + + #ifdef SPECULARMAP + vec4 specularColor = texture2D(m_SpecularMap, newTexCoord); + #else + vec4 specularColor = vec4(1.0); + #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; + diffuseColor.rgb *= lightMapColor; + #endif + + // pack + outGBuffer3.xyz = normal; + outGBuffer0.a = alpha; + +// #ifdef USE_REFLECTION +// vec4 refColor = Optics_GetEnvColor(m_EnvMap, refVec.xyz); +// #endif +// // Workaround, since it is not possible to modify varying variables +// vec4 SpecularSum2 = vec4(SpecularSum, 1.0); +// #ifdef USE_REFLECTION +// // Interpolate light specularity toward reflection color +// // Multiply result by specular map +// specularColor = mix(SpecularSum2 * light.y, refColor, refVec.w) * specularColor; +// +// SpecularSum2 = vec4(1.0); +// #endif +// +// vec3 DiffuseSum2 = DiffuseSum.rgb; +// #ifdef COLORRAMP +// DiffuseSum2.rgb *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb; +// SpecularSum2.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb; +// light.xy = vec2(1.0); +// #endif + outGBuffer0.rgb = diffuseColor.rgb * DiffuseSum.rgb; + outGBuffer1.rgb = specularColor.rgb * SpecularSum.rgb * 100.0f + AmbientSum * 0.01f; + outGBuffer1.a = m_Shininess; + + // shading model id + outGBuffer2.a = PHONG_LIGHTING; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/LightingGBufferPack.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/LightingGBufferPack.vert new file mode 100644 index 0000000000..b8a1598c9b --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/LightingGBufferPack.vert @@ -0,0 +1,149 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Instancing.glsllib" +#import "Common/ShaderLib/Skinning.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" +#import "Common/ShaderLib/MorphAnim.glsllib" + +#ifdef VERTEX_LIGHTING + #import "Common/ShaderLib/BlinnPhongLighting.glsllib" +#endif + +// fog - jayfella +#ifdef USE_FOG +varying float fog_distance; +uniform vec3 g_CameraPosition; +#endif + +uniform vec4 m_Ambient; +uniform vec4 m_Diffuse; +uniform vec4 m_Specular; +uniform float m_Shininess; + +#if defined(VERTEX_LIGHTING) + uniform vec4 g_LightData[NB_LIGHTS]; +#endif +varying vec2 texCoord; + +#ifdef SEPARATE_TEXCOORD + varying vec2 texCoord2; + attribute vec2 inTexCoord2; +#endif + +varying vec3 AmbientSum; +varying vec4 DiffuseSum; +varying vec3 SpecularSum; + +attribute vec3 inPosition; +attribute vec2 inTexCoord; +attribute vec3 inNormal; + +#ifdef VERTEX_COLOR + attribute vec4 inColor; +#endif + +#ifndef VERTEX_LIGHTING + varying vec3 vNormal; + varying vec3 vPos; + #ifdef NORMALMAP + attribute vec4 inTangent; + varying vec4 vTangent; + #endif +#else + #ifdef COLORRAMP + uniform sampler2D m_ColorRamp; + #endif +#endif + +#ifdef USE_REFLECTION + uniform vec3 g_CameraPosition; + uniform vec3 m_FresnelParams; + varying vec4 refVec; + + /** + * Input: + * attribute inPosition + * attribute inNormal + * uniform g_WorldMatrix + * uniform g_CameraPosition + * + * Output: + * varying refVec + */ + void computeRef(in vec4 modelSpacePos){ + // vec3 worldPos = (g_WorldMatrix * modelSpacePos).xyz; + vec3 worldPos = TransformWorld(modelSpacePos).xyz; + + vec3 I = normalize( g_CameraPosition - worldPos ).xyz; + // vec3 N = normalize( (g_WorldMatrix * vec4(inNormal, 0.0)).xyz ); + vec3 N = normalize( TransformWorld(vec4(inNormal, 0.0)).xyz ); + + refVec.xyz = reflect(I, N); + refVec.w = m_FresnelParams.x + m_FresnelParams.y * pow(1.0 + dot(I, N), m_FresnelParams.z); + } +#endif + +void main(){ + vec4 modelSpacePos = vec4(inPosition, 1.0); + vec3 modelSpaceNorm = inNormal; + + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + vec3 modelSpaceTan = inTangent.xyz; + #endif + + #ifdef NUM_MORPH_TARGETS + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); + #else + Morph_Compute(modelSpacePos, modelSpaceNorm); + #endif + #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 + + vec3 wPosition = TransformWorld(modelSpacePos).xyz; + vec3 wNormal = normalize(TransformWorldNormal(modelSpaceNorm)); + + #if (defined(NORMALMAP) || defined(PARALLAXMAP)) && !defined(VERTEX_LIGHTING) + vTangent = vec4(TransformWorldNormal(modelSpaceTan).xyz,inTangent.w); + vNormal = wNormal; + vPos = wPosition; + #elif !defined(VERTEX_LIGHTING) + vNormal = wNormal; + vPos = wPosition; + #endif + + #ifdef MATERIAL_COLORS + AmbientSum = m_Ambient.rgb; + SpecularSum = m_Specular.rgb; + DiffuseSum = m_Diffuse; + #else + // Defaults: Ambient and diffuse are white, specular is black. + AmbientSum = vec3(1.0); + SpecularSum = vec3(0.0); + DiffuseSum = vec4(1.0); + #endif + #ifdef VERTEX_COLOR + AmbientSum *= inColor.rgb; + DiffuseSum *= inColor; + #endif + +// #ifdef USE_REFLECTION +// computeRef(modelSpacePos); +// #endif + + #ifdef USE_FOG + fog_distance = distance(g_CameraPosition, (TransformWorld(modelSpacePos)).xyz); + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md index 1ee65eeb61..c9b419dd76 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md @@ -217,6 +217,49 @@ MaterialDef PBR Lighting { } + Technique GBufferPass { + + VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/PBRLightingGBufferPack.vert + FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/PBRLightingGBufferPack.frag + + WorldParameters { + WorldViewProjectionMatrix + CameraPosition + WorldMatrix + WorldNormalMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BASECOLORMAP : BaseColorMap + NORMALMAP : NormalMap + METALLICMAP : MetallicMap + ROUGHNESSMAP : RoughnessMap + EMISSIVEMAP : EmissiveMap + EMISSIVE : Emissive + SPECGLOSSPIPELINE : UseSpecGloss + PARALLAXMAP : ParallaxMap + NORMALMAP_PARALLAX : PackedNormalParallax + STEEP_PARALLAX : SteepParallax + LIGHTMAP : LightMap + SEPARATE_TEXCOORD : SeparateTexCoord + DISCARD_ALPHA : AlphaDiscardThreshold + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + USE_PACKED_MR: MetallicRoughnessMap + USE_PACKED_SG: SpecularGlossinessMap + SPECULARMAP : SpecularMap + GLOSSINESSMAP : GlossinessMap + NORMAL_TYPE: NormalType + VERTEX_COLOR : UseVertexColor + AO_MAP: LightMapAsAOMap + AO_PACKED_IN_MR_MAP : AoPackedInMRMap + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + HORIZON_FADE: HorizonFade + } + } Technique PostShadow { VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLightingGBufferPack.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLightingGBufferPack.frag new file mode 100644 index 0000000000..0ed0aa3cd3 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLightingGBufferPack.frag @@ -0,0 +1,258 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/PBR.glsllib" +#import "Common/ShaderLib/Parallax.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" + +// shading model +#import "Common/ShaderLib/ShadingModel.glsllib" +// octahedral +#import "Common/ShaderLib/Octahedral.glsllib" + +varying vec2 texCoord; +#ifdef SEPARATE_TEXCOORD + varying vec2 texCoord2; +#endif + +varying vec4 Color; + +uniform vec3 g_CameraPosition; + +uniform float m_Roughness; +uniform float m_Metallic; + +varying vec3 wPosition; + + +#if NB_PROBES >= 1 + uniform samplerCube g_PrefEnvMap; + uniform vec3 g_ShCoeffs[9]; + uniform mat4 g_LightProbeData; +#endif +#if NB_PROBES >= 2 + uniform samplerCube g_PrefEnvMap2; + uniform vec3 g_ShCoeffs2[9]; + uniform mat4 g_LightProbeData2; +#endif +#if NB_PROBES == 3 + uniform samplerCube g_PrefEnvMap3; + uniform vec3 g_ShCoeffs3[9]; + uniform mat4 g_LightProbeData3; +#endif + +#ifdef BASECOLORMAP + uniform sampler2D m_BaseColorMap; +#endif + +#ifdef USE_PACKED_MR + uniform sampler2D m_MetallicRoughnessMap; +#else + #ifdef METALLICMAP + uniform sampler2D m_MetallicMap; + #endif + #ifdef ROUGHNESSMAP + uniform sampler2D m_RoughnessMap; + #endif +#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 vec4 m_Specular; + uniform float m_Glossiness; + #ifdef USE_PACKED_SG + uniform sampler2D m_SpecularGlossinessMap; + #else + uniform sampler2D m_SpecularMap; + uniform sampler2D m_GlossinessMap; + #endif +#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); + + vec3 norm = normalize(wNormal); + #if defined(NORMALMAP) || defined(PARALLAXMAP) + vec3 tan = normalize(wTangent.xyz); + mat3 tbnMat = mat3(tan, wTangent.w * cross( (norm), (tan)), norm); + #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) * Color; + #else + vec4 albedo = Color; + #endif + + //ao in r channel, roughness in green channel, metallic in blue channel! + vec3 aoRoughnessMetallicValue = vec3(1.0, 1.0, 0.0); + #ifdef USE_PACKED_MR + aoRoughnessMetallicValue = texture2D(m_MetallicRoughnessMap, newTexCoord).rgb; + float Roughness = aoRoughnessMetallicValue.g * max(m_Roughness, 1e-4); + float Metallic = aoRoughnessMetallicValue.b * max(m_Metallic, 0.0); + #else + #ifdef ROUGHNESSMAP + float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-4); + #else + float Roughness = max(m_Roughness, 1e-4); + #endif + #ifdef METALLICMAP + float Metallic = texture2D(m_MetallicMap, newTexCoord).r * max(m_Metallic, 0.0); + #else + float Metallic = max(m_Metallic, 0.0); + #endif + #endif + + float alpha = 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 compliant 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, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0))); + normal = normalize(tbnMat * normal); + //normal = normalize(normal * inverse(tbnMat)); + #else + vec3 normal = norm; + #endif + + #ifdef SPECGLOSSPIPELINE + + #ifdef USE_PACKED_SG + vec4 specularColor = texture2D(m_SpecularGlossinessMap, newTexCoord); + float glossiness = specularColor.a * m_Glossiness; + specularColor *= m_Specular; + #else + #ifdef SPECULARMAP + vec4 specularColor = texture2D(m_SpecularMap, newTexCoord); + #else + vec4 specularColor = vec4(1.0); + #endif + #ifdef GLOSSINESSMAP + float glossiness = texture2D(m_GlossinessMap, newTexCoord).r * m_Glossiness; + #else + float glossiness = m_Glossiness; + #endif + specularColor *= m_Specular; + #endif + vec4 diffuseColor = albedo;// * (1.0 - max(max(specularColor.r, specularColor.g), specularColor.b)); + Roughness = 1.0 - glossiness; + vec3 fZero = specularColor.xyz; + #else + float specular = 0.5; + float nonMetalSpec = 0.08 * specular; + vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic; + vec4 diffuseColor = albedo - albedo * Metallic; + vec3 fZero = vec3(specular); + #endif + + vec3 ao = vec3(1.0); + + #ifdef LIGHTMAP + vec3 lightMapColor; + #ifdef SEPARATE_TEXCOORD + lightMapColor = texture2D(m_LightMap, texCoord2).rgb; + #else + lightMapColor = texture2D(m_LightMap, texCoord).rgb; + #endif + #ifdef AO_MAP + lightMapColor.gb = lightMapColor.rr; + ao = lightMapColor; + #else + diffuseColor.rgb *= lightMapColor; + #endif + specularColor.rgb *= lightMapColor; + #endif + + #if defined(AO_PACKED_IN_MR_MAP) && defined(USE_PACKED_MR) + ao = aoRoughnessMetallicValue.rrr; + #endif + + //float ndotv = max( dot( normal, viewDir ),0.0); + + #if defined(EMISSIVE) || defined (EMISSIVEMAP) + #ifdef EMISSIVEMAP + vec4 emissive = texture2D(m_EmissiveMap, newTexCoord); + #else + vec4 emissive = m_Emissive; + #endif + outGBuffer2.rgb = (emissive * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity).rgb; + #endif + // pack + vec2 n1 = octEncode(normal); + vec2 n2 = octEncode(norm); + outGBuffer3.xy = n1; + outGBuffer3.zw = n2; + outGBuffer0.rgb = floor(diffuseColor.rgb * 100.0f) + ao * 0.1f; + outGBuffer1.rgb = floor(specularColor.rgb * 100.0f) + fZero * 0.1f; + outGBuffer1.a = Roughness; + outGBuffer0.a = alpha; + + // shading model id + outGBuffer2.a = PBR_LIGHTING + 0.01f; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLightingGBufferPack.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLightingGBufferPack.vert new file mode 100644 index 0000000000..b910a8d4b2 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLightingGBufferPack.vert @@ -0,0 +1,74 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Instancing.glsllib" +#import "Common/ShaderLib/Skinning.glsllib" +#import "Common/ShaderLib/MorphAnim.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_MORPH_TARGETS + #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) + Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); + #else + Morph_Compute(modelSpacePos, modelSpaceNorm); + #endif + #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 +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md index 86b1d8e212..87a294ef17 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md @@ -5,6 +5,13 @@ MaterialDef Colored Textured { Int BoundDrawBuffer Texture2D ColorMap Color Color (Color) + + // Context GBuffer Data + Texture2D Context_InGBuff0 + Texture2D Context_InGBuff1 + Texture2D Context_InGBuff2 + Texture2D Context_InGBuff3 + Texture2D Context_InGBuff4 } Technique { @@ -19,5 +26,17 @@ MaterialDef Colored Textured { } } + Technique GBufferPass{ + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/ColoredTextured.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/ColoredTexturedGBufferPack.frag + + WorldParameters { + WorldViewProjectionMatrix + } + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } + } + } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTexturedGBufferPack.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTexturedGBufferPack.frag new file mode 100644 index 0000000000..bc0d0f7567 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTexturedGBufferPack.frag @@ -0,0 +1,19 @@ +#import "Common/ShaderLib/Deferred.glsllib" +// shading model +#import "Common/ShaderLib/ShadingModel.glsllib" + +varying vec2 texCoord; + +uniform sampler2D m_ColorMap; +uniform vec4 m_Color; + +void main(){ + vec4 texColor = texture2D(m_ColorMap, texCoord); + vec4 color = vec4(mix(m_Color.rgb, texColor.rgb, texColor.a), 1.0); + + + Context_OutGBuff2.rgb = color.rgb; + + // shading model id + Context_OutGBuff2.a = UNLIT + color.a * 0.1f; +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Null.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/Null.frag new file mode 100644 index 0000000000..face67a151 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Null.frag @@ -0,0 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + +void main() { + discard; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Null.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Null.j3md new file mode 100644 index 0000000000..362a967547 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Null.j3md @@ -0,0 +1,18 @@ +MaterialDef Null { + + MaterialParameters { + } + + Technique { + + VertexShader GLSL310 GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/Null.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/Null.frag + + WorldParameters { + } + + Defines { + } + + } +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Null.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/Null.vert new file mode 100644 index 0000000000..6cfed024dc --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Null.vert @@ -0,0 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + +void main() { + gl_Position = vec4(0.0); +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Screen.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/Screen.frag new file mode 100644 index 0000000000..5740c7370a --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Screen.frag @@ -0,0 +1,21 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" + +#ifdef MAP + uniform sampler2D m_ColorMap; +#else + uniform vec4 m_Color; +#endif + +varying vec2 texCoord; + +void main(){ + if (texCoord.y > 0.5) { + discard; + } + #ifdef MAP + gl_FragColor = texture2D(m_ColorMap, texCoord); + #else + gl_FragColor = m_Color; + #endif +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Screen.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Screen.j3md new file mode 100644 index 0000000000..51ffe4907d --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Screen.j3md @@ -0,0 +1,18 @@ +MaterialDef Screen { + + MaterialParameters { + Texture2D ColorMap + Color Color : 1.0 1.0 1.0 1.0 + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/Screen.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/Screen.frag + WorldParameters { + } + Defines { + MAP : ColorMap + } + } + +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Screen.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/Screen.vert new file mode 100644 index 0000000000..6bc2540dae --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Screen.vert @@ -0,0 +1,13 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + +attribute vec3 inPosition; +attribute vec2 inTexCoord; + +varying vec2 texCoord; + +void main(){ + + texCoord = inTexCoord; + gl_Position = vec4(sign(inPosition.xy-vec2(0.5)), 0.0, 1.0); + +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md index b5854c2a23..d0066f23de 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md @@ -92,6 +92,34 @@ MaterialDef Unshaded { } } + Technique GBufferPass { + + VertexShader GLSL310 GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/UnshadedGBufferPack.frag + + WorldParameters { + WorldViewProjectionMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + INSTANCING : UseInstancing + SEPARATE_TEXCOORD : SeparateTexCoord + HAS_COLORMAP : ColorMap + HAS_LIGHTMAP : LightMap + HAS_VERTEXCOLOR : VertexColor + HAS_POINTSIZE : PointSize + HAS_COLOR : Color + NUM_BONES : NumberOfBones + DISCARD_ALPHA : AlphaDiscardThreshold + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + DESATURATION : DesaturationValue + } + } + Technique PreNormalPass { VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/SSAO/normal.vert diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedGBufferPack.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedGBufferPack.frag new file mode 100644 index 0000000000..3bda12aec3 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/UnshadedGBufferPack.frag @@ -0,0 +1,65 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" +// shading model +#import "Common/ShaderLib/ShadingModel.glsllib" + +#if defined(HAS_GLOWMAP) || defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD)) + #define NEED_TEXCOORD1 +#endif + +#if defined(DISCARD_ALPHA) + uniform float m_AlphaDiscardThreshold; +#endif + +uniform vec4 m_Color; +uniform sampler2D m_ColorMap; +uniform sampler2D m_LightMap; + +#ifdef DESATURATION + uniform float m_DesaturationValue; +#endif + +varying vec2 texCoord1; +varying vec2 texCoord2; + +varying vec4 vertColor; + +void main(){ + vec4 color = vec4(1.0); + + #ifdef HAS_COLORMAP + color *= texture2D(m_ColorMap, texCoord1); + #endif + + #ifdef HAS_VERTEXCOLOR + color *= vertColor; + #endif + + #ifdef HAS_COLOR + color *= m_Color; + #endif + + #ifdef HAS_LIGHTMAP + #ifdef SEPARATE_TEXCOORD + color.rgb *= texture2D(m_LightMap, texCoord2).rgb; + #else + color.rgb *= texture2D(m_LightMap, texCoord1).rgb; + #endif + #endif + + #if defined(DISCARD_ALPHA) + if(color.a < m_AlphaDiscardThreshold){ + discard; + } + #endif + + #ifdef DESATURATION + vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), color.rgb)); + color.rgb = vec3(mix(color.rgb, gray, m_DesaturationValue)); + #endif + + outGBuffer2.rgb = color.rgb; + + // shading model id + outGBuffer2.a = UNLIT + color.a * 0.1f; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.frag b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.frag new file mode 100644 index 0000000000..d0e8d92911 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.frag @@ -0,0 +1,300 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" +#import "Common/ShaderLib/Parallax.glsllib" +#import "Common/ShaderLib/Optics.glsllib" +#import "Common/ShaderLib/BlinnPhongLighting.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" +#import "Common/ShaderLib/PBR.glsllib" +#import "Common/ShaderLib/ShadingModel.glsllib" +// octahedral +#import "Common/ShaderLib/Octahedral.glsllib" +// skyLight and reflectionProbe +uniform vec4 g_AmbientLightColor; +#import "Common/ShaderLib/SkyLightReflectionProbe.glsllib" +//#ifdef USE_LIGHT_TEXTURES +// uniform vec2 g_ResolutionInverse; +//#else + varying vec2 texCoord; +//#endif + +varying mat4 viewProjectionMatrixInverse; +uniform mat4 g_ViewMatrix; +uniform vec3 g_CameraPosition; +uniform int m_NBLights; + +#if NB_LIGHTS > 0 + #ifdef USE_LIGHT_TEXTURES + uniform sampler2D m_LightTex1; + uniform sampler2D m_LightTex2; + uniform sampler2D m_LightTex3; + uniform float m_LightTexInv; + #ifdef TILED_LIGHTS + uniform sampler2D m_Tiles; + uniform sampler2D m_LightIndex; + uniform vec3 m_LightIndexSize; // x=width, yz=size inverse + #define TILES true + #endif + #else + uniform vec4 g_LightData[NB_LIGHTS]; + #endif +#endif + + +void main(){ + //#ifdef USE_LIGHT_TEXTURES + // vec2 innerTexCoord = gl_FragCoord.xy * g_ResolutionInverse; + //#else + vec2 innerTexCoord = texCoord; + //#endif + // unpack m_GBuffer + vec4 shadingInfo = texture2D(m_GBuffer2, innerTexCoord); + int shadingModelId = int(floor(shadingInfo.a)); + float depth = texture2D(m_GBuffer4, innerTexCoord).r; + gl_FragDepth = depth; + // Due to GPU architecture, each shading mode is performed for each pixel, which is very inefficient. + // TODO: Remove these if statements if possible. + if (shadingModelId == PHONG_LIGHTING) { + + // phong shading + vec3 vPos = getPosition(innerTexCoord, depth, viewProjectionMatrixInverse); + vec4 buff1 = texture2D(m_GBuffer1, innerTexCoord); + vec4 diffuseColor = texture2D(m_GBuffer0, innerTexCoord); + vec3 specularColor = floor(buff1.rgb) * 0.01; + vec3 AmbientSum = min(fract(buff1.rgb) * 100.0, vec3(1.0)) * g_AmbientLightColor.rgb; + float shininess = buff1.a; + float alpha = diffuseColor.a; + vec3 normal = texture2D(m_GBuffer3, innerTexCoord).xyz; + vec3 viewDir = normalize(g_CameraPosition - vPos); + gl_FragColor.rgb = AmbientSum * diffuseColor.rgb; + //gl_FragColor.a = alpha; + gl_FragColor.a = 1.0; + #if NB_LIGHTS > 0 + #ifdef TILES + // fetch index info from tile this fragment is contained by + vec4 tileInfo = texture2D(m_Tiles, innerTexCoord); + int x = int(tileInfo.x); + int y = int(tileInfo.y); + int componentIndex = int(tileInfo.z); + int lightCount = int(tileInfo.w); + vec4 lightIndex = vec4(-1.0); + for (int i = 0; i < lightCount;) { + #else + for (int i = 0; i < NB_LIGHTS;) { + #endif + #ifdef USE_LIGHT_TEXTURES + #ifdef TILED_LIGHTS + if (componentIndex == 0 || lightIndex.x < 0) { + // get indices from next pixel + lightIndex = texture2D(m_LightIndex, vec2(x, y) * m_LightIndexSize.yz); + } + // apply index from each component in order + vec2 pixel = vec2(m_LightTexInv, 0); + switch (componentIndex) { + case 0: pixel.x *= lightIndex.x; break; + case 1: pixel.x *= lightIndex.y; break; + case 2: pixel.x *= lightIndex.z; break; + case 3: pixel.x *= lightIndex.w; break; + } + // increment indices + componentIndex++; + if (componentIndex > 3) { + componentIndex = 0; + x++; + if (x >= m_LightIndexSize.x) { + x = 0; + y++; + } + } + #else + vec2 pixel = vec2(m_LightTexInv * i, 0); + #endif + vec4 lightColor = texture2D(m_LightTex1, pixel); + vec4 lightData1 = texture2D(m_LightTex2, pixel); + #else + vec4 lightColor = g_LightData[i]; + vec4 lightData1 = g_LightData[i+1]; + #endif + vec4 lightDir; + vec3 lightVec; + lightComputeDir(vPos, lightColor.w, lightData1, lightDir, lightVec); + + float spotFallOff = 1.0; + #if __VERSION__ >= 110 + // allow use of control flow + if (lightColor.w > 1.0) { + #endif + #ifdef USE_LIGHT_TEXTURES + spotFallOff = computeSpotFalloff(texture2D(m_LightTex3, pixel), lightVec); + #else + spotFallOff = computeSpotFalloff(g_LightData[i+2], lightVec); + #endif + #if __VERSION__ >= 110 + } + #endif + + #ifdef NORMALMAP + // Normal map -> lighting is computed in tangent space + lightDir.xyz = normalize(lightDir.xyz * tbnMat); + #else + // no Normal map -> lighting is computed in view space + lightDir.xyz = normalize(lightDir.xyz); + #endif + + vec2 light = computeLighting(normal, viewDir, lightDir.xyz, + lightDir.w * spotFallOff, shininess); + + gl_FragColor.rgb += lightColor.rgb * diffuseColor.rgb * vec3(light.x) + + lightColor.rgb * specularColor.rgb * vec3(light.y); + + #ifdef USE_LIGHT_TEXTURES + i++; + #else + i += 3; + #endif + } + #endif + + //gl_FragColor.rgb = AmbientSum; + + } else if (shadingModelId == PBR_LIGHTING) { + + // PBR shading + vec3 vPos = getPosition(innerTexCoord, viewProjectionMatrixInverse); + vec4 buff0 = texture2D(m_GBuffer0, innerTexCoord); + vec4 buff1 = texture2D(m_GBuffer1, innerTexCoord); + vec3 emissive = shadingInfo.rgb; + vec3 diffuseColor = floor(buff0.rgb) * 0.01f; + vec3 specularColor = floor(buff1.rgb) * 0.01f; + vec3 ao = min(fract(buff0.rgb) * 10.0f, vec3(1.0f)); + vec3 fZero = min(fract(buff1.rgb) * 10.0f, vec3(0.5f)); + float Roughness = buff1.a; + float indoorSunLightExposure = fract(shadingInfo.a) * 100.0f; + float alpha = buff0.a; + vec4 n1n2 = texture2D(m_GBuffer3, innerTexCoord); + vec3 normal = octDecode(n1n2.xy); + vec3 norm = octDecode(n1n2.zw); + vec3 viewDir = normalize(g_CameraPosition - vPos); + + float ndotv = max( dot( normal, viewDir ),0.0); + gl_FragColor.rgb = vec3(0.0); + #if NB_LIGHTS > 0 + #ifdef TILES + // fetch index info from tile this fragment is contained by + vec4 tileInfo = texture2D(m_Tiles, innerTexCoord); + int x = int(tileInfo.x); + int y = int(tileInfo.y); + int componentIndex = int(tileInfo.z); + int lightCount = int(tileInfo.w); + vec4 lightIndex = vec4(-1.0); + for (int i = 0; i < lightCount;) { + #else + for (int i = 0; i < NB_LIGHTS;) { + #endif + #ifdef USE_LIGHT_TEXTURES + #ifdef TILED_LIGHTS + if (componentIndex == 0 || lightIndex.x < 0) { + // get indices from next pixel + lightIndex = texture2D(m_LightIndex, vec2(x, y) * m_LightIndexSize.yz); + } + // apply index from each component in order + vec2 pixel = vec2(m_LightTexInv, 0); + switch (componentIndex) { + case 0: pixel.x *= lightIndex.x; break; + case 1: pixel.x *= lightIndex.y; break; + case 2: pixel.x *= lightIndex.z; break; + case 3: pixel.x *= lightIndex.w; break; + } + // increment indices + componentIndex++; + if (componentIndex > 3) { + componentIndex = 0; + x++; + if (x >= m_LightIndexSize.x) { + x = 0; + y++; + } + } + #else + vec2 pixel = vec2(m_LightTexInv * i, 0); + #endif + vec4 lightColor = texture2D(m_LightTex1, pixel); + vec4 lightData1 = texture2D(m_LightTex2, pixel); + #else + vec4 lightColor = g_LightData[i]; + vec4 lightData1 = g_LightData[i+1]; + #endif + + vec4 lightDir; + vec3 lightVec; + lightComputeDir(vPos, lightColor.w, lightData1, lightDir, lightVec); + + float spotFallOff = 1.0; + #if __VERSION__ >= 110 + // allow use of control flow + if (lightColor.w > 1.0) { + #endif + #if USE_LIGHT_TEXTURES + spotFallOff = computeSpotFalloff(texture2D(m_LightTex3, pixel), lightVec); + #else + spotFallOff = computeSpotFalloff(g_LightData[i+2], lightVec); + #endif + #if __VERSION__ >= 110 + } + #endif + spotFallOff *= lightDir.w; + + float radius = 1.0 / lightData1.w; + if (distance(vPos, lightData1.xyz) < radius) { + //gl_FragColor.rgb += lightColor.rgb * spotFallOff; + } + + #ifdef NORMALMAP + //Normal map -> lighting is computed in tangent space + lightDir.xyz = normalize(lightDir.xyz * tbnMat); + #else + //no Normal map -> lighting is computed in view space + lightDir.xyz = normalize(lightDir.xyz); + #endif + + vec3 directDiffuse; + vec3 directSpecular; + + float hdotv = PBR_ComputeDirectLight(normal, lightDir.xyz, viewDir, + lightColor.rgb, fZero, Roughness, ndotv, + directDiffuse, directSpecular); + + vec3 directLighting = diffuseColor.rgb * directDiffuse + directSpecular; + gl_FragColor.rgb += directLighting * spotFallOff; + + //gl_FragColor.rgb += directSpecular; + + #if USE_LIGHT_TEXTURES + i++; + #else + i += 3; + #endif + } + #endif + // skyLight and reflectionProbe + vec3 skyLight = renderSkyLightAndReflectionProbes( + indoorSunLightExposure, viewDir, vPos, normal, norm, + Roughness, diffuseColor, specularColor, ndotv, ao); + gl_FragColor.rgb += skyLight; + gl_FragColor.rgb += emissive; + //gl_FragColor.a = alpha; + gl_FragColor.a = 1.0; + } else if (shadingModelId == SUBSURFACE_SCATTERING) { + // TODO: implement subsurface scattering + } else if (shadingModelId == UNLIT) { + + // unlit shading + gl_FragColor.rgb = shadingInfo.rgb; + gl_FragColor.a = min(fract(shadingInfo.a) * 10.0f, 0.0f); + + } else { + discard; + } + //gl_FragColor.r = gl_FragColor.a; + //gl_FragColor.a = 0.5; + //gl_FragColor.a = 0.01; +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.j3md b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.j3md new file mode 100644 index 0000000000..e5c26785bb --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.j3md @@ -0,0 +1,40 @@ +MaterialDef DeferredShading { + + MaterialParameters { + + // GBuffer Data + Texture2D GBuffer0 + Texture2D GBuffer1 + Texture2D GBuffer2 + Texture2D GBuffer3 + Texture2D GBuffer4 + + // LightData + Texture2D LightTex1 + Texture2D LightTex2 + Texture2D LightTex3 + Int LightTexSize + Vector2 LightTexInv + + // Tiles + Texture2D Tiles + Texture2D LightIndex + + } + + Technique DeferredPass { + + VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/ShadingCommon/DeferredShading.vert + FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/ShadingCommon/DeferredShading.frag + + WorldParameters { + CameraPosition + ViewProjectionMatrixInverse + WorldViewProjectionMatrix + ViewProjectionMatrix + ResolutionInverse + } + + } + +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.vert b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.vert new file mode 100644 index 0000000000..5e23007ec2 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.vert @@ -0,0 +1,24 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Instancing.glsllib" + +attribute vec3 inPosition; + +#ifndef USE_LIGHTS_CULL_MODE + varying vec2 texCoord; +#endif +varying mat4 viewProjectionMatrixInverse; + +void main(){ + + #ifndef USE_LIGHTS_CULL_MODE + texCoord = inPosition.xy; + gl_Position = vec4(sign(inPosition.xy - vec2(0.5)), 0.0, 1.0); + #else + gl_Position = TransformWorldViewProjection(vec4(inPosition, 1.0)); + #endif + + viewProjectionMatrixInverse = GetViewProjectionMatrixInverse(); + +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/Screen.vert b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/Screen.vert new file mode 100644 index 0000000000..651b349ab7 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/Screen.vert @@ -0,0 +1,14 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" + +attribute vec3 inPosition; +varying vec2 texCoord; + +const vec2 sub = vec2(1.0); + +void main() { + texCoord = inPosition.xy; + vec2 pos = inPosition.xy * 2.0 - sub; + gl_Position = vec4(pos, 0.0, 1.0); +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TextureTransfer.frag b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TextureTransfer.frag new file mode 100644 index 0000000000..b70d3dc4af --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TextureTransfer.frag @@ -0,0 +1,43 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" + +#ifdef WRITE_COLOR_MAP + uniform sampler2D m_ColorMap; + #ifdef ALPHA_DISCARD + uniform float m_AlphaDiscard; + #endif +#endif +#ifdef WRITE_DEPTH + uniform sampler2D m_DepthMap; +#endif + +varying vec2 texCoord; + +void main() { + + #ifdef WRITE_COLOR_MAP + gl_FragColor = texture2D(m_ColorMap, texCoord); + #ifdef ALPHA_DISCARD + if (gl_FragColor.a <= m_AlphaDiscard) { + //gl_FragColor = vec4(1.0); + discard; + } + #endif + if (gl_FragColor.rgb == vec3(0.0)) { + //gl_FragColor.rgb = vec3(1.0, 0.0, 0.0); + } + #ifdef DEBUG + gl_FragColor.rgba = vec4(gl_FragColor.a, 0.0, 0.0, 1.0); + #endif + #else + gl_FragColor = vec4(0.0); + #endif + + #ifdef WRITE_DEPTH + gl_FragDepth = texture2D(m_DepthMap, texCoord).r; + #else + gl_FragDepth = 1.0; + #endif + +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TextureTransfer.j3md b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TextureTransfer.j3md new file mode 100644 index 0000000000..4339d99e6c --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TextureTransfer.j3md @@ -0,0 +1,32 @@ +MaterialDef TextureTransfer { + + MaterialParameters { + Texture2D ColorMap + Texture2D DepthMap + Vector2 Scale : 1.0 1.0 + Float AlphaDiscard + Boolean Debug + } + + Technique { + + VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/ShadingCommon/Screen.vert + FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/ShadingCommon/TextureTransfer.frag + + WorldParameters { + CameraPosition + ViewProjectionMatrixInverse + WorldViewProjectionMatrix + ViewProjectionMatrix + ResolutionInverse + } + + Defines { + WRITE_COLOR_MAP : ColorMap + WRITE_DEPTH : DepthMap + ALPHA_DISCARD : AlphaDiscard + DEBUG : Debug + } + } + +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TileBasedDeferredShading.frag b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TileBasedDeferredShading.frag new file mode 100644 index 0000000000..4cfd74b1cc --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TileBasedDeferredShading.frag @@ -0,0 +1,375 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" +#import "Common/ShaderLib/Parallax.glsllib" +#import "Common/ShaderLib/Optics.glsllib" +#import "Common/ShaderLib/BlinnPhongLighting.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" +#import "Common/ShaderLib/PBR.glsllib" +#import "Common/ShaderLib/ShadingModel.glsllib" +// octahedral +#import "Common/ShaderLib/Octahedral.glsllib" +// skyLight and reflectionProbe +uniform vec4 g_AmbientLightColor; +#import "Common/ShaderLib/SkyLightReflectionProbe.glsllib" +#if defined(USE_LIGHTS_CULL_MODE) + uniform vec2 g_ResolutionInverse; +#else + varying vec2 texCoord; +#endif + +varying mat4 viewProjectionMatrixInverse; +uniform mat4 g_ViewMatrix; +uniform vec3 g_CameraPosition; +uniform int m_NBLight; + + + +#if defined(USE_TEXTURE_PACK_MODE) + uniform int g_LightCount; + uniform sampler2D m_LightPackData1; + uniform sampler2D m_LightPackData2; + uniform sampler2D m_LightPackData3; +#else + uniform vec4 g_LightData[NB_LIGHTS]; +#endif + +uniform vec2 g_Resolution; +uniform int g_TileSize; +uniform int g_WidthTile; +uniform int g_HeightTile; +uniform int g_TileLightOffsetSize; +uniform sampler2D m_TileLightDecode; +uniform sampler2D m_TileLightIndex; + +void main(){ + vec2 innerTexCoord; +#if defined(USE_LIGHTS_CULL_MODE) + innerTexCoord = gl_FragCoord.xy * g_ResolutionInverse; +#else + innerTexCoord = texCoord; +#endif + // unpack GBuffer + vec4 shadingInfo = texture2D(Context_InGBuff2, innerTexCoord); + int shadingModelId = int(floor(shadingInfo.a)); + float depth = texture2D(GBUFFER_DEPTH, texCoord).r; + gl_FragDepth = depth; + // Perform corresponding pixel shading based on the shading model + if (IS_LIT(shadingModelId)) { + // lit shading model + // todo:For now, use the big branch first, and extract the common parts later + if (shadingModelId == LEGACY_LIGHTING) { + vec3 vPos = getPosition(innerTexCoord, depth, viewProjectionMatrixInverse); + vec4 buff1 = texture2D(Context_InGBuff1, innerTexCoord); + vec4 diffuseColor = texture2D(Context_InGBuff0, innerTexCoord); + vec3 specularColor = floor(buff1.rgb) * 0.01f; + vec3 AmbientSum = min(fract(buff1.rgb) * 100.0f, vec3(1.0f)) * g_AmbientLightColor.rgb; + float Shininess = buff1.a; + float alpha = diffuseColor.a; + vec3 normal = texture2D(Context_InGBuff3, innerTexCoord).xyz; + vec3 viewDir = normalize(g_CameraPosition - vPos); + + + gl_FragColor.rgb = AmbientSum * diffuseColor.rgb; + gl_FragColor.a = alpha; + int lightNum = 0; + #if defined(USE_TEXTURE_PACK_MODE) + float lightTexSizeInv = 1.0f / (float(PACK_NB_LIGHTS) - 1.0f); + lightNum = m_NBLight; + #else + lightNum = NB_LIGHTS; + float lightTexSizeInv = 1.0f / (float(lightNum) - 1.0f); + #endif + + // Tile Based Shading + // get the grid data index + vec2 gridIndex = vec2(((innerTexCoord.x*g_Resolution.x) / float(g_TileSize)) / float(g_WidthTile), ((innerTexCoord.y*g_Resolution.y) / float(g_TileSize)) / float(g_HeightTile)); + // get tile info + vec3 tile = texture2D(m_TileLightDecode, gridIndex).xyz; + int uoffset = int(tile.x); + int voffset = int(tile.z); + int count = int(tile.y); + if(count > 0){ + int lightId; + float temp; + int offset; + // Normalize lightIndex sampling range to unit space + float uvSize = 1.0f / (g_TileLightOffsetSize - 1.0f); + vec2 lightUV; + vec2 lightDataUV; + for(int i = 0;i < count;){ + temp = float(uoffset + i); + offset = 0; + + if(temp >= g_TileLightOffsetSize){ + //temp -= g_TileLightOffsetSize; + offset += int(temp / float(g_TileLightOffsetSize)); + temp = float(int(temp) % g_TileLightOffsetSize); + } + if(temp == g_TileLightOffsetSize){ + temp = 0.0f; + } + + // lightIndexUV + lightUV = vec2(temp * uvSize, float(voffset + offset) * uvSize); + lightId = int(texture2D(m_TileLightIndex, lightUV).x); + lightDataUV = vec2(float(lightId) * lightTexSizeInv, 0.0f); + + #if defined(USE_TEXTURE_PACK_MODE) + vec4 lightColor = texture2D(m_LightPackData1, lightDataUV); + vec4 lightData1 = texture2D(m_LightPackData2, lightDataUV); + #else + vec4 lightColor = g_LightData[lightId*3]; + vec4 lightData1 = g_LightData[lightId*3+1]; + #endif + vec4 lightDir; + vec3 lightVec; + lightComputeDir(vPos, lightColor.w, lightData1, lightDir,lightVec); + + float spotFallOff = 1.0; + #if __VERSION__ >= 110 + // allow use of control flow + if(lightColor.w > 1.0){ + #endif + #if defined(USE_TEXTURE_PACK_MODE) + spotFallOff = computeSpotFalloff(texture2D(m_LightPackData3, lightDataUV), lightVec); + #else + spotFallOff = computeSpotFalloff(g_LightData[lightId*3+2], lightVec); + #endif + #if __VERSION__ >= 110 + } + #endif + + #ifdef NORMALMAP + //Normal map -> lighting is computed in tangent space + lightDir.xyz = normalize(lightDir.xyz * tbnMat); + #else + //no Normal map -> lighting is computed in view space + lightDir.xyz = normalize(lightDir.xyz); + #endif + + vec2 light = computeLighting(normal, viewDir, lightDir.xyz, lightDir.w * spotFallOff , Shininess); + + // Workaround, since it is not possible to modify varying variables + // #ifdef USE_REFLECTION + // // Interpolate light specularity toward reflection color + // // Multiply result by specular map + // specularColor = mix(specularColor * light.y, refColor, refVec.w) * specularColor; + // light.y = 1.0; + // #endif + // + // #ifdef COLORRAMP + // diffuseColor.rgb *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb; + // specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb; + // light.xy = vec2(1.0); + // #endif + + gl_FragColor.rgb += lightColor.rgb * diffuseColor.rgb * vec3(light.x) + + lightColor.rgb * specularColor.rgb * vec3(light.y); + #if defined(USE_TEXTURE_PACK_MODE) + i++; + #else + i++; + #endif + } + } +// for( int i = 0;i < lightNum; ){ +// #if defined(USE_TEXTURE_PACK_MODE) +// vec4 lightColor = texture2D(m_LightPackData1, vec2(i * lightTexSizeInv, 0)); +// vec4 lightData1 = texture2D(m_LightPackData2, vec2(i * lightTexSizeInv, 0)); +// #else +// vec4 lightColor = g_LightData[i]; +// vec4 lightData1 = g_LightData[i+1]; +// #endif +// vec4 lightDir; +// vec3 lightVec; +// lightComputeDir(vPos, lightColor.w, lightData1, lightDir,lightVec); +// +// float spotFallOff = 1.0; +// #if __VERSION__ >= 110 +// // allow use of control flow +// if(lightColor.w > 1.0){ +// #endif +// #if defined(USE_TEXTURE_PACK_MODE) +// spotFallOff = computeSpotFalloff(texture2D(m_LightPackData3, vec2(i * lightTexSizeInv, 0)), lightVec); +// #else +// spotFallOff = computeSpotFalloff(g_LightData[i+2], lightVec); +// #endif +// #if __VERSION__ >= 110 +// } +// #endif +// +// #ifdef NORMALMAP +// //Normal map -> lighting is computed in tangent space +// lightDir.xyz = normalize(lightDir.xyz * tbnMat); +// #else +// //no Normal map -> lighting is computed in view space +// lightDir.xyz = normalize(lightDir.xyz); +// #endif +// +// vec2 light = computeLighting(normal, viewDir, lightDir.xyz, lightDir.w * spotFallOff , Shininess); +// +// // Workaround, since it is not possible to modify varying variables +// // #ifdef USE_REFLECTION +// // // Interpolate light specularity toward reflection color +// // // Multiply result by specular map +// // specularColor = mix(specularColor * light.y, refColor, refVec.w) * specularColor; +// // light.y = 1.0; +// // #endif +// // +// // #ifdef COLORRAMP +// // diffuseColor.rgb *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb; +// // specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb; +// // light.xy = vec2(1.0); +// // #endif +// +// gl_FragColor.rgb += lightColor.rgb * diffuseColor.rgb * vec3(light.x) + +// lightColor.rgb * specularColor.rgb * vec3(light.y); +// #if defined(USE_TEXTURE_PACK_MODE) +// i++; +// #else +// i+=3; +// #endif +// } + } else if (shadingModelId == STANDARD_LIGHTING) { + // todo: + vec3 vPos = getPosition(innerTexCoord, depth, viewProjectionMatrixInverse); + vec4 buff0 = texture2D(Context_InGBuff0, innerTexCoord); + vec4 buff1 = texture2D(Context_InGBuff1, innerTexCoord); + vec3 emissive = shadingInfo.rgb; + vec3 diffuseColor = floor(buff0.rgb) * 0.01f; + vec3 specularColor = floor(buff1.rgb) * 0.01f; + vec3 ao = min(fract(buff0.rgb) * 10.0f, vec3(1.0f)); + vec3 fZero = min(fract(buff1.rgb) * 10.0f, vec3(0.5f)); + float Roughness = buff1.a; + float indoorSunLightExposure = fract(shadingInfo.a) * 100.0f; + float alpha = buff0.a; + vec4 n1n2 = texture2D(Context_InGBuff3, innerTexCoord); + vec3 normal = octDecode(n1n2.xy); + vec3 norm = octDecode(n1n2.zw); + vec3 viewDir = normalize(g_CameraPosition - vPos); + + float ndotv = max( dot( normal, viewDir ),0.0); + int lightNum = 0; + #if defined(USE_TEXTURE_PACK_MODE) + float lightTexSizeInv = 1.0f / (float(PACK_NB_LIGHTS) - 1.0f); + lightNum = m_NBLight; + #else + lightNum = NB_LIGHTS; + float lightTexSizeInv = 1.0f / (float(lightNum) - 1.0f); + #endif + gl_FragColor.rgb = vec3(0.0); + // Tile Based Shading + // get the grid data index + vec2 gridIndex = vec2(((innerTexCoord.x*g_Resolution.x) / float(g_TileSize)) / float(g_WidthTile), ((innerTexCoord.y*g_Resolution.y) / float(g_TileSize)) / float(g_HeightTile)); + // get tile info + vec3 tile = texture2D(m_TileLightDecode, gridIndex).xyz; + int uoffset = int(tile.x); + int voffset = int(tile.z); + int count = int(tile.y); + if(count > 0){ + int lightId; + float temp; + int offset; + // Normalize lightIndex sampling range to unit space + float uvSize = 1.0f / (g_TileLightOffsetSize - 1.0f); + vec2 lightUV; + vec2 lightDataUV; + for (int i = 0;i < count;){ + temp = float(uoffset + i); + offset = 0; + + if(temp >= g_TileLightOffsetSize){ + //temp -= g_TileLightOffsetSize; + offset += int(temp / float(g_TileLightOffsetSize)); + temp = float(int(temp) % g_TileLightOffsetSize); + } + if(temp == g_TileLightOffsetSize){ + temp = 0.0f; + } + + // lightIndexUV + lightUV = vec2(temp * uvSize, float(voffset + offset) * uvSize); + lightId = int(texture2D(m_TileLightIndex, lightUV).x); + lightDataUV = vec2(float(lightId) * lightTexSizeInv, 0.0f); + + #if defined(USE_TEXTURE_PACK_MODE) + vec4 lightColor = texture2D(m_LightPackData1, lightDataUV); + vec4 lightData1 = texture2D(m_LightPackData2, lightDataUV); + #else + vec4 lightColor = g_LightData[lightId*3]; + vec4 lightData1 = g_LightData[lightId*3+1]; + #endif + vec4 lightDir; + vec3 lightVec; + lightComputeDir(vPos, lightColor.w, lightData1, lightDir,lightVec); + + float spotFallOff = 1.0; + #if __VERSION__ >= 110 + // allow use of control flow + if(lightColor.w > 1.0){ + #endif + #if defined(USE_TEXTURE_PACK_MODE) + spotFallOff = computeSpotFalloff(texture2D(m_LightPackData3, lightDataUV), lightVec); + #else + spotFallOff = computeSpotFalloff(g_LightData[lightId*3+2], lightVec); + #endif + #if __VERSION__ >= 110 + } + #endif + spotFallOff *= lightDir.w; + + #ifdef NORMALMAP + //Normal map -> lighting is computed in tangent space + lightDir.xyz = normalize(lightDir.xyz * tbnMat); + #else + //no Normal map -> lighting is computed in view space + lightDir.xyz = normalize(lightDir.xyz); + #endif + + vec3 directDiffuse; + vec3 directSpecular; + + float hdotv = PBR_ComputeDirectLight(normal, lightDir.xyz, viewDir, + lightColor.rgb, fZero, Roughness, ndotv, + directDiffuse, directSpecular); + + vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular; + + // Workaround, since it is not possible to modify varying variables + // #ifdef USE_REFLECTION + // // Interpolate light specularity toward reflection color + // // Multiply result by specular map + // specularColor = mix(specularColor * light.y, refColor, refVec.w) * specularColor; + // light.y = 1.0; + // #endif + // + // #ifdef COLORRAMP + // diffuseColor.rgb *= texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb; + // specularColor.rgb *= texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb; + // light.xy = vec2(1.0); + // #endif + + gl_FragColor.rgb += directLighting * spotFallOff; + i++; + //#if defined(USE_TEXTURE_PACK_MODE) + //i++; + //#else + //i++; + //#endif + } + } + // skyLight and reflectionProbe + vec3 skyLightAndReflection = renderSkyLightAndReflectionProbes(indoorSunLightExposure, viewDir, vPos, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao); + gl_FragColor.rgb += skyLightAndReflection; + gl_FragColor.rgb += emissive; + gl_FragColor.a = alpha; + } else if (shadingModelId == SUBSURFACE_SCATTERING) { + // todo: + } + } else if (shadingModelId == UNLIT) { + gl_FragColor.rgb = shadingInfo.rgb; + gl_FragColor.a = min(fract(shadingInfo.a) * 10.0f, 0.0f); + } else { + discard; + } +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TileBasedDeferredShading.j3md b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TileBasedDeferredShading.j3md new file mode 100644 index 0000000000..07c033ab5e --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TileBasedDeferredShading.j3md @@ -0,0 +1,47 @@ +MaterialDef DeferredShading { + + MaterialParameters { + Int NBLight + // For instancing + Boolean UseInstancing + // UseLightsCull + Boolean UseLightsCullMode + + // Context GBuffer Data + Texture2D Context_InGBuff0 + Texture2D Context_InGBuff1 + Texture2D Context_InGBuff2 + Texture2D Context_InGBuff3 + Texture2D Context_InGBuff4 + + // LightData + Texture2D LightPackData1 + Texture2D LightPackData2 + Texture2D LightPackData3 + + // TileInfo + Texture2D TileLightDecode + Texture2D TileLightIndex + } + + Technique TileBasedDeferredPass { + + VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/ShadingCommon/DeferredShading.vert + FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/ShadingCommon/TileBasedDeferredShading.frag + + WorldParameters { + CameraPosition + ViewProjectionMatrixInverse + WorldViewProjectionMatrix + ViewProjectionMatrix + ResolutionInverse + Resolution + } + + Defines { + INSTANCING : UseInstancing + USE_LIGHTS_CULL_MODE : UseLightsCullMode + } + } + +} diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Deferred.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Deferred.glsllib new file mode 100644 index 0000000000..a3a5a14f4f --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/Deferred.glsllib @@ -0,0 +1,51 @@ +//#ifndef _JME_CONTEXT_ +//#define _JME_CONTEXT_ +#if __VERSION__ >= 120 + layout(location = 0) out vec4 out0; + layout(location = 1) out vec4 out1; + layout(location = 2) out vec4 out2; + layout(location = 3) out vec4 out3; + layout(location = 4) out vec4 out4; + #define outGBuffer0 out0 + #define outGBuffer1 out1 + #define outGBuffer2 out2 + #define outGBuffer3 out3 + #define outGBuffer4 out4 +#else + #define outGBuffer0 gl_FragData[0] + #define outGBuffer1 gl_FragData[1] + #define outGBuffer2 gl_FragData[2] + #define outGBuffer3 gl_FragData[3] + #define outGBuffer4 gl_FragData[4] +#endif +uniform sampler2D m_GBuffer0; +uniform sampler2D m_GBuffer1; +uniform sampler2D m_GBuffer2; +uniform sampler2D m_GBuffer3; +uniform sampler2D m_GBuffer4; +//#endif + +vec3 decodeNormal(in vec4 enc){ + vec4 nn = enc * vec4(2.0,2.0,0.0,0.0) + vec4(-1.0,-1.0,1.0,-1.0); + float l = dot(nn.xyz, -nn.xyw); + nn.z = l; + nn.xy *= sqrt(l); + return nn.xyz * vec3(2.0) + vec3(0.0,0.0,-1.0); +} + +vec3 getPosition(in vec2 texCoord, in float depth, in mat4 matrixInverse){ + vec4 pos = vec4(1.0); + pos.xy = (texCoord * vec2(2.0)) - vec2(1.0); + pos.z = depth * 2.0 - 1.0; + //pos.w = 1.0; + pos = matrixInverse * pos; + pos.xyz /= pos.w; + return pos.xyz; +} + +vec3 getPosition(in vec2 texCoord, in mat4 matrixInverse){ + return getPosition(texCoord, texture2D(m_GBuffer4, texCoord).r, matrixInverse); +} + + + diff --git a/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib index bcd895f60a..9598f71153 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib @@ -1,16 +1,21 @@ -#ifdef GL_ES - #ifdef FRAGMENT_SHADER - precision highp float; - precision highp int; - precision highp sampler2DArray; - precision highp sampler2DShadow; - precision highp samplerCube; - precision highp sampler3D; - precision highp sampler2D; - #if __VERSION__ >= 310 - precision highp sampler2DMS; + +#if __VERSION__ >= 130 + #extension GL_ARB_explicit_attrib_location : enable +#endif + +#if __VERSION__ >= 310 + #ifdef FRAGMENT_SHADER + precision highp float; + precision highp int; + precision highp sampler2DArray; + precision highp sampler2DShadow; + precision highp samplerCube; + precision highp sampler3D; + precision highp sampler2D; + #if __VERSION__ >= 310 + precision highp sampler2DMS; + #endif #endif - #endif #endif #if defined GL_ES @@ -38,7 +43,7 @@ #ifdef FRAGMENT_SHADER #ifdef GL_ES #ifdef BOUND_DRAW_BUFFER - #for i=0..15 ( #if $i<=BOUND_DRAW_BUFFER $0 #endif ) + #for i=0..15 ( #if $i<=BOUND_DRAW_BUFFER $0 #endif ) #if BOUND_DRAW_BUFFER == $i layout( location = $i ) out highp vec4 outFragColor; #else @@ -109,4 +114,3 @@ mat3 inverse(mat3 m) { + (m[0][0] * m[1][1] - m[1][0] * m[0][1])) / determinant(m); } #endif - diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib index 37c3a40cf2..d5d750a35b 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib @@ -26,6 +26,12 @@ uniform mat4 g_WorldViewProjectionMatrix; uniform mat4 g_ViewProjectionMatrix; uniform mat3 g_NormalMatrix; uniform mat3 g_WorldNormalMatrix; +uniform mat4 g_ViewProjectionMatrixInverse; + + +mat4 GetViewProjectionMatrixInverse(){ + return g_ViewProjectionMatrixInverse; +} #if defined INSTANCING diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Octahedral.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Octahedral.glsllib new file mode 100644 index 0000000000..5406c4029d --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/Octahedral.glsllib @@ -0,0 +1,43 @@ +// -*- c++ -*- + +/** Efficient GPU implementation of the octahedral unit vector encoding from + + Cigolle, Donow, Evangelakos, Mara, McGuire, Meyer, + A Survey of Efficient Representations for Independent Unit Vectors, Journal of Computer Graphics Techniques (JCGT), vol. 3, no. 2, 1-30, 2014 + + Available online http://jcgt.org/published/0003/02/01/ +*/ +#ifndef G3D_octahedral_glsl +#define G3D_octahedral_glsl + + +float signNotZero(float f){ + return(f >= 0.0) ? 1.0 : -1.0; +} +vec2 signNotZero(vec2 v) { + return vec2(signNotZero(v.x), signNotZero(v.y)); +} + +/** Assumes that v is a unit vector. The result is an octahedral vector on the [-1, +1] square. */ +vec2 octEncode(in vec3 v) { + float l1norm = abs(v.x) + abs(v.y) + abs(v.z); + vec2 result = v.xy * (1.0 / l1norm); + if (v.z < 0.0) { + result = (1.0 - abs(result.yx)) * signNotZero(result.xy); + } + return result; +} + + +/** Returns a unit vector. Argument o is an octahedral vector packed via octEncode, + on the [-1, +1] square*/ +vec3 octDecode(vec2 o) { + vec3 v = vec3(o.x, o.y, 1.0 - abs(o.x) - abs(o.y)); + if (v.z < 0.0) { + v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy); + } + return normalize(v); +} + + +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/ShadingModel.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/ShadingModel.glsllib new file mode 100644 index 0000000000..db72345b81 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/ShadingModel.glsllib @@ -0,0 +1,4 @@ +#define PHONG_LIGHTING 1 +#define PBR_LIGHTING 2 +#define UNLIT 3 +#define SUBSURFACE_SCATTERING 4 diff --git a/jme3-core/src/main/resources/Common/ShaderLib/SkyLightReflectionProbe.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/SkyLightReflectionProbe.glsllib new file mode 100644 index 0000000000..3b3326cb35 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/SkyLightReflectionProbe.glsllib @@ -0,0 +1,74 @@ +#if NB_PROBES >= 1 + uniform samplerCube g_ReflectionEnvMap; + uniform vec3 g_ShCoeffs[9]; + uniform mat4 g_SkyLightData; +#endif +#if NB_PROBES >= 2 + uniform samplerCube g_ReflectionEnvMap2; + uniform vec3 g_ShCoeffs2[9]; + uniform mat4 g_SkyLightData2; +#endif +#if NB_PROBES == 3 + uniform samplerCube g_ReflectionEnvMap3; + uniform vec3 g_ShCoeffs3[9]; + uniform mat4 g_SkyLightData3; +#endif + +vec3 renderSkyLightAndReflectionProbes( + in float indoorSunLightExposure, in vec3 viewDir, in vec3 wPosition, + in vec3 normal, in vec3 norm, in float Roughness, in vec3 diffuseColor, + in vec3 specularColor, in float ndotv, in vec3 ao) { + vec3 result = vec3(0); + vec4 difColor = vec4(diffuseColor, 1.0f); + vec4 specColor = vec4(specularColor, 1.0f); + #if NB_PROBES >= 1 + vec3 color1 = vec3(0.0); + vec3 color2 = vec3(0.0); + vec3 color3 = vec3(0.0); + float weight1 = 1.0; + float weight2 = 0.0; + float weight3 = 0.0; + + float ndf = renderProbe(viewDir, wPosition, normal, norm, Roughness, difColor, specColor, ndotv, ao, g_SkyLightData, g_ShCoeffs, g_ReflectionEnvMap, color1); + #if NB_PROBES >= 2 + float ndf2 = renderProbe(viewDir, wPosition, normal, norm, Roughness, difColor, specColor, ndotv, ao, g_SkyLightData2, g_ShCoeffs2, g_ReflectionEnvMap2, color2); + #endif + #if NB_PROBES == 3 + float ndf3 = renderProbe(viewDir, wPosition, normal, norm, Roughness, difColor, specColor, ndotv, ao, g_SkyLightData3, g_ShCoeffs3, g_ReflectionEnvMap3, color3); + #endif + + #if NB_PROBES >= 2 + float invNdf = max(1.0 - ndf,0.0); + float invNdf2 = max(1.0 - ndf2,0.0); + float sumNdf = ndf + ndf2; + float sumInvNdf = invNdf + invNdf2; + #if NB_PROBES == 3 + float invNdf3 = max(1.0 - ndf3,0.0); + sumNdf += ndf3; + sumInvNdf += invNdf3; + weight3 = ((1.0 - (ndf3 / sumNdf)) / (NB_PROBES - 1)) * (invNdf3 / sumInvNdf); + #endif + + weight1 = ((1.0 - (ndf / sumNdf)) / (NB_PROBES - 1)) * (invNdf / sumInvNdf); + weight2 = ((1.0 - (ndf2 / sumNdf)) / (NB_PROBES - 1)) * (invNdf2 / sumInvNdf); + + float weightSum = weight1 + weight2 + weight3; + + weight1 /= weightSum; + weight2 /= weightSum; + weight3 /= weightSum; + #endif + + #ifdef USE_AMBIENT_LIGHT + color1.rgb *= g_AmbientLightColor.rgb; + color2.rgb *= g_AmbientLightColor.rgb; + color3.rgb *= g_AmbientLightColor.rgb; + #endif + color1.rgb *= indoorSunLightExposure; + color2.rgb *= indoorSunLightExposure; + color3.rgb *= indoorSunLightExposure; + result.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0); + + #endif + return result; +} diff --git a/jme3-core/src/main/resources/com/jme3/asset/General.cfg b/jme3-core/src/main/resources/com/jme3/asset/General.cfg index 89fb8e67f8..3518774b5a 100644 --- a/jme3-core/src/main/resources/com/jme3/asset/General.cfg +++ b/jme3-core/src/main/resources/com/jme3/asset/General.cfg @@ -14,6 +14,7 @@ LOADER com.jme3.texture.plugins.HDRLoader : hdr LOADER com.jme3.texture.plugins.TGALoader : tga LOADER com.jme3.export.binary.BinaryLoader : j3o LOADER com.jme3.export.binary.BinaryLoader : j3f +LOADER com.jme3.export.binary.BinaryLoader : j3g LOADER com.jme3.scene.plugins.OBJLoader : obj LOADER com.jme3.scene.plugins.MTLLoader : mtl LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml 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 0a0c0403c2..08ab7985ae 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 @@ -51,8 +51,6 @@ import com.jme3.util.blockparser.Statement; import com.jme3.util.clone.Cloner; import jme3tools.shader.Preprocessor; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; @@ -64,7 +62,7 @@ public class J3MLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(J3MLoader.class.getName()); - // private ErrorLogger errors; + // private ErrorLogger errors; private ShaderNodeLoaderDelegate nodesLoaderDelegate; boolean isUseNodes = false; int langSize = 0; @@ -131,15 +129,15 @@ private void readLightMode(String statement) throws IOException{ 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]); + TechniqueDef.LightSpace ls = TechniqueDef.LightSpace.valueOf(split[1]); technique.setLightSpace(ls); } @@ -299,7 +297,7 @@ private Texture parseTextureType(final VarType type, final String value) { for (final TextureOptionValue textureOptionValue : textureOptionValues) { textureOptionValue.applyToTexture(texture); } - } + } return texture; } @@ -313,28 +311,28 @@ private Object readValue(final VarType type, final String value) throws IOExcept if (split.length != 1){ throw new IOException("Float value parameter must have 1 entry: " + value); } - return Float.parseFloat(split[0]); + return Float.parseFloat(split[0]); case Vector2: if (split.length != 2){ throw new IOException("Vector2 value parameter must have 2 entries: " + value); } return new Vector2f(Float.parseFloat(split[0]), - Float.parseFloat(split[1])); + Float.parseFloat(split[1])); case Vector3: if (split.length != 3){ throw new IOException("Vector3 value parameter must have 3 entries: " + value); } return new Vector3f(Float.parseFloat(split[0]), - Float.parseFloat(split[1]), - Float.parseFloat(split[2])); + Float.parseFloat(split[1]), + Float.parseFloat(split[2])); case Vector4: if (split.length != 4){ throw new IOException("Vector4 value parameter must have 4 entries: " + value); } return new ColorRGBA(Float.parseFloat(split[0]), - Float.parseFloat(split[1]), - Float.parseFloat(split[2]), - Float.parseFloat(split[3])); + Float.parseFloat(split[1]), + Float.parseFloat(split[2]), + Float.parseFloat(split[3])); case Int: if (split.length != 1){ throw new IOException("Int value parameter must have 1 entry: " + value); @@ -538,12 +536,12 @@ private void readDefine(String statement) throws IOException{ MatParam param = materialDef.getMaterialParam(paramName); if (param == null) { logger.log(Level.WARNING, "In technique ''{0}'':\n" - + "Define ''{1}'' mapped to non-existent" - + " material parameter ''{2}'', ignoring.", + + "Define ''{1}'' mapped to non-existent" + + " material parameter ''{2}'', ignoring.", new Object[]{technique.getName(), defineName, paramName}); return; } - + VarType paramType = param.getVarType(); technique.addShaderParamDefine(paramName, paramType, defineName); }else{ @@ -566,6 +564,8 @@ private void readTechniqueStatement(Statement statement) throws IOException{ split[0].equals("TessellationControlShader") || split[0].equals("TessellationEvaluationShader")) { readShaderStatement(statement.getLine()); + }else if(split[0].equals("Pipeline")){ + throw new UnsupportedOperationException("Pipeline statement is not supported."); }else if (split[0].equals("LightMode")){ readLightMode(statement.getLine()); }else if (split[0].equals("LightSpace")){ @@ -609,7 +609,7 @@ private void readTransparentStatement(String statement) throws IOException{ } material.setTransparent(parseBoolean(split[1])); } - + private static String createShaderPrologue(List presetDefines) { DefineList dl = new DefineList(presetDefines.size()); for (int i = 0; i < presetDefines.size(); i++) { @@ -667,7 +667,7 @@ private void readTechnique(Statement techStat) throws IOException{ if(isUseNodes){ //used for caching later, the shader here is not a file. - + // KIRILL 9/19/2015 // Not sure if this is needed anymore, since shader caching // is now done by TechniqueDef. diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java index e8f5029c37..31b708a84d 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java @@ -95,7 +95,29 @@ protected void initFilter(AssetManager assets, RenderManager renderManager, material.setFloat("XScale", blurScale * xScale); material.setFloat("YScale", blurScale * yScale); } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule out = ex.getCapsule(this); + out.write(focusDistance, "focusDistance", 50f); + out.write(focusRange, "focusRange", 10f); + out.write(blurScale, "blurScale", 1f); + out.write(blurThreshold, "blurThreshold", 0.2f); + out.write(debugUnfocus, "debugUnfocus", false); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule in = im.getCapsule(this); + focusDistance = in.readFloat("focusDistance", 50f); + focusRange = in.readFloat("focusRange", 10f); + blurScale = in.readFloat("blurScale", 1f); + blurThreshold = in.readFloat("blurThreshold", 0.2f); + debugUnfocus = in.readBoolean("debugUnfocus", false); + } + /** * Sets the distance at which objects are purely in focus. * @@ -221,26 +243,4 @@ public void setDebugUnfocus( boolean b ) { public boolean getDebugUnfocus() { return debugUnfocus; } - - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule oc = ex.getCapsule(this); - oc.write(blurScale, "blurScale", 1f); - oc.write(blurThreshold, "blurThreshold", 0.2f); - oc.write(focusDistance, "focusDistance", 50f); - oc.write(focusRange, "focusRange", 10f); - oc.write(debugUnfocus, "debugUnfocus", false); // strange to write this I guess - } - - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule ic = im.getCapsule(this); - blurScale = ic.readFloat("blurScale", 1f); - blurThreshold = ic.readFloat("blurThreshold", 0.2f); - focusDistance = ic.readFloat("focusDistance", 50f); - focusRange = ic.readFloat("focusRange", 10f); - debugUnfocus = ic.readBoolean("debugUnfocus", false); - } } diff --git a/jme3-effects/src/main/java/com/jme3/post/framegraph/BloomPass.java b/jme3-effects/src/main/java/com/jme3/post/framegraph/BloomPass.java new file mode 100644 index 0000000000..07e5928959 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/framegraph/BloomPass.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2024 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.framegraph; + +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.ColorRGBA; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.GeometryQueue; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.renderer.framegraph.passes.RenderPass; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture2D; +import java.io.IOException; + +/** + * Port of {@link com.jme3.post.filters.BloomFilter} as a RenderPass. + *

    + * Inputs: + *

      + *
    • Color ({@link Texture2D}): color texture to apply bloom effect to.
    • + *
    • Objects ({@link GeometryQueue}): specific geometries to apply bloom effect to (optional).
    • + *
    + * Outputs: + *
      + *
    • Color ({@link Texture2D}): resulting color texture.
    • + *
    + * If "Objects" is undefined, the texture from "Color" (input) will be used. Otherwise, the + * queue from "Objects" will be rendered with the Glow technique and the result used. + * + * @author codex + */ +public class BloomPass extends RenderPass { + + private ResourceTicket inColor; + private ResourceTicket objects; + private ResourceTicket midTex; + private ResourceTicket result; + private final TextureDef texDef = TextureDef.texture2D(); + private Material extractMat, hBlurMat, vBlurMat, outMat; + private float blurScale = 1.5f; + private float exposurePower = 5.0f; + private float exposureCutOff = 0.0f; + private float bloomIntensity = 2.0f; + private float downSamplingFactor = 1; + + @Override + protected void initialize(FrameGraph frameGraph) { + inColor = addInput("Color"); + objects = addInput("Objects"); + result = addOutput("Color"); + midTex = new ResourceTicket("MiddleTex"); + AssetManager assets = frameGraph.getAssetManager(); + extractMat = new Material(assets, "Common/MatDefs/Post/BloomExtract.j3md"); + hBlurMat = new Material(assets, "Common/MatDefs/Blur/HGaussianBlur.j3md"); + vBlurMat = new Material(assets, "Common/MatDefs/Blur/VGaussianBlur.j3md"); + outMat = new Material(assets, "Common/MatDefs/Post/BloomFinal.j3md"); + } + @Override + protected void prepare(FGRenderContext context) { + declare(texDef, result); + declareTemporary(texDef, midTex); + reserve(result, midTex); + referenceOptional(inColor, objects); + } + @Override + protected void execute(FGRenderContext context) { + + // configure definitions + int w = (int)Math.max(1f, context.getWidth()/downSamplingFactor); + int h = (int)Math.max(1f, context.getHeight()/downSamplingFactor); + texDef.setSize(w, h); + + // get framebuffers and resources + FrameBuffer outFb = getFrameBuffer(w, h, 1); + Texture2D outTarget = resources.acquireColorTarget(outFb, result); + FrameBuffer midFb = getFrameBuffer("mid", w, h, 1); + Texture2D midTarget = resources.acquireColorTarget(midFb, midTex); + GeometryQueue geometry = resources.acquireOrElse(objects, null); + Texture2D scene = resources.acquire(inColor); + context.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha); + + // geometry render + if (geometry != null) { + context.getRenderer().setFrameBuffer(outFb); + context.getRenderer().clearBuffers(true, true, true); + context.getRenderManager().setForcedTechnique("Glow"); + context.renderGeometry(geometry, null, null); + context.getRenderManager().setForcedTechnique(null); + extractMat.setTexture("GlowMap", outTarget); + } else { + extractMat.clearParam("GlowMap"); + } + + // extraction + extractMat.setFloat("ExposurePow", exposurePower); + extractMat.setFloat("ExposureCutoff", exposureCutOff); + extractMat.setBoolean("Extract", scene != null); + if (scene != null) { + extractMat.setTexture("Texture", scene); + } else { + extractMat.clearParam("Texture"); + } + render(context, midFb, extractMat); + + // horizontal blur + hBlurMat.setTexture("Texture", midTarget); + hBlurMat.setFloat("Size", w); + hBlurMat.setFloat("Scale", blurScale); + //render(context, outFb, hBlurMat); + + // vertical blur + vBlurMat.setTexture("Texture", outTarget); + vBlurMat.setFloat("Size", h); + vBlurMat.setFloat("Scale", blurScale); + //render(context, midFb, vBlurMat); + + // final output + outMat.setTexture("Texture", scene); + outMat.setTexture("BloomTex", midTarget); + render(context, outFb, outMat); + + // manual release required for unregistered tickets + resources.release(midTex); + + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(blurScale, "blurScale", 1.5f); + oc.write(exposurePower, "exposurePower", 5.0f); + oc.write(exposureCutOff, "exposureCutOff", 0.0f); + oc.write(bloomIntensity, "bloomIntensity", 2.0f); + oc.write(downSamplingFactor, "downSamplingFactor", 1); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + blurScale = ic.readFloat("blurScale", 1.5f); + exposurePower = ic.readFloat("exposurePower", 5.0f); + exposureCutOff = ic.readFloat("exposureCutOff", 0.0f); + bloomIntensity = ic.readFloat("bloomIntensity", 2.0f); + downSamplingFactor = ic.readFloat("downSamplingFactor", 1); + } + + private void render(FGRenderContext context, FrameBuffer fb, Material mat) { + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + context.resizeCamera(fb.getWidth(), fb.getHeight(), false, false, false); + context.renderFullscreen(mat); + } + + /** + * returns the bloom intensity + * @return the intensity value + */ + public float getBloomIntensity() { + return bloomIntensity; + } + + /** + * intensity of the bloom effect default is 2.0 + * + * @param bloomIntensity the desired intensity (default=2) + */ + public void setBloomIntensity(float bloomIntensity) { + this.bloomIntensity = bloomIntensity; + } + + /** + * returns the blur scale + * @return the blur scale + */ + public float getBlurScale() { + return blurScale; + } + + /** + * sets The spread of the bloom default is 1.5f + * + * @param blurScale the desired scale (default=1.5) + */ + public void setBlurScale(float blurScale) { + this.blurScale = blurScale; + } + + /** + * returns the exposure cutoff
    + * for more details see {@link #setExposureCutOff(float exposureCutOff)} + * @return the exposure cutoff + */ + public float getExposureCutOff() { + return exposureCutOff; + } + + /** + * Define the color threshold on which the bloom will be applied (0.0 to 1.0) + * + * @param exposureCutOff the desired threshold (≥0, ≤1, default=0) + */ + public void setExposureCutOff(float exposureCutOff) { + this.exposureCutOff = exposureCutOff; + } + + /** + * returns the exposure power
    + * for more details see {@link #setExposurePower(float exposurePower)} + * @return the exposure power + */ + public float getExposurePower() { + return exposurePower; + } + + /** + * defines how many times the bloom extracted color will be multiplied by itself. default is 5.0
    + * a high value will reduce rough edges in the bloom and somehow the range of the bloom area + * + * @param exposurePower the desired exponent (default=5) + */ + public void setExposurePower(float exposurePower) { + this.exposurePower = exposurePower; + } + + /** + * returns the downSampling factor
    + * for more details see {@link #setDownSamplingFactor(float downSamplingFactor)} + * @return the downsampling factor + */ + public float getDownSamplingFactor() { + return downSamplingFactor; + } + + /** + * Sets the downSampling factor : the size of the computed texture will be divided by this factor. default is 1 for no downsampling + * A 2 value is a good way of widening the blur + * + * @param downSamplingFactor the desired factor (default=1) + */ + public void setDownSamplingFactor(float downSamplingFactor) { + this.downSamplingFactor = downSamplingFactor; + } + +} diff --git a/jme3-effects/src/main/java/com/jme3/post/framegraph/CartoonEdgePass.java b/jme3-effects/src/main/java/com/jme3/post/framegraph/CartoonEdgePass.java new file mode 100644 index 0000000000..eced0c1f1b --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/framegraph/CartoonEdgePass.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2024 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.framegraph; + +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.ColorRGBA; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.renderer.framegraph.passes.RenderPass; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture2D; +import java.io.IOException; + +/** + * Port of {@link com.jme3.post.filters.CartoonEdgeFilter} to a RenderPass. + *

    + * Inputs: + *

      + *
    • Color ({@link Texture2D}): scene color texture.
    • + *
    • Depth ({@link Texture2D}): scene depth texture.
    • + *
    • Normals ({@link Texture2D}): scene normals texture.
    • + *
    + * Outputs: + *
      + *
    • Color ({@link Texture2D}): resulting color texture.
    • + *
    + * + * @author codex + */ +public class CartoonEdgePass extends RenderPass { + + private ResourceTicket color, depth, normals; + private ResourceTicket result; + private final TextureDef texDef = TextureDef.texture2D(); + private Material material; + private float edgeWidth = 1.0f; + private float edgeIntensity = 1.0f; + private float normalThreshold = 0.5f; + private float depthThreshold = 0.1f; + private float normalSensitivity = 1.0f; + private float depthSensitivity = 10.0f; + private ColorRGBA edgeColor = new ColorRGBA(0, 0, 0, 1); + + @Override + protected void initialize(FrameGraph frameGraph) { + color = addInput("Color"); + depth = addInput("Depth"); + normals = addInput("Normals"); + result = addOutput("Color"); + material = new Material(frameGraph.getAssetManager(), "Common/MatDefs/Post/CartoonEdge.j3md"); + material.setFloat("EdgeWidth", edgeWidth); + material.setFloat("EdgeIntensity", edgeIntensity); + material.setFloat("NormalThreshold", normalThreshold); + material.setFloat("DepthThreshold", depthThreshold); + material.setFloat("NormalSensitivity", normalSensitivity); + material.setFloat("DepthSensitivity", depthSensitivity); + material.setColor("EdgeColor", edgeColor); + } + @Override + protected void prepare(FGRenderContext context) { + declare(texDef, result); + reserve(result); + reference(color, depth, normals); + texDef.setSize(context.getWidth(), context.getHeight()); + } + @Override + protected void execute(FGRenderContext context) { + FrameBuffer fb = getFrameBuffer(context, 1); + resources.acquireColorTarget(fb, result); + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + material.setTexture("Texture", resources.acquire(color)); + material.setTexture("DepthTexture", resources.acquire(depth)); + material.setTexture("NormalsTexture", resources.acquire(normals)); + context.renderFullscreen(material); + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) { + material = null; + } + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(edgeWidth, "edgeWidth", 1.0f); + oc.write(edgeIntensity, "edgeIntensity", 1.0f); + oc.write(normalThreshold, "normalThreshold", 0.5f); + oc.write(depthThreshold, "depthThreshold", 0.1f); + oc.write(normalSensitivity, "normalSensitivity", 1.0f); + oc.write(depthSensitivity, "depthSensitivity", 10.0f); + oc.write(edgeColor, "edgeColor", ColorRGBA.Black); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + edgeWidth = ic.readFloat("edgeWidth", 1.0f); + edgeIntensity = ic.readFloat("edgeIntensity", 1.0f); + normalThreshold = ic.readFloat("normalThreshold", 0.5f); + depthThreshold = ic.readFloat("depthThreshold", 0.1f); + normalSensitivity = ic.readFloat("normalSensitivity", 1.0f); + depthSensitivity = ic.readFloat("depthSensitivity", 10.0f); + edgeColor = (ColorRGBA)ic.readSavable("edgeColor", ColorRGBA.Black.clone()); + } + + /** + * Return the depth sensitivity
    + * for more details see {@link #setDepthSensitivity(float depthSensitivity)} + * @return the depth sensitivity + */ + public float getDepthSensitivity() { + return depthSensitivity; + } + + /** + * sets the depth sensitivity
    + * defines how much depth will influence edges, default is 10 + * + * @param depthSensitivity the desired sensitivity (default=10) + */ + public void setDepthSensitivity(float depthSensitivity) { + this.depthSensitivity = depthSensitivity; + if (material != null) { + material.setFloat("DepthSensitivity", depthSensitivity); + } + } + + /** + * returns the depth threshold
    + * for more details see {@link #setDepthThreshold(float depthThreshold)} + * @return the threshold + */ + public float getDepthThreshold() { + return depthThreshold; + } + + /** + * sets the depth threshold
    + * Defines at what threshold of difference of depth an edge is outlined default is 0.1f + * + * @param depthThreshold the desired threshold (default=0.1) + */ + public void setDepthThreshold(float depthThreshold) { + this.depthThreshold = depthThreshold; + if (material != null) { + material.setFloat("DepthThreshold", depthThreshold); + } + } + + /** + * returns the edge intensity
    + * for more details see {@link #setEdgeIntensity(float edgeIntensity) } + * @return the intensity + */ + public float getEdgeIntensity() { + return edgeIntensity; + } + + /** + * sets the edge intensity
    + * Defines how visible the outlined edges will be + * + * @param edgeIntensity the desired intensity (default=1) + */ + public void setEdgeIntensity(float edgeIntensity) { + this.edgeIntensity = edgeIntensity; + if (material != null) { + material.setFloat("EdgeIntensity", edgeIntensity); + } + } + + /** + * returns the width of the edges + * @return the width + */ + public float getEdgeWidth() { + return edgeWidth; + } + + /** + * sets the width of the edge in pixels default is 1 + * + * @param edgeWidth the desired width (in pixels, default=1) + */ + public void setEdgeWidth(float edgeWidth) { + this.edgeWidth = edgeWidth; + if (material != null) { + material.setFloat("EdgeWidth", edgeWidth); + } + } + + /** + * returns the normals sensitivity
    + * form more details see {@link #setNormalSensitivity(float normalSensitivity)} + * @return the sensitivity + */ + public float getNormalSensitivity() { + return normalSensitivity; + } + + /** + * Sets the normals sensitivity. Default is 1. + * + * @param normalSensitivity the desired sensitivity (default=1) + */ + public void setNormalSensitivity(float normalSensitivity) { + this.normalSensitivity = normalSensitivity; + if (material != null) { + material.setFloat("NormalSensitivity", normalSensitivity); + } + } + + /** + * returns the normal threshold
    + * for more details see {@link #setNormalThreshold(float normalThreshold)} + * + * @return the threshold + */ + public float getNormalThreshold() { + return normalThreshold; + } + + /** + * sets the normal threshold default is 0.5 + * + * @param normalThreshold the desired threshold (default=0.5) + */ + public void setNormalThreshold(float normalThreshold) { + this.normalThreshold = normalThreshold; + if (material != null) { + material.setFloat("NormalThreshold", normalThreshold); + } + } + + /** + * returns the edge color + * @return the pre-existing instance + */ + public ColorRGBA getEdgeColor() { + return edgeColor; + } + + /** + * Sets the edge color, default is black + * + * @param edgeColor the desired color (alias created, default=(0,0,0,1)) + */ + public void setEdgeColor(ColorRGBA edgeColor) { + this.edgeColor = edgeColor; + if (material != null) { + material.setColor("EdgeColor", edgeColor); + } + } + +} diff --git a/jme3-effects/src/main/java/com/jme3/post/framegraph/DepthOfFieldPass.java b/jme3-effects/src/main/java/com/jme3/post/framegraph/DepthOfFieldPass.java new file mode 100644 index 0000000000..933c4af043 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/framegraph/DepthOfFieldPass.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2024 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.framegraph; + +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.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.renderer.framegraph.passes.RenderPass; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture2D; +import java.io.IOException; + +/** + * Port of {@link com.jme3.post.filters.DepthOfFieldFilter} to a RenderPass. + *

    + * Inputs: + *

      + *
    • Color ({@link Texture2D}): scene color texture.
    • + *
    • Depth ({@link Texture2D}): scene depth texture.
    • + *
    + * Outputs: + *
      + *
    • Color ({@link Texture2D}): resulting color texture.
    • + *
    + * + * @author codex + */ +public class DepthOfFieldPass extends RenderPass { + + private ResourceTicket color, depth; + private ResourceTicket result; + private final TextureDef texDef = TextureDef.texture2D(); + private Material material; + private float focusDistance = 50f; + private float focusRange = 10f; + private float blurScale = 1f; + private float blurThreshold = 0.2f; + private boolean debugUnfocus; + + @Override + protected void initialize(FrameGraph frameGraph) { + color = addInput("Color"); + depth = addInput("Depth"); + result = addOutput("Color"); + material = new Material(frameGraph.getAssetManager(), "Common/MatDefs/Post/DepthOfField.j3md"); + material.setFloat("FocusDistance", focusDistance); + material.setFloat("FocusRange", focusRange); + material.setFloat("BlurThreshold", blurThreshold); + material.setBoolean("DebugUnfocus", debugUnfocus); + } + @Override + protected void prepare(FGRenderContext context) { + declare(texDef, result); + reserve(result); + reference(color, depth); + texDef.setSize(context.getWidth(), context.getHeight()); + material.setFloat("XScale", blurScale/context.getWidth()); + material.setFloat("YScale", blurScale/context.getHeight()); + } + @Override + protected void execute(FGRenderContext context) { + FrameBuffer fb = getFrameBuffer(context, 1); + resources.acquireColorTarget(fb, result); + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + material.setTexture("Texture", resources.acquire(color)); + material.setTexture("DepthTexture", resources.acquire(depth)); + context.renderFullscreen(material); + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) { + material = null; + } + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(blurScale, "blurScale", 1f); + oc.write(blurThreshold, "blurThreshold", 0.2f); + oc.write(focusDistance, "focusDistance", 50f); + oc.write(focusRange, "focusRange", 10f); + oc.write(debugUnfocus, "debugUnfocus", false); + } + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + blurScale = ic.readFloat("blurScale", 1f); + blurThreshold = ic.readFloat("blurThreshold", 0.2f); + focusDistance = ic.readFloat("focusDistance", 50f); + focusRange = ic.readFloat("focusRange", 10f); + debugUnfocus = ic.readBoolean("debugUnfocus", false); + } + + /** + * Sets the distance at which objects are purely in focus. + * + * @param f the desired distance (in world units, default=50) + */ + public void setFocusDistance(float f) { + + this.focusDistance = f; + if (material != null) { + material.setFloat("FocusDistance", focusDistance); + } + + } + + /** + * returns the focus distance + * @return the distance + */ + public float getFocusDistance() { + return focusDistance; + } + + /** + * Sets the range to either side of focusDistance where the + * objects go gradually out of focus. Less than focusDistance - focusRange + * and greater than focusDistance + focusRange, objects are maximally "blurred". + * + * @param f the desired half extent (in world units, default=10) + */ + public void setFocusRange(float f) { + this.focusRange = f; + if (material != null) { + material.setFloat("FocusRange", focusRange); + } + + } + + /** + * returns the focus range + * @return the distance + */ + public float getFocusRange() { + return focusRange; + } + + /** + * Sets the blur amount by scaling the convolution filter up or + * down. A value of 1 (the default) performs a sparse 5x5 evenly + * distributed convolution at pixel level accuracy. Higher values skip + * more pixels, and so on until you are no longer blurring the image + * but simply hashing it. + * + * The sparse convolution is as follows: + *%MINIFYHTMLc3d0cd9fab65de6875a381fd3f83e1b338%* + * Where 'x' is the texel being modified. Setting blur scale higher + * than 1 spaces the samples out. + * + * @param f the desired filter scale (default=1) + */ + public void setBlurScale(float f) { + this.blurScale = f; + } + + /** + * returns the blur scale + * @return the scale + */ + public float getBlurScale() { + return blurScale; + } + + /** + * Sets the minimum blur factor before the convolution filter is + * calculated. The default is 0.2 which means if the "unfocus" + * amount is less than 0.2 (where 0 is no blur and 1.0 is full blurScale) + * then no blur will be applied at all. Depending on the GPU implementation, + * this may be an optimization since it uses branching to skip the expensive + * convolution filter. + * + *

    In scenes where the focus distance is close (like 0) and the focus range + * is relatively large, this threshold will remove some subtlety in + * the near-camera blurring and should be set smaller than the default + * or to 0 to disable completely. Sometimes that cut-off is desired if + * mid-to-far field unfocusing is all that is desired.

    + * + * @param f the desired blur factor (default=0.2) + */ + public void setBlurThreshold( float f ) { + this.blurThreshold = f; + if (material != null) { + material.setFloat("BlurThreshold", blurThreshold); + } + } + + /** + * returns the blur threshold. + * @return the threshold + */ + public float getBlurThreshold() { + return blurThreshold; + } + + /** + * Turns on/off debugging of the 'unfocus' value that is used to + * mix the convolution filter. When this is on, the 'unfocus' value + * is rendered as gray scale. This can be used to more easily visualize + * where in your view the focus is centered and how steep the gradient/cutoff + * is, etcetera. + * + * @param b true to enable debugging, false to disable it (default=false) + */ + public void setDebugUnfocus( boolean b ) { + this.debugUnfocus = b; + if( material != null ) { + material.setBoolean("DebugUnfocus", debugUnfocus); + } + } + + /** + * + * @return + */ + public boolean getDebugUnfocus() { + return debugUnfocus; + } + +} diff --git a/jme3-effects/src/main/java/com/jme3/post/framegraph/FogPass.java b/jme3-effects/src/main/java/com/jme3/post/framegraph/FogPass.java new file mode 100644 index 0000000000..8fd8625b7d --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/framegraph/FogPass.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2024 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.framegraph; + +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.client.GraphSource; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.renderer.framegraph.passes.RenderPass; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; + +/** + * Port of {@link com.jme3.post.filters.FogFilter} to a RenderPass. + *

    + * Inputs: + *

      + *
    • Color ({@link Texture2D}): scene color texture.
    • + *
    • Depth ({@link Texture2D}): scene depth texture.
    • + *
    • Fog ({@link Texture2D}): screenspace fog texture to fade into (optional).
    • + *
    + * Outputs: + *
      + *
    • Result ({@link Texture2D}): resulting color texture.
    • + *
    + * If "Fog" is defined, the fog texture will be used to determine the fog color in screenspace. + * Otherwise, a solid color from a {@link GraphSource} will be used. + * + * @author codex + */ +public class FogPass extends RenderPass { + + private ResourceTicket colorMap, depthMap, fogMap, result; + private final TextureDef texDef = TextureDef.texture2D(); + private Material material; + private GraphSource color; + private GraphSource density, distance; + + @Override + protected void initialize(FrameGraph frameGraph) { + colorMap = addInput("Color"); + depthMap = addInput("Depth"); + fogMap = addInput("Fog"); + result = addOutput("Result"); + texDef.setMagFilter(Texture.MagFilter.Bilinear); + texDef.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + material = new Material(frameGraph.getAssetManager(), "Common/MatDefs/Post/SkyFog.j3md"); + } + @Override + protected void prepare(FGRenderContext context) { + declare(texDef, result); + reserve(result); + reference(colorMap, depthMap); + referenceOptional(fogMap); + } + @Override + protected void execute(FGRenderContext context) { + + ViewPort vp = context.getViewPort(); + + Texture2D colorTex = resources.acquire(colorMap); + Texture2D fogTex = resources.acquireOrElse(fogMap, null); + material.setTexture("ColorMap", colorTex); + material.setTexture("DepthMap", resources.acquire(depthMap)); + if (fogTex != null) { + material.setTexture("FogMap", fogTex); + } else { + material.clearParam("FogMap"); + material.setColor("FogColor", GraphSource.get(color, ColorRGBA.White, frameGraph, vp)); + } + material.setFloat("Density", GraphSource.get(density, .7f, frameGraph, vp)); + material.setFloat("Distance", GraphSource.get(distance, 1000f, frameGraph, vp)); + + int w = colorTex.getImage().getWidth(); + int h = colorTex.getImage().getHeight(); + texDef.setSize(w, h); + texDef.setFormat(colorTex.getImage().getFormat()); + + FrameBuffer fb = getFrameBuffer(w, h, 1); + resources.acquireColorTarget(fb, result); + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + context.resizeCamera(w, h, false, false, false); + + context.getScreen().render(context.getRenderManager(), material); + + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + + /** + * Sets the fog color used if no fog map is specified. + *

    + * default={@link ColorRGBA#White} + * + * @param color + */ + public void setColor(GraphSource color) { + this.color = color; + } + /** + * Sets the fog density. + *

    + * default={@code 0.7f} + * + * @param density + */ + public void setDensity(GraphSource density) { + this.density = density; + } + /** + * Sets the distance the fog begins at. + *

    + * default={@code 1000f} + * + * @param distance + */ + public void setDistance(GraphSource distance) { + this.distance = distance; + } + + /** + * + * @return + */ + public GraphSource getColor() { + return color; + } + /** + * + * @return + */ + public GraphSource getDensity() { + return density; + } + /** + * + * @return + */ + public GraphSource getDistance() { + return distance; + } + +} diff --git a/jme3-effects/src/main/java/com/jme3/post/framegraph/HazePass.java b/jme3-effects/src/main/java/com/jme3/post/framegraph/HazePass.java new file mode 100644 index 0000000000..c4f0979145 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/framegraph/HazePass.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024 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.framegraph; + +import com.jme3.export.InputCapsule; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.client.GraphSource; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.renderer.framegraph.passes.RenderPass; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import java.io.IOException; + +/** + * Fog that only affects objects within a specified depth range. + *

    + * Inputs: + *

      + *
    • Color ({@link Texture2D}): scene color texture.
    • + *
    • Depth ({@link Texture2D}): scene depth texture.
    • + *
    + * Outputs: + *
      + *
    • Result ({@link Texture2D}): resulting color texture.
    • + *
    + * + * @author codex + */ +public class HazePass extends RenderPass { + + private static final Vector2f DEF_RANGE = new Vector2f(0.5f, 1.0f); + + private ResourceTicket inColor, inDepth, result; + private final TextureDef texDef = TextureDef.texture2D(); + private Material material; + private GraphSource color; + private GraphSource range; + + @Override + protected void initialize(FrameGraph frameGraph) { + inColor = addInput("Color"); + inDepth = addInput("Depth"); + result = addOutput("Result"); + texDef.setMagFilter(Texture.MagFilter.Bilinear); + texDef.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + material = new Material(frameGraph.getAssetManager(), "Common/MatDefs/Post/Haze.j3md"); + } + @Override + protected void prepare(FGRenderContext context) { + declare(texDef, result); + reserve(result); + reference(inColor, inDepth); + } + @Override + protected void execute(FGRenderContext context) { + + Texture2D colorTex = resources.acquire(inColor); + Texture2D depthTex = resources.acquire(inDepth); + material.setTexture("ColorMap", colorTex); + material.setTexture("DepthMap", depthTex); + material.setColor("HazeColor", GraphSource.get(color, ColorRGBA.White, frameGraph, context.getViewPort())); + material.setVector2("Range", GraphSource.get(range, DEF_RANGE, frameGraph, context.getViewPort())); + + int w = colorTex.getImage().getWidth(); + int h = colorTex.getImage().getHeight(); + texDef.setSize(w, h); + texDef.setFormat(colorTex.getImage().getFormat()); + + FrameBuffer fb = getFrameBuffer(w, h, 1); + resources.acquireColorTarget(fb, result); + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + context.resizeCamera(w, h, false, false, false); + + context.getScreen().render(context.getRenderManager(), material); + + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + @Override + protected void write(OutputCapsule out) throws IOException { + if (color != null && color instanceof Savable) { + out.write((Savable)color, "color", null); + } + } + @Override + protected void read(InputCapsule in) throws IOException { + color = (GraphSource)in.readSavable("color", null); + } + + /** + * Sets the color of the haze. + *

    + * The alpha value determines the strength of the haze effect. One being + * total haze at the maximum range, and zero being no haze at the maximum range. + *

    + * default={@link ColorRGBA#White} + * + * @param color color source (or null to use default) + */ + public void setColor(GraphSource color) { + this.color = color; + } + /** + * Sets the depth range in which haze is increasing (exclusive). + *

    + * The x value determines the range's lower boundary, and the y value + * determines the range's upper boundary. Pixels outside the range + * are not affected by haze. + *

    + * default={@code (0.5, 1.0)} + * + * @param range range source (or null to use default) + */ + public void setRange(GraphSource range) { + this.range = range; + } + + /** + * + * @return + */ + public GraphSource getColor() { + return color; + } + /** + * + * @return + */ + public GraphSource getRange() { + return range; + } + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthDebug.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthDebug.frag new file mode 100644 index 0000000000..2820a12090 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthDebug.frag @@ -0,0 +1,15 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform DEPTHTEXTURE m_DepthTexture; +varying vec2 texCoord; + +void main() { + vec3 val = getDepth(m_DepthTexture, texCoord).rrr; + if (val.r <= 1.0 && val.r >= 0.0) { + gl_FragColor.rgb = vec3(1.0) - val; + } else { + gl_FragColor.rgb = vec3(1.0, 0.0, 0.0); + } +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthDebug.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthDebug.j3md new file mode 100644 index 0000000000..21b13bf401 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthDebug.j3md @@ -0,0 +1,22 @@ +MaterialDef DepthDebug { + + MaterialParameters { + Int BoundDrawBuffer + Int NumSamples + Texture2D Texture; + Texture2D DepthTexture; + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/DepthDebug.frag + + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } + } + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Haze.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Haze.frag new file mode 100644 index 0000000000..d64dee7daf --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Haze.frag @@ -0,0 +1,26 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + +uniform sampler2D m_ColorMap; +uniform sampler2D m_DepthMap; +uniform vec4 m_HazeColor; +uniform vec2 m_Range; + +varying vec2 texCoord; + +float mapRange(in float value, in vec2 range) { + if (value > range.x && value < range.y) { + return (range.y - value) / (range.y - range.x); + } else { + return 0.0; + } +} + +void main() { + + vec4 color = texture2D(m_ColorMap, texCoord); + float depth = texture2D(m_DepthMap, texCoord).r; + gl_FragColor.rgb = mix(color.rgb, m_HazeColor.rgb, mapRange(depth, m_Range) * m_HazeColor.a); + //gl_FragColor = mix(color, m_HazeColor, depth); + //gl_FragColor = color; + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Haze.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Haze.j3md new file mode 100644 index 0000000000..41a44a5839 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Haze.j3md @@ -0,0 +1,17 @@ +MaterialDef Haze { + + MaterialParameters { + Texture2D ColorMap + Texture2D DepthMap + Color HazeColor + Vector2 Range : 0.5 1.0 + } + + Technique { + + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/ShadingCommon/Screen.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Haze.frag + + } + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/SkyFog.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/SkyFog.frag new file mode 100644 index 0000000000..e969d0b3ec --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/SkyFog.frag @@ -0,0 +1,34 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + +uniform sampler2D m_ColorMap; +uniform sampler2D m_DepthMap; +uniform float m_Density; +uniform float m_Distance; +#ifdef FOG_MAP + uniform sampler2D m_FogMap; +#else + uniform vec4 m_FogColor; +#endif + +varying vec2 texCoord; + +const float LOG2 = 1.442695; + +void main() { + + vec2 frustum = vec2(1.0, m_Distance); + vec4 color = texture2D(m_ColorMap, texCoord); + float depth = texture2D(m_DepthMap, texCoord).r; + depth = (2.0 * frustum.x) / (frustum.y + frustum.x - depth * (frustum.y - frustum.x)); + + #ifdef FOG_MAP + vec4 fogClr = texture2D(m_FogMap, texCoord); + #else + vec4 fogClr = m_FogColor; + #endif + + float fogFactor = exp2(-m_Density * m_Density * depth * depth * LOG2); + fogFactor = clamp(fogFactor, 0.0, 1.0); + gl_FragColor = mix(fogClr, color, fogFactor); + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/SkyFog.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/SkyFog.j3md new file mode 100644 index 0000000000..42bd88ec36 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/SkyFog.j3md @@ -0,0 +1,26 @@ +MaterialDef SkyFog { + + MaterialParameters { + Texture2D ColorMap + Texture2D DepthMap + Texture2D FogMap + Color FogColor + Float Density : 0.7 + Float Distance : 1000 + } + + Technique { + + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/ShadingCommon/Screen.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/SkyFog.frag + + WorldParameters { + } + + Defines { + FOG_MAP : FogMap + } + + } + +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TerrainTestAdvancedRenderPath.java b/jme3-examples/src/main/java/jme3test/framegraph/TerrainTestAdvancedRenderPath.java new file mode 100644 index 0000000000..56c0a7ca03 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TerrainTestAdvancedRenderPath.java @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2009-2021 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.framegraph; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bounding.BoundingBox; +import com.jme3.environment.EnvironmentProbeControl; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.TechniqueDef; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.framegraph.FrameGraphFactory; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.Arrow; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; + +/** + * Uses the terrain's lighting texture with normal maps and lights. + * + * @author bowens,johnKkk + */ +public class TerrainTestAdvancedRenderPath extends SimpleApplication { + + private TerrainQuad terrain; + private Material matTerrain; + private Material matWire; + private boolean wireframe = false; + private boolean triPlanar = false; + private float dirtScale = 16; + private float darkRockScale = 32; + private float pinkRockScale = 32; + private float riverRockScale = 80; + private float grassScale = 32; + private float brickScale = 128; + private float roadScale = 200; + + + public static void main(String[] args) { + TerrainTestAdvancedRenderPath app = new TerrainTestAdvancedRenderPath(); + app.start(); + } + + @Override + public void initialize() { + super.initialize(); + + loadHintText(); + } + + @Override + public void simpleInitApp() { + setupKeys(); + + viewPort.setFrameGraph(FrameGraphFactory.deferred(assetManager, false)); + + // First, we load up our textures and the heightmap texture for the terrain + + // TERRAIN TEXTURE material + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matTerrain.setBoolean("useTriPlanarMapping", false); + matTerrain.setFloat("Shininess", 0.0f); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png")); + matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png")); + // this material also supports 'AlphaMap_2', so you can get up to 12 diffuse textures + + // HEIGHTMAP image (for the terrain heightmap) + TextureKey hmKey = new TextureKey("Textures/Terrain/splat/mountains512.png", false); + Texture heightMapImage = assetManager.loadTexture(hmKey); + + // DIRT texture, Diffuse textures 0 to 3 use the first AlphaMap + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap", dirt); + matTerrain.setFloat("DiffuseMap_0_scale", dirtScale); + + // DARK ROCK texture + Texture darkRock = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); + darkRock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_1", darkRock); + matTerrain.setFloat("DiffuseMap_1_scale", darkRockScale); + + // PINK ROCK texture + Texture pinkRock = assetManager.loadTexture("Textures/Terrain/Rock/Rock.PNG"); + pinkRock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_2", pinkRock); + matTerrain.setFloat("DiffuseMap_2_scale", pinkRockScale); + + // RIVER ROCK texture, this texture will use the next alphaMap: AlphaMap_1 + Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"); + riverRock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_3", riverRock); + matTerrain.setFloat("DiffuseMap_3_scale", riverRockScale); + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_4", grass); + matTerrain.setFloat("DiffuseMap_4_scale", grassScale); + + // BRICK texture + Texture brick = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"); + brick.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_5", brick); + matTerrain.setFloat("DiffuseMap_5_scale", brickScale); + + // ROAD texture + Texture road = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + road.setWrap(WrapMode.Repeat); + matTerrain.setTexture("DiffuseMap_6", road); + matTerrain.setFloat("DiffuseMap_6_scale", roadScale); + + + // diffuse textures 0 to 3 use AlphaMap + // diffuse textures 4 to 7 use AlphaMap_1 + // diffuse textures 8 to 11 use AlphaMap_2 + + + // NORMAL MAPS + Texture normalMapDirt = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMapDirt.setWrap(WrapMode.Repeat); + Texture normalMapPinkRock = assetManager.loadTexture("Textures/Terrain/Rock/Rock_normal.png"); + normalMapPinkRock.setWrap(WrapMode.Repeat); + Texture normalMapGrass = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMapGrass.setWrap(WrapMode.Repeat); + Texture normalMapRoad = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMapRoad.setWrap(WrapMode.Repeat); + matTerrain.setTexture("NormalMap", normalMapDirt); + matTerrain.setTexture("NormalMap_1", normalMapPinkRock); + matTerrain.setTexture("NormalMap_2", normalMapPinkRock); + matTerrain.setTexture("NormalMap_4", normalMapGrass); + matTerrain.setTexture("NormalMap_6", normalMapRoad); + + + // WIREFRAME material (used to debug the terrain, only useful for this test case) + matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matWire.getAdditionalRenderState().setWireframe(true); + matWire.setColor("Color", ColorRGBA.Green); + + createSky(); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f); + heightmap.load(); + heightmap.smooth(0.9f, 1); + + } catch (Exception e) { + e.printStackTrace(); + } + + /* + * Here we create the actual terrain. The tiles will be 65x65, and the total size of the + * terrain will be 513x513. It uses the heightmap we created to generate the height values. + */ + /* + * Optimal terrain patch size is 65 (64x64). + * The total size is up to you. At 1025, it ran fine for me (200+FPS), however at + * size=2049 it got really slow. But that is a jump from 2 million to 8 million triangles... + */ + terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setModelBound(new BoundingBox()); + terrain.updateModelBound(); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(1f, 1f, 1f); + rootNode.attachChild(terrain); + + //Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); + //terrain.generateDebugTangents(debugMat); + + DirectionalLight light = new DirectionalLight(); + light.setDirection((new Vector3f(-0.1f, -0.1f, -0.1f)).normalize()); + rootNode.addLight(light); + + cam.setLocation(new Vector3f(0, 10, -10)); + cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + flyCam.setMoveSpeed(400); + + rootNode.attachChild(createAxisMarker(20)); + + renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass); + } + + public void loadHintText() { + BitmapText hintText = new BitmapText(guiFont); + hintText.setSize(guiFont.getCharSet().getRenderedSize()); + hintText.setLocalTranslation(0, getCamera().getHeight(), 0); + hintText.setText("Press T to toggle wireframe, P to toggle tri-planar texturing"); + guiNode.attachChild(hintText); + } + + private void setupKeys() { + flyCam.setMoveSpeed(50); + inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(actionListener, "wireframe"); + inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addListener(actionListener, "triPlanar"); + inputManager.addMapping("WardIso", new KeyTrigger(KeyInput.KEY_9)); + inputManager.addListener(actionListener, "WardIso"); + inputManager.addMapping("DetachControl", new KeyTrigger(KeyInput.KEY_0)); + inputManager.addListener(actionListener, "DetachControl"); + } + final private ActionListener actionListener = new ActionListener() { + + @Override + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("wireframe") && !pressed) { + wireframe = !wireframe; + if (wireframe) { + terrain.setMaterial(matWire); + } else { + terrain.setMaterial(matTerrain); + } + } else if (name.equals("triPlanar") && !pressed) { + triPlanar = !triPlanar; + if (triPlanar) { + matTerrain.setBoolean("useTriPlanarMapping", true); + // Planar textures don't use the mesh's texture coordinates but real-world coordinates, + // so we need to convert these texture coordinate scales into real-world scales, so it looks + // the same when we switch to/from tri-planar mode. (1024 is the alphamap size.) + matTerrain.setFloat("DiffuseMap_0_scale", 1f / (1024f / dirtScale)); + matTerrain.setFloat("DiffuseMap_1_scale", 1f / (1024f / darkRockScale)); + matTerrain.setFloat("DiffuseMap_2_scale", 1f / (1024f / pinkRockScale)); + matTerrain.setFloat("DiffuseMap_3_scale", 1f / (1024f / riverRockScale)); + matTerrain.setFloat("DiffuseMap_4_scale", 1f / (1024f / grassScale)); + matTerrain.setFloat("DiffuseMap_5_scale", 1f / (1024f / brickScale)); + matTerrain.setFloat("DiffuseMap_6_scale", 1f / (1024f / roadScale)); + } else { + matTerrain.setBoolean("useTriPlanarMapping", false); + + matTerrain.setFloat("DiffuseMap_0_scale", dirtScale); + matTerrain.setFloat("DiffuseMap_1_scale", darkRockScale); + matTerrain.setFloat("DiffuseMap_2_scale", pinkRockScale); + matTerrain.setFloat("DiffuseMap_3_scale", riverRockScale); + matTerrain.setFloat("DiffuseMap_4_scale", grassScale); + matTerrain.setFloat("DiffuseMap_5_scale", brickScale); + matTerrain.setFloat("DiffuseMap_6_scale", roadScale); + + + + } + } if (name.equals("DetachControl") && !pressed) { + TerrainLodControl control = terrain.getControl(TerrainLodControl.class); + if (control != null) + control.detachAndCleanUpControl(); + else { + control = new TerrainLodControl(terrain, cam); + terrain.addControl(control); + } + + } + } + }; + + private void createSky() { + Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg"); + Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg"); + Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg"); + Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg"); + Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg"); + Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg"); + + Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down); + EnvironmentProbeControl.tagGlobal(sky); + rootNode.attachChild(sky); + + rootNode.addControl(new EnvironmentProbeControl(assetManager, 256)); + rootNode.addLight(new AmbientLight(ColorRGBA.White.mult(0.05f))); + + } + + protected Node createAxisMarker(float arrowSize) { + + Material redMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + redMat.getAdditionalRenderState().setWireframe(true); + redMat.setColor("Color", ColorRGBA.Red); + + Material greenMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + greenMat.getAdditionalRenderState().setWireframe(true); + greenMat.setColor("Color", ColorRGBA.Green); + + Material blueMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + blueMat.getAdditionalRenderState().setWireframe(true); + blueMat.setColor("Color", ColorRGBA.Blue); + + Node axis = new Node(); + + // create arrows + Geometry arrowX = new Geometry("arrowX", new Arrow(new Vector3f(arrowSize, 0, 0))); + arrowX.setMaterial(redMat); + Geometry arrowY = new Geometry("arrowY", new Arrow(new Vector3f(0, arrowSize, 0))); + arrowY.setMaterial(greenMat); + Geometry arrowZ = new Geometry("arrowZ", new Arrow(new Vector3f(0, 0, arrowSize))); + arrowZ.setMaterial(blueMat); + axis.attachChild(arrowX); + axis.attachChild(arrowY); + axis.attachChild(arrowZ); + + //axis.setModelBound(new BoundingBox()); + return axis; + } +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TerrainTestAndroidRenderPath.java b/jme3-examples/src/main/java/jme3test/framegraph/TerrainTestAndroidRenderPath.java new file mode 100644 index 0000000000..b51f2db996 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TerrainTestAndroidRenderPath.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2009-2021 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.framegraph; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +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.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +/** + * Demonstrates how to use terrain on Android. + * The only difference is it uses a much smaller heightmap, so it won't use + * all the device's memory. + * + * @author bowens,johnKkk + */ +public class TerrainTestAndroidRenderPath extends SimpleApplication { + + private TerrainQuad terrain; + private Material matRock; + private Material matWire; + private boolean wireframe = false; + private boolean triPlanar = false; + private float grassScale = 64; + private float dirtScale = 16; + private float rockScale = 128; + + public static void main(String[] args) { + TerrainTestAndroidRenderPath app = new TerrainTestAndroidRenderPath(); + app.start(); + } + + @Override + public void initialize() { + super.initialize(); + + loadHintText(); + } + + @Override + public void simpleInitApp() { + setupKeys(); + + // First, we load up our textures and the heightmap texture for the terrain + + // TERRAIN TEXTURE material + matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + + // ALPHA map (for splat textures) + matRock.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + + // HEIGHTMAP image (for the terrain heightmap) + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains128.png"); + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matRock.setTexture("Tex1", grass); + matRock.setFloat("Tex1Scale", grassScale); + + // DIRT texture + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matRock.setTexture("Tex2", dirt); + matRock.setFloat("Tex2Scale", dirtScale); + + // ROCK texture + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matRock.setTexture("Tex3", rock); + matRock.setFloat("Tex3Scale", rockScale); + + // WIREFRAME material + matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matWire.getAdditionalRenderState().setWireframe(true); + matWire.setColor("Color", ColorRGBA.Green); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f); + heightmap.load(); + + } catch (Exception e) { + e.printStackTrace(); + } + + /* + * Here we create the actual terrain. The tiles will be 33x33, and the total size of the + * terrain will be 129x129. It uses the heightmap we created to generate the height values. + */ + terrain = new TerrainQuad("terrain", 33, 129, heightmap.getHeightMap()); + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator( new DistanceLodCalculator(33, 2.7f) ); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matRock); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(8f, 0.5f, 8f); + rootNode.attachChild(terrain); + + DirectionalLight light = new DirectionalLight(); + light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize()); + rootNode.addLight(light); + + cam.setLocation(new Vector3f(0, 10, -10)); + cam.setRotation(new Quaternion(0.01f, 0.964871f, -0.25966f, 0.0387f)); + } + + public void loadHintText() { + BitmapText hintText = new BitmapText(guiFont); + hintText.setSize(guiFont.getCharSet().getRenderedSize()); + hintText.setLocalTranslation(0, getCamera().getHeight(), 0); + hintText.setText("Press T to toggle wireframe, P to toggle tri-planar texturing"); + guiNode.attachChild(hintText); + } + + private void setupKeys() { + flyCam.setMoveSpeed(50); + inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(actionListener, "wireframe"); + inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addListener(actionListener, "triPlanar"); + } + final private ActionListener actionListener = new ActionListener() { + + @Override + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("wireframe") && !pressed) { + wireframe = !wireframe; + if (wireframe) { + terrain.setMaterial(matWire); + } else { + terrain.setMaterial(matRock); + } + } else if (name.equals("triPlanar") && !pressed) { + triPlanar = !triPlanar; + if (triPlanar) { + matRock.setBoolean("useTriPlanarMapping", true); + // Planar textures don't use the mesh's texture coordinates but real-world coordinates, + // so we need to convert these texture-coordinate scales into real-world scales so it looks + // the same when we switch to tri-planar mode. + matRock.setFloat("Tex1Scale", 1f / (512f / grassScale)); + matRock.setFloat("Tex2Scale", 1f / (512f / dirtScale)); + matRock.setFloat("Tex3Scale", 1f / (512f / rockScale)); + } else { + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setFloat("Tex1Scale", grassScale); + matRock.setFloat("Tex2Scale", dirtScale); + matRock.setFloat("Tex3Scale", rockScale); + } + } + } + }; +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestDeferredPBRShading.java b/jme3-examples/src/main/java/jme3test/framegraph/TestDeferredPBRShading.java new file mode 100644 index 0000000000..d67ceab37a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestDeferredPBRShading.java @@ -0,0 +1,164 @@ +package jme3test.framegraph; + +import com.jme3.app.SimpleApplication; +import com.jme3.environment.EnvironmentProbeControl; +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.AmbientLight; +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.ToneMapFilter; +import com.jme3.renderer.framegraph.FrameGraphFactory; +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.SkyFactory; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; + +public class TestDeferredPBRShading extends SimpleApplication { + private DirectionalLight dl; + + private float roughness = 0.0f; + + private Node modelNode; + private int frame = 0; + private Material pbrMat; + private Geometry model; + private Node tex; + @Override + public void simpleInitApp() { + + viewPort.setFrameGraph(FrameGraphFactory.deferred(assetManager, false)); + + roughness = 1.0f; + assetManager.registerLoader(KTXLoader.class, "ktx"); + + viewPort.setBackgroundColor(ColorRGBA.White); + modelNode = 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); + + rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray)); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + int numSamples = context.getSettings().getSamples(); + if (numSamples > 0) { + fpp.setNumSamples(numSamples); + } + +// fpp.addFilter(new FXAAFilter()); + fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(1.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); + EnvironmentProbeControl.tagGlobal(sky); + + pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m"); + model.setMaterial(pbrMat); + + rootNode.addControl(new EnvironmentProbeControl(assetManager, 256)); + + 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) { + if (tex == null) { + return; + } + if (tex.getParent() == null) { + guiNode.attachChild(tex); + } else { + tex.removeFromParent(); + } + } + + if (name.equals("rup") && isPressed) { + roughness = FastMath.clamp(roughness + 0.1f, 0.0f, 1.0f); + pbrMat.setFloat("Roughness", roughness); + } + if (name.equals("rdown") && isPressed) { + roughness = FastMath.clamp(roughness - 0.1f, 0.0f, 1.0f); + pbrMat.setFloat("Roughness", roughness); + } + + + 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", "rup", "rdown"); + + 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)); + inputManager.addMapping("rup", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addMapping("rdown", new KeyTrigger(KeyInput.KEY_G)); + } + + @Override + public void simpleUpdate(float tpf) { + frame++; + + if (frame == 2) { + + } + if (frame > 10 && modelNode.getParent() == null) { + rootNode.attachChild(modelNode); + } + } + + public static void main(String[] args) { + TestDeferredPBRShading testDeferredPBRShading = new TestDeferredPBRShading(); + testDeferredPBRShading.start(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestDeferredShading.java b/jme3-examples/src/main/java/jme3test/framegraph/TestDeferredShading.java new file mode 100644 index 0000000000..4efe0d464b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestDeferredShading.java @@ -0,0 +1,142 @@ +package jme3test.framegraph; + +import com.jme3.app.DetailedProfilerState; +import com.jme3.app.SimpleApplication; +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.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.post.filters.ToneMapFilter; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.FrameGraphFactory; +import com.jme3.renderer.framegraph.light.TiledRenderGrid; +import com.jme3.renderer.framegraph.passes.LightImagePass; +import com.jme3.scene.Geometry; +import com.jme3.scene.instancing.InstancedNode; +import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; +import com.jme3.renderer.framegraph.modules.ModuleLocator; + +/** + * https://en.wikipedia.org/wiki/Deferred_shading/ + * @author JohnKkk + */ +public class TestDeferredShading extends SimpleApplication { + + private Material material; + + public static void main(String[] args) { + TestDeferredShading app = new TestDeferredShading(); + AppSettings settings = new AppSettings(true); + settings.setWidth(768); + settings.setHeight(768); + settings.setVSync(false); + settings.setFrameRate(-1); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + + FrameGraph fg = FrameGraphFactory.deferred(assetManager, true, true); + fg.enableFeature("LightPackMethod", true); + fg.get(ModuleLocator.by(LightImagePass.class)).setMaxLights(4096); + fg.setSetting("TileInfo", new TiledRenderGrid(4, -1)); + viewPort.setFrameGraph(fg); + + stateManager.attach(new DetailedProfilerState()); + + // Test Forward +// renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass); +// renderManager.setSinglePassLightBatchSize(30); +// renderManager.setRenderPath(RenderManager.RenderPath.Forward); + //renderManager.setMaxDeferredShadingLights(1000);// Pre-allocate a maximum value for light sources to ensure the maximum number of light sources in the scene does not exceed this value. + //renderManager.setRenderPath(RenderManager.RenderPath.Deferred); + renderManager.setSinglePassLightBatchSize(200); + Quad quad = new Quad(20, 20); + Geometry geo = new Geometry("Floor", quad); + material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + material.setFloat("Shininess", 25); + material.setColor("Ambient", ColorRGBA.White); + material.setColor("Diffuse", ColorRGBA.White); + material.setColor("Specular", ColorRGBA.White); + material.setBoolean("UseMaterialColors", true); + geo.setMaterial(material); + geo.rotate((float) Math.toRadians(-90), 0, 0); + geo.setLocalTranslation(-10, 0, 10); + rootNode.attachChild(geo); + + Sphere sphere = new Sphere(15, 15, 0.1f); + Geometry sp = new Geometry("sp", sphere); + sp.setMaterial(material.clone()); + sp.getMaterial().setBoolean("UseInstancing", true); + ColorRGBA colors[] = new ColorRGBA[] { + ColorRGBA.White, + ColorRGBA.Red, + ColorRGBA.Blue, + ColorRGBA.Green, + ColorRGBA.Yellow, + ColorRGBA.Orange, + ColorRGBA.Brown, + }; + + InstancedNode instancedNode = new InstancedNode("sp"); + for(int i = 0;i < 2000;i++){ + PointLight pl = new PointLight(); + pl.setColor(colors[i % colors.length]); + pl.setPosition(new Vector3f(FastMath.nextRandomFloat(-5.0f, 5.0f), 0.1f, FastMath.nextRandomFloat(-5.0f, 5.0f))); +// pl.setPosition(new Vector3f(0, 1, 0)); + pl.setRadius(1.0f); + rootNode.addLight(pl); + Geometry g = sp.clone(false); +// g.getMaterial().setColor("Ambient", ColorRGBA.Gray); +// g.getMaterial().setColor("Diffuse", colors[i % colors.length]); + g.setLocalTranslation(pl.getPosition()); + instancedNode.attachChild(g); + } + instancedNode.instance(); + rootNode.attachChild(instancedNode); + + +// AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f)); +// rootNode.addLight(ambientLight); +// DirectionalLight sun = new DirectionalLight(); +// sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal()); +// sun.setColor(ColorRGBA.Gray); +// rootNode.addLight(sun); + + + //cam.setLocation(new Vector3f(0, 2, 0)); + cam.lookAtDirection(Vector3f.UNIT_Z.negate(), Vector3f.UNIT_Y); + flyCam.setMoveSpeed(10.0f); + flyCam.setDragToRotate(true); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + int numSamples = context.getSettings().getSamples(); + if (numSamples > 0) { + fpp.setNumSamples(numSamples); + } + + BloomFilter bloom=new BloomFilter(); + bloom.setDownSamplingFactor(1); + bloom.setBlurScale(1.1f); + bloom.setExposurePower(1.30f); + bloom.setExposureCutOff(0.3f); + bloom.setBloomIntensity(1.15f); + fpp.addFilter(bloom); + + fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(2.5f))); + viewPort.addProcessor(fpp); + } + + @Override + public void simpleUpdate(float tpf) { + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestDeferredShadingPathShadow.java b/jme3-examples/src/main/java/jme3test/framegraph/TestDeferredShadingPathShadow.java new file mode 100644 index 0000000000..24a5c89981 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestDeferredShadingPathShadow.java @@ -0,0 +1,94 @@ +package jme3test.framegraph; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +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.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.framegraph.FrameGraphFactory; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Quad; +import com.jme3.shadow.DirectionalLightShadowFilter; + +/** + * Under the deferred rendering path, only screen space post processing shadows can be used.
    + * @author JohnKkk + */ +public class TestDeferredShadingPathShadow extends SimpleApplication implements ActionListener { + + @Override + public void simpleInitApp() { + + //renderManager.setFrameGraph(RenderPipelineFactory.create(this, RenderManager.RenderPath.Forward)); + viewPort.setFrameGraph(FrameGraphFactory.deferred(assetManager, true)); + + Material boxMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Node tank = (Node) assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml"); + tank.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + tank.setLocalScale(0.3f); + rootNode.attachChild(tank); + + Quad plane = new Quad(10, 10); + Geometry planeGeo = new Geometry("Plane", plane); + planeGeo.setShadowMode(RenderQueue.ShadowMode.Receive); + planeGeo.rotate(-45, 0, 0); + planeGeo.setLocalTranslation(-5, -5, 0); + Material planeMat = boxMat.clone(); + planeMat.setBoolean("UseMaterialColors", true); + planeMat.setColor("Ambient", ColorRGBA.White); + planeMat.setColor("Diffuse", ColorRGBA.Gray); + planeGeo.setMaterial(planeMat); + rootNode.attachChild(planeGeo); + + + DirectionalLight sun = new DirectionalLight(); + sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal()); + sun.setColor(ColorRGBA.White); + rootNode.addLight(sun); + DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 1024, 1); + dlsf.setLight(sun); + + sun = new DirectionalLight(); + sun.setDirection((new Vector3f(0.5f, -0.5f, -0.5f)).normalizeLocal()); + sun.setColor(ColorRGBA.White); + rootNode.addLight(sun); + DirectionalLightShadowFilter dlsf2 = new DirectionalLightShadowFilter(assetManager, 1024, 1); + dlsf2.setLight(sun); + + sun = new DirectionalLight(); + sun.setDirection((new Vector3f(0.0f, -0.5f, -0.5f)).normalizeLocal()); + sun.setColor(ColorRGBA.White); + rootNode.addLight(sun); + DirectionalLightShadowFilter dlsf3 = new DirectionalLightShadowFilter(assetManager, 1024, 1); + dlsf3.setLight(sun); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(dlsf); + fpp.addFilter(dlsf2); + fpp.addFilter(dlsf3); + viewPort.addProcessor(fpp); + + inputManager.addListener(this, "toggleRenderPath"); + inputManager.addMapping("toggleRenderPath", new KeyTrigger(KeyInput.KEY_SPACE)); + + flyCam.setMoveSpeed(20.0f); + } + + public static void main(String[] args) { + TestDeferredShadingPathShadow testDeferredShadingPathShadow = new TestDeferredShadingPathShadow(); + testDeferredShadingPathShadow.start(); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + + } +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestFrameGraphImportExport.java b/jme3-examples/src/main/java/jme3test/framegraph/TestFrameGraphImportExport.java new file mode 100644 index 0000000000..c7685d174e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestFrameGraphImportExport.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 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.framegraph; + +import com.jme3.app.DetailedProfilerState; +import com.jme3.app.SimpleApplication; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.export.xml.XMLExporter; +import com.jme3.export.xml.XMLImporter; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.FrameGraphFactory; +import com.jme3.system.AppSettings; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Tests framegraph export and import with xml and binary formats. + *

    + * The test succeeds if both export and import operations for both xml and binary + * formats functions without error. For importing, the resulting framegraph must + * show up on the detailed profiler gui. + *

    + * The test fails if any errors occur, or the framegraph does not show up on the + * detailed profiler gui after importing. + *

    + * The test application closes after exporting. + * + * @author codex + */ +public class TestFrameGraphImportExport extends SimpleApplication { + + private final String path = System.getProperty("user.home")+"/myGraph"; + private final boolean export = true; + private final boolean xml = false; + + public static void main(String[] args) { + TestFrameGraphImportExport app = new TestFrameGraphImportExport(); + AppSettings as = new AppSettings(true); + as.setWidth(768); + as.setHeight(768); + app.setSettings(as); + app.start(); + } + + @Override + public void simpleInitApp() { + + final File file = new File(path+"."+(xml ? "xml" : "j3g")); + + if (export) { + FrameGraph graph = FrameGraphFactory.forward(assetManager); + try { + if (xml) { + XMLExporter.getInstance().save(graph.createModuleData(), file); + } else { + BinaryExporter.getInstance().save(graph.createModuleData(), file); + } + } catch (IOException ex) { + Logger.getLogger(TestFrameGraphImportExport.class.getName()).log(Level.SEVERE, null, ex); + } + stop(); + } else { + stateManager.attach(new DetailedProfilerState()); + flyCam.setDragToRotate(true); + try { + FrameGraph graph = new FrameGraph(assetManager); + if (xml) { + graph.applyData(XMLImporter.getInstance().load(file)); + } else { + graph.applyData(BinaryImporter.getInstance().load(file)); + } + viewPort.setFrameGraph(graph); + } catch (IOException ex) { + Logger.getLogger(TestFrameGraphImportExport.class.getName()).log(Level.SEVERE, null, ex); + } + } + + } + +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestFrameGraphLoadingSpeeds.java b/jme3-examples/src/main/java/jme3test/framegraph/TestFrameGraphLoadingSpeeds.java new file mode 100644 index 0000000000..d1ac0a61be --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestFrameGraphLoadingSpeeds.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2009-2021 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.framegraph; + +import com.jme3.app.SimpleApplication; +import com.jme3.renderer.framegraph.FrameGraph; + +/** + * Tests framegraph loading speeds. + *

    + * The test loads a total of 6 framegraphs (3 forward and 3 deferred) + * after a delay of 100 frames, with 10 frames seperating each load. After all loading + * is complete, the application quits. Results are printed to the console as milliseconds + * each load took to complete. + *

    + * In general, earlier loads take around 15ms, and later loads take around 2ms. + *

    + * Note that framegraph data are not cached. + * + * @author codex + */ +public class TestFrameGraphLoadingSpeeds extends SimpleApplication { + + private int frameDelay = 100; + private int loadIndex = 0; + + public static void main(String[] args) { + new TestFrameGraphLoadingSpeeds().start(); + } + + @Override + public void simpleInitApp() {} + @Override + public void simpleUpdate(float tpf) { + if (--frameDelay <= 0) { + switch (loadIndex++) { + case 0: + case 2: + case 4: load(loadIndex, "forward", "Common/FrameGraphs/Forward.j3g"); + break; + case 1: + case 3: + case 5: load(loadIndex, "deferred", "Common/FrameGraphs/Deferred.j3g"); + break; + default: stop(); + } + frameDelay = 10; + } + } + + private FrameGraph load(int index, String name, String path) { + long timeBefore = System.currentTimeMillis(); + FrameGraph fg = new FrameGraph(assetManager, path); + long timeAfter = System.currentTimeMillis(); + System.out.println(index+": "+name+": "+(timeAfter-timeBefore)+"ms"); + return fg; + } + +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestPBRTerrainAdvancedRenderPath.java b/jme3-examples/src/main/java/jme3test/framegraph/TestPBRTerrainAdvancedRenderPath.java new file mode 100644 index 0000000000..29e0b3c380 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestPBRTerrainAdvancedRenderPath.java @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2009-2021 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.framegraph; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.LightProbe; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.shader.VarType; +import com.jme3.system.AppSettings; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.TextureArray; + +import java.util.ArrayList; +import java.util.List; + +/** + * This test uses 'AdvancedPBRTerrain.j3md' to create a terrain Material with + * more textures than 'PBRTerrain.j3md' can handle. + * + * Upon running the app, the user should see a mountainous, terrain-based + * landscape with some grassy areas, some snowy areas, and some tiled roads and + * gravel paths weaving between the valleys. Snow should be slightly + * shiny/reflective, and marble texture should be even shinier. If you would + * like to know what each texture is supposed to look like, you can find the + * textures used for this test case located in jme3-testdata. (Screenshots + * showing how this test-case should look will also be available soon so you can + * compare your results, and I will replace this comment with a link to their + * location as soon as they are posted.) + * + * Press 'p' to toggle tri-planar mode. Enabling tri-planar mode should prevent + * stretching of textures in steep areas of the terrain. + * + * Press 'n' to toggle between night and day. Pressing 'n' will cause the light + * to gradually fade darker/brighter until the min/max lighting levels are + * reached. At night the scene should be noticeably darker, and the marble and + * tiled-road texture should be noticeably glowing from the emissiveColors and + * the emissiveIntensity map that is packed into the alpha channel of the + * MetallicRoughness maps. + * + * The MetallicRoughness map stores: + *

      + *
    • AmbientOcclusion in the Red channel
    • + *
    • Roughness in the Green channel
    • + *
    • Metallic in the Blue channel
    • + *
    • EmissiveIntensity in the Alpha channel
    • + *
    + * + * The shaders are still subject to the GLSL max limit of 16 textures, however + * each TextureArray counts as a single texture, and each TextureArray can store + * multiple images. For more information on texture arrays see: + * https://www.khronos.org/opengl/wiki/Array_Texture + * + * Uses assets from CC0Textures.com, licensed under CC0 1.0 Universal. For more + * information on the textures this test case uses, view the license.txt file + * located in the jme3-testdata directory where these textures are located: + * jme3-testdata/src/main/resources/Textures/Terrain/PBR + * + *

    + * Notes: (as of 12 April 2021) + *

      + *
    1. + * The results look better with anti-aliasing, especially from a distance. This + * may be due to the way that the terrain is generated from a heightmap, as + * these same textures do not have this issue in my other project. + *
    2. + *
    3. + * The number of images per texture array may still be limited by + * GL_MAX_ARRAY_TEXTURE_LAYERS, however this value should be high enough that + * users will likely run into issues with extremely low FPS from too many + * texture-reads long before you surpass the limit of texture-layers per + * textureArray. If this ever becomes an issue, a secondary set of + * Albedo/Normal/MetallicRoughness texture arrays could be added to the shader + * to store any textures that surpass the limit of the primary textureArrays. + *
    4. + *
    + * + * @author yaRnMcDonuts,johnKkk + */ +public class TestPBRTerrainAdvancedRenderPath extends SimpleApplication { + + private TerrainQuad terrain; + private Material matTerrain; + private boolean triPlanar = false; + + private final int terrainSize = 512; + private final int patchSize = 256; + private final float dirtScale = 24; + private final float darkRockScale = 24; + private final float snowScale = 64; + private final float tileRoadScale = 64; + private final float grassScale = 24; + private final float marbleScale = 64; + private final float gravelScale = 64; + + private final ColorRGBA tilesEmissiveColor = new ColorRGBA(0.12f, 0.02f, 0.23f, 0.85f); //dim magenta emission + private final ColorRGBA marbleEmissiveColor = new ColorRGBA(0.0f, 0.0f, 1.0f, 1.0f); //fully saturated blue emission + + private AmbientLight ambientLight; + private DirectionalLight directionalLight; + private boolean isNight = false; + + private final float dayLightIntensity = 1.0f; + private final float nightLightIntensity = 0.03f; + + private BitmapText keybindingsText; + + private final float camMoveSpeed = 50f; + + public static void main(String[] args) { + TestPBRTerrainAdvancedRenderPath app = new TestPBRTerrainAdvancedRenderPath(); + AppSettings settings = new AppSettings(true); + settings.setWidth(768); + settings.setHeight(768); + app.setSettings(settings); + app.start(); + } + + private final ActionListener actionListener = new ActionListener() { + @Override + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("triPlanar") && !pressed) { + triPlanar = !triPlanar; + if (triPlanar) { + matTerrain.setBoolean("useTriPlanarMapping", true); + // Tri-planar textures don't use the mesh's texture coordinates but real world coordinates, + // so we need to convert these texture coordinate scales into real world scales so it looks + // the same when we switch to/from tri-planar mode. + matTerrain.setFloat("AlbedoMap_0_scale", (dirtScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_1_scale", (darkRockScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_2_scale", (snowScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_3_scale", (tileRoadScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_4_scale", (grassScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_5_scale", (marbleScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_6_scale", (gravelScale / terrainSize)); + } else { + matTerrain.setBoolean("useTriPlanarMapping", false); + + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + } + } + if (name.equals("toggleNight") && !pressed) { + isNight = !isNight; + // Ambient and directional light are faded smoothly in update loop below. + } + } + }; + + @Override + public void simpleInitApp() { + + //viewPort.setFrameGraph(FrameGraphFactory.deferred(assetManager, false)); + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(50); + + setupKeys(); + setUpTerrain(); + setUpTerrainMaterial(); + setUpLights(); + setUpCamera(); + + } + + private void setUpTerrainMaterial() { + // advanced PBR terrain matdef + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md"); + + matTerrain.setBoolean("useTriPlanarMapping", false); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png")); + matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png")); + // this material also supports 'AlphaMap_2', so you can get up to 12 texture slots + + // load textures for texture arrays + // These MUST all have the same dimensions and format in order to be put into a texture array. + //ALBEDO MAPS + Texture dirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + Texture darkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Color.png"); + Texture snow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Color.png"); + Texture tileRoad = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Color.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + Texture marble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Color.png"); + Texture gravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Color.png"); + + // NORMAL MAPS + Texture normalMapDirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground036_1K_Normal.png"); + Texture normalMapDarkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Normal.png"); + Texture normalMapSnow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Normal.png"); + Texture normalMapGravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Normal.png"); + Texture normalMapGrass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Normal.png"); + Texture normalMapMarble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Normal.png"); + Texture normalMapRoad = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Normal.png"); + + //PACKED METALLIC/ROUGHNESS / AMBIENT OCCLUSION / EMISSIVE INTENSITY MAPS + Texture metallicRoughnessAoEiMapDirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground036_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapDarkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapSnow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapGravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel_015_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapGrass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapMarble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapRoad = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_PackedMetallicRoughnessMap.png"); + + // put all images into lists to create texture arrays. + // + // The index of each image in its list will be + // sent to the material to tell the shader to choose that texture from + // the textureArray when setting up a texture slot's mat params. + // + List albedoImages = new ArrayList<>(); + List normalMapImages = new ArrayList<>(); + List metallicRoughnessAoEiMapImages = new ArrayList<>(); + + albedoImages.add(dirt.getImage()); //0 + albedoImages.add(darkRock.getImage()); //1 + albedoImages.add(snow.getImage()); //2 + albedoImages.add(tileRoad.getImage()); //3 + albedoImages.add(grass.getImage()); //4 + albedoImages.add(marble.getImage()); //5 + albedoImages.add(gravel.getImage()); //6 + + normalMapImages.add(normalMapDirt.getImage()); //0 + normalMapImages.add(normalMapDarkRock.getImage()); //1 + normalMapImages.add(normalMapSnow.getImage()); //2 + normalMapImages.add(normalMapRoad.getImage()); //3 + normalMapImages.add(normalMapGrass.getImage()); //4 + normalMapImages.add(normalMapMarble.getImage()); //5 + normalMapImages.add(normalMapGravel.getImage()); //6 + + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapDirt.getImage()); //0 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapDarkRock.getImage()); //1 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapSnow.getImage()); //2 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapRoad.getImage()); //3 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapGrass.getImage()); //4 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapMarble.getImage()); //5 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapGravel.getImage()); //6 + + //initiate texture arrays + TextureArray albedoTextureArray = new TextureArray(albedoImages); + TextureArray normalParallaxTextureArray = new TextureArray(normalMapImages); // parallax is not used currently + TextureArray metallicRoughnessAoEiTextureArray = new TextureArray(metallicRoughnessAoEiMapImages); + + //apply wrapMode to the whole texture array, rather than each individual texture in the array + albedoTextureArray.setWrap(WrapMode.Repeat); + normalParallaxTextureArray.setWrap(WrapMode.Repeat); + metallicRoughnessAoEiTextureArray.setWrap(WrapMode.Repeat); + + //assign texture array to materials + matTerrain.setParam("AlbedoTextureArray", VarType.TextureArray, albedoTextureArray); + matTerrain.setParam("NormalParallaxTextureArray", VarType.TextureArray, normalParallaxTextureArray); + matTerrain.setParam("MetallicRoughnessAoEiTextureArray", VarType.TextureArray, metallicRoughnessAoEiTextureArray); + + //set up texture slots: + matTerrain.setInt("AlbedoMap_0", 0); // dirt is index 0 in the albedo image list + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("Roughness_0", 1); + matTerrain.setFloat("Metallic_0", 0.02f); + + matTerrain.setInt("AlbedoMap_1", 1); // darkRock is index 1 in the albedo image list + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("Roughness_1", 1); + matTerrain.setFloat("Metallic_1", 0.04f); + + matTerrain.setInt("AlbedoMap_2", 2); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("Roughness_2", 0.72f); + matTerrain.setFloat("Metallic_2", 0.12f); + + matTerrain.setInt("AlbedoMap_3", 3); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("Roughness_3", 1); + matTerrain.setFloat("Metallic_3", 0.04f); + + matTerrain.setInt("AlbedoMap_4", 4); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("Roughness_4", 1); + matTerrain.setFloat("Metallic_4", 0); + + matTerrain.setInt("AlbedoMap_5", 5); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("Roughness_5", 1); + matTerrain.setFloat("Metallic_5", 0.2f); + + matTerrain.setInt("AlbedoMap_6", 6); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + matTerrain.setFloat("Roughness_6", 1); + matTerrain.setFloat("Metallic_6", 0.01f); + + // NORMAL MAPS + // int being passed to shader corresponds to the index of the texture's + // image in the List of images used to create its texture array + matTerrain.setInt("NormalMap_0", 0); + matTerrain.setInt("NormalMap_1", 1); + matTerrain.setInt("NormalMap_2", 2); + matTerrain.setInt("NormalMap_3", 3); + matTerrain.setInt("NormalMap_4", 4); + matTerrain.setInt("NormalMap_5", 5); + matTerrain.setInt("NormalMap_6", 6); + + //METALLIC/ROUGHNESS/AO/EI MAPS + matTerrain.setInt("MetallicRoughnessMap_0", 0); + matTerrain.setInt("MetallicRoughnessMap_1", 1); + matTerrain.setInt("MetallicRoughnessMap_2", 2); + matTerrain.setInt("MetallicRoughnessMap_3", 3); + matTerrain.setInt("MetallicRoughnessMap_4", 4); + matTerrain.setInt("MetallicRoughnessMap_5", 5); + matTerrain.setInt("MetallicRoughnessMap_6", 6); + + //EMISSIVE + matTerrain.setColor("EmissiveColor_5", marbleEmissiveColor); + matTerrain.setColor("EmissiveColor_3", tilesEmissiveColor); + //these two texture slots (marble & tiledRoad, indexed in each texture array at 5 and 3 respectively) both + // have packed MRAoEi maps with an emissiveTexture packed into the alpha channel + +// matTerrain.setColor("EmissiveColor_1", new ColorRGBA(0.08f, 0.01f, 0.1f, 0.4f)); +//this texture slot does not have a unique emissiveIntensityMap packed into its MRAoEi map, + // so setting an emissiveColor will apply equal intensity to every pixel + + terrain.setMaterial(matTerrain); + } + + private void setupKeys() { + flyCam.setMoveSpeed(50); + inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addMapping("toggleNight", new KeyTrigger(KeyInput.KEY_N)); + + inputManager.addListener(actionListener, "triPlanar"); + inputManager.addListener(actionListener, "toggleNight"); + + keybindingsText = new BitmapText(assetManager.loadFont("Interface/Fonts/Default.fnt")); + keybindingsText.setText("Press 'N' to toggle day/night fade (takes a moment) \nPress 'P' to toggle tri-planar mode"); + + getGuiNode().attachChild(keybindingsText); + keybindingsText.move(new Vector3f(200, 120, 0)); + } + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + //smoothly transition from day to night + float currentLightIntensity = ambientLight.getColor().getRed(); + float incrementPerFrame = tpf * 0.3f; + + if (isNight) { + if (ambientLight.getColor().getRed() > nightLightIntensity) { + currentLightIntensity -= incrementPerFrame; + if (currentLightIntensity < nightLightIntensity) { + currentLightIntensity = nightLightIntensity; + } + + ambientLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + directionalLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + } + } else { + if (ambientLight.getColor().getRed() < dayLightIntensity) { + currentLightIntensity += incrementPerFrame; + if (currentLightIntensity > dayLightIntensity) { + currentLightIntensity = dayLightIntensity; + } + + ambientLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + directionalLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + } + } + } + + private void setUpTerrain() { + // HEIGHTMAP image (for the terrain heightmap) + TextureKey hmKey = new TextureKey("Textures/Terrain/splat/mountains512.png", false); + Texture heightMapImage = assetManager.loadTexture(hmKey); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f); + heightmap.load(); + heightmap.smooth(0.9f, 1); + + } catch (Exception e) { + e.printStackTrace(); + } + + terrain = new TerrainQuad("terrain", patchSize + 1, terrainSize + 1, heightmap.getHeightMap()); +//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 2.7f)); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(1f, 1f, 1f); + rootNode.attachChild(terrain); + } + + private void setUpLights() { + LightProbe probe = (LightProbe) assetManager.loadAsset("Scenes/LightProbes/quarry_Probe.j3o"); + + probe.setAreaType(LightProbe.AreaType.Spherical); + probe.getArea().setRadius(2000); + probe.getArea().setCenter(new Vector3f(0, 0, 0)); + rootNode.addLight(probe); + + directionalLight = new DirectionalLight(); + directionalLight.setDirection((new Vector3f(-0.3f, -0.5f, -0.3f)).normalize()); + directionalLight.setColor(ColorRGBA.White); + rootNode.addLight(directionalLight); + +// for (int i = 0; i < 30; i++) { +// for (int j = 0; j < 30; j++) { +// PointLight p = new PointLight(); +// p.setPosition(new Vector3f(-50+i*10, -60, -50+j*10)); +// p.setRadius(50); +// p.setColor(ColorRGBA.randomColor()); +// rootNode.addLight(p); +// } +// } + + ambientLight = new AmbientLight(); + directionalLight.setColor(ColorRGBA.White); + rootNode.addLight(ambientLight); + } + + private void setUpCamera() { + cam.setLocation(new Vector3f(0, 10, -10)); + cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + + getFlyByCamera().setMoveSpeed(camMoveSpeed); + //flyCam.setEnabled(false); + //inputManager.setCursorVisible(true); + } +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestPBRTerrainRenderPath.java b/jme3-examples/src/main/java/jme3test/framegraph/TestPBRTerrainRenderPath.java new file mode 100644 index 0000000000..4f6eadc0ba --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestPBRTerrainRenderPath.java @@ -0,0 +1,473 @@ +package jme3test.framegraph; + +/* + * Copyright (c) 2009-2021 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. + */ +import com.jme3.app.DetailedProfilerState; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.LightProbe; +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.post.FilterPostProcessor; +import com.jme3.post.filters.ToneMapFilter; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.FrameGraphFactory; +import com.jme3.renderer.framegraph.light.TiledRenderGrid; +import com.jme3.renderer.framegraph.passes.LightImagePass; +import com.jme3.system.AppSettings; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.renderer.framegraph.modules.ModuleLocator; + +/** + * This test uses 'PBRTerrain.j3md' to create a terrain Material for PBR. + * + * Upon running the app, the user should see a mountainous, terrain-based + * landscape with some grassy areas, some snowy areas, and some tiled roads and + * gravel paths weaving between the valleys. Snow should be slightly + * shiny/reflective, and marble texture should be even shinier. If you would + * like to know what each texture is supposed to look like, you can find the + * textures used for this test case located in jme3-testdata. (Screenshots + * showing how this test-case should look will also be available soon so you can + * compare your results, and I will replace this comment with a link to their + * location as soon as they are posted.) + * + * Press 'p' to toggle tri-planar mode. Enabling tri-planar mode should prevent + * stretching of textures in steep areas of the terrain. + * + * Press 'n' to toggle between night and day. Pressing 'n' will cause the light + * to gradually fade darker/brighter until the min/max lighting levels are + * reached. At night the scene should be noticeably darker. + * + * Uses assets from CC0Textures.com, licensed under CC0 1.0 Universal. For more + * information on the textures this test case uses, view the license.txt file + * located in the jme3-testdata directory where these textures are located: + * jme3-testdata/src/main/resources/Textures/Terrain/PBR + * + *

    + * Notes: (as of 12 April 2021) + *

      + *
    1. + * This shader is subject to the GLSL max limit of 16 textures, and users should + * consider using "AdvancedPBRTerrain.j3md" instead if they need additional + * texture slots. + *
    2. + *
    + * + * @author yaRnMcDonuts,johnKkk + */ +public class TestPBRTerrainRenderPath extends SimpleApplication { + + private TerrainQuad terrain; + private Material matTerrain; + private boolean triPlanar = false; + + private final int terrainSize = 512; + private final int patchSize = 256; + private final float dirtScale = 24; + private final float darkRockScale = 24; + private final float snowScale = 64; + private final float tileRoadScale = 64; + private final float grassScale = 24; + private final float marbleScale = 64; + private final float gravelScale = 64; + + private AmbientLight ambientLight; + private DirectionalLight directionalLight; + private PointLight[] pointLights; + private int currentPointLightNum = 700; + private boolean isNight = true; + + private final float dayLightIntensity = 1.0f; + private final float nightLightIntensity = 0.03f; + + private BitmapText keybindingsText; + private BitmapText currentPointLightsText; + + private final float camMoveSpeed = 50f; + + public static void main(String[] args) { + TestPBRTerrainRenderPath app = new TestPBRTerrainRenderPath(); + AppSettings appSettings = new AppSettings(true); + // For this scene, use a tileSize=64 configuration (at 1600*900 resolution) + // TileSize 64 + appSettings.setWidth(768); + appSettings.setHeight(768); + appSettings.setVSync(false); + app.setSettings(appSettings); + app.showSettings = false; +// appSettings.setRenderer(AppSettings.LWJGL_OPENGL33); + app.start(); + } + + private final ActionListener actionListener = new ActionListener() { + @Override + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("triPlanar") && !pressed) { + triPlanar = !triPlanar; + if (triPlanar) { + matTerrain.setBoolean("useTriPlanarMapping", true); + // Tri-planar textures don't use the mesh's texture coordinates but real world coordinates, + // so we need to convert these texture coordinate scales into real world scales so it looks + // the same when we switch to/from tri-planar mode. + matTerrain.setFloat("AlbedoMap_0_scale", (dirtScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_1_scale", (darkRockScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_2_scale", (snowScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_3_scale", (tileRoadScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_4_scale", (grassScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_5_scale", (marbleScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_6_scale", (gravelScale / terrainSize)); + } else { + matTerrain.setBoolean("useTriPlanarMapping", false); + + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + } + } + if (name.equals("toggleNight") && !pressed) { + isNight = !isNight; + // Ambient and directional light are faded smoothly in update loop below. + } + if (name.equals("delPointLight") && !pressed) { + if(currentPointLightNum > 0){ + for(int i = 0;i < 10;i++){ + pointLights[currentPointLightNum - i - 1].setEnabled(false); + } + currentPointLightNum-=10; + currentPointLightsText.setText("Current PointLights[" + currentPointLightNum + "],Press '1' addPointLight,Press '2' delPointLight"); + } + } + else if(name.equals("addPointLight") && !pressed){ + if(currentPointLightNum < 1000){ + for(int i = 0;i < 10;i++){ + pointLights[currentPointLightNum + i].setEnabled(true); + } + currentPointLightNum+=10; + currentPointLightsText.setText("Current PointLights[" + currentPointLightNum + "],Press '1' addPointLight,Press '2' delPointLight"); + } + } + } + }; + + @Override + public void simpleInitApp() { + + FrameGraph fg = FrameGraphFactory.deferred(assetManager, true); + fg.get(ModuleLocator.by(LightImagePass.class)).setMaxLights(1024); + fg.setSetting("TileInfo", new TiledRenderGrid(7, -1)); + //viewPort.setFrameGraph(fg); + flyCam.setDragToRotate(true); + + // For this scene, use a tileSize=64 configuration (at 1600*900 resolution) + //renderManager.setForceTileSize(64);// 1600 * 900 resolution config + setupKeys(); + setUpTerrain(); + setUpTerrainMaterial(); + setUpLights(); + setUpCamera(); + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + int numSamples = context.getSettings().getSamples(); + if (numSamples > 0) { + fpp.setNumSamples(numSamples); + } + +// fpp.addFilter(new FXAAFilter()); + fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(1.0f))); +// fpp.addFilter(new SSAOFilter(0.5f, 3, 0.2f, 0.2f)); + viewPort.addProcessor(fpp); + } + + private void setUpTerrainMaterial() { + // PBR terrain matdef + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/PBRTerrain.j3md"); + + matTerrain.setBoolean("useTriPlanarMapping", false); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png")); + matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png")); + // this material also supports 'AlphaMap_2', so you can get up to 12 diffuse textures + + // DIRT texture, Diffuse textures 0 to 3 use the first AlphaMap + Texture dirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + dirt.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_0", dirt); + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("Roughness_0", 1); + matTerrain.setFloat("Metallic_0", 0); + //matTerrain.setInt("AfflictionMode_0", 0); + + // DARK ROCK texture + Texture darkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Color.png"); + darkRock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_1", darkRock); + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("Roughness_1", 0.92f); + matTerrain.setFloat("Metallic_1", 0.02f); + //matTerrain.setInt("AfflictionMode_1", 0); + + // SNOW texture + Texture snow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Color.png"); + snow.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_2", snow); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("Roughness_2", 0.55f); + matTerrain.setFloat("Metallic_2", 0.12f); + + Texture tiles = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Color.png"); + tiles.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_3", tiles); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("Roughness_3", 0.87f); + matTerrain.setFloat("Metallic_3", 0.08f); + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + grass.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_4", grass); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("Roughness_4", 1); + matTerrain.setFloat("Metallic_4", 0); + + // MARBLE texture + Texture marble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Color.png"); + marble.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_5", marble); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("Roughness_5", 0.06f); + matTerrain.setFloat("Metallic_5", 0.8f); + + // Gravel texture + Texture gravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Color.png"); + gravel.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_6", gravel); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + matTerrain.setFloat("Roughness_6", 0.9f); + matTerrain.setFloat("Metallic_6", 0.07f); + // NORMAL MAPS + Texture normalMapDirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground036_1K_Normal.png"); + normalMapDirt.setWrap(WrapMode.Repeat); + + Texture normalMapDarkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Normal.png"); + normalMapDarkRock.setWrap(WrapMode.Repeat); + + Texture normalMapSnow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Normal.png"); + normalMapSnow.setWrap(WrapMode.Repeat); + + Texture normalMapGravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Normal.png"); + normalMapGravel.setWrap(WrapMode.Repeat); + + Texture normalMapGrass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Normal.png"); + normalMapGrass.setWrap(WrapMode.Repeat); + +// Texture normalMapMarble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Normal.png"); +// normalMapMarble.setWrap(WrapMode.Repeat); + + Texture normalMapTiles = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Normal.png"); + normalMapTiles.setWrap(WrapMode.Repeat); + + matTerrain.setTexture("NormalMap_0", normalMapDirt); + matTerrain.setTexture("NormalMap_1", normalMapDarkRock); + matTerrain.setTexture("NormalMap_2", normalMapSnow); + matTerrain.setTexture("NormalMap_3", normalMapTiles); + matTerrain.setTexture("NormalMap_4", normalMapGrass); +// matTerrain.setTexture("NormalMap_5", normalMapMarble); // Adding this texture would exceed the 16 texture limit. + matTerrain.setTexture("NormalMap_6", normalMapGravel); + + terrain.setMaterial(matTerrain); + //new RenderPathHelper(this, new Vector3f(0, cam.getHeight() / 2, 0), KeyInput.KEY_K, "K"); + getStateManager().attach(new DetailedProfilerState()); + } + + private void setupKeys() { + flyCam.setMoveSpeed(50); + inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addMapping("toggleNight", new KeyTrigger(KeyInput.KEY_N)); + inputManager.addMapping("addPointLight", new KeyTrigger(KeyInput.KEY_1)); + inputManager.addMapping("delPointLight", new KeyTrigger(KeyInput.KEY_2)); + + inputManager.addListener(actionListener, "triPlanar"); + inputManager.addListener(actionListener, "toggleNight"); + inputManager.addListener(actionListener, "addPointLight"); + inputManager.addListener(actionListener, "delPointLight"); + + keybindingsText = new BitmapText(assetManager.loadFont("Interface/Fonts/Default.fnt")); + keybindingsText.setText("Press 'N' to toggle day/night fade (takes a moment) \nPress 'P' to toggle tri-planar mode"); + + getGuiNode().attachChild(keybindingsText); + keybindingsText.move(new Vector3f(200, 120, 0)); + + currentPointLightsText = new BitmapText(assetManager.loadFont("Interface/Fonts/Default.fnt")); + currentPointLightsText.setText("Current PointLights[" + currentPointLightNum + "],Press '1' addPointLight,Press '2' delPointLight"); + getGuiNode().attachChild(currentPointLightsText); + currentPointLightsText.move(new Vector3f(200, 200, 0)); + } + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + //smoothly transition from day to night + float currentLightIntensity = ambientLight.getColor().getRed(); + float incrementPerFrame = tpf * 0.3f; + + if (isNight) { + if (ambientLight.getColor().getRed() > nightLightIntensity) { + currentLightIntensity -= incrementPerFrame; + if (currentLightIntensity < nightLightIntensity) { + currentLightIntensity = nightLightIntensity; + } + + ambientLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + directionalLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + } + } else { + if (ambientLight.getColor().getRed() < dayLightIntensity) { + currentLightIntensity += incrementPerFrame; + if (currentLightIntensity > dayLightIntensity) { + currentLightIntensity = dayLightIntensity; + } + + ambientLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + directionalLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + } + } + } + + private void setUpTerrain() { + // HEIGHTMAP image (for the terrain heightmap) + TextureKey hmKey = new TextureKey("Textures/Terrain/splat/mountains512.png", false); + Texture heightMapImage = assetManager.loadTexture(hmKey); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f); + heightmap.load(); + heightmap.smooth(0.9f, 1); + + } catch (Exception e) { + e.printStackTrace(); + } + + terrain = new TerrainQuad("terrain", patchSize + 1, terrainSize + 1, heightmap.getHeightMap()); +//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 2.7f)); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(1f, 1f, 1f); + rootNode.attachChild(terrain); + } + + private void setUpLights() { + LightProbe probe = (LightProbe) assetManager.loadAsset("Scenes/LightProbes/quarry_Probe.j3o"); + + probe.setAreaType(LightProbe.AreaType.Spherical); + probe.getArea().setRadius(2000); + probe.getArea().setCenter(new Vector3f(0, 0, 0)); + rootNode.addLight(probe); + + directionalLight = new DirectionalLight(); + directionalLight.setDirection((new Vector3f(-0.3f, -0.5f, -0.3f)).normalize()); + directionalLight.setColor(ColorRGBA.White); + rootNode.addLight(directionalLight); + + ambientLight = new AmbientLight(); + directionalLight.setColor(ColorRGBA.White); + rootNode.addLight(ambientLight); + + + // pointLights + currentPointLightNum = 1000; + ColorRGBA colors[] = new ColorRGBA[]{ + ColorRGBA.White, + ColorRGBA.Red, + ColorRGBA.Blue, + ColorRGBA.Green, + ColorRGBA.Yellow, + ColorRGBA.Orange, + ColorRGBA.Brown, + }; + pointLights = new PointLight[currentPointLightNum]; +// InstancedNode debugSpheres = new InstancedNode("debugSpheres"); +// Sphere sphereMesh = new Sphere(16, 16, 1); +// Geometry sphere = new Geometry("Sphere"); +// sphere.setMesh(sphereMesh); +// Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); +// mat.getAdditionalRenderState().setWireframe(true); +// mat.setColor("Color", ColorRGBA.Green); +// mat.setBoolean("UseInstancing", true); +// sphere.setMaterial(mat); + float xHalf = 200, yHalf = 100, zHalf = 200; + for(int i = 0;i < currentPointLightNum;i++){ + pointLights[i] = new PointLight(); + pointLights[i].setColor(colors[FastMath.nextRandomInt(0, colors.length - 1)]); + pointLights[i].setRadius(FastMath.nextRandomFloat(10.0f, 20.0f)); + pointLights[i].setPosition(new Vector3f(FastMath.nextRandomFloat(-xHalf, xHalf), FastMath.nextRandomFloat(-yHalf, -60.0f), FastMath.nextRandomFloat(-zHalf, zHalf))); + rootNode.addLight(pointLights[i]); +// Geometry sp = sphere.clone(false); +// sp.setLocalTranslation(new Vector3f(FastMath.nextRandomFloat(-xHalf, xHalf), FastMath.nextRandomFloat(-yHalf, -60.0f), FastMath.nextRandomFloat(-zHalf, zHalf))); +// sp.setLocalScale(FastMath.nextRandomFloat(10.0f, 20.0f)); +// debugSpheres.attachChild(sp); + } +// debugSpheres.instance(); +// rootNode.attachChild(debugSpheres); + } + + private void setUpCamera() { + cam.setLocation(new Vector3f(0, 10, -10)); + cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + + getFlyByCamera().setMoveSpeed(camMoveSpeed); + } +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestRenderPathPointDirectionalAndSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/framegraph/TestRenderPathPointDirectionalAndSpotLightShadows.java new file mode 100644 index 0000000000..67aeb4f359 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestRenderPathPointDirectionalAndSpotLightShadows.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2009-2021 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.framegraph; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.TechniqueDef; +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.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.FrameGraphFactory; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.*; + +/** + * This example shows all shadow types, check rendering performance under different rendering paths + * @author JohnKkk + */ +public class TestRenderPathPointDirectionalAndSpotLightShadows extends SimpleApplication { + public static final int SHADOWMAP_SIZE = 512; + + public static void main(String[] args) { + TestRenderPathPointDirectionalAndSpotLightShadows app = new TestRenderPathPointDirectionalAndSpotLightShadows(); + app.start(); + } + private Node lightNode; + private SpotLight spotLight; + + @Override + public void simpleInitApp() { + + //FrameGraph graph = new FrameGraph(assetManager, renderManager); + //graph.setConstructor(new DeferredGraphConstructor()); + FrameGraph graph = FrameGraphFactory.deferred(assetManager, false); + viewPort.setFrameGraph(graph); + //renderManager.setFrameGraph(RenderPipelineFactory.create(this, RenderManager.RenderPath.Deferred)); + + // Note that for this j3o Cube model, the value of vLightDir passed from vs to ps in MultPass LightModel is different from using SinglePass. See the lightComputeDir() function, there will be some differences when this function calculates in world space and view space. It's an existing bug in JME, so here we set it to use SinglePass instead. + renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass); + flyCam.setMoveSpeed(10); + cam.setLocation(new Vector3f(0.040581334f, 1.7745866f, 6.155161f)); + cam.setRotation(new Quaternion(4.3868728E-5f, 0.9999293f, -0.011230096f, 0.0039059948f)); + + + Node scene = (Node) assetManager.loadModel("Models/Test/CornellBox.j3o"); + scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + rootNode.attachChild(scene); + rootNode.getChild("Cube").setShadowMode(RenderQueue.ShadowMode.Receive); + lightNode = (Node) rootNode.getChild("Lamp"); + Geometry lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + //Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.setShadowMode(RenderQueue.ShadowMode.Off); + lightNode.attachChild(lightMdl); + //lightMdl.setLocalTranslation(lightNode.getLocalTranslation()); + + + Geometry box = new Geometry("box", new Box(0.2f, 0.2f, 0.2f)); + //Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f)); + box.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + box.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + rootNode.attachChild(box); + box.setLocalTranslation(-1f, 0.5f, -2); + + scene.getLocalLightList().get(0).setColor(ColorRGBA.Red); + + PointLightShadowFilter plsf + = new PointLightShadowFilter(assetManager, SHADOWMAP_SIZE); + plsf.setLight((PointLight) scene.getLocalLightList().get(0)); + plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + + //DIRECTIONAL LIGHT + DirectionalLight directionalLight = new DirectionalLight(); + rootNode.addLight(directionalLight); + directionalLight.setColor(ColorRGBA.Blue); + directionalLight.setDirection(new Vector3f(-1f, -.2f, 0f)); + + DirectionalLightShadowFilter dlsf + = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE*2, 4); + dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + dlsf.setLight(directionalLight); + + //SPOT LIGHT + spotLight = new SpotLight(); + spotLight.setDirection(new Vector3f(1f,-1f,0f)); + spotLight.setPosition(new Vector3f(-1f,3f,0f)); + spotLight.setSpotOuterAngle(0.5f); + spotLight.setColor(ColorRGBA.Green); + Sphere sphere = new Sphere(8, 8, .1f); + Geometry sphereGeometry = new Geometry("Sphere", sphere); + sphereGeometry.setLocalTranslation(-1f, 3f, 0f); + sphereGeometry.setMaterial(assetManager.loadMaterial("Common/Materials/WhiteColor.j3m")); + rootNode.attachChild(sphereGeometry); + rootNode.addLight(spotLight); + + SpotLightShadowFilter slsf + = new SpotLightShadowFilter(assetManager, SHADOWMAP_SIZE); + slsf.setLight(spotLight); + slsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(plsf); + fpp.addFilter(dlsf); + fpp.addFilter(slsf); + viewPort.addProcessor(fpp); + + } + + private float timeElapsed = 0.0f; + @Override + public void simpleUpdate(float tpf) { + timeElapsed += tpf; + lightNode.setLocalTranslation(FastMath.cos(timeElapsed), lightNode.getLocalTranslation().y, FastMath.sin(timeElapsed)); + spotLight.setDirection(new Vector3f(FastMath.cos(-timeElapsed*.7f), -1.0f, FastMath.sin(-timeElapsed*.7f))); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestShadingModel.java b/jme3-examples/src/main/java/jme3test/framegraph/TestShadingModel.java new file mode 100644 index 0000000000..3de0c03dcc --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestShadingModel.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2024 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.framegraph; + +import com.jme3.app.DetailedProfilerState; +import com.jme3.app.SimpleApplication; +import com.jme3.environment.EnvironmentProbeControl; +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.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.RenderObjectMap; +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.shape.Quad; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.system.AppSettings; +import com.jme3.texture.plugins.ktx.KTXLoader; +import com.jme3.util.SkyFactory; +import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; + +/** + * This example demonstrates unified handling of several built-in shading models under the same render path. + * @author JohnKkk + */ +public class TestShadingModel extends SimpleApplication { + private DirectionalLight dl; + + private float roughness = 0.0f; + + private FrameGraph graph; + private Node modelNode; + private int frame = 0; + private Material pbrMat; + private Geometry model; + private Node tex; + + public static void main(String[] args) { + TestShadingModel app = new TestShadingModel(); + AppSettings settings = new AppSettings(true); + settings.setWidth(768); + settings.setHeight(768); + //settings.setGraphicsDebug(true); + //settings.setGraphicsTrace(true); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + + stateManager.attach(new DetailedProfilerState()); + //flyCam.setEnabled(false); + flyCam.setDragToRotate(true); + inputManager.setCursorVisible(true); + + //FrameGraph graph = RenderPipelineFactory.create(this, RenderManager.RenderPath.Deferred); + //graph = FrameGraphFactory.deferred(assetManager, renderManager, false); + graph = new FrameGraph(assetManager); + graph.applyData(assetManager.loadFrameGraph("Common/FrameGraphs/Deferred.j3g")); + //graph.setConstructor(new ForwardGraphConstructor()); + //graph.setConstructor(new TestConstructor()); + //MyFrameGraph graph = RenderPipelineFactory.createBackroundScreenTest(assetManager, renderManager); + viewPort.setFrameGraph(graph); + //guiViewPort.setFrameGraph(graph); + //renderManager.setFrameGraph(graph); + + viewPort.setBackgroundColor(ColorRGBA.Green.mult(0.2f)); + //viewPort.setBackgroundColor(ColorRGBA.White); + +// FrameBuffer fb = new FrameBuffer(768, 768, 1); +// Texture2D depth = new Texture2D(768, 768, Image.Format.Depth); +// fb.setDepthTarget(FrameBuffer.FrameBufferTarget.newTarget(depth)); +// viewPort.setOutputFrameBuffer(fb); + + Geometry debugView = new Geometry("debug", new Quad(150, 150)); + debugView.setLocalTranslation(0, 200, 0); + Material debugMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + //debugMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + //debugMat.setTransparent(true); + debugView.setMaterial(debugMat); + //debugMat.setTexture("ColorMap", depth); +// MatParamTargetControl texParam = new MatParamTargetControl("ColorMap", VarType.Texture2D); +// graph.get(Attribute.class, "OpaqueColor").setTarget(texParam); +// debugView.addControl(texParam); +// guiNode.attachChild(debugView); + + // UNLIT + Material unlitMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + unlitMat.setTexture("ColorMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg")); + //unlitMat.setColor("Color", new ColorRGBA(0, 0, 1, .5f)); + //unlitMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + //unlitMat.getAdditionalRenderState().setDepthWrite(false); + //unlitMat.getAdditionalRenderState().setDepthTest(false); + unlitMat.setTransparent(true); + Sphere sp = new Sphere(15, 15, 1.0f); + Geometry unlitSphere = new Geometry("unlitSphere", sp); + unlitSphere.setLocalTranslation(-5, 0, 0); + unlitSphere.setLocalRotation(new Quaternion(new float[]{(float) Math.toRadians(-90), 0, 0})); + unlitSphere.setMaterial(unlitMat); + unlitSphere.setQueueBucket(RenderQueue.Bucket.Transparent); + rootNode.attachChild(unlitSphere); + + // LEGACY_LIGHTING + Geometry lightSphere = unlitSphere.clone(false); + TangentBinormalGenerator.generate(lightSphere.getMesh()); + Material lightMat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); + lightSphere.setLocalTranslation(5, 0, 0); + lightSphere.setMaterial(lightMat); + lightSphere.setQueueBucket(RenderQueue.Bucket.Inherit); + rootNode.attachChild(lightSphere); + + // STANDARD_LIGHTING + roughness = 1.0f; + assetManager.registerLoader(KTXLoader.class, "ktx"); + + modelNode = 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); + modelNode.setLocalScale(0.3f); + rootNode.attachChild(modelNode); + + AmbientLight al = new AmbientLight(ColorRGBA.White.mult(0.1f)); + rootNode.addLight(al); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + int numSamples = context.getSettings().getSamples(); + if (numSamples > 0) { + fpp.setNumSamples(numSamples); + } + +// fpp.addFilter(new FXAAFilter()); + //fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(1.0f))); +// fpp.addFilter(new SSAOFilter(0.5f, 3, 0.2f, 0.2f)); + viewPort.addProcessor(fpp); + + DirectionalLightShadowRenderer dr = new DirectionalLightShadowRenderer(assetManager, 1024, 2); + dr.setLight(dl); + viewPort.addProcessor(dr); + + //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); + EnvironmentProbeControl.tagGlobal(sky); + + pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m"); + model.setMaterial(pbrMat); + + //new RenderPathHelper(this); + flyCam.setMoveSpeed(10.0f); + } + + @Override + public void simpleRender(RenderManager rm) { + super.simpleRender(rm); + frame++; + + if (frame == 2) { + + rootNode.addControl(new EnvironmentProbeControl(assetManager, 256)); + + } + if (frame > 10 && modelNode.getParent() == null) { + rootNode.attachChild(modelNode); + } + } + +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestSimpleDeferredLighting.java b/jme3-examples/src/main/java/jme3test/framegraph/TestSimpleDeferredLighting.java new file mode 100644 index 0000000000..dddf37a3d9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestSimpleDeferredLighting.java @@ -0,0 +1,811 @@ +/* + * Copyright (c) 2009-2021 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.framegraph; + +import com.jme3.app.ChaseCameraAppState; +import com.jme3.app.DetailedProfilerState; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +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.environment.util.LightsDebugState; +import com.jme3.font.BitmapText; +import com.jme3.input.ChaseCamera; +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.*; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.ToneMapFilter; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.FrameGraphFactory; +import com.jme3.renderer.framegraph.debug.GraphEventCapture; +import com.jme3.renderer.framegraph.passes.Junction; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.instancing.InstancedGeometry; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.DirectionalLightShadowFilter; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; +import com.jme3.texture.plugins.ktx.KTXLoader; +import com.jme3.util.SkyFactory; +import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; +import java.io.File; +import com.jme3.renderer.framegraph.modules.ModuleLocator; + +public class TestSimpleDeferredLighting extends SimpleApplication implements ActionListener { + + private boolean bUseFramegraph = true; + private Material mat; + private BitmapText hitText; + + private int sceneId; + + private float angle; + private float angles[]; + private PointLight pl; + private PointLight pls[]; + private Spatial lightMdl; + private Geometry lightMdls[]; + + private final Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + private float parallaxHeight = 0.05f; + private boolean steep = false; + private InstancedGeometry instancedGeometry; + private DirectionalLight dl; + + private float roughness = 0.0f; + + private Node modelNode; + private int frame = 0; + private Material pbrMat; + private Geometry model; + private Node tex; + + public static void main(String[] args){ + TestSimpleDeferredLighting app = new TestSimpleDeferredLighting(); + AppSettings appSettings = new AppSettings(true); + //appSettings.setRenderer(AppSettings.LWJGL_OPENGL40); + appSettings.setWidth(768); + appSettings.setHeight(768); + app.setSettings(appSettings); + app.start(); + } + private void testScene1(){ + sceneId = 0; + Geometry teapot = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); + TangentBinormalGenerator.generate(teapot.getMesh(), true); + + teapot.setLocalScale(2f); + renderManager.setSinglePassLightBatchSize(1); + mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); +// m_currentTechnique = TechniqueDef.DEFAULT_TECHNIQUE_NAME; +// mat.selectTechnique(m_currentTechnique, getRenderManager()); +// mat.selectTechnique("GBuf"); +// System.out.println("tech:" + mat.getMaterialDef().getTechniqueDefsNames().toString()); + mat.setFloat("Shininess", 25); +// mat.setBoolean("UseMaterialColors", true); + cam.setLocation(new Vector3f(0.015041917f, 0.4572918f, 5.2874837f)); + cam.setRotation(new Quaternion(-1.8875003E-4f, 0.99882424f, 0.04832061f, 0.0039016632f)); + +// mat.setTexture("ColorRamp", assetManager.loadTexture("Textures/ColorRamp/cloudy.png")); +// +// mat.setBoolean("VTangent", true); +// mat.setBoolean("Minnaert", true); +// mat.setBoolean("WardIso", true); +// mat.setBoolean("VertexLighting", true); +// mat.setBoolean("LowQuality", true); +// mat.setBoolean("HighQuality", true); + + mat.setColor("Ambient", ColorRGBA.Black); + mat.setColor("Diffuse", ColorRGBA.Gray); + mat.setColor("Specular", ColorRGBA.Gray); + + teapot.setMaterial(mat); + rootNode.attachChild(teapot); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + dl.setColor(ColorRGBA.White); + rootNode.addLight(dl); + } + private void testScene2(){ + sceneId = 1; + Sphere sphMesh = new Sphere(32, 32, 1); + sphMesh.setTextureMode(Sphere.TextureMode.Projected); + sphMesh.updateGeometry(32, 32, 1, false, false); + TangentBinormalGenerator.generate(sphMesh); + + Geometry sphere = new Geometry("Rock Ball", sphMesh); + mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); +// m_currentTechnique = TechniqueDef.DEFAULT_TECHNIQUE_NAME; +// mat.selectTechnique(m_currentTechnique, getRenderManager()); + sphere.setMaterial(mat); + rootNode.attachChild(sphere); + + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + pl = new PointLight(); + pl.setColor(ColorRGBA.White); + pl.setPosition(new Vector3f(0f, 0f, 4f)); + rootNode.addLight(pl); + } + private void testScene3(){ + renderManager.setSinglePassLightBatchSize(300); + sceneId = 2; + Node tank = (Node) assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml"); + rootNode.attachChild(tank); + + + + pls = new PointLight[2]; + angles = new float[pls.length]; + ColorRGBA colors[] = new ColorRGBA[]{ + ColorRGBA.White, + ColorRGBA.Red, + ColorRGBA.Blue, + ColorRGBA.Green, + ColorRGBA.Yellow, + ColorRGBA.Orange, + ColorRGBA.Brown, + }; + Material pml = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); + lightMdls = new Geometry[pls.length]; + for(int i = 0;i < pls.length;i++){ + pls[i] = new PointLight(); + pls[i].setColor(colors[pls.length % colors.length]); + pls[i].setRadius(FastMath.nextRandomFloat(1.0f, 2.0f)); + pls[i].setPosition(new Vector3f(FastMath.nextRandomFloat(-1.0f, 1.0f), FastMath.nextRandomFloat(-1.0f, 1.0f), FastMath.nextRandomFloat(-1.0f, 1.0f))); + rootNode.addLight(pls[i]); + + lightMdls[i] = new Geometry("Light", new Sphere(10, 10, 0.02f)); + lightMdls[i].setMaterial(pml); + lightMdls[i].getMesh().setStatic(); + rootNode.attachChild(lightMdls[i]); + } + +// DirectionalLight dl = new DirectionalLight(); +// dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); +// dl.setColor(ColorRGBA.Green); +// rootNode.addLight(dl); + } + public Geometry putShape(Mesh shape, ColorRGBA color, float lineWidth){ + Geometry g = new Geometry("shape", shape); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.getAdditionalRenderState().setLineWidth(lineWidth); + mat.setColor("Color", color); + g.setMaterial(mat); + rootNode.attachChild(g); + return g; + } + private void testScene4(){ + renderManager.setSinglePassLightBatchSize(300); + sceneId = 3; + Node tank = (Node) assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml"); + rootNode.attachChild(tank); + + ColorRGBA colors[] = new ColorRGBA[]{ + ColorRGBA.White, + ColorRGBA.Red, + ColorRGBA.Blue, + ColorRGBA.Green, + ColorRGBA.Yellow, + ColorRGBA.Orange, + ColorRGBA.Brown, + }; + PointLight p1 = new PointLight(new Vector3f(0, 1, 0), ColorRGBA.White); + PointLight p2 = new PointLight(new Vector3f(1, 0, 0), ColorRGBA.Green); + p1.setRadius(10); + p2.setRadius(10); + rootNode.addLight(p1); + rootNode.addLight(p2); + +// Geometry g = putShape(new WireSphere(1), ColorRGBA.Yellow, 1); +// g.setLocalTranslation(p1.getPosition()); +// g.setLocalScale(p1.getRadius() * 0.5f); +// +// g = putShape(new WireSphere(1), ColorRGBA.Yellow, 1); +// g.setLocalTranslation(p2.getPosition()); +// g.setLocalScale(p2.getRadius()); + +// DirectionalLight dl = new DirectionalLight(); +// dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); +// dl.setColor(ColorRGBA.White); +// rootNode.addLight(dl); + } + private void testScene5(){ + sceneId = 4; + // setupLighting + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(lightDir); + dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1)); + rootNode.addLight(dl); + // setupSkyBox + rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap)); + // setupFloor + mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.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); + // setupSignpost + Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); + Material matSp = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); + TangentBinormalGenerator.generate(signpost); + signpost.setMaterial(matSp); + signpost.rotate(0, FastMath.HALF_PI, 0); + signpost.setLocalTranslation(12, 23.5f, 30); + signpost.setLocalScale(4); + signpost.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + // other + rootNode.attachChild(signpost); + 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); + inputManager.addListener(new AnalogListener() { + + @Override + public void onAnalog(String name, float value, float tpf) { + if ("heightUP".equals(name)) { + parallaxHeight += 0.01; + mat.setFloat("ParallaxHeight", parallaxHeight); + } + if ("heightDown".equals(name)) { + parallaxHeight -= 0.01; + parallaxHeight = Math.max(parallaxHeight, 0); + mat.setFloat("ParallaxHeight", parallaxHeight); + } + + } + }, "heightUP", "heightDown"); + inputManager.addMapping("heightUP", new KeyTrigger(KeyInput.KEY_I)); + inputManager.addMapping("heightDown", new KeyTrigger(KeyInput.KEY_K)); + + inputManager.addListener(new ActionListener() { + + @Override + 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_O)); + } + private void testScene6(){ + sceneId = 6; + final Node buggy = (Node) assetManager.loadModel("Models/Buggy/Buggy.j3o"); + + TextureKey key = new TextureKey("Textures/Sky/Bright/BrightSky.dds", true); + key.setGenerateMips(true); + key.setTextureTypeHint(Texture.Type.CubeMap); + final Texture tex = assetManager.loadTexture(key); + + for (Spatial geom : buggy.getChildren()) { + if (geom instanceof Geometry) { + Material m = ((Geometry) geom).getMaterial(); + m.setTexture("EnvMap", tex); + m.setVector3("FresnelParams", new Vector3f(0.05f, 0.18f, 0.11f)); + } + } + + flyCam.setEnabled(false); + + ChaseCamera chaseCam = new ChaseCamera(cam, inputManager); + chaseCam.setLookAtOffset(new Vector3f(0,0.5f,-1.0f)); + buggy.addControl(chaseCam); + rootNode.attachChild(buggy); + rootNode.attachChild(SkyFactory.createSky(assetManager, tex, + SkyFactory.EnvMapType.CubeMap)); + + DirectionalLight l = new DirectionalLight(); + l.setDirection(new Vector3f(0, -1, -1)); + rootNode.addLight(l); + } + private void testScene7(){ + sceneId = 6; + Node scene = (Node) assetManager.loadModel("Scenes/ManyLights/Main.scene"); + rootNode.attachChild(scene); + Node n = (Node) rootNode.getChild(0); + final LightList lightList = n.getWorldLightList(); + final Geometry g = (Geometry) n.getChild("Grid-geom-1"); + + g.getMaterial().setColor("Ambient", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f)); + g.getMaterial().setBoolean("VertexLighting", false); + + /* A colored lit cube. Needs light source! */ +// Geometry boxGeo = new Geometry("shape", new Box(1, 1, 1)); +// Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); +// mat.getAdditionalRenderState().setWireframe(true); +// mat.setColor("Color", ColorRGBA.Green); +// mat.setBoolean("UseInstancing", true); +// boxGeo.setMaterial(mat); +// +// InstancedNode instancedNode = new InstancedNode("instanced_node"); +// n.attachChild(instancedNode); + int nb = 0; + for (Light light : lightList) { + nb++; + PointLight p = (PointLight) light; + if (nb > 20) { + n.removeLight(light); + } else { + int rand = FastMath.nextRandomInt(0, 3); + switch (rand) { + case 0: + light.setColor(ColorRGBA.Red); + break; + case 1: + light.setColor(ColorRGBA.Blue); + break; + case 2: + light.setColor(ColorRGBA.Green); + break; + case 3: + light.setColor(ColorRGBA.Yellow); + break; + } + } +// Geometry b = boxGeo.clone(false); +// instancedNode.attachChild(b); +// b.setLocalTranslation(p.getPosition().x, p.getPosition().y, p.getPosition().z); +// b.setLocalScale(p.getRadius() * 0.5f); + + } +// instancedNode.instance(); +// for(int i = 0,num = instancedNode.getChildren().size();i < num;i++){ +// if(instancedNode.getChild(i) instanceof InstancedGeometry){ +// instancedGeometry = (InstancedGeometry)instancedNode.getChild(i); +// instancedGeometry.setForceNumVisibleInstances(2); +// } +// } + + +// cam.setLocation(new Vector3f(3.1893547f, 17.977385f, 30.8378f)); +// cam.setRotation(new Quaternion(0.14317635f, 0.82302624f, -0.23777823f, 0.49557027f)); + + cam.setLocation(new Vector3f(-180.61f, 64, 7.657533f)); + cam.lookAtDirection(new Vector3f(0.93f, -0.344f, 0.044f), Vector3f.UNIT_Y); + + cam.setLocation(new Vector3f(-26.85569f, 15.701239f, -19.206047f)); + cam.lookAtDirection(new Vector3f(0.13871355f, -0.6151029f, 0.7761488f), Vector3f.UNIT_Y); + } + private void testScene8(){ + Quad quadMesh = new Quad(512,512); + Geometry quad = new Geometry("Quad", quadMesh); + quad.setQueueBucket(RenderQueue.Bucket.Opaque); + + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png")); + quad.setMaterial(mat); + + rootNode.attachChild(quad); + } + private void testScene9(){ + viewPort.setBackgroundColor(ColorRGBA.Black); + + dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + rootNode.addLight(dl); + dl.setColor(ColorRGBA.White); + + ChaseCameraAppState chaser = new ChaseCameraAppState(); + chaser.setDragToRotate(true); + chaser.setMinVerticalRotation(-FastMath.HALF_PI); + chaser.setMaxDistance(1000); + chaser.setInvertVerticalAxis(true); + getStateManager().attach(chaser); + chaser.setTarget(rootNode); + flyCam.setEnabled(false); + + Geometry sphere = new Geometry("sphere", new Sphere(32, 32, 1)); + final Material m = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md"); + m.setColor("BaseColor", ColorRGBA.Black); + m.setFloat("Metallic", 0f); + m.setFloat("Roughness", roughness); + sphere.setMaterial(m); + rootNode.attachChild(sphere); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + + if (name.equals("rup") && isPressed) { + roughness = FastMath.clamp(roughness + 0.1f, 0.0f, 1.0f); + m.setFloat("Roughness", roughness); + } + if (name.equals("rdown") && isPressed) { + roughness = FastMath.clamp(roughness - 0.1f, 0.0f, 1.0f); + m.setFloat("Roughness", roughness); + } + + if (name.equals("light") && isPressed) { + dl.setDirection(cam.getDirection().normalize()); + } + } + }, "light", "rup", "rdown"); + + + inputManager.addMapping("light", new KeyTrigger(KeyInput.KEY_F)); + inputManager.addMapping("rup", new KeyTrigger(KeyInput.KEY_UP)); + inputManager.addMapping("rdown", new KeyTrigger(KeyInput.KEY_DOWN)); + } + private void testScene10(){ + sceneId = 9; + roughness = 1.0f; + assetManager.registerLoader(KTXLoader.class, "ktx"); + + viewPort.setBackgroundColor(ColorRGBA.White); + modelNode = 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); + int numSamples = context.getSettings().getSamples(); + if (numSamples > 0) { + fpp.setNumSamples(numSamples); + } + +// 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(256, 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) { + if (tex == null) { + return; + } + if (tex.getParent() == null) { + guiNode.attachChild(tex); + } else { + tex.removeFromParent(); + } + } + + if (name.equals("rup") && isPressed) { + roughness = FastMath.clamp(roughness + 0.1f, 0.0f, 1.0f); + pbrMat.setFloat("Roughness", roughness); + } + if (name.equals("rdown") && isPressed) { + roughness = FastMath.clamp(roughness - 0.1f, 0.0f, 1.0f); + pbrMat.setFloat("Roughness", roughness); + } + + + 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", "rup", "rdown"); + + 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)); + inputManager.addMapping("rup", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addMapping("rdown", new KeyTrigger(KeyInput.KEY_G)); + } + private void testScene11(){ +// Box boxMesh = new Box(0.5f,0.5f,0.5f); +// Geometry boxGeo = new Geometry("Colored Box", boxMesh); +// boxGeo.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + Material boxMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); +// boxMat.setBoolean("UseMaterialColors", true); +// boxMat.setColor("Ambient", ColorRGBA.Green); +// boxMat.setColor("Diffuse", ColorRGBA.Green); +// boxGeo.setMaterial(boxMat); +// rootNode.attachChild(boxGeo); + Node tank = (Node) assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml"); + tank.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + tank.setLocalScale(0.3f); + rootNode.attachChild(tank); + + Quad plane = new Quad(10, 10); + Geometry planeGeo = new Geometry("Plane", plane); + planeGeo.setShadowMode(RenderQueue.ShadowMode.Receive); + planeGeo.rotate(-45, 0, 0); + planeGeo.setLocalTranslation(-5, -5, 0); + Material planeMat = boxMat.clone(); + planeMat.setBoolean("UseMaterialColors", true); + planeMat.setColor("Ambient", ColorRGBA.White); + planeMat.setColor("Diffuse", ColorRGBA.Gray); + planeGeo.setMaterial(planeMat); + rootNode.attachChild(planeGeo); + + + DirectionalLight sun = new DirectionalLight(); + sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal()); + sun.setColor(ColorRGBA.White); + rootNode.addLight(sun); + DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 1024, 1); + dlsf.setLight(sun); + + sun = new DirectionalLight(); + sun.setDirection((new Vector3f(0.5f, -0.5f, -0.5f)).normalizeLocal()); + sun.setColor(ColorRGBA.White); + rootNode.addLight(sun); + DirectionalLightShadowFilter dlsf2 = new DirectionalLightShadowFilter(assetManager, 1024, 1); + dlsf2.setLight(sun); + + sun = new DirectionalLight(); + sun.setDirection((new Vector3f(0.0f, -0.5f, -0.5f)).normalizeLocal()); + sun.setColor(ColorRGBA.White); + rootNode.addLight(sun); + DirectionalLightShadowFilter dlsf3 = new DirectionalLightShadowFilter(assetManager, 1024, 1); + dlsf3.setLight(sun); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(dlsf); + fpp.addFilter(dlsf2); + fpp.addFilter(dlsf3); + viewPort.addProcessor(fpp); + } + + @Override + public void simpleInitApp() { + + stateManager.attach(new DetailedProfilerState()); + //guiViewPort.setEnabled(false); + + //FrameGraph graph = RenderPipelineFactory.create(this, RenderManager.RenderPath.Deferred); + FrameGraph forward = FrameGraphFactory.forward(assetManager); + //forward.setName("forward"); + //FrameGraph deferred = new FrameGraph(assetManager, "Common/FrameGraphs/Deferred.j3g"); + FrameGraph deferred = FrameGraphFactory.deferred(assetManager, false); + deferred.setName("deferred"); + //FrameGraph deferred2 = FrameGraphFactory.deferred(assetManager, false); + //deferred2.setName("deferred2"); + //FrameGraph graph = FrameGraphFactory.forward(assetManager, renderManager); + viewPort.setFrameGraph(deferred); + //guiViewPort.setFrameGraph(deferred); + //renderManager.setFrameGraph(deferred); + //renderManager.setFrameGraph(forward); + + rootNode.addLight(new AmbientLight(ColorRGBA.White)); + + Junction lightingMethod = deferred.get(ModuleLocator.by(Junction.class, "LightPackMethod")); + //lightingMethod.setIndexSource((fg, vp) -> 0); + + File capTarget = new File(System.getProperty("user.home")+"/earlyFrameCapture.txt"); + GraphEventCapture cap = new GraphEventCapture(capTarget); + cap.setIncludeNanos(false); + //renderManager.setGraphCapture(cap, 10); + + viewPort.setBackgroundColor(ColorRGBA.Green.mult(.1f)); + guiViewPort.setBackgroundColor(ColorRGBA.Red.mult(.1f)); + //guiViewPort.setClearFlags(false, true, false); + + //Camera myCam = cam.clone(); + //ViewPort vp = renderManager.createPostView("myPostView", myCam); + //vp.setClearFlags(false, true, false); + //vp.attachScene(rootNode); + //vp.setFrameGraph(deferred); + + Geometry debugView = new Geometry("debug", new Quad(200, 200)); + debugView.setLocalTranslation(0, 200, 0); + Material debugMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + //debugMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + debugView.setMaterial(debugMat); + //MatParamTargetControl texTarget = new MatParamTargetControl("ColorMap", VarType.Texture2D); + //texTarget.setViewPorts(viewPort); + //deferred.get(Attribute.class, "GBufferDebug").addTarget(texTarget); + //debugView.addControl(texTarget); + //guiNode.attachChild(debugView); + + Geometry g = new Geometry("test", new Box(1, 1, 1)); + g.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); + g.setQueueBucket(RenderQueue.Bucket.Opaque); + //guiNode.attachChild(g); + + //renderManager.setRenderPath(currentRenderPath); + testScene7(); +// cam.setFrustumPerspective(45.0f, 4.0f / 3.0f, 0.01f, 100.0f); + flyCam.setMoveSpeed(10.0f); + // deferred +// testScene7(); + + +// MaterialDebugAppState debug = new MaterialDebugAppState(); +// debug.registerBinding("Common/ShaderLib/BlinnPhongLighting.glsllib", teapot); +// stateManager.attach(debug); + setPauseOnLostFocus(false); + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(50.0f); + + //rootNode.addControl(new EnvironmentProbeControl(assetManager, 256)); + + registerInput(); + } + + private void registerInput(){ + inputManager.addListener(this, "toggleRenderPath"); + inputManager.addListener(this, "toggleFramegraph"); + inputManager.addListener(this, "addInstNum"); + inputManager.addListener(this, "deleteInstNum"); + inputManager.addMapping("toggleRenderPath", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("toggleFramegraph", new KeyTrigger(KeyInput.KEY_N)); + inputManager.addMapping("addInstNum", new KeyTrigger(KeyInput.KEY_1)); + inputManager.addMapping("deleteInstNum", new KeyTrigger(KeyInput.KEY_2)); + } + + @Override + public void simpleUpdate(float tpf) { + if(sceneId == 1){ + angle += tpf * 0.25f; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 4f, 0.5f, FastMath.sin(angle) * 4f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + else if(sceneId == 2){ +// float t = 0; +// for(int i = 0;i < pls.length;i++){ +// t = i * 1.0f / pls.length * 1.5f + 1.5f; +// angles[i] += tpf * ((i + 1)) / pls.length; +// angles[i] %= FastMath.TWO_PI; +// +// pls[i].setPosition(new Vector3f(FastMath.cos(angles[i]) * t, i *1.0f / pls.length, FastMath.sin(angles[i]) * t)); +// lightMdls[i].setLocalTranslation(pls[i].getPosition()); +// } + } + else if(sceneId == 9){ + + 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"); + tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager); + } + }); + probe.getArea().setRadius(100); + rootNode.addLight(probe); + //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe); + + } + if (frame > 10 && modelNode.getParent() == null) { + rootNode.attachChild(modelNode); + } + } +// System.out.println("cam.pos:" + cam.getLocation()); +// System.out.println("cam.look:" + cam.getDirection()); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if(name.equals("toggleFramegraph") && !isPressed){ + bUseFramegraph = !bUseFramegraph; + //renderManager.enableFramegraph(bUseFramegraph); + } + if(name.equals("toggleRenderPath") && !isPressed){ + //renderManager.setRenderPath(currentRenderPath); +// getRenderManager().setForcedTechnique(null); + } +// if(name.equals("addInstNum") && !isPressed){ +// if(sceneId == 6){ +// instancedGeometry.setForceNumVisibleInstances(instancedGeometry.getNumVisibleInstances() + 1); +// } +// } +// else if(name.equals("deleteInstNum") && !isPressed){ +// if(sceneId == 6){ +// instancedGeometry.setForceNumVisibleInstances(instancedGeometry.getNumVisibleInstances() - 1); +// } +// } + } +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/TestTileBasedDeferredShading.java b/jme3-examples/src/main/java/jme3test/framegraph/TestTileBasedDeferredShading.java new file mode 100644 index 0000000000..bfcfa383a1 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/TestTileBasedDeferredShading.java @@ -0,0 +1,133 @@ +package jme3test.framegraph; + +import com.jme3.app.SimpleApplication; +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.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.post.filters.ToneMapFilter; +import com.jme3.renderer.framegraph.FrameGraphFactory; +import com.jme3.scene.Geometry; +import com.jme3.scene.instancing.InstancedNode; +import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; + +/** + * https://leifnode.com/2015/05/tiled-deferred-shading/ + * @author JohnKkk + */ +public class TestTileBasedDeferredShading extends SimpleApplication { + private Material material; + @Override + public void simpleInitApp() { + + viewPort.setFrameGraph(FrameGraphFactory.deferred(assetManager, true)); + + // Based on your current resolution and total light source count, try adjusting the tileSize - it must be a power of 2 such as 32, 64, 128 etc. + //renderManager.setForceTileSize(128);// 1600 * 900 resolution config +// renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass); +// renderManager.setSinglePassLightBatchSize(30); +// renderManager.setRenderPath(RenderManager.RenderPath.Forward); + //renderManager.setMaxDeferredShadingLights(1000); + //renderManager.setRenderPath(RenderManager.RenderPath.TiledDeferred); + Quad quad = new Quad(15, 15); + Geometry geo = new Geometry("Floor", quad); + material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + material.setFloat("Shininess", 25); + material.setColor("Ambient", ColorRGBA.White); + material.setColor("Diffuse", ColorRGBA.White); + material.setColor("Specular", ColorRGBA.White); + material.setBoolean("UseMaterialColors", true); + geo.setMaterial(material); + geo.rotate((float) Math.toRadians(-90), 0, 0); + geo.setLocalTranslation(-7, 0, -7); + rootNode.attachChild(geo); + + Sphere sphere = new Sphere(15, 15, 0.1f); + Geometry sp = new Geometry("sp", sphere); + sp.setMaterial(material.clone()); + sp.getMaterial().setBoolean("UseInstancing", true); + ColorRGBA colors[] = new ColorRGBA[]{ + ColorRGBA.White, + ColorRGBA.Red, + ColorRGBA.Blue, + ColorRGBA.Green, + ColorRGBA.Yellow, + ColorRGBA.Orange, + ColorRGBA.Brown, + }; + + InstancedNode instancedNode = new InstancedNode("sp"); + for(int i = 0;i < 1000;i++){ + PointLight pl = new PointLight(); + pl.setColor(colors[i % colors.length]); + pl.setPosition(new Vector3f(FastMath.nextRandomFloat(-5.0f, 5.0f), 0.1f, FastMath.nextRandomFloat(-20.0f, -10.0f))); +// pl.setPosition(new Vector3f(0, 1, 0)); +// if(i % 2 == 0){ +// pl.setColor(ColorRGBA.Red); +// pl.setPosition(new Vector3f(-5, 0.1f, -15.0f)); +// } +// else{ +// pl.setColor(ColorRGBA.White); +// pl.setPosition(new Vector3f(-4, 0.1f, -14.0f)); +// } + pl.setRadius(1.0f); + rootNode.addLight(pl); + Geometry g = sp.clone(false); +// g.getMaterial().setColor("Ambient", ColorRGBA.Gray); +// g.getMaterial().setColor("Diffuse", colors[i % colors.length]); + g.setLocalTranslation(pl.getPosition()); + instancedNode.attachChild(g); + } + instancedNode.instance(); + rootNode.attachChild(instancedNode); + + +// AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f)); +// rootNode.addLight(ambientLight); +// DirectionalLight sun = new DirectionalLight(); +// sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal()); +// sun.setColor(ColorRGBA.Gray); +// rootNode.addLight(sun); + + + cam.setLocation(new Vector3f(0, 2, 0)); + cam.lookAtDirection(Vector3f.UNIT_Z.negate(), Vector3f.UNIT_Y); +// cam.lookAtDirection(new Vector3f(-0.30149722f, 0.04880875f, -0.952217f), Vector3f.UNIT_Y); + cam.setFrustumPerspective(45.0f, cam.getWidth() * 1.0f / cam.getHeight(), 0.1f, 100.0f); + flyCam.setMoveSpeed(10.0f); +// flyCam.setEnabled(false); + + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + int numSamples = context.getSettings().getSamples(); + if (numSamples > 0) { + fpp.setNumSamples(numSamples); + } + + BloomFilter bloom=new BloomFilter(); + bloom.setDownSamplingFactor(1); + bloom.setBlurScale(1.1f); + bloom.setExposurePower(1.30f); + bloom.setExposureCutOff(0.3f); + bloom.setBloomIntensity(1.15f); + fpp.addFilter(bloom); + + fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(2.5f))); + viewPort.addProcessor(fpp); + } + + public static void main(String[] args) { + TestTileBasedDeferredShading testTileBasedDeferredShading = new TestTileBasedDeferredShading(); + AppSettings appSettings = new AppSettings(true); + appSettings.setWidth(1600); + appSettings.setHeight(900); +// appSettings.setRenderer(AppSettings.LWJGL_OPENGL33); + testTileBasedDeferredShading.setSettings(appSettings); + testTileBasedDeferredShading.start(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/examples/DownsamplingPass.java b/jme3-examples/src/main/java/jme3test/framegraph/examples/DownsamplingPass.java new file mode 100644 index 0000000000..d4cdb3a436 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/examples/DownsamplingPass.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024 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.framegraph.examples; + +import com.jme3.renderer.framegraph.FGRenderContext; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.ResourceTicket; +import com.jme3.renderer.framegraph.definitions.TextureDef; +import com.jme3.renderer.framegraph.passes.RenderPass; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; + +/** + * Downsamples from the input texture to an output texture 1/4 the size. + *

    + * Inputs: + *

      + *
    • Input: the input color texture.
    • + *
    + * Outputs: + *
      + *
    • Output: the texture downsampled to. Is 1/4 the size of the input texture, + * and matches the format of the input texture.
    • + *
    + * + * @author codex + */ +public class DownsamplingPass extends RenderPass { + + private ResourceTicket in; + private ResourceTicket out; + private final TextureDef texDef = TextureDef.texture2D(); + + @Override + protected void initialize(FrameGraph frameGraph) { + in = addInput("Input"); + out = addOutput("Output"); + texDef.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + texDef.setMagFilter(Texture.MagFilter.Nearest); + } + @Override + protected void prepare(FGRenderContext context) { + declare(texDef, out); + reserve(out); + reference(in); + } + @Override + protected void execute(FGRenderContext context) { + + Texture2D inTex = resources.acquire(in); + Image img = inTex.getImage(); + + int w = img.getWidth() / 2; + int h = img.getHeight() / 2; + texDef.setSize(w, h); + + texDef.setFormat(img.getFormat()); + + FrameBuffer fb = getFrameBuffer(w, h, 1); + resources.acquireColorTarget(fb, out); + context.getRenderer().setFrameBuffer(fb); + context.getRenderer().clearBuffers(true, true, true); + + context.resizeCamera(w, h, false, false, false); + context.renderTextures(inTex, null); + + } + @Override + protected void reset(FGRenderContext context) {} + @Override + protected void cleanup(FrameGraph frameGraph) {} + +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/examples/HelloCustomFrameGraph.java b/jme3-examples/src/main/java/jme3test/framegraph/examples/HelloCustomFrameGraph.java new file mode 100644 index 0000000000..f55ccef32f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/examples/HelloCustomFrameGraph.java @@ -0,0 +1,85 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package jme3test.framegraph.examples; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.passes.QueueMergePass; +import com.jme3.renderer.framegraph.passes.GeometryPass; +import com.jme3.renderer.framegraph.passes.OutputPass; +import com.jme3.renderer.framegraph.passes.SceneEnqueuePass; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; + +/** + * + * @author codex + */ +public class HelloCustomFrameGraph extends SimpleApplication { + + public static void main(String[] args) { + HelloCustomFrameGraph app = new HelloCustomFrameGraph(); + AppSettings settings = new AppSettings(true); + settings.setWidth(700); + settings.setHeight(700); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + + FrameGraph fg = new FrameGraph(assetManager); + viewPort.setFrameGraph(fg); + + SceneEnqueuePass enqueue = fg.add(new SceneEnqueuePass(true, true)); + QueueMergePass merge = fg.add(new QueueMergePass(5)); + GeometryPass bucket = fg.add(new GeometryPass()); + DownsamplingPass[] downsamples = fg.addLoop(new DownsamplingPass[4], + (i) -> new DownsamplingPass(), "Input", "Output"); + OutputPass out = fg.add(new OutputPass()); + + merge.makeInput(enqueue, "Opaque", "Queues[0]"); + merge.makeInput(enqueue, "Sky", "Queues[1]"); + merge.makeInput(enqueue, "Transparent", "Queues[2]"); + merge.makeInput(enqueue, "Gui", "Queues[3]"); + merge.makeInput(enqueue, "Translucent", "Queues[4]"); + + bucket.makeInput(merge, "Result", "Geometry"); + + downsamples[0].makeInput(bucket, "Color", "Input"); + + out.makeInput(downsamples[downsamples.length-1], "Output", "Color"); + out.makeInput(bucket, "Depth", "Depth"); + + Geometry box = new Geometry("box", new Box(1, 1, 1)); + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Diffuse", ColorRGBA.Blue); + box.setMaterial(mat); + rootNode.attachChild(box); + + PointLight pl = new PointLight(); + pl.setPosition(new Vector3f(2, 5, 5)); + pl.setRadius(100); + rootNode.addLight(pl); + + flyCam.setMoveSpeed(15); + flyCam.setDragToRotate(true); + viewPort.setBackgroundColor(ColorRGBA.White.mult(0.05f)); + + } + + @Override + public void simpleUpdate(float tpf) { + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/examples/HelloFrameGraph.java b/jme3-examples/src/main/java/jme3test/framegraph/examples/HelloFrameGraph.java new file mode 100644 index 0000000000..5f5959bcd9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/examples/HelloFrameGraph.java @@ -0,0 +1,61 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package jme3test.framegraph.examples; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.FrameGraphFactory; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; + +/** + * + * @author codex + */ +public class HelloFrameGraph extends SimpleApplication { + + public static void main(String[] args) { + HelloFrameGraph app = new HelloFrameGraph(); + AppSettings settings = new AppSettings(true); + settings.setWidth(700); + settings.setHeight(700); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + + FrameGraph frameGraph = FrameGraphFactory.forward(assetManager); + viewPort.setFrameGraph(frameGraph); + + Geometry box = new Geometry("box", new Box(1, 1, 1)); + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Diffuse", ColorRGBA.Blue); + box.setMaterial(mat); + rootNode.attachChild(box); + + PointLight pl = new PointLight(); + pl.setPosition(new Vector3f(2, 5, 5)); + pl.setRadius(100); + rootNode.addLight(pl); + + flyCam.setMoveSpeed(10); + flyCam.setDragToRotate(true); + + } + + @Override + public void simpleUpdate(float tpf) { + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/examples/effects/TestFog.java b/jme3-examples/src/main/java/jme3test/framegraph/examples/effects/TestFog.java new file mode 100644 index 0000000000..4ea7d4d819 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/examples/effects/TestFog.java @@ -0,0 +1,95 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package jme3test.framegraph.examples.effects; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.post.framegraph.HazePass; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.PassIndex; +import com.jme3.renderer.framegraph.passes.GeometryPass; +import com.jme3.renderer.framegraph.passes.OutputPass; +import com.jme3.renderer.framegraph.passes.QueueMergePass; +import com.jme3.renderer.framegraph.passes.SceneEnqueuePass; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.util.SkyFactory; + +/** + * + * @author codex + */ +public class TestFog extends SimpleApplication { + + public static void main(String[] args) { + TestFog app = new TestFog(); + AppSettings settings = new AppSettings(true); + settings.setWidth(700); + settings.setHeight(700); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + + FrameGraph fg = new FrameGraph(assetManager); + viewPort.setFrameGraph(fg); + //viewPort.setFrameGraph(FrameGraphFactory.forward(assetManager)); + + SceneEnqueuePass enqueue = fg.add(new SceneEnqueuePass(true, true)); + QueueMergePass merge = fg.add(new QueueMergePass(5)); + GeometryPass geom = fg.add(new GeometryPass()); + HazePass haze = fg.add(new HazePass()); + OutputPass out = fg.add(new OutputPass()); + OutputPass out2 = fg.add(new OutputPass()); + + merge.makeInput(enqueue, "Opaque", "Queues[0]"); + merge.makeInput(enqueue, "Sky", "Queues[1]"); + merge.makeInput(enqueue, "Transparent", "Queues[2]"); + merge.makeInput(enqueue, "Gui", "Queues[3]"); + merge.makeInput(enqueue, "Translucent", "Queues[4]"); + + geom.makeInput(merge, "Result", "Geometry"); + + haze.makeInput(geom, "Color", "Color"); + haze.makeInput(geom, "Depth", "Depth"); + + out.makeInput(haze, "Result", "Color"); + out.makeInput(geom, "Depth", "Depth"); + + //out2.makeInput(sky, "Color", "Color"); + //out2.makeInput(sky, "Depth", "Depth"); + + flyCam.setMoveSpeed(100); + flyCam.setDragToRotate(true); + + for (int i = 0; i < 1000; i++) { + Geometry g = new Geometry("cube", new Box(1, 1, 1)); + g.setLocalTranslation( + FastMath.nextRandomFloat(-100, 100), + FastMath.nextRandomFloat(-100, 100), + FastMath.nextRandomFloat(-100, 100)); + g.setLocalRotation(new Quaternion().fromAngles( + FastMath.nextRandomFloat(0, FastMath.TWO_PI), + FastMath.nextRandomFloat(0, FastMath.TWO_PI), + FastMath.nextRandomFloat(0, FastMath.TWO_PI))); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.randomColor()); + g.setMaterial(mat); + rootNode.attachChild(g); + } + + Spatial skyBox = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); + rootNode.attachChild(skyBox); + + } + +} diff --git a/jme3-examples/src/main/java/jme3test/framegraph/sandbox/Main.java b/jme3-examples/src/main/java/jme3test/framegraph/sandbox/Main.java new file mode 100644 index 0000000000..feecfccb70 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/framegraph/sandbox/Main.java @@ -0,0 +1,108 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package jme3test.framegraph.sandbox; + +import com.jme3.app.DetailedProfilerState; +import com.jme3.app.SimpleApplication; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.framegraph.CartoonEdgePass; +import com.jme3.renderer.DepthRange; +import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.passes.GeometryPass; +import com.jme3.renderer.framegraph.passes.OutputPass; +import com.jme3.renderer.framegraph.passes.SceneEnqueuePass; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; + +/** + * + * @author codex + */ +public class Main extends SimpleApplication { + + public static void main(String[] args) { + Main app = new Main(); + AppSettings settings = new AppSettings(true); + settings.setWidth(768); + settings.setHeight(768); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + + FrameGraph frameGraph = new FrameGraph(assetManager); + + SceneEnqueuePass enqueue = frameGraph.add(new SceneEnqueuePass()); + GeometryPass opaque = frameGraph.add(new GeometryPass()); + GeometryPass sky = frameGraph.add(new GeometryPass(DepthRange.REAR)); + GeometryPass transparent = frameGraph.add(new GeometryPass()); + GeometryPass gui = frameGraph.add(new GeometryPass(DepthRange.FRONT, false)); + GeometryPass translucent = frameGraph.add(new GeometryPass()); + OutputPass output = frameGraph.add(new OutputPass()); + + opaque.makeInput(enqueue, "Opaque", "Geometry"); + + sky.makeInput(opaque, "Color", "Color"); + sky.makeInput(opaque, "Depth", "Depth"); + sky.makeInput(enqueue, "Sky", "Geometry"); + + transparent.makeInput(sky, "Color", "Color"); + transparent.makeInput(sky, "Depth", "Depth"); + transparent.makeInput(enqueue, "Transparent", "Geometry"); + + gui.makeInput(transparent, "Color", "Color"); + gui.makeInput(transparent, "Depth", "Depth"); + gui.makeInput(enqueue, "Gui", "Geometry"); + + translucent.makeInput(gui, "Color", "Color"); + translucent.makeInput(gui, "Depth", "Depth"); + translucent.makeInput(enqueue, "Translucent", "Geometry"); + + output.makeInput(translucent, "Color", "Color"); + output.makeInput(translucent, "Depth", "Depth"); + + viewPort.setFrameGraph(frameGraph); + + // setup camera + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(20); + + // setup background + viewPort.setBackgroundColor(ColorRGBA.White.mult(0.02f)); + + // add a cube to the scene + Geometry cube = new Geometry("cube", new Box(1, 1, 1)); + Material mat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md"); + mat.setColor("BaseColor", ColorRGBA.White); + mat.setFloat("Metallic", 0.5f); + cube.setMaterial(mat); + rootNode.attachChild(cube); + + // add a light to the scene + PointLight pl = new PointLight(); + pl.setColor(ColorRGBA.White); + pl.setPosition(new Vector3f(2, 3, 3)); + pl.setRadius(20); + rootNode.addLight(pl); + + // profiler + DetailedProfilerState profiler = new DetailedProfilerState(); + profiler.setEnabled(false); + stateManager.attach(profiler); + + } + + @Override + public void simpleUpdate(float tpf) { + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md index 2e192c7159..04f37b4daf 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md @@ -350,6 +350,83 @@ MaterialDef Advanced PBR Terrain { } } + Technique GBufferPass { + + VertexShader GLSL300 GLSL150 : Common/MatDefs/Terrain/PBRTerrain.vert + FragmentShader GLSL300 GLSL150 : Common/MatDefs/Terrain/GBufferPack/AdvancedPBRTerrainGBufferPack.frag + + WorldParameters { + WorldViewProjectionMatrix + CameraPosition + WorldMatrix + WorldNormalMatrix + ViewProjectionMatrix + ViewMatrix + Time + } + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + + AFFLICTIONTEXTURE : AfflictionAlphaMap + AFFLICTIONALBEDOMAP: SplatAlbedoMap + AFFLICTIONNORMALMAP : SplatNormalMap + AFFLICTIONROUGHNESSMETALLICMAP : SplatRoughnessMetallicMap + AFFLICTIONEMISSIVEMAP : SplatEmissiveMap + USE_SPLAT_NOISE : SplatNoiseVar + + TRI_PLANAR_MAPPING : useTriPlanarMapping + + DISCARD_ALPHA : AlphaDiscardThreshold + + ALPHAMAP : AlphaMap + ALPHAMAP_1 : AlphaMap_1 + ALPHAMAP_2 : AlphaMap_2 + + USE_FOG : UseFog + FOG_LINEAR : LinearFog + FOG_EXP : ExpFog + FOG_EXPSQ : ExpSqFog + + ALBEDOMAP_0 : AlbedoMap_0 + ALBEDOMAP_1 : AlbedoMap_1 + ALBEDOMAP_2 : AlbedoMap_2 + ALBEDOMAP_3 : AlbedoMap_3 + ALBEDOMAP_4 : AlbedoMap_4 + ALBEDOMAP_5 : AlbedoMap_5 + ALBEDOMAP_6 : AlbedoMap_6 + ALBEDOMAP_7 : AlbedoMap_7 + ALBEDOMAP_8 : AlbedoMap_8 + ALBEDOMAP_9 : AlbedoMap_9 + ALBEDOMAP_10 : AlbedoMap_10 + ALBEDOMAP_11 : AlbedoMap_11 + + NORMALMAP_0 : NormalMap_0 + NORMALMAP_1 : NormalMap_1 + NORMALMAP_2 : NormalMap_2 + NORMALMAP_3 : NormalMap_3 + NORMALMAP_4 : NormalMap_4 + NORMALMAP_5 : NormalMap_5 + NORMALMAP_6 : NormalMap_6 + NORMALMAP_7 : NormalMap_7 + NORMALMAP_8 : NormalMap_8 + NORMALMAP_9 : NormalMap_9 + NORMALMAP_10 : NormalMap_10 + NORMALMAP_11 : NormalMap_11 + + METALLICROUGHNESSMAP_0 : MetallicRoughnessMap_0 + METALLICROUGHNESSMAP_1 : MetallicRoughnessMap_1 + METALLICROUGHNESSMAP_2 : MetallicRoughnessMap_2 + METALLICROUGHNESSMAP_3 : MetallicRoughnessMap_3 + METALLICROUGHNESSMAP_4 : MetallicRoughnessMap_4 + METALLICROUGHNESSMAP_5 : MetallicRoughnessMap_5 + METALLICROUGHNESSMAP_6 : MetallicRoughnessMap_6 + METALLICROUGHNESSMAP_7 : MetallicRoughnessMap_7 + METALLICROUGHNESSMAP_8 : MetallicRoughnessMap_8 + METALLICROUGHNESSMAP_9 : MetallicRoughnessMap_9 + METALLICROUGHNESSMAP_10 : MetallicRoughnessMap_10 + METALLICROUGHNESSMAP_11 : MetallicRoughnessMap_11 + } + } Technique PreShadow { diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/AdvancedPBRTerrainGBufferPack.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/AdvancedPBRTerrainGBufferPack.frag new file mode 100644 index 0000000000..56a60a66d5 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/AdvancedPBRTerrainGBufferPack.frag @@ -0,0 +1,516 @@ +#extension GL_EXT_texture_array : enable +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/PBR.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" +#import "Common/MatDefs/Terrain/AfflictionLib.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" + +// shading model +#import "Common/ShaderLib/ShadingModel.glsllib" +// octahedral +#import "Common/ShaderLib/Octahedral.glsllib" + +varying vec3 wPosition; +varying vec3 vNormal; +varying vec2 texCoord; +uniform vec3 g_CameraPosition; +varying vec3 vPosition; +varying vec3 vnPosition; +varying vec3 vViewDir; +varying vec4 vLightDir; +varying vec4 vnLightDir; +varying vec3 lightVec; +varying vec3 inNormal; +varying vec3 wNormal; + +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; +#endif + +//texture arrays: +uniform sampler2DArray m_AlbedoTextureArray; +uniform sampler2DArray m_NormalParallaxTextureArray; +uniform sampler2DArray m_MetallicRoughnessAoEiTextureArray; + +//texture-slot params for 12 unique texture slots (0-11) where the integer value points to the desired texture's index in the corresponding texture array: +#for i=0..12 ( $0 ) + uniform int m_AfflictionMode_$i; + uniform float m_Roughness_$i; + uniform float m_Metallic_$i; + uniform float m_AlbedoMap_$i_scale; + uniform vec4 m_EmissiveColor_$i; + + #ifdef ALBEDOMAP_$i + uniform int m_AlbedoMap_$i; + #endif + #ifdef NORMALMAP_$i + uniform int m_NormalMap_$i; + #endif + #ifdef METALLICROUGHNESSMAP_$i + uniform int m_MetallicRoughnessMap_$i; + #endif +#endfor + +//3 alpha maps : +#ifdef ALPHAMAP + uniform sampler2D m_AlphaMap; +#endif +#ifdef ALPHAMAP_1 + uniform sampler2D m_AlphaMap_1; +#endif +#ifdef ALPHAMAP_2 + uniform sampler2D m_AlphaMap_2; +#endif + +#ifdef DISCARD_ALPHA + uniform float m_AlphaDiscardThreshold; +#endif + +//fog vars for basic fog : +#ifdef USE_FOG +#import "Common/ShaderLib/MaterialFog.glsllib" + uniform vec4 m_FogColor; + float fogDistance; + + uniform vec2 m_LinearFog; +#endif +#ifdef FOG_EXP + uniform float m_ExpFog; +#endif +#ifdef FOG_EXPSQ + uniform float m_ExpSqFog; +#endif + +//sun intensity is a secondary AO value that can be painted per-vertex in the red channel of the +// vertex colors, or it can be set as a static value for an entire material with the StaticSunIntensity float param +#if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) + varying vec4 vertColors; +#endif + +#ifdef STATIC_SUN_INTENSITY + uniform float m_StaticSunIntensity; +#endif +//sun intensity AO value is only applied to the directional light, not to point lights, so it is important to track if the +//sun is more/less bright than the brightest point light for each fragment to determine how the light probe's ambient light should be scaled later on in light calculation code +float brightestPointLight = 0.0; + +//optional affliction paramaters that use the AfflictionAlphaMap's green channel for splatting m_SplatAlbedoMap and the red channel for splatting desaturation : +#ifdef AFFLICTIONTEXTURE + uniform sampler2D m_AfflictionAlphaMap; +#endif +#ifdef USE_SPLAT_NOISE + uniform float m_SplatNoiseVar; +#endif +//only defined for non-terrain geoemtries and terrains that are not positioned nor sized in correlation to the 2d array of AfflictionAlphaMaps used for splatting accross large tile based scenes in a grid +#ifdef TILELOCATION + uniform float m_TileWidth; + uniform vec3 m_TileLocation; +#endif +#ifdef AFFLICTIONALBEDOMAP + uniform sampler2D m_SplatAlbedoMap; +#endif +#ifdef AFFLICTIONNORMALMAP + uniform sampler2D m_SplatNormalMap; +#endif +#ifdef AFFLICTIONROUGHNESSMETALLICMAP + uniform sampler2D m_SplatRoughnessMetallicMap; +#endif +#ifdef AFFLICTIONEMISSIVEMAP + uniform sampler2D m_SplatEmissiveMap; +#endif + +uniform int m_AfflictionSplatScale; +uniform float m_AfflictionRoughnessValue; +uniform float m_AfflictionMetallicValue; +uniform float m_AfflictionEmissiveValue; +uniform vec4 m_AfflictionEmissiveColor; + +vec4 afflictionVector; +float noiseHash; +float livelinessValue; +float afflictionValue; +int afflictionMode = 1; + +//general temp vars : +vec4 tempAlbedo, tempNormal, tempEmissiveColor; +float tempParallax, tempMetallic, tempRoughness, tempAo, tempEmissiveIntensity; + +vec3 viewDir; +vec2 coord; +vec4 albedo = vec4(1.0); +vec3 normal = vec3(0.5,0.5,1); +vec3 norm; +float Metallic; +float Roughness; +float packedAoValue = 1.0; +vec4 emissive; +float emissiveIntensity = 1.0; +float indoorSunLightExposure = 1.0; + +vec4 packedMetallicRoughnessAoEiVec; +vec4 packedNormalParallaxVec; + + +void main(){ + + #ifdef USE_FOG + fogDistance = distance(g_CameraPosition, wPosition.xyz); + #endif + + float indoorSunLightExposure = 1.0; + + viewDir = normalize(g_CameraPosition - wPosition); + + norm = normalize(wNormal); + normal = norm; + + + afflictionVector = vec4(1.0, 0.0, 1.0, 0.0); //r channel is sturation, g channel is affliction splat texture intensity, b and a unused (might use b channel for wetness eventually) + + #ifdef AFFLICTIONTEXTURE + + #ifdef TILELOCATION + //subterrains that are not centred in tile or equal to tile width in total size need to have m_TileWidth pre-set. (tileWidth is the x,z dimesnions that the AfflictionAlphaMap represents).. + vec2 tileCoords; + float xPos, zPos; + + vec3 locInTile = (wPosition - m_TileLocation); + + locInTile += vec3(m_TileWidth/2, 0, m_TileWidth/2); + + xPos = (locInTile.x / m_TileWidth); + zPos = 1 - (locInTile.z / m_TileWidth); + + tileCoords = vec2(xPos, zPos); + + afflictionVector = texture2D(m_AfflictionAlphaMap, tileCoords).rgba; + + + + #else + // ..othrewise when terrain size matches tileWidth, the terrain's texCoords can be used for simple texel fetching of the AfflictionAlphaMap + afflictionVector = texture2D(m_AfflictionAlphaMap, texCoord.xy).rgba; + #endif + #endif + + livelinessValue = afflictionVector.r; + afflictionValue = afflictionVector.g; + + + #ifdef ALBEDOMAP_0 + #ifdef ALPHAMAP + + vec4 alphaBlend; + vec4 alphaBlend_0, alphaBlend_1, alphaBlend_2; + int texChannelForAlphaBlending; + + alphaBlend_0 = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + alphaBlend_1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + alphaBlend_2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec2 texSlotCoords; + + float finalAlphaBlendForLayer = 1.0; + + vec3 blending = abs( norm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + #for i=0..12 (#ifdef ALBEDOMAP_$i $0 #endif) + + //assign texture slot's blending from index's correct alpha map + if($i <= 3){ + alphaBlend = alphaBlend_0; + }else if($i <= 7){ + alphaBlend = alphaBlend_1; + }else if($i <= 11){ + alphaBlend = alphaBlend_2; + } + + texChannelForAlphaBlending = int(mod($i, 4.0)); //pick the correct channel (r g b or a) based on the layer's index + switch(texChannelForAlphaBlending) { + case 0: + finalAlphaBlendForLayer = alphaBlend.r; + break; + case 1: + finalAlphaBlendForLayer = alphaBlend.g; + break; + case 2: + finalAlphaBlendForLayer = alphaBlend.b; + break; + case 3: + finalAlphaBlendForLayer = alphaBlend.a; + break; + } + + afflictionMode = m_AfflictionMode_$i; + tempEmissiveColor = m_EmissiveColor_$i; + + #ifdef TRI_PLANAR_MAPPING + //tri planar + tempAlbedo = getTriPlanarBlendFromTexArray(wVertex, blending, m_AlbedoMap_$i, m_AlbedoMap_$i_scale, m_AlbedoTextureArray); + + #ifdef NORMALMAP_$i + packedNormalParallaxVec.rgba = getTriPlanarBlendFromTexArray(wVertex, blending, m_NormalMap_$i, m_AlbedoMap_$i_scale, m_NormalParallaxTextureArray).rgba; + tempNormal.xyz = calculateTangentsAndApplyToNormals(packedNormalParallaxVec.xyz, wNormal);// this gets rid of the need for pre-generating tangents for TerrainPatches, since doing so doesn't seem to work (tbnMat is always blank for terrains even with tangents pre-generated, not sure why...) + tempParallax = packedNormalParallaxVec.w; + + #ifdef PARALLAXHEIGHT_0 + //wip + #endif + #else + tempNormal.rgb = wNormal.rgb; + #endif + #ifdef METALLICROUGHNESSMAP_$i + packedMetallicRoughnessAoEiVec = getTriPlanarBlendFromTexArray(wVertex, blending, m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, m_MetallicRoughnessAoEiTextureArray).rgba; + tempRoughness = packedMetallicRoughnessAoEiVec.g * m_Roughness_$i; + tempMetallic = packedMetallicRoughnessAoEiVec.b * m_Metallic_$i; + tempAo = packedMetallicRoughnessAoEiVec.r; + tempEmissiveIntensity = packedMetallicRoughnessAoEiVec.a; + #endif + #else + + // non triplanar + texSlotCoords = texCoord * m_AlbedoMap_$i_scale; + + tempAlbedo = texture2DArray(m_AlbedoTextureArray, vec3(texSlotCoords, m_AlbedoMap_$i)); + + #ifdef NORMALMAP_$i + packedNormalParallaxVec = texture2DArray(m_NormalParallaxTextureArray, vec3(texSlotCoords, m_NormalMap_$i)); + tempNormal.xyz = calculateTangentsAndApplyToNormals(packedNormalParallaxVec.xyz, wNormal); + tempParallax = packedNormalParallaxVec.w; + + #ifdef PARALLAXHEIGHT_0 + //eventually add parallax code here if a PARALLAXHEIGHT_$i float is defined. but this shader is at the define limit currently, + // so to do that will require removing defines around scale to use that for enabling parallax per layer instead, since there's no reason for define around basic float scale anyways + #endif + #else + tempNormal.rgb = wNormal.rgb; + #endif + + #ifdef METALLICROUGHNESSMAP_$i + packedMetallicRoughnessAoEiVec = texture2DArray(m_MetallicRoughnessAoEiTextureArray, vec3(texSlotCoords, m_MetallicRoughnessMap_$i)); + tempRoughness = packedMetallicRoughnessAoEiVec.g * m_Roughness_$i; + tempMetallic = packedMetallicRoughnessAoEiVec.b * m_Metallic_$i; + tempAo = packedMetallicRoughnessAoEiVec.r; + tempEmissiveIntensity = packedMetallicRoughnessAoEiVec.a; + #endif + #endif + + + //blend to float values if no texture value for mrao map exists + #if !defined(METALLICROUGHNESSMAP_$i) + tempRoughness = m_Roughness_$i; + tempMetallic = m_Metallic_$i; + tempAo = 1.0; + #endif + + //note: most of these functions can be found in AfflictionLib.glslib + tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode); //changes saturation of albedo for this layer; does nothing if not using AfflictionAlphaMap for affliction splatting + + tempEmissiveColor *= tempEmissiveIntensity; + + //mix values from this index layer to final output values based on finalAlphaBlendForLayer + albedo.rgb = mix(albedo.rgb, tempAlbedo.rgb , finalAlphaBlendForLayer); + normal.rgb = mix(normal.rgb, tempNormal.rgb, finalAlphaBlendForLayer); + Metallic = mix(Metallic, tempMetallic, finalAlphaBlendForLayer); + Roughness = mix(Roughness, tempRoughness, finalAlphaBlendForLayer); + packedAoValue = mix(packedAoValue, tempAo, finalAlphaBlendForLayer); + emissiveIntensity = mix(emissiveIntensity, tempEmissiveIntensity, finalAlphaBlendForLayer); + emissive = mix(emissive, tempEmissiveColor, finalAlphaBlendForLayer); + + #endfor + #else + albedo = texture2D(m_AlbedoMap_0, texCoord); + #endif + #endif + + float alpha = albedo.a; + #ifdef DISCARD_ALPHA + if(alpha < m_AlphaDiscardThreshold){ + discard; + } + #endif + + //APPLY AFFLICTIONN TO THE PIXEL + #ifdef AFFLICTIONTEXTURE + vec4 afflictionAlbedo; + + + float newAfflictionScale = m_AfflictionSplatScale; + vec2 newScaledCoords; + + + #ifdef AFFLICTIONALBEDOMAP + #ifdef TRI_PLANAR_MAPPING + newAfflictionScale = newAfflictionScale / 256; + afflictionAlbedo = getTriPlanarBlend(wVertex, blending, m_SplatAlbedoMap , newAfflictionScale); + #else + newScaledCoords = mod(wPosition.xz / m_AfflictionSplatScale, 0.985); + afflictionAlbedo = texture2D(m_SplatAlbedoMap , newScaledCoords); + #endif + + #else + afflictionAlbedo = vec4(1.0, 1.0, 1.0, 1.0); + #endif + + vec3 afflictionNormal; + #ifdef AFFLICTIONNORMALMAP + #ifdef TRI_PLANAR_MAPPING + + afflictionNormal = getTriPlanarBlend(wVertex, blending, m_SplatNormalMap , newAfflictionScale).rgb; + + #else + afflictionNormal = texture2D(m_SplatNormalMap , newScaledCoords).rgb; + #endif + + #else + afflictionNormal = norm; + + #endif + float afflictionMetallic = m_AfflictionMetallicValue; + float afflictionRoughness = m_AfflictionRoughnessValue; + float afflictionAo = 1.0; + + + vec4 afflictionEmissive = m_AfflictionEmissiveColor; + float afflictionEmissiveIntensity = m_AfflictionEmissiveValue; + + #ifdef AFFLICTIONROUGHNESSMETALLICMAP + vec4 metallicRoughnessAoEiVec; + #ifdef TRI_PLANAR_MAPPING + metallicRoughnessAoEiVec = texture2D(m_SplatRoughnessMetallicMap, newScaledCoords); + #else + metallicRoughnessAoEiVec = getTriPlanarBlend(wVertex, blending, m_SplatRoughnessMetallicMap, newAfflictionScale); + #endif + + afflictionRoughness *= metallicRoughnessAoEiVec.g; + afflictionMetallic *= metallicRoughnessAoEiVec.b; + afflictionAo = metallicRoughnessAoEiVec.r; + afflictionEmissiveIntensity *= metallicRoughnessAoEiVec.a; //important not to leave this channel all black by accident when creating the mraoei map if using affliction emissiveness + + #endif + + #ifdef AFFLICTIONEMISSIVEMAP + vec4 emissiveMapColor; + #ifdef TRI_PLANAR_MAPPING + emissiveMapColor = texture2D(m_SplatEmissiveMap, newScaledCoords); + #else + emissiveMapColor = getTriPlanarBlend(wVertex, blending, m_SplatEmissiveMap, newAfflictionScale); + #endif + afflictionEmissive *= emissiveMapColor; + #endif + + float adjustedAfflictionValue = afflictionValue; + #ifdef USE_SPLAT_NOISE + noiseHash = getStaticNoiseVar0(wPosition, afflictionValue * m_SplatNoiseVar); //VERY IMPORTANT to replace this with a noiseMap texture, as calculating noise per pixel in-shader like this does lower framerate a lot + + adjustedAfflictionValue = getAdjustedAfflictionVar(afflictionValue); + if(afflictionValue >= 0.99){ + adjustedAfflictionValue = afflictionValue; + } + #else + noiseHash = 1.0; + #endif + + Roughness = alterAfflictionRoughness(adjustedAfflictionValue, Roughness, afflictionRoughness, noiseHash); + Metallic = alterAfflictionMetallic(adjustedAfflictionValue, Metallic, afflictionMetallic, noiseHash); + albedo = alterAfflictionColor(adjustedAfflictionValue, albedo, afflictionAlbedo, noiseHash ); + normal = alterAfflictionNormalsForTerrain(adjustedAfflictionValue, normal, afflictionNormal, noiseHash , wNormal); + emissive = alterAfflictionGlow(adjustedAfflictionValue, emissive, afflictionEmissive, noiseHash); + emissiveIntensity = alterAfflictionEmissiveIntensity(adjustedAfflictionValue, emissiveIntensity, afflictionEmissiveIntensity, noiseHash); + emissiveIntensity *= afflictionEmissive.a; + //affliction ao value blended below after specular calculation + + #endif + + // spec gloss pipeline code would go here if supported, but likely will not be for terrain shaders as defines are limited and heavily used + + float specular = 0.5; + float nonMetalSpec = 0.08 * specular; + vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic; + vec4 diffuseColor = albedo - albedo * Metallic; + vec3 fZero = vec3(specular); + + //gl_FragColor.rgb = vec3(0.0); + +//simple ao calculation, no support for lightmaps like stock pbr shader.. (probably could add lightmap support with another texture array, but +// that would add another texture read per slot and require removing 12 other defines to make room...) + vec3 ao = vec3(packedAoValue); + + #ifdef AFFLICTIONTEXTURE + ao = alterAfflictionAo(afflictionValue, ao, vec3(afflictionAo), noiseHash); // alter the AO map for affliction values + #endif + ao.rgb = ao.rrr; + specularColor.rgb *= ao; + + + + #ifdef STATIC_SUN_INTENSITY + indoorSunLightExposure = m_StaticSunIntensity; //single float value to indicate percentage of + //sunlight hitting the model (only works for small models or models with 100% consistent sunlighting accross every pixel) + #endif + #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY + indoorSunLightExposure = vertColors.r * indoorSunLightExposure; //use R channel of vertexColors for.. + #endif + // similar purpose as above... + //but uses r channel vert colors like an AO map specifically + //for sunlight (solution for scaling lighting for indoor + // and shadey/dimly lit models, especially big ones with) + brightestPointLight = 0.0; + + // pack + vec2 n1 = octEncode(normal); + vec2 n2 = octEncode(norm); + outGBuffer3.xy = n1; + outGBuffer3.zw = n2; + outGBuffer0.rgb = floor(diffuseColor.rgb * 100.0f) + ao * 0.1f; + outGBuffer1.rgb = floor(specularColor.rgb * 100.0f) + fZero * 0.1f; + outGBuffer1.a = Roughness; + outGBuffer0.a = alpha; + + + + + float minVertLighting; + #ifdef BRIGHTEN_INDOOR_SHADOWS + minVertLighting = 0.0833; //brighten shadows so that caves which are naturally covered from the DL shadows are not way too dark compared to when shadows are off (mostly only necessary for naturally dark scenes, or dark areas when using the sun intensity code above) + #else + minVertLighting = 0.0533; + + #endif + + indoorSunLightExposure = max(indoorSunLightExposure, brightestPointLight); + indoorSunLightExposure = max(indoorSunLightExposure, minVertLighting); //scale the indoorSunLightExposure back up to account for the brightest point light nearby before scaling light probes by this value below + + // shading model id + outGBuffer2.a = PBR_LIGHTING + indoorSunLightExposure * 0.01f; + + if(emissive.a > 0){ + emissive = emissive * pow(emissive.a * 5, emissiveIntensity) * emissiveIntensity * 20 * emissive.a; + } + // emissive = emissive * pow(emissiveIntensity * 2.3, emissive.a); + + outGBuffer2.rgb = emissive.rgb; + + // add fog after the lighting because shadows will cause the fog to darken + // which just results in the geometry looking like it's changed color + //#ifdef USE_FOG + // #ifdef FOG_LINEAR + // gl_FragColor = getFogLinear(gl_FragColor, m_FogColor, m_LinearFog.x, m_LinearFog.y, fogDistance); + // #endif + // #ifdef FOG_EXP + // gl_FragColor = getFogExp(gl_FragColor, m_FogColor, m_ExpFog, fogDistance); + // #endif + // #ifdef FOG_EXPSQ + // gl_FragColor = getFogExpSquare(gl_FragColor, m_FogColor, m_ExpSqFog, fogDistance); + // #endif + //#endif +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/HeightBasedTerrainGBufferPack.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/HeightBasedTerrainGBufferPack.frag new file mode 100644 index 0000000000..9fe9f347a2 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/HeightBasedTerrainGBufferPack.frag @@ -0,0 +1,83 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" +// shading model +#import "Common/ShaderLib/ShadingModel.glsllib" +uniform vec3 m_region1; +uniform vec3 m_region2; +uniform vec3 m_region3; +uniform vec3 m_region4; + +uniform sampler2D m_region1ColorMap; +uniform sampler2D m_region2ColorMap; +uniform sampler2D m_region3ColorMap; +uniform sampler2D m_region4ColorMap; +uniform sampler2D m_slopeColorMap; + +uniform float m_slopeTileFactor; +uniform float m_terrainSize; + +varying vec3 normal; +varying vec4 position; + +vec4 GenerateTerrainColor() { + float height = position.y; + vec4 p = position / m_terrainSize; + + vec3 blend = abs( normal ); + blend = (blend -0.2) * 0.7; + blend = normalize(max(blend, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blend.x + blend.y + blend.z); + blend /= vec3(b, b, b); + + vec4 terrainColor = vec4(0.0, 0.0, 0.0, 1.0); + + float m_regionMin = 0.0; + float m_regionMax = 0.0; + float m_regionRange = 0.0; + float m_regionWeight = 0.0; + + vec4 slopeCol1 = texture2D(m_slopeColorMap, p.yz * m_slopeTileFactor); + vec4 slopeCol2 = texture2D(m_slopeColorMap, p.xy * m_slopeTileFactor); + + // Terrain m_region 1. + m_regionMin = m_region1.x; + m_regionMax = m_region1.y; + m_regionRange = m_regionMax - m_regionMin; + m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange; + m_regionWeight = max(0.0, m_regionWeight); + terrainColor += m_regionWeight * texture2D(m_region1ColorMap, p.xz * m_region1.z); + + // Terrain m_region 2. + m_regionMin = m_region2.x; + m_regionMax = m_region2.y; + m_regionRange = m_regionMax - m_regionMin; + m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange; + m_regionWeight = max(0.0, m_regionWeight); + terrainColor += m_regionWeight * (texture2D(m_region2ColorMap, p.xz * m_region2.z)); + + // Terrain m_region 3. + m_regionMin = m_region3.x; + m_regionMax = m_region3.y; + m_regionRange = m_regionMax - m_regionMin; + m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange; + m_regionWeight = max(0.0, m_regionWeight); + terrainColor += m_regionWeight * texture2D(m_region3ColorMap, p.xz * m_region3.z); + + // Terrain m_region 4. + m_regionMin = m_region4.x; + m_regionMax = m_region4.y; + m_regionRange = m_regionMax - m_regionMin; + m_regionWeight = (m_regionRange - abs(height - m_regionMax)) / m_regionRange; + m_regionWeight = max(0.0, m_regionWeight); + terrainColor += m_regionWeight * texture2D(m_region4ColorMap, p.xz * m_region4.z); + + return (blend.y * terrainColor + blend.x * slopeCol1 + blend.z * slopeCol2); +} + +void main() { + vec4 color = GenerateTerrainColor(); + Context_OutGBuff2.rgb = color.rgb; + + // shading model id + Context_OutGBuff2.a = UNLIT + color.a * 0.1f; +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/PBRTerrainGBufferPack.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/PBRTerrainGBufferPack.frag new file mode 100644 index 0000000000..21a1fa04d7 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/PBRTerrainGBufferPack.frag @@ -0,0 +1,435 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/PBR.glsllib" +#import "Common/ShaderLib/Parallax.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" +#import "Common/MatDefs/Terrain/AfflictionLib.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" + +// shading model +#import "Common/ShaderLib/ShadingModel.glsllib" +// octahedral +#import "Common/ShaderLib/Octahedral.glsllib" + +varying vec3 wPosition; +varying vec3 vNormal; +varying vec2 texCoord; +uniform vec3 g_CameraPosition; +varying vec3 vPosition; +varying vec3 vnPosition; +varying vec3 vViewDir; +varying vec4 vLightDir; +varying vec4 vnLightDir; +varying vec3 lightVec; +varying vec3 inNormal; +varying vec3 wNormal; + +#ifdef DEBUG_VALUES_MODE + uniform int m_DebugValuesMode; +#endif + +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; +#endif + +//texture-slot params for 12 unique texture slots (0-11) : +#for i=0..12 ( $0 ) + uniform int m_AfflictionMode_$i; + uniform float m_Roughness_$i; + uniform float m_Metallic_$i; + + #ifdef ALBEDOMAP_$i + uniform sampler2D m_AlbedoMap_$i; + #endif + #ifdef ALBEDOMAP_$i_SCALE + uniform float m_AlbedoMap_$i_scale; + #endif + #ifdef NORMALMAP_$i + uniform sampler2D m_NormalMap_$i; + #endif +#endfor + +//3 alpha maps : +#ifdef ALPHAMAP + uniform sampler2D m_AlphaMap; +#endif +#ifdef ALPHAMAP_1 + uniform sampler2D m_AlphaMap_1; +#endif +#ifdef ALPHAMAP_2 + uniform sampler2D m_AlphaMap_2; +#endif + +#ifdef DISCARD_ALPHA + uniform float m_AlphaDiscardThreshold; +#endif + +//fog vars for basic fog : +#ifdef USE_FOG +#import "Common/ShaderLib/MaterialFog.glsllib" + uniform vec4 m_FogColor; + float fogDistance; + + uniform vec2 m_LinearFog; +#endif +#ifdef FOG_EXP + uniform float m_ExpFog; +#endif +#ifdef FOG_EXPSQ + uniform float m_ExpSqFog; +#endif + +//sun intensity is a secondary AO value that can be painted per-vertex in the red channel of the +// vertex colors, or it can be set as a static value for an entire material with the StaticSunIntensity float param +#if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) + varying vec4 vertColors; +#endif + +#ifdef STATIC_SUN_INTENSITY + uniform float m_StaticSunIntensity; +#endif +//sun intensity AO value is only applied to the directional light, not to point lights, so it is important to track if the +//sun is more/less bright than the brightest point light for each fragment to determine how the light probe's ambient light should be scaled later on in light calculation code +float brightestPointLight = 0.0; + +//optional affliction paramaters that use the AfflictionAlphaMap's green channel for splatting m_SplatAlbedoMap and the red channel for splatting desaturation : +#ifdef AFFLICTIONTEXTURE + uniform sampler2D m_AfflictionAlphaMap; +#endif +#ifdef USE_SPLAT_NOISE + uniform float m_SplatNoiseVar; +#endif +//only defined for non-terrain geoemtries and terrains that are not positioned nor sized in correlation to the 2d array of AfflictionAlphaMaps used for splatting accross large tile based scenes in a grid +#ifdef TILELOCATION + uniform float m_TileWidth; + uniform vec3 m_TileLocation; +#endif +#ifdef AFFLICTIONALBEDOMAP + uniform sampler2D m_SplatAlbedoMap; +#endif +#ifdef AFFLICTIONNORMALMAP + uniform sampler2D m_SplatNormalMap; +#endif +#ifdef AFFLICTIONROUGHNESSMETALLICMAP + uniform sampler2D m_SplatRoughnessMetallicMap; +#endif +#ifdef AFFLICTIONEMISSIVEMAP + uniform sampler2D m_SplatEmissiveMap; +#endif + +uniform int m_AfflictionSplatScale; +uniform float m_AfflictionRoughnessValue; +uniform float m_AfflictionMetallicValue; +uniform float m_AfflictionEmissiveValue; +uniform vec4 m_AfflictionEmissiveColor; + +vec4 afflictionVector; +float noiseHash; +float livelinessValue; +float afflictionValue; +int afflictionMode = 1; + +//general temp vars : +vec4 tempAlbedo, tempNormal, tempEmissiveColor; +float tempParallax, tempMetallic, tempRoughness, tempAo, tempEmissiveIntensity; + +vec3 viewDir; +vec2 coord; +vec4 albedo = vec4(1.0); +vec3 normal = vec3(0.5,0.5,1); +vec3 norm; +float Metallic; +float Roughness; +float packedAoValue = 1.0; +vec4 emissive; +float emissiveIntensity = 1.0; +float indoorSunLightExposure = 1.0; + +vec4 packedMetallicRoughnessAoEiVec; +vec4 packedNormalParallaxVec; + +void main(){ + + #ifdef USE_FOG + fogDistance = distance(g_CameraPosition, wPosition.xyz); + #endif + + indoorSunLightExposure = 1.0; + + viewDir = normalize(g_CameraPosition - wPosition); + + norm = normalize(wNormal); + normal = norm; + + afflictionVector = vec4(1.0, 0.0, 1.0, 0.0); //r channel is sturation, g channel is affliction splat texture intensity, b and a unused (might use b channel for wetness eventually) + + #ifdef AFFLICTIONTEXTURE + + #ifdef TILELOCATION + //subterrains that are not centred in tile or equal to tile width in total size need to have m_TileWidth pre-set. (tileWidth is the x,z dimesnions that the AfflictionAlphaMap represents) + vec2 tileCoords; + float xPos, zPos; + + vec3 locInTile = (wPosition - m_TileLocation); + + locInTile += vec3(m_TileWidth/2, 0, m_TileWidth/2); + + xPos = (locInTile.x / m_TileWidth); + zPos = 1 - (locInTile.z / m_TileWidth); + + tileCoords = vec2(xPos, zPos); + + afflictionVector = texture2D(m_AfflictionAlphaMap, tileCoords).rgba; + #else + // ..othrewise when terrain size matches tileWidth and location matches tileLocation, the terrain's texCoords can be used for simple texel fetching of the AfflictionAlphaMap + afflictionVector = texture2D(m_AfflictionAlphaMap, texCoord.xy).rgba; + #endif + #endif + + livelinessValue = afflictionVector.r; + afflictionValue = afflictionVector.g; + + #ifdef ALBEDOMAP_0 + #ifdef ALPHAMAP + + vec4 alphaBlend; + vec4 alphaBlend_0, alphaBlend_1, alphaBlend_2; + int texChannelForAlphaBlending; + + alphaBlend_0 = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + alphaBlend_1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + alphaBlend_2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec2 texSlotCoords; + + float finalAlphaBlendForLayer = 1.0; + + vec3 blending = abs( norm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + #for i=0..12 (#ifdef ALBEDOMAP_$i $0 #endif) + + //assign texture slot's blending from index's correct alpha map + if($i <= 3){ + alphaBlend = alphaBlend_0; + }else if($i <= 7){ + alphaBlend = alphaBlend_1; + }else if($i <= 11){ + alphaBlend = alphaBlend_2; + } + + texChannelForAlphaBlending = int(mod($i, 4.0)); //pick the correct channel (r g b or a) based on the layer's index + switch(texChannelForAlphaBlending) { + case 0: + finalAlphaBlendForLayer = alphaBlend.r; + break; + case 1: + finalAlphaBlendForLayer = alphaBlend.g; + break; + case 2: + finalAlphaBlendForLayer = alphaBlend.b; + break; + case 3: + finalAlphaBlendForLayer = alphaBlend.a; + break; + } + + afflictionMode = m_AfflictionMode_$i; + + #ifdef TRI_PLANAR_MAPPING + //tri planar + tempAlbedo = getTriPlanarBlend(wVertex, blending, m_AlbedoMap_$i, m_AlbedoMap_$i_scale); + + #ifdef NORMALMAP_$i + tempNormal.rgb = getTriPlanarBlend(wVertex, blending, m_NormalMap_$i, m_AlbedoMap_$i_scale).rgb; + tempNormal.rgb = calculateTangentsAndApplyToNormals(tempNormal.rgb, wNormal);// this gets rid of the need for pre-generating tangents for TerrainPatches, since doing so doesn't seem to work (tbnMat is always blank for terrains even with tangents pre-generated, not sure why...) + #else + tempNormal.rgb = wNormal.rgb; + #endif + #else + + // non triplanar + texSlotCoords = texCoord * m_AlbedoMap_$i_scale; + + tempAlbedo.rgb = texture2D(m_AlbedoMap_$i, texSlotCoords).rgb; + #ifdef NORMALMAP_$i + tempNormal.xyz = texture2D(m_NormalMap_$i, texSlotCoords).xyz; + tempNormal.rgb = calculateTangentsAndApplyToNormals(tempNormal.rgb, wNormal); + #else + tempNormal.rgb = wNormal.rgb; + #endif + #endif + + //note: most of these functions can be found in AfflictionLib.glslib + tempAlbedo.rgb = alterLiveliness(tempAlbedo.rgb, livelinessValue, afflictionMode); //changes saturation of albedo for this layer; does nothing if not using AfflictionAlphaMap for affliction splatting + + //mix values from this index layer to final output values based on finalAlphaBlendForLayer + albedo.rgb = mix(albedo.rgb, tempAlbedo.rgb , finalAlphaBlendForLayer); + normal.rgb = mix(normal.rgb, tempNormal.rgb, finalAlphaBlendForLayer); + Metallic = mix(Metallic, m_Metallic_$i, finalAlphaBlendForLayer); + Roughness = mix(Roughness, m_Roughness_$i, finalAlphaBlendForLayer); + + #endfor + #endif + #endif + + + float alpha = albedo.a; + #ifdef DISCARD_ALPHA + if(alpha < m_AlphaDiscardThreshold){ + discard; + } + #endif + + + //APPLY AFFLICTIONN TO THE PIXEL + #ifdef AFFLICTIONTEXTURE + vec4 afflictionAlbedo; + + float newAfflictionScale = m_AfflictionSplatScale; + vec2 newScaledCoords; + + #ifdef AFFLICTIONALBEDOMAP + #ifdef TRI_PLANAR_MAPPING + newAfflictionScale = newAfflictionScale / 256; + afflictionAlbedo = getTriPlanarBlend(wVertex, blending, m_SplatAlbedoMap , newAfflictionScale); + #else + newScaledCoords = mod(wPosition.xz / m_AfflictionSplatScale, 0.985); + afflictionAlbedo = texture2D(m_SplatAlbedoMap , newScaledCoords); + #endif + + #else + afflictionAlbedo = vec4(1.0, 1.0, 1.0, 1.0); + #endif + + vec3 afflictionNormal; + #ifdef AFFLICTIONNORMALMAP + #ifdef TRI_PLANAR_MAPPING + + afflictionNormal = getTriPlanarBlend(wVertex, blending, m_SplatNormalMap , newAfflictionScale).rgb; + + #else + afflictionNormal = texture2D(m_SplatNormalMap , newScaledCoords).rgb; + #endif + + #else + afflictionNormal = norm; + + #endif + float afflictionMetallic = m_AfflictionMetallicValue; + float afflictionRoughness = m_AfflictionRoughnessValue; + float afflictionAo = 1.0; + + + vec4 afflictionEmissive = m_AfflictionEmissiveColor; + float afflictionEmissiveIntensity = m_AfflictionEmissiveValue; + + + #ifdef AFFLICTIONROUGHNESSMETALLICMAP + vec4 metallicRoughnessAoEiVec = texture2D(m_SplatRoughnessMetallicMap, newScaledCoords); + afflictionRoughness *= metallicRoughnessAoEiVec.g; + afflictionMetallic *= metallicRoughnessAoEiVec.b; + afflictionAo = metallicRoughnessAoEiVec.r; + afflictionEmissiveIntensity *= metallicRoughnessAoEiVec.a; //important not to leave this channel all black by accident when creating the mraoei map if using affliction emissiveness + + #endif + + #ifdef AFFLICTIONEMISSIVEMAP + vec4 emissiveMapColor = texture2D(m_SplatEmissiveMap, newScaledCoords); + afflictionEmissive *= emissiveMapColor; + #endif + + float adjustedAfflictionValue = afflictionValue; + #ifdef USE_SPLAT_NOISE + noiseHash = getStaticNoiseVar0(wPosition, afflictionValue * m_SplatNoiseVar); + + adjustedAfflictionValue = getAdjustedAfflictionVar(afflictionValue); + if(afflictionValue >= 0.99){ + adjustedAfflictionValue = afflictionValue; + } + #else + noiseHash = 1.0; + #endif + + Roughness = alterAfflictionRoughness(adjustedAfflictionValue, Roughness, afflictionRoughness, noiseHash); + Metallic = alterAfflictionMetallic(adjustedAfflictionValue, Metallic, afflictionMetallic, noiseHash); + albedo = alterAfflictionColor(adjustedAfflictionValue, albedo, afflictionAlbedo, noiseHash ); + normal = alterAfflictionNormalsForTerrain(adjustedAfflictionValue, normal, afflictionNormal, noiseHash , wNormal); + emissive = alterAfflictionGlow(adjustedAfflictionValue, emissive, afflictionEmissive, noiseHash); + emissiveIntensity = alterAfflictionEmissiveIntensity(adjustedAfflictionValue, emissiveIntensity, afflictionEmissiveIntensity, noiseHash); + emissiveIntensity *= afflictionEmissive.a; + //affliction ao value blended below after specular calculation + #endif + +// spec gloss pipeline code would go here if supported, but likely will not be for terrain shaders as defines are limited and heavily used + +float specular = 0.5; +float nonMetalSpec = 0.08 * specular; +vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic; +vec4 diffuseColor = albedo - albedo * Metallic; +vec3 fZero = vec3(specular); + +//simple ao calculation, no support for lightmaps like stock pbr shader.. (probably could add lightmap support with another texture array, but +// that would add another texture read per slot and require removing 12 other defines to make room...) + vec3 ao = vec3(packedAoValue); + + #ifdef AFFLICTIONTEXTURE + ao = alterAfflictionAo(afflictionValue, ao, vec3(afflictionAo), noiseHash); // alter the AO map for affliction values + #endif + ao.rgb = ao.rrr; + specularColor.rgb *= ao; + + #ifdef STATIC_SUN_INTENSITY + indoorSunLightExposure = m_StaticSunIntensity; //single float value to indicate percentage of + //sunlight hitting the model (only works for small models or models with 100% consistent sunlighting accross every pixel) + #endif + #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY + indoorSunLightExposure = vertColors.r * indoorSunLightExposure; //use R channel of vertexColors for.. + #endif + // similar purpose as above... + //but uses r channel vert colors like an AO map specifically + //for sunlight (solution for scaling lighting for indoor + // and shadey/dimly lit models, especially big ones that + // span accross varying directionalLight exposure) + brightestPointLight = 0.0; + + + // pack + vec2 n1 = octEncode(normal); + vec2 n2 = octEncode(norm); + outGBuffer3.xy = n1; + outGBuffer3.zw = n2; + outGBuffer0.rgb = floor(diffuseColor.rgb * 100.0f) + ao * 0.1f; + outGBuffer1.rgb = floor(specularColor.rgb * 100.0f) + fZero * 0.1f; + outGBuffer1.a = Roughness; + outGBuffer0.a = alpha; + + + + + float minVertLighting; + #ifdef BRIGHTEN_INDOOR_SHADOWS + minVertLighting = 0.0833; //brighten shadows so that caves which are naturally covered from the DL shadows are not way too dark compared to when shadows are off (mostly only necessary for naturally dark scenes, or dark areas when using the sun intensity code above) + #else + minVertLighting = 0.0533; + + #endif + + indoorSunLightExposure = max(indoorSunLightExposure, brightestPointLight); + indoorSunLightExposure = max(indoorSunLightExposure, minVertLighting); //scale the indoorSunLightExposure back up to account for the brightest point light nearby before scaling light probes by this value below + + // shading model id + outGBuffer2.a = PBR_LIGHTING + indoorSunLightExposure * 0.01f; + + if(emissive.a > 0){ + emissive = emissive * pow(emissive.a * 5, emissiveIntensity) * emissiveIntensity * 20 * emissive.a; + } + outGBuffer2.rgb = emissive.rgb; +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainGBufferPack.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainGBufferPack.frag new file mode 100644 index 0000000000..9f29382c19 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainGBufferPack.frag @@ -0,0 +1,70 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" +// shading model +#import "Common/ShaderLib/ShadingModel.glsllib" +uniform sampler2D m_Alpha; +uniform sampler2D m_Tex1; +uniform sampler2D m_Tex2; +uniform sampler2D m_Tex3; +uniform float m_Tex1Scale; +uniform float m_Tex2Scale; +uniform float m_Tex3Scale; + +varying vec2 texCoord; + +#ifdef TRI_PLANAR_MAPPING + varying vec4 vVertex; + varying vec3 vNormal; +#endif + +void main(void) +{ + + // get the alpha value at this 2D texture coord + vec4 alpha = texture2D( m_Alpha, texCoord.xy ); + +#ifdef TRI_PLANAR_MAPPING + // tri-planar texture bending factor for this fragment's normal + vec3 blending = abs( vNormal ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + // texture coords + vec4 coords = vVertex; + + vec4 col1 = texture2D( m_Tex1, coords.yz * m_Tex1Scale ); + vec4 col2 = texture2D( m_Tex1, coords.xz * m_Tex1Scale ); + vec4 col3 = texture2D( m_Tex1, coords.xy * m_Tex1Scale ); + // blend the results of the 3 planar projections. + vec4 tex1 = col1 * blending.x + col2 * blending.y + col3 * blending.z; + + col1 = texture2D( m_Tex2, coords.yz * m_Tex2Scale ); + col2 = texture2D( m_Tex2, coords.xz * m_Tex2Scale ); + col3 = texture2D( m_Tex2, coords.xy * m_Tex2Scale ); + // blend the results of the 3 planar projections. + vec4 tex2 = col1 * blending.x + col2 * blending.y + col3 * blending.z; + + col1 = texture2D( m_Tex3, coords.yz * m_Tex3Scale ); + col2 = texture2D( m_Tex3, coords.xz * m_Tex3Scale ); + col3 = texture2D( m_Tex3, coords.xy * m_Tex3Scale ); + // blend the results of the 3 planar projections. + vec4 tex3 = col1 * blending.x + col2 * blending.y + col3 * blending.z; + +#else + vec4 tex1 = texture2D( m_Tex1, texCoord.xy * m_Tex1Scale ); // Tile + vec4 tex2 = texture2D( m_Tex2, texCoord.xy * m_Tex2Scale ); // Tile + vec4 tex3 = texture2D( m_Tex3, texCoord.xy * m_Tex3Scale ); // Tile + +#endif + + vec4 outColor = tex1 * alpha.r; // Red channel + outColor = mix( outColor, tex2, alpha.g ); // Green channel + outColor = mix( outColor, tex3, alpha.b ); // Blue channel + + Context_OutGBuff2.rgb = outColor.rgb; + // shading model id + Context_OutGBuff2.a = UNLIT + outColor.a * 0.1f; +} + diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.frag new file mode 100644 index 0000000000..33e13bc950 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.frag @@ -0,0 +1,629 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/BlinnPhongLighting.glsllib" +#import "Common/ShaderLib/Lighting.glsllib" +#import "Common/ShaderLib/Deferred.glsllib" +// shading model +#import "Common/ShaderLib/ShadingModel.glsllib" + +uniform float m_Shininess; +uniform vec4 g_AmbientLightColor; + +varying vec4 AmbientSum; +varying vec4 DiffuseSum; +varying vec4 SpecularSum; + +varying vec3 wTangent; +varying vec3 wBinormal; +varying vec3 wNormal; +varying vec2 texCoord; + + +#ifdef DIFFUSEMAP + uniform sampler2D m_DiffuseMap; +#endif +#ifdef DIFFUSEMAP_1 + uniform sampler2D m_DiffuseMap_1; +#endif +#ifdef DIFFUSEMAP_2 + uniform sampler2D m_DiffuseMap_2; +#endif +#ifdef DIFFUSEMAP_3 + uniform sampler2D m_DiffuseMap_3; +#endif +#ifdef DIFFUSEMAP_4 + uniform sampler2D m_DiffuseMap_4; +#endif +#ifdef DIFFUSEMAP_5 + uniform sampler2D m_DiffuseMap_5; +#endif +#ifdef DIFFUSEMAP_6 + uniform sampler2D m_DiffuseMap_6; +#endif +#ifdef DIFFUSEMAP_7 + uniform sampler2D m_DiffuseMap_7; +#endif +#ifdef DIFFUSEMAP_8 + uniform sampler2D m_DiffuseMap_8; +#endif +#ifdef DIFFUSEMAP_9 + uniform sampler2D m_DiffuseMap_9; +#endif +#ifdef DIFFUSEMAP_10 + uniform sampler2D m_DiffuseMap_10; +#endif +#ifdef DIFFUSEMAP_11 + uniform sampler2D m_DiffuseMap_11; +#endif + + +#ifdef DIFFUSEMAP_0_SCALE + uniform float m_DiffuseMap_0_scale; +#endif +#ifdef DIFFUSEMAP_1_SCALE + uniform float m_DiffuseMap_1_scale; +#endif +#ifdef DIFFUSEMAP_2_SCALE + uniform float m_DiffuseMap_2_scale; +#endif +#ifdef DIFFUSEMAP_3_SCALE + uniform float m_DiffuseMap_3_scale; +#endif +#ifdef DIFFUSEMAP_4_SCALE + uniform float m_DiffuseMap_4_scale; +#endif +#ifdef DIFFUSEMAP_5_SCALE + uniform float m_DiffuseMap_5_scale; +#endif +#ifdef DIFFUSEMAP_6_SCALE + uniform float m_DiffuseMap_6_scale; +#endif +#ifdef DIFFUSEMAP_7_SCALE + uniform float m_DiffuseMap_7_scale; +#endif +#ifdef DIFFUSEMAP_8_SCALE + uniform float m_DiffuseMap_8_scale; +#endif +#ifdef DIFFUSEMAP_9_SCALE + uniform float m_DiffuseMap_9_scale; +#endif +#ifdef DIFFUSEMAP_10_SCALE + uniform float m_DiffuseMap_10_scale; +#endif +#ifdef DIFFUSEMAP_11_SCALE + uniform float m_DiffuseMap_11_scale; +#endif + + +#ifdef ALPHAMAP + uniform sampler2D m_AlphaMap; +#endif +#ifdef ALPHAMAP_1 + uniform sampler2D m_AlphaMap_1; +#endif +#ifdef ALPHAMAP_2 + uniform sampler2D m_AlphaMap_2; +#endif + +#ifdef NORMALMAP + uniform sampler2D m_NormalMap; +#endif +#ifdef NORMALMAP_1 + uniform sampler2D m_NormalMap_1; +#endif +#ifdef NORMALMAP_2 + uniform sampler2D m_NormalMap_2; +#endif +#ifdef NORMALMAP_3 + uniform sampler2D m_NormalMap_3; +#endif +#ifdef NORMALMAP_4 + uniform sampler2D m_NormalMap_4; +#endif +#ifdef NORMALMAP_5 + uniform sampler2D m_NormalMap_5; +#endif +#ifdef NORMALMAP_6 + uniform sampler2D m_NormalMap_6; +#endif +#ifdef NORMALMAP_7 + uniform sampler2D m_NormalMap_7; +#endif +#ifdef NORMALMAP_8 + uniform sampler2D m_NormalMap_8; +#endif +#ifdef NORMALMAP_9 + uniform sampler2D m_NormalMap_9; +#endif +#ifdef NORMALMAP_10 + uniform sampler2D m_NormalMap_10; +#endif +#ifdef NORMALMAP_11 + uniform sampler2D m_NormalMap_11; +#endif + + +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; + varying vec3 wNormal; +#endif + + +#ifdef ALPHAMAP + + vec4 calculateDiffuseBlend(in vec2 texCoord) { + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + vec4 diffuseColor = vec4(1.0); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + #ifdef DIFFUSEMAP + diffuseColor = texture2D(m_DiffuseMap, texCoord * m_DiffuseMap_0_scale); + #ifdef USE_ALPHA + alphaBlend.r *= diffuseColor.a; + #endif + diffuseColor *= alphaBlend.r; + #endif + #ifdef DIFFUSEMAP_1 + vec4 diffuseColor1 = texture2D(m_DiffuseMap_1, texCoord * m_DiffuseMap_1_scale); + #ifdef USE_ALPHA + alphaBlend.g *= diffuseColor1.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor1, alphaBlend.g ); + #endif + #ifdef DIFFUSEMAP_2 + vec4 diffuseColor2 = texture2D(m_DiffuseMap_2, texCoord * m_DiffuseMap_2_scale); + #ifdef USE_ALPHA + alphaBlend.b *= diffuseColor2.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor2, alphaBlend.b ); + #endif + #ifdef DIFFUSEMAP_3 + vec4 diffuseColor3 = texture2D(m_DiffuseMap_3, texCoord * m_DiffuseMap_3_scale); + #ifdef USE_ALPHA + alphaBlend.a *= diffuseColor3.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor3, alphaBlend.a ); + #endif + + #ifdef ALPHAMAP_1 + #ifdef DIFFUSEMAP_4 + vec4 diffuseColor4 = texture2D(m_DiffuseMap_4, texCoord * m_DiffuseMap_4_scale); + #ifdef USE_ALPHA + alphaBlend1.r *= diffuseColor4.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor4, alphaBlend1.r ); + #endif + #ifdef DIFFUSEMAP_5 + vec4 diffuseColor5 = texture2D(m_DiffuseMap_5, texCoord * m_DiffuseMap_5_scale); + #ifdef USE_ALPHA + alphaBlend1.g *= diffuseColor5.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor5, alphaBlend1.g ); + #endif + #ifdef DIFFUSEMAP_6 + vec4 diffuseColor6 = texture2D(m_DiffuseMap_6, texCoord * m_DiffuseMap_6_scale); + #ifdef USE_ALPHA + alphaBlend1.b *= diffuseColor6.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor6, alphaBlend1.b ); + #endif + #ifdef DIFFUSEMAP_7 + vec4 diffuseColor7 = texture2D(m_DiffuseMap_7, texCoord * m_DiffuseMap_7_scale); + #ifdef USE_ALPHA + alphaBlend1.a *= diffuseColor7.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor7, alphaBlend1.a ); + #endif + #endif + + #ifdef ALPHAMAP_2 + #ifdef DIFFUSEMAP_8 + vec4 diffuseColor8 = texture2D(m_DiffuseMap_8, texCoord * m_DiffuseMap_8_scale); + #ifdef USE_ALPHA + alphaBlend2.r *= diffuseColor8.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor8, alphaBlend2.r ); + #endif + #ifdef DIFFUSEMAP_9 + vec4 diffuseColor9 = texture2D(m_DiffuseMap_9, texCoord * m_DiffuseMap_9_scale); + #ifdef USE_ALPHA + alphaBlend2.g *= diffuseColor9.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor9, alphaBlend2.g ); + #endif + #ifdef DIFFUSEMAP_10 + vec4 diffuseColor10 = texture2D(m_DiffuseMap_10, texCoord * m_DiffuseMap_10_scale); + #ifdef USE_ALPHA + alphaBlend2.b *= diffuseColor10.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor10, alphaBlend2.b ); + #endif + #ifdef DIFFUSEMAP_11 + vec4 diffuseColor11 = texture2D(m_DiffuseMap_11, texCoord * m_DiffuseMap_11_scale); + #ifdef USE_ALPHA + alphaBlend2.a *= diffuseColor11.a; + #endif + diffuseColor = mix( diffuseColor, diffuseColor11, alphaBlend2.a ); + #endif + #endif + + return diffuseColor; + } + + vec3 calculateNormal(in vec2 texCoord) { + vec3 normal = vec3(0,0,1); + vec3 n = vec3(0,0,0); + + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + #ifdef NORMALMAP + n = texture2D(m_NormalMap, texCoord * m_DiffuseMap_0_scale).xyz; + normal += n * alphaBlend.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_1 + n = texture2D(m_NormalMap_1, texCoord * m_DiffuseMap_1_scale).xyz; + normal += n * alphaBlend.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_2 + n = texture2D(m_NormalMap_2, texCoord * m_DiffuseMap_2_scale).xyz; + normal += n * alphaBlend.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_3 + n = texture2D(m_NormalMap_3, texCoord * m_DiffuseMap_3_scale).xyz; + normal += n * alphaBlend.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + + #ifdef ALPHAMAP_1 + #ifdef NORMALMAP_4 + n = texture2D(m_NormalMap_4, texCoord * m_DiffuseMap_4_scale).xyz; + normal += n * alphaBlend1.r; + #endif + + #ifdef NORMALMAP_5 + n = texture2D(m_NormalMap_5, texCoord * m_DiffuseMap_5_scale).xyz; + normal += n * alphaBlend1.g; + #endif + + #ifdef NORMALMAP_6 + n = texture2D(m_NormalMap_6, texCoord * m_DiffuseMap_6_scale).xyz; + normal += n * alphaBlend1.b; + #endif + + #ifdef NORMALMAP_7 + n = texture2D(m_NormalMap_7, texCoord * m_DiffuseMap_7_scale).xyz; + normal += n * alphaBlend1.a; + #endif + #endif + + #ifdef ALPHAMAP_2 + #ifdef NORMALMAP_8 + n = texture2D(m_NormalMap_8, texCoord * m_DiffuseMap_8_scale).xyz; + normal += n * alphaBlend2.r; + #endif + + #ifdef NORMALMAP_9 + n = texture2D(m_NormalMap_9, texCoord * m_DiffuseMap_9_scale); + normal += n * alphaBlend2.g; + #endif + + #ifdef NORMALMAP_10 + n = texture2D(m_NormalMap_10, texCoord * m_DiffuseMap_10_scale); + normal += n * alphaBlend2.b; + #endif + + #ifdef NORMALMAP_11 + n = texture2D(m_NormalMap_11, texCoord * m_DiffuseMap_11_scale); + normal += n * alphaBlend2.a; + #endif + #endif + + normal = (normal.xyz * vec3(2.0) - vec3(1.0)); + return normalize(normal); + } + + #ifdef TRI_PLANAR_MAPPING + + vec4 getTriPlanarBlend(in vec4 coords, in vec3 blending, in sampler2D map, in float scale) { + vec4 col1 = texture2D( map, coords.yz * scale); + vec4 col2 = texture2D( map, coords.xz * scale); + vec4 col3 = texture2D( map, coords.xy * scale); + // blend the results of the 3 planar projections. + vec4 tex = col1 * blending.x + col2 * blending.y + col3 * blending.z; + return tex; + } + + vec4 calculateTriPlanarDiffuseBlend(in vec3 wNorm, in vec4 wVert, in vec2 texCoord) { + // tri-planar texture bending factor for this fragment's normal + vec3 blending = abs( wNorm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + // texture coords + vec4 coords = wVert; + + // blend the results of the 3 planar projections. + vec4 tex0 = getTriPlanarBlend(coords, blending, m_DiffuseMap, m_DiffuseMap_0_scale); + + #ifdef DIFFUSEMAP_1 + // blend the results of the 3 planar projections. + vec4 tex1 = getTriPlanarBlend(coords, blending, m_DiffuseMap_1, m_DiffuseMap_1_scale); + #endif + #ifdef DIFFUSEMAP_2 + // blend the results of the 3 planar projections. + vec4 tex2 = getTriPlanarBlend(coords, blending, m_DiffuseMap_2, m_DiffuseMap_2_scale); + #endif + #ifdef DIFFUSEMAP_3 + // blend the results of the 3 planar projections. + vec4 tex3 = getTriPlanarBlend(coords, blending, m_DiffuseMap_3, m_DiffuseMap_3_scale); + #endif + #ifdef DIFFUSEMAP_4 + // blend the results of the 3 planar projections. + vec4 tex4 = getTriPlanarBlend(coords, blending, m_DiffuseMap_4, m_DiffuseMap_4_scale); + #endif + #ifdef DIFFUSEMAP_5 + // blend the results of the 3 planar projections. + vec4 tex5 = getTriPlanarBlend(coords, blending, m_DiffuseMap_5, m_DiffuseMap_5_scale); + #endif + #ifdef DIFFUSEMAP_6 + // blend the results of the 3 planar projections. + vec4 tex6 = getTriPlanarBlend(coords, blending, m_DiffuseMap_6, m_DiffuseMap_6_scale); + #endif + #ifdef DIFFUSEMAP_7 + // blend the results of the 3 planar projections. + vec4 tex7 = getTriPlanarBlend(coords, blending, m_DiffuseMap_7, m_DiffuseMap_7_scale); + #endif + #ifdef DIFFUSEMAP_8 + // blend the results of the 3 planar projections. + vec4 tex8 = getTriPlanarBlend(coords, blending, m_DiffuseMap_8, m_DiffuseMap_8_scale); + #endif + #ifdef DIFFUSEMAP_9 + // blend the results of the 3 planar projections. + vec4 tex9 = getTriPlanarBlend(coords, blending, m_DiffuseMap_9, m_DiffuseMap_9_scale); + #endif + #ifdef DIFFUSEMAP_10 + // blend the results of the 3 planar projections. + vec4 tex10 = getTriPlanarBlend(coords, blending, m_DiffuseMap_10, m_DiffuseMap_10_scale); + #endif + #ifdef DIFFUSEMAP_11 + // blend the results of the 3 planar projections. + vec4 tex11 = getTriPlanarBlend(coords, blending, m_DiffuseMap_11, m_DiffuseMap_11_scale); + #endif + + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec4 diffuseColor = tex0 * alphaBlend.r; + #ifdef DIFFUSEMAP_1 + diffuseColor = mix( diffuseColor, tex1, alphaBlend.g ); + #endif + #ifdef DIFFUSEMAP_2 + diffuseColor = mix( diffuseColor, tex2, alphaBlend.b ); + #endif + #ifdef DIFFUSEMAP_3 + diffuseColor = mix( diffuseColor, tex3, alphaBlend.a ); + #endif + #ifdef ALPHAMAP_1 + #ifdef DIFFUSEMAP_4 + diffuseColor = mix( diffuseColor, tex4, alphaBlend1.r ); + #endif + #ifdef DIFFUSEMAP_5 + diffuseColor = mix( diffuseColor, tex5, alphaBlend1.g ); + #endif + #ifdef DIFFUSEMAP_6 + diffuseColor = mix( diffuseColor, tex6, alphaBlend1.b ); + #endif + #ifdef DIFFUSEMAP_7 + diffuseColor = mix( diffuseColor, tex7, alphaBlend1.a ); + #endif + #endif + #ifdef ALPHAMAP_2 + #ifdef DIFFUSEMAP_8 + diffuseColor = mix( diffuseColor, tex8, alphaBlend2.r ); + #endif + #ifdef DIFFUSEMAP_9 + diffuseColor = mix( diffuseColor, tex9, alphaBlend2.g ); + #endif + #ifdef DIFFUSEMAP_10 + diffuseColor = mix( diffuseColor, tex10, alphaBlend2.b ); + #endif + #ifdef DIFFUSEMAP_11 + diffuseColor = mix( diffuseColor, tex11, alphaBlend2.a ); + #endif + #endif + + return diffuseColor; + } + + vec3 calculateNormalTriPlanar(in vec3 wNorm, in vec4 wVert,in vec2 texCoord) { + // tri-planar texture bending factor for this fragment's world-space normal + vec3 blending = abs( wNorm ); + blending = (blending -0.2) * 0.7; + blending = normalize(max(blending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (blending.x + blending.y + blending.z); + blending /= vec3(b, b, b); + + // texture coords + vec4 coords = wVert; + vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy ); + + #ifdef ALPHAMAP_1 + vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + + vec3 normal = vec3(0,0,1); + vec3 n = vec3(0,0,0); + + #ifdef NORMALMAP + n = getTriPlanarBlend(coords, blending, m_NormalMap, m_DiffuseMap_0_scale).xyz; + normal += n * alphaBlend.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_1 + n = getTriPlanarBlend(coords, blending, m_NormalMap_1, m_DiffuseMap_1_scale).xyz; + normal += n * alphaBlend.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_2 + n = getTriPlanarBlend(coords, blending, m_NormalMap_2, m_DiffuseMap_2_scale).xyz; + normal += n * alphaBlend.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_3 + n = getTriPlanarBlend(coords, blending, m_NormalMap_3, m_DiffuseMap_3_scale).xyz; + normal += n * alphaBlend.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + + #ifdef ALPHAMAP_1 + #ifdef NORMALMAP_4 + n = getTriPlanarBlend(coords, blending, m_NormalMap_4, m_DiffuseMap_4_scale).xyz; + normal += n * alphaBlend1.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_5 + n = getTriPlanarBlend(coords, blending, m_NormalMap_5, m_DiffuseMap_5_scale).xyz; + normal += n * alphaBlend1.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_6 + n = getTriPlanarBlend(coords, blending, m_NormalMap_6, m_DiffuseMap_6_scale).xyz; + normal += n * alphaBlend1.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_7 + n = getTriPlanarBlend(coords, blending, m_NormalMap_7, m_DiffuseMap_7_scale).xyz; + normal += n * alphaBlend1.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + #endif + + #ifdef ALPHAMAP_2 + #ifdef NORMALMAP_8 + n = getTriPlanarBlend(coords, blending, m_NormalMap_8, m_DiffuseMap_8_scale).xyz; + normal += n * alphaBlend2.r; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.r; + #endif + + #ifdef NORMALMAP_9 + n = getTriPlanarBlend(coords, blending, m_NormalMap_9, m_DiffuseMap_9_scale).xyz; + normal += n * alphaBlend2.g; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.g; + #endif + + #ifdef NORMALMAP_10 + n = getTriPlanarBlend(coords, blending, m_NormalMap_10, m_DiffuseMap_10_scale).xyz; + normal += n * alphaBlend2.b; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.b; + #endif + + #ifdef NORMALMAP_11 + n = getTriPlanarBlend(coords, blending, m_NormalMap_11, m_DiffuseMap_11_scale).xyz; + normal += n * alphaBlend2.a; + #else + normal += vec3(0.5,0.5,1) * alphaBlend.a; + #endif + #endif + + normal = (normal.xyz * vec3(2.0) - vec3(1.0)); + return normalize(normal); + } + #endif + +#endif + +void main(){ + + //---------------------- + // diffuse calculations + //---------------------- + #ifdef DIFFUSEMAP + #ifdef ALPHAMAP + #ifdef TRI_PLANAR_MAPPING + vec4 diffuseColor = calculateTriPlanarDiffuseBlend(wNormal, wVertex, texCoord); + #else + vec4 diffuseColor = calculateDiffuseBlend(texCoord); + #endif + #else + vec4 diffuseColor = texture2D(m_DiffuseMap, texCoord); + #endif + #else + vec4 diffuseColor = vec4(1.0); + #endif + + + //--------------------- + // normal calculations + //--------------------- + #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) || defined(NORMALMAP_4) || defined(NORMALMAP_5) || defined(NORMALMAP_6) || defined(NORMALMAP_7) || defined(NORMALMAP_8) || defined(NORMALMAP_9) || defined(NORMALMAP_10) || defined(NORMALMAP_11) + #ifdef TRI_PLANAR_MAPPING + vec3 normal = calculateNormalTriPlanar(wNormal, wVertex, texCoord); + #else + vec3 normal = calculateNormal(texCoord); + #endif + mat3 tbnMat = mat3(normalize(wTangent.xyz) , normalize(wBinormal.xyz) , normalize(wNormal.xyz)); + normal = tbnMat * normal; + #else + vec3 normal = wNormal; + #endif + + //------------------------------- + // pack phongLighting parameters + //------------------------------- + outGBuffer3.xyz = normal; + outGBuffer0 = diffuseColor * DiffuseSum; + outGBuffer1.rgb = SpecularSum.rgb * 100.0f + AmbientSum.rgb * 0.01f; + //outGBuffer1.rgb = SpecularSum.rgb * 100.0f + g_AmbientLightColor.rgb * 0.01f; + outGBuffer1.a = m_Shininess; + // shading model id + outGBuffer2.a = PHONG_LIGHTING; +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.vert new file mode 100644 index 0000000000..1b54f6551e --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.vert @@ -0,0 +1,58 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +uniform mat4 g_WorldViewProjectionMatrix; +uniform mat3 g_WorldNormalMatrix; + +uniform vec4 g_AmbientLightColor; + +attribute vec3 inPosition; +attribute vec3 inNormal; +attribute vec2 inTexCoord; +attribute vec4 inTangent; + +varying vec3 wNormal; +varying vec2 texCoord; +varying vec3 wTangent; +varying vec3 wBinormal; + +varying vec4 AmbientSum; +varying vec4 DiffuseSum; +varying vec4 SpecularSum; + +#ifdef TRI_PLANAR_MAPPING + varying vec4 wVertex; + varying vec3 wNormal; +#endif + + + +void main(){ + vec4 pos = vec4(inPosition, 1.0); + gl_Position = g_WorldViewProjectionMatrix * pos; + #ifdef TERRAIN_GRID + texCoord = inTexCoord * 2.0; + #else + texCoord = inTexCoord; + #endif + + wNormal = normalize(g_WorldNormalMatrix * inNormal); + + //-------------------------- + // specific to normal maps: + //-------------------------- + #if defined(NORMALMAP) || defined(NORMALMAP_1) || defined(NORMALMAP_2) || defined(NORMALMAP_3) || defined(NORMALMAP_4) || defined(NORMALMAP_5) || defined(NORMALMAP_6) || defined(NORMALMAP_7) || defined(NORMALMAP_8) || defined(NORMALMAP_9) || defined(NORMALMAP_10) || defined(NORMALMAP_11) + wTangent = g_WorldNormalMatrix * inTangent.xyz; + wBinormal = cross(wNormal, wTangent)* inTangent.w; + #endif + + //AmbientSum = g_AmbientLightColor; + AmbientSum = vec4(1.0); + DiffuseSum = vec4(1.0); + SpecularSum = vec4(0.0); + + +#ifdef TRI_PLANAR_MAPPING + wVertex = vec4(inPosition,0.0); + wNormal = inNormal; +#endif + +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md index d2bfa0c01d..9be9124757 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md @@ -23,6 +23,13 @@ MaterialDef Terrain { Vector3 region2 Vector3 region3 Vector3 region4 + + // Context GBuffer Data + Texture2D Context_InGBuff0 + Texture2D Context_InGBuff1 + Texture2D Context_InGBuff2 + Texture2D Context_InGBuff3 + Texture2D Context_InGBuff4 } Technique { @@ -40,6 +47,22 @@ MaterialDef Terrain { } } + Technique GBufferPass{ + Pipeline Deferred + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/HeightBasedTerrain.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/GBufferPack/HeightBasedTerrainGBufferPack.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + NormalMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } + } + Technique { } } \ No newline at end of file diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md index 707442fafd..fb1197fcdf 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md @@ -328,6 +328,95 @@ MaterialDef PBR Terrain { } } + Technique GBufferPass { + + VertexShader GLSL300 GLSL150 GLSL130 GLSL100: Common/MatDefs/Terrain/PBRTerrain.vert + FragmentShader GLSL300 GLSL150 GLSL130 GLSL100: Common/MatDefs/Terrain/GBufferPack/PBRTerrainGBufferPack.frag + + WorldParameters { + WorldViewProjectionMatrix + CameraPosition + WorldMatrix + WorldNormalMatrix + ViewProjectionMatrix + ViewMatrix + Time + + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + + TILELOCATION : TileLocation + AFFLICTIONTEXTURE : AfflictionAlphaMap + + AFFLICTIONALBEDOMAP: SplatAlbedoMap + AFFLICTIONNORMALMAP : SplatNormalMap + AFFLICTIONROUGHNESSMETALLICMAP : SplatRoughnessMetallicMap + AFFLICTIONEMISSIVEMAP : SplatEmissiveMap + + USE_SPLAT_NOISE : SplatNoiseVar + + + USE_VERTEX_COLORS_AS_SUN_INTENSITY : UseVertexColorsAsSunIntensity + STATIC_SUN_INTENSITY : StaticSunIntensity + BRIGHTEN_INDOOR_SHADOWS : BrightenIndoorShadows + + DISCARD_ALPHA : AlphaDiscardThreshold + + USE_FOG : UseFog + FOG_LINEAR : LinearFog + FOG_EXP : ExpFog + FOG_EXPSQ : ExpSqFog + + TRI_PLANAR_MAPPING : useTriPlanarMapping + + ALBEDOMAP_0 : AlbedoMap_0 + ALBEDOMAP_1 : AlbedoMap_1 + ALBEDOMAP_2 : AlbedoMap_2 + ALBEDOMAP_3 : AlbedoMap_3 + ALBEDOMAP_4 : AlbedoMap_4 + ALBEDOMAP_5 : AlbedoMap_5 + ALBEDOMAP_6 : AlbedoMap_6 + ALBEDOMAP_7 : AlbedoMap_7 + ALBEDOMAP_8 : AlbedoMap_8 + ALBEDOMAP_9 : AlbedoMap_9 + ALBEDOMAP_10 : AlbedoMap_10 + ALBEDOMAP_11 : AlbedoMap_11 + + NORMALMAP_0 : NormalMap_0 + NORMALMAP_1 : NormalMap_1 + NORMALMAP_2 : NormalMap_2 + NORMALMAP_3 : NormalMap_3 + NORMALMAP_4 : NormalMap_4 + NORMALMAP_5 : NormalMap_5 + NORMALMAP_6 : NormalMap_6 + NORMALMAP_7 : NormalMap_7 + NORMALMAP_8 : NormalMap_8 + NORMALMAP_9 : NormalMap_9 + NORMALMAP_10 : NormalMap_10 + NORMALMAP_11 : NormalMap_11 + + ALPHAMAP : AlphaMap + ALPHAMAP_1 : AlphaMap_1 + ALPHAMAP_2 : AlphaMap_2 + ALBEDOMAP_0_SCALE : AlbedoMap_0_scale + ALBEDOMAP_1_SCALE : AlbedoMap_1_scale + ALBEDOMAP_2_SCALE : AlbedoMap_2_scale + ALBEDOMAP_3_SCALE : AlbedoMap_3_scale + ALBEDOMAP_4_SCALE : AlbedoMap_4_scale + ALBEDOMAP_5_SCALE : AlbedoMap_5_scale + ALBEDOMAP_6_SCALE : AlbedoMap_6_scale + ALBEDOMAP_7_SCALE : AlbedoMap_7_scale + ALBEDOMAP_8_SCALE : AlbedoMap_8_scale + ALBEDOMAP_9_SCALE : AlbedoMap_9_scale + ALBEDOMAP_10_SCALE : AlbedoMap_10_scale + ALBEDOMAP_11_SCALE : AlbedoMap_11_scale + + DEBUG_VALUES_MODE : DebugValuesMode + + } + } Technique PreShadow { diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md index 1ac3b21dc1..d4696cd1e1 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md @@ -13,12 +13,19 @@ MaterialDef Terrain { Float Tex1Scale Float Tex2Scale Float Tex3Scale + + // Context GBuffer Data + Texture2D Context_InGBuff0 + Texture2D Context_InGBuff1 + Texture2D Context_InGBuff2 + Texture2D Context_InGBuff3 + Texture2D Context_InGBuff4 } Technique { VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/Terrain.vert FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/Terrain.frag - + WorldParameters { WorldViewProjectionMatrix } @@ -28,7 +35,35 @@ MaterialDef Terrain { TRI_PLANAR_MAPPING : useTriPlanarMapping } } + + Technique GBufferPass { + + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/Terrain.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/GBufferPack/TerrainGBufferPack.frag + + WorldParameters { + WorldViewProjectionMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + INSTANCING : UseInstancing + SEPARATE_TEXCOORD : SeparateTexCoord + HAS_COLORMAP : ColorMap + HAS_LIGHTMAP : LightMap + HAS_VERTEXCOLOR : VertexColor + HAS_POINTSIZE : PointSize + HAS_COLOR : Color + NUM_BONES : NumberOfBones + DISCARD_ALPHA : AlphaDiscardThreshold + NUM_MORPH_TARGETS: NumberOfMorphTargets + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + DESATURATION : DesaturationValue + } + } Technique { } -} \ No newline at end of file +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md index 341ef1c683..773eacb03a 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md @@ -103,6 +103,13 @@ MaterialDef Terrain Lighting { // The glow color of the object Color GlowColor + + // Context GBuffer Data + Texture2D Context_InGBuff0 + Texture2D Context_InGBuff1 + Texture2D Context_InGBuff2 + Texture2D Context_InGBuff3 + Texture2D Context_InGBuff4 } Technique { @@ -236,6 +243,66 @@ MaterialDef Terrain Lighting { } } + Technique GBufferPass { + + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldNormalMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + TRI_PLANAR_MAPPING : useTriPlanarMapping + TERRAIN_GRID : isTerrainGrid + WARDISO : WardIso + + DIFFUSEMAP : DiffuseMap + DIFFUSEMAP_1 : DiffuseMap_1 + DIFFUSEMAP_2 : DiffuseMap_2 + DIFFUSEMAP_3 : DiffuseMap_3 + DIFFUSEMAP_4 : DiffuseMap_4 + DIFFUSEMAP_5 : DiffuseMap_5 + DIFFUSEMAP_6 : DiffuseMap_6 + DIFFUSEMAP_7 : DiffuseMap_7 + DIFFUSEMAP_8 : DiffuseMap_8 + DIFFUSEMAP_9 : DiffuseMap_9 + DIFFUSEMAP_10 : DiffuseMap_10 + DIFFUSEMAP_11 : DiffuseMap_11 + NORMALMAP : NormalMap + NORMALMAP_1 : NormalMap_1 + NORMALMAP_2 : NormalMap_2 + NORMALMAP_3 : NormalMap_3 + NORMALMAP_4 : NormalMap_4 + NORMALMAP_5 : NormalMap_5 + NORMALMAP_6 : NormalMap_6 + NORMALMAP_7 : NormalMap_7 + NORMALMAP_8 : NormalMap_8 + NORMALMAP_9 : NormalMap_9 + NORMALMAP_10 : NormalMap_10 + NORMALMAP_11 : NormalMap_11 + SPECULARMAP : SpecularMap + ALPHAMAP : AlphaMap + ALPHAMAP_1 : AlphaMap_1 + ALPHAMAP_2 : AlphaMap_2 + DIFFUSEMAP_0_SCALE : DiffuseMap_0_scale + DIFFUSEMAP_1_SCALE : DiffuseMap_1_scale + DIFFUSEMAP_2_SCALE : DiffuseMap_2_scale + DIFFUSEMAP_3_SCALE : DiffuseMap_3_scale + DIFFUSEMAP_4_SCALE : DiffuseMap_4_scale + DIFFUSEMAP_5_SCALE : DiffuseMap_5_scale + DIFFUSEMAP_6_SCALE : DiffuseMap_6_scale + DIFFUSEMAP_7_SCALE : DiffuseMap_7_scale + DIFFUSEMAP_8_SCALE : DiffuseMap_8_scale + DIFFUSEMAP_9_SCALE : DiffuseMap_9_scale + DIFFUSEMAP_10_SCALE : DiffuseMap_10_scale + DIFFUSEMAP_11_SCALE : DiffuseMap_11_scale + + USE_ALPHA : useDiffuseAlpha + } + } Technique PreShadow {