In the latest version of PlayCanvas, the post-effect does not work properly

The “vignette” post-effect works incorrectly. This problem appeared only today, before everything worked correctly. If this script attached to the camera is turned off and then on after initialization, it returns to normal behavior. I guess this may be a problem with the release of the latest version of PlayCanvas.

Code of “vignette” post-effect:

//--------------- POST EFFECT DEFINITION ------------------------//
pc.extend(pc, function () {

    /**
     * @name pc.VignetteEffect
     * @class Implements the VignetteEffect post processing effect.
     * @extends pc.PostEffect
     * @param {pc.GraphicsDevice} graphicsDevice The graphics device of the application
     * @property {Number} offset Controls the offset of the effect.
     * @property {Number} darkness Controls the darkness of the effect.
     */
    var VignetteEffect = function (graphicsDevice) {
        // Shaders
        var attributes = {
            aPosition: pc.SEMANTIC_POSITION
        };

        var passThroughVert = [
            "attribute vec2 aPosition;",
            "",
            "varying vec2 vUv0;",
            "",
            "void main(void)",
            "{",
            "    gl_Position = vec4(aPosition, 0.0, 1.0);",
            "    vUv0 = (aPosition.xy + 1.0) * 0.5;",
            "}"
        ].join("\n");

        var luminosityFrag = [
            "precision " + graphicsDevice.precision + " float;",
            "",
            "uniform sampler2D uColorBuffer;",
            "uniform float uDarkness;",
            "uniform float uOffset;",
            "",
            "varying vec2 vUv0;",
            "",
            "void main() {",
            "    vec4 texel = texture2D(uColorBuffer, vUv0);",
            "    vec2 uv = (vUv0 - vec2(0.5)) * vec2(uOffset);",
            "    gl_FragColor = vec4(mix(texel.rgb, vec3(1.0 - uDarkness), dot(uv, uv)), texel.a);",
            "}"
        ].join("\n");

        this.vignetteShader = new pc.Shader(graphicsDevice, {
            attributes: attributes,
            vshader: passThroughVert,
            fshader: luminosityFrag
        });

        this.offset = 1;
        this.darkness = 1;
    };

    VignetteEffect = pc.inherits(VignetteEffect, pc.PostEffect);

    VignetteEffect.prototype = pc.extend(VignetteEffect, {
        render: function (inputTarget, outputTarget, rect) {
            var device = this.device;
            var scope = device.scope;

            scope.resolve("uColorBuffer").setValue(inputTarget.colorBuffer);
            scope.resolve("uOffset").setValue(this.offset);
            scope.resolve("uDarkness").setValue(this.darkness);
            pc.drawFullscreenQuad(device, outputTarget, this.vertexBuffer, this.vignetteShader, rect);
        }
    });

    return {
        VignetteEffect: VignetteEffect
    };
}());


//--------------- SCRIPT DEFINITION------------------------//
var Vignette = pc.createScript('vignette');

Vignette.attributes.add('offset', {
    type: 'number',
    default: 1,
    min: 0,
    precision: 5,
    title: 'Offset'
});

Vignette.attributes.add('darkness', {
    type: 'number',
    default: 1,
    precision: 5,
    title: 'Darkness'
});

// initialize code called once per entity
Vignette.prototype.initialize = function() {
    this.effect = new pc.VignetteEffect(this.app.graphicsDevice);
    this.effect.offset = this.offset;
    this.effect.darkness = this.darkness;

    this.on('attr', function (name, value) {
        this.effect[name] = value;
    }, this);

    var queue = this.entity.camera.postEffects;
    queue.addEffect(this.effect);

    this.on('state', function (enabled) {
        if (enabled) {
            queue.addEffect(this.effect);
        } else {
            queue.removeEffect(this.effect);
        }
    });

    this.on('destroy', function () {
        queue.removeEffect(this.effect);
    });
};

Screenshots of how it looked before and now:

Hi @Yurii,

I’ve grabbed an example from the tutorials section and added the effect directly from the github engine repo and it seems to work:

The script is enabled by default when the project launches. Maybe it’s something specific with your project?

Are you using multiple cameras?

No, I’m using one camera, but it has several different post-effects. I also noticed that if turned off some post-effects are then turned on, not all of them will work properly, some will not work at all.

I think there are generally some issues with disabling/enabling the effects.

Would you be able to create a reproducible project of the issue you are getting please?

@mvaligursky is this an issue you are aware of/currently looking at?

I found that the behavior has changed inside the fragment shader, which was used with post-effects composition:

// --------------- POST EFFECT DEFINITION --------------- //
Object.assign(pc, function () {

    var CompositionEffect = function (graphicsDevice, vs, fs) {
        pc.PostEffect.call(this, graphicsDevice);

        var fragmentShader = "precision " + graphicsDevice.precision + " float;\n";
        fragmentShader = fragmentShader + fs;
        
        // this is the shader definition for our effect
        this.shader = new pc.Shader(graphicsDevice, {
            attributes: {
                aPosition: pc.SEMANTIC_POSITION
            },
            vshader: vs,
            fshader: fs
        });

        // Uniforms
        this.texture = new pc.Texture(graphicsDevice);
        this.texture2 = new pc.Texture(graphicsDevice);
    };

    CompositionEffect.prototype = Object.create(pc.PostEffect.prototype);
    CompositionEffect.prototype.constructor = CompositionEffect;

    Object.assign(CompositionEffect.prototype, {
        render: function (inputTarget, outputTarget, rect) {
            var device = this.device;
            var scope = device.scope;

            scope.resolve("uWidth").setValue(inputTarget.width);
            scope.resolve("uHeight").setValue(inputTarget.height);
            scope.resolve("uColorBuffer").setValue(inputTarget.colorBuffer);
            scope.resolve("uOutlineTex").setValue(this.texture);
            scope.resolve("uOutlineTex2").setValue(this.texture2);
            pc.drawFullscreenQuad(device, outputTarget, this.vertexBuffer, this.shader, rect);
        }
    });

    return {
        CompositionEffect: CompositionEffect
    };
}());

// ----------------- SCRIPT DEFINITION ------------------ //
var PosteffectComposition = pc.createScript('posteffectComposition');


PosteffectComposition.attributes.add('texture', {
    type: 'asset',
    assetType: 'texture',
    title: 'Texture'
});

PosteffectComposition.attributes.add('texture2', {
    type: 'asset',
    assetType: 'texture',
    title: 'Texture2'
});

PosteffectComposition.prototype.initialize = function () {
    this.effect = new pc.CompositionEffect(this.app.graphicsDevice);
    this.effect.texture = this.texture.resource;
    this.effect.texture2 = this.texture2.resource;

    var queue = this.entity.camera.postEffects;

    queue.addEffect(this.effect);

    this.on('state', function (enabled) {
        if (enabled) {
            queue.addEffect(this.effect);
        } else {
            queue.removeEffect(this.effect);
        }
    });

    this.on('destroy', function () {
        queue.removeEffect(this.effect);
    });


    this.on('attr:texture', function (value) {
        this.effect.texture = value ? value.resource : null;
    }, this);
    
    this.on('attr:texture2', function (value) {
        this.effect.texture2 = value ? value.resource : null;
    }, this);
};

I use this script to apply post-effects composition :

var ApplyPosteffectComposition = pc.createScript('applyPosteffectComposition');

ApplyPosteffectComposition.attributes.add('vs', {
    type: 'asset',
    assetType: 'shader',
    title: 'Vertex Shader'
});

ApplyPosteffectComposition.attributes.add('fs', {
    type: 'asset',
    assetType: 'shader',
    title: 'Fragment Shader'
});

// initialize code called once per entity
ApplyPosteffectComposition.prototype.initialize = function() {
  
    // --- variables
    this.vec = new pc.Vec3();
    
    // --- execute
    this.prepare();
    

};

ApplyPosteffectComposition.prototype.prepare = function() {

    // create texture and render target for rendering into, including depth buffer
    this.texture = new pc.Texture(this.app.graphicsDevice, {
        width: this.app.graphicsDevice.width,
        height: this.app.graphicsDevice.height,
        format: pc.PIXELFORMAT_R8_G8_B8_A8,
        autoMipmap: true,
        minFilter: pc.FILTER_LINEAR,
        magFilter: pc.FILTER_LINEAR
    });
    
    this.texture2 = new pc.Texture(this.app.graphicsDevice, {
        width: this.app.graphicsDevice.width,
        height: this.app.graphicsDevice.height,
        format: pc.PIXELFORMAT_R8_G8_B8_A8,
        autoMipmap: true,
        minFilter: pc.FILTER_LINEAR,
        magFilter: pc.FILTER_LINEAR
    });
    
    this.renderTarget = new pc.RenderTarget(this.app.graphicsDevice, this.texture, { depth: true });
    this.renderTarget2 = new pc.RenderTarget(this.app.graphicsDevice, this.texture2, { depth: true });

    // get layers
    this.worldLayer = this.app.scene.layers.getLayerByName("World");
    this.compositionLayer = this.app.scene.layers.getLayerByName("Layer 2");
    this.headLayer1 = this.app.scene.layers.getLayerByName("Layer 1");

    // set up layer to render to the render target
    this.compositionLayer.renderTarget = this.renderTarget;
    this.headLayer1.renderTarget = this.renderTarget2;
    
    // Create outline camera, which renders entities in outline layer
    this.posteffectCamera = new pc.Entity();
    
    this.posteffectCamera.addComponent("camera", {
        clearColor: new pc.Color(0.0, 0.0, 0.0, 0.0),
        layers: [this.compositionLayer.id],
        fov : this.entity.camera.fov,
        horizontalFov : this.entity.camera.horizontalFov,
        aspectRatio : this.entity.camera.aspectRatio
    });    
    this.app.root.addChild(this.posteffectCamera);    
    

    
    // Create outline camera, which renders entities in outline layer
    this.posteffectCamera2 = new pc.Entity();
    
    this.posteffectCamera2.addComponent("camera", {
        clearColor: new pc.Color(1.0, 0.0, 0.0, 0.0),
        layers: [this.headLayer1.id],
        fov : this.entity.camera.fov,
        horizontalFov : this.entity.camera.horizontalFov,
        aspectRatio : this.entity.camera.aspectRatio
    });    
    this.app.root.addChild(this.posteffectCamera2);    
    
    
    // instanciate outline post process effect
    this.composition = new pc.CompositionEffect(this.app.graphicsDevice, this.vs.resource, this.fs.resource);
    this.composition.texture = this.texture;
    this.composition.texture2 = this.texture2;
    this.entity.camera.postEffects.addEffect(this.composition);
};


// update code called every frame
ApplyPosteffectComposition.prototype.update = function(dt) {
    
    var transform = this.entity.getWorldTransform();

    this.posteffectCamera.setPosition(transform.getTranslation());        
    this.posteffectCamera.setEulerAngles(transform.getEulerAngles());
    this.posteffectCamera.camera.fov = this.entity.camera.fov;
    this.posteffectCamera.camera.horizontalFov = this.entity.camera.horizontalFov;
    this.posteffectCamera.camera.aspectRatio = this.entity.camera.aspectRatio;
    
    
    this.posteffectCamera2.setPosition(transform.getTranslation());        
    this.posteffectCamera2.setEulerAngles(transform.getEulerAngles());
    this.posteffectCamera2.camera.fov = this.entity.camera.fov;
    this.posteffectCamera2.camera.horizontalFov = this.entity.camera.horizontalFov;
    this.posteffectCamera2.camera.aspectRatio = this.entity.camera.aspectRatio;
    

};

In general I’m looking at post-effects, and making them more compatible with some layer changes recently and also with multiple cameras.

You mention you don’t use multiple cameras, but your script has these two lines, which does look like two cameras, so this is likely related to my work to help this out:

this.posteffectCamera.addComponent("camera", {
this.posteffectCamera2.addComponent("camera", {