Explorar el Código

Merge pull request #2518 from richardTingle/screenshotTests-toon-billboard-fog-scattering

Add new screenshot tests for bill boarding, cartoons, fog and scattering
Ryan McDonough hace 2 meses
padre
commit
8ff17a8d47

+ 2 - 0
.github/workflows/screenshot-test-comment.yml

@@ -113,5 +113,7 @@ jobs:
             **Note;**  it is very important that the committed reference images are created on the build pipeline, locally created images are not reliable. Similarly tests will fail locally but you can look at the report to check they are "visually similar".
 
             See https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-screenshot-tests/README.md for more information
+            
+            Contact @richardTingle (aka richtea) for guidance if required
           edit-mode: replace
           comment-id: ${{ steps.existingCommentId.outputs.comment-id }}

+ 151 - 0
jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/model/shape/TestBillboard.java

@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2025 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.model.shape;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.material.Material;
+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.control.BillboardControl;
+import com.jme3.scene.debug.Arrow;
+import com.jme3.scene.debug.Grid;
+import com.jme3.scene.shape.Quad;
+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 test for the Billboard test.
+ * 
+ * <p>This test creates three different billboard alignments (Screen, Camera, AxialY)
+ * with different colored quads. Each billboard is positioned at a different x-coordinate
+ * and has a blue Z-axis arrow attached to it. Screenshots are taken from three different angles:
+ * front, above, and right.
+ * 
+ * @author Richard Tingle (screenshot test adaptation)
+ */
+@SuppressWarnings("OptionalGetWithoutIsPresent")
+public class TestBillboard extends ScreenshotTestBase {
+
+    private static Stream<Arguments> testParameters() {
+        return Stream.of(
+                Arguments.of("fromFront", new Vector3f(0, 1, 15)),
+                Arguments.of("fromAbove", new Vector3f(0, 15, 6)),
+                Arguments.of("fromRight", new Vector3f(-15, 10, 5))
+        );
+    }
+
+    /**
+     * A billboard test with the specified camera parameters.
+     *
+     * @param cameraPosition The position of the camera
+     */
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("testParameters")
+    public void testBillboard(String testName, Vector3f cameraPosition, TestInfo testInfo) {
+        String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName;
+
+        screenshotTest(new BaseAppState() {
+            @Override
+            protected void initialize(Application app) {
+                SimpleApplication simpleApplication = (SimpleApplication) app;
+                Node rootNode = simpleApplication.getRootNode();
+
+                // Set up the camera
+                simpleApplication.getCamera().setLocation(cameraPosition);
+                simpleApplication.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+
+                // Set background color
+                simpleApplication.getViewPort().setBackgroundColor(ColorRGBA.DarkGray);
+
+                // Create grid
+                Geometry grid = makeShape(simpleApplication, "DebugGrid", new Grid(21, 21, 2), ColorRGBA.Gray);
+                grid.center().move(0, 0, 0);
+                rootNode.attachChild(grid);
+
+                // Create billboards with different alignments
+                Node node = createBillboard(simpleApplication, BillboardControl.Alignment.Screen, ColorRGBA.Red);
+                node.setLocalTranslation(-6f, 0, 0);
+                rootNode.attachChild(node);
+
+                node = createBillboard(simpleApplication, BillboardControl.Alignment.Camera, ColorRGBA.Green);
+                node.setLocalTranslation(-2f, 0, 0);
+                rootNode.attachChild(node);
+
+                node = createBillboard(simpleApplication, BillboardControl.Alignment.AxialY, ColorRGBA.Blue);
+                node.setLocalTranslation(2f, 0, 0);
+                rootNode.attachChild(node);
+            }
+
+            @Override
+            protected void cleanup(Application app) {}
+
+            @Override
+            protected void onEnable() {}
+
+            @Override
+            protected void onDisable() {}
+
+            private Node createBillboard(SimpleApplication app, BillboardControl.Alignment alignment, ColorRGBA color) {
+                Node node = new Node("Parent");
+                Quad quad = new Quad(2, 2);
+                Geometry g = makeShape(app, alignment.name(), quad, color);
+                BillboardControl bc = new BillboardControl();
+                bc.setAlignment(alignment);
+                g.addControl(bc);
+                node.attachChild(g);
+                node.attachChild(makeShape(app, "ZAxis", new Arrow(Vector3f.UNIT_Z), ColorRGBA.Blue));
+                return node;
+            }
+
+            private Geometry makeShape(SimpleApplication app, String name, Mesh shape, ColorRGBA color) {
+                Geometry geo = new Geometry(name, shape);
+                Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
+                mat.setColor("Color", color);
+                geo.setMaterial(mat);
+                return geo;
+            }
+        })
+        .setBaseImageFileName(imageName)
+        .setFramesToTakeScreenshotsOn(1)
+        .run();
+    }
+}

+ 150 - 0
jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestCartoonEdge.java

@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2025 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.post;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.CartoonEdgeFilter;
+import com.jme3.renderer.Caps;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.texture.Texture;
+import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Screenshot test for the CartoonEdge filter.
+ * 
+ * <p>This test creates a scene with a monkey head model that has a cartoon/cel-shaded effect
+ * applied to it. The CartoonEdgeFilter is used to create yellow outlines around the edges
+ * of the model, and a toon shader is applied to the model's material to create the cel-shaded look.
+ * 
+ * @author Richard Tingle (screenshot test adaptation)
+ */
+public class TestCartoonEdge extends ScreenshotTestBase {
+
+    /**
+     * This test creates a scene with a cartoon-shaded monkey head model.
+     */
+    @Test
+    public void testCartoonEdge() {
+        screenshotTest(new BaseAppState() {
+            @Override
+            protected void initialize(Application app) {
+                SimpleApplication simpleApplication = (SimpleApplication) app;
+                Node rootNode = simpleApplication.getRootNode();
+
+                simpleApplication.getViewPort().setBackgroundColor(ColorRGBA.Gray);
+
+                simpleApplication.getCamera().setLocation(new Vector3f(-1, 2, -5));
+                simpleApplication.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+                simpleApplication.getCamera().setFrustumFar(300);
+
+                rootNode.setCullHint(CullHint.Never);
+
+                setupLighting(rootNode);
+                
+                setupModel(simpleApplication, rootNode);
+                
+                setupFilters(simpleApplication);
+            }
+
+            private void setupFilters(SimpleApplication app) {
+                if (app.getRenderer().getCaps().contains(Caps.GLSL100)) {
+                    FilterPostProcessor fpp = new FilterPostProcessor(app.getAssetManager());
+
+                    CartoonEdgeFilter toon = new CartoonEdgeFilter();
+                    toon.setEdgeColor(ColorRGBA.Yellow);
+                    fpp.addFilter(toon);
+                    app.getViewPort().addProcessor(fpp);
+                }
+            }
+
+            private void setupLighting(Node rootNode) {
+                DirectionalLight dl = new DirectionalLight();
+                dl.setDirection(new Vector3f(-1, -1, 1).normalizeLocal());
+                dl.setColor(new ColorRGBA(2, 2, 2, 1));
+                rootNode.addLight(dl);
+            }
+
+            private void setupModel(SimpleApplication app, Node rootNode) {
+                Spatial model = app.getAssetManager().loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml");
+                makeToonish(app, model);
+                model.rotate(0, FastMath.PI, 0);
+                rootNode.attachChild(model);
+            }
+
+            private void makeToonish(SimpleApplication app, Spatial spatial) {
+                if (spatial instanceof Node) {
+                    Node n = (Node) spatial;
+                    for (Spatial child : n.getChildren()) {
+                        makeToonish(app, child);
+                    }
+                } else if (spatial instanceof Geometry) {
+                    Geometry g = (Geometry) spatial;
+                    Material m = g.getMaterial();
+                    if (m.getMaterialDef().getMaterialParam("UseMaterialColors") != null) {
+                        Texture t = app.getAssetManager().loadTexture("Textures/ColorRamp/toon.png");
+                        m.setTexture("ColorRamp", t);
+                        m.setBoolean("UseMaterialColors", true);
+                        m.setColor("Specular", ColorRGBA.Black);
+                        m.setColor("Diffuse", ColorRGBA.White);
+                        m.setBoolean("VertexLighting", true);
+                    }
+                }
+            }
+
+            @Override
+            protected void cleanup(Application app) {
+            }
+
+            @Override
+            protected void onEnable() {
+            }
+
+            @Override
+            protected void onDisable() {
+            }
+        })
+        .setFramesToTakeScreenshotsOn(1)
+        .run();
+    }
+}

+ 172 - 0
jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestFog.java

@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2025 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.post;
+
+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.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.FogFilter;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Node;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.texture.Texture;
+import com.jme3.util.SkyFactory;
+import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Screenshot test for the Fog filter.
+ * 
+ * <p>This test creates a scene with a terrain and sky, with a fog effect applied.
+ * The fog is a light gray color and has a specific density and distance setting.
+ * 
+ * @author Richard Tingle (screenshot test adaptation)
+ */
+public class TestFog extends ScreenshotTestBase {
+
+    /**
+     * This test creates a scene with a fog effect.
+     */
+    @Test
+    public void testFog() {
+        screenshotTest(new BaseAppState() {
+            @Override
+            protected void initialize(Application app) {
+                SimpleApplication simpleApplication = (SimpleApplication) app;
+                Node rootNode = simpleApplication.getRootNode();
+
+                simpleApplication.getCamera().setLocation(new Vector3f(-34.74095f, 95.21318f, -287.4945f));
+                simpleApplication.getCamera().setRotation(new Quaternion(0.023536969f, 0.9361278f, -0.016098259f, -0.35050195f));
+
+                Node mainScene = new Node();
+
+                mainScene.attachChild(SkyFactory.createSky(simpleApplication.getAssetManager(),
+                        "Textures/Sky/Bright/BrightSky.dds", 
+                        SkyFactory.EnvMapType.CubeMap));
+                
+                createTerrain(mainScene, app.getAssetManager());
+
+                DirectionalLight sun = new DirectionalLight();
+                Vector3f lightDir = new Vector3f(-0.37352666f, -0.50444174f, -0.7784704f);
+                sun.setDirection(lightDir);
+                sun.setColor(ColorRGBA.White.clone().multLocal(2));
+                mainScene.addLight(sun);
+
+                rootNode.attachChild(mainScene);
+
+                FilterPostProcessor fpp = new FilterPostProcessor(simpleApplication.getAssetManager());
+
+                FogFilter fog = new FogFilter();
+                fog.setFogColor(new ColorRGBA(0.9f, 0.9f, 0.9f, 1.0f));
+                fog.setFogDistance(155);
+                fog.setFogDensity(1.0f);
+                fpp.addFilter(fog);
+                simpleApplication.getViewPort().addProcessor(fpp);
+            }
+
+
+            private void createTerrain(Node rootNode, AssetManager assetManager) {
+                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(Texture.WrapMode.Repeat);
+                matRock.setTexture("DiffuseMap", grass);
+                matRock.setFloat("DiffuseMap_0_scale", 64);
+                Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+                dirt.setWrap(Texture.WrapMode.Repeat);
+                matRock.setTexture("DiffuseMap_1", dirt);
+                matRock.setFloat("DiffuseMap_1_scale", 16);
+                Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
+                rock.setWrap(Texture.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(Texture.WrapMode.Repeat);
+                Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
+                normalMap1.setWrap(Texture.WrapMode.Repeat);
+                Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
+                normalMap2.setWrap(Texture.WrapMode.Repeat);
+                matRock.setTexture("NormalMap", normalMap0);
+                matRock.setTexture("NormalMap_1", normalMap1);
+                matRock.setTexture("NormalMap_2", normalMap2);
+
+                AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
+                heightmap.load();
+
+                TerrainQuad terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());
+
+                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.setShadowMode(RenderQueue.ShadowMode.Receive);
+                rootNode.attachChild(terrain);
+
+            }
+
+
+            @Override
+            protected void cleanup(Application app) {
+            }
+
+            @Override
+            protected void onEnable() {
+            }
+
+            @Override
+            protected void onDisable() {
+            }
+
+            @Override
+            public void update(float tpf) {
+                super.update(tpf);
+                System.out.println(getApplication().getCamera().getLocation());
+            }
+
+        })
+        .setFramesToTakeScreenshotsOn(1)
+        .run();
+    }
+}

+ 126 - 0
jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestLightScattering.java

@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2025 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.post;
+
+import com.jme3.app.Application;
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.LightScatteringFilter;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+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.Test;
+
+/**
+ * Screenshot test for the LightScattering filter.
+ * 
+ * <p>This test creates a scene with a terrain model and a sky, with a light scattering
+ * (god rays) effect applied. The effect simulates light rays scattering through the atmosphere
+ * from a bright light source (like the sun).
+ * 
+ * @author Richard Tingle (screenshot test adaptation)
+ */
+public class TestLightScattering extends ScreenshotTestBase {
+
+    /**
+     * This test creates a scene with a light scattering effect.
+     */
+    @Test
+    public void testLightScattering() {
+        screenshotTest(new BaseAppState() {
+            @Override
+            protected void initialize(Application app) {
+                SimpleApplication simpleApplication = (SimpleApplication) app;
+                Node rootNode = simpleApplication.getRootNode();
+
+                simpleApplication.getCamera().setLocation(new Vector3f(55.35316f, -0.27061665f, 27.092093f));
+                simpleApplication.getCamera().setRotation(new Quaternion(0.010414706f, 0.9874893f, 0.13880467f, -0.07409228f));
+
+                Material mat = simpleApplication.getAssetManager().loadMaterial("Textures/Terrain/Rocky/Rocky.j3m");
+                Spatial scene = simpleApplication.getAssetManager().loadModel("Models/Terrain/Terrain.mesh.xml");
+                MikktspaceTangentGenerator.generate(((Geometry) ((Node) scene).getChild(0)).getMesh());
+                scene.setMaterial(mat);
+                scene.setShadowMode(ShadowMode.CastAndReceive);
+                scene.setLocalScale(400);
+                scene.setLocalTranslation(0, -10, -120);
+                rootNode.attachChild(scene);
+
+                rootNode.attachChild(SkyFactory.createSky(simpleApplication.getAssetManager(),
+                        "Textures/Sky/Bright/FullskiesBlueClear03.dds", 
+                        SkyFactory.EnvMapType.CubeMap));
+
+                DirectionalLight sun = new DirectionalLight();
+                Vector3f lightDir = new Vector3f(-0.12f, -0.3729129f, 0.74847335f);
+                sun.setDirection(lightDir);
+                sun.setColor(ColorRGBA.White.clone().multLocal(2));
+                scene.addLight(sun);
+
+                FilterPostProcessor fpp = new FilterPostProcessor(simpleApplication.getAssetManager());
+
+                Vector3f lightPos = lightDir.normalize().negate().multLocal(3000);
+                LightScatteringFilter filter = new LightScatteringFilter(lightPos);
+
+                filter.setLightDensity(1.0f);
+                filter.setBlurStart(0.02f);
+                filter.setBlurWidth(0.9f);
+                filter.setLightPosition(lightPos);
+                
+                fpp.addFilter(filter);
+                simpleApplication.getViewPort().addProcessor(fpp);
+            }
+
+            @Override
+            protected void cleanup(Application app) {
+            }
+
+            @Override
+            protected void onEnable() {
+            }
+
+            @Override
+            protected void onDisable() {
+            }
+        })
+        .setFramesToTakeScreenshotsOn(1)
+        .run();
+    }
+}

BIN
jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromAbove_f1.png


BIN
jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromFront_f1.png


BIN
jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromRight_f1.png


BIN
jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestCartoonEdge.testCartoonEdge_f1.png


BIN
jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestFog.testFog_f1.png


BIN
jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestLightScattering.testLightScattering_f1.png