Custom filter input texture black

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 :confused: 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.

Where’s the “normalTexture” variabile in your source code? I’m unable to recognize it.

It’s actually gbuff.getNormalData().getImage().getData(), but I was trying to get a point across without going into specifics of my implementation :wink:

1 Like

I think that texture data is not being loaded on RAM after being renderized on the GPU, that’s why getData is not working. Data may be lost before they get processed by the filter.