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..cc2d2e93bb 100644
--- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
+++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
@@ -62,6 +62,47 @@ public class TechniqueDef implements Savable, Cloneable {
*/
public static final String DEFAULT_TECHNIQUE_NAME = "Default";
+ /**
+ * RenderPipeline.
+ */
+ public enum Pipeline{
+ /**
+ * Default, most basic rendering
+ */
+ Forward("Forward"),
+
+ /**
+ * Forward based on Cluster
+ */
+ ForwardPlus("ForwardPlus"),
+
+ /**
+ * Standard Deferred Rendering
+ */
+ Deferred("Deferred"),
+
+ /**
+ * Tiled Based Deferred Rendering
+ */
+ TiledBasedDeferred("TiledBasedDeferred"),
+
+ /**
+ * Clustered Based Deferred Rendering
+ */
+ ClusteredBasedDeferred("ClusteredBasedDeferred"),
+ ;
+
+ private String text;
+ Pipeline(String t){
+ text = t;
+ }
+
+ @Override
+ public String toString() {
+ return text;
+ }
+ }
+
/**
* Describes light rendering mode.
*/
@@ -100,6 +141,22 @@ public enum LightMode {
*/
SinglePassAndImageBased,
+ /**
+ * Enable light rendering by using deferred single pass.
+ *
+ * An array of light positions and light colors is passed to the shader
+ * containing the world light list for the geometry being rendered.
+ */
+ DeferredSinglePass,
+
+ /**
+ * Enable light rendering by using tile-based deferred single pass.
+ *
+ * An array of light positions and light colors is passed to the shader
+ * containing the world light list for the geometry being rendered.
+ */
+ TileBasedDeferredSinglePass,
+
/**
* @deprecated OpenGL1 is not supported anymore
*/
@@ -156,6 +213,7 @@ public enum LightSpace {
private LightMode lightMode = LightMode.Disable;
private ShadowMode shadowMode = ShadowMode.Disable;
+ private Pipeline pipeline = Pipeline.Forward;
private TechniqueDefLogic logic;
private ArrayList worldBinds;
@@ -239,6 +297,19 @@ public void setLightMode(LightMode lightMode) {
}
}
+ public Pipeline getPipeline() {
+ return pipeline;
+ }
+
+ /**
+ * Set the pipeline
+ * @param pipeline the render pipeline
+ * @see Pipeline
+ */
+ public void setPipeline(Pipeline pipeline){
+ this.pipeline = pipeline;
+ }
+
public void setLogic(TechniqueDefLogic logic) {
this.logic = logic;
}
@@ -514,13 +585,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 +600,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 +886,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/DeferredSinglePassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/DeferredSinglePassLightingLogic.java
new file mode 100644
index 0000000000..79ed98e35a
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/material/logic/DeferredSinglePassLightingLogic.java
@@ -0,0 +1,531 @@
+/*
+ * 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.asset.AssetManager;
+import com.jme3.light.*;
+import com.jme3.material.RenderState;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.material.TechniqueDef;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+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.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.texture.image.ImageRaster;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * DeferredShading.
+ *
+ * This is a standard DeferredShading, without any optimizations, but it should be faster than Forward when there are complex scenes and lots of lights. It is suitable for deferred rendering scenarios with simple requirements. There are some ways to optimize it:
+ * A few involved drawing proxy geometry that approximated the bounds of each type of light and evaluating lighting by sampling from the G buffer for each fragment that the geometry touched. This can be implemented with varying complexity of proxy geometry.
+ * Some implementations just used billboarded quads with enough width and height in world space to approximate the bounds of the area that the light influences. For instance a point light would just have a quad with a width and height the same as the lights radius of influence.
+ * Other implementations actually draw 3D proxy geometry like spheres for point lights and cones for spotlights.
+ *
+ * These implementations have the issue that they require many additional samples of the G buffer. Each light still needs to sample the G buffer for each texture that it has; in my case 4 textures. So each fragment of the G buffer gets sampled 4 * the number of lights affecting that fragment.
+ * Additionally these techniques incur a lot of overdraw since many of the proxy geometry objects will overlap and cannot be culled most of the time.
+ * @author JohnKkk
+ */
+public final class DeferredSinglePassLightingLogic extends DefaultTechniqueDefLogic {
+ private final static String _S_LIGHT_CULL_DRAW_STAGE = "Light_Cull_Draw_Stage";
+ private static final String DEFINE_DEFERRED_SINGLE_PASS_LIGHTING = "DEFERRED_SINGLE_PASS_LIGHTING";
+ private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS";
+ private static final String DEFINE_USE_TEXTURE_PACK_MODE = "USE_TEXTURE_PACK_MODE";
+ private static final String DEFINE_USE_AMBIENT_LIGHT = "USE_AMBIENT_LIGHT";
+ private static final String DEFINE_PACK_NB_LIGHTS = "PACK_NB_LIGHTS";
+ private static final String DEFINE_NB_SKY_LIGHT_AND_REFLECTION_PROBES = "NB_SKY_LIGHT_AND_REFLECTION_PROBES";
+ private static final RenderState ADDITIVE_LIGHT = new RenderState();
+ private boolean bUseTexturePackMode = true;
+ // Avoid too many lights
+ private static final int MAX_LIGHT_NUM = 9046;
+ // Use textures to store large amounts of light data at once, avoiding multiple draw calls
+ private Texture2D lightData1;
+ private Texture2D lightData2;
+ private Texture2D lightData3;
+ private ImageRaster lightDataUpdateIO1;
+ private ImageRaster lightDataUpdateIO2;
+ private ImageRaster lightDataUpdateIO3;
+ private int lightNum;
+ private boolean useAmbientLight;
+
+ private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+ final private List skyLightAndReflectionProbes = new ArrayList<>(3);
+
+ static {
+ ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
+ ADDITIVE_LIGHT.setDepthWrite(false);
+ }
+
+ private final int singlePassLightingDefineId;
+ private int nbLightsDefineId;
+ private int packNbLightsDefineId;
+ private int packTextureModeDefineId;
+ private final int nbSkyLightAndReflectionProbesDefineId;
+ private final int useAmbientLightDefineId;
+
+ public DeferredSinglePassLightingLogic(TechniqueDef techniqueDef) {
+ super(techniqueDef);
+ singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_DEFERRED_SINGLE_PASS_LIGHTING, VarType.Boolean);
+ if(bUseTexturePackMode){
+ packNbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_PACK_NB_LIGHTS, VarType.Int);
+ packTextureModeDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_USE_TEXTURE_PACK_MODE, VarType.Boolean);
+ prepareLightData(1024);
+ }
+ else{
+ nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
+ }
+ nbSkyLightAndReflectionProbesDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_SKY_LIGHT_AND_REFLECTION_PROBES, VarType.Int);
+ useAmbientLightDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_USE_AMBIENT_LIGHT, VarType.Boolean);
+ }
+
+ private void cleanupLightData(){
+ if(this.lightData1 != null){
+ this.lightData1.getImage().dispose();
+ }
+ if(this.lightData2 != null){
+ this.lightData2.getImage().dispose();
+ }
+ if(this.lightData3 != null){
+ this.lightData3.getImage().dispose();
+ }
+ }
+
+ /**
+ * Try reallocating textures to accommodate enough light data.
+ * Currently, a large amount of light information is stored in textures, divided into three texture1d,
+ * lightData1 stores lightColor (rgb stores lightColor, a stores lightType), lightData2 stores lightPosition +
+ * invRange/lightDir, lightData3 stores dir and spotAngleCos about SpotLight.
+ * @param lightNum By preallocating texture memory for the known number of lights, dynamic reallocation at runtime can be prevented.
+ */
+ private void prepareLightData(int lightNum){
+ this.lightNum = lightNum;
+ // 1d texture
+ lightData1 = new Texture2D(lightNum, 1, Image.Format.RGBA32F);
+ lightData1.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ lightData1.setMagFilter(Texture.MagFilter.Nearest);
+ lightData1.setWrap(Texture.WrapMode.EdgeClamp);
+ ByteBuffer data = BufferUtils.createByteBuffer( (int)Math.ceil(Image.Format.RGBA32F.getBitsPerPixel() / 8.0) * lightNum);
+ Image convertedImage = new Image(Image.Format.RGBA32F, lightNum, 1, data, null, ColorSpace.Linear);
+ lightData1.setImage(convertedImage);
+ lightData1.getImage().setMipmapsGenerated(false);
+ lightDataUpdateIO1 = ImageRaster.create(lightData1.getImage());
+
+ lightData2 = new Texture2D(lightNum, 1, Image.Format.RGBA32F);
+ lightData2.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ lightData2.setMagFilter(Texture.MagFilter.Nearest);
+ lightData2.setWrap(Texture.WrapMode.EdgeClamp);
+ ByteBuffer data2 = BufferUtils.createByteBuffer( (int)Math.ceil(Image.Format.RGBA32F.getBitsPerPixel() / 8.0) * lightNum);
+ Image convertedImage2 = new Image(Image.Format.RGBA32F, lightNum, 1, data2, null, ColorSpace.Linear);
+ lightData2.setImage(convertedImage2);
+ lightData2.getImage().setMipmapsGenerated(false);
+ lightDataUpdateIO2 = ImageRaster.create(lightData2.getImage());
+
+ lightData3 = new Texture2D(lightNum, 1, Image.Format.RGBA32F);
+ lightData3.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ lightData3.setMagFilter(Texture.MagFilter.Nearest);
+ lightData3.setWrap(Texture.WrapMode.EdgeClamp);
+ ByteBuffer data3 = BufferUtils.createByteBuffer( (int)Math.ceil(Image.Format.RGBA32F.getBitsPerPixel() / 8.0) * lightNum);
+ Image convertedImage3 = new Image(Image.Format.RGBA32F, lightNum, 1, data3, null, ColorSpace.Linear);
+ lightData3.setImage(convertedImage3);
+ lightData3.getImage().setMipmapsGenerated(false);
+ lightDataUpdateIO3 = ImageRaster.create(lightData3.getImage());
+ }
+
+ @Override
+ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
+ EnumSet rendererCaps, LightList lights, DefineList defines) {
+ if(bUseTexturePackMode){
+ defines.set(packNbLightsDefineId, this.lightNum);
+ defines.set(packTextureModeDefineId, true);
+ }
+ else{
+ defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3);
+ }
+ defines.set(singlePassLightingDefineId, true);
+ //TODO here we have a problem, this is called once before render, so the define will be set for all passes (in case we have more than NB_LIGHTS lights)
+ //Though the second pass should not render IBL as it is taken care of on first pass like ambient light in phong lighting.
+ //We cannot change the define between passes and the old technique, and for some reason the code fails on mac (renders nothing).
+ if(lights != null) {
+ useAmbientLight = SkyLightAndReflectionProbeRender.extractSkyLightAndReflectionProbes(lights, ambientLightColor, skyLightAndReflectionProbes, false);
+ defines.set(nbSkyLightAndReflectionProbesDefineId, skyLightAndReflectionProbes.size());
+ defines.set(useAmbientLightDefineId, useAmbientLight);
+ }
+ return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
+ }
+
+ /**
+ * It seems the lighting information is encoded into 3 1D textures that are updated each frame with the currently visible lights:
+ * lightData1:
+ * - rgb stores lightColor
+ * - a stores lightTypeId
+ * lightData2:
+ * - directionalLightDirection
+ * - pointLightPosition + invRadius
+ * - spotLightPosition + invRadius
+ * lightData3:
+ * - spotLightDirection
+ * @param shader Current shader used for rendering (a global shader)
+ * @param g Current geometry used for rendering (a rect)
+ * @param lightList Information about all visible lights this frame
+ * @param numLights numLights
+ * @param rm renderManager
+ * @param startIndex first light start offset
+ * @param isLightCullStageDraw cullMode
+ * @param lastTexUnit lastTexUnit the index of the most recently-used texture unit
+ * @return the next starting index in the LightList
+ */
+ protected int updateLightListPackToTexture(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, boolean isLightCullStageDraw, int lastTexUnit) {
+ if (numLights == 0) { // this shader does not do lighting, ignore.
+ return 0;
+ }
+
+ Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+
+// skyLightAndReflectionProbes.clear();
+ if (startIndex != 0 || isLightCullStageDraw) {
+ // apply additive blending for 2nd and future passes
+ rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
+ ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+ } else {
+// extractSkyLightAndReflectionProbes(lightList,true);
+ ambientColor.setValue(VarType.Vector4, ambientLightColor);
+ }
+
+ // render skyLights and reflectionProbes
+ if(!skyLightAndReflectionProbes.isEmpty()){
+ // Matrix4f
+ Uniform skyLightData = shader.getUniform("g_SkyLightData");
+ Uniform skyLightData2 = shader.getUniform("g_SkyLightData2");
+ Uniform skyLightData3 = shader.getUniform("g_SkyLightData3");
+
+ Uniform shCoeffs = shader.getUniform("g_ShCoeffs");
+ Uniform reflectionProbePemMap = shader.getUniform("g_ReflectionEnvMap");
+ Uniform shCoeffs2 = shader.getUniform("g_ShCoeffs2");
+ Uniform reflectionProbePemMap2 = shader.getUniform("g_ReflectionEnvMap2");
+ Uniform shCoeffs3 = shader.getUniform("g_ShCoeffs3");
+ Uniform reflectionProbePemMap3 = shader.getUniform("g_ReflectionEnvMap3");
+
+ LightProbe skyLight = skyLightAndReflectionProbes.get(0);
+ lastTexUnit = SkyLightAndReflectionProbeRender.setSkyLightAndReflectionProbeData(rm, lastTexUnit, skyLightData, shCoeffs, reflectionProbePemMap, skyLight);
+ if (skyLightAndReflectionProbes.size() > 1) {
+ skyLight = skyLightAndReflectionProbes.get(1);
+ lastTexUnit = SkyLightAndReflectionProbeRender.setSkyLightAndReflectionProbeData(rm, lastTexUnit, skyLightData2, shCoeffs2, reflectionProbePemMap2, skyLight);
+ }
+ if (skyLightAndReflectionProbes.size() > 2) {
+ skyLight = skyLightAndReflectionProbes.get(2);
+ SkyLightAndReflectionProbeRender.setSkyLightAndReflectionProbeData(rm, lastTexUnit, skyLightData3, shCoeffs3, reflectionProbePemMap3, skyLight);
+ }
+ } else {
+ Uniform skyLightData = shader.getUniform("g_SkyLightData");
+ //Disable IBL for this pass
+ skyLightData.setValue(VarType.Matrix4, LightProbe.FALLBACK_MATRIX);
+ }
+
+ TempVars vars = TempVars.get();
+ int curIndex;
+ int endIndex = numLights + startIndex;
+ ColorRGBA temp = vars.color;
+ for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
+
+ Light l = lightList.get(curIndex);
+ if (l.getType() == Light.Type.Ambient || l.getType() == Light.Type.Probe) {
+ endIndex++;
+ continue;
+ }
+ ColorRGBA color = l.getColor();
+ //Color
+ temp.r = color.getRed();
+ temp.g = color.getGreen();
+ temp.b = color.getBlue();
+ temp.a = l.getType().getId();
+ lightDataUpdateIO1.setPixel(curIndex, 0, temp);
+
+ switch (l.getType()) {
+ case Directional:
+ DirectionalLight dl = (DirectionalLight) l;
+ Vector3f dir = dl.getDirection();
+ temp.r = dir.getX();
+ temp.g = dir.getY();
+ temp.b = dir.getZ();
+ temp.a = -1;
+ lightDataUpdateIO2.setPixel(curIndex, 0, temp);
+ break;
+ case Point:
+ PointLight pl = (PointLight) l;
+ Vector3f pos = pl.getPosition();
+ float invRadius = pl.getInvRadius();
+ temp.r = pos.getX();
+ temp.g = pos.getY();
+ temp.b = pos.getZ();
+ temp.a = invRadius;
+ lightDataUpdateIO2.setPixel(curIndex, 0, temp);
+ break;
+ case Spot:
+ SpotLight sl = (SpotLight) l;
+ Vector3f pos2 = sl.getPosition();
+ Vector3f dir2 = sl.getDirection();
+ float invRange = sl.getInvSpotRange();
+ float spotAngleCos = sl.getPackedAngleCos();
+ temp.r = pos2.getX();
+ temp.g = pos2.getY();
+ temp.b = pos2.getZ();
+ temp.a = invRange;
+ lightDataUpdateIO2.setPixel(curIndex, 0, temp);
+
+ //We transform the spot direction in view space here to save 5 varying later in the lighting shader
+ //one vec4 less and a vec4 that becomes a vec3
+ //the downside is that spotAngleCos decoding happens now in the frag shader.
+ temp.r = dir2.getX();
+ temp.g = dir2.getY();
+ temp.b = dir2.getZ();
+ temp.a = spotAngleCos;
+ lightDataUpdateIO3.setPixel(curIndex, 0, temp);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+ }
+ }
+// temp.r = temp.g = temp.b = temp.a = 0;
+// // Since the drawing is sent within the loop branch, and actually before the actual glSwapBuffers, the gl commands actually reside at the graphics driver level. So in order to correctly branch within the loop, the size must be fixed here (while filling the number of light sources).
+// ColorRGBA temp2 = vars.color2;
+// for(;curIndex < this.lightNum;curIndex++){
+// temp2 = lightDataUpdateIO1.getPixel(curIndex, 0);
+// if(temp2.r == 0 && temp2.g == 0 && temp2.b == 0 && temp2.a == 0)break;
+// lightDataUpdateIO1.setPixel(curIndex, 0, temp);
+// lightDataUpdateIO2.setPixel(curIndex, 0, temp);
+// lightDataUpdateIO3.setPixel(curIndex, 0, temp);
+// }
+ vars.release();
+ lightData1.getImage().setUpdateNeeded();
+ lightData2.getImage().setUpdateNeeded();
+ lightData3.getImage().setUpdateNeeded();
+// Uniform g_LightPackData1 = shader.getUniform("g_LightPackData1");
+// g_LightPackData1.setValue(VarType.Texture2D, lightData1);
+// Uniform g_LightPackData2 = shader.getUniform("g_LightPackData2");
+// g_LightPackData2.setValue(VarType.Texture2D, lightData2);
+// Uniform g_LightPackData3 = shader.getUniform("g_LightPackData3");
+// g_LightPackData3.setValue(VarType.Texture2D, lightData3);
+ g.getMaterial().setTexture("LightPackData1", lightData1);
+ g.getMaterial().setTexture("LightPackData2", lightData2);
+ g.getMaterial().setTexture("LightPackData3", lightData3);
+ return curIndex;
+ }
+
+ /**
+ * Uploads the lights in the light list as two uniform arrays.
+ *
+ * uniform vec4 g_LightColor[numLights];
//
+ * g_LightColor.rgb is the diffuse/specular color of the light.
//
+ * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
//
+ * 2 = Spot.
+ * uniform vec4 g_LightPosition[numLights];
//
+ * g_LightPosition.xyz is the position of the light (for point lights)
+ * // or the direction of the light (for directional lights).
//
+ * g_LightPosition.w is the inverse radius (1/r) of the light (for
+ * attenuation)
+ *
+ * @param shader the Shader being used
+ * @param g the Geometry being rendered
+ * @param lightList the list of lights
+ * @param numLights the number of lights to upload
+ * @param rm to manage rendering
+ * @param startIndex the starting index in the LightList
+ * @param isLightCullStageDraw isLightCullStageDraw
+ * @return the next starting index in the LightList
+ */
+ protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, boolean isLightCullStageDraw) {
+ if (numLights == 0) { // this shader does not do lighting, ignore.
+ return 0;
+ }
+
+ Uniform lightData = shader.getUniform("g_LightData");
+ lightData.setVector4Length(numLights * 3);//8 lights * max 3
+ Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+
+ if (startIndex != 0 || isLightCullStageDraw) {
+ // apply additive blending for 2nd and future passes
+ rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
+ ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+ } else {
+ ambientColor.setValue(VarType.Vector4, ambientLightColor);
+ }
+
+ int lightDataIndex = 0;
+ TempVars vars = TempVars.get();
+ Vector4f tmpVec = vars.vect4f1;
+ int curIndex;
+ int endIndex = numLights + startIndex;
+ for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
+
+ Light l = lightList.get(curIndex);
+ if (l.getType() == Light.Type.Ambient || l.getType() == Light.Type.Probe) {
+ endIndex++;
+ continue;
+ }
+ ColorRGBA color = l.getColor();
+ //Color
+ lightData.setVector4InArray(color.getRed(),
+ color.getGreen(),
+ color.getBlue(),
+ l.getType().getId(),
+ lightDataIndex);
+ lightDataIndex++;
+
+ switch (l.getType()) {
+ case Directional:
+ DirectionalLight dl = (DirectionalLight) l;
+ Vector3f dir = dl.getDirection();
+ lightData.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightDataIndex);
+ lightDataIndex++;
+ //PADDING
+ lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex);
+ lightDataIndex++;
+ break;
+ case Point:
+ PointLight pl = (PointLight) l;
+ Vector3f pos = pl.getPosition();
+ float invRadius = pl.getInvRadius();
+ lightData.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightDataIndex);
+ lightDataIndex++;
+ //PADDING
+ lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex);
+ lightDataIndex++;
+ break;
+ case Spot:
+ SpotLight sl = (SpotLight) l;
+ Vector3f pos2 = sl.getPosition();
+ Vector3f dir2 = sl.getDirection();
+ float invRange = sl.getInvSpotRange();
+ float spotAngleCos = sl.getPackedAngleCos();
+ lightData.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightDataIndex);
+ lightDataIndex++;
+
+ //We transform the spot direction in view space here to save 5 varying later in the lighting shader
+ //one vec4 less and a vec4 that becomes a vec3
+ //the downside is that spotAngleCos decoding happens now in the frag shader.
+ lightData.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightDataIndex);
+ lightDataIndex++;
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+ }
+ }
+ vars.release();
+ // pad unused buffer space
+ while(lightDataIndex < numLights * 3) {
+ lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
+ lightDataIndex++;
+ }
+ return curIndex;
+ }
+
+ @Override
+ public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
+ int nbRenderedLights = 0;
+ Renderer renderer = renderManager.getRenderer();
+ boolean isLightCullStageDraw = false;
+ if(geometry.getUserData(_S_LIGHT_CULL_DRAW_STAGE) != null){
+ isLightCullStageDraw = geometry.getUserData(_S_LIGHT_CULL_DRAW_STAGE);
+ }
+ // todo: One optimization approach here is:
+ // todo: Use uniform array storage scheme:
+ // todo: First divide lights into DirectionalLights, PointLights, SpotLights three lists
+ // todo: Then first draw DirectionalLights and SpotLights, these two are drawn with Rect, submitting the number of lights specified by SingleBatch at a time
+ // todo: Then use Sphere (turn on FrontFace culling to avoid camera entering sphere and light source shading disappears) to draw point lights, drawing groups of Sphere instances based on SingleBatch at a time
+
+ // todo: Another approach is to divide lights into full-screen and non full-screen, handle DirectionalLight and SpotLight as full-screen, then traverse all PointLights with invalid radius also categorized as full-screen
+ // todo: Then take the remaining PointLights as non full-screen lights, then store all light source information in the texture at once, with full-screen in front and non full-screen behind in memory
+ // todo: Then initiate a RectPass, draw all full-screen lights in one DC, update light_offset, then use precreated SphereInstance with SingleBatch to draw the remaining non full-screen lights
+ // todo: The second method will be adopted here
+
+ // todo: For light probes (temporarily implemented based on preCompute light probe), get light probe grid based on current view frustum visible range, execute multi pass according to light probe grid
+ // todo: For reflection probes, use textureArray (cubemap projection, with mipmap), collect reflection probes visible to current camera view frustum, and limit the number of reflection probes allowed in the current view frustum
+ if(bUseTexturePackMode){
+ if(this.lightNum != renderManager.getCurMaxDeferredShadingLightNum()){
+ cleanupLightData();
+ prepareLightData(renderManager.getCurMaxDeferredShadingLightNum());
+ }
+ // todo:Currently, this texturePackMode is only suitable for scenes where there are a large number of light sources per frame. The number of light sources is submitted to the texture all at once, so lightNum can be pre-allocated, but light source information can also be submitted to the texture all at once here, and then drawn in multiple passes (drawing each time by the specified singlePassLightBatchSize)
+ useAmbientLight = SkyLightAndReflectionProbeRender.extractSkyLightAndReflectionProbes(lights, ambientLightColor, skyLightAndReflectionProbes, true);
+ int count = lights.size();
+ // FIXME:Setting uniform variables this way will take effect immediately in the current frame, however lightData is set through geometry.getMaterial().setTexture(XXX) which will take effect next frame, so here I changed to use geometry.getMaterial().setParam() uniformly to update all parameters, to keep the frequency consistent.
+// Uniform lightCount = shader.getUniform("g_LightCount");
+// lightCount.setValue(VarType.Int, count);
+// lightCount.setValue(VarType.Int, this.lightNum);
+ geometry.getMaterial().setInt("NBLight", count);
+ if(count == 0){
+ nbRenderedLights = updateLightListPackToTexture(shader, geometry, lights, count, renderManager, nbRenderedLights, isLightCullStageDraw, lastTexUnit);
+ renderer.setShader(shader);
+ renderMeshFromGeometry(renderer, geometry);
+ }
+ else{
+ while (nbRenderedLights < count) {
+ // todo:Optimize deferred using the second method, here use the geometrys (rect, sphere) of the current class for drawing, instead of using the geometry passed in (or pass two geometry externally, one rect one sphereinstance)
+ nbRenderedLights = updateLightListPackToTexture(shader, geometry, lights, count, renderManager, nbRenderedLights, isLightCullStageDraw, lastTexUnit);
+ renderer.setShader(shader);
+ renderMeshFromGeometry(renderer, geometry);
+ }
+ }
+ }
+ else{
+ int batchSize = renderManager.getSinglePassLightBatchSize();
+ if (lights.size() == 0) {
+ updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, 0, isLightCullStageDraw);
+ renderer.setShader(shader);
+ renderMeshFromGeometry(renderer, geometry);
+ } else {
+ while (nbRenderedLights < lights.size()) {
+ nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, isLightCullStageDraw);
+ renderer.setShader(shader);
+ 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..c51f30918d
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/material/logic/SkyLightAndReflectionProbeRender.java
@@ -0,0 +1,125 @@
+/*
+ * 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/TileBasedDeferredSinglePassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/TileBasedDeferredSinglePassLightingLogic.java
new file mode 100644
index 0000000000..a12aab6cec
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/material/logic/TileBasedDeferredSinglePassLightingLogic.java
@@ -0,0 +1,1038 @@
+/*
+ * 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.asset.AssetManager;
+import com.jme3.light.*;
+import com.jme3.material.RenderState;
+import com.jme3.material.TechniqueDef;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+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.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.texture.image.ImageRaster;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * Tile-based DeferredShading.
+ *
+ * Tile-based deferred shading utilizes the overhead of one-sample MRT and efficient culling of lights to accelerate deferred rendering.
+ * The implementation details here separate the data into three main parts:
+ * 1.Global Light List contains light properties(lightData1+lightData2+lightData3).
+ * 2.The light index list contains light indices to the global light list(lightsIndexData).
+ * 3.Light grid contains an offset and size of the light list for each tile(lightsDecodeData).
+ * https://www.digipen.edu/sites/default/files/public/docs/theses/denis-ishmukhametov-master-of-science-in-computer-science-thesis-efficient-tile-based-deferred-shading-pipeline.pdf
+ * @author JohnKkk
+ */
+public class TileBasedDeferredSinglePassLightingLogic extends DefaultTechniqueDefLogic{
+ private final static String _S_LIGHT_CULL_DRAW_STAGE = "Light_Cull_Draw_Stage";
+ private final static String _S_TILE_SIZE = "g_TileSize";
+ private final static String _S_TILE_WIDTH = "g_WidthTile";
+ private final static String _S_TILE_HEIGHT = "g_HeightTile";
+ private static final String DEFINE_TILE_BASED_DEFERRED_SINGLE_PASS_LIGHTING = "TILE_BASED_DEFERRED_SINGLE_PASS_LIGHTING";
+ private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS";
+ private static final String DEFINE_USE_AMBIENT_LIGHT = "USE_AMBIENT_LIGHT";
+ private static final String DEFINE_USE_TEXTURE_PACK_MODE = "USE_TEXTURE_PACK_MODE";
+ private static final String DEFINE_PACK_NB_LIGHTS = "PACK_NB_LIGHTS";
+ private static final String DEFINE_NB_SKY_LIGHT_AND_REFLECTION_PROBES = "NB_SKY_LIGHT_AND_REFLECTION_PROBES";
+ // Light source retrieval encoded in ppx of Tile
+ private static final String TILE_LIGHT_DECODE = "TileLightDecode";
+ // Light source id encoded in ppx of Tile
+ private static final String TILE_LIGHT_INDEX = "TileLightIndex";
+ // Sampling offset size in Tile
+ private static final String TILE_LIGHT_OFFSET_SIZE = "g_TileLightOffsetSize";
+ private static final RenderState ADDITIVE_LIGHT = new RenderState();
+ private boolean bUseTexturePackMode = true;
+ // Avoid too many lights
+ private static final int MAX_LIGHT_NUM = 9046;
+ // Use textures to store large amounts of light data at once, avoiding multiple draw calls
+ private Texture2D lightData1;
+ private Texture2D lightData2;
+ private Texture2D lightData3;
+ private ImageRaster lightDataUpdateIO1;
+ private ImageRaster lightDataUpdateIO2;
+ private ImageRaster lightDataUpdateIO3;
+ private int lightNum;
+ private boolean useAmbientLight;
+
+ private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+ final private List skyLightAndReflectionProbes = new ArrayList<>(3);
+
+ static {
+ ADDITIVE_LIGHT.setBlendMode(RenderState.BlendMode.AlphaAdditive);
+ ADDITIVE_LIGHT.setDepthWrite(false);
+ }
+
+ private final int singlePassLightingDefineId;
+ private int nbLightsDefineId;
+ private int packNbLightsDefineId;
+ private int packTextureModeDefineId;
+ private final int nbSkyLightAndReflectionProbesDefineId;
+ private final int useAmbientLightDefineId;
+
+
+
+ // temp var
+ private Matrix4f _vp;
+ private float _camLeftCoeff = -1;
+ private float _camTopCoeff = -1;
+ private float _viewPortWidth = -1;
+ private float _viewPortHeight = -1;
+ private float[] _matArray1 = new float[16];
+ private float[] _matArray2 = new float[16];
+ private Vector3f _tempVec3 = new Vector3f();
+ private Vector3f _tempVec3_2 = new Vector3f();
+ private Vector4f _tempVec4 = new Vector4f();
+ private Vector4f _tempvec4_2 = new Vector4f();
+ private Vector4f _tempVec4_3 = new Vector4f();
+ private Vector4f _camUp = new Vector4f();
+ private Vector4f _camLeft = new Vector4f();
+ private Vector4f _lightLeft = new Vector4f();
+ private Vector4f _lightUp = new Vector4f();
+ private Vector4f _lightCenter = new Vector4f();
+ private LightFrustum _lightFrustum = new LightFrustum(0, 0, 0, 0);
+
+ // tile info
+ private TileInfo tileInfo;
+ private int _tileWidth = -1;
+ private int _tileHeight = -1;
+ private int _curTileNum = 0;
+
+ // tile light ids
+ private ArrayList> tiles;
+ // lightsIndex per tile
+ private ArrayList lightsIndex;
+ // lightsDecode per tile
+ private ArrayList lightsDecode;
+ private Texture2D lightsIndexData;
+ private Texture2D lightsDecodeData;
+ private ImageRaster lightsIndexDataUpdateIO;
+ private ImageRaster lightsDecodeDataUpdateIO;
+ private int lightIndexWidth;
+
+ /**
+ * TileInfo
+ * This is a structure representing Tile information, including how many tiles the screen is divided into, how many tiles in horizontal and vertical directions, the size of each tile, etc.
+ * @author JohnKkk
+ */
+ public static class TileInfo{
+ int tileSize = 0;
+ int tileWidth = 0;
+ int tileHeight = 0;
+ int tileNum = 0;
+
+ public TileInfo(int tileSize, int tileWidth, int tileHeight, int tileNum) {
+ this.tileSize = tileSize;
+ this.tileWidth = tileWidth;
+ this.tileHeight = tileHeight;
+ this.tileNum = tileNum;
+ }
+
+ public void updateTileSize(int tileSize){
+ this.tileSize = tileSize;
+ }
+ }
+
+ /**
+ * LightFrustum
+ * This is an internal helper class to determine the on-screen Rect for the current PointLight. It's called Frustum because it may be expanded to 3D Cluster in space in the future.
+ * @author JohnKkk
+ */
+ private static class LightFrustum{
+ float left;
+ float right;
+ float top;
+ float bottom;
+
+ public LightFrustum(float left, float right, float top, float bottom) {
+ this.left = left;
+ this.right = right;
+ this.top = top;
+ this.bottom = bottom;
+ }
+ }
+
+ /**
+ * Clear the lightsIndex cache texture for rebuilding.
+ */
+ private void cleanupLightsIndexTexture(){
+ if(lightsIndexData != null){
+ lightsIndexData.getImage().dispose();
+ lightsIndexData = null;
+ }
+ }
+
+ /**
+ * Rebuild the lightsIndex cache texture.
+ * The square root of the total number of lights that need to be queried. Note that the total number of lights to be queried is different from the total number of visible lights in the current view frustum. One tile may contain 100 lights, another tile may also contain 100 lights, then the total lights to query is 200, while the total lights in the current view frustum may only be 100!
+ * @param lightIndexWidth
+ */
+ private void createLightsIndexTexture(int lightIndexWidth){
+ this.lightIndexWidth = lightIndexWidth;
+ lightsIndexData = new Texture2D(lightIndexWidth, lightIndexWidth, Image.Format.RGBA32F);
+ lightsIndexData.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ lightsIndexData.setMagFilter(Texture.MagFilter.Nearest);
+ lightsIndexData.setWrap(Texture.WrapMode.EdgeClamp);
+ ByteBuffer dataT = BufferUtils.createByteBuffer( (int)Math.ceil(Image.Format.RGBA32F.getBitsPerPixel() / 8.0) * lightIndexWidth * lightIndexWidth);
+ Image convertedImageT = new Image(Image.Format.RGBA32F, lightIndexWidth, lightIndexWidth, dataT, null, ColorSpace.Linear);
+ lightsIndexData.setImage(convertedImageT);
+ lightsIndexData.getImage().setMipmapsGenerated(false);
+ lightsIndexDataUpdateIO = ImageRaster.create(lightsIndexData.getImage());
+ }
+
+ /**
+ * Clear the lightsDecode cache texture for rebuilding.
+ */
+ private void cleanupLightsDecodeTexture(){
+ if(lightsDecodeData != null){
+ lightsDecodeData.getImage().dispose();
+ lightsDecodeData = null;
+ }
+ }
+
+ /**
+ * Clear the lightsIndex and lightsDecode cache texture for rebuilding.
+ */
+ private void cleanupTileTexture(){
+ cleanupLightsIndexTexture();
+ cleanupLightsDecodeTexture();
+ }
+
+ /**
+ * Reallocate texture memory for the query texture for the specified tile size. This detection is performed every frame to ensure the cache textures lightsDecode and lightsIndex are recreated when screen changes or tiles change dynamically.
+ * @param tileWidth Number of tiles in horizontal direction
+ * @param tileHeight Number of tiles in vertical direction
+ * @param tileNum tileWidth * tileHeight
+ */
+ private void reset(int tileWidth, int tileHeight, int tileNum){
+ if(tileWidth != _tileWidth || tileHeight != _tileHeight){
+ cleanupTileTexture();
+ _tileWidth = tileWidth;
+ _tileHeight = tileHeight;
+
+// lightIndexWidth = (int)(Math.floor(Math.sqrt(1024)));
+ createLightsIndexTexture(lightIndexWidth);
+
+
+ lightsDecodeData = new Texture2D(_tileWidth, _tileHeight, Image.Format.RGBA32F);
+ lightsDecodeData.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ lightsDecodeData.setMagFilter(Texture.MagFilter.Nearest);
+ lightsDecodeData.setWrap(Texture.WrapMode.EdgeClamp);
+ ByteBuffer dataU = BufferUtils.createByteBuffer( (int)Math.ceil(Image.Format.RGBA32F.getBitsPerPixel() / 8.0) * _tileWidth * _tileHeight);
+ Image convertedImageU = new Image(Image.Format.RGBA32F, _tileWidth, _tileHeight, dataU, null, ColorSpace.Linear);
+ lightsDecodeData.setImage(convertedImageU);
+ lightsDecodeData.getImage().setMipmapsGenerated(false);
+ lightsDecodeDataUpdateIO = ImageRaster.create(lightsDecodeData.getImage());
+ }
+ if(_curTileNum != tileNum){
+ tiles = new ArrayList>();
+ for(int i = 0;i < tileNum;i++){
+ tiles.add(new ArrayList<>());
+ }
+ }
+ else{
+ for(int i = 0;i < tileNum;i++){
+ tiles.get(i).clear();
+ }
+ }
+ if(lightsDecode == null){
+ lightsDecode = new ArrayList<>();
+ }
+ lightsDecode.clear();
+ if(lightsIndex == null){
+ lightsIndex = new ArrayList<>();
+ }
+ lightsIndex.clear();
+ }
+
+ /**
+ * Some lights affect the whole screen, such as DirectionalLight, PointLight with infinite radius, and SpotLight. These lights will affect all tiles, so they are passed to all tiles in this method.
+ * @param tileWidth
+ * @param tileHeight
+ * @param tileNum
+ * @param tiles
+ * @param lightId
+ */
+ private void tilesFullUpdate(int tileWidth, int tileHeight, int tileNum, ArrayList> tiles, int lightId){
+ // calc tiles
+ int tileId = 0;
+ for(int l = 0;l < tileWidth;l++){
+ for(int b = 0;b < tileHeight;b++){
+ tileId = l + b * tileWidth;
+ if(tileId >= 0 && tileId < tileNum){
+ tiles.get(tileId).add(lightId);
+ }
+ }
+ }
+ }
+
+ /**
+ * Assigns the given light source to affected tiles based on its screen space view frustum.
+ * @param tileSize
+ * @param tileWidth
+ * @param tileHeight
+ * @param tileNum
+ * @param tiles
+ * @param lightFrustum screen space lightFrustum(rect)
+ * @param lightId targetLightId
+ */
+ private void tilesUpdate(int tileSize, int tileWidth, int tileHeight, int tileNum, ArrayList> tiles, LightFrustum lightFrustum, int lightId){
+ // tile built on
+ //⬆
+ //|
+ //|
+ //----------➡
+ // so,Using pixel screen precision, stepping top-right
+ int tileLeft = (int) Math.max(Math.floor(lightFrustum.left / tileSize), 0);
+ int tileRight = (int) Math.min(Math.ceil(lightFrustum.right / tileSize), tileWidth);
+ int tileBottom = (int) Math.max(Math.floor(lightFrustum.bottom / tileSize), 0);
+ int tileTop = (int) Math.min(Math.ceil(lightFrustum.top / tileSize), tileHeight);
+
+ // calc tiles
+ int tileId = 0;
+ for(int l = tileLeft;l < tileRight;l++){
+ for(int b = tileBottom;b < tileTop;b++){
+ tileId = l + b * tileWidth;
+ if(tileId >= 0 && tileId < tileNum){
+ tiles.get(tileId).add(lightId);
+ }
+ }
+ }
+ }
+
+ /**
+ * Keep the method but don't use it.
+ * @param camera
+ * @param light
+ * @return
+ */
+ @Deprecated
+ private final LightFrustum lightClip(Camera camera, Light light){
+ if(light instanceof PointLight){
+ PointLight pl = (PointLight)light;
+ float r = pl.getRadius();
+ if(r <= 0)return null;
+ camera.getUp().add(pl.getPosition(), _tempVec3);
+ _tempVec3.multLocal(r);
+ camera.getScreenCoordinates(_tempVec3, _tempVec3_2);
+ float t = _tempVec3_2.y;
+
+ camera.getUp().mult(-1, _tempVec3);
+ _tempVec3.add(pl.getPosition(), _tempVec3_2);
+ _tempVec3_2.multLocal(r);
+ camera.getScreenCoordinates(_tempVec3_2, _tempVec3);
+ float b = _tempVec3.y;
+
+ camera.getLeft().add(pl.getPosition(), _tempVec3_2);
+ _tempVec3_2.multLocal(r);
+ camera.getScreenCoordinates(_tempVec3_2, _tempVec3);
+ float l = _tempVec3.x;
+
+ camera.getLeft().mult(-1, _tempVec3);
+ _tempVec3.add(pl.getPosition(), _tempVec3_2);
+ camera.getScreenCoordinates(_tempVec3_2, _tempVec3);
+ r = _tempVec3.x;
+ _lightFrustum.left = l;
+ _lightFrustum.right = r;
+ _lightFrustum.bottom = b;
+ _lightFrustum.top = t;
+ return _lightFrustum;
+ }
+ return null;
+ }
+
+ /**
+ * Find out the view frustum (currently 2D so it's Rect) of the given light source in screen space. Once the screen-space view frustum of the light is determined, it can be assigned to associated tiles so that the tiles can use it for lighting shading.
+ * @param light targetLight
+ * @return Returns the screen space view frustum of the light, used to find associated tiles
+ */
+ private final LightFrustum lightClip(Light light){
+ // todo:Currently, only point light sources are processed
+ if(light instanceof PointLight){
+ PointLight pl = (PointLight)light;
+ float r = pl.getRadius();
+ if(r <= 0)return null;
+ 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 left = -1.0f, btm = -1.0f;
+ if(_lightCenter.z < -_lightCenter.w){
+ left = -_lightCenter.x - lw;
+ btm = -_lightCenter.y + lh;
+ }
+ else{
+ left = _lightCenter.x - lw;
+ btm = _lightCenter.y + lh;
+ }
+ float bottom = _viewPortHeight * 2.0f - btm;
+ _lightFrustum.left = left;
+ _lightFrustum.right = lw * 2.0f + left;
+ _lightFrustum.top = lh * 2.0f + bottom;
+ _lightFrustum.bottom = bottom;
+ return _lightFrustum;
+ }
+ return null;
+ }
+
+ /**
+ * Encode light information for each tile, which contains two parts:
+ * 1. The light offset and total number of lights to draw for each tile
+ * 2. The encoded list of light ids
+ * This method is called every frame, and the texture is dynamically sized (although jme3's encapsulation of dynamic texture sizes is a bit problematic, so it's currently a fixed-size dynamic texture).
+ * @param tileNum Total number of tiles
+ * @param tiles List of light ids for each tile
+ * @param tileWidth Number of tiles in horizontal direction
+ * @param tileHeight Number of tiles in vertical direction
+ * @param shader Current shader used for rendering (a global shader)
+ * @param g Current geometry used for rendering (a rect)
+ * @param lights Information about all visible lights this frame
+ */
+ private void tileLightDecode(int tileNum, ArrayList> tiles, int tileWidth, int tileHeight, Shader shader, Geometry g, LightList lights){
+ int len = lights.size();
+
+ ArrayList tile = null;
+ for(int i = 0, offset = 0;i < tileNum;i++){
+ tile = tiles.get(i);
+ len = tile.size();
+ for(int l = 0;l < len;l++){
+ lightsIndex.add(tile.get(l));
+ lightsIndex.add(0);
+ lightsIndex.add(0);
+ }
+ // u offset
+ lightsDecode.add(offset);
+ // tile light num
+ lightsDecode.add(len);
+ // Add in next step
+ lightsDecode.add(-1);
+ offset += len;
+ }
+ // Calculate light sampling size
+ int lightIndexWidth = (int) Math.ceil(Math.sqrt(lightsIndex.size() / 3));
+ if(lightIndexWidth > this.lightIndexWidth){
+ // recreate
+ cleanupLightsIndexTexture();
+ // Expanding the texture size by 1.5 times can avoid the flickering issue caused by repeatedly allocating new textures due to insufficient size in consecutive frames.
+ createLightsIndexTexture((int) (lightIndexWidth * 1.5));
+ }
+ else{
+ // todo:Due to the unknown dynamic texture size causing tile flickering, the current fixed texture size is forced to be used each time here.
+ // todo:Adjust to dynamic texture size after finding the cause later, otherwise a lot of padding data needs to be filled each time.
+ lightIndexWidth = this.lightIndexWidth;
+ }
+// else{
+// lightIndexWidth = this.lightIndexWidth;
+// }
+// int _lightIndexWidth = (int) Math.ceil(lightIndexWidth / 3);
+ // updateData
+ Uniform tileLightOffsetSizeUniform = shader.getUniform(TILE_LIGHT_OFFSET_SIZE);
+ tileLightOffsetSizeUniform.setValue(VarType.Int, lightIndexWidth);
+
+ // padding
+ for(int i = lightsIndex.size(), size = lightIndexWidth * lightIndexWidth * 3;i < size;i++){
+ lightsIndex.add(-1);
+ }
+
+ // Normalize the light uv of each tile to the light size range
+ for(int i = 0, size = lightsDecode.size();i < size;i+=3){
+ // The b component stores the v offset
+ lightsDecode.set(i + 2, lightsDecode.get(i) / lightIndexWidth);
+ lightsDecode.set(i, lightsDecode.get(i) % lightIndexWidth);
+ }
+ // updateData
+ TempVars vars = TempVars.get();
+ ColorRGBA temp = vars.color;
+ temp.b = temp.g = 0.0f;
+ temp.a = 1.0f;
+ for(int i = 0, size = lightIndexWidth;i < size;i++){
+ for(int j = 0, size2 = lightIndexWidth;j < size2;j++){
+ temp.r = lightsIndex.get((j + i * lightIndexWidth) * 3);
+ temp.g = 0.0f;
+ lightsIndexDataUpdateIO.setPixel(j, i, temp);
+ }
+ }
+ // todo:Due to the unknown dynamic texture size causing tile flickering, the current fixed texture size is forced to be used each time here.
+ // todo:Adjust to dynamic texture size after finding the cause later, otherwise a lot of padding data needs to be filled each time.
+// lightsIndexData.getImage().setWidth(lightIndexWidth);
+// lightsIndexData.getImage().setHeight(lightIndexWidth);
+// for(int i = 0, x = 0, y = 0;i < lightsIndex.size();i+=3){
+// temp.r = lightsIndex.get(i);
+// temp.g = 0.0f;
+// lightsIndexDataUpdateIO.setPixel(x++, y, temp);
+// if(x >= this.lightIndexWidth){
+// x = 0;
+// y++;
+// }
+// }
+ for(int i = 0;i < tileHeight;i++){
+ for(int j = 0;j < tileWidth;j++){
+ temp.r = lightsDecode.get((j + i * tileWidth) * 3);
+ temp.g = lightsDecode.get((j + i * tileWidth) * 3 + 1);
+ temp.b = lightsDecode.get((j + i * tileWidth) * 3 + 2);
+ lightsDecodeDataUpdateIO.setPixel(j, i, temp);
+ }
+ }
+ vars.release();
+
+ lightsIndexData.getImage().setUpdateNeeded();
+ lightsDecodeData.getImage().setUpdateNeeded();
+ g.getMaterial().setTexture(TILE_LIGHT_INDEX, lightsIndexData);
+ g.getMaterial().setTexture(TILE_LIGHT_DECODE, lightsDecodeData);
+ }
+
+
+ public TileBasedDeferredSinglePassLightingLogic(TechniqueDef techniqueDef) {
+ super(techniqueDef);
+ lightIndexWidth = (int)(Math.floor(Math.sqrt(1024)));
+ singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_TILE_BASED_DEFERRED_SINGLE_PASS_LIGHTING, VarType.Boolean);
+ if(bUseTexturePackMode){
+ packNbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_PACK_NB_LIGHTS, VarType.Int);
+ packTextureModeDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_USE_TEXTURE_PACK_MODE, VarType.Boolean);
+ prepareLightData(1024);
+ }
+ else{
+ nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
+ }
+ nbSkyLightAndReflectionProbesDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_SKY_LIGHT_AND_REFLECTION_PROBES, VarType.Int);
+ useAmbientLightDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_USE_AMBIENT_LIGHT, VarType.Boolean);
+ }
+
+ private void cleanupLightData(){
+ if(this.lightData1 != null){
+ this.lightData1.getImage().dispose();
+ }
+ if(this.lightData2 != null){
+ this.lightData2.getImage().dispose();
+ }
+ if(this.lightData3 != null){
+ this.lightData3.getImage().dispose();
+ }
+ }
+
+ /**
+ * Try reallocating textures to accommodate enough light data.
+ * Currently, a large amount of light information is stored in textures, divided into three texture1d,
+ * lightData1 stores lightColor (rgb stores lightColor, a stores lightType), lightData2 stores lightPosition +
+ * invRange/lightDir, lightData3 stores dir and spotAngleCos about SpotLight.
+ * @param lightNum By preallocating texture memory for the known number of lights, dynamic reallocation at runtime can be prevented.
+ */
+ private void prepareLightData(int lightNum){
+ this.lightNum = lightNum;
+ // 1d texture
+ lightData1 = new Texture2D(lightNum, 1, Image.Format.RGBA32F);
+ lightData1.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ lightData1.setMagFilter(Texture.MagFilter.Nearest);
+ lightData1.setWrap(Texture.WrapMode.EdgeClamp);
+ ByteBuffer data = BufferUtils.createByteBuffer( (int)Math.ceil(Image.Format.RGBA32F.getBitsPerPixel() / 8.0) * lightNum);
+ Image convertedImage = new Image(Image.Format.RGBA32F, lightNum, 1, data, null, ColorSpace.Linear);
+ lightData1.setImage(convertedImage);
+ lightData1.getImage().setMipmapsGenerated(false);
+ lightDataUpdateIO1 = ImageRaster.create(lightData1.getImage());
+
+ lightData2 = new Texture2D(lightNum, 1, Image.Format.RGBA32F);
+ lightData2.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ lightData2.setMagFilter(Texture.MagFilter.Nearest);
+ lightData2.setWrap(Texture.WrapMode.EdgeClamp);
+ ByteBuffer data2 = BufferUtils.createByteBuffer( (int)Math.ceil(Image.Format.RGBA32F.getBitsPerPixel() / 8.0) * lightNum);
+ Image convertedImage2 = new Image(Image.Format.RGBA32F, lightNum, 1, data2, null, ColorSpace.Linear);
+ lightData2.setImage(convertedImage2);
+ lightData2.getImage().setMipmapsGenerated(false);
+ lightDataUpdateIO2 = ImageRaster.create(lightData2.getImage());
+
+ lightData3 = new Texture2D(lightNum, 1, Image.Format.RGBA32F);
+ lightData3.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ lightData3.setMagFilter(Texture.MagFilter.Nearest);
+ lightData3.setWrap(Texture.WrapMode.EdgeClamp);
+ ByteBuffer data3 = BufferUtils.createByteBuffer( (int)Math.ceil(Image.Format.RGBA32F.getBitsPerPixel() / 8.0) * lightNum);
+ Image convertedImage3 = new Image(Image.Format.RGBA32F, lightNum, 1, data3, null, ColorSpace.Linear);
+ lightData3.setImage(convertedImage3);
+ lightData3.getImage().setMipmapsGenerated(false);
+ lightDataUpdateIO3 = ImageRaster.create(lightData3.getImage());
+
+// TempVars vars = TempVars.get();
+// ColorRGBA temp = vars.color;
+// temp.r = temp.g = temp.b = temp.a = 0;
+// // Since the drawing is sent within the loop branch, and actually before the actual glSwapBuffers, the gl commands actually reside at the graphics driver level. So in order to correctly branch within the loop, the size must be fixed here (while filling the number of light sources).
+// ColorRGBA temp2 = vars.color2;
+// for(int curIndex = 0;curIndex < this.lightNum;curIndex++){
+// temp2 = lightDataUpdateIO1.getPixel(curIndex, 0);
+// if(temp2.r == 0 && temp2.g == 0 && temp2.b == 0 && temp2.a == 0)break;
+// lightDataUpdateIO1.setPixel(curIndex, 0, temp);
+// lightDataUpdateIO2.setPixel(curIndex, 0, temp);
+// lightDataUpdateIO3.setPixel(curIndex, 0, temp);
+// }
+// vars.release();
+ }
+
+ @Override
+ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
+ EnumSet rendererCaps, LightList lights, DefineList defines) {
+ if(bUseTexturePackMode){
+ defines.set(packNbLightsDefineId, this.lightNum);
+ defines.set(packTextureModeDefineId, true);
+ }
+ else{
+ defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3);
+ }
+ defines.set(singlePassLightingDefineId, true);
+ //TODO here we have a problem, this is called once before render, so the define will be set for all passes (in case we have more than NB_LIGHTS lights)
+ //Though the second pass should not render IBL as it is taken care of on first pass like ambient light in phong lighting.
+ //We cannot change the define between passes and the old technique, and for some reason the code fails on mac (renders nothing).
+ if(lights != null) {
+ useAmbientLight = SkyLightAndReflectionProbeRender.extractSkyLightAndReflectionProbes(lights, ambientLightColor, skyLightAndReflectionProbes, false);
+ defines.set(nbSkyLightAndReflectionProbesDefineId, skyLightAndReflectionProbes.size());
+ defines.set(useAmbientLightDefineId, useAmbientLight);
+ }
+ return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
+ }
+
+ /**
+ * It seems the lighting information is encoded into 3 1D textures that are updated each frame with the currently visible lights:
+ * lightData1:
+ * - rgb stores lightColor
+ * - a stores lightTypeId
+ * lightData2:
+ * - directionalLightDirection
+ * - pointLightPosition + invRadius
+ * - spotLightPosition + invRadius
+ * lightData3:
+ * - spotLightDirection
+ * @param shader Current shader used for rendering (a global shader)
+ * @param g Current geometry used for rendering (a rect)
+ * @param lightList Information about all visible lights this frame
+ * @param numLights numLights
+ * @param rm renderManager
+ * @param startIndex first light start offset
+ * @param isLightCullStageDraw cullMode
+ * @param lastTexUnit lastTexUnit the index of the most recently-used texture unit
+ * @return the next starting index in the LightList
+ */
+ protected int updateLightListPackToTexture(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, boolean isLightCullStageDraw, int lastTexUnit) {
+ if (numLights == 0) { // this shader does not do lighting, ignore.
+ return 0;
+ }
+
+ Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+
+ if (startIndex != 0 || isLightCullStageDraw) {
+ // apply additive blending for 2nd and future passes
+ rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
+ ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+ } else {
+ ambientColor.setValue(VarType.Vector4, ambientLightColor);
+ }
+
+ // render skyLights and reflectionProbes
+ if(!skyLightAndReflectionProbes.isEmpty()){
+ // Matrix4f
+ Uniform skyLightData = shader.getUniform("g_SkyLightData");
+ Uniform skyLightData2 = shader.getUniform("g_SkyLightData2");
+ Uniform skyLightData3 = shader.getUniform("g_SkyLightData3");
+
+ Uniform shCoeffs = shader.getUniform("g_ShCoeffs");
+ Uniform reflectionProbePemMap = shader.getUniform("g_ReflectionEnvMap");
+ Uniform shCoeffs2 = shader.getUniform("g_ShCoeffs2");
+ Uniform reflectionProbePemMap2 = shader.getUniform("g_ReflectionEnvMap2");
+ Uniform shCoeffs3 = shader.getUniform("g_ShCoeffs3");
+ Uniform reflectionProbePemMap3 = shader.getUniform("g_ReflectionEnvMap3");
+
+ LightProbe skyLight = skyLightAndReflectionProbes.get(0);
+ lastTexUnit = SkyLightAndReflectionProbeRender.setSkyLightAndReflectionProbeData(rm, lastTexUnit, skyLightData, shCoeffs, reflectionProbePemMap, skyLight);
+ if (skyLightAndReflectionProbes.size() > 1) {
+ skyLight = skyLightAndReflectionProbes.get(1);
+ lastTexUnit = SkyLightAndReflectionProbeRender.setSkyLightAndReflectionProbeData(rm, lastTexUnit, skyLightData2, shCoeffs2, reflectionProbePemMap2, skyLight);
+ }
+ if (skyLightAndReflectionProbes.size() > 2) {
+ skyLight = skyLightAndReflectionProbes.get(2);
+ SkyLightAndReflectionProbeRender.setSkyLightAndReflectionProbeData(rm, lastTexUnit, skyLightData3, shCoeffs3, reflectionProbePemMap3, skyLight);
+ }
+ } else {
+ Uniform skyLightData = shader.getUniform("g_SkyLightData");
+ //Disable IBL for this pass
+ skyLightData.setValue(VarType.Matrix4, LightProbe.FALLBACK_MATRIX);
+ }
+
+ TempVars vars = TempVars.get();
+ int curIndex;
+ int endIndex = numLights + startIndex;
+ ColorRGBA temp = vars.color;
+ for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
+
+ Light l = lightList.get(curIndex);
+ if (l.getType() == Light.Type.Ambient || l.getType() == Light.Type.Probe) {
+ endIndex++;
+ continue;
+ }
+ ColorRGBA color = l.getColor();
+ //Color
+ temp.r = color.getRed();
+ temp.g = color.getGreen();
+ temp.b = color.getBlue();
+ temp.a = l.getType().getId();
+ lightDataUpdateIO1.setPixel(curIndex, 0, temp);
+
+ switch (l.getType()) {
+ case Directional:
+ DirectionalLight dl = (DirectionalLight) l;
+ Vector3f dir = dl.getDirection();
+ temp.r = dir.getX();
+ temp.g = dir.getY();
+ temp.b = dir.getZ();
+ temp.a = -1;
+ lightDataUpdateIO2.setPixel(curIndex, 0, temp);
+ break;
+ case Point:
+ PointLight pl = (PointLight) l;
+ Vector3f pos = pl.getPosition();
+ float invRadius = pl.getInvRadius();
+ temp.r = pos.getX();
+ temp.g = pos.getY();
+ temp.b = pos.getZ();
+ temp.a = invRadius;
+ lightDataUpdateIO2.setPixel(curIndex, 0, temp);
+ break;
+ case Spot:
+ SpotLight sl = (SpotLight) l;
+ Vector3f pos2 = sl.getPosition();
+ Vector3f dir2 = sl.getDirection();
+ float invRange = sl.getInvSpotRange();
+ float spotAngleCos = sl.getPackedAngleCos();
+ temp.r = pos2.getX();
+ temp.g = pos2.getY();
+ temp.b = pos2.getZ();
+ temp.a = invRange;
+ lightDataUpdateIO2.setPixel(curIndex, 0, temp);
+
+ //We transform the spot direction in view space here to save 5 varying later in the lighting shader
+ //one vec4 less and a vec4 that becomes a vec3
+ //the downside is that spotAngleCos decoding happens now in the frag shader.
+ temp.r = dir2.getX();
+ temp.g = dir2.getY();
+ temp.b = dir2.getZ();
+ temp.a = spotAngleCos;
+ lightDataUpdateIO3.setPixel(curIndex, 0, temp);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+ }
+ }
+ temp.r = temp.g = temp.b = temp.a = 0;
+ // Since the drawing is sent within the loop branch, and actually before the actual glSwapBuffers, the gl commands actually reside at the graphics driver level. So in order to correctly branch within the loop, the size must be fixed here (while filling the number of light sources).
+// ColorRGBA temp2 = vars.color2;
+// for(;curIndex < this.lightNum;curIndex++){
+// temp2 = lightDataUpdateIO1.getPixel(curIndex, 0);
+// if(temp2.r == 0 && temp2.g == 0 && temp2.b == 0 && temp2.a == 0)break;
+// lightDataUpdateIO1.setPixel(curIndex, 0, temp);
+// lightDataUpdateIO2.setPixel(curIndex, 0, temp);
+// lightDataUpdateIO3.setPixel(curIndex, 0, temp);
+// }
+ vars.release();
+ lightData1.getImage().setUpdateNeeded();
+ lightData2.getImage().setUpdateNeeded();
+ lightData3.getImage().setUpdateNeeded();
+// Uniform g_LightPackData1 = shader.getUniform("g_LightPackData1");
+// g_LightPackData1.setValue(VarType.Texture2D, lightData1);
+// Uniform g_LightPackData2 = shader.getUniform("g_LightPackData2");
+// g_LightPackData2.setValue(VarType.Texture2D, lightData2);
+// Uniform g_LightPackData3 = shader.getUniform("g_LightPackData3");
+// g_LightPackData3.setValue(VarType.Texture2D, lightData3);
+ g.getMaterial().setTexture("LightPackData1", lightData1);
+ g.getMaterial().setTexture("LightPackData2", lightData2);
+ g.getMaterial().setTexture("LightPackData3", lightData3);
+ return curIndex;
+ }
+
+ /**
+ * Uploads the lights in the light list as two uniform arrays.
+ *
+ * uniform vec4 g_LightColor[numLights];
//
+ * g_LightColor.rgb is the diffuse/specular color of the light.
//
+ * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
//
+ * 2 = Spot.
+ * uniform vec4 g_LightPosition[numLights];
//
+ * g_LightPosition.xyz is the position of the light (for point lights)
+ * // or the direction of the light (for directional lights).
//
+ * g_LightPosition.w is the inverse radius (1/r) of the light (for
+ * attenuation)
+ *
+ * @param shader the Shader being used
+ * @param g the Geometry being rendered
+ * @param lightList the list of lights
+ * @param numLights the number of lights to upload
+ * @param rm to manage rendering
+ * @param startIndex the starting index in the LightList
+ * @param isLightCullStageDraw isLightCullStageDraw
+ * @return the next starting index in the LightList
+ */
+ protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, boolean isLightCullStageDraw) {
+ if (numLights == 0) { // this shader does not do lighting, ignore.
+ return 0;
+ }
+
+ Uniform lightData = shader.getUniform("g_LightData");
+ lightData.setVector4Length(numLights * 3);//8 lights * max 3
+ Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+
+ if (startIndex != 0 || isLightCullStageDraw) {
+ // apply additive blending for 2nd and future passes
+ rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
+ ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+ } else {
+ ambientColor.setValue(VarType.Vector4, ambientLightColor);
+ }
+
+ int lightDataIndex = 0;
+ TempVars vars = TempVars.get();
+ Vector4f tmpVec = vars.vect4f1;
+ int curIndex;
+ int endIndex = numLights + startIndex;
+ for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
+
+ Light l = lightList.get(curIndex);
+ if (l.getType() == Light.Type.Ambient || l.getType() == Light.Type.Probe) {
+ endIndex++;
+ continue;
+ }
+ ColorRGBA color = l.getColor();
+ //Color
+ lightData.setVector4InArray(color.getRed(),
+ color.getGreen(),
+ color.getBlue(),
+ l.getType().getId(),
+ lightDataIndex);
+ lightDataIndex++;
+
+ switch (l.getType()) {
+ case Directional:
+ DirectionalLight dl = (DirectionalLight) l;
+ Vector3f dir = dl.getDirection();
+ lightData.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightDataIndex);
+ lightDataIndex++;
+ //PADDING
+ lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex);
+ lightDataIndex++;
+ break;
+ case Point:
+ PointLight pl = (PointLight) l;
+ Vector3f pos = pl.getPosition();
+ float invRadius = pl.getInvRadius();
+ lightData.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightDataIndex);
+ lightDataIndex++;
+ //PADDING
+ lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex);
+ lightDataIndex++;
+ break;
+ case Spot:
+ SpotLight sl = (SpotLight) l;
+ Vector3f pos2 = sl.getPosition();
+ Vector3f dir2 = sl.getDirection();
+ float invRange = sl.getInvSpotRange();
+ float spotAngleCos = sl.getPackedAngleCos();
+ lightData.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightDataIndex);
+ lightDataIndex++;
+
+ //We transform the spot direction in view space here to save 5 varying later in the lighting shader
+ //one vec4 less and a vec4 that becomes a vec3
+ //the downside is that spotAngleCos decoding happens now in the frag shader.
+ lightData.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightDataIndex);
+ lightDataIndex++;
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+ }
+ }
+ vars.release();
+ // pad unused buffer space
+ while(lightDataIndex < numLights * 3) {
+ lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
+ lightDataIndex++;
+ }
+ return curIndex;
+ }
+
+ @Override
+ public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
+ int nbRenderedLights = 0;
+ Renderer renderer = renderManager.getRenderer();
+ boolean isLightCullStageDraw = false;
+ if(geometry.getUserData(_S_LIGHT_CULL_DRAW_STAGE) != null){
+ isLightCullStageDraw = geometry.getUserData(_S_LIGHT_CULL_DRAW_STAGE);
+ }
+ if(bUseTexturePackMode){
+ if(this.lightNum != renderManager.getCurMaxDeferredShadingLightNum()){
+ cleanupLightData();
+ prepareLightData(renderManager.getCurMaxDeferredShadingLightNum());
+ }
+ useAmbientLight = SkyLightAndReflectionProbeRender.extractSkyLightAndReflectionProbes(lights, ambientLightColor, skyLightAndReflectionProbes, true);
+ int count = lights.size();
+ // FIXME:Setting uniform variables this way will take effect immediately in the current frame, however lightData is set through geometry.getMaterial().setTexture(XXX) which will take effect next frame, so here I changed to use geometry.getMaterial().setParam() uniformly to update all parameters, to keep the frequency consistent.
+// Uniform lightCount = shader.getUniform("g_LightCount");
+// lightCount.setValue(VarType.Int, count);
+ // Divide lights into full screen lights and non-full screen lights. Currently only PointLights with radius are treated as non-full screen lights.
+ // The lights passed in here must be PointLights with valid radii. Another approach is to fill tiles with infinite range Lights.
+ if(count > 0){
+
+ // Get tileInfo from RenderManager
+ tileInfo = renderManager.getTileInfo();
+ int tileSize = tileInfo.tileSize;
+ int tileWidth = tileInfo.tileWidth;
+ int tileHeight = tileInfo.tileHeight;
+ int tileNum = tileInfo.tileNum;
+ Uniform u_tileSize = shader.getUniform(_S_TILE_SIZE);
+ Uniform u_tileWidth = shader.getUniform(_S_TILE_WIDTH);
+ Uniform u_tileHeight = shader.getUniform(_S_TILE_HEIGHT);
+ u_tileSize.setValue(VarType.Int, tileSize);
+ u_tileWidth.setValue(VarType.Int, tileWidth);
+ u_tileHeight.setValue(VarType.Int, tileHeight);
+ reset(tileWidth, tileHeight, tileNum);
+
+ {
+ Camera camera = renderManager.getCurrentCamera();
+ _viewPortWidth = camera.getWidth() * 0.5f;
+ _viewPortHeight = camera.getHeight() * 0.5f;
+ _vp = camera.getViewProjectionMatrix();
+ Matrix4f v = camera.getViewMatrix();
+ v.get(_matArray1);
+ _tempVec3.set(_matArray1[0], _matArray1[1], _matArray1[2]);
+ _camLeftCoeff = 1.0f / camera.getWorldPlane(1).getNormal().dot(_tempVec3);
+ _tempVec3.set(_matArray1[4], _matArray1[5], _matArray1[6]);
+ _camTopCoeff = 1.0f / camera.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);
+ }
+
+ // update tiles
+ LightFrustum lightFrustum = null;
+ for(int i = 0;i < count;i++){
+ // filterLights(remove ambientLight,lightprobe...)
+ if(lights.get(i).getType() == Light.Type.Ambient || lights.get(i).getType() == Light.Type.Probe)continue;
+ lightFrustum = lightClip(lights.get(i));
+ if(lightFrustum != null){
+ tilesUpdate(tileSize, tileWidth, tileHeight, tileNum, tiles, lightFrustum, i);
+ }
+ else{
+ // full tilesLight
+ tilesFullUpdate(tileWidth, tileHeight, tileNum, tiles, i);
+ }
+ }
+
+ // Encode light source information
+// lightCount.setValue(VarType.Int, count);
+// geometry.getMaterial().setInt("NBLight", count);
+ tileLightDecode(tileNum, tiles, tileWidth, tileHeight, shader, geometry, lights);
+ while (nbRenderedLights < count) {
+ nbRenderedLights = updateLightListPackToTexture(shader, geometry, lights, count, renderManager, nbRenderedLights, isLightCullStageDraw, lastTexUnit);
+ renderer.setShader(shader);
+ renderMeshFromGeometry(renderer, geometry);
+ }
+ }
+ else{
+// geometry.getMaterial().setInt("NBLight", 0);
+ nbRenderedLights = updateLightListPackToTexture(shader, geometry, lights, count, renderManager, nbRenderedLights, isLightCullStageDraw, lastTexUnit);
+ renderer.setShader(shader);
+ renderMeshFromGeometry(renderer, geometry);
+ }
+ }
+ else{
+ // Do not use this branch, but keep it for possible future use
+ int batchSize = renderManager.getSinglePassLightBatchSize();
+ if (lights.size() == 0) {
+ updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, 0, isLightCullStageDraw);
+ renderer.setShader(shader);
+ renderMeshFromGeometry(renderer, geometry);
+ } else {
+ while (nbRenderedLights < lights.size()) {
+ nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, isLightCullStageDraw);
+ renderer.setShader(shader);
+ renderMeshFromGeometry(renderer, geometry);
+ }
+ }
+ }
+ }
+}
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 712e322e30..66bd1d6da4 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,19 @@ 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/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
index 5969a1d5bd..0b551b7af1 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
@@ -40,16 +40,21 @@
import com.jme3.material.RenderState;
import com.jme3.material.Technique;
import com.jme3.material.TechniqueDef;
+import com.jme3.material.logic.TileBasedDeferredSinglePassLightingLogic;
import com.jme3.math.Matrix4f;
import com.jme3.post.SceneProcessor;
import com.jme3.profile.AppProfiler;
import com.jme3.profile.AppStep;
import com.jme3.profile.SpStep;
import com.jme3.profile.VpStep;
+import com.jme3.renderer.framegraph.FGGlobal;
+import com.jme3.renderer.framegraph.FGRenderContext;
+import com.jme3.renderer.framegraph.FrameGraph;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.renderer.pass.*;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
@@ -79,6 +84,53 @@
* @see Spatial
*/
public class RenderManager {
+ // Maximum total number of light sources for deferred rendering
+ private int curMaxDeferredShadingLightNum = 1024;
+ // TileInfo
+ private TileBasedDeferredSinglePassLightingLogic.TileInfo tileInfo;
+ // 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.
+ private int forceTileSize = 64;
+ // If ForceTileSize is not specified, curTileSize is calculated each frame by dividing viewport horizontal width by NumberTileDivisions.
+ private int curTileSize = -1;
+ private int numberTileDivisions = 4;
+ // frameGraph=============================================================================↓
+ private RenderGeometry renderGeometry;
+ private boolean useFramegraph = true;
+ private FrameGraph frameGraph;
+ private GBufferPass gBufferPass;
+ private DeferredShadingPass deferredShadingPass;
+ private TileDeferredShadingPass tileDeferredShadingPass;
+ private OpaquePass opaquePass;
+ private SkyPass skyPass;
+ private TransparentPass transparentPass;
+ private TranslucentPass translucentPass;
+ private GuiPass guiPass;
+ private PostProcessorPass postProcessorPass;
+ // frameGraph=============================================================================↑
+
+ // RenderPath
+ public enum RenderPath{
+ None(-1, "None"),
+ Forward(0, "Forward"),
+ ForwardPlus(1, "ForwardPlus"),
+ Deferred(2, "Deferred"),
+ TiledDeferred(3, "TiledDeferred")
+ ;
+ private int id;
+ private String info;
+ RenderPath(int id, String info){
+ this.id = id;
+ this.info = info;
+ }
+
+ public int getId() {
+ return id;
+ }
+ public String getInfo(){
+ return info;
+ }
+ }
+ private RenderPath renderPath = RenderPath.Forward;
private static final Logger logger = Logger.getLogger(RenderManager.class.getName());
private final Renderer renderer;
@@ -115,8 +167,127 @@ public class RenderManager {
public RenderManager(Renderer renderer) {
this.renderer = renderer;
this.forcedOverrides.add(boundDrawBufferId);
+ if(useFramegraph){
+ frameGraph = new FrameGraph(new FGRenderContext(null, null, null));
+ gBufferPass = new GBufferPass();
+ deferredShadingPass = new DeferredShadingPass();
+ tileDeferredShadingPass = new TileDeferredShadingPass();
+ opaquePass = new OpaquePass();
+ skyPass = new SkyPass();
+ transparentPass = new TransparentPass();
+ translucentPass = new TranslucentPass();
+ guiPass = new GuiPass();
+ postProcessorPass = new PostProcessorPass("PostPass");
+ }
+ }
+
+ /**
+ * then the number of tiles per frame is dynamically calculated based on NumberTileDivisions and current viewport width.
+ * @param numberTileDivisions defaultValue is 4
+ */
+ public void setNumberTileDivisions(int numberTileDivisions) {
+ this.numberTileDivisions = numberTileDivisions;
+ }
+
+ public int getNumberTileDivisions() {
+ return numberTileDivisions;
+ }
+
+ /**
+ * Tile-based DeferredShading divides the screen into multiple tiles, then assigns lights to corresponding tiles for rendering. In theory, the number of tiles should be set as powers of 2, such as 32, 64 etc, but it can be set larger depending on usage.
+ * 0 means auto calculate, then the number of tiles per frame is dynamically calculated based on NumberTileDivisions and current viewport width.
+ * 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.
+ * @param forceTileSize
+ */
+ public void setForceTileSize(int forceTileSize) {
+ this.forceTileSize = forceTileSize;
+ }
+
+ public int getForceTileSize() {
+ return forceTileSize;
+ }
+
+ /**
+ * EnableFrameGraph?
+ * @param useFramegraph
+ */
+ public final void enableFramegraph(boolean useFramegraph){
+ this.useFramegraph = useFramegraph;
+ }
+
+ /**
+ * For performance considerations, the engine will pre-allocate a texture memory block based on this tag for packing light source data. Therefore, please adjust this to a reasonable maximum value for the scene light sources based on scene needs.
+ * @param curMaxDeferredShadingLightNum default value 1024
+ */
+ public void setCurMaxDeferredShadingLightNum(int curMaxDeferredShadingLightNum) {
+ this.curMaxDeferredShadingLightNum = curMaxDeferredShadingLightNum;
+ }
+
+ public int getCurMaxDeferredShadingLightNum() {
+ return curMaxDeferredShadingLightNum;
+ }
+
+ /**
+ * SetTileBasedInfo.
+ * @param tileSize The current size of tiles for partitioning (default 32x32 pixels).
+ * @param tileWidth Number of tiles in the horizontal direction for partitioning.
+ * @param tileHeight Number of tiles in the vertical direction for partitioning.
+ * @param tileNum
+ */
+ private final void setTileInfo(int tileSize, int tileWidth, int tileHeight, int tileNum){
+ if(tileInfo == null){
+ tileInfo = new TileBasedDeferredSinglePassLightingLogic.TileInfo(tileSize, tileWidth, tileHeight, tileNum);
+ }
}
+ /**
+ * update tile size.
+ * @param tileSize
+ */
+ public final void updateTileSize(int tileSize){
+ if(curTileSize == tileSize)return;
+ curTileSize = tileSize;
+ }
+
+ public TileBasedDeferredSinglePassLightingLogic.TileInfo getTileInfo() {
+ return tileInfo;
+ }
+
+ /**
+ * Set an IRenderGeometry for executing drawing call interfaces for the specified FGPass.
+ * @param renderGeometry
+ *
+ * @see RenderGeometry
+ */
+ public final void setRenderGeometryHandler(RenderGeometry renderGeometry){
+ this.renderGeometry = renderGeometry;
+ }
+
+ public RenderGeometry getRenderGeometryHandler() {
+ return renderGeometry;
+ }
+
+ /**
+ * SetRenderPath.
+ * @param renderPath
+ *
+ * @see RenderManager.RenderPath
+ */
+ public final void setRenderPath(RenderPath renderPath){
+ this.renderPath = renderPath;
+ }
+
+ /**
+ * Return renderPath.
+ * @return Current ActiveRenderPath.
+ *
+ * @see RenderManager.RenderPath
+ */
+ public final RenderPath getRenderPath(){
+ return this.renderPath;
+ }
+
+
/**
* Returns the pre ViewPort with the given name.
*
@@ -400,7 +571,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) {
@@ -647,9 +818,11 @@ public void renderGeometry(Geometry geom) {
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.
@@ -1171,88 +1344,196 @@ public void renderViewPort(ViewPort vp, float tpf) {
if (!vp.isEnabled()) {
return;
}
- if (prof != null) {
- prof.vpStep(VpStep.BeginRender, vp, null);
- }
- SafeArrayList processors = vp.getProcessors();
- if (processors.isEmpty()) {
- processors = null;
- }
+ if(useFramegraph){
+ RenderPath curRenderPath = vp.getRenderPath() == RenderPath.None ? renderPath : vp.getRenderPath();
- if (processors != null) {
- if (prof != null) {
- prof.vpStep(VpStep.PreFrame, vp, null);
+ if (prof!=null) prof.vpStep(VpStep.BeginRender, vp, null);
+
+ SafeArrayList processors = vp.getProcessors();
+ if (processors.isEmpty()) {
+ processors = null;
}
- for (SceneProcessor proc : processors.getArray()) {
- if (!proc.isInitialized()) {
- proc.initialize(this, vp);
+
+ 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);
}
- proc.setProfiler(this.prof);
- if (prof != null) {
- prof.spStep(SpStep.ProcPreFrame, proc.getClass().getSimpleName());
+ }
+
+ renderer.setFrameBuffer(vp.getOutputFrameBuffer());
+ setCamera(vp.getCamera(), false);
+ if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) {
+ if (vp.isClearColor()) {
+ renderer.setBackgroundColor(vp.getBackgroundColor());
}
- proc.preFrame(tpf);
+ renderer.clearBuffers(vp.isClearColor(),
+ vp.isClearDepth(),
+ vp.isClearStencil());
}
- }
- renderer.setFrameBuffer(vp.getOutputFrameBuffer());
- setCamera(vp.getCamera(), false);
- if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) {
- if (vp.isClearColor()) {
- renderer.setBackgroundColor(vp.getBackgroundColor());
+ 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);
}
- 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 (processors != null) {
+ frameGraph.reset();
+ frameGraph.getRenderContext().renderManager = this;
+ frameGraph.getRenderContext().renderQueue = vp.getQueue();
+ frameGraph.getRenderContext().viewPort = vp;
+
+ if(curRenderPath == RenderPath.Deferred){
+ frameGraph.addPass(gBufferPass);
+ deferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_0, gBufferPass.getName() + "." + GBufferPass.S_RT_0);
+ deferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_1, gBufferPass.getName() + "." + GBufferPass.S_RT_1);
+ deferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_2, gBufferPass.getName() + "." + GBufferPass.S_RT_2);
+ deferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_3, gBufferPass.getName() + "." + GBufferPass.S_RT_3);
+ deferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_4, gBufferPass.getName() + "." + GBufferPass.S_RT_4);
+ deferredShadingPass.setSinkLinkage(DeferredShadingPass.S_LIGHT_DATA, gBufferPass.getName() + "." + GBufferPass.S_LIGHT_DATA);
+ deferredShadingPass.setSinkLinkage(DeferredShadingPass.S_EXECUTE_STATE, gBufferPass.getName() + "." + GBufferPass.S_EXECUTE_STATE);
+ deferredShadingPass.setSinkLinkage(FGGlobal.S_DEFAULT_FB, gBufferPass.getName() + "." + GBufferPass.S_FB);
+ frameGraph.addPass(deferredShadingPass);
+ }
+ else if(curRenderPath == RenderPath.TiledDeferred){
+ curTileSize = forceTileSize > 0 ? forceTileSize : (getCurrentCamera().getWidth() / numberTileDivisions);
+ int tileWidth = (int)(viewWidth / curTileSize);
+ int tileHeight = (int)(viewHeight / curTileSize);
+ setTileInfo(curTileSize, tileWidth, tileHeight, tileWidth * tileHeight);
+ frameGraph.addPass(gBufferPass);
+ tileDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_0, gBufferPass.getName() + "." + GBufferPass.S_RT_0);
+ tileDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_1, gBufferPass.getName() + "." + GBufferPass.S_RT_1);
+ tileDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_2, gBufferPass.getName() + "." + GBufferPass.S_RT_2);
+ tileDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_3, gBufferPass.getName() + "." + GBufferPass.S_RT_3);
+ tileDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_4, gBufferPass.getName() + "." + GBufferPass.S_RT_4);
+ tileDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_LIGHT_DATA, gBufferPass.getName() + "." + GBufferPass.S_LIGHT_DATA);
+ tileDeferredShadingPass.setSinkLinkage(FGGlobal.S_DEFAULT_FB, gBufferPass.getName() + "." + GBufferPass.S_FB);
+ tileDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_EXECUTE_STATE, gBufferPass.getName() + "." + GBufferPass.S_EXECUTE_STATE);
+ frameGraph.addPass(tileDeferredShadingPass);
+ }
+ frameGraph.addPass(opaquePass);
+ frameGraph.addPass(skyPass);
+ frameGraph.addPass(transparentPass);
+ frameGraph.addPass(guiPass);
+
+ // todo:A temporary workaround for old pipeline postprocessors, unify later to use FG for internal logic, currently just replace with a simple PostProcessorPass
+// 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);
+// }
+ frameGraph.addPass(postProcessorPass);
+ //renders the translucent objects queue after processors have been rendered
+ frameGraph.addPass(translucentPass);
+
+ frameGraph.finalize();
+ frameGraph.execute();
+ // clear any remaining spatials that were not rendered.
+ clearQueue(vp);
+
+ if (prof!=null) prof.vpStep(VpStep.EndRender, vp, null);
+ }
+ else{
if (prof != null) {
- prof.vpStep(VpStep.PostQueue, vp, null);
+ prof.vpStep(VpStep.BeginRender, vp, null);
}
- for (SceneProcessor proc : processors.getArray()) {
+
+ SafeArrayList processors = vp.getProcessors();
+ if (processors.isEmpty()) {
+ processors = null;
+ }
+
+ if (processors != null) {
if (prof != null) {
- prof.spStep(SpStep.ProcPostQueue, proc.getClass().getSimpleName());
+ 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);
}
- proc.postQueue(vp.getQueue());
}
- }
- if (prof != null) {
- prof.vpStep(VpStep.FlushQueue, vp, null);
- }
- flushQueue(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());
+ }
- if (processors != null) {
if (prof != null) {
- prof.vpStep(VpStep.PostFrame, vp, 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);
}
- for (SceneProcessor proc : processors.getArray()) {
+
+ if (processors != null) {
if (prof != null) {
- prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName());
+ 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());
}
- proc.postFrame(vp.getOutputFrameBuffer());
}
+
if (prof != null) {
- prof.vpStep(VpStep.ProcEndRender, vp, null);
+ prof.vpStep(VpStep.FlushQueue, vp, null);
}
- }
- //renders the translucent objects queue after processors have been rendered
- renderTranslucentQueue(vp);
- // clear any remaining spatials that were not rendered.
- clearQueue(vp);
+ flushQueue(vp);
- if (prof != null) {
- prof.vpStep(VpStep.EndRender, vp, null);
+ 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);
+
+ if (prof != null) {
+ prof.vpStep(VpStep.EndRender, vp, null);
+ }
}
}
@@ -1309,10 +1590,9 @@ public void render(float tpf, boolean mainFrameBufferActive) {
}
}
-
/**
* 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() {
@@ -1323,7 +1603,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)
*/
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..7fb6a49be4 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java
@@ -107,6 +107,8 @@ public class ViewPort {
protected boolean clearStencil = false;
private boolean enabled = true;
+ private RenderManager.RenderPath renderPath = RenderManager.RenderPath.None;
+
/**
* Creates a new viewport. User code should generally use these methods instead:
*
@@ -124,6 +126,18 @@ public ViewPort(String name, Camera cam) {
this.cam = cam;
}
+ /**
+ * forceRenderPath,if None use GlobalRenderPath
+ * @param renderPath
+ */
+ public void setRenderPath(RenderManager.RenderPath renderPath) {
+ this.renderPath = renderPath;
+ }
+
+ public RenderManager.RenderPath getRenderPath() {
+ return renderPath;
+ }
+
/**
* Returns the name of the viewport as set in the constructor.
*
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGBindable.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGBindable.java
new file mode 100644
index 0000000000..64d8b63cb2
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGBindable.java
@@ -0,0 +1,47 @@
+/*
+ * 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.renderer.framegraph;
+
+/**
+ *FGBindable can be any resource that needs binding (such as state machine, FBO, Texture, Paramater...)
+ * @author JohnKkk
+ */
+public abstract class FGBindable {
+ private final static String _S_DEFAULT_BINDABLE_UID = "";
+
+ public void bind(FGRenderContext renderContext){}
+
+ public String getUID(){
+ assert false;
+ return _S_DEFAULT_BINDABLE_UID;
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGBindingPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGBindingPass.java
new file mode 100644
index 0000000000..0288e38216
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGBindingPass.java
@@ -0,0 +1,69 @@
+/*
+ * 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.renderer.framegraph;
+
+import java.util.ArrayList;
+
+/**
+ *A FGBindingPass represents a Pass that needs to perform state machine binding, ShaderResource binding, FrameBuffer binding and other operations.
+ * @author JohnKkk
+ */
+public class FGBindingPass extends FGPass{
+ protected ArrayList binds;
+ protected FGBindingPass(String name){
+ this(name, new ArrayList());
+ }
+ protected FGBindingPass(String name, ArrayList binds){
+ super(name);
+ this.binds = binds;
+ }
+
+ public void addBind(FGBindable bind){
+ binds.add(bind);
+ }
+
+ public void addBindSink(String name){
+ int index = binds.size() - 1;
+ registerSink(new FGContainerBindableSink(name, binds, index));
+ }
+
+ public void bindAll(FGRenderContext renderContext){
+ // Bind all objects
+ for(FGBindable bind : binds){
+ bind.bind(renderContext);
+ }
+ }
+
+ @Override
+ public void execute(FGRenderContext renderContext) {
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGCallbackPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGCallbackPass.java
new file mode 100644
index 0000000000..2b5dd005db
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGCallbackPass.java
@@ -0,0 +1,61 @@
+/*
+ * 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.renderer.framegraph;
+
+/**
+ *
+ * @author JohnKkk
+ */
+public class FGCallbackPass extends FGBindingPass{
+ public static interface IFGCallbackInterface{
+ void callbackPass();
+ }
+ private IFGCallbackInterface iFGCallbackInterface;
+
+ private FGCallbackPass(String name, IFGCallbackInterface ifgci) {
+ super(name);
+ iFGCallbackInterface = ifgci;
+ }
+
+ @Override
+ public void execute(FGRenderContext renderContext) {
+ bindAll(renderContext);
+ if(iFGCallbackInterface != null){
+ iFGCallbackInterface.callbackPass();
+ }
+ }
+
+ public final static FGCallbackPass makePass(String passName, IFGCallbackInterface iFGCallbackInterface){
+ return new FGCallbackPass(passName, iFGCallbackInterface);
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGComputePass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGComputePass.java
new file mode 100644
index 0000000000..015017ff67
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGComputePass.java
@@ -0,0 +1,44 @@
+/*
+ * 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.renderer.framegraph;
+
+/**
+ *
+ * @author JohnKkk
+ */
+public class FGComputePass extends FGBindingPass{
+
+ public FGComputePass(String name) {
+ super(name);
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGContainerBindableSink.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGContainerBindableSink.java
new file mode 100644
index 0000000000..25c7eb70f8
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGContainerBindableSink.java
@@ -0,0 +1,102 @@
+/*
+ * 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.renderer.framegraph;
+
+import java.util.ArrayList;
+
+/**
+ *FGContainerBindableSink is used to proxy a FGSink, and also has the role of FGBindable. Typically, a Sink needed by a Pass may be a Bindable object.
+ * @author JohnKkk
+ */
+public class FGContainerBindableSink extends FGSink{
+ protected boolean linked = false;
+ protected ArrayList container;
+ protected int index;
+ protected FGBindableProxy bindableProxy;
+
+ public final static class FGBindableProxy extends FGBindable{
+ public FGBindable targetBindable;
+
+ public FGBindableProxy(FGBindable targetBindable) {
+ this.targetBindable = targetBindable;
+ }
+
+ @Override
+ public void bind(FGRenderContext renderContext) {
+ if(targetBindable != null){
+ targetBindable.bind(renderContext);
+ }
+ }
+ }
+
+ public FGContainerBindableSink(String registeredName, ArrayList container, int index) {
+ super(registeredName);
+ this.container = container;
+ this.index = index;
+ bindableProxy = new FGBindableProxy(null);
+ if(index < this.container.size()){
+ this.container.set(index, bindableProxy);
+ }
+ else{
+ this.container.add(bindableProxy);
+ this.index = this.container.size() - 1;
+ }
+ }
+
+ @Override
+ public void bind(FGSource fgSource) {
+ T p = (T)fgSource.yieldBindable();
+ if(p == null){
+ System.err.println("Binding input [" + getRegisteredName() + "] to output [" + getLinkPassName() + "." + getLinkPassResName() + "] " + " { " + fgSource.getName() + " } ");
+ return;
+ }
+ bindableProxy.targetBindable = p;
+// container.set(index, p);
+ linked = true;
+ }
+
+ @Override
+ public void postLinkValidate() {
+ if(!linked){
+ if(bIsRequired)
+ System.err.println("Unlinked input: " + getRegisteredName());
+ }
+ else{
+ bLinkValidate = true;
+ }
+ }
+
+ @Override
+ public FGBindable getBind() {
+ return bindableProxy.targetBindable;
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGFramebufferCopyBindableSink.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGFramebufferCopyBindableSink.java
new file mode 100644
index 0000000000..ef6d86945d
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGFramebufferCopyBindableSink.java
@@ -0,0 +1,100 @@
+/*
+ * 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.renderer.framegraph;
+
+import com.jme3.texture.FrameBuffer;
+
+import java.util.ArrayList;
+
+/**
+ * @author JohnKkk
+ * @param
+ */
+public class FGFramebufferCopyBindableSink extends FGContainerBindableSink{
+ FramebufferCopyBindableProxy framebufferCopyBindableProxy;
+ public final void setDistFrameBuffer(FrameBuffer distFrameBuffer){
+ framebufferCopyBindableProxy.distFramebuffer = distFrameBuffer;
+ }
+ public FGFramebufferCopyBindableSink(String registeredName, FrameBuffer distFrameBuffer, boolean copyColor, boolean copyDepth, boolean copyStencil, ArrayList container, int index) {
+ super(registeredName, container, index);
+ framebufferCopyBindableProxy = new FramebufferCopyBindableProxy(distFrameBuffer, copyColor, copyDepth, copyStencil);
+ }
+
+ private final static class FramebufferCopyBindableProxy extends FGBindable{
+ FrameBuffer sourceFramebuffer;
+ FrameBuffer distFramebuffer;
+ boolean bCopyColor;
+ boolean bCopyDepth;
+ boolean bCopyStencil;
+
+ public FramebufferCopyBindableProxy(FrameBuffer distFramebuffer, boolean bCopyColor, boolean bCopyDepth, boolean bCopyStencil) {
+ this.distFramebuffer = distFramebuffer;
+ this.bCopyColor = bCopyColor;
+ this.bCopyDepth = bCopyDepth;
+ this.bCopyStencil = bCopyStencil;
+ }
+
+ public void setSourceFramebuffer(FrameBuffer sourceFramebuffer) {
+ this.sourceFramebuffer = sourceFramebuffer;
+ }
+
+ @Override
+ public void bind(FGRenderContext renderContext) {
+ if(this.distFramebuffer != null || this.sourceFramebuffer != null){
+ renderContext.renderManager.getRenderer().copyFrameBuffer(this.sourceFramebuffer, this.distFramebuffer != null ? this.distFramebuffer : renderContext.viewPort.getOutputFrameBuffer(), bCopyColor, bCopyDepth || bCopyStencil);
+ }
+ }
+ }
+
+ @Override
+ public void bind(FGSource fgSource) {
+ T p = (T)fgSource.yieldBindable();
+ if(p == null){
+ System.err.println("Binding input [" + getRegisteredName() + "] to output [" + getLinkPassName() + "." + getLinkPassResName() + "] " + " { " + fgSource.getName() + " } ");
+ return;
+ }
+ if(fgSource instanceof FGFramebufferSource){
+ linked = true;
+ FGFramebufferSource framebufferSource = (FGFramebufferSource)fgSource;
+ framebufferCopyBindableProxy.setSourceFramebuffer(((FGFramebufferSource.FrameBufferSourceProxy)framebufferSource.yieldBindable()).getFrameBuffer());
+ bindableProxy.targetBindable = framebufferCopyBindableProxy;
+ }
+ else{
+ System.err.println(getRegisteredName() + " needs a FGFramebufferSource");
+ }
+ }
+
+ @Override
+ public void postLinkValidate() {
+
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGFramebufferSource.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGFramebufferSource.java
new file mode 100644
index 0000000000..876b42ba4d
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGFramebufferSource.java
@@ -0,0 +1,66 @@
+/*
+ * 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.renderer.framegraph;
+
+import com.jme3.texture.FrameBuffer;
+
+/**
+ * @author JohnKkk
+ */
+public class FGFramebufferSource extends FGSource{
+ private FrameBufferSourceProxy frameBufferSourceProxy;
+ public final static class FrameBufferSourceProxy extends FGBindable{
+ private FrameBuffer frameBuffer;
+
+ public FrameBufferSourceProxy(FrameBuffer frameBuffer) {
+ this.frameBuffer = frameBuffer;
+ }
+
+ public FrameBuffer getFrameBuffer() {
+ return frameBuffer;
+ }
+ }
+ public FGFramebufferSource(String name, FrameBuffer frameBuffer) {
+ super(name);
+ frameBufferSourceProxy = new FrameBufferSourceProxy(frameBuffer);
+ }
+
+ @Override
+ public void postLinkValidate() {
+
+ }
+
+ @Override
+ public FGBindable yieldBindable() {
+ return frameBufferSourceProxy;
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGGlobal.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGGlobal.java
new file mode 100644
index 0000000000..40332f6af3
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGGlobal.java
@@ -0,0 +1,59 @@
+/*
+ * 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.renderer.framegraph;
+
+import java.util.ArrayList;
+
+/**
+ *
+ * @author JohnKkk
+ */
+public class FGGlobal {
+ public final static String S_GLOABLE_PASS_SOURCE_NAME = "$";
+ public final static String S_SCENE_COLOR_FB = "sceneColorFramebuffer";
+ public final static String S_SCENE_COLOR_RT = "sceneColorRT";
+ public final static String S_SCENE_DEPTH_RT = "sceneDepthRT";
+ public final static String S_DEFAULT_FB = "defaultFramebuffer";
+ private final static ArrayList g_Sinks = new ArrayList<>();
+ private final static ArrayList g_Sources = new ArrayList<>();
+ public final static boolean linkSink(FGSink outSink){
+ boolean bound = false;
+ for(FGSource soucre : g_Sources){
+ if(soucre.getName().equals(outSink.getLinkPassResName())){
+ outSink.bind(soucre);
+ bound = true;
+ break;
+ }
+ }
+ return bound;
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGPass.java
new file mode 100644
index 0000000000..f4075bf630
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGPass.java
@@ -0,0 +1,140 @@
+/*
+ * 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.renderer.framegraph;
+
+import java.util.ArrayList;
+
+/**
+ * FGPass.
+ * @author JohnKkk
+ */
+public abstract class FGPass {
+ private String name;
+ private ArrayList sinks;
+ private ArrayList sources;
+ protected boolean resetClearSinksAndSources = false;
+ public FGPass(String name){
+ this.name = name;
+ this.sinks = new ArrayList<>();
+ this.sources = new ArrayList<>();
+ }
+ public void prepare(FGRenderContext renderContext){}
+ public abstract void execute(FGRenderContext renderContext);
+
+ public String getName() {
+ return name;
+ }
+
+ public ArrayList getSinks() {
+ return sinks;
+ }
+
+ public ArrayList getSources() {
+ return sources;
+ }
+
+ public FGSource getSource(String name){
+ for(FGSource src : sources){
+ if(src.getName().equals(name)){
+ return src;
+ }
+ }
+
+ System.err.println("Output name [" + name + "] not fount in pass:" + getName());
+ return null;
+ }
+
+ public FGSink getSink(String registeredName){
+ for(FGSink sink : sinks){
+ if(sink.getRegisteredName().equals(registeredName)){
+ return sink;
+ }
+ }
+ return null;
+ }
+
+ public void setSinkLinkage(String registeredName, String target){
+ FGSink sink = getSink(registeredName);
+
+ String targetSplit[] = target.split("\\.");
+ if(targetSplit.length != 2){
+ System.err.println("Input target has incorrect format");
+ }
+ sink.setTarget(targetSplit[0], targetSplit[1]);
+ }
+
+ protected void registerSink(FGSink sink){
+ // check for overlap of input names
+ for(FGSink si : sinks){
+ if(si.getRegisteredName().equals(sink.getRegisteredName())){
+ System.err.println("Registered input overlaps with existing: " + sink.getRegisteredName());
+ return;
+ }
+ }
+
+ sinks.add(sink);
+ }
+
+ public void registerSource(FGSource source){
+ // check for overlap of output names
+ for(FGSource src : sources){
+ if(src.getName().equals(source.getName())){
+ System.err.println("Registered input overlaps with existing: " + source.getName());
+ return;
+ }
+ }
+
+ sources.add(source);
+ }
+
+ public void reset(){
+ if(resetClearSinksAndSources){
+ this.sources.clear();
+ this.sinks.clear();
+ }
+ }
+
+ public void finalize(){
+ if(sinks != null && sinks.size() > 0){
+ for(FGSink sink : sinks){
+ sink.postLinkValidate();
+ }
+ }
+
+ if(sources != null && sources.size() > 0){
+ for(FGSource src : sources){
+ src.postLinkValidate();
+ }
+ }
+ }
+
+}
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..57821553db
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java
@@ -0,0 +1,69 @@
+/*
+ * 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.renderer.framegraph;
+
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+
+/**
+ * In order to be compatible with existing logic, FGRenderContext is currently just a local proxy, and may gradually replace the existing state machine manager in the future.
+ * @author JohnKkk
+ */
+public class FGRenderContext {
+ // PSO
+ public static class FGPipelineObjectState{
+ public float startDepth;
+ public float endDepth;
+ }
+
+ public RenderManager renderManager;
+ public RenderQueue renderQueue;
+ public ViewPort viewPort;
+ protected FGPipelineObjectState currentPSO;
+
+ public FGRenderContext(RenderManager renderManager, RenderQueue renderQueue, ViewPort viewPort) {
+ this.renderManager = renderManager;
+ this.renderQueue = renderQueue;
+ this.viewPort = viewPort;
+ currentPSO = new FGPipelineObjectState();
+ currentPSO.startDepth = 0;
+ currentPSO.endDepth = 1;
+ }
+ public final void setDepthRange(float start, float end){
+ if(currentPSO.startDepth != start || currentPSO.endDepth != end){
+ renderManager.getRenderer().setDepthRange(start, end);
+ currentPSO.startDepth = start;
+ currentPSO.endDepth = end;
+ }
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderQueuePass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderQueuePass.java
new file mode 100644
index 0000000000..dbd5ad6916
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderQueuePass.java
@@ -0,0 +1,101 @@
+/*
+ * 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.renderer.framegraph;
+
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.pass.RenderGeometry;
+
+/**
+ * All passes that need to perform rendering must inherit from this class.
+ * @author JohnKkk
+ */
+public abstract class FGRenderQueuePass extends FGBindingPass implements RenderGeometry {
+ protected ViewPort forceViewPort;
+ // It is just geometry data for now. If we extend the RHI interface in the future, it may be adjusted to MeshDrawCommand.
+ protected GeometryList passMeshDrawCommandList;
+ protected boolean canExecute;
+
+ public FGRenderQueuePass(String name) {
+ super(name);
+ }
+
+ /**
+ * A RenderPass may use a specified viewport. This can be set using this function.
+ * @param forceViewPort targetViewPort
+ */
+ public void setForceViewPort(ViewPort forceViewPort) {
+ this.forceViewPort = forceViewPort;
+ }
+
+ /**
+ * Dispatch visible mesh draw commands to process task, to prepare for this pass.
+ * todo:For the current GLRenderer, the MeshDrawCommand concept actually does not exist. So this is prepared for future Vulkan-like renderers
+ * @param renderQueue targetRenderQueue
+ */
+ public abstract void dispatchPassSetup(RenderQueue renderQueue);
+
+ @Override
+ public void execute(FGRenderContext renderContext) {
+ renderContext.renderManager.setRenderGeometryHandler(this);
+ dispatchPassSetup(renderContext.renderQueue);
+ if(!canExecute){
+ renderContext.renderManager.setRenderGeometryHandler(null);
+ return;
+ }
+ bindAll(renderContext);
+
+ // todo:Use the default queue temporarily to avoid creating a temporary copy
+ if(passMeshDrawCommandList != null && passMeshDrawCommandList.size() > 0){
+ // drawcall
+ }
+ executeDrawCommandList(renderContext);
+ renderContext.renderManager.setRenderGeometryHandler(null);
+ }
+
+ /**
+ * todo:For the current GLRenderer, the MeshDrawCommand concept actually does not exist. So this is prepared for future Vulkan-like renderers
+ * @param renderContext
+ */
+ public abstract void executeDrawCommandList(FGRenderContext renderContext);
+
+ @Override
+ public void reset() {
+ super.reset();
+ if(passMeshDrawCommandList != null && passMeshDrawCommandList.size() > 0){
+ passMeshDrawCommandList.clear();
+ }
+ }
+
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderTargetSource.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderTargetSource.java
new file mode 100644
index 0000000000..645651061a
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderTargetSource.java
@@ -0,0 +1,76 @@
+/*
+ * 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.renderer.framegraph;
+
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Texture;
+
+public class FGRenderTargetSource extends FGSource{
+ RenderTargetSourceProxy renderTargetSourceProxy;
+ public final static class RenderTargetSourceProxy extends FGBindable{
+ FrameBuffer.FrameBufferTextureTarget renderTarget;
+
+ public RenderTargetSourceProxy(FrameBuffer.FrameBufferTextureTarget renderTarget) {
+ this.renderTarget = renderTarget;
+ }
+
+ /**
+ * return RT.
+ * @return
+ */
+ public FrameBuffer.FrameBufferTextureTarget getRenderTarget() {
+ return renderTarget;
+ }
+
+ /**
+ * return RT shaderResource.
+ * @return
+ */
+ public Texture getShaderResource(){
+ return renderTarget.getTexture();
+ }
+ }
+ public FGRenderTargetSource(String name, FrameBuffer.FrameBufferTextureTarget renderTarget) {
+ super(name);
+ renderTargetSourceProxy = new RenderTargetSourceProxy(renderTarget);
+ }
+
+ @Override
+ public void postLinkValidate() {
+
+ }
+
+ @Override
+ public FGBindable yieldBindable() {
+ return renderTargetSourceProxy;
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGSink.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGSink.java
new file mode 100644
index 0000000000..c073ce04b8
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGSink.java
@@ -0,0 +1,76 @@
+/*
+ * 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.renderer.framegraph;
+
+/**
+ *
+ * @author JohnKkk
+ */
+public abstract class FGSink {
+ private String registeredName;
+ private String linkPassName;
+ private String linkPassResName;
+ protected boolean bIsRequired = false;
+ // Always validate?
+ protected boolean bLinkValidate = false;
+ protected FGSink(String registeredName){
+ this.registeredName = registeredName;
+ }
+
+ public boolean isRequired() {
+ return bIsRequired;
+ }
+
+ public String getRegisteredName() {
+ return registeredName;
+ }
+
+ public String getLinkPassName() {
+ return linkPassName;
+ }
+
+ public String getLinkPassResName() {
+ return linkPassResName;
+ }
+
+ public void setTarget(String inPassName, String inPassResName){
+ linkPassName = inPassName;
+ linkPassResName = inPassResName;
+ }
+ public abstract void bind(FGSource fgSource);
+ public abstract void postLinkValidate();
+
+ public boolean isLinkValidate() {
+ return bLinkValidate;
+ }
+ public FGBindable getBind(){return null;}
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGSource.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGSource.java
new file mode 100644
index 0000000000..cdf9ad3c19
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGSource.java
@@ -0,0 +1,49 @@
+/*
+ * 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.renderer.framegraph;
+
+/**
+ *
+ * @author JohnKkk
+ */
+public abstract class FGSource {
+ private String name;
+ public FGSource(String name){
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ public abstract void postLinkValidate();
+ public abstract FGBindable yieldBindable();
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGTextureBindableSink.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGTextureBindableSink.java
new file mode 100644
index 0000000000..798aab91d8
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGTextureBindableSink.java
@@ -0,0 +1,89 @@
+/*
+ * 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.renderer.framegraph;
+
+import com.jme3.material.Material;
+import com.jme3.shader.VarType;
+
+import java.util.ArrayList;
+
+public class FGTextureBindableSink extends FGContainerBindableSink{
+ private Material material;
+ private TextureBindableProxy textureBindableProxy;
+ private static class TextureBindableProxy extends FGBindable{
+ Material material;
+ VarType bindTextureType;
+ Object bindValue;
+ String bindName;
+
+ public TextureBindableProxy(Material material, String bindName, VarType bindTextureType) {
+ // check bindTextureType
+ this.bindTextureType = bindTextureType;
+ this.material = material;
+ this.bindName = bindName;
+ }
+ public final void setValue(Object value){
+ bindValue = value;
+ }
+
+ @Override
+ public void bind(FGRenderContext renderContext) {
+ super.bind(renderContext);
+ if(material != null && bindName != null){
+ this.material.setParam(bindName, bindTextureType, bindValue);
+ }
+ }
+ }
+ public FGTextureBindableSink(String registeredName, ArrayList container, int index, Material material, VarType bindTextureType) {
+ super(registeredName, container, index);
+ this.material = material;
+ textureBindableProxy = new TextureBindableProxy(material, registeredName, bindTextureType);
+ }
+
+ @Override
+ public void bind(FGSource fgSource) {
+ T p = (T)fgSource.yieldBindable();
+ if(p == null){
+ System.err.println("Binding input [" + getRegisteredName() + "] to output [" + getLinkPassName() + "." + getLinkPassResName() + "] " + " { " + fgSource.getName() + " } ");
+ return;
+ }
+ if(fgSource instanceof FGRenderTargetSource){
+ linked = true;
+ FGRenderTargetSource renderTargetSource = (FGRenderTargetSource)fgSource;
+ textureBindableProxy.setValue(((FGRenderTargetSource.RenderTargetSourceProxy)renderTargetSource.yieldBindable()).getShaderResource());
+ bindableProxy.targetBindable = textureBindableProxy;
+ }
+ else{
+ System.err.println(getRegisteredName() + " needs a FGRenderTargetSource");
+ }
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGVarBindableSink.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGVarBindableSink.java
new file mode 100644
index 0000000000..e657912272
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGVarBindableSink.java
@@ -0,0 +1,49 @@
+/*
+ * 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.renderer.framegraph;
+
+import java.util.ArrayList;
+
+/**
+ * @author JohnKkk
+ * @param
+ */
+public class FGVarBindableSink extends FGContainerBindableSink{
+ public FGVarBindableSink(String registeredName, ArrayList container, int index) {
+ super(registeredName, container, index);
+ }
+
+ @Override
+ public void bind(FGSource fgSource) {
+ super.bind(fgSource);
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGVarSource.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGVarSource.java
new file mode 100644
index 0000000000..5ee2d24575
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGVarSource.java
@@ -0,0 +1,69 @@
+/*
+ * 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.renderer.framegraph;
+
+/**
+ * @author JohnKkk
+ * @param
+ */
+public class FGVarSource extends FGSource{
+ public static class FGVarBindableProxy extends FGBindable{
+ T value;
+
+ public FGVarBindableProxy(T value) {
+ this.value = value;
+ }
+
+ public T getValue() {
+ return value;
+ }
+ }
+ private FGVarBindableProxy varBindableProxy;
+ public FGVarSource(String name, T value) {
+ super(name);
+ varBindableProxy = new FGVarBindableProxy(value);
+ }
+
+ public void setValue(T t){
+ varBindableProxy.value = t;
+ }
+
+ @Override
+ public void postLinkValidate() {
+
+ }
+
+ @Override
+ public FGBindable yieldBindable() {
+ return varBindableProxy;
+ }
+}
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..0f3db8e0df
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java
@@ -0,0 +1,276 @@
+/*
+ * 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.renderer.framegraph;
+
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The FrameGraph system is used to manage render passes and the dependencies between them in a declarative way. Some key aspects:
+ * - It represents the rendering pipeline as a directed acyclic graph of passes and their inputs/outputs.
+ * - Passes can be things like shadow map creation, geometry passes, post-processing etc.
+ * - Outputs from one pass can be reused as inputs to others, avoiding redundant work.
+ * - The FrameGraph handles synchronization, pass scheduling and resource transitions automatically based on dependencies.
+ * - Developers define passes and connect inputs/outputs, while the system handles execution.
+ * - Passes can have arbitrary Java logic, shaders, and framebuffer configurations.
+ * - Framebuffers are created on demand based on pass requirements.
+ * - FrameGraphRecursion queues allow recursive pass execution (e.g. for nested realtime reflections).
+ * - The system optimizes what needs to run each frame based on dirty state.
+ * In summary, FrameGraph enables declaring a rendering pipeline at a high level while handling all the underlying complexity of synchronization, resource management, and efficient execution. This simplifies the developer's job and avoids boilerplate code.
+ * The key advantages are automatic input/output reuse, efficient scheduling and batching, simplified boilerplate, and handling advanced cases like recursions. Overall it provides a clean abstraction for building complex, efficient rendering pipelines.
+ * https://www.gdcvault.com/play/1024612/FrameGraph-Extensible-Rendering-Architecture-in.
+ * @author JohnKkk
+ */
+public class FrameGraph {
+ private static final Logger logger = Logger.getLogger(FrameGraph.class.getName());
+ private ArrayList passes;
+ private ArrayList globalSources;
+ private ArrayList globalSinks;
+ private boolean finalized = false;
+ private FGRenderContext renderContext;
+
+ public FrameGraph(FGRenderContext renderContext){
+ passes = new ArrayList();
+ globalSinks = new ArrayList();
+ globalSources = new ArrayList();
+ this.renderContext = renderContext;
+ }
+
+ public FGRenderContext getRenderContext() {
+ return renderContext;
+ }
+
+ /**
+ * Binding input resources to the global sink in a FrameGraph refers to making certain resources available globally to all passes.
+ */
+ protected void linkGlobalSinks(){
+ for(FGSink sink : globalSinks){
+ String linkPassName = sink.getLinkPassName();
+ for(FGPass nextPass : passes){
+ if(nextPass.getName().equals(linkPassName)){
+ FGSource source = nextPass.getSource(sink.getLinkPassResName());
+ sink.bind(source);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Binding input resources to a specific pass in a FrameGraph refers to connecting the necessary resources that pass requires as inputs.
+ * @param pass targetPass
+ */
+ protected void linkSinks(FGPass pass){
+ for(FGSink sink : pass.getSinks()){
+ String linkPassName = sink.getLinkPassName();
+
+ if((linkPassName == null || linkPassName.isEmpty())){
+ if(sink.isRequired()){
+ logger.throwing(FrameGraph.class.toString(), "", new NullPointerException("In pass named [" + pass.getName() + "] sink named [" + sink.getRegisteredName() + "] has no target source set."));
+// logger.log(Level.WARNING, "In pass named [" + pass.getName() + "] sink named [" + sink.getRegisteredName() + "] has no target source set.");
+// System.err.println("In pass named [" + pass.getName() + "] sink named [" + sink.getRegisteredName() + "] has no target source set.");
+ return;
+ }
+ else{
+ continue;
+ }
+ }
+
+ // check check whether target source is global
+ if(linkPassName.equals(FGGlobal.S_GLOABLE_PASS_SOURCE_NAME)){
+ boolean bound = false;
+ for(FGSource source : globalSources){
+ if(source.getName().equals(sink.getLinkPassResName())){
+ sink.bind(source);
+ bound = true;
+ break;
+ }
+ }
+ if(!bound){
+ bound = FGGlobal.linkSink(sink);
+ if(!bound && sink.isRequired()){
+ logger.throwing(FrameGraph.class.toString(), "", new NullPointerException("Pass named [" + linkPassName + "] not found"));
+// System.err.println("Pass named [" + linkPassName + "] not found");
+ return;
+ }
+ }
+ }
+ else{
+ // find source from within existing passes
+ boolean bound = false;
+ for(FGPass nextPass : passes){
+ if(nextPass.getName().equals(linkPassName)){
+ FGSource source = nextPass.getSource(sink.getLinkPassResName());
+ sink.bind(source);
+ bound = true;
+ break;
+ }
+ }
+ if(!bound){
+ bound = FGGlobal.linkSink(sink);
+ if(!bound && sink.isRequired()){
+ logger.throwing(FrameGraph.class.toString(), "", new NullPointerException("Pass named [" + linkPassName + "] not found"));
+// System.err.println("Pass named [" + linkPassName + "] not found");
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Execute frameGraph
+ *
+ * example:
+ * FrameGraph fg = new FrameGraph();
+ * fg.addPass(pass1);
+ * fg.addPass(pass2);
+ * fg.finalize();
+ * fg.execute();
+ *
+ */
+ public void execute(){
+ assert finalized;
+ for(FGPass nextPass : passes){
+ nextPass.execute(renderContext);
+ }
+ finalized = false;
+ }
+
+ /**
+ * The FrameGraph can be reused by just calling reset() to clear the current graph, then re-adding the required passes and binding the necessary resources again, before calling execute() once more.
+ * This allows reusing the same FrameGraph instance to construct different render pipelines, avoiding repeated resource creation. Just update the passes and connections as needed. This improves code reuse and simplifies render pipeline adjustments.
+ */
+ public void reset(){
+ assert !finalized;
+ for(FGPass nextPass : passes){
+ nextPass.reset();
+ }
+ passes.clear();
+ if(renderContext != null && renderContext.renderManager != null){
+ renderContext.renderManager.setRenderGeometryHandler(null);
+ }
+ }
+
+ /**
+ * Some resources may not belong to any pass, but need to be shared across multiple framegraphs.
+ * @param source targetFGSource
+ */
+ public void addGlobalSource(FGSource source){
+ globalSources.add(source);
+ }
+
+ /**
+ * Some resources may not belong to any pass, but need to be shared across multiple framegraphs.
+ * @param source targetFGSource
+ */
+ public void replaceOrAddGlobalSource(FGSource source){
+ int index = -1;
+ for(int i = 0;i < globalSources.size();i++){
+ if(globalSources.get(i).getName().equals(source.getName())){
+ index = i;
+ break;
+ }
+ }
+ if(index >= 0){
+ globalSources.remove(index);
+ }
+ globalSources.add(source);
+ }
+
+ /**
+ * A FrameGraph may contain global sinks, such as a backBuffer.
+ * @param sink targetFGSink
+ */
+ public void addGlobalSink(FGSink sink){
+ globalSinks.add(sink);
+ }
+
+ /**
+ * Adding an executable Pass to a FrameGraph, note that Passes will execute in the order they are added:
+ * - To add a Pass to a FrameGraph, call frameGraph.addPass() and provide a name and a Pass executor function.
+ * - The executor function contains the actual rendering commands for that Pass.
+ * - Passes added earlier will execute before ones added later.
+ * - Add passes in the order of desired execution.
+ * - After adding passes, call frameGraph.validate() to validate the graph before execution.
+ * - Then call frameGraph.compile() to prepare the graph for execution.
+ * - In the render loop, call frameGraph.execute() to run the Pass network.
+ * - Passes with unsatisfied resource dependencies will be skipped until their inputs are ready.
+ * - FrameGraph handles scheduling passes in the correct order automatically based on dependencies.
+ * - But the order passes are added determines the base execution order.
+ * So in summary, add Passes in the desired execution order to the FrameGraph. The FrameGraph system will then handle scheduling them based on resource availability while respecting the original adding order.
+ * @param pass targetPass
+ */
+ public final void addPass(FGPass pass){
+ assert !finalized;
+ // validate name uniqueness
+ for(FGPass nextPass : passes){
+ if(nextPass.getName().equals(pass.getName())){
+ logger.throwing(FrameGraph.class.toString(), "", new NullPointerException("Pass name already exists: " + pass.getName()));
+// System.err.println("Pass name already exists: " + pass.getName());
+ return;
+ }
+ }
+
+ // link outputs from passes (and global outputs) to pass inputs
+ if(pass.getSinks() != null && pass.getSinks().size() > 0)
+ linkSinks(pass);
+
+ // add to container of passes
+ pass.prepare(renderContext);
+ passes.add(pass);
+ }
+
+ public final FGPass findPassByName(String name){
+ for(FGPass nextPass : passes){
+ if(nextPass.getName().equals(name)){
+ return nextPass;
+ }
+ }
+ logger.throwing(FrameGraph.class.toString(), "", new NullPointerException("Failed to find pass name"));
+// System.err.println("Failed to find pass name");
+ return null;
+ }
+
+ /**
+ * Prepare all passes to get ready for execution of the frameGraph, by calling this function before executing the frameGraph.
+ */
+ public final void finalize(){
+ assert !finalized;
+ for(FGPass nextPass : passes){
+ nextPass.finalize();
+ }
+ linkGlobalSinks();
+ finalized = true;
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/DeferredLightDataSink.java b/jme3-core/src/main/java/com/jme3/renderer/pass/DeferredLightDataSink.java
new file mode 100644
index 0000000000..7543371b45
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/DeferredLightDataSink.java
@@ -0,0 +1,48 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.renderer.framegraph.FGBindable;
+import com.jme3.renderer.framegraph.FGContainerBindableSink;
+
+import java.util.ArrayList;
+
+public class DeferredLightDataSink extends FGContainerBindableSink {
+ public DeferredLightDataSink(String registeredName, ArrayList container, int index) {
+ super(registeredName, container, index);
+ }
+
+ @Override
+ public void postLinkValidate() {
+ bLinkValidate = bindableProxy.targetBindable != null;
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/DeferredLightDataSource.java b/jme3-core/src/main/java/com/jme3/renderer/pass/DeferredLightDataSource.java
new file mode 100644
index 0000000000..a2e2866102
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/DeferredLightDataSource.java
@@ -0,0 +1,67 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.light.LightList;
+import com.jme3.renderer.framegraph.FGBindable;
+import com.jme3.renderer.framegraph.FGSource;
+
+public class DeferredLightDataSource extends FGSource {
+ DeferredLightDataProxy deferredLightDataProxy;
+ public DeferredLightDataSource(String name, LightList lightData) {
+ super(name);
+ deferredLightDataProxy = new DeferredLightDataProxy(lightData);
+ }
+
+ @Override
+ public void postLinkValidate() {
+
+ }
+
+ @Override
+ public FGBindable yieldBindable() {
+ return deferredLightDataProxy;
+ }
+
+ public static class DeferredLightDataProxy extends FGBindable {
+ private LightList lightData;
+
+ public DeferredLightDataProxy(LightList lightData) {
+ this.lightData = lightData;
+ }
+
+ public LightList getLightData() {
+ return lightData;
+ }
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/DeferredShadingPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/DeferredShadingPass.java
new file mode 100644
index 0000000000..4712feb294
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/DeferredShadingPass.java
@@ -0,0 +1,129 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.light.LightList;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialDef;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.framegraph.*;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.VarType;
+import com.jme3.ui.Picture;
+
+/**
+ * @author JohnKkk
+ */
+public class DeferredShadingPass extends ScreenPass{
+ public final static String S_RT_0 = "Context_InGBuff0";
+ public final static String S_RT_1 = "Context_InGBuff1";
+ public final static String S_RT_2 = "Context_InGBuff2";
+ public final static String S_RT_3 = "Context_InGBuff3";
+ public final static String S_RT_4 = "Context_InGBuff4";
+ public final static String S_LIGHT_DATA = "LIGHT_DATA";
+ public final static String S_EXECUTE_STATE = "EXECUTE_STATE";
+ protected final static String _S_DEFERRED_PASS = "DeferredPass";
+ private static final String _S_DEFERRED_SHADING_PASS_MAT_DEF = "Common/MatDefs/ShadingCommon/DeferredShading.j3md";
+ public DeferredShadingPass(){
+ this("DeferredShadingPass");
+ }
+ public DeferredShadingPass(String name) {
+ super(name, RenderQueue.Bucket.Opaque);
+ }
+
+ @Override
+ public void prepare(FGRenderContext renderContext) {
+ super.prepare(renderContext);
+ ViewPort vp = renderContext.viewPort;
+ if(forceViewPort != null){
+ vp = forceViewPort;
+ }
+ ((FGFramebufferCopyBindableSink)getSink(FGGlobal.S_DEFAULT_FB)).setDistFrameBuffer(vp.getOutputFrameBuffer());
+ }
+
+ protected Material getMaterial(){
+ MaterialDef def = (MaterialDef) assetManager.loadAsset(_S_DEFERRED_SHADING_PASS_MAT_DEF);
+ screenMat = new Material(def);
+ return screenMat;
+ }
+
+ @Override
+ public void init() {
+
+ screenRect = new Picture(getName() + "_rect");
+ screenRect.setWidth(1);
+ screenRect.setHeight(1);
+ screenRect.setMaterial(getMaterial());
+
+ // register Sinks
+ registerSink(new FGTextureBindableSink(S_RT_0, binds, binds.size(), screenMat, VarType.Texture2D));
+ registerSink(new FGTextureBindableSink(S_RT_1, binds, binds.size(), screenMat, VarType.Texture2D));
+ registerSink(new FGTextureBindableSink(S_RT_2, binds, binds.size(), screenMat, VarType.Texture2D));
+ registerSink(new FGTextureBindableSink(S_RT_3, binds, binds.size(), screenMat, VarType.Texture2D));
+ registerSink(new FGTextureBindableSink(S_RT_4, binds, binds.size(), screenMat, VarType.Texture2D));
+ registerSink(new DeferredLightDataSink(S_LIGHT_DATA, binds, binds.size()));
+ registerSink(new FGVarBindableSink(S_EXECUTE_STATE, binds, binds.size()));
+ registerSink(new FGFramebufferCopyBindableSink(FGGlobal.S_DEFAULT_FB, null, false, true, true, binds, binds.size()));
+ }
+
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ screenMat.selectTechnique(_S_DEFERRED_PASS, renderContext.renderManager);
+ DeferredLightDataSink deferredLightDataSink = (DeferredLightDataSink) getSink(S_LIGHT_DATA);
+ DeferredLightDataSource.DeferredLightDataProxy deferredLightDataProxy = (DeferredLightDataSource.DeferredLightDataProxy) deferredLightDataSink.getBind();
+ LightList lights = deferredLightDataProxy.getLightData();
+ boolean depthWrite = screenMat.getAdditionalRenderState().isDepthWrite();
+ boolean depthTest = screenMat.getAdditionalRenderState().isDepthTest();
+ screenMat.getAdditionalRenderState().setDepthWrite(false);
+ screenMat.getAdditionalRenderState().setDepthTest(false);
+ screenMat.setBoolean("UseLightsCullMode", false);
+ screenRect.updateGeometricState();
+ screenMat.render(screenRect, lights, renderContext.renderManager);
+ screenMat.getAdditionalRenderState().setDepthWrite(depthWrite);
+ screenMat.getAdditionalRenderState().setDepthTest(depthTest);
+ }
+
+ @Override
+ public void dispatchPassSetup(RenderQueue renderQueue) {
+ boolean executeState = getSink(S_EXECUTE_STATE).isLinkValidate() && ((FGVarSource.FGVarBindableProxy)getSink(S_EXECUTE_STATE).getBind()).getValue() == Boolean.TRUE;
+ boolean hasLightData = getSink(S_LIGHT_DATA).isLinkValidate() && ((DeferredLightDataSource.DeferredLightDataProxy)((DeferredLightDataSink) getSink(S_LIGHT_DATA)).getBind()).getLightData().size() > 0;
+ canExecute = hasLightData || executeState;
+ }
+
+ @Override
+ public boolean drawGeometry(RenderManager rm, Geometry geom) {
+ // Does not process any drawing in queues and always returns true, because we perform a RectDraw internally
+ return true;
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/ForwardPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/ForwardPass.java
new file mode 100644
index 0000000000..658862ff3d
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/ForwardPass.java
@@ -0,0 +1,75 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.framegraph.FGRenderContext;
+import com.jme3.renderer.framegraph.FGRenderQueuePass;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+
+/**
+ * @author JohnKkk
+ */
+public class ForwardPass extends FGRenderQueuePass {
+ private RenderQueue.Bucket bucket;
+ public ForwardPass(String name, RenderQueue.Bucket bucket) {
+ super(name);
+ this.bucket = bucket;
+ }
+
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ if(!canExecute)return;
+ Camera cam = null;
+ if(forceViewPort != null){
+ cam = forceViewPort.getCamera();
+ }
+ else{
+ cam = renderContext.viewPort.getCamera();
+ }
+ RenderManager rm = renderContext.renderManager;
+ renderContext.renderQueue.renderQueue(this.bucket, rm, cam, true);
+ }
+
+ @Override
+ public void dispatchPassSetup(RenderQueue renderQueue) {
+ canExecute = !renderQueue.isQueueEmpty(this.bucket);
+ }
+
+ @Override
+ public boolean drawGeometry(RenderManager rm, Geometry geom) {
+ rm.renderGeometry(geom);
+ return true;
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/GBufferPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/GBufferPass.java
new file mode 100644
index 0000000000..9344fec975
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/GBufferPass.java
@@ -0,0 +1,218 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.framegraph.FGFramebufferSource;
+import com.jme3.renderer.framegraph.FGRenderContext;
+import com.jme3.renderer.framegraph.FGRenderTargetSource;
+import com.jme3.renderer.framegraph.FGVarSource;
+import com.jme3.scene.Geometry;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture2D;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author JohnKkk
+ */
+public class GBufferPass extends OpaquePass{
+ private final static String S_GBUFFER_PASS = "GBufferPass";
+ public final static String S_RT_0 = "RT_0";
+ public final static String S_RT_1 = "RT_1";
+ public final static String S_RT_2 = "RT_2";
+ public final static String S_RT_3 = "RT_3";
+ public final static String S_RT_4 = "RT_4";
+ public final static String S_FB = "GBufferFramebuffer";
+ public final static String S_LIGHT_DATA = "LIGHT_DATA";
+ public final static String S_EXECUTE_STATE = "EXECUTE_STATE";
+ private final LightList lightData = new LightList(null);
+ private final List tempLights = new ArrayList();
+ private Boolean bHasDraw = new Boolean(false);
+ private FGVarSource bHasDrawVarSource = null;
+ // gBuffer
+ private FrameBuffer gBuffer;
+ private Texture2D gBufferData0 = null;
+ private Texture2D gBufferData1 = null;
+ private Texture2D gBufferData2 = null;
+ private Texture2D gBufferData3 = null;
+ private Texture2D gBufferData4 = null;
+ private ColorRGBA gBufferMask = new ColorRGBA(0, 0, 0, 0);
+ private int frameBufferWidth, frameBufferHeight;
+
+ public GBufferPass() {
+ super("GBufferPass");
+ }
+
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ if(canExecute){
+ bHasDraw = false;
+ tempLights.clear();
+ lightData.clear();
+ ViewPort vp = null;
+ if(forceViewPort != null){
+ vp = forceViewPort;
+ }
+ else{
+ vp = renderContext.viewPort;
+ }
+ reshape(renderContext.renderManager.getRenderer(), vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());
+ FrameBuffer opfb = vp.getOutputFrameBuffer();
+ vp.setOutputFrameBuffer(gBuffer);
+ ColorRGBA opClearColor = vp.getBackgroundColor();
+ gBufferMask.set(opClearColor);
+ gBufferMask.a = 0.0f;
+ renderContext.renderManager.getRenderer().setFrameBuffer(gBuffer);
+ renderContext.renderManager.getRenderer().setBackgroundColor(gBufferMask);
+ renderContext.renderManager.getRenderer().clearBuffers(vp.isClearColor(), vp.isClearDepth(), vp.isClearStencil());
+ String techOrig = renderContext.renderManager.getForcedTechnique();
+ renderContext.renderManager.setForcedTechnique(S_GBUFFER_PASS);
+ super.executeDrawCommandList(renderContext);
+ renderContext.renderManager.setForcedTechnique(techOrig);
+ vp.setOutputFrameBuffer(opfb);
+ renderContext.renderManager.getRenderer().setBackgroundColor(opClearColor);
+ renderContext.renderManager.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer());
+ bHasDrawVarSource.setValue(bHasDraw);
+ if(bHasDraw){
+ for(Light light : tempLights){
+ lightData.add(light);
+ }
+// renderContext.renderManager.getRenderer().copyFrameBuffer(gBuffer, vp.getOutputFrameBuffer(), false, true);
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ tempLights.clear();
+ lightData.clear();
+ bHasDraw = false;
+ bHasDrawVarSource.setValue(bHasDraw);
+ }
+
+ public void reshape(Renderer renderer, ViewPort vp, int w, int h){
+ boolean recreate = false;
+ if(gBuffer != null){
+ if(frameBufferWidth != w || frameBufferHeight != h){
+ gBuffer.dispose();
+ gBuffer.deleteObject(renderer);
+
+ frameBufferWidth = w;
+ frameBufferHeight = h;
+
+ recreate = true;
+ }
+ }
+ else{
+ recreate = true;
+ frameBufferWidth = w;
+ frameBufferHeight = h;
+ }
+
+ if(recreate){
+ // recreate
+ // To ensure accurate results, 32bit is used here for generalization.
+ gBufferData0 = new Texture2D(w, h, Image.Format.RGBA16F);
+ gBufferData1 = new Texture2D(w, h, Image.Format.RGBA16F);
+ gBufferData2 = new Texture2D(w, h, Image.Format.RGBA16F);
+ gBufferData3 = new Texture2D(w, h, Image.Format.RGBA32F); // The third buffer provides 32-bit floating point to store high-precision information, such as normals
+ this.getSinks().clear();
+ // Depth16/Depth32/Depth32F provide higher precision to prevent clipping when camera gets close, but it seems some devices do not support copying Depth16/Depth32/Depth32F to default FrameBuffer.
+ gBufferData4 = new Texture2D(w, h, Image.Format.Depth);
+ gBuffer = new FrameBuffer(w, h, 1);
+ FrameBuffer.FrameBufferTextureTarget rt0 = FrameBuffer.FrameBufferTarget.newTarget(gBufferData0);
+ FrameBuffer.FrameBufferTextureTarget rt1 = FrameBuffer.FrameBufferTarget.newTarget(gBufferData1);
+ FrameBuffer.FrameBufferTextureTarget rt2 = FrameBuffer.FrameBufferTarget.newTarget(gBufferData2);
+ FrameBuffer.FrameBufferTextureTarget rt3 = FrameBuffer.FrameBufferTarget.newTarget(gBufferData3);
+ FrameBuffer.FrameBufferTextureTarget rt4 = FrameBuffer.FrameBufferTarget.newTarget(gBufferData4);
+ gBuffer.addColorTarget(rt0);
+ gBuffer.addColorTarget(rt1);
+ gBuffer.addColorTarget(rt2);
+ gBuffer.addColorTarget(rt3);
+ gBuffer.setDepthTarget(rt4);
+ gBuffer.setMultiTarget(true);
+ registerSource(new FGRenderTargetSource(S_RT_0, rt0));
+ registerSource(new FGRenderTargetSource(S_RT_1, rt1));
+ registerSource(new FGRenderTargetSource(S_RT_2, rt2));
+ registerSource(new FGRenderTargetSource(S_RT_3, rt3));
+ registerSource(new FGRenderTargetSource(S_RT_4, rt4));
+ registerSource(new DeferredLightDataSource(S_LIGHT_DATA, lightData));
+ bHasDrawVarSource = new FGVarSource(S_EXECUTE_STATE, bHasDraw);
+ registerSource(bHasDrawVarSource);
+ registerSource(new FGFramebufferSource(S_FB, gBuffer));
+ }
+ }
+
+ @Override
+ public boolean drawGeometry(RenderManager rm, Geometry geom) {
+ Material material = geom.getMaterial();
+ if(material.getMaterialDef().getTechniqueDefs(rm.getForcedTechnique()) == null)return false;
+ rm.renderGeometry(geom);
+ if(material.getActiveTechnique() != null){
+ if(material.getMaterialDef().getTechniqueDefs(S_GBUFFER_PASS) != null){
+ LightList lights = geom.getFilterWorldLights();
+ for(Light light : lights){
+ if(!tempLights.contains(light)){
+ tempLights.add(light);
+ }
+ }
+ // Whether it has lights or not, material objects containing GBufferPass will perform DeferredShading, and shade according to shadingModelId
+ bHasDraw = true;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void prepare(FGRenderContext renderContext) {
+ super.prepare(renderContext);
+ ViewPort vp = null;
+ if(forceViewPort != null){
+ vp = forceViewPort;
+ }
+ else{
+ vp = renderContext.viewPort;
+ }
+ reshape(renderContext.renderManager.getRenderer(), vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/GuiPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/GuiPass.java
new file mode 100644
index 0000000000..540aaa7dc2
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/GuiPass.java
@@ -0,0 +1,64 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.framegraph.FGRenderContext;
+import com.jme3.renderer.queue.RenderQueue;
+
+/**
+ * @author JohnKkk
+ */
+public class GuiPass extends ForwardPass{
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ Camera cam = null;
+ if(forceViewPort != null){
+ cam = forceViewPort.getCamera();
+ }
+ else{
+ cam = renderContext.viewPort.getCamera();
+ }
+ if(canExecute){
+ renderContext.setDepthRange(0, 0);
+ renderContext.renderManager.setCamera(cam, true);
+ }
+ super.executeDrawCommandList(renderContext);
+ if(canExecute){
+ renderContext.renderManager.setCamera(cam, false);
+ }
+ }
+
+ public GuiPass() {
+ super("GUIPass", RenderQueue.Bucket.Gui);
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/OpaquePass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/OpaquePass.java
new file mode 100644
index 0000000000..f4c98956aa
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/OpaquePass.java
@@ -0,0 +1,56 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.renderer.framegraph.FGRenderContext;
+import com.jme3.renderer.queue.RenderQueue;
+
+/**
+ * @author JohnKkk
+ */
+public class OpaquePass extends ForwardPass {
+ public OpaquePass(String name) {
+ super(name, RenderQueue.Bucket.Opaque);
+ }
+
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ if(canExecute){
+ renderContext.setDepthRange(0, 1);
+ }
+ super.executeDrawCommandList(renderContext);
+ }
+
+ public OpaquePass() {
+ super("ForwardOpaquePass", RenderQueue.Bucket.Opaque);
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/PostProcessorPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/PostProcessorPass.java
new file mode 100644
index 0000000000..e218b14998
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/PostProcessorPass.java
@@ -0,0 +1,62 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.framegraph.FGPass;
+import com.jme3.renderer.framegraph.FGRenderContext;
+import com.jme3.util.SafeArrayList;
+
+/**
+ * @author JohnKkk
+ */
+public class PostProcessorPass extends FGPass {
+ public PostProcessorPass(String name) {
+ super(name);
+ }
+
+ @Override
+ public void execute(FGRenderContext renderContext) {
+ renderContext.setDepthRange(0, 1);
+ ViewPort vp = renderContext.viewPort;
+ SafeArrayList processors = vp.getProcessors();
+ 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);
+ }
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/RenderGeometry.java b/jme3-core/src/main/java/com/jme3/renderer/pass/RenderGeometry.java
new file mode 100644
index 0000000000..b858c6da8b
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/RenderGeometry.java
@@ -0,0 +1,48 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+
+/**
+ * @author JohnKkk
+ */
+public interface RenderGeometry {
+ /**
+ * Submit the given Geometry to the Pipeline for rendering.
+ * @param rm
+ * @param geom
+ * @return If true is returned, the geometry will be removed from the render Bucket after being rendered.
+ */
+ public boolean drawGeometry(RenderManager rm, Geometry geom);
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/ResolveSceneColorPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/ResolveSceneColorPass.java
new file mode 100644
index 0000000000..51b9b39488
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/ResolveSceneColorPass.java
@@ -0,0 +1,94 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.material.Material;
+import com.jme3.material.MaterialDef;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.framegraph.*;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.VarType;
+import com.jme3.ui.Picture;
+
+/**
+ * @author JohnKkk
+ */
+public class ResolveSceneColorPass extends ScreenPass {
+ public final static String S_SCENE_COLOR_RT = "SceneColorRT";
+ public final static String S_SCENE_DEPTH = "SceneDepth";
+ private static final String _S_RESOLVE_SCENE_COLOR_MAT_DEF = "Common/MatDefs/Misc/ResolveSceneColor.j3md";
+
+ public ResolveSceneColorPass(String name) {
+ super(name, RenderQueue.Bucket.Opaque);
+ }
+
+ @Override
+ public void dispatchPassSetup(RenderQueue renderQueue) {
+ canExecute = true;
+ }
+
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ renderContext.renderManager.getRenderer().setFrameBuffer(null);
+ boolean depthWrite = screenMat.getAdditionalRenderState().isDepthWrite();
+ boolean depthTest = screenMat.getAdditionalRenderState().isDepthTest();
+ screenMat.getAdditionalRenderState().setDepthWrite(false);
+ screenMat.getAdditionalRenderState().setDepthTest(false);
+ screenRect.updateGeometricState();
+ screenMat.render(screenRect, renderContext.renderManager);
+ screenMat.getAdditionalRenderState().setDepthWrite(depthWrite);
+ screenMat.getAdditionalRenderState().setDepthTest(depthTest);
+ }
+ public void updateExposure(float exposure){
+ screenMat.setFloat("Exposure", exposure);
+ }
+
+ @Override
+ public boolean drawGeometry(RenderManager rm, Geometry geom) {
+ return true;
+ }
+
+ @Override
+ public void init() {
+ MaterialDef def = (MaterialDef) assetManager.loadAsset(_S_RESOLVE_SCENE_COLOR_MAT_DEF);
+ screenMat = new Material(def);
+ screenRect = new Picture(getName() + "_rect");
+ screenRect.setWidth(1);
+ screenRect.setHeight(1);
+ screenRect.setMaterial(screenMat);
+
+ // register Sinks
+ registerSink(new FGTextureBindableSink(S_SCENE_COLOR_RT, binds, binds.size(), screenMat, VarType.Texture2D));
+ registerSink(new FGFramebufferCopyBindableSink(FGGlobal.S_DEFAULT_FB, null, false, true, true, binds, binds.size()));
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/ScreenPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/ScreenPass.java
new file mode 100644
index 0000000000..d6c4745d53
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/ScreenPass.java
@@ -0,0 +1,66 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.asset.plugins.FileLocator;
+import com.jme3.material.Material;
+import com.jme3.material.plugins.J3MLoader;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.shader.plugins.GLSLLoader;
+import com.jme3.system.JmeSystem;
+import com.jme3.ui.Picture;
+
+public abstract class ScreenPass extends ForwardPass{
+ protected static AssetManager assetManager;
+ protected Material screenMat;
+ protected Picture screenRect;
+ public ScreenPass(String name, RenderQueue.Bucket bucket) {
+ super(name, bucket);
+ initAssetManager();
+ init();
+ }
+
+ public abstract void init();
+
+ private static void initAssetManager(){
+ if(assetManager == null){
+ assetManager = JmeSystem.newAssetManager();
+ assetManager.registerLocator(".", FileLocator.class);
+ assetManager.registerLocator("/", ClasspathLocator.class);
+ assetManager.registerLoader(J3MLoader.class, "j3m");
+ assetManager.registerLoader(J3MLoader.class, "j3md");
+ assetManager.registerLoader(GLSLLoader.class, "vert", "frag","geom","tsctrl","tseval","glsllib","glsl");
+ }
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/ScreenSpaceSubsurfaceScatteringPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/ScreenSpaceSubsurfaceScatteringPass.java
new file mode 100644
index 0000000000..99583579bd
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/ScreenSpaceSubsurfaceScatteringPass.java
@@ -0,0 +1,9 @@
+package com.jme3.renderer.pass;
+
+/**
+ * Subsurface Scattering.
+ * http://www.iryoku.com/screen-space-subsurface-scattering
+ * @author JohnKkk
+ */
+public class ScreenSpaceSubsurfaceScatteringPass extends OpaquePass{
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/SkyPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/SkyPass.java
new file mode 100644
index 0000000000..17f0dda921
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/SkyPass.java
@@ -0,0 +1,52 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.renderer.framegraph.FGRenderContext;
+import com.jme3.renderer.queue.RenderQueue;
+
+/**
+ * @author JohnKkk
+ */
+public class SkyPass extends ForwardPass{
+ public SkyPass() {
+ super("Sky", RenderQueue.Bucket.Sky);
+ }
+
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ if(canExecute){
+ renderContext.setDepthRange(1, 1);
+ }
+ super.executeDrawCommandList(renderContext);
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/TileDeferredShadingPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/TileDeferredShadingPass.java
new file mode 100644
index 0000000000..399cd10529
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/TileDeferredShadingPass.java
@@ -0,0 +1,75 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.light.LightList;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialDef;
+import com.jme3.renderer.framegraph.FGRenderContext;
+
+public class TileDeferredShadingPass extends DeferredShadingPass{
+ private static final String _S_TILE_BASED_DEFERRED_SHADING_PASS_MAT_DEF = "Common/MatDefs/ShadingCommon/TileBasedDeferredShading.j3md";
+ protected final static String _S_TILE_BASED_DEFERRED_PASS = "TileBasedDeferredPass";
+
+ public TileDeferredShadingPass() {
+ super("TileDeferredShadingPass");
+ }
+
+ @Override
+ protected Material getMaterial() {
+ MaterialDef def = (MaterialDef) assetManager.loadAsset(_S_TILE_BASED_DEFERRED_SHADING_PASS_MAT_DEF);
+ screenMat = new Material(def);
+ return screenMat;
+ }
+
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ DeferredLightDataSink deferredLightDataSink = (DeferredLightDataSink) getSink(S_LIGHT_DATA);
+ DeferredLightDataSource.DeferredLightDataProxy deferredLightDataProxy = (DeferredLightDataSource.DeferredLightDataProxy) deferredLightDataSink.getBind();
+ LightList lights = deferredLightDataProxy.getLightData();
+
+ // Handle FullScreenLights
+ screenMat.selectTechnique(_S_TILE_BASED_DEFERRED_PASS, renderContext.renderManager);
+ boolean depthWrite = screenMat.getAdditionalRenderState().isDepthWrite();
+ boolean depthTest = screenMat.getAdditionalRenderState().isDepthTest();
+ screenMat.getAdditionalRenderState().setDepthTest(false);
+ screenMat.getAdditionalRenderState().setDepthWrite(false);
+ screenMat.setBoolean("UseLightsCullMode", false);
+ screenRect.updateGeometricState();
+ screenMat.render(screenRect, lights, renderContext.renderManager);
+ screenMat.getAdditionalRenderState().setDepthTest(depthTest);
+ screenMat.getAdditionalRenderState().setDepthWrite(depthWrite);
+
+ // Handle non-fullscreen lights
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/TranslucentPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/TranslucentPass.java
new file mode 100644
index 0000000000..67a2f1e9c1
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/TranslucentPass.java
@@ -0,0 +1,49 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.renderer.framegraph.FGRenderContext;
+import com.jme3.renderer.queue.RenderQueue;
+
+public class TranslucentPass extends ForwardPass{
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ if(canExecute){
+ renderContext.setDepthRange(0, 1);
+ }
+ super.executeDrawCommandList(renderContext);
+ }
+
+ public TranslucentPass() {
+ super("TranslucentPass", RenderQueue.Bucket.Translucent);
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/pass/TransparentPass.java b/jme3-core/src/main/java/com/jme3/renderer/pass/TransparentPass.java
new file mode 100644
index 0000000000..5496bdebb5
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/pass/TransparentPass.java
@@ -0,0 +1,52 @@
+/*
+ * 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.renderer.pass;
+
+import com.jme3.renderer.framegraph.FGRenderContext;
+import com.jme3.renderer.queue.RenderQueue;
+
+/**
+ * @author JohnKkk
+ */
+public class TransparentPass extends ForwardPass{
+ @Override
+ public void executeDrawCommandList(FGRenderContext renderContext) {
+ if(canExecute){
+ renderContext.setDepthRange(0, 1);
+ }
+ super.executeDrawCommandList(renderContext);
+ }
+
+ public TransparentPass() {
+ super("TransparentPass", RenderQueue.Bucket.Transparent);
+ }
+}
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 e95463f14e..f9754864db 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
@@ -34,13 +34,14 @@
import com.jme3.post.SceneProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.pass.RenderGeometry;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
/**
- * 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 {
@@ -50,12 +51,14 @@ public class RenderQueue {
private GeometryList transparentList;
private GeometryList translucentList;
private GeometryList skyList;
+ private GeometryList tempList;
/**
* Creates a new RenderQueue, the default {@link GeometryComparator comparators}
* are used for all {@link GeometryList geometry lists}.
*/
public RenderQueue() {
+ this.tempList = new GeometryList(new NullComparator());
this.opaqueList = new GeometryList(new OpaqueComparator());
this.guiList = new GeometryList(new GuiComparator());
this.transparentList = new GeometryList(new TransparentComparator());
@@ -65,49 +68,49 @@ public RenderQueue() {
/**
* The render queue Bucket specifies the bucket
- * to which the spatial will be placed when rendered.
+ * 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 +120,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 +138,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.
*/
@@ -237,9 +240,9 @@ public GeometryComparator getGeometryComparator(Bucket 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() }.
- *
+ *
* @param g The geometry to add
- * @param bucket The bucket to add to, usually
+ * @param bucket The bucket to add to, usually
* {@link Geometry#getQueueBucket() }.
*/
public void addToQueue(Geometry g, Bucket bucket) {
@@ -265,16 +268,42 @@ public void addToQueue(Geometry g, Bucket bucket) {
}
private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) {
- 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;
+ RenderGeometry renderGeometryHandler = rm.getRenderGeometryHandler();
+ if(renderGeometryHandler != null){
+ list.setCamera(cam); // select camera for sorting
+ list.sort();
+ tempList.clear();
+ for (int i = 0; i < list.size(); i++) {
+ Geometry obj = list.get(i);
+ assert obj != null;
+ // Check if it is actually rendered
+ if(!renderGeometryHandler.drawGeometry(rm, obj)){
+ tempList.add(obj);
+ }
+ obj.queueDistance = Float.NEGATIVE_INFINITY;
+ }
+ if (clear) {
+ list.clear();
+ // In order to perform subsequent RenderPath rendering
+ if(tempList.size() > 0){
+ for(int i = 0;i < tempList.size();i++){
+ list.add(tempList.get(i));
+ }
+ }
+ }
}
- if (clear) {
- list.clear();
+ else{
+ 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) {
+ list.clear();
+ }
}
}
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..dea54b355c 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 extends Spatial> spatialSubclass,
- String nameRegex) {
+ String nameRegex) {
if (spatialSubclass != null && !spatialSubclass.isInstance(this)) {
return false;
}
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/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
index ec85c0729e..7940dd3da3 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
@@ -128,7 +128,6 @@ MaterialDef Phong Lighting {
Vector2 LinearFog
Float ExpFog
Float ExpSqFog
-
}
Technique {
@@ -257,6 +256,50 @@ MaterialDef Phong Lighting {
}
+ Technique GBufferPass{
+ Pipeline Deferred
+ 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..f4911adb5d
--- /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
+ Context_OutGBuff3.xyz = normal;
+ Context_OutGBuff0.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
+ Context_OutGBuff0.rgb = diffuseColor.rgb * DiffuseSum.rgb;
+ Context_OutGBuff1.rgb = specularColor.rgb * SpecularSum.rgb * 100.0f + AmbientSum * 0.01f;
+ Context_OutGBuff1.a = m_Shininess;
+
+ // shading model id
+ Context_OutGBuff2.a = LEGACY_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 2b2d5453f4..9783972821 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
@@ -207,6 +207,49 @@ MaterialDef PBR Lighting {
}
+ Technique GBufferPass{
+ Pipeline Deferred
+ 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..e3079c696b
--- /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
+ Context_OutGBuff2.rgb = (emissive * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity).rgb;
+ #endif
+ // pack
+ vec2 n1 = octEncode(normal);
+ vec2 n2 = octEncode(norm);
+ Context_OutGBuff3.xy = n1;
+ Context_OutGBuff3.zw = n2;
+ Context_OutGBuff0.rgb = floor(diffuseColor.rgb * 100.0f) + ao * 0.1f;
+ Context_OutGBuff1.rgb = floor(specularColor.rgb * 100.0f) + fZero * 0.1f;
+ Context_OutGBuff1.a = Roughness;
+ Context_OutGBuff0.a = alpha;
+
+ // shading model id
+ Context_OutGBuff2.a = STANDARD_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/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
index b5854c2a23..7af5add5b9 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{
+ Pipeline Deferred
+ 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..0cbfdeb00c
--- /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
+
+ Context_OutGBuff2.rgb = color.rgb;
+
+ // shading model id
+ Context_OutGBuff2.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..a8e7b3eb84
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.frag
@@ -0,0 +1,221 @@
+#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
+
+
+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));
+ // 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, 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;
+ #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, 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;
+ #endif
+ gl_FragColor.rgb = vec3(0.0);
+ 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
+ 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;
+
+ gl_FragColor.rgb += directLighting * spotFallOff;
+ #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);
+ }
+}
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..265bab64b9
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.j3md
@@ -0,0 +1,44 @@
+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
+ }
+
+ Technique DeferredPass{
+ Pipeline Deferred
+ LightMode DeferredSinglePass
+
+ 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
+ }
+
+ Defines {
+ INSTANCING : UseInstancing
+ USE_LIGHTS_CULL_MODE : UseLightsCullMode
+ }
+ }
+
+}
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..cfb28fcf4a
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/DeferredShading.vert
@@ -0,0 +1,23 @@
+#import "Common/ShaderLib/GLSLCompat.glsllib"
+#import "Common/ShaderLib/Instancing.glsllib"
+varying vec2 texCoord;
+
+attribute vec3 inPosition;
+#if !defined(USE_LIGHTS_CULL_MODE)
+ attribute vec2 inTexCoord;
+#endif
+
+varying mat4 viewProjectionMatrixInverse;
+
+void main(){
+#if !defined(USE_LIGHTS_CULL_MODE)
+ texCoord = inTexCoord;
+ vec4 pos = vec4(inPosition, 1.0);
+ gl_Position = vec4(sign(pos.xy-vec2(0.5)), 0.0, 1.0);
+#else
+ gl_Position = TransformWorldViewProjection(vec4(inPosition, 1.0));// g_WorldViewProjectionMatrix * modelSpacePos;
+#endif
+
+ viewProjectionMatrixInverse = GetViewProjectionMatrixInverse();
+
+}
\ No newline at end of file
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..d316ed90e2
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TileBasedDeferredShading.frag
@@ -0,0 +1,373 @@
+#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));
+ // 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, 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, 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;
+ #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);
+ }
+}
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..98a0b58a98
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/MatDefs/ShadingCommon/TileBasedDeferredShading.j3md
@@ -0,0 +1,49 @@
+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{
+ Pipeline Deferred
+ LightMode TileBasedDeferredSinglePass
+
+ 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..bc8c4cfa06
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/ShaderLib/Deferred.glsllib
@@ -0,0 +1,65 @@
+#ifndef _JME_CONTEXT_
+#define _JME_CONTEXT_
+
+#if __VERSION__ >= 120
+layout(location = 0) out vec4 outColor0;
+layout(location = 1) out vec4 outColor1;
+layout(location = 2) out vec4 outColor2;
+layout(location = 3) out vec4 outColor3;
+layout(location = 4) out vec4 outColor4;
+#define Context_OutGBuff0 outColor0
+#define Context_OutGBuff1 outColor1
+#define Context_OutGBuff2 outColor2
+#define Context_OutGBuff3 outColor3
+#define Context_OutGBuff4 outColor4
+#else
+#define Context_OutGBuff0 gl_FragData[0]
+#define Context_OutGBuff1 gl_FragData[1]
+#define Context_OutGBuff2 gl_FragData[2]
+#define Context_OutGBuff3 gl_FragData[3]
+#define Context_OutGBuff4 gl_FragData[4]
+#endif
+
+
+uniform sampler2D m_Context_InGBuff0;
+uniform sampler2D m_Context_InGBuff1;
+uniform sampler2D m_Context_InGBuff2;
+uniform sampler2D m_Context_InGBuff3;
+uniform sampler2D m_Context_InGBuff4;
+#define Context_InGBuff0 m_Context_InGBuff0
+#define Context_InGBuff1 m_Context_InGBuff1
+#define Context_InGBuff2 m_Context_InGBuff2
+#define Context_InGBuff3 m_Context_InGBuff3
+#define Context_InGBuff4 m_Context_InGBuff4
+
+#endif
+#define GBUFFER_DEPTH Context_InGBuff4
+
+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;
+ 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){
+ float depth = texture2D(GBUFFER_DEPTH, texCoord).r;
+ vec4 pos;
+ 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;
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib
index 77cb34c8c0..af3bd33c3d 100644
--- a/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib
+++ b/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib
@@ -1,3 +1,8 @@
+#if __VERSION__ >= 130
+#extension GL_ARB_explicit_attrib_location : enable
+#endif
+
+#if __VERSION__ >= 310
#ifdef FRAGMENT_SHADER
precision highp float;
precision highp int;
@@ -11,6 +16,7 @@
#endif
#endif
+#endif
#if defined GL_ES
# define hfloat highp float
@@ -37,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
@@ -108,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..b12bfe685d
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/ShaderLib/ShadingModel.glsllib
@@ -0,0 +1,18 @@
+// SHADING_MODEL
+// 0 is clearRT mask
+#define LEGACY_LIGHTING 1
+#define STANDARD_LIGHTING 2
+#define UNLIT 3
+#define SUBSURFACE_SCATTERING 4
+
+#define IS_LIT(SHADING_MODEL_ID) (SHADING_MODEL_ID == LEGACY_LIGHTING || SHADING_MODEL_ID == STANDARD_LIGHTING || SHADING_MODEL_ID == SUBSURFACE_SCATTERING)
+
+
+#ifdef USE_REFLECTION_PROBE
+ uniform vec3 m_FresnelParams;
+ vec4 computeProbeRef(in vec3 wPosition, in vec3 wNormal, in vec3 wViewDir, in float VdotN){
+ vec4 refVec;
+ refVec.xyz = reflect(wViewDir, wNormal);
+ refVec.w = m_FresnelParams.x + m_FresnelParams.y * pow(1.0 + VdotN, m_FresnelParams.z);
+ }
+#endif
\ No newline at end of file
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..54a103f869
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/ShaderLib/SkyLightReflectionProbe.glsllib
@@ -0,0 +1,73 @@
+// BEGIN@JohnKkk,render skyLights and reflectionProbe
+// sky lights and reflection probes
+#if NB_SKY_LIGHT_AND_REFLECTION_PROBES >= 1
+uniform samplerCube g_ReflectionEnvMap;
+uniform vec3 g_ShCoeffs[9];
+uniform mat4 g_SkyLightData;
+#endif
+#if NB_SKY_LIGHT_AND_REFLECTION_PROBES >= 2
+uniform samplerCube g_ReflectionEnvMap2;
+uniform vec3 g_ShCoeffs2[9];
+uniform mat4 g_SkyLightData2;
+#endif
+#if NB_SKY_LIGHT_AND_REFLECTION_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_SKY_LIGHT_AND_REFLECTION_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_SKY_LIGHT_AND_REFLECTION_PROBES >= 2
+ float ndf2 = renderProbe(viewDir, wPosition, normal, norm, Roughness, difColor, specColor, ndotv, ao, g_SkyLightData2, g_ShCoeffs2, g_ReflectionEnvMap2, color2);
+ #endif
+ #if NB_SKY_LIGHT_AND_REFLECTION_PROBES == 3
+ float ndf3 = renderProbe(viewDir, wPosition, normal, norm, Roughness, difColor, specColor, ndotv, ao, g_SkyLightData3, g_ShCoeffs3, g_ReflectionEnvMap3, color3);
+ #endif
+
+ #if NB_SKY_LIGHT_AND_REFLECTION_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_SKY_LIGHT_AND_REFLECTION_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;
+}
+// END@render skyLights and reflectionProbe
\ No newline at end of file
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..0fef61733e 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
@@ -64,7 +64,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;
@@ -121,6 +121,17 @@ private void readShaderDefinition(Shader.ShaderType shaderType, String name, Str
}
}
+ // Pipeline
+ private void readPipeline(String statement) throws IOException{
+ String[] split = statement.split(whitespacePattern);
+ if (split.length != 2){
+ throw new IOException("Pipeline statement syntax incorrect");
+ }
+
+ TechniqueDef.Pipeline pl = TechniqueDef.Pipeline.valueOf(split[1]);
+ technique.setPipeline(pl);
+ }
+
// LightMode
private void readLightMode(String statement) throws IOException{
String[] split = statement.split(whitespacePattern);
@@ -131,15 +142,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 +310,7 @@ private Texture parseTextureType(final VarType type, final String value) {
for (final TextureOptionValue textureOptionValue : textureOptionValues) {
textureOptionValue.applyToTexture(texture);
}
- }
+ }
return texture;
}
@@ -313,28 +324,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 +549,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 +577,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")){
+ readPipeline(statement.getLine());
}else if (split[0].equals("LightMode")){
readLightMode(statement.getLine());
}else if (split[0].equals("LightSpace")){
@@ -609,7 +622,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++) {
@@ -653,6 +666,12 @@ private void readTechnique(Statement techStat) throws IOException{
case SinglePass:
technique.setLogic(new SinglePassLightingLogic(technique));
break;
+ case DeferredSinglePass:
+ technique.setLogic(new DeferredSinglePassLightingLogic(technique));
+ break;
+ case TileBasedDeferredSinglePass:
+ technique.setLogic(new TileBasedDeferredSinglePassLightingLogic(technique));
+ break;
case StaticPass:
technique.setLogic(new StaticPassLightingLogic(technique));
break;
@@ -667,7 +686,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-examples/src/main/java/jme3test/renderpath/RenderPathHelper.java b/jme3-examples/src/main/java/jme3test/renderpath/RenderPathHelper.java
new file mode 100644
index 0000000000..9ace9b4e13
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/RenderPathHelper.java
@@ -0,0 +1,71 @@
+package jme3test.renderpath;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapFont;
+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.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+
+public class RenderPathHelper implements ActionListener {
+ private RenderManager.RenderPath currentRenderPath;
+ private BitmapText hitText;
+ private RenderManager renderManager;
+ private Vector3f hitLocation = new Vector3f(0, 0, 0);
+ private String keyHit = "SPACE";
+ private int keyInput = KeyInput.KEY_SPACE;
+ public RenderPathHelper(SimpleApplication simpleApplication){
+ renderManager = simpleApplication.getRenderManager();
+ currentRenderPath = renderManager.getRenderPath();
+ makeHudText(simpleApplication);
+ hitLocation.set(0, simpleApplication.getCamera().getHeight(), 0);
+ simpleApplication.getInputManager().addListener(this, "toggleRenderPath");
+ simpleApplication.getInputManager().addMapping("toggleRenderPath", new KeyTrigger(keyInput));
+ }
+
+ public RenderPathHelper(SimpleApplication simpleApplication, Vector3f hitLocation, int keyInput, String keyHit){
+ renderManager = simpleApplication.getRenderManager();
+ currentRenderPath = renderManager.getRenderPath();
+ this.hitLocation.set(hitLocation);
+ this.keyInput = keyInput;
+ this.keyHit = keyHit;
+ makeHudText(simpleApplication);
+ simpleApplication.getInputManager().addListener(this, "toggleRenderPath");
+ simpleApplication.getInputManager().addMapping("toggleRenderPath", new KeyTrigger(keyInput));
+ }
+
+ private void makeHudText(SimpleApplication simpleApplication) {
+ BitmapFont guiFont = simpleApplication.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
+ hitText = new BitmapText(guiFont, false);
+ hitText.setSize(guiFont.getCharSet().getRenderedSize());
+ hitText.setText("RendererPath : "+ currentRenderPath.getInfo());
+ hitText.setLocalTranslation(hitLocation);
+ simpleApplication.getGuiNode().attachChild(hitText);
+
+ // hit text
+ BitmapText title = new BitmapText(guiFont, false);
+ title.setSize(guiFont.getCharSet().getRenderedSize());
+ title.setText("Please press the " + keyHit + " to toggle the render path");
+ title.setLocalTranslation(hitLocation);
+ title.getLocalTranslation().y -= 20;
+ simpleApplication.getGuiNode().attachChild(title);
+ }
+ @Override
+ public void onAction(String name, boolean isPressed, float tpf) {
+ if(name.equals("toggleRenderPath") && !isPressed){
+ if(currentRenderPath == RenderManager.RenderPath.Deferred){
+ currentRenderPath = RenderManager.RenderPath.TiledDeferred;
+ }
+ else if(currentRenderPath == RenderManager.RenderPath.TiledDeferred){
+ currentRenderPath = RenderManager.RenderPath.Forward;
+ }
+ else{
+ currentRenderPath = RenderManager.RenderPath.Deferred;
+ }
+ renderManager.setRenderPath(currentRenderPath);
+ hitText.setText("RendererPath : "+ currentRenderPath.getInfo());
+ }
+ }
+}
diff --git a/jme3-examples/src/main/java/jme3test/renderpath/TerrainTestAdvancedRenderPath.java b/jme3-examples/src/main/java/jme3test/renderpath/TerrainTestAdvancedRenderPath.java
new file mode 100644
index 0000000000..c40353608c
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TerrainTestAdvancedRenderPath.java
@@ -0,0 +1,344 @@
+/*
+ * 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.renderpath;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.bounding.BoundingBox;
+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.material.TechniqueDef;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+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();
+
+ // 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));
+
+ new RenderPathHelper(this, new Vector3f(0, cam.getHeight() / 2, 0), KeyInput.KEY_K, "K");
+ 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);
+ rootNode.attachChild(sky);
+ }
+
+ 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/renderpath/TerrainTestAndroidRenderPath.java b/jme3-examples/src/main/java/jme3test/renderpath/TerrainTestAndroidRenderPath.java
new file mode 100644
index 0000000000..6e455689dd
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TerrainTestAndroidRenderPath.java
@@ -0,0 +1,199 @@
+/*
+ * 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.renderpath;
+
+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));
+
+ new RenderPathHelper(this, new Vector3f(0, cam.getHeight() / 2, 0), KeyInput.KEY_K, "K");
+ }
+
+ 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/renderpath/TestDeferredPBRShading.java b/jme3-examples/src/main/java/jme3test/renderpath/TestDeferredPBRShading.java
new file mode 100644
index 0000000000..c24ebb29d5
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestDeferredPBRShading.java
@@ -0,0 +1,180 @@
+package jme3test.renderpath;
+
+import com.jme3.app.SimpleApplication;
+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.input.ChaseCamera;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.LightProbe;
+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.RenderManager;
+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() {
+ // Baking irradiance data requires the Forward path!
+ renderManager.setRenderPath(RenderManager.RenderPath.Forward);
+
+ 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(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);
+
+ pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
+ model.setMaterial(pbrMat);
+
+
+ final EnvironmentCamera envCam = new EnvironmentCamera(256, new Vector3f(0, 3f, 0));
+ stateManager.attach(envCam);
+
+ 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) {
+ 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);
+ // Now, switching to the Deferred rendering path.
+ renderManager.setRenderPath(RenderManager.RenderPath.Deferred);
+ }
+ });
+ probe.getArea().setRadius(100);
+ rootNode.addLight(probe);
+ //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
+
+ }
+ 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/renderpath/TestDeferredShading.java b/jme3-examples/src/main/java/jme3test/renderpath/TestDeferredShading.java
new file mode 100644
index 0000000000..645e6429cc
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestDeferredShading.java
@@ -0,0 +1,113 @@
+package jme3test.renderpath;
+
+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.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.instancing.InstancedNode;
+import com.jme3.scene.shape.Quad;
+import com.jme3.scene.shape.Sphere;
+
+/**
+ * https://en.wikipedia.org/wiki/Deferred_shading/
+ * @author JohnKkk
+ */
+public class TestDeferredShading extends SimpleApplication {
+ private Material material;
+ @Override
+ public void simpleInitApp() {
+ // Test Forward↓
+// renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass);
+// renderManager.setSinglePassLightBatchSize(30);
+// renderManager.setRenderPath(RenderManager.RenderPath.Forward);
+ renderManager.setCurMaxDeferredShadingLightNum(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(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));
+ 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);
+
+
+ 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) {
+ TestDeferredShading testTileBasedDeferredShading = new TestDeferredShading();
+ testTileBasedDeferredShading.start();
+ }
+}
diff --git a/jme3-examples/src/main/java/jme3test/renderpath/TestDeferredShadingPathShadow.java b/jme3-examples/src/main/java/jme3test/renderpath/TestDeferredShadingPathShadow.java
new file mode 100644
index 0000000000..a22da79b91
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestDeferredShadingPathShadow.java
@@ -0,0 +1,116 @@
+package jme3test.renderpath;
+
+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.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 {
+ private RenderManager.RenderPath currentRenderPath;
+ private BitmapText hitText;
+
+ private void makeHudText() {
+ guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+ hitText = new BitmapText(guiFont, false);
+ hitText.setSize(guiFont.getCharSet().getRenderedSize());
+ hitText.setText("RendererPath : "+ currentRenderPath.getInfo());
+ hitText.setLocalTranslation(0, cam.getHeight(), 0);
+ guiNode.attachChild(hitText);
+ }
+
+ @Override
+ public void simpleInitApp() {
+ 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));
+
+ currentRenderPath = RenderManager.RenderPath.Forward;
+ renderManager.setRenderPath(currentRenderPath);
+ makeHudText();
+
+ 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) {
+ if(name.equals("toggleRenderPath") && !isPressed){
+ if(currentRenderPath == RenderManager.RenderPath.Deferred){
+ currentRenderPath = RenderManager.RenderPath.TiledDeferred;
+ }
+ else if(currentRenderPath == RenderManager.RenderPath.TiledDeferred){
+ currentRenderPath = RenderManager.RenderPath.Forward;
+ }
+ else{
+ currentRenderPath = RenderManager.RenderPath.Deferred;
+ }
+ renderManager.setRenderPath(currentRenderPath);
+ hitText.setText("RendererPath : "+ currentRenderPath.getInfo());
+ }
+ }
+}
diff --git a/jme3-examples/src/main/java/jme3test/renderpath/TestPBRTerrainAdvancedRenderPath.java b/jme3-examples/src/main/java/jme3test/renderpath/TestPBRTerrainAdvancedRenderPath.java
new file mode 100644
index 0000000000..0ae44506c2
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestPBRTerrainAdvancedRenderPath.java
@@ -0,0 +1,459 @@
+/*
+ * 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.renderpath;
+
+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.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)
+ *
+ * -
+ * 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.
+ *
+ * -
+ * 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.
+ *
+ *
+ *
+ * @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();
+ 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() {
+ setupKeys();
+ setUpTerrain();
+ setUpTerrainMaterial(); // <- This method contains the important info about using 'AdvancedPBRTerrain.j3md'
+ 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);
+ new RenderPathHelper(this, new Vector3f(0, cam.getHeight() / 2, 0), KeyInput.KEY_K, "K");
+ }
+
+ 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);
+
+ 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);
+ }
+}
diff --git a/jme3-examples/src/main/java/jme3test/renderpath/TestPBRTerrainRenderPath.java b/jme3-examples/src/main/java/jme3test/renderpath/TestPBRTerrainRenderPath.java
new file mode 100644
index 0000000000..2603562506
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestPBRTerrainRenderPath.java
@@ -0,0 +1,464 @@
+package jme3test.renderpath;
+
+/*
+ * 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.scene.Geometry;
+import com.jme3.scene.instancing.InstancedNode;
+import com.jme3.scene.shape.Sphere;
+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;
+
+/**
+ * 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)
+ *
+ * -
+ * 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.
+ *
+ *
+ *
+ * @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 = 1000;
+ 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(1600);
+ appSettings.setHeight(900);
+ 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() {
+ // 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/renderpath/TestRenderPathChanged.java b/jme3-examples/src/main/java/jme3test/renderpath/TestRenderPathChanged.java
new file mode 100644
index 0000000000..68fb71f65c
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestRenderPathChanged.java
@@ -0,0 +1,110 @@
+package jme3test.renderpath;
+
+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.Light;
+import com.jme3.light.LightList;
+import com.jme3.light.PointLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+
+/**
+ * This demonstrates how to switch the rendering path at runtime.
+ * @author JohnKkk
+ */
+public class TestRenderPathChanged extends SimpleApplication implements ActionListener {
+ private RenderManager.RenderPath currentRenderPath;
+ private BitmapText hitText;
+
+ private void makeHudText() {
+ guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+ hitText = new BitmapText(guiFont, false);
+ hitText.setSize(guiFont.getCharSet().getRenderedSize());
+ hitText.setText("RendererPath : "+ currentRenderPath.getInfo());
+ hitText.setLocalTranslation(0, cam.getHeight(), 0);
+ guiNode.attachChild(hitText);
+ }
+
+ @Override
+ public void simpleInitApp() {
+ 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);
+
+ int nb = 0;
+ for (Light light : lightList) {
+ nb++;
+ PointLight p = (PointLight) light;
+ if (nb > 60) {
+ n.removeLight(light);
+ } else {
+ int rand = FastMath.nextRandomInt(0, 3);
+ switch (rand) {
+ case 0:
+ light.setColor(ColorRGBA.Red);
+ break;
+ case 1:
+ light.setColor(ColorRGBA.Yellow);
+ break;
+ case 2:
+ light.setColor(ColorRGBA.Green);
+ break;
+ case 3:
+ light.setColor(ColorRGBA.Orange);
+ break;
+ }
+ }
+ }
+
+ 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);
+
+
+ inputManager.addListener(this, "toggleRenderPath");
+ inputManager.addMapping("toggleRenderPath", new KeyTrigger(KeyInput.KEY_SPACE));
+
+ // set RenderPath
+ currentRenderPath = RenderManager.RenderPath.Forward;
+ renderManager.setRenderPath(currentRenderPath);
+ makeHudText();
+
+ flyCam.setMoveSpeed(20.0f);
+ }
+
+ @Override
+ public void onAction(String name, boolean isPressed, float tpf) {
+ if(name.equals("toggleRenderPath") && !isPressed){
+ if(currentRenderPath == RenderManager.RenderPath.Deferred){
+ currentRenderPath = RenderManager.RenderPath.TiledDeferred;
+ }
+ else if(currentRenderPath == RenderManager.RenderPath.TiledDeferred){
+ currentRenderPath = RenderManager.RenderPath.Forward;
+ }
+ else{
+ currentRenderPath = RenderManager.RenderPath.Deferred;
+ }
+ renderManager.setRenderPath(currentRenderPath);
+ hitText.setText("RendererPath : "+ currentRenderPath.getInfo());
+ }
+ }
+
+ public static void main(String[] args) {
+ TestRenderPathChanged testRenderPathChanged = new TestRenderPathChanged();
+ testRenderPathChanged.start();
+ }
+}
diff --git a/jme3-examples/src/main/java/jme3test/renderpath/TestRenderPathPointDirectionalAndSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/renderpath/TestRenderPathPointDirectionalAndSpotLightShadows.java
new file mode 100644
index 0000000000..71b01573af
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestRenderPathPointDirectionalAndSpotLightShadows.java
@@ -0,0 +1,147 @@
+/*
+ * 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.renderpath;
+
+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.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.*;
+import jme3test.light.ShadowTestUIManager;
+
+/**
+ * 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() {
+ // 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);
+
+ new RenderPathHelper(this);
+ }
+
+ 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/renderpath/TestShadingModel.java b/jme3-examples/src/main/java/jme3test/renderpath/TestShadingModel.java
new file mode 100644
index 0000000000..bdba06efa7
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestShadingModel.java
@@ -0,0 +1,137 @@
+package jme3test.renderpath;
+
+import com.jme3.app.SimpleApplication;
+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.light.DirectionalLight;
+import com.jme3.light.LightProbe;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.ToneMapFilter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+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 Node modelNode;
+ private int frame = 0;
+ private Material pbrMat;
+ private Geometry model;
+ private Node tex;
+
+ @Override
+ public void simpleInitApp() {
+
+ // UNLIT
+ Material unlitMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ unlitMat.setTexture("ColorMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg"));
+ 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);
+ 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);
+ rootNode.attachChild(lightSphere);
+
+ // STANDARD_LIGHTING
+ 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);
+ modelNode.setLocalScale(0.3f);
+ 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(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);
+
+ pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
+ model.setMaterial(pbrMat);
+
+
+ final EnvironmentCamera envCam = new EnvironmentCamera(256, new Vector3f(0, 3f, 0));
+ stateManager.attach(envCam);
+
+ new RenderPathHelper(this);
+ flyCam.setMoveSpeed(10.0f);
+ }
+
+ @Override
+ public void simpleRender(RenderManager rm) {
+ super.simpleRender(rm);
+ frame++;
+
+ if (frame == 2) {
+ modelNode.removeFromParent();
+ final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter() {
+
+ @Override
+ public void done(LightProbe result) {
+ System.err.println("Done rendering env maps");
+ 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);
+ }
+ }
+
+ public static void main(String[] args) {
+ TestShadingModel testShadingModel = new TestShadingModel();
+ testShadingModel.start();
+ }
+}
diff --git a/jme3-examples/src/main/java/jme3test/renderpath/TestSimpleDeferredLighting.java b/jme3-examples/src/main/java/jme3test/renderpath/TestSimpleDeferredLighting.java
new file mode 100644
index 0000000000..8d35e523dc
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestSimpleDeferredLighting.java
@@ -0,0 +1,768 @@
+/*
+ * 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.renderpath;
+
+import com.jme3.app.ChaseCameraAppState;
+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.RenderManager;
+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.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;
+
+public class TestSimpleDeferredLighting extends SimpleApplication implements ActionListener {
+ private RenderManager.RenderPath currentRenderPath;
+ 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);
+ 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 > 60) {
+ n.removeLight(light);
+ } else {
+ int rand = FastMath.nextRandomInt(0, 3);
+ switch (rand) {
+ case 0:
+ light.setColor(ColorRGBA.Red);
+ break;
+ case 1:
+ light.setColor(ColorRGBA.Yellow);
+ break;
+ case 2:
+ light.setColor(ColorRGBA.Green);
+ break;
+ case 3:
+ light.setColor(ColorRGBA.Orange);
+ 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() {
+ currentRenderPath = RenderManager.RenderPath.Forward;
+ renderManager.setRenderPath(currentRenderPath);
+ testScene10();
+// 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);
+
+ makeHudText();
+ registerInput();
+ }
+
+ private void makeHudText() {
+ guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+ hitText = new BitmapText(guiFont, false);
+ hitText.setSize(guiFont.getCharSet().getRenderedSize());
+ hitText.setText("RendererPath : "+ currentRenderPath.getInfo());
+ hitText.setLocalTranslation(0, cam.getHeight(), 0);
+ guiNode.attachChild(hitText);
+ }
+
+ 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){
+ frame++;
+
+ if (frame == 2) {
+ modelNode.removeFromParent();
+ final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter() {
+
+ @Override
+ public void done(LightProbe result) {
+ System.err.println("Done rendering env maps");
+ 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){
+ if(currentRenderPath == RenderManager.RenderPath.Deferred){
+ currentRenderPath = RenderManager.RenderPath.TiledDeferred;
+ }
+ else if(currentRenderPath == RenderManager.RenderPath.TiledDeferred){
+ currentRenderPath = RenderManager.RenderPath.Forward;
+ }
+ else{
+ currentRenderPath = RenderManager.RenderPath.Deferred;
+ }
+ renderManager.setRenderPath(currentRenderPath);
+// getRenderManager().setForcedTechnique(null);
+ hitText.setText("RendererPath : "+ currentRenderPath.getInfo());
+ }
+// 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/renderpath/TestTileBasedDeferredShading.java b/jme3-examples/src/main/java/jme3test/renderpath/TestTileBasedDeferredShading.java
new file mode 100644
index 0000000000..3e8fe040d8
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderpath/TestTileBasedDeferredShading.java
@@ -0,0 +1,131 @@
+package jme3test.renderpath;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.material.TechniqueDef;
+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.RenderManager;
+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() {
+ // 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.setCurMaxDeferredShadingLightNum(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-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md
index fe0680482b..d17e8e860b 100644
--- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md
+++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md
@@ -339,6 +339,82 @@ MaterialDef Advanced PBR Terrain {
}
}
+ Technique GBufferPass{
+ Pipeline Deferred
+ 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..90feef10b5
--- /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);
+ Context_OutGBuff3.xy = n1;
+ Context_OutGBuff3.zw = n2;
+ Context_OutGBuff0.rgb = floor(diffuseColor.rgb * 100.0f) + ao * 0.1f;
+ Context_OutGBuff1.rgb = floor(specularColor.rgb * 100.0f) + fZero * 0.1f;
+ Context_OutGBuff1.a = Roughness;
+ Context_OutGBuff0.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
+ Context_OutGBuff2.a = STANDARD_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);
+
+ Context_OutGBuff2.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..ba92341a60
--- /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);
+ Context_OutGBuff3.xy = n1;
+ Context_OutGBuff3.zw = n2;
+ Context_OutGBuff0.rgb = floor(diffuseColor.rgb * 100.0f) + ao * 0.1f;
+ Context_OutGBuff1.rgb = floor(specularColor.rgb * 100.0f) + fZero * 0.1f;
+ Context_OutGBuff1.a = Roughness;
+ Context_OutGBuff0.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
+ Context_OutGBuff2.a = STANDARD_LIGHTING + indoorSunLightExposure * 0.01f;
+
+ if(emissive.a > 0){
+ emissive = emissive * pow(emissive.a * 5, emissiveIntensity) * emissiveIntensity * 20 * emissive.a;
+ }
+ Context_OutGBuff2.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..92f1e6f231
--- /dev/null
+++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.frag
@@ -0,0 +1,627 @@
+#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;
+
+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
+ //-------------------------------
+ Context_OutGBuff3.xyz = normal;
+ Context_OutGBuff0 = diffuseColor * DiffuseSum;
+ Context_OutGBuff1.rgb = SpecularSum.rgb * 100.0f + AmbientSum.rgb * 0.01f;
+ Context_OutGBuff1.a = m_Shininess;
+ // shading model id
+ Context_OutGBuff2.a = LEGACY_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..b1284d28f3
--- /dev/null
+++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/GBufferPack/TerrainLightingGBufferPack.vert
@@ -0,0 +1,57 @@
+#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;
+ DiffuseSum = vec4(1.0);
+ SpecularSum = vec4(0.0);
+
+
+#ifdef TRI_PLANAR_MAPPING
+ wVertex = vec4(inPosition,0.0);
+ wNormal = inNormal;
+#endif
+
+}
\ No newline at end of file
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 572bf8ef49..0f36c14c8a 100644
--- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md
+++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md
@@ -317,6 +317,97 @@ MaterialDef PBR Terrain {
}
}
+ Technique GBufferPass{
+
+ Pipeline Deferred
+
+ 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..4841cd8105 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,6 +35,34 @@ MaterialDef Terrain {
TRI_PLANAR_MAPPING : useTriPlanarMapping
}
}
+
+ Technique GBufferPass{
+ Pipeline Deferred
+ 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 {
}
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..ea31874abb 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,68 @@ MaterialDef Terrain Lighting {
}
}
+ Technique GBufferPass{
+
+ Pipeline Deferred
+
+ 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 {