Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.FullscreenTriangle;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.FrameBuffer.FrameBufferTarget;
import com.jme3.texture.Image.Format;
Expand Down Expand Up @@ -88,7 +90,8 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
private Texture2D depthTexture;
private SafeArrayList<Filter> filters = new SafeArrayList<>(Filter.class);
private AssetManager assetManager;
private Picture fsQuad;
private boolean useFullscreenTriangle = false;
private Geometry fsQuad;
private boolean computeDepth = false;
private FrameBuffer outputBuffer;
private int width;
Expand Down Expand Up @@ -116,6 +119,18 @@ public FilterPostProcessor(AssetManager assetManager) {
this.assetManager = assetManager;
}

/**
* Constructs a new instance of FilterPostProcessor.
*
* @param assetManager The asset manager used to load resources needed by the processor.
* @param useFullscreenTriangle If true, a fullscreen triangle will be used for rendering;
* otherwise, a quad will be used.
*/
public FilterPostProcessor(AssetManager assetManager, boolean useFullscreenTriangle) {
this(assetManager);
this.useFullscreenTriangle = useFullscreenTriangle;
}

/**
* Serialization-only constructor. Do not use this constructor directly;
* use {@link #FilterPostProcessor(AssetManager)}.
Expand Down Expand Up @@ -181,9 +196,14 @@ public void initialize(RenderManager rm, ViewPort vp) {
renderManager = rm;
renderer = rm.getRenderer();
viewPort = vp;
fsQuad = new Picture("filter full screen quad");
fsQuad.setWidth(1);
fsQuad.setHeight(1);
if(useFullscreenTriangle) {
fsQuad = new Geometry("FsQuad", new FullscreenTriangle());
}else{
Picture fullscreenQuad = new Picture("filter full screen quad");
fullscreenQuad.setWidth(1);
fullscreenQuad.setHeight(1);
fsQuad = fullscreenQuad;
}

// Determine optimal framebuffer format based on renderer capabilities
if (!renderer.getCaps().contains(Caps.PackedFloatTexture)) {
Expand Down Expand Up @@ -715,6 +735,7 @@ public Format getFrameBufferDepthFormat() {
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(numSamples, "numSamples", 0);
oc.write(useFullscreenTriangle, "useFullscreenTriangle", false);
oc.writeSavableArrayList(new ArrayList(filters), "filters", null);
}

Expand All @@ -723,6 +744,7 @@ public void write(JmeExporter ex) throws IOException {
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
numSamples = ic.readInt("numSamples", 0);
useFullscreenTriangle = ic.readBoolean("useFullscreenTriangle", false);
filters = new SafeArrayList<>(Filter.class, ic.readSavableArrayList("filters", null));
for (Filter filter : filters.getArray()) {
filter.setProcessor(this);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.jme3.scene.shape;

import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;


/**
* The FullscreenTriangle class defines a mesh representing a single
* triangle that spans the entire screen. It is typically used in rendering
* techniques where a fullscreen quad or triangle is needed, such as in
* post-processing effects or screen-space operations.
*/
public class FullscreenTriangle extends Mesh {

/**
* Encapsulates the vertex positions for a fullscreen triangle.
* The positions are transformed by the vertex shader to cover the entire screen.
*/
private static final float[] POSITIONS = {
0, 0, 0, // -1 -1 0 after vertex shader transform
2, 0, 0, // 3 -1 0 after vertex shader transform
0, 2, 0 // -1 3 0 after vertex shader transform
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

};

private static final float[] TEXCOORDS = {
0,0,
2,0,
0,2
};


public FullscreenTriangle() {
super();
setBuffer(VertexBuffer.Type.Position, 3, POSITIONS);
setBuffer(VertexBuffer.Type.TexCoord, 2, TEXCOORDS);
setBuffer(VertexBuffer.Type.Index, 3, new short[]{0, 1, 2});
updateBound();
}
}
4 changes: 2 additions & 2 deletions jme3-effects/src/main/resources/Common/MatDefs/Post/Post.vert
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ attribute vec2 inTexCoord;

varying vec2 texCoord;

void main() {
void main() {
vec2 pos = inPosition.xy * 2.0 - 1.0;
gl_Position = vec4(pos, 0.0, 1.0);
gl_Position = vec4(pos, 0.0, 1.0);
texCoord = inTexCoord;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ in vec2 inTexCoord;
out vec2 texCoord;

void main() {
vec2 pos = inPosition.xy * 2.0 - 1.0;
vec2 pos = inPosition.xy * 2.0 - 1.0;
gl_Position = vec4(pos, 0.0, 1.0);
texCoord = inTexCoord;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* 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 TestFogFullscreenTriangle 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(),true);

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());
}

})
.setBaseImageFileName("org.jmonkeyengine.screenshottests.post.TestFog.testFog")
.setFramesToTakeScreenshotsOn(1)
.run();
}
}