");
+
+ InstancedNode instancedNode = new InstancedNode("InstancedNode");
+ rootNode.attachChild(instancedNode);
+
+ Box mesh = new Box(0.5f, 0.5f, 0.5f);
+ box = new Geometry("Box", mesh);
+ Material pbrMaterial = createPbrMaterial(ColorRGBA.Red);
+ box.setMaterial(pbrMaterial);
+
+ instancedNode.attachChild(box);
+ instancedNode.instance();
+
+ DirectionalLight light = new DirectionalLight();
+ light.setDirection(new Vector3f(-1, -2, -3).normalizeLocal());
+ rootNode.addLight(light);
+ }
+
+ private Material createPbrMaterial(ColorRGBA color) {
+ Material mat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md");
+ mat.setColor("BaseColor", color);
+ mat.setFloat("Roughness", 0.8f);
+ mat.setFloat("Metallic", 0.1f);
+ mat.setBoolean("UseInstancing", true);
+ return mat;
+ }
+
+ private void configureCamera() {
+ flyCam.setMoveSpeed(15f);
+ flyCam.setDragToRotate(true);
+
+ cam.setLocation(Vector3f.UNIT_XYZ.mult(12));
+ cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+ }
+
+ private BitmapText createLabelText(int x, int y, String text) {
+ BitmapText bmp = new BitmapText(guiFont);
+ bmp.setText(text);
+ bmp.setLocalTranslation(x, settings.getHeight() - y, 0);
+ bmp.setColor(ColorRGBA.Red);
+ guiNode.attachChild(bmp);
+ return bmp;
+ }
+
+ @Override
+ public void simpleUpdate(float tpf) {
+ pos += tpf * vel;
+ if (pos < -10f || pos > 10f) {
+ vel *= -1;
+ }
+ box.setLocalTranslation(pos, 0f, 0f);
+ bmp.setText(String.format(Locale.ENGLISH, "BoxPosition: (%.2f, %.1f, %.1f)", pos, 0f, 0f));
+ }
+
+}
diff --git a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java
index 6127c279c0..d4cd04e403 100644
--- a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java
+++ b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java
@@ -189,6 +189,20 @@ public void onAction(String name, boolean pressed, float tpf) {
isNight = !isNight;
// Ambient and directional light are faded smoothly in update loop below.
}
+
+ if(name.length() == 1 && !pressed){
+ if(name.equals("-")){
+ matTerrain.setInt("DebugValuesMode", -1);
+ }else{
+ try{
+ int debugValueMode = Integer.parseInt(name);
+ matTerrain.setInt("DebugValuesMode", debugValueMode);
+ }
+ catch(Exception e){
+
+ }
+ }
+ }
}
};
@@ -364,16 +378,51 @@ private void setupKeys() {
inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P));
inputManager.addMapping("toggleNight", new KeyTrigger(KeyInput.KEY_N));
+ inputManager.addMapping("0", new KeyTrigger(KeyInput.KEY_0)); // toggleDebugModeForAlbedo
+ inputManager.addMapping("1", new KeyTrigger(KeyInput.KEY_1)); // toggleDebugModeForNormalMap
+ inputManager.addMapping("2", new KeyTrigger(KeyInput.KEY_2)); // toggleDebugModeForRoughness
+ inputManager.addMapping("3", new KeyTrigger(KeyInput.KEY_3)); // toggleDebugModeForMetallic
+ inputManager.addMapping("4", new KeyTrigger(KeyInput.KEY_4)); // toggleDebugModeForAo
+ inputManager.addMapping("5", new KeyTrigger(KeyInput.KEY_5)); // toggleDebugModeForEmissive
+ inputManager.addMapping("6", new KeyTrigger(KeyInput.KEY_6)); // toggleDebugModeForExposure
+ inputManager.addMapping("7", new KeyTrigger(KeyInput.KEY_7)); // toggleDebugModeForAlpha
+ inputManager.addMapping("8", new KeyTrigger(KeyInput.KEY_8)); // toggleDebugModeForGeometryNormals
+
+ inputManager.addMapping("-", new KeyTrigger(KeyInput.KEY_MINUS)); // - key will disable dbug mode
+
inputManager.addListener(actionListener, "triPlanar");
inputManager.addListener(actionListener, "toggleNight");
+ inputManager.addListener(actionListener, "0");
+ inputManager.addListener(actionListener, "1");
+ inputManager.addListener(actionListener, "2");
+ inputManager.addListener(actionListener, "3");
+ inputManager.addListener(actionListener, "4");
+ inputManager.addListener(actionListener, "5");
+ inputManager.addListener(actionListener, "6");
+ inputManager.addListener(actionListener, "7");
+ inputManager.addListener(actionListener, "8");
+ inputManager.addListener(actionListener, "-");
+
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");
+ keybindingsText.setText("Press N to toggle day/night fade (takes a moment) \n"
+ + "Press P to toggle tri-planar mode\n\n"
+ + "Press - for Final Render (disable debug view)\n"
+ + "Press 0 for Albedo debug view\n"
+ + "Press 1 for Normal Map debug view\n"
+ + "Press 2 for Roughness debug view\n"
+ + "Press 3 for Metallic debug view\n"
+ + "Press 4 for Ambient Occlusion (ao) debug view\n"
+ + "Press 5 for Emissive debug view\n"
+ + "Press 6 for Exposure debug view\n"
+ + "Press 7 for Alpha debug view\n"
+ + "Press 8 for Geoemtry Normals debug view\n");
+
getGuiNode().attachChild(keybindingsText);
keybindingsText.move(new Vector3f(200, 120, 0));
+ keybindingsText.move(new Vector3f(5, cam.getHeight() * 0.995f, 0));
}
-
@Override
public void simpleUpdate(float tpf) {
super.simpleUpdate(tpf);
diff --git a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java
index 1006fbd5f4..1500257612 100644
--- a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java
+++ b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java
@@ -153,6 +153,22 @@ public void onAction(String name, boolean pressed, float tpf) {
isNight = !isNight;
// Ambient and directional light are faded smoothly in update loop below.
}
+
+ if(name.length() == 1 && !pressed){
+ if(name.equals("-")){
+ matTerrain.setInt("DebugValuesMode", -1);
+ }else{
+ try{
+ int debugValueMode = Integer.parseInt(name);
+ matTerrain.setInt("DebugValuesMode", debugValueMode);
+ }
+ catch(Exception e){
+
+ }
+ }
+
+ }
+
}
};
@@ -270,14 +286,49 @@ private void setupKeys() {
inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P));
inputManager.addMapping("toggleNight", new KeyTrigger(KeyInput.KEY_N));
+ inputManager.addMapping("0", new KeyTrigger(KeyInput.KEY_0)); // toggleDebugModeForAlbedo
+ inputManager.addMapping("1", new KeyTrigger(KeyInput.KEY_1)); // toggleDebugModeForNormalMap
+ inputManager.addMapping("2", new KeyTrigger(KeyInput.KEY_2)); // toggleDebugModeForRoughness
+ inputManager.addMapping("3", new KeyTrigger(KeyInput.KEY_3)); // toggleDebugModeForMetallic
+ inputManager.addMapping("4", new KeyTrigger(KeyInput.KEY_4)); // toggleDebugModeForAo
+ inputManager.addMapping("5", new KeyTrigger(KeyInput.KEY_5)); // toggleDebugModeForEmissive
+ inputManager.addMapping("6", new KeyTrigger(KeyInput.KEY_6)); // toggleDebugModeForExposure
+ inputManager.addMapping("7", new KeyTrigger(KeyInput.KEY_7)); // toggleDebugModeForAlpha
+ inputManager.addMapping("8", new KeyTrigger(KeyInput.KEY_8)); // toggleDebugModeForGeometryNormals
+
+ inputManager.addMapping("-", new KeyTrigger(KeyInput.KEY_MINUS)); // - key will disable dbug mode
+
inputManager.addListener(actionListener, "triPlanar");
inputManager.addListener(actionListener, "toggleNight");
+ inputManager.addListener(actionListener, "0");
+ inputManager.addListener(actionListener, "1");
+ inputManager.addListener(actionListener, "2");
+ inputManager.addListener(actionListener, "3");
+ inputManager.addListener(actionListener, "4");
+ inputManager.addListener(actionListener, "5");
+ inputManager.addListener(actionListener, "6");
+ inputManager.addListener(actionListener, "7");
+ inputManager.addListener(actionListener, "8");
+ inputManager.addListener(actionListener, "-");
+
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");
+ keybindingsText.setText("Press N to toggle day/night fade (takes a moment) \n"
+ + "Press P to toggle tri-planar mode\n\n"
+ + "Press - for Final Render (disable debug view)\n"
+ + "Press 0 for Albedo debug view\n"
+ + "Press 1 for Normal Map debug view\n"
+ + "Press 2 for Roughness debug view\n"
+ + "Press 3 for Metallic debug view\n"
+ + "Press 4 for Ambient Occlusion (ao) debug view\n"
+ + "Press 5 for Emissive debug view\n"
+ + "Press 6 for Exposure debug view\n"
+ + "Press 7 for Alpha debug view\n"
+ + "Press 8 for Geoemtry Normals debug view\n");
+
getGuiNode().attachChild(keybindingsText);
- keybindingsText.move(new Vector3f(200, 120, 0));
+ keybindingsText.move(new Vector3f(5, cam.getHeight() * 0.995f, 0));
}
@Override
diff --git a/jme3-examples/src/main/java/jme3test/water/TestPostWater.java b/jme3-examples/src/main/java/jme3test/water/TestPostWater.java
index 384d88f7f9..7f6117bc0c 100644
--- a/jme3-examples/src/main/java/jme3test/water/TestPostWater.java
+++ b/jme3-examples/src/main/java/jme3test/water/TestPostWater.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2022 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -40,6 +40,7 @@
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.Trigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
@@ -55,7 +56,9 @@
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
+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;
@@ -66,18 +69,12 @@
import com.jme3.water.WaterFilter;
/**
- * test
- *
* @author normenhansen
*/
public class TestPostWater extends SimpleApplication {
- final private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f);
+ private final Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f);
private WaterFilter water;
- private AudioNode waves;
- final private LowPassFilter aboveWaterAudioFilter = new LowPassFilter(1, 1);
- final private Filter underWaterAudioFilter = new LowPassFilter(0.5f, 0.1f);
- private boolean useDryFilter = true;
public static void main(String[] args) {
TestPostWater app = new TestPostWater();
@@ -87,173 +84,154 @@ public static void main(String[] args) {
@Override
public void simpleInitApp() {
- setDisplayFps(false);
- setDisplayStatView(false);
-
Node mainScene = new Node("Main Scene");
rootNode.attachChild(mainScene);
+ configureCamera();
+ createSky(mainScene);
createTerrain(mainScene);
+ createLights(mainScene);
+ createWaterFilter();
+ setupPostFilters();
+ addAudioClip();
+ setupUI();
+ registerInputMappings();
+ }
+
+ private void configureCamera() {
+ flyCam.setMoveSpeed(50f);
+ cam.setLocation(new Vector3f(-370.31592f, 182.04016f, 196.81192f));
+ cam.setRotation(new Quaternion(0.015302252f, 0.9304095f, -0.039101653f, 0.3641086f));
+ cam.setFrustumFar(2000);
+ }
+
+ private void createLights(Node mainScene) {
DirectionalLight sun = new DirectionalLight();
sun.setDirection(lightDir);
- sun.setColor(ColorRGBA.White.clone().multLocal(1f));
mainScene.addLight(sun);
-
+
AmbientLight al = new AmbientLight();
al.setColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f));
mainScene.addLight(al);
-
- flyCam.setMoveSpeed(50);
+ }
- //cam.setLocation(new Vector3f(-700, 100, 300));
- //cam.setRotation(new Quaternion().fromAngleAxis(0.5f, Vector3f.UNIT_Z));
-// cam.setLocation(new Vector3f(-327.21957f, 61.6459f, 126.884346f));
-// cam.setRotation(new Quaternion(0.052168474f, 0.9443102f, -0.18395276f, 0.2678024f));
+ private void createSky(Node mainScene) {
+ Spatial sky = SkyFactory.createSky(assetManager,
+ "Scenes/Beach/FullskiesSunset0068.dds", EnvMapType.CubeMap);
+ sky.setShadowMode(ShadowMode.Off);
+ mainScene.attachChild(sky);
+ }
+ private void setupUI() {
+ setText(0, 50, "1 - Set Foam Texture to Foam.jpg");
+ setText(0, 80, "2 - Set Foam Texture to Foam2.jpg");
+ setText(0, 110, "3 - Set Foam Texture to Foam3.jpg");
+ setText(0, 140, "4 - Turn Dry Filter under water On/Off");
+ setText(0, 240, "PgUp - Larger Reflection Map");
+ setText(0, 270, "PgDn - Smaller Reflection Map");
+ }
- cam.setLocation(new Vector3f(-370.31592f, 182.04016f, 196.81192f));
- cam.setRotation(new Quaternion(0.015302252f, 0.9304095f, -0.039101653f, 0.3641086f));
+ private void setText(int x, int y, String text) {
+ BitmapText bmp = new BitmapText(guiFont);
+ bmp.setText(text);
+ bmp.setLocalTranslation(x, cam.getHeight() - y, 0);
+ bmp.setColor(ColorRGBA.Red);
+ guiNode.attachChild(bmp);
+ }
+ private void registerInputMappings() {
+ addMapping("foam1", new KeyTrigger(KeyInput.KEY_1));
+ addMapping("foam2", new KeyTrigger(KeyInput.KEY_2));
+ addMapping("foam3", new KeyTrigger(KeyInput.KEY_3));
+ addMapping("dryFilter", new KeyTrigger(KeyInput.KEY_4));
+ addMapping("upRM", new KeyTrigger(KeyInput.KEY_PGUP));
+ addMapping("downRM", new KeyTrigger(KeyInput.KEY_PGDN));
+ }
+ private void addMapping(String mappingName, Trigger... triggers) {
+ inputManager.addMapping(mappingName, triggers);
+ inputManager.addListener(actionListener, mappingName);
+ }
+ private final ActionListener actionListener = new ActionListener() {
+ @Override
+ public void onAction(String name, boolean isPressed, float tpf) {
+ if (!isPressed) return;
- Spatial sky = SkyFactory.createSky(assetManager,
- "Scenes/Beach/FullskiesSunset0068.dds", EnvMapType.CubeMap);
- sky.setLocalScale(350);
+ if (name.equals("foam1")) {
+ water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg"));
- mainScene.attachChild(sky);
- cam.setFrustumFar(4000);
+ } else if (name.equals("foam2")) {
+ water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg"));
- //Water Filter
- water = new WaterFilter(rootNode, lightDir);
- water.setWaterColor(new ColorRGBA().setAsSrgb(0.0078f, 0.3176f, 0.5f, 1.0f));
- water.setDeepWaterColor(new ColorRGBA().setAsSrgb(0.0039f, 0.00196f, 0.145f, 1.0f));
- water.setUnderWaterFogDistance(80);
- water.setWaterTransparency(0.12f);
- water.setFoamIntensity(0.4f);
- water.setFoamHardness(0.3f);
- water.setFoamExistence(new Vector3f(0.8f, 8f, 1f));
- water.setReflectionDisplace(50);
- water.setRefractionConstant(0.25f);
- water.setColorExtinction(new Vector3f(30, 50, 70));
- water.setCausticsIntensity(0.4f);
- water.setWaveScale(0.003f);
- water.setMaxAmplitude(2f);
- water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg"));
- water.setRefractionStrength(0.2f);
- water.setWaterHeight(initialWaterHeight);
-
- //Bloom Filter
- BloomFilter bloom = new BloomFilter();
+ } else if (name.equals("foam3")) {
+ water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam3.jpg"));
+
+ } else if (name.equals("upRM")) {
+ water.setReflectionMapSize(Math.min(water.getReflectionMapSize() * 2, 4096));
+ System.out.println("Reflection map size : " + water.getReflectionMapSize());
+
+ } else if (name.equals("downRM")) {
+ water.setReflectionMapSize(Math.max(water.getReflectionMapSize() / 2, 32));
+ System.out.println("Reflection map size : " + water.getReflectionMapSize());
+
+ } else if (name.equals("dryFilter")) {
+ useDryFilter = !useDryFilter;
+ }
+ }
+ };
+
+ private void setupPostFilters() {
+ BloomFilter bloom = new BloomFilter();
bloom.setExposurePower(55);
bloom.setBloomIntensity(1.0f);
-
- //Light Scattering Filter
+
LightScatteringFilter lsf = new LightScatteringFilter(lightDir.mult(-300));
- lsf.setLightDensity(0.5f);
-
- //Depth of field Filter
+ lsf.setLightDensity(0.5f);
+
DepthOfFieldFilter dof = new DepthOfFieldFilter();
dof.setFocusDistance(0);
dof.setFocusRange(100);
-
+
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
-
fpp.addFilter(water);
fpp.addFilter(bloom);
fpp.addFilter(dof);
fpp.addFilter(lsf);
fpp.addFilter(new FXAAFilter());
-
-// fpp.addFilter(new TranslucentBucketFilter());
+
int numSamples = getContext().getSettings().getSamples();
if (numSamples > 0) {
fpp.setNumSamples(numSamples);
}
-
-
- uw = cam.getLocation().y < waterHeight;
-
- waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg",
- DataType.Buffer);
- waves.setLooping(true);
- updateAudio();
- audioRenderer.playSource(waves);
- //
viewPort.addProcessor(fpp);
+ }
- setText(0, 50, "1 - Set Foam Texture to Foam.jpg");
- setText(0, 80, "2 - Set Foam Texture to Foam2.jpg");
- setText(0, 110, "3 - Set Foam Texture to Foam3.jpg");
- setText(0, 140, "4 - Turn Dry Filter under water On/Off");
- setText(0, 240, "PgUp - Larger Reflection Map");
- setText(0, 270, "PgDn - Smaller Reflection Map");
-
- inputManager.addListener(new ActionListener() {
- @Override
- public void onAction(String name, boolean isPressed, float tpf) {
- if (isPressed) {
- if (name.equals("foam1")) {
- water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg"));
- }
- if (name.equals("foam2")) {
- water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg"));
- }
- if (name.equals("foam3")) {
- water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam3.jpg"));
- }
-
- if (name.equals("upRM")) {
- water.setReflectionMapSize(Math.min(water.getReflectionMapSize() * 2, 4096));
- System.out.println("Reflection map size : " + water.getReflectionMapSize());
- }
- if (name.equals("downRM")) {
- water.setReflectionMapSize(Math.max(water.getReflectionMapSize() / 2, 32));
- System.out.println("Reflection map size : " + water.getReflectionMapSize());
- }
- if (name.equals("dryFilter")) {
- useDryFilter = !useDryFilter;
- }
- }
- }
- }, "foam1", "foam2", "foam3", "upRM", "downRM", "dryFilter");
- inputManager.addMapping("foam1", new KeyTrigger(KeyInput.KEY_1));
- inputManager.addMapping("foam2", new KeyTrigger(KeyInput.KEY_2));
- inputManager.addMapping("foam3", new KeyTrigger(KeyInput.KEY_3));
- inputManager.addMapping("dryFilter", new KeyTrigger(KeyInput.KEY_4));
- inputManager.addMapping("upRM", new KeyTrigger(KeyInput.KEY_PGUP));
- inputManager.addMapping("downRM", new KeyTrigger(KeyInput.KEY_PGDN));
+ private void createWaterFilter() {
+ //Water Filter
+ water = new WaterFilter(rootNode, lightDir);
+ water.setWaterColor(new ColorRGBA().setAsSrgb(0.0078f, 0.3176f, 0.5f, 1.0f));
+ water.setDeepWaterColor(new ColorRGBA().setAsSrgb(0.0039f, 0.00196f, 0.145f, 1.0f));
+ water.setUnderWaterFogDistance(80);
+ water.setWaterTransparency(0.12f);
+ water.setFoamIntensity(0.4f);
+ water.setFoamHardness(0.3f);
+ water.setFoamExistence(new Vector3f(0.8f, 8f, 1f));
+ water.setReflectionDisplace(50);
+ water.setRefractionConstant(0.25f);
+ water.setColorExtinction(new Vector3f(30, 50, 70));
+ water.setCausticsIntensity(0.4f);
+ water.setWaveScale(0.003f);
+ water.setMaxAmplitude(2f);
+ water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg"));
+ water.setRefractionStrength(0.2f);
+ water.setWaterHeight(initialWaterHeight);
}
- private void createTerrain(Node rootNode) {
- Material matRock = new Material(assetManager,
- "Common/MatDefs/Terrain/TerrainLighting.j3md");
- matRock.setBoolean("useTriPlanarMapping", false);
- matRock.setBoolean("WardIso", true);
- matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
- Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
- Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
- grass.setWrap(WrapMode.Repeat);
- matRock.setTexture("DiffuseMap", grass);
- matRock.setFloat("DiffuseMap_0_scale", 64);
- Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
- dirt.setWrap(WrapMode.Repeat);
- matRock.setTexture("DiffuseMap_1", dirt);
- matRock.setFloat("DiffuseMap_1_scale", 16);
- Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
- rock.setWrap(WrapMode.Repeat);
- matRock.setTexture("DiffuseMap_2", rock);
- matRock.setFloat("DiffuseMap_2_scale", 128);
- Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
- normalMap0.setWrap(WrapMode.Repeat);
- Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
- normalMap1.setWrap(WrapMode.Repeat);
- Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
- normalMap2.setWrap(WrapMode.Repeat);
- matRock.setTexture("NormalMap", normalMap0);
- matRock.setTexture("NormalMap_1", normalMap1);
- matRock.setTexture("NormalMap_2", normalMap2);
+ private void createTerrain(Node mainScene) {
+ Material matRock = createTerrainMaterial();
+ Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
AbstractHeightMap heightmap = null;
try {
heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
@@ -261,51 +239,86 @@ private void createTerrain(Node rootNode) {
} catch (Exception e) {
e.printStackTrace();
}
- TerrainQuad terrain
- = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
+
+ int patchSize = 64;
+ int totalSize = 512;
+ TerrainQuad terrain = new TerrainQuad("terrain", patchSize + 1, totalSize + 1, heightmap.getHeightMap());
+ TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
+ control.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 2.7f)); // patch size, and a multiplier
+ terrain.addControl(control);
terrain.setMaterial(matRock);
- terrain.setLocalScale(new Vector3f(5, 5, 5));
+
terrain.setLocalTranslation(new Vector3f(0, -30, 0));
- terrain.setLocked(false); // unlock it so we can edit the height
+ terrain.setLocalScale(new Vector3f(5, 5, 5));
terrain.setShadowMode(ShadowMode.Receive);
- rootNode.attachChild(terrain);
+ mainScene.attachChild(terrain);
+ }
+
+ private Material createTerrainMaterial() {
+ Material matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+ matRock.setBoolean("useTriPlanarMapping", false);
+ matRock.setBoolean("WardIso", true);
+ matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
+
+ setTexture("Textures/Terrain/splat/grass.jpg", matRock, "DiffuseMap");
+ setTexture("Textures/Terrain/splat/dirt.jpg", matRock, "DiffuseMap_1");
+ setTexture("Textures/Terrain/splat/road.jpg", matRock, "DiffuseMap_2");
+ matRock.setFloat("DiffuseMap_0_scale", 64);
+ matRock.setFloat("DiffuseMap_1_scale", 16);
+ matRock.setFloat("DiffuseMap_2_scale", 128);
+
+ setTexture("Textures/Terrain/splat/grass_normal.jpg", matRock, "NormalMap");
+ setTexture("Textures/Terrain/splat/dirt_normal.png", matRock, "NormalMap_1");
+ setTexture("Textures/Terrain/splat/road_normal.png", matRock, "NormalMap_2");
+ return matRock;
}
- //This part is to emulate tides, slightly varying the height of the water plane
+
+ private void setTexture(String texture, Material mat, String param) {
+ Texture tex = assetManager.loadTexture(texture);
+ tex.setWrap(WrapMode.Repeat);
+ mat.setTexture(param, tex);
+ }
+
+ // This part is to emulate tides, slightly varying the height of the water plane
private float time = 0.0f;
private float waterHeight = 0.0f;
- final private float initialWaterHeight = 90f;//0.8f;
- private boolean uw = false;
+ private final float initialWaterHeight = 90f;
+ private boolean underWater = false;
+
+ private AudioNode waves;
+ private final LowPassFilter aboveWaterAudioFilter = new LowPassFilter(1, 1);
+ private final LowPassFilter underWaterAudioFilter = new LowPassFilter(0.5f, 0.1f);
+ private boolean useDryFilter = true;
@Override
public void simpleUpdate(float tpf) {
- super.simpleUpdate(tpf);
- // box.updateGeometricState();
time += tpf;
waterHeight = (float) Math.cos(((time * 0.6f) % FastMath.TWO_PI)) * 1.5f;
water.setWaterHeight(initialWaterHeight + waterHeight);
- uw = water.isUnderWater();
+ underWater = water.isUnderWater();
updateAudio();
}
-
- protected void setText(int x, int y, String text) {
- BitmapText txt2 = new BitmapText(guiFont);
- txt2.setText(text);
- txt2.setLocalTranslation(x, cam.getHeight() - y, 0);
- txt2.setColor(ColorRGBA.Red);
- guiNode.attachChild(txt2);
+
+ private void addAudioClip() {
+ underWater = cam.getLocation().y < waterHeight;
+
+ waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", DataType.Buffer);
+ waves.setLooping(true);
+ updateAudio();
+ waves.play();
}
/**
* Update the audio settings (dry filter and reverb)
- * based on boolean fields ({@code uw} and {@code useDryFilter}).
+ * based on boolean fields ({@code underWater} and {@code useDryFilter}).
*/
- protected void updateAudio() {
+ private void updateAudio() {
Filter newDryFilter;
if (!useDryFilter) {
newDryFilter = null;
- } else if (uw) {
+ } else if (underWater) {
newDryFilter = underWaterAudioFilter;
} else {
newDryFilter = aboveWaterAudioFilter;
@@ -316,11 +329,11 @@ protected void updateAudio() {
waves.setDryFilter(newDryFilter);
}
- boolean newReverbEnabled = !uw;
+ boolean newReverbEnabled = !underWater;
boolean oldReverbEnabled = waves.isReverbEnabled();
if (oldReverbEnabled != newReverbEnabled) {
System.out.println("reverb enabled : " + newReverbEnabled);
waves.setReverbEnabled(newReverbEnabled);
}
}
-}
\ No newline at end of file
+}
diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java
index cdc754f3a0..6fa525f355 100644
--- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java
+++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2024 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -677,7 +677,7 @@ protected String getPrintContextInitInfo(GLData glData) {
sb.append('\n')
.append(" * Red Size: ").append(glData.redSize);
sb.append('\n')
- .append(" * Rreen Size: ").append(glData.greenSize);
+ .append(" * Green Size: ").append(glData.greenSize);
sb.append('\n')
.append(" * Blue Size: ").append(glData.blueSize);
sb.append('\n')
diff --git a/jme3-screenshot-tests/README.md b/jme3-screenshot-tests/README.md
index 3945ac20cb..9215123bb9 100644
--- a/jme3-screenshot-tests/README.md
+++ b/jme3-screenshot-tests/README.md
@@ -1,7 +1,8 @@
# jme3-screenshot-tests
-This module contains tests that compare screenshots of the JME3 test applications to reference images. The tests are run using
-the following command:
+This module contains tests that compare screenshots of the JME3 test applications to reference images. Think of these like visual unit tests
+
+The tests are run using the following command:
```
./gradlew :jme3-screenshot-test:screenshotTest
diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestDriver.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestDriver.java
index a0ac646229..6e8bbe20f3 100644
--- a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestDriver.java
+++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestDriver.java
@@ -72,7 +72,7 @@
*/
public class TestDriver extends BaseAppState{
- public static final String IMAGES_ARE_DIFFERENT = "Images are different.";
+ public static final String IMAGES_ARE_DIFFERENT = "Images are different. (If you are running the test locally this is expected, images only reproducible on github CI infrastructure)";
public static final String IMAGES_ARE_DIFFERENT_SIZES = "Images are different sizes.";
@@ -197,7 +197,7 @@ private Integer extractNumber(Path path){
});
if(imageFiles.isEmpty()){
- fail("No screenshot found in the temporary directory.");
+ fail("No screenshot found in the temporary directory. Did the application crash?");
}
if(imageFiles.size() != framesToTakeScreenshotsOn.size()){
fail("Not all screenshots were taken, expected " + framesToTakeScreenshotsOn.size() + " but got " + imageFiles.size());
@@ -218,7 +218,7 @@ private Integer extractNumber(Path path){
try{
Path savedImage = saveGeneratedImageToChangedImages(generatedImage, thisFrameBaseImageFileName);
attachImage("New image:", thisFrameBaseImageFileName + ".png", savedImage);
- String message = "Expected image not found, is this a new test? If so collect the new image from the step artefacts";
+ String message = "Expected image not found, is this a new test? If so collect the new image from the step artefacts (on github). If running locally you can see them at build/changed-images but those should not be committed";
if(failureMessage==null){ //only want the first thing to go wrong as the junit test fail reason
failureMessage = message;
}
diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java
new file mode 100644
index 0000000000..50435b1218
--- /dev/null
+++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.jmonkeyengine.screenshottests.animation;
+
+import com.jme3.anim.SkinningControl;
+import com.jme3.anim.util.AnimMigrationUtils;
+import com.jme3.animation.SkeletonControl;
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.asset.AssetManager;
+import com.jme3.light.AmbientLight;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.VertexBuffer;
+import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Screenshot test for JMonkeyEngine issue #2076: software skinning requires vertex
+ * normals.
+ *
+ * If the issue is resolved, 2 copies of the Jaime model will be rendered in the screenshot.
+ *
+ *
If the issue is present, then the application will immediately crash,
+ * typically with a {@code NullPointerException}.
+ *
+ * @author Stephen Gold (original test)
+ * @author Richard Tingle (screenshot test adaptation)
+ */
+public class TestIssue2076 extends ScreenshotTestBase {
+
+ /**
+ * This test creates a scene with two Jaime models, one using the old animation system
+ * and one using the new animation system, both with software skinning and no vertex normals.
+ */
+ @Test
+ public void testIssue2076() {
+ screenshotTest(new BaseAppState() {
+ @Override
+ protected void initialize(Application app) {
+ SimpleApplication simpleApplication = (SimpleApplication) app;
+ Node rootNode = simpleApplication.getRootNode();
+ AssetManager assetManager = simpleApplication.getAssetManager();
+
+ // Add ambient light
+ AmbientLight ambientLight = new AmbientLight();
+ ambientLight.setColor(new ColorRGBA(1f, 1f, 1f, 1f));
+ rootNode.addLight(ambientLight);
+
+ /*
+ * The original Jaime model was chosen for testing because it includes
+ * tangent buffers (needed to trigger issue #2076) and uses the old
+ * animation system (so it can be easily used to test both systems).
+ */
+ String assetPath = "Models/Jaime/Jaime.j3o";
+
+ // Test old animation system
+ Node oldJaime = (Node) assetManager.loadModel(assetPath);
+ rootNode.attachChild(oldJaime);
+ oldJaime.setLocalTranslation(-1f, 0f, 0f);
+
+ // Enable software skinning
+ SkeletonControl skeletonControl = oldJaime.getControl(SkeletonControl.class);
+ skeletonControl.setHardwareSkinningPreferred(false);
+
+ // Remove its vertex normals
+ Geometry oldGeometry = (Geometry) oldJaime.getChild(0);
+ Mesh oldMesh = oldGeometry.getMesh();
+ oldMesh.clearBuffer(VertexBuffer.Type.Normal);
+ oldMesh.clearBuffer(VertexBuffer.Type.BindPoseNormal);
+
+ // Test new animation system
+ Node newJaime = (Node) assetManager.loadModel(assetPath);
+ AnimMigrationUtils.migrate(newJaime);
+ rootNode.attachChild(newJaime);
+ newJaime.setLocalTranslation(1f, 0f, 0f);
+
+ // Enable software skinning
+ SkinningControl skinningControl = newJaime.getControl(SkinningControl.class);
+ skinningControl.setHardwareSkinningPreferred(false);
+
+ // Remove its vertex normals
+ Geometry newGeometry = (Geometry) newJaime.getChild(0);
+ Mesh newMesh = newGeometry.getMesh();
+ newMesh.clearBuffer(VertexBuffer.Type.Normal);
+ newMesh.clearBuffer(VertexBuffer.Type.BindPoseNormal);
+
+ // Position the camera to see both models
+ simpleApplication.getCamera().setLocation(new Vector3f(0f, 0f, 5f));
+ }
+
+ @Override
+ protected void cleanup(Application app) {
+ }
+
+ @Override
+ protected void onEnable() {
+ }
+
+ @Override
+ protected void onDisable() {
+ }
+
+ @Override
+ public void update(float tpf) {
+ super.update(tpf);
+ }
+ }).run();
+ }
+}
\ No newline at end of file
diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java
new file mode 100644
index 0000000000..28a5e042d2
--- /dev/null
+++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.jmonkeyengine.screenshottests.animation;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.asset.AssetManager;
+import com.jme3.cinematic.MotionPath;
+import com.jme3.cinematic.MotionPathListener;
+import com.jme3.cinematic.events.MotionEvent;
+import com.jme3.font.BitmapText;
+import com.jme3.input.ChaseCamera;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Screenshot test for the MotionPath functionality.
+ *
+ *
This test creates a teapot model that follows a predefined path with several waypoints.
+ * The animation is automatically started and screenshots are taken at frames 10 and 60
+ * to capture the teapot at different positions along the path.
+ *
+ * @author Richard Tingle (screenshot test adaptation)
+ */
+public class TestMotionPath extends ScreenshotTestBase {
+
+ /**
+ * This test creates a scene with a teapot following a motion path.
+ */
+ @Test
+ public void testMotionPath() {
+ screenshotTest(new BaseAppState() {
+ private Spatial teapot;
+ private MotionPath path;
+ private MotionEvent motionControl;
+ private BitmapText wayPointsText;
+
+ @Override
+ protected void initialize(Application app) {
+ SimpleApplication simpleApplication = (SimpleApplication) app;
+ Node rootNode = simpleApplication.getRootNode();
+ Node guiNode = simpleApplication.getGuiNode();
+ AssetManager assetManager = simpleApplication.getAssetManager();
+
+ // Set camera position
+ app.getCamera().setLocation(new Vector3f(8.4399185f, 11.189463f, 14.267577f));
+
+ // Create the scene
+ createScene(rootNode, assetManager);
+
+ // Create the motion path
+ path = new MotionPath();
+ path.addWayPoint(new Vector3f(10, 3, 0));
+ path.addWayPoint(new Vector3f(10, 3, 10));
+ path.addWayPoint(new Vector3f(-40, 3, 10));
+ path.addWayPoint(new Vector3f(-40, 3, 0));
+ path.addWayPoint(new Vector3f(-40, 8, 0));
+ path.addWayPoint(new Vector3f(10, 8, 0));
+ path.addWayPoint(new Vector3f(10, 8, 10));
+ path.addWayPoint(new Vector3f(15, 8, 10));
+ path.enableDebugShape(assetManager, rootNode);
+
+ // Create the motion event
+ motionControl = new MotionEvent(teapot, path);
+ motionControl.setDirectionType(MotionEvent.Direction.PathAndRotation);
+ motionControl.setRotation(new Quaternion().fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y));
+ motionControl.setInitialDuration(10f);
+ motionControl.setSpeed(2f);
+
+ // Create text for waypoint notifications
+ wayPointsText = new BitmapText(assetManager.loadFont("Interface/Fonts/Default.fnt"));
+ wayPointsText.setSize(wayPointsText.getFont().getCharSet().getRenderedSize());
+ guiNode.attachChild(wayPointsText);
+
+ // Add listener for waypoint events
+ path.addListener(new MotionPathListener() {
+ @Override
+ public void onWayPointReach(MotionEvent control, int wayPointIndex) {
+ if (path.getNbWayPoints() == wayPointIndex + 1) {
+ wayPointsText.setText(control.getSpatial().getName() + " Finished!!! ");
+ } else {
+ wayPointsText.setText(control.getSpatial().getName() + " Reached way point " + wayPointIndex);
+ }
+ wayPointsText.setLocalTranslation(
+ (app.getCamera().getWidth() - wayPointsText.getLineWidth()) / 2,
+ app.getCamera().getHeight(),
+ 0);
+ }
+ });
+
+ // note that the ChaseCamera is self-initialising, so just creating this object attaches it
+ new ChaseCamera(getApplication().getCamera(), teapot);
+
+ // Start the animation automatically
+ motionControl.play();
+ }
+
+ private void createScene(Node rootNode, AssetManager assetManager) {
+ // Create materials
+ Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+ mat.setFloat("Shininess", 1f);
+ mat.setBoolean("UseMaterialColors", true);
+ mat.setColor("Ambient", ColorRGBA.Black);
+ mat.setColor("Diffuse", ColorRGBA.DarkGray);
+ mat.setColor("Specular", ColorRGBA.White.mult(0.6f));
+
+ Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+ matSoil.setBoolean("UseMaterialColors", true);
+ matSoil.setColor("Ambient", ColorRGBA.Black);
+ matSoil.setColor("Diffuse", ColorRGBA.Black);
+ matSoil.setColor("Specular", ColorRGBA.Black);
+
+ // Create teapot
+ teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+ teapot.setName("Teapot");
+ teapot.setLocalScale(3);
+ teapot.setMaterial(mat);
+ rootNode.attachChild(teapot);
+
+ // Create ground
+ Geometry soil = new Geometry("soil", new Box(50, 1, 50));
+ soil.setLocalTranslation(0, -1, 0);
+ soil.setMaterial(matSoil);
+ rootNode.attachChild(soil);
+
+ // Add light
+ DirectionalLight light = new DirectionalLight();
+ light.setDirection(new Vector3f(0, -1, 0).normalizeLocal());
+ light.setColor(ColorRGBA.White.mult(1.5f));
+ rootNode.addLight(light);
+ }
+
+ @Override
+ protected void cleanup(Application app) {
+ }
+
+ @Override
+ protected void onEnable() {
+ }
+
+ @Override
+ protected void onDisable() {
+ }
+ })
+ .setFramesToTakeScreenshotsOn(10, 60)
+ .run();
+ }
+}
\ No newline at end of file
diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRLighting.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRLighting.java
new file mode 100644
index 0000000000..0cbd19da24
--- /dev/null
+++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRLighting.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.jmonkeyengine.screenshottests.light.pbr;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.EnvironmentCamera;
+import com.jme3.environment.FastLightProbeFactory;
+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.post.FilterPostProcessor;
+import com.jme3.post.filters.ToneMapFilter;
+import com.jme3.renderer.Camera;
+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;
+import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+/**
+ * Screenshot tests for PBR lighting.
+ *
+ * @author nehon - original test
+ * @author Richard Tingle (aka richtea) - screenshot test adaptation
+ *
+ */
+public class TestPBRLighting extends ScreenshotTestBase {
+
+ private static Stream testParameters() {
+ return Stream.of(
+ Arguments.of("LowRoughness", 0.1f, false),
+ Arguments.of("HighRoughness", 1.0f, false),
+ Arguments.of("DefaultDirectionalLight", 0.5f, false),
+ Arguments.of("UpdatedDirectionalLight", 0.5f, true)
+ );
+ }
+
+ /**
+ * Test PBR lighting with different parameters
+ *
+ * @param testName The name of the test (used for screenshot filename)
+ * @param roughness The roughness value to use
+ * @param updateLight Whether to update the directional light to match camera direction
+ */
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("testParameters")
+ public void testPBRLighting(String testName, float roughness, boolean updateLight, TestInfo testInfo) {
+
+ if(!testInfo.getTestClass().isPresent() || !testInfo.getTestMethod().isPresent()) {
+ throw new RuntimeException("Test preconditions not met");
+ }
+
+ String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName;
+
+ screenshotTest(new BaseAppState() {
+ private static final int RESOLUTION = 256;
+
+ private Node modelNode;
+ private int frame = 0;
+
+ @Override
+ protected void initialize(Application app) {
+ Camera cam = app.getCamera();
+ cam.setLocation(new Vector3f(18, 10, 0));
+ cam.lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
+
+ AssetManager assetManager = app.getAssetManager();
+ assetManager.registerLoader(KTXLoader.class, "ktx");
+
+ app.getViewPort().setBackgroundColor(ColorRGBA.White);
+
+ modelNode = new Node("modelNode");
+ Geometry model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o");
+ MikktspaceTangentGenerator.generate(model);
+ modelNode.attachChild(model);
+
+ DirectionalLight dl = new DirectionalLight();
+ dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+ SimpleApplication simpleApp = (SimpleApplication) app;
+ simpleApp.getRootNode().addLight(dl);
+ dl.setColor(ColorRGBA.White);
+
+ // If we need to update the light direction to match camera
+ if (updateLight) {
+ dl.setDirection(app.getCamera().getDirection().normalize());
+ }
+
+ simpleApp.getRootNode().attachChild(modelNode);
+
+ FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+ int numSamples = app.getContext().getSettings().getSamples();
+ if (numSamples > 0) {
+ fpp.setNumSamples(numSamples);
+ }
+
+ fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f)));
+ app.getViewPort().addProcessor(fpp);
+
+ Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
+ simpleApp.getRootNode().attachChild(sky);
+
+ Material pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
+ pbrMat.setFloat("Roughness", roughness);
+ model.setMaterial(pbrMat);
+
+ // Set up environment camera
+ EnvironmentCamera envCam = new EnvironmentCamera(RESOLUTION, new Vector3f(0, 3f, 0));
+ app.getStateManager().attach(envCam);
+ }
+
+ @Override
+ protected void cleanup(Application app) {}
+
+ @Override
+ protected void onEnable() {}
+
+ @Override
+ protected void onDisable() {}
+
+ @Override
+ public void update(float tpf) {
+ frame++;
+
+ if (frame == 2) {
+ modelNode.removeFromParent();
+ LightProbe probe;
+
+ SimpleApplication simpleApp = (SimpleApplication) getApplication();
+ probe = FastLightProbeFactory.makeProbe(simpleApp.getRenderManager(),
+ simpleApp.getAssetManager(),
+ RESOLUTION,
+ Vector3f.ZERO,
+ 1f,
+ 1000f,
+ simpleApp.getRootNode());
+
+ probe.getArea().setRadius(100);
+ simpleApp.getRootNode().addLight(probe);
+ }
+
+ if (frame > 10 && modelNode.getParent() == null) {
+ SimpleApplication simpleApp = (SimpleApplication) getApplication();
+ simpleApp.getRootNode().attachChild(modelNode);
+ }
+ }
+ }).setBaseImageFileName(imageName)
+ .setFramesToTakeScreenshotsOn(12)
+ .run();
+ }
+}
diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSimple.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSimple.java
new file mode 100644
index 0000000000..70220f7eef
--- /dev/null
+++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSimple.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.jmonkeyengine.screenshottests.light.pbr;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.EnvironmentProbeControl;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
+import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+/**
+ * A simpler PBR example that uses EnvironmentProbeControl to bake the environment
+ *
+ * @author Richard Tingle (aka richtea) - screenshot test adaptation
+ */
+public class TestPBRSimple extends ScreenshotTestBase {
+
+ private static Stream testParameters() {
+ return Stream.of(
+ Arguments.of("WithRealtimeBaking", true),
+ Arguments.of("WithoutRealtimeBaking", false)
+ );
+ }
+
+ /**
+ * Test PBR simple with different parameters
+ *
+ * @param testName The name of the test (used for screenshot filename)
+ * @param realtimeBaking Whether to use realtime baking
+ */
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("testParameters")
+ public void testPBRSimple(String testName, boolean realtimeBaking, TestInfo testInfo) {
+ if(!testInfo.getTestClass().isPresent() || !testInfo.getTestMethod().isPresent()) {
+ throw new RuntimeException("Test preconditions not met");
+ }
+
+ String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName;
+
+ screenshotTest(new BaseAppState() {
+ private int frame = 0;
+
+ @Override
+ protected void initialize(Application app) {
+ Camera cam = app.getCamera();
+ cam.setLocation(new Vector3f(18, 10, 0));
+ cam.lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y);
+
+ AssetManager assetManager = app.getAssetManager();
+ SimpleApplication simpleApp = (SimpleApplication) app;
+
+ // Create the tank model
+ Geometry model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o");
+ MikktspaceTangentGenerator.generate(model);
+
+ Material pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
+ model.setMaterial(pbrMat);
+ simpleApp.getRootNode().attachChild(model);
+
+ // Create sky
+ Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
+ simpleApp.getRootNode().attachChild(sky);
+
+ // Create baker control
+ EnvironmentProbeControl envProbe = new EnvironmentProbeControl(assetManager, 256);
+ simpleApp.getRootNode().addControl(envProbe);
+
+ // Tag the sky, only the tagged spatials will be rendered in the env map
+ envProbe.tag(sky);
+ }
+
+ @Override
+ protected void cleanup(Application app) {}
+
+ @Override
+ protected void onEnable() {}
+
+ @Override
+ protected void onDisable() {}
+
+ @Override
+ public void update(float tpf) {
+ if (realtimeBaking) {
+ frame++;
+ if (frame == 2) {
+ SimpleApplication simpleApp = (SimpleApplication) getApplication();
+ simpleApp.getRootNode().getControl(EnvironmentProbeControl.class).rebake();
+ }
+ }
+ }
+ }).setBaseImageFileName(imageName)
+ .setFramesToTakeScreenshotsOn(10)
+ .run();
+ }
+}
\ No newline at end of file
diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java
new file mode 100644
index 0000000000..0e4e53df54
--- /dev/null
+++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.jmonkeyengine.screenshottests.material;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.asset.AssetManager;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
+import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Screenshot test for the SimpleBumps material test.
+ *
+ * This test creates a quad with a bump map material and a point light that orbits around it.
+ * The light's position is represented by a small red sphere. Screenshots are taken at frames 10 and 60
+ * to capture the light at different positions in its orbit.
+ *
+ * @author Richard Tingle (screenshot test adaptation)
+ */
+public class TestSimpleBumps extends ScreenshotTestBase {
+
+ /**
+ * This test creates a scene with a bump-mapped quad and an orbiting light.
+ */
+ @Test
+ public void testSimpleBumps() {
+ screenshotTest(new BaseAppState() {
+ private float angle;
+ private PointLight pl;
+ private Spatial lightMdl;
+
+ @Override
+ protected void initialize(Application app) {
+ SimpleApplication simpleApplication = (SimpleApplication) app;
+ Node rootNode = simpleApplication.getRootNode();
+ AssetManager assetManager = simpleApplication.getAssetManager();
+
+ // Create quad with bump map material
+ Quad quadMesh = new Quad(1, 1);
+ Geometry sphere = new Geometry("Rock Ball", quadMesh);
+ Material mat = assetManager.loadMaterial("Textures/BumpMapTest/SimpleBump.j3m");
+ sphere.setMaterial(mat);
+ MikktspaceTangentGenerator.generate(sphere);
+ rootNode.attachChild(sphere);
+
+ // Create light representation
+ lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f));
+ lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m"));
+ rootNode.attachChild(lightMdl);
+
+ // Create point light
+ pl = new PointLight();
+ pl.setColor(ColorRGBA.White);
+ pl.setPosition(new Vector3f(0f, 0f, 4f));
+ rootNode.addLight(pl);
+ }
+
+ @Override
+ protected void cleanup(Application app) {
+ }
+
+ @Override
+ protected void onEnable() {
+ }
+
+ @Override
+ protected void onDisable() {
+ }
+
+ @Override
+ public void update(float tpf) {
+ super.update(tpf);
+
+ angle += tpf * 2f;
+ angle %= FastMath.TWO_PI;
+
+ pl.setPosition(new Vector3f(FastMath.cos(angle) * 4f, 0.5f, FastMath.sin(angle) * 4f));
+ lightMdl.setLocalTranslation(pl.getPosition());
+ }
+ })
+ .setFramesToTakeScreenshotsOn(10, 60)
+ .run();
+ }
+}
\ No newline at end of file
diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/scene/instancing/TestInstanceNodeWithPbr.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/scene/instancing/TestInstanceNodeWithPbr.java
new file mode 100644
index 0000000000..9069fb4464
--- /dev/null
+++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/scene/instancing/TestInstanceNodeWithPbr.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.jmonkeyengine.screenshottests.scene.instancing;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.font.BitmapText;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.instancing.InstancedNode;
+import com.jme3.scene.shape.Box;
+import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.Test;
+
+import java.util.Locale;
+
+/**
+ * This test specifically validates the corrected PBR rendering when combined
+ * with instancing, as addressed in issue #2435.
+ *
+ *
+ * It creates an InstancedNode with a PBR-materialized Box to ensure the fix in
+ * PBRLighting.vert correctly handles world position calculations for instanced geometry.
+ *
+ *
+ * @author Ryan McDonough - original test
+ * @author Richard Tingle (aka richtea) - screenshot test adaptation
+ */
+public class TestInstanceNodeWithPbr extends ScreenshotTestBase {
+
+ @Test
+ public void testInstanceNodeWithPbr() {
+ screenshotTest(
+ new BaseAppState() {
+ private Geometry box;
+ private float pos = -5;
+ private float vel = 50;
+ private BitmapText bmp;
+
+ @Override
+ protected void initialize(Application app) {
+ SimpleApplication simpleApp = (SimpleApplication) app;
+
+ app.getCamera().setLocation(Vector3f.UNIT_XYZ.mult(12));
+ app.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+
+ bmp = new BitmapText(app.getAssetManager().loadFont("Interface/Fonts/Default.fnt"));
+ bmp.setText("");
+ bmp.setLocalTranslation(10, app.getContext().getSettings().getHeight() - 20, 0);
+ bmp.setColor(ColorRGBA.Red);
+ simpleApp.getGuiNode().attachChild(bmp);
+
+ InstancedNode instancedNode = new InstancedNode("InstancedNode");
+ simpleApp.getRootNode().attachChild(instancedNode);
+
+ Box mesh = new Box(0.5f, 0.5f, 0.5f);
+ box = new Geometry("Box", mesh);
+ Material pbrMaterial = createPbrMaterial(app, ColorRGBA.Red);
+ box.setMaterial(pbrMaterial);
+
+ instancedNode.attachChild(box);
+ instancedNode.instance();
+
+ DirectionalLight light = new DirectionalLight();
+ light.setDirection(new Vector3f(-1, -2, -3).normalizeLocal());
+ simpleApp.getRootNode().addLight(light);
+ }
+
+ private Material createPbrMaterial(Application app, ColorRGBA color) {
+ Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/PBRLighting.j3md");
+ mat.setColor("BaseColor", color);
+ mat.setFloat("Roughness", 0.8f);
+ mat.setFloat("Metallic", 0.1f);
+ mat.setBoolean("UseInstancing", true);
+ return mat;
+ }
+
+ @Override
+ public void update(float tpf) {
+ pos += tpf * vel;
+ box.setLocalTranslation(pos, 0f, 0f);
+
+ bmp.setText(String.format(Locale.ENGLISH, "BoxPosition: (%.2f, %.1f, %.1f)", pos, 0f, 0f));
+ }
+
+ @Override
+ protected void cleanup(Application app) {}
+
+ @Override
+ protected void onEnable() {}
+
+ @Override
+ protected void onDisable() { }
+ }
+ )
+ .setFramesToTakeScreenshotsOn(1, 10)
+ .run();
+ }
+}
\ No newline at end of file
diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrain.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrain.java
new file mode 100644
index 0000000000..37f8063300
--- /dev/null
+++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrain.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.jmonkeyengine.screenshottests.terrain;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.TextureKey;
+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.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 org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+/**
+ * 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.
+ *
+ * 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
+ *
+ * @author yaRnMcDonuts (Original manual test)
+ * @author Richard Tingle (aka richtea) - screenshot test adaptation
+ */
+@SuppressWarnings("FieldCanBeLocal")
+public class TestPBRTerrain extends ScreenshotTestBase {
+
+ private static Stream testParameters() {
+ return Stream.of(
+ Arguments.of("FinalRender", 0),
+ Arguments.of("NormalMap", 1),
+ Arguments.of("RoughnessMap", 2),
+ Arguments.of("MetallicMap", 3),
+ Arguments.of("GeometryNormals", 8)
+ );
+ }
+
+ /**
+ * Test PBR terrain with different debug modes
+ *
+ * @param testName The name of the test (used for screenshot filename)
+ * @param debugMode The debug mode to use
+ */
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("testParameters")
+ public void testPBRTerrain(String testName, int debugMode, TestInfo testInfo) {
+
+ if(!testInfo.getTestClass().isPresent() || !testInfo.getTestMethod().isPresent()) {
+ throw new RuntimeException("Test preconditions not met");
+ }
+
+ String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName;
+
+ screenshotTest(new BaseAppState() {
+ private TerrainQuad terrain;
+ private Material matTerrain;
+
+ 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;
+
+ @Override
+ protected void initialize(Application app) {
+ SimpleApplication simpleApp = (SimpleApplication) app;
+ AssetManager assetManager = app.getAssetManager();
+
+ setUpTerrain(simpleApp, assetManager);
+ setUpTerrainMaterial(assetManager);
+ setUpLights(simpleApp, assetManager);
+ setUpCamera(app);
+
+ // Set debug mode
+ matTerrain.setInt("DebugValuesMode", debugMode);
+ }
+
+ private void setUpTerrainMaterial(AssetManager assetManager) {
+ // 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"));
+
+ // DIRT texture
+ 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);
+
+ // 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);
+
+ // 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);
+
+ // TILES texture
+ 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 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_6", normalMapGravel);
+
+ terrain.setMaterial(matTerrain);
+ }
+
+ private void setUpTerrain(SimpleApplication simpleApp, AssetManager assetManager) {
+ // 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;
+ try {
+ heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f);
+ heightmap.load();
+ heightmap.smooth(0.9f, 1);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ terrain = new TerrainQuad("terrain", patchSize + 1, terrainSize + 1, heightmap.getHeightMap());
+ TerrainLodControl control = new TerrainLodControl(terrain, getApplication().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);
+ simpleApp.getRootNode().attachChild(terrain);
+ }
+
+ private void setUpLights(SimpleApplication simpleApp, AssetManager assetManager) {
+ 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));
+ simpleApp.getRootNode().addLight(probe);
+
+ DirectionalLight directionalLight = new DirectionalLight();
+ directionalLight.setDirection((new Vector3f(-0.3f, -0.5f, -0.3f)).normalize());
+ directionalLight.setColor(ColorRGBA.White);
+ simpleApp.getRootNode().addLight(directionalLight);
+
+ AmbientLight ambientLight = new AmbientLight();
+ ambientLight.setColor(ColorRGBA.White);
+ simpleApp.getRootNode().addLight(ambientLight);
+ }
+
+ private void setUpCamera(Application app) {
+ app.getCamera().setLocation(new Vector3f(0, 10, -10));
+ app.getCamera().lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y);
+ }
+
+ @Override
+ protected void cleanup(Application app) {}
+
+ @Override
+ protected void onEnable() {}
+
+ @Override
+ protected void onDisable() {}
+
+ }).setBaseImageFileName(imageName)
+ .setFramesToTakeScreenshotsOn(5)
+ .run();
+ }
+}
diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrainAdvanced.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrainAdvanced.java
new file mode 100644
index 0000000000..a1f5830896
--- /dev/null
+++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrainAdvanced.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (c) 2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.jmonkeyengine.screenshottests.terrain;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.TextureKey;
+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.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.TextureArray;
+import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+
+/**
+ * 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.
+
+ * 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
+ *
+ * @author yaRnMcDonuts - original test
+ * @author Richard Tingle (aka richtea) - screenshot test adaptation
+ */
+@SuppressWarnings("FieldCanBeLocal")
+public class TestPBRTerrainAdvanced extends ScreenshotTestBase {
+
+ private static Stream testParameters() {
+ return Stream.of(
+ Arguments.of("FinalRender", 0),
+ Arguments.of("AmbientOcclusion", 4),
+ Arguments.of("Emissive", 5)
+ );
+ }
+
+ /**
+ * Test advanced PBR terrain with different debug modes
+ *
+ * @param testName The name of the test (used for screenshot filename)
+ * @param debugMode The debug mode to use
+ */
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("testParameters")
+ public void testPBRTerrainAdvanced(String testName, int debugMode, TestInfo testInfo) {
+ if(!testInfo.getTestClass().isPresent() || !testInfo.getTestMethod().isPresent()) {
+ throw new RuntimeException("Test preconditions not met");
+ }
+
+ String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName;
+
+ screenshotTest(new BaseAppState() {
+ private TerrainQuad terrain;
+ private Material matTerrain;
+
+ 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
+
+ @Override
+ protected void initialize(Application app) {
+ SimpleApplication simpleApp = (SimpleApplication) app;
+ AssetManager assetManager = app.getAssetManager();
+
+ setUpTerrain(simpleApp, assetManager);
+ setUpTerrainMaterial(assetManager);
+ setUpLights(simpleApp, assetManager);
+ setUpCamera(app);
+
+ // Set debug mode
+ matTerrain.setInt("DebugValuesMode", debugMode);
+ }
+
+ private void setUpTerrainMaterial(AssetManager assetManager) {
+ // 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"));
+
+ // 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.
+ 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
+ setWrapAndMipMaps(albedoTextureArray);
+ setWrapAndMipMaps(normalParallaxTextureArray);
+ setWrapAndMipMaps(metallicRoughnessAoEiTextureArray);
+
+ //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
+ 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);
+
+ terrain.setMaterial(matTerrain);
+ }
+
+ private void setWrapAndMipMaps(Texture texture) {
+ texture.setWrap(WrapMode.Repeat);
+ texture.setMinFilter(MinFilter.Trilinear);
+ texture.setMagFilter(MagFilter.Bilinear);
+ }
+
+ private void setUpTerrain(SimpleApplication simpleApp, AssetManager assetManager) {
+ // 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;
+ try {
+ heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f);
+ heightmap.load();
+ heightmap.smooth(0.9f, 1);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ terrain = new TerrainQuad("terrain", patchSize + 1, terrainSize + 1, heightmap.getHeightMap());
+ TerrainLodControl control = new TerrainLodControl(terrain, getApplication().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);
+ simpleApp.getRootNode().attachChild(terrain);
+ }
+
+ private void setUpLights(SimpleApplication simpleApp, AssetManager assetManager) {
+ 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));
+ simpleApp.getRootNode().addLight(probe);
+
+ DirectionalLight directionalLight = new DirectionalLight();
+ directionalLight.setDirection((new Vector3f(-0.3f, -0.5f, -0.3f)).normalize());
+ directionalLight.setColor(ColorRGBA.White);
+ simpleApp.getRootNode().addLight(directionalLight);
+
+ AmbientLight ambientLight = new AmbientLight();
+ ambientLight.setColor(ColorRGBA.White);
+ simpleApp.getRootNode().addLight(ambientLight);
+ }
+
+ private void setUpCamera(Application app) {
+ app.getCamera().setLocation(new Vector3f(0, 10, -10));
+ app.getCamera().lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y);
+ }
+
+ @Override
+ protected void cleanup(Application app) {}
+
+ @Override
+ protected void onEnable() {}
+
+ @Override
+ protected void onDisable() {}
+
+ }).setBaseImageFileName(imageName)
+ .setFramesToTakeScreenshotsOn(5)
+ .run();
+ }
+}
\ No newline at end of file
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestIssue2076.testIssue2076_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestIssue2076.testIssue2076_f1.png
new file mode 100644
index 0000000000..f4cbc42002
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestIssue2076.testIssue2076_f1.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f10.png
new file mode 100644
index 0000000000..028530e9c8
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f10.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f60.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f60.png
new file mode 100644
index 0000000000..cca8ae963f
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f60.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_DefaultDirectionalLight_f12.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_DefaultDirectionalLight_f12.png
new file mode 100644
index 0000000000..79b1dada0e
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_DefaultDirectionalLight_f12.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_HighRoughness_f12.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_HighRoughness_f12.png
new file mode 100644
index 0000000000..bd1789c7e8
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_HighRoughness_f12.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_LowRoughness_f12.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_LowRoughness_f12.png
new file mode 100644
index 0000000000..1f734de5cf
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_LowRoughness_f12.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_UpdatedDirectionalLight_f12.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_UpdatedDirectionalLight_f12.png
new file mode 100644
index 0000000000..15f219dd73
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_UpdatedDirectionalLight_f12.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithRealtimeBaking_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithRealtimeBaking_f10.png
new file mode 100644
index 0000000000..f9bc99fe37
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithRealtimeBaking_f10.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithoutRealtimeBaking_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithoutRealtimeBaking_f10.png
new file mode 100644
index 0000000000..f9bc99fe37
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithoutRealtimeBaking_f10.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f10.png
new file mode 100644
index 0000000000..4608681de3
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f10.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f60.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f60.png
new file mode 100644
index 0000000000..11ac17390f
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f60.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f1.png
new file mode 100644
index 0000000000..c761934d2d
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f1.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f10.png
new file mode 100644
index 0000000000..2ddf12bb33
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f10.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_FinalRender_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_FinalRender_f5.png
new file mode 100644
index 0000000000..aa8c62dcd4
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_FinalRender_f5.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_GeometryNormals_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_GeometryNormals_f5.png
new file mode 100644
index 0000000000..215274f23d
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_GeometryNormals_f5.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_MetallicMap_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_MetallicMap_f5.png
new file mode 100644
index 0000000000..dbbe6cf3a5
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_MetallicMap_f5.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_NormalMap_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_NormalMap_f5.png
new file mode 100644
index 0000000000..791339d4c3
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_NormalMap_f5.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_RoughnessMap_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_RoughnessMap_f5.png
new file mode 100644
index 0000000000..167ec2eeb7
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_RoughnessMap_f5.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_AmbientOcclusion_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_AmbientOcclusion_f5.png
new file mode 100644
index 0000000000..5c515e1e26
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_AmbientOcclusion_f5.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_Emissive_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_Emissive_f5.png
new file mode 100644
index 0000000000..f1304956c3
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_Emissive_f5.png differ
diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_FinalRender_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_FinalRender_f5.png
new file mode 100644
index 0000000000..aa8c62dcd4
Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_FinalRender_f5.png differ
diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag
index d889ec82b7..9cad93f886 100644
--- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag
+++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag
@@ -49,7 +49,7 @@ void main(){
// read and blend up to 12 texture layers
#for i=0..12 (#ifdef ALBEDOMAP_$i $0 #endif)
- PBRTerrainTextureLayer terrainTextureLayer_$i = PBRTerrainUtils_createAdvancedPBRTerrainLayer($i);
+ PBRTerrainTextureLayer terrainTextureLayer_$i = PBRTerrainUtils_createAdvancedPBRTerrainLayer($i, surface.geometryNormal);
#ifdef USE_FIRST_LAYER_AS_TRANSPARENCY
if($i == 0){
@@ -59,29 +59,51 @@ void main(){
}
#endif
+ terrainTextureLayer_$i.roughness = m_Roughness_$i;
+ terrainTextureLayer_$i.metallic = m_Metallic_$i;
terrainTextureLayer_$i.emission = m_EmissiveColor_$i;
-
- #if defined(TRI_PLANAR_MAPPING) || defined(TRI_PLANAR_MAPPING_$i)
- //triplanar:
-
- PBRTerrainUtils_readTriPlanarAlbedoTexArray(ALBEDOMAP_$i, m_AlbedoMap_$i_scale, m_AlbedoTextureArray, terrainTextureLayer_$i);
- #ifdef NORMALMAP_$i
- PBRTerrainUtils_readTriPlanarNormalTexArray(NORMALMAP_$i, m_AlbedoMap_$i_scale, m_NormalParallaxTextureArray, terrainTextureLayer_$i);
- #endif
- #ifdef METALLICROUGHNESSMAP_$i
- PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexArray(METALLICROUGHNESSMAP_$i, m_AlbedoMap_$i_scale, m_MetallicRoughnessAoEiTextureArray, terrainTextureLayer_$i);
- #endif
- #else
- //non tri-planar:
-
- PBRTerrainUtils_readAlbedoTexArray(ALBEDOMAP_$i, m_AlbedoMap_$i_scale, m_AlbedoTextureArray, terrainTextureLayer_$i);
- #ifdef NORMALMAP_$i
- PBRTerrainUtils_readNormalTexArray(NORMALMAP_$i, m_AlbedoMap_$i_scale, m_NormalParallaxTextureArray, terrainTextureLayer_$i);
- #endif
- #ifdef METALLICROUGHNESSMAP_$i
- PBRTerrainUtils_readMetallicRoughnessAoEiTexArray(METALLICROUGHNESSMAP_$i, m_AlbedoMap_$i_scale, m_MetallicRoughnessAoEiTextureArray, terrainTextureLayer_$i);
- #endif
- #endif
+
+ #ifdef USE_TEXTURE_ARRAYS
+ #if defined(TRI_PLANAR_MAPPING) || defined(TRI_PLANAR_MAPPING_$i)
+ //triplanar for texture arrays:
+ PBRTerrainUtils_readTriPlanarAlbedoTexArray(m_AlbedoMap_$i, m_AlbedoMap_$i_scale, m_AlbedoTextureArray, terrainTextureLayer_$i);
+ #ifdef NORMALMAP_$i
+ PBRTerrainUtils_readTriPlanarNormalTexArray(m_NormalMap_$i, m_AlbedoMap_$i_scale, m_NormalParallaxTextureArray, terrainTextureLayer_$i);
+ #endif
+ #ifdef METALLICROUGHNESSMAP_$i
+ PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexArray(m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, m_MetallicRoughnessAoEiTextureArray, terrainTextureLayer_$i);
+ #endif
+ #else
+ //non tri-planar for texture arrays:
+ PBRTerrainUtils_readAlbedoTexArray(m_AlbedoMap_$i, m_AlbedoMap_$i_scale, m_AlbedoTextureArray, terrainTextureLayer_$i);
+ #ifdef NORMALMAP_$i
+ PBRTerrainUtils_readNormalTexArray(m_NormalMap_$i, m_AlbedoMap_$i_scale, m_NormalParallaxTextureArray, terrainTextureLayer_$i);
+ #endif
+ #ifdef METALLICROUGHNESSMAP_$i
+ PBRTerrainUtils_readMetallicRoughnessAoEiTexArray(m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, m_MetallicRoughnessAoEiTextureArray, terrainTextureLayer_$i);
+ #endif
+ #endif
+ #else
+ #if defined(TRI_PLANAR_MAPPING) || defined(TRI_PLANAR_MAPPING_$i)
+ //triplanar texture reads:
+ PBRTerrainUtils_readTriPlanarAlbedoTexture(m_AlbedoMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i);
+ #ifdef NORMALMAP_$i
+ PBRTerrainUtils_readTriPlanarNormalTexture(m_NormalMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i);
+ #endif
+ #ifdef METALLICROUGHNESSMAP_$i
+ PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexture(m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i);
+ #endif
+ #else
+ //non tri-planar texture reads:
+ PBRTerrainUtils_readAlbedoTexture(m_AlbedoMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i);
+ #ifdef NORMALMAP_$i
+ PBRTerrainUtils_readNormalTexture(m_NormalMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i);
+ #endif
+ #ifdef METALLICROUGHNESSMAP_$i
+ PBRTerrainUtils_readMetallicRoughnessAoEiTexture(m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i);
+ #endif
+ #endif
+ #endif
//CUSTOM LIB EXAMPLE: uses a custom alpha map to desaturate albedo color for a color-removal effect
#ifdef AFFLICTIONTEXTURE
@@ -129,8 +151,8 @@ void main(){
gl_FragColor.rgb += surface.directLightContribution;
gl_FragColor.rgb += surface.envLightContribution;
gl_FragColor.rgb += surface.emission;
- gl_FragColor.a = surface.alpha;
-
+ gl_FragColor.a = surface.alpha;
+
#ifdef USE_FOG
gl_FragColor = MaterialFog_calculateFogColor(vec4(gl_FragColor));
#endif
@@ -138,5 +160,5 @@ void main(){
//outputs the final value of the selected layer as a color for debug purposes.
#ifdef DEBUG_VALUES_MODE
gl_FragColor = PBRLightingUtils_getColorOutputForDebugMode(m_DebugValuesMode, vec4(gl_FragColor.rgba), surface);
- #endif
+ #endif
}
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 105a38b8b0..2098bd77f3 100644
--- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md
+++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md
@@ -4,8 +4,8 @@ MaterialDef AdvancedPBRTerrain {
Int BoundDrawBuffer
Texture2D SunLightExposureMap
- Boolean UseVertexColorsAsSunIntensity //set true to make the vertex color's R channel how exposed a vertex is to the sun
- Float StaticSunIntensity //used for setting the sun exposure value for a whole material
+ Boolean UseVertexColorsAsSunExposure //set true to make the vertex color's R channel how exposed a vertex is to the sun
+ Float StaticSunExposure //used for setting the sun exposure value for a whole material
//these are usually generated at run time or setup in a level editor per-geometry, so that models indoors can have the DirectionalLight dimmed accordingly.
Boolean BrightenIndoorShadows //set true if shadows are enabled and indoor areas without full sun exposure are too dark compared to when shadows are turned off in settings
@@ -16,6 +16,9 @@ MaterialDef AdvancedPBRTerrain {
TextureArray NormalParallaxTextureArray -LINEAR
TextureArray MetallicRoughnessAoEiTextureArray -LINEAR
+ //The type of normal map: -1.0 (DirectX), 1.0 (OpenGl)
+ Float NormalType : -1.0
+
// Specular-AA
Boolean UseSpecularAA : true
// screen space variance,Use the slider to set the strength of the geometric specular anti-aliasing effect between 0 and 1. Higher values produce a blurrier result with less aliasing.
@@ -28,7 +31,6 @@ MaterialDef AdvancedPBRTerrain {
Float AfflictionMetallicValue : 0.0
Float AfflictionEmissiveValue : 0.0 //note that this is simplified into one value, rather than 2 with power and intensity like the regular pbr values.
-
// affliction texture splatting & desaturation functionality
Boolean UseTriplanarAfflictionMapping
@@ -43,7 +45,6 @@ MaterialDef AdvancedPBRTerrain {
Float SplatNoiseVar
-
Int AfflictionMode_0 : 1
Int AfflictionMode_1 : 1
Int AfflictionMode_2 : 1
@@ -109,7 +110,6 @@ MaterialDef AdvancedPBRTerrain {
Int AlbedoMap_10
Int AlbedoMap_11
-
Float AlbedoMap_0_scale : 1
Float AlbedoMap_1_scale : 1
Float AlbedoMap_2_scale : 1
@@ -136,7 +136,6 @@ MaterialDef AdvancedPBRTerrain {
Boolean UseTriPlanarMapping_10
Boolean UseTriPlanarMapping_11
-
Int NormalMap_0
Int NormalMap_1
Int NormalMap_2
@@ -150,7 +149,6 @@ MaterialDef AdvancedPBRTerrain {
Int NormalMap_10
Int NormalMap_11
-
Int MetallicRoughnessMap_0
Int MetallicRoughnessMap_1
Int MetallicRoughnessMap_2
@@ -164,7 +162,6 @@ MaterialDef AdvancedPBRTerrain {
Int MetallicRoughnessMap_10
Int MetallicRoughnessMap_11
-
Float ParallaxHeight_0
Float ParallaxHeight_1
Float ParallaxHeight_2
@@ -178,13 +175,11 @@ MaterialDef AdvancedPBRTerrain {
Float ParallaxHeight_10
Float ParallaxHeight_11
-
-
//used in order to convert world coords to tex coords so afflictionTexture accurately represents the world in cases where terrain is not scaled at a 1,1,1 value
Float TileWidth : 0
Vector3 TileLocation
- // debug the final value of the selected layer as a color output
+ // debug the final value of the selected layer as a color output
Int DebugValuesMode
// Layers:
// 0 - albedo (unshaded)
@@ -195,6 +190,7 @@ MaterialDef AdvancedPBRTerrain {
// 5 - emissive
// 6 - exposure
// 7 - alpha
+ // 8 - geometryNormals
// use tri-planar mapping
Boolean useTriPlanarMapping
@@ -204,13 +200,6 @@ MaterialDef AdvancedPBRTerrain {
Texture2D AlphaMap_1 -LINEAR
Texture2D AlphaMap_2 -LINEAR
- Boolean UseSpecGloss
- Texture2D SpecularMap
- Texture2D GlossinessMap
- Texture2D SpecularGlossinessMap
- Color Specular : 1.0 1.0 1.0 1.0
- Float Glossiness : 1.0
-
Vector4 ProbeData
// Prefiltered Env Map for indirect specular lighting
@@ -222,7 +211,6 @@ MaterialDef AdvancedPBRTerrain {
//integrate BRDF map for indirect Lighting
Texture2D IntegrateBRDF -LINEAR
-
//shadows
Int FilterMode
Boolean HardwareShadows
@@ -289,7 +277,6 @@ MaterialDef AdvancedPBRTerrain {
ViewProjectionMatrix
ViewMatrix
Time
-
}
Defines {
@@ -301,10 +288,12 @@ MaterialDef AdvancedPBRTerrain {
FOG_EXPSQ : ExpSqFog
EXPOSUREMAP : SunLightExposureMap
- USE_VERTEX_COLORS_AS_SUN_EXPOSURE : UseVertexColorsAsSunIntensity
- STATIC_SUN_EXPOSURE : StaticSunIntensity
+ USE_VERTEX_COLORS_AS_SUN_EXPOSURE : UseVertexColorsAsSunExposure
+ STATIC_SUN_EXPOSURE : StaticSunExposure
BRIGHTEN_INDOOR_SHADOWS : BrightenIndoorShadows
+ NORMAL_TYPE: NormalType
+
USE_FIRST_LAYER_AS_TRANSPARENCY : UseFirstLayerAsTransparency
SPECULAR_AA : UseSpecularAA
@@ -323,6 +312,8 @@ MaterialDef AdvancedPBRTerrain {
AFFLICTIONEMISSIVEMAP : SplatEmissiveMap
USE_SPLAT_NOISE : SplatNoiseVar
+ USE_TRIPLANAR_AFFLICTION_MAPPING : UseTriplanarAfflictionMapping
+
TRI_PLANAR_MAPPING : useTriPlanarMapping
ALPHAMAP : AlphaMap
@@ -383,10 +374,10 @@ MaterialDef AdvancedPBRTerrain {
DEBUG_VALUES_MODE : DebugValuesMode
+ USE_TEXTURE_ARRAYS : AlbedoTextureArray
}
}
-
Technique PreShadow {
VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Shadow/PreShadow.vert
diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainUtils.glsllib b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainUtils.glsllib
index 9dc9390c45..e731ad2e27 100644
--- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainUtils.glsllib
+++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainUtils.glsllib
@@ -7,11 +7,17 @@
#import "Common/ShaderLib/TriPlanarUtils.glsllib"
#ifdef ENABLE_PBRTerrainUtils_readPBRTerrainLayers
+
+ #ifndef NORMAL_TYPE
+ #define NORMAL_TYPE -1.0
+ #endif
- //texture arrays:
- uniform sampler2DArray m_AlbedoTextureArray;
- uniform sampler2DArray m_NormalParallaxTextureArray;
- uniform sampler2DArray m_MetallicRoughnessAoEiTextureArray;
+ #ifdef USE_TEXTURE_ARRAYS
+ //texture arrays:
+ uniform sampler2DArray m_AlbedoTextureArray;
+ uniform sampler2DArray m_NormalParallaxTextureArray;
+ uniform sampler2DArray m_MetallicRoughnessAoEiTextureArray;
+ #endif
//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 (#ifdef ALBEDOMAP_$i $0 #endif)
@@ -21,15 +27,25 @@
uniform float m_AlbedoMap_$i_scale;
uniform vec4 m_EmissiveColor_$i;
- uniform int m_AlbedoMap_$i;
+ #ifdef USE_TEXTURE_ARRAYS
+ uniform int m_AlbedoMap_$i;
+ #ifdef NORMALMAP_$i
+ uniform int m_NormalMap_$i;
+ #endif
+ #ifdef METALLICROUGHNESSMAP_$i
+ uniform int m_MetallicRoughnessMap_$i;
+ #endif
+ #else
+ uniform sampler2D m_AlbedoMap_$i;
+ #ifdef NORMALMAP_$i
+ uniform sampler2D m_NormalMap_$i;
+ #endif
+ #ifdef METALLICROUGHNESSMAP_$i
+ uniform sampler2D m_MetallicRoughnessMap_$i;
+ #endif
+ #endif
#endfor
- #for n=0..12 (#ifdef METALLICROUGHNESSMAP_$n $0 #endif)
- uniform int m_MetallicRoughnessMap_$n;
- #endfor
- #for x=0..12 (#ifdef NORMALMAP_$x $0 #endif)
- uniform int m_NormalMap_$x;
- #endfor
//3 alpha maps :
#ifdef ALPHAMAP
@@ -41,9 +57,9 @@
#ifdef ALPHAMAP_2
uniform sampler2D m_AlphaMap_2;
#endif
+
vec4 alphaBlend_0, alphaBlend_1, alphaBlend_2;
-
void PBRTerrainUtils_readAlphaMaps(){
#ifdef ALPHAMAP
@@ -54,9 +70,7 @@
#endif
#ifdef ALPHAMAP_2
alphaBlend_2 = texture2D( m_AlphaMap_2, texCoord.xy );
- #endif
-
-
+ #endif
}
float PBRTerrainUtils_getAlphaBlendFromChannel(int layer){
@@ -85,80 +99,124 @@
break;
}
+ finalAlphaBlendForLayer = clamp(finalAlphaBlendForLayer, 0.0, 1.0);
+
return finalAlphaBlendForLayer;
}
-
- PBRTerrainTextureLayer PBRTerrainUtils_createAdvancedPBRTerrainLayer(int layerNum){
+ PBRTerrainTextureLayer PBRTerrainUtils_createAdvancedPBRTerrainLayer(int layerNum, vec3 geometryNormal){
PBRTerrainTextureLayer terrainTextureLayer;
terrainTextureLayer.blendValue = PBRTerrainUtils_getAlphaBlendFromChannel(layerNum);
+ terrainTextureLayer.albedo = vec4(1.0);
+ terrainTextureLayer.emission = vec4(0.0);
+ terrainTextureLayer.normal = geometryNormal;
+ terrainTextureLayer.alpha = 1.0;
+ terrainTextureLayer.ao = 1.0;
+ terrainTextureLayer.roughness = 1.0;
+ terrainTextureLayer.metallic = 0.0;
+ terrainTextureLayer.height = 0.0;
return terrainTextureLayer;
- }
+ }
- //________
-
+ //3 functions to update layers from respective packed data vecs:
void updateLayerFromPackedAlbedoMap(inout vec4 packedAlbedoVec, inout PBRTerrainTextureLayer layer){
layer.albedo = packedAlbedoVec;
layer.alpha = packedAlbedoVec.a;
}
void updateLayerFromPackedNormalParallaxVec(inout vec4 packedNormalParallaxVec, inout PBRTerrainTextureLayer layer){
- layer.normal = calculateTangentsAndApplyToNormals(packedNormalParallaxVec.rgb, PBRLightingUtils_getWorldNormal());
+ layer.normal = normalize(calculateTangentsAndApplyToNormals(packedNormalParallaxVec.rgb, PBRLightingUtils_getWorldNormal()));
layer.height = packedNormalParallaxVec.a;
}
void updateLayerFromPackedMRAoEiVec(inout vec4 packedMRAoEiVec, inout PBRTerrainTextureLayer layer){
- layer.ao = packedMRAoEiVec.r;
- layer.roughness = packedMRAoEiVec.g;
- layer.metallic = packedMRAoEiVec.b;
+ layer.ao = packedMRAoEiVec.r; //ao only comes from texture (no float scalars) so no *= is done here
+ layer.roughness *= packedMRAoEiVec.g;
+ layer.metallic *= packedMRAoEiVec.b;
layer.emission *= packedMRAoEiVec.a * layer.emission.a;
+ }
+ //________________________________
+ // Basic Texture Reads:
+
+ // Albedo:
+ void PBRTerrainUtils_readAlbedoTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){
+ vec4 packedAlbedoVec = texture2D(tex, texCoord * scale);
+ updateLayerFromPackedAlbedoMap(packedAlbedoVec, layer);
}
- //________
-
- // read Triplanar Albedo from TextureArray:
- void PBRTerrainUtils_readTriPlanarAlbedoTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){
- vec4 packedAlbedoVec = getTriPlanarBlendFromTexArray(lPosition, indexInTexArray, scale, texArray);
+ // normal:
+ void PBRTerrainUtils_readNormalTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){
+ vec4 packedNormalParallaxVec = texture2D(tex, texCoord * scale);
+ packedNormalParallaxVec.xyz = normalize(packedNormalParallaxVec.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0));
+ updateLayerFromPackedNormalParallaxVec(packedNormalParallaxVec, layer);
+ }
+ // metallicRoughnessAoEi:
+ void PBRTerrainUtils_readMetallicRoughnessAoEiTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){
+ vec4 packedMRAoEi = texture2D(tex, texCoord * scale);
+ updateLayerFromPackedMRAoEiVec(packedMRAoEi, layer);
+ }
+ //________________________________
+ // Basic Triplanar Reads:
+
+ // Triplanar Albedo:
+ void PBRTerrainUtils_readTriPlanarAlbedoTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){
+ vec4 packedAlbedoVec = getTriPlanarBlend(lPosition, tex, scale);
updateLayerFromPackedAlbedoMap(packedAlbedoVec, layer);
}
- // read Triplanar normal from TextureArray:
- void PBRTerrainUtils_readTriPlanarNormalTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){
- vec4 packedNormalParallaxVec = getTriPlanarBlendFromTexArray(lPosition, indexInTexArray, scale, texArray);
+ // Triplanar normal:
+ void PBRTerrainUtils_readTriPlanarNormalTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){
+ vec4 packedNormalParallaxVec = getTriPlanarNormalBlend(lPosition, tex, scale);
updateLayerFromPackedNormalParallaxVec(packedNormalParallaxVec, layer);
}
- // read TriPlanar metallicRoughnessAoEi from TextureArray:
- void PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){
- vec4 packedMRAoEi = getTriPlanarBlendFromTexArray(lPosition, indexInTexArray, scale, texArray);
+ // TriPlanar metallicRoughnessAoEi:
+ void PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){
+ vec4 packedMRAoEi = getTriPlanarBlend(lPosition, tex, scale);
updateLayerFromPackedMRAoEiVec(packedMRAoEi, layer);
- }
- //________
-
- // read Albedo from TextureArray:
+ }
+ //________________________________
+ // Basic TexArray reads:
+
+ // Albedo TextureArray:
void PBRTerrainUtils_readAlbedoTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){
vec4 packedAlbedoVec = texture2DArray(texArray, vec3(texCoord * scale, indexInTexArray));
- updateLayerFromPackedAlbedoMap(packedAlbedoVec, layer);
-
+ updateLayerFromPackedAlbedoMap(packedAlbedoVec, layer);
}
- // read Normal from TextureArray:
+ // Normal TextureArray:
void PBRTerrainUtils_readNormalTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){
vec4 packedNormalParallaxVec = texture2DArray(texArray, vec3(texCoord * scale, indexInTexArray));
+ packedNormalParallaxVec.xyz = normalize(packedNormalParallaxVec.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0));
updateLayerFromPackedNormalParallaxVec(packedNormalParallaxVec, layer);
}
- // read metallicRoughnessAoEi from TextureArray:
+ // metallicRoughnessAoEi TextureArray:
void PBRTerrainUtils_readMetallicRoughnessAoEiTexArray(in int indexInTexArray, float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){
vec4 packedMRAoEi = texture2DArray(texArray, vec3(texCoord * scale, indexInTexArray));
- updateLayerFromPackedMRAoEiVec(packedMRAoEi, layer);
-
+ updateLayerFromPackedMRAoEiVec(packedMRAoEi, layer);
}
- //________
-
-
-
- void PBRTerrainUtils_blendPBRTerrainLayer(inout PBRSurface surface, inout PBRTerrainTextureLayer layer){
-
-
- //mix values from this index layer to final output values based on finalAlphaBlendForLayer
+ //________________________________
+ // Triplanar TexArray reads:
+
+ // Triplana Albedo TextureArray:
+ void PBRTerrainUtils_readTriPlanarAlbedoTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){
+ vec4 packedAlbedoVec = getTriPlanarBlendFromTexArray(lPosition, indexInTexArray, scale, texArray);
+ updateLayerFromPackedAlbedoMap(packedAlbedoVec, layer);
+ }
+ // Triplanar normal TextureArray:
+ void PBRTerrainUtils_readTriPlanarNormalTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){
+ vec4 packedNormalParallaxVec = getTriPlanarNormalBlendFromTexArray(lPosition, indexInTexArray, scale, texArray);
+ updateLayerFromPackedNormalParallaxVec(packedNormalParallaxVec, layer);
+ }
+ // TriPlanar metallicRoughnessAoEi TextureArray:
+ void PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){
+ vec4 packedMRAoEi = getTriPlanarBlendFromTexArray(lPosition, indexInTexArray, scale, texArray);
+ updateLayerFromPackedMRAoEiVec(packedMRAoEi, layer);
+ }
+ //_______________________________
+
+ //blend layer function. This mixes each layer's pbr vars over top of the current surface values based on the layer's blendValue
+ void PBRTerrainUtils_blendPBRTerrainLayer(inout PBRSurface surface, inout PBRTerrainTextureLayer layer){
+ layer.ao = clamp(layer.ao, 0.0, 1.0);
+
surface.albedo = mix(surface.albedo, layer.albedo.rgb, layer.blendValue);
- surface.normal = mix(surface.normal.rgb, layer.normal, layer.blendValue);
+ surface.normal = normalize(mix(surface.normal.rgb, layer.normal, layer.blendValue));
surface.metallic = mix(surface.metallic, layer.metallic, layer.blendValue);
surface.roughness = mix(surface.roughness, layer.roughness, layer.blendValue);
surface.ao = mix(surface.ao, vec3(layer.ao), layer.blendValue);
@@ -167,4 +225,3 @@
#endif
#endif
-
diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag
deleted file mode 100644
index b3705383c3..0000000000
--- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.frag
+++ /dev/null
@@ -1,601 +0,0 @@
-#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"
-
-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;
-
-// Specular-AA
-#ifdef SPECULAR_AA_SCREEN_SPACE_VARIANCE
- uniform float m_SpecularAASigma;
-#endif
-#ifdef SPECULAR_AA_THRESHOLD
- uniform float m_SpecularAAKappa;
-#endif
-
-#ifdef DEBUG_VALUES_MODE
- uniform int m_DebugValuesMode;
-#endif
-
-uniform vec4 g_LightData[NB_LIGHTS];
-uniform vec4 g_AmbientLightColor;
-
-#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 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(float($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);
-
-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 that
- // span accross varying directionalLight exposure)
- brightestPointLight = 0.0;
-
-
- float ndotv = max( dot( normal, viewDir ),0.0);
- #ifdef SPECULAR_AA
- float sigma = 1.0;
- float kappa = 0.18;
- #ifdef SPECULAR_AA_SCREEN_SPACE_VARIANCE
- sigma = m_SpecularAASigma;
- #endif
- #ifdef SPECULAR_AA_THRESHOLD
- kappa = m_SpecularAAKappa;
- #endif
- #endif
- for( int i = 0;i < NB_LIGHTS; i+=3){
- vec4 lightColor = g_LightData[i];
- vec4 lightData1 = g_LightData[i+1];
- vec4 lightDir;
- vec3 lightVec;
- lightComputeDir(wPosition, lightColor.w, lightData1, lightDir, lightVec);
-
- float fallOff = 1.0;
- #if __VERSION__ >= 110
- // allow use of control flow
- if(lightColor.w > 1.0){
- #endif
- fallOff = computeSpotFalloff(g_LightData[i+2], lightVec);
- #if __VERSION__ >= 110
- }
- #endif
- //point light attenuation
- fallOff *= lightDir.w;
-
- lightDir.xyz = normalize(lightDir.xyz);
- vec3 directDiffuse;
- vec3 directSpecular;
-
- #ifdef SPECULAR_AA
- float hdotv = PBR_ComputeDirectLightWithSpecularAA(
- normal, lightDir.xyz, viewDir,
- lightColor.rgb, fZero, Roughness, sigma, kappa, ndotv,
- directDiffuse, directSpecular);
- #else
- float hdotv = PBR_ComputeDirectLight(
- normal, lightDir.xyz, viewDir,
- lightColor.rgb, fZero, Roughness, ndotv,
- directDiffuse, directSpecular);
- #endif
-
- vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular;
-
- #if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) || defined(STATIC_SUN_INTENSITY)
- if(fallOff == 1.0){
- directLighting.rgb *= indoorSunLightExposure;// ... *^. to scale down how intense just the sun is (ambient and direct light are 1.0 fallOff)
-
- }
- else{
- brightestPointLight = max(fallOff, brightestPointLight);
-
- }
- #endif
-
- gl_FragColor.rgb += directLighting * fallOff;
-
- }
-
- float minVertLighting;
- #ifdef BRIGHTEN_INDOOR_SHADOWS
- minVertLighting = 0.0833; //brighten shadows so that caves/indoors 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
-
- #if NB_PROBES >= 1
- vec3 color1 = vec3(0.0);
- vec3 color2 = vec3(0.0);
- vec3 color3 = vec3(0.0);
- float weight1 = 1.0;
- float weight2 = 0.0;
- float weight3 = 0.0;
-
- float ndf = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData, g_ShCoeffs, g_PrefEnvMap, color1);
- #if NB_PROBES >= 2
- float ndf2 = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData2, g_ShCoeffs2, g_PrefEnvMap2, color2);
- #endif
- #if NB_PROBES == 3
- float ndf3 = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData3, g_ShCoeffs3, g_PrefEnvMap3, color3);
- #endif
-
- #if NB_PROBES >= 2
- float invNdf = max(1.0 - ndf,0.0);
- float invNdf2 = max(1.0 - ndf2,0.0);
- float sumNdf = ndf + ndf2;
- float sumInvNdf = invNdf + invNdf2;
- #if NB_PROBES == 3
- float invNdf3 = max(1.0 - ndf3,0.0);
- sumNdf += ndf3;
- sumInvNdf += invNdf3;
- weight3 = ((1.0 - (ndf3 / sumNdf)) / (NB_PROBES - 1)) * (invNdf3 / sumInvNdf);
- #endif
-
- weight1 = ((1.0 - (ndf / sumNdf)) / (NB_PROBES - 1)) * (invNdf / sumInvNdf);
- weight2 = ((1.0 - (ndf2 / sumNdf)) / (NB_PROBES - 1)) * (invNdf2 / sumInvNdf);
-
- float weightSum = weight1 + weight2 + weight3;
-
- weight1 /= weightSum;
- weight2 /= weightSum;
- weight3 /= weightSum;
- #endif
-
- #ifdef USE_AMBIENT_LIGHT
- color1.rgb *= g_AmbientLightColor.rgb;
- color2.rgb *= g_AmbientLightColor.rgb;
- color3.rgb *= g_AmbientLightColor.rgb;
- #endif
-
-
-// multiply probes by the indoorSunLightExposure, as determined by pixel's sunlightExposure and adjusted for
-// nearby point/spot lights ( will be multiplied by 1.0 and left unchanged if you are not defining any of the sunlight exposure variables for dimming indoors areas)
- color1.rgb *= indoorSunLightExposure;
- color2.rgb *= indoorSunLightExposure;
- color3.rgb *= indoorSunLightExposure;
-
- gl_FragColor.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0);
-
- #endif
-
- if(emissive.a > 0){
- emissive = emissive * pow(emissive.a * 5, emissiveIntensity) * emissiveIntensity * 20 * emissive.a;
- }
-
- // emissive = emissive * pow(emissiveIntensity * 2.3, emissive.a);
-
- gl_FragColor += emissive;
-
- // 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
-
- //outputs the final value of the selected layer as a color for debug purposes.
- #ifdef DEBUG_VALUES_MODE
- if(m_DebugValuesMode == 0){
- gl_FragColor.rgb = vec3(albedo);
- }
- else if(m_DebugValuesMode == 1){
- gl_FragColor.rgb = vec3(normal);
- }
- else if(m_DebugValuesMode == 2){
- gl_FragColor.rgb = vec3(Roughness);
- }
- else if(m_DebugValuesMode == 3){
- gl_FragColor.rgb = vec3(Metallic);
- }
- else if(m_DebugValuesMode == 4){
- gl_FragColor.rgb = ao.rgb;
- }
- else if(m_DebugValuesMode == 5){
- gl_FragColor.rgb = vec3(emissive.rgb);
- }
- #endif
- gl_FragColor.a = albedo.a;
-
-}
diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md
index 707442fafd..92e601c1c1 100644
--- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md
+++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md
@@ -4,14 +4,16 @@ MaterialDef PBR Terrain {
MaterialParameters {
Int BoundDrawBuffer
-
- Boolean UseVertexColorsAsSunIntensity //set true to make the vertex color's R channel how exposed a vertex is to the sun
- Float StaticSunIntensity //used for setting the sun exposure value for a whole material
+ Texture2D SunLightExposureMap
+ Boolean UseVertexColorsAsSunExposure //set true to make the vertex color's R channel how exposed a vertex is to the sun
+ Float StaticSunExposure //used for setting the sun exposure value for a whole material
//these are usually generated at run time or setup in a level editor per-geometry, so that models indoors can have the DirectionalLight dimmed accordingly.
Boolean BrightenIndoorShadows //set true if shadows are enabled and indoor areas without full sun exposure are too dark compared to when shadows are turned off in settings
+ Boolean UseFirstLayerAsTransparency
+ Boolean UseTriplanarAfflictionMapping
Int AfflictionSplatScale : 8
Float AfflictionRoughnessValue : 1.0
@@ -24,6 +26,9 @@ MaterialDef PBR Terrain {
Texture2D SplatRoughnessMetallicMap -LINEAR
Texture2D SplatEmissiveMap -LINEAR
+ //The type of normal map: -1.0 (DirectX), 1.0 (OpenGl)
+ Float NormalType : -1.0
+
// Specular-AA
Boolean UseSpecularAA : true
// screen space variance,Use the slider to set the strength of the geometric specular anti-aliasing effect between 0 and 1. Higher values produce a blurrier result with less aliasing.
@@ -78,18 +83,44 @@ MaterialDef PBR Terrain {
Float Metallic_10 : 0.0
Float Metallic_11 : 0.0
-
- // debug the final value of the selected layer as a color output
+ Color EmissiveColor_0 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_1 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_2 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_3 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_4 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_5 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_6 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_7 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_8 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_9 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_10 : 0.0 0.0 0.0 0.0
+ Color EmissiveColor_11 : 0.0 0.0 0.0 0.0
+
+ Boolean UseTriPlanarMapping_0
+ Boolean UseTriPlanarMapping_1
+ Boolean UseTriPlanarMapping_2
+ Boolean UseTriPlanarMapping_3
+ Boolean UseTriPlanarMapping_4
+ Boolean UseTriPlanarMapping_5
+ Boolean UseTriPlanarMapping_6
+ Boolean UseTriPlanarMapping_7
+ Boolean UseTriPlanarMapping_8
+ Boolean UseTriPlanarMapping_9
+ Boolean UseTriPlanarMapping_10
+ Boolean UseTriPlanarMapping_11
+
+ // debug the final value of the selected layer as a color output
Int DebugValuesMode
-
// Layers:
- // 0 - albedo (un-shaded)
+ // 0 - albedo (unshaded)
// 1 - normals
// 2 - roughness
// 3 - metallic
// 4 - ao
- // 5 - emissive
-
+ // 5 - emissive
+ // 6 - exposure
+ // 7 - alpha
+ // 8 - geometryNormals
// use tri-planar mapping
Boolean useTriPlanarMapping
@@ -154,21 +185,11 @@ MaterialDef PBR Terrain {
Float AlbedoMap_11_scale
Texture2D NormalMap_11 -LINEAR
-
-
// Texture that specifies alpha values
Texture2D AlphaMap -LINEAR
Texture2D AlphaMap_1 -LINEAR
Texture2D AlphaMap_2 -LINEAR
- // For Spec gloss pipeline
- Boolean UseSpecGloss
- Texture2D SpecularMap
- Texture2D GlossinessMap
- Texture2D SpecularGlossinessMap
- Color Specular : 1.0 1.0 1.0 1.0
- Float Glossiness : 1.0
-
Vector4 ProbeData
// Prefiltered Env Map for indirect specular lighting
@@ -221,7 +242,6 @@ MaterialDef PBR Terrain {
Boolean BackfaceShadows : false
-
Boolean UseFog
Color FogColor
Vector2 LinearFog
@@ -237,7 +257,7 @@ MaterialDef PBR Terrain {
LightMode SinglePassAndImageBased
VertexShader GLSL300 GLSL150 GLSL130 GLSL100: Common/MatDefs/Terrain/PBRTerrain.vert
- FragmentShader GLSL300 GLSL150 GLSL130 GLSL100: Common/MatDefs/Terrain/PBRTerrain.frag
+ FragmentShader GLSL300 GLSL150 GLSL130 GLSL100: Common/MatDefs/Terrain/AdvancedPBRTerrain.frag
WorldParameters {
WorldViewProjectionMatrix
@@ -247,7 +267,6 @@ MaterialDef PBR Terrain {
ViewProjectionMatrix
ViewMatrix
Time
-
}
Defines {
@@ -256,6 +275,7 @@ MaterialDef PBR Terrain {
TILELOCATION : TileLocation
AFFLICTIONTEXTURE : AfflictionAlphaMap
+ USE_TRIPLANAR_AFFLICTION_MAPPING : UseTriplanarAfflictionMapping
AFFLICTIONALBEDOMAP: SplatAlbedoMap
AFFLICTIONNORMALMAP : SplatNormalMap
AFFLICTIONROUGHNESSMETALLICMAP : SplatRoughnessMetallicMap
@@ -267,11 +287,15 @@ MaterialDef PBR Terrain {
SPECULAR_AA_SCREEN_SPACE_VARIANCE : SpecularAASigma
SPECULAR_AA_THRESHOLD : SpecularAAKappa
-
- USE_VERTEX_COLORS_AS_SUN_INTENSITY : UseVertexColorsAsSunIntensity
- STATIC_SUN_INTENSITY : StaticSunIntensity
+ EXPOSUREMAP : SunLightExposureMap
+ USE_VERTEX_COLORS_AS_SUN_EXPOSURE : UseVertexColorsAsSunExposure
+ STATIC_SUN_EXPOSURE : StaticSunExposure
BRIGHTEN_INDOOR_SHADOWS : BrightenIndoorShadows
+ NORMAL_TYPE: NormalType
+
+ USE_FIRST_LAYER_AS_TRANSPARENCY : UseFirstLayerAsTransparency
+
DISCARD_ALPHA : AlphaDiscardThreshold
USE_FOG : UseFog
@@ -323,6 +347,19 @@ MaterialDef PBR Terrain {
ALBEDOMAP_10_SCALE : AlbedoMap_10_scale
ALBEDOMAP_11_SCALE : AlbedoMap_11_scale
+ TRI_PLANAR_MAPPING_0 : UseTriPlanarMapping_0
+ TRI_PLANAR_MAPPING_1 : UseTriPlanarMapping_1
+ TRI_PLANAR_MAPPING_2 : UseTriPlanarMapping_2
+ TRI_PLANAR_MAPPING_3 : UseTriPlanarMapping_3
+ TRI_PLANAR_MAPPING_4 : UseTriPlanarMapping_4
+ TRI_PLANAR_MAPPING_5 : UseTriPlanarMapping_5
+ TRI_PLANAR_MAPPING_6 : UseTriPlanarMapping_6
+ TRI_PLANAR_MAPPING_7 : UseTriPlanarMapping_7
+ TRI_PLANAR_MAPPING_8 : UseTriPlanarMapping_8
+ TRI_PLANAR_MAPPING_9 : UseTriPlanarMapping_9
+ TRI_PLANAR_MAPPING_10 : UseTriPlanarMapping_10
+ TRI_PLANAR_MAPPING_11 : UseTriPlanarMapping_11
+
DEBUG_VALUES_MODE : DebugValuesMode
}