2

I'm having trouble with a shader in my Phaser game. I've applied the shader as a post pipeline, hoping it would cover the whole screen with a water effect. But when I move my character around, the effect moves too. It's like the shader is stuck to the camera instead of staying put on the screen.

I've put together a quick demo so you can see what I mean. You can use the arrow keys to move around and check out the issue: https://phaser.io/sandbox/vFDT9xXy

Here's the relevant shader code:

const waterFragShader = `
#ifdef GL_ES
precision mediump float;
#endif

uniform float uTime;
uniform vec2 uResolution;
uniform sampler2D uMainSampler;

varying vec2 outTexCoord;

void main(void)
{
    vec2 uv = gl_FragCoord.xy / uResolution.xy;

    vec2 distortion = vec2(
      sin(uv.y * 10.0 + uTime) * 0.002,
      cos(uv.x * 10.0 + uTime) * 0.002
    );

    vec4 texture_color = texture2D(uMainSampler, uv + distortion);

    vec4 k = vec4(uTime)*0.6;
    k.xy = uv * 7.0;
    float val1 = length(0.5-fract(k.xyw*=mat3(vec3(-2.0,-1.0,0.0), vec3(3.0,-1.0,1.0), vec3(1.0,-1.0,-1.0))*0.5));
    float val2 = length(0.5-fract(k.xyw*=mat3(vec3(-2.0,-1.0,0.0), vec3(3.0,-1.0,1.0), vec3(1.0,-1.0,-1.0))*0.2));
    float val3 = length(0.5-fract(k.xyw*=mat3(vec3(-2.0,-1.0,0.0), vec3(3.0,-1.0,1.0), vec3(1.0,-1.0,-1.0))*0.5));

    float pattern = pow(min(min(val1,val2),val3), 7.0) * 2.0;
    vec4 pattern_color = vec4(1, 1, 1, pattern);
    vec4 color = vec4(pattern, pattern, pattern, pattern);

    gl_FragColor = mix(texture_color, pattern_color, pattern_color.a);
}
`;

class WaterPipeline extends Phaser.Renderer.WebGL.Pipelines
    .PostFXPipeline {
    constructor(game) {
        super({
            game,
            name: "Water",
            fragShader: waterFragShader,
        });
    }

    onPreRender() {
        this.set1f("uTime", this.game.loop.time / 1000);
    }

    onDraw(renderTarget) {
        this.set2f("uResolution", renderTarget.width, renderTarget.height);
        this.bindAndDraw(renderTarget);
    }
}

Any ideas on how I can get this shader to stay put and cover the whole screen, no matter where the camera moves?

1 Answer 1

1

Well I'm no shader expert, so take my answer with a grain of salt. I would initially say no it is not really possible, because the distortion is generated from the current image.

But tha I remembered you can can pass variables into the shader, so if you pass the position of the player as an offset, the distortion would seem to be moving.

Short Demo:

(ShowCasing the player offset; use arrow keys to move player = red square)

const waterFragShader = `
#ifdef GL_ES
precision mediump float;
#endif

uniform float uTime;
uniform vec2 uResolution;
uniform sampler2D uMainSampler;

varying vec2 outTexCoord;

// ADDED the OffSet Variable
uniform vec2 offSet; 

void main(void)
{
    vec2 uv = (gl_FragCoord.xy / uResolution.xy) + offSet ;

    vec2 distortion = vec2(
      sin(uv.y * 10.0 + uTime) * 0.002,
      cos(uv.x * 10.0 + uTime) * 0.002
    );

    vec4 texture_color = texture2D(uMainSampler, uv + distortion);

    vec4 k = vec4(uTime)*0.6;
    k.xy = uv * 7.0;
    float val1 = length(0.5-fract(k.xyw*=mat3(vec3(-2.0,-1.0,0.0), vec3(3.0,-1.0,1.0), vec3(1.0,-1.0,-1.0))*0.5));
    float val2 = length(0.5-fract(k.xyw*=mat3(vec3(-2.0,-1.0,0.0), vec3(3.0,-1.0,1.0), vec3(1.0,-1.0,-1.0))*0.2));
    float val3 = length(0.5-fract(k.xyw*=mat3(vec3(-2.0,-1.0,0.0), vec3(3.0,-1.0,1.0), vec3(1.0,-1.0,-1.0))*0.5));

    float pattern = pow(min(min(val1,val2),val3), 7.0) * 2.0;
    vec4 pattern_color = vec4(1, 1, 1, pattern + .2); // added .2 to make it more visible
    vec4 color = vec4(pattern, pattern, pattern, pattern);

    gl_FragColor = mix(texture_color, pattern_color, pattern_color.a);
}
`;

class WaterPipeline extends Phaser.Renderer.WebGL.Pipelines
    .PostFXPipeline {
    constructor(game) {
        super({
            game,
            name: "Water",
            fragShader: waterFragShader,
        });
        // Initialize the OffSet Variable
        this.offset = { x: 10, y: 10 };
    }

    setOffset(x,y){
        // Update OffSet Variable
        this.offset = { x, y};
    }

    onPreRender() {
        this.set1f("uTime", this.game.loop.time / 1000);
        // pass OffSet Variable to shader
        this.set2f("offSet", this.offset.x / 1000, this.offset.y/ 1000);
    }

    onDraw(renderTarget) {
        this.set2f("uResolution", renderTarget.width, renderTarget.height);
        this.bindAndDraw(renderTarget);
    }
}


class MainScene extends Phaser.Scene {

    constructor() {
        super({ key: "MainScene" });
    }

    create() {
        this.renderer.pipelines.addPostPipeline("Water", WaterPipeline);

        this.bg = this.add.rectangle(100, 100, 100, 100, 0x0000ff)
          .setPostPipeline("Water");

        this.player = this.add
            .rectangle(200, 100, 10, 10, 0xff0000)
            .setOrigin(0, 0);

        this.cameras.main.startFollow(this.player)
    }

    update() {
        const cursors = this.input.keyboard.createCursorKeys();
        if (cursors.left.isDown) {
            this.player.x -= 10;
        } else if (cursors.right.isDown) {
            this.player.x += 10;
        } else if (cursors.up.isDown) {
            this.player.y -= 10;
        } else if (cursors.down.isDown) {
            this.player.y += 10;
        }

        // Pass the players position
        this.bg.getPostPipeline("Water").setOffset(this.player.x, this.player.y)
    }

}

var config = {
    width: 540,
    height: 180,
    scene: [MainScene]
}; 

new Phaser.Game(config);

console.clear();
document.body.style = 'margin:0;';
<script src="//cdn.jsdelivr.net/npm/phaser/dist/phaser.min.js"></script>

There might be a better way, and/or de shader code might/will need some more tweaking, but this is the main idea.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks a lot, this is perfect. The only thing I noticed is the vertical movement is inverted but I can sort that. What determines the size of the shader? Would making the size of the shader the same as the image avoid having to offset it?
I think you need to use the offest, even if you change the size, since the shader is caculate based on the position of the gameObject, and this would change relative to the player, but as mentioned before sadly I'm no shader expert. So I could be wrong.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.