Render to Texture: Render Passes

Hey,

In my project, I’ve set up an extra camera that renders to a separate texture I intend to use as a mask. I set this up much like in this example project: PlayCanvas Examples

However, I am in need of further processing this “mask texture”, preferably through multiple render passes. I tried reproducing this example project: PlayCanvas Examples

→ And it works wonderfully! Right until I try to apply the render passes to a different camera than the main. Then I just get lots of “ASSERT FAILED:” error messages and incomplete rendering.

I made an example project here: Test project
Note that if you disable the main camera before runtime (so that the second “mask camera” that are created in the script becomes the new main camera that renders to screen, everything works as expected. The code managing the render passes are in the render-pass.js script, a copy of which follows below.

There’s not a lot of official documentation yet on how set up render passes, so I’m somewhat at a loss… Anyone familiar with how to set up render passes on different cameras than the main camera?

var RenderPass = pc.createScript('renderPass');

// A simple render pass that renders a quad with a shader. The shader tints the source texture.
class RenderPassTint extends pc.RenderPassShaderQuad {
    constructor(device, sourceTexture) {
        super(device);
        this.sourceTexture = sourceTexture;
        this.tint = pc.Color.WHITE.clone();

        this.shader = this.createQuadShader('TintShader', `
            uniform sampler2D sourceTexture;
            uniform vec3 tint;
            varying vec2 uv0;
            void main() {
                vec4 color = texture2D(sourceTexture, uv0);
                gl_FragColor = vec4(color.rgb * tint, color.a);
            }`
        );
    }

    execute() {
        this.device.scope.resolve('sourceTexture').setValue(this.sourceTexture);
        this.device.scope.resolve('tint').setValue([this.tint.r, this.tint.g, this.tint.b]);
        super.execute();
    }
}


// initialize code called once per entity
RenderPass.prototype.initialize = function() {
    let app = this.app;
    let device = app.graphicsDevice;

    // the scene gets rendered to a texture first
        const texture = new pc.Texture(device, {
            name: 'RTTexture',
            width: 128,
            height: 128,
            format: pc.PIXELFORMAT_RGBA8,
            mipmaps: false,
            minFilter: pc.FILTER_LINEAR,
            magFilter: pc.FILTER_LINEAR,
            addressU: pc.ADDRESS_CLAMP_TO_EDGE,
            addressV: pc.ADDRESS_CLAMP_TO_EDGE
        });

        const rt = new pc.RenderTarget({
            colorBuffer: texture,
            depth: false
        });

        // layers used in rendering
        const worldLayer = app.scene.layers.getLayerByName("World");
        const uiLayer = app.scene.layers.getLayerById(pc.LAYERID_UI);


        //Create camera
        const cameraEntity = new pc.Entity('camerEntity');
        cameraEntity.addComponent("camera", {
            clearColorBuffer: false,
            layers: [worldLayer.id, uiLayer.id],
            renderTarget: rt,
            priority: -1
        });
        this.entity.addChild(cameraEntity);


        // use the render pass to render the world and ui layers to the created texture
        const renderPass = new pc.RenderPassForward(app.graphicsDevice, app.scene.layers, app.scene, app.renderer);    // <-- Should use pc.RenderPassColorGrab() instead??
        console.log('render info', app.graphicsDevice, app.scene.layers, app.scene, app.renderer)

        // this render pass resizes the texture to match the size of are on the scene we render to
        renderPass.init(rt);
        renderPass.addLayer(cameraEntity.camera, worldLayer, false);
        renderPass.addLayer(cameraEntity.camera, uiLayer, true);

        // tint pass uses the scene rendered to a texture, and applies a tint to it
        const tintPass = new RenderPassTint(app.graphicsDevice, texture);

        // rendering goes directly to the front-buffer
        tintPass.init(null);

        // assign those two passes to the camera to be used instead of its default rendering
        cameraEntity.camera.renderPasses = [renderPass, tintPass];

        // update things every frame
        let angle = 3;
        app.on("update", function (/** @type {number} */dt) {
            angle += dt;

            // tint color
            tintPass.tint.lerp(pc.Color.YELLOW, pc.Color.CYAN, Math.sin(angle * 0.5) * 0.5 + 0.5);

            // draw texture
            app.drawTexture(-0.6, -0.6, 0.6, 0.6, texture);
        });

};

Well you were on the right track. Sorry for the lack of information, this is still being worked on before a public release. Feel free to use it / test it, that’s much appreciated, but expect bugs / limitations unfortunately.

In this case, you’re hitting a bug. I created an engine fix here: [Fix] Fix to render composiion update if multiple cameras use render passes by mvaligursky · Pull Request #6046 · playcanvas/engine · GitHub

So for now you might need to build a local version of the engine to get this going.

During testing I also did few small updates to your script to make sure all works well, see it here:
https://playcanvas.com/project/1188954/overview/test--render-pass--fork

This is what I get now when running against the local engine with the fix:

1 Like

Woah, sweet - Thx!!

Any idea when this fix will be released?

Also, I must say, I’m really exited for official render pass support!

1 Like

We’ve release new engine on Monday (to the Editor), so are waiting for more issues from the customers … and then we’d release the patch. In about a week or so most likely.

1 Like

Neat!
Then I’ll just wait for that :slight_smile: