I’m writing a screen space snow filter for which I need per pixel normal data. So I wrote a custom SceneProcessor which makes a frameBuffer which supports Multiple Render Targets (MRT) and a fragment shader that writes the normal data to the second output. The rendered texture of normals could be rendered on a Picture in the GUI node just fine.
(Normal texture bottom left, glow texture bottom right)
Now I’m trying to input that normals texture into a Filter and suddenly the whole scene goes black. After some debugging, I realized that the normal texture the filter’s shader gets is pitch black The only other useful thing I could find is that normalTexture.getImage().getData() returns an empty array.
Main class:
package mygame;
import com.jme3.app.SimpleApplication;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.SceneProcessor;
import com.jme3.profile.AppProfiler;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture;
import com.jme3.util.TangentBinormalGenerator;
/**
* Used to test multiple render targets.
* @author grizeldi
*/
public class MRTTest extends SimpleApplication {
private GBuffEnabler gbe = new GBuffEnabler(this);
@Override
public void simpleInitApp() {
flyCam.setMoveSpeed(30f);
viewPort.addProcessor(gbe);
Spatial tank = assetManager.loadModel("Models/Tank/tank.j3o");
Material tankMat = new Material(assetManager, "Shaders/MRT/MRTLighting.j3md");
tankMat.setTexture("DiffuseMap", assetManager.loadTexture("Models/Tank/Tank_Base_Color.png"));
tankMat.setTexture("NormalMap", assetManager.loadTexture("Models/Tank/Tank_Normal.png"));
tankMat.setTexture("GlowMap", assetManager.loadTexture("Models/Tank/Tank_Emissive.png"));
tank.setMaterial(tankMat);
TangentBinormalGenerator.generate(tank);
rootNode.attachChild(tank);
/** A white, directional light source */
DirectionalLight sun = new DirectionalLight();
sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
sun.setColor(ColorRGBA.White);
rootNode.addLight(sun);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
SnowFilter snow = new SnowFilter(gbe);
snow.setMaxAngle(80);
fpp.addFilter(snow);
viewPort.addProcessor(fpp);
}
public static void main(String[] args){
new MRTTest().start();
}
}
class GBuffEnabler implements SceneProcessor {
private SimpleApplication app;
private FrameBuffer fbuff;
private Texture2D normalData, rendered, depthData, glowData;
private Picture display, normalDisplay, glowDisplay;
public GBuffEnabler(SimpleApplication app) {
this.app = app;
}
@Override
public void initialize(RenderManager rm, ViewPort vp) {
reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());
vp.setOutputFrameBuffer(fbuff);
}
@Override
public void reshape(ViewPort vp, int w, int h) {
rendered = new Texture2D(w, h, Image.Format.RGBA8);
normalData = new Texture2D(w, h, Image.Format.RGBA8);
glowData = new Texture2D(w, h, Image.Format.RGBA8);
depthData = new Texture2D(w, h, Image.Format.Depth);
display = new Picture("Renderer Display Quad");
display.move(0, 0, -1);
display.setWidth(w);
display.setHeight(h);
display.setTexture(app.getAssetManager(), rendered, false);
app.getGuiNode().attachChild(display);
normalDisplay = new Picture("Normal Channel Display Quad");
normalDisplay.move(200, 0, -0.5f);
normalDisplay.setWidth(320);
normalDisplay.setHeight(180);
normalDisplay.setTexture(app.getAssetManager(), normalData, false);
app.getGuiNode().attachChild(normalDisplay);
glowDisplay = new Picture("Glow Channel Display Quad");
glowDisplay.move(600, 0, -0.5f);
glowDisplay.setWidth(320);
glowDisplay.setHeight(180);
glowDisplay.setTexture(app.getAssetManager(), glowData, false);
app.getGuiNode().attachChild(glowDisplay);
app.getGuiNode().updateGeometricState();
fbuff = new FrameBuffer(w, h, 1);
fbuff.setDepthTexture(depthData);
fbuff.addColorTexture(rendered);
fbuff.addColorTexture(normalData);
fbuff.addColorTexture(glowData);
fbuff.setMultiTarget(true);
}
@Override
public boolean isInitialized() {
return rendered != null;
}
@Override
public void preFrame(float tpf) {
}
@Override
public void postQueue(RenderQueue rq) {
}
@Override
public void postFrame(FrameBuffer out) {
}
@Override
public void cleanup() {
app.getGuiNode().detachChild(display);
app.getGuiNode().updateGeometricState();
}
@Override
public void setProfiler(AppProfiler profiler) {}
public Texture2D getNormalData() {
return normalData;
}
public Texture2D getRendered() {
return rendered;
}
public Texture2D getDepthData() {
return depthData;
}
public Texture2D getGlowData() {
return glowData;
}
}
SnowFilter.java
package mygame;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.post.Filter;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import java.util.ArrayList;
/**
*
* @author grizeldi
*/
public class SnowFilter extends Filter{
private float maxAngle = 90;
private GBuffEnabler gbuff;
public SnowFilter(GBuffEnabler gbuff) {
this.gbuff = gbuff;
}
@Override
protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
postRenderPasses = new ArrayList<>();
final Material calcMaterial = new Material(manager, "Shaders/ScreenSpaceSnow/SSSnowCalc.j3md");
Pass calcPass = new Pass() {
@Override
public boolean requiresSceneAsTexture() {
return true;
}
@Override
public void beforeRender() {
calcMaterial.setTexture("NormalsTexture", gbuff.getNormalData());
calcMaterial.setFloat("Angle", maxAngle * FastMath.DEG_TO_RAD);
}
};
calcPass.init(renderManager.getRenderer(), w, h, Image.Format.RGBA8, Image.Format.Depth, 1, calcMaterial);
postRenderPasses.add(calcPass);
material = new Material(manager, "Shaders/ScreenSpaceSnow/SSSnowFinal.j3md");
material.setTexture("Factors", calcPass.getRenderedTexture());
}
@Override
protected Material getMaterial() {
return material;
}
/**
* Set the maximal angle from the Y axis at which snow will still appear.
* @param maxAngle New angle in degrees.
*/
public void setMaxAngle(float maxAngle) {
this.maxAngle = maxAngle;
if (material != null)
material.setFloat("Angle", maxAngle * FastMath.DEG_TO_RAD);
}
}
I tried doing the calculations directly in the final material instead of an extra pass, but the result was the same, a black normals texture. The SnowFilter’s shaders currently only forward the normal texture as gl_FragColor.
I’m running out of ideas and would really love some help, since not even JME discord’s current resident shader guru @RiccardoBlb knows what is going on at this point.