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?
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.