Revised FX pipeline. Added Glow, Shadow and Pixelate. Tested against multi-pass.

This commit is contained in:
Richard Davey 2023-02-13 18:50:27 +00:00
parent c09ce7b2e9
commit 532d5a9375
13 changed files with 558 additions and 168 deletions

View file

@ -4,6 +4,10 @@
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Glow = require('../fx/Glow');
var Shadow = require('../fx/Shadow');
var Pixelate = require('../fx/Pixelate');
/**
* Provides methods used for setting the FX values of a Game Object.
* Should be applied as a mixin and not used directly.
@ -93,28 +97,8 @@ var FX = {
{
},
enableFX: function ()
enableFX: function (padding)
{
this.fx = {
glow: {
quality: 0.1,
distance: 10,
outerStrength: 4,
innerStrength: 0,
knockout: false,
color: [ 1, 1, 1, 1 ]
},
shadow: {
x: 0,
y: 0,
decay: 0.1,
power: 1.0,
shadowColor: [ 0, 0, 0, 1 ],
samples: 12,
intensity: 1
}
};
var renderer = this.scene.sys.renderer;
if (!renderer)
@ -132,9 +116,66 @@ var FX = {
this.pipeline = pipeline;
if (!this.fx)
{
this.fx = [];
}
if (padding !== undefined)
{
this.fxPadding = padding;
}
return this;
},
clearFX: function ()
{
// Remove them all
},
removeFX: function (fx)
{
// Remove specific fx
},
disableFX: function (clear)
{
this.resetPipeline();
if (clear)
{
this.clearFX();
}
},
addGlowFX: function ()
{
var fx = new Glow(this);
this.fx.push(fx);
return fx;
},
addShadowFX: function ()
{
var fx = new Shadow(this);
this.fx.push(fx);
return fx;
},
addPixelateFX: function ()
{
var fx = new Pixelate(this);
this.fx.push(fx);
return fx;
},
addBloom: function (r, g, b)
{
}

View file

@ -0,0 +1,73 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2013-2023 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../utils/Class');
var FX_CONST = require('./const');
/**
* @classdesc
*
* @class Glow
* @memberof Phaser.GameObjects.FX
* @constructor
* @since 3.60.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - A reference to the Game Object that has this fx.
*/
var Glow = new Class({
initialize:
function Glow (gameObject)
{
this.type = FX_CONST.GLOW;
this.gameObject = gameObject;
this.active = true;
this.distance = 16;
this.outerStrength = 4;
this.innerStrength = 0;
this.knockout = false;
this._color = [ 1, 1, 1, 1 ];
},
/**
* The color of the glow.
*
* @name Phaser.GameObjects.FX.Glow#color
* @type {number}
* @since 3.60.0
*/
color: {
get: function ()
{
var color = this._color;
return (((color[0] * 255) << 16) + ((color[1] * 255) << 8) + (color[2] * 255 | 0));
},
set: function (value)
{
var color = this._color;
color[0] = ((value >> 16) & 0xFF) / 255;
color[1] = ((value >> 8) & 0xFF) / 255;
color[2] = (value & 0xFF) / 255;
}
},
destroy: function ()
{
this.gameObject = null;
}
});
module.exports = Glow;

View file

@ -0,0 +1,42 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2013-2023 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../utils/Class');
var FX_CONST = require('./const');
/**
* @classdesc
*
* @class Pixelate
* @memberof Phaser.GameObjects.FX
* @constructor
* @since 3.60.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - A reference to the Game Object that has this fx.
*/
var Pixelate = new Class({
initialize:
function Pixelate (gameObject)
{
this.type = FX_CONST.PIXELATE;
this.gameObject = gameObject;
this.active = true;
this.amount = 1;
},
destroy: function ()
{
this.gameObject = null;
}
});
module.exports = Pixelate;

View file

@ -0,0 +1,75 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2013-2023 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../utils/Class');
var FX_CONST = require('./const');
/**
* @classdesc
*
* @class Shadow
* @memberof Phaser.GameObjects.FX
* @constructor
* @since 3.60.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - A reference to the Game Object that has this fx.
*/
var Shadow = new Class({
initialize:
function Shadow (gameObject)
{
this.type = FX_CONST.SHADOW;
this.gameObject = gameObject;
this.active = true;
this.x = 0;
this.y = 0;
this.decay = 0.1;
this.power = 1.0;
this._color = [ 0, 0, 0, 1 ];
this.samples = 6; // max 12, min 1
this.intensity = 1;
},
/**
* The color of the shadow.
*
* @name Phaser.GameObjects.FX.Shadow#color
* @type {number}
* @since 3.60.0
*/
color: {
get: function ()
{
var color = this._color;
return (((color[0] * 255) << 16) + ((color[1] * 255) << 8) + (color[2] * 255 | 0));
},
set: function (value)
{
var color = this._color;
color[0] = ((value >> 16) & 0xFF) / 255;
color[1] = ((value >> 8) & 0xFF) / 255;
color[2] = (value & 0xFF) / 255;
}
},
destroy: function ()
{
this.gameObject = null;
}
});
module.exports = Shadow;

View file

@ -0,0 +1,41 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2013-2023 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var FX_CONST = {
/**
* The Glow FX.
*
* @name Phaser.GameObjects.FX.GLOW
* @type {number}
* @const
* @since 3.60.0
*/
GLOW: 4,
/**
* The Shadow FX.
*
* @name Phaser.GameObjects.FX.SHADOW
* @type {number}
* @const
* @since 3.60.0
*/
SHADOW: 5,
/**
* The Pixelate FX.
*
* @name Phaser.GameObjects.FX.PIXELATE
* @type {number}
* @const
* @since 3.60.0
*/
PIXELATE: 6
};
module.exports = FX_CONST;

View file

@ -5,9 +5,10 @@
*/
var Class = require('../../../utils/Class');
var PreFXPipeline = require('./PreFXPipeline');
var BloomFrag = require('../shaders/FXBloom-frag.js');
var FX_CONST = require('../../../gameobjects/fx/const');
var GlowFrag = require('../shaders/FXGlow-frag.js');
var PixelateFrag = require('../shaders/FXPixelate-frag.js');
var PreFXPipeline = require('./PreFXPipeline');
var ShadowFrag = require('../shaders/FXShadow-frag.js');
var SingleQuadVS = require('../shaders/Single-vert.js');
@ -30,54 +31,17 @@ var FXPipeline = new Class({
function FXPipeline (config)
{
var shaders = [];
var fragShader = GlowFrag;
var quality = 0.1;
var distance = 10;
fragShader = fragShader.replace(/__SIZE__/gi, `${(1 / quality / distance).toFixed(7)}`);
fragShader = fragShader.replace(/__DIST__/gi, `${distance.toFixed(0)}.0`);
shaders.push({
name: 'Glow',
fragShader: fragShader,
vertShader: SingleQuadVS
});
fragShader = ShadowFrag;
var samples = 12;
fragShader = fragShader.replace(/__SAMPLES__/gi, samples.toFixed(0));
shaders.push({
name: 'Shadow',
fragShader: fragShader,
vertShader: SingleQuadVS
});
config.shaders = shaders;
config.shaders = [
{ name: 'Glow', fragShader: GlowFrag, vertShader: SingleQuadVS },
{ name: 'Shadow', fragShader: ShadowFrag, vertShader: SingleQuadVS },
{ name: 'Pixelate', fragShader: PixelateFrag, vertShader: SingleQuadVS }
];
PreFXPipeline.call(this, config);
this.bloom = {
steps: 4,
offsetX: 2,
offsetY: 3,
blurStrength: 1,
strength: 1.5,
color: [ 1, 1, 1 ]
};
this.glow = {
quality: 0.1,
distance: 10,
outerStrength: 4,
innerStrength: 0,
knockout: false,
color: [ 1, 1, 1, 1 ]
};
this.source;
this.target;
this.swap;
},
boot: function ()
@ -85,60 +49,130 @@ var FXPipeline = new Class({
PreFXPipeline.prototype.boot.call(this);
console.log(this.shaders);
},
onDraw: function (target1, target2, target3)
{
// tempSprite = current sprite being drawn
this.source = target1;
this.target = target2;
this.swap = target3;
this.setShader(this.shaders[4]);
var drawn = false;
var sprite = this.tempSprite;
var glow = this.tempSprite.fx.glow;
this.set1f('outerStrength', glow.outerStrength);
this.set1f('innerStrength', glow.innerStrength);
this.set4fv('glowColor', glow.color);
this.setBoolean('knockout', glow.knockout);
this.set2f('resolution', target1.width, target1.height);
this.copy(target1, target2);
this.setShader(this.shaders[5]);
var shadow = this.tempSprite.fx.shadow;
this.set1f('intensity', shadow.intensity);
this.set1f('decay', shadow.decay);
this.set1f('power', shadow.power / shadow.samples);
this.set2f('lightPosition', shadow.x, shadow.y);
this.set4fv('shadowColor', shadow.shadowColor);
this.copy(target2, target1);
this.drawToGame(target1);
},
onBloomDraw: function (target1, target2, target3)
{
this.manager.copyFrame(target1, target3);
var x = (2 / target1.width) * this.offsetX;
var y = (2 / target1.height) * this.offsetY;
for (var i = 0; i < this.steps; i++)
if (sprite && sprite.fx)
{
this.set2f('uOffset', x, 0);
this.copySprite(target1, target2);
for (var i = 0; i < sprite.fx.length; i++)
{
var fx = sprite.fx[i];
this.set2f('uOffset', 0, y);
this.copySprite(target2, target1);
if (fx.active)
{
switch (fx.type)
{
case FX_CONST.GLOW:
this.onGlowDraw(fx);
drawn = true;
break;
case FX_CONST.SHADOW:
this.onShadowDraw(fx);
drawn = true;
break;
case FX_CONST.PIXELATE:
this.onPixelateDraw(fx);
drawn = true;
break;
}
}
}
}
this.blendFrames(target3, target1, target2, this.strength);
if (!drawn)
{
this.source = target1;
}
this.copyToGame(target2);
}
this.drawToGame(this.source);
},
onGlowDraw: function (config)
{
var source = this.source;
var target = this.target;
this.setShader(this.shaders[FX_CONST.GLOW]);
this.set1f('distance', config.distance);
this.set1f('outerStrength', config.outerStrength);
this.set1f('innerStrength', config.innerStrength);
this.set4fv('glowColor', config._color);
this.setBoolean('knockout', config.knockout);
this.set2f('resolution', source.width, source.height);
this.copy(source, target);
this.source = target;
this.target = source;
},
onShadowDraw: function (config)
{
var source = this.source;
var target = this.target;
this.setShader(this.shaders[FX_CONST.SHADOW]);
this.set1i('samples', config.samples);
this.set1f('intensity', config.intensity);
this.set1f('decay', config.decay);
this.set1f('power', config.power / config.samples);
this.set2f('lightPosition', config.x, config.y);
this.set4fv('color', config._color);
this.copy(source, target);
this.source = target;
this.target = source;
},
onPixelateDraw: function (config)
{
var source = this.source;
var target = this.target;
this.setShader(this.shaders[FX_CONST.PIXELATE]);
this.set1f('amount', config.amount);
this.set2f('resolution', source.width, source.height);
this.copy(source, target);
this.source = target;
this.target = source;
},
// onBloomDraw: function (target1, target2, target3)
// {
// this.manager.copyFrame(target1, target3);
// var x = (2 / target1.width) * this.offsetX;
// var y = (2 / target1.height) * this.offsetY;
// for (var i = 0; i < this.steps; i++)
// {
// this.set2f('uOffset', x, 0);
// this.copySprite(target1, target2);
// this.set2f('uOffset', 0, y);
// this.copySprite(target2, target1);
// }
// this.blendFrames(target3, target1, target2, this.strength);
// this.copyToGame(target2);
// }
});

View file

@ -566,25 +566,22 @@ var PreFXPipeline = new Class({
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
},
// cls: function (target1, target2, target3)
// {
// var gl = this.gl;
// gl.clearColor(0, 0, 0, 0);
// gl.viewport(0, 0, target1.width, target1.height);
// gl.bindFramebuffer(gl.FRAMEBUFFER, target1.framebuffer);
// gl.clear(gl.COLOR_BUFFER_BIT);
// gl.bindFramebuffer(gl.FRAMEBUFFER, target2.framebuffer);
// gl.clear(gl.COLOR_BUFFER_BIT);
// gl.bindFramebuffer(gl.FRAMEBUFFER, target3.framebuffer);
// gl.clear(gl.COLOR_BUFFER_BIT);
// gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// },
/**
* Draws the `source` Render Target to the `target` Render Target.
*
* This is done using whatever the currently bound shader is. This method does
* not set a shader. All it does is bind the source texture, set the viewport and UVs
* then bind the target framebuffer, clears it and draws the source to it.
*
* At the end a null framebuffer is bound. No other clearing-up takes place, so
* use this method carefully.
*
* @method Phaser.Renderer.WebGL.Pipelines.PreFXPipeline#copy
* @since 3.60.0
*
* @param {Phaser.Renderer.WebGL.RenderTarget} source - The source Render Target.
* @param {Phaser.Renderer.WebGL.RenderTarget} target - The target Render Target.
*/
copy: function (source, target)
{
var gl = this.gl;

View file

@ -7,6 +7,7 @@ module.exports = [
'',
'varying vec2 outTexCoord;',
'',
'uniform float distance;',
'uniform float outerStrength;',
'uniform float innerStrength;',
'uniform vec2 resolution;',
@ -14,12 +15,8 @@ module.exports = [
'uniform bool knockout;',
'',
'const float PI = 3.14159265358979323846264;',
'',
'const float DIST = __DIST__;',
'const float SIZE = min(__SIZE__, PI * 2.0);',
'const float NUM = ceil(PI * 2.0 / SIZE);',
'',
'const float MAX_TOTAL_ALPHA = NUM * DIST * (DIST + 1.0) / 2.0;',
'const float MAX_DIST = 128.0;',
'const float SIZE = 0.15;',
'',
'void main ()',
'{',
@ -34,24 +31,30 @@ module.exports = [
' {',
' direction = vec2(cos(angle), sin(angle)) * px;',
'',
' for (float curDistance = 0.0; curDistance < DIST; curDistance++)',
' for (float curDistance = 0.0; curDistance < MAX_DIST; curDistance++)',
' {',
' if (curDistance >= distance)',
' {',
' break;',
' }',
'',
' displaced = outTexCoord + direction * (curDistance + 1.0);',
'',
' totalAlpha += (DIST - curDistance) * texture2D(uMainSampler, displaced).a;',
' totalAlpha += (distance - curDistance) * texture2D(uMainSampler, displaced).a;',
' }',
' }',
'',
' vec4 curColor = texture2D(uMainSampler, outTexCoord);',
'',
' float alphaRatio = (totalAlpha / MAX_TOTAL_ALPHA);',
' float maxAlpha = 42.0 * distance * (distance + 1.0) / 2.0;',
' float alphaRatio = (totalAlpha / maxAlpha);',
'',
' float innerGlowAlpha = (1.0 - alphaRatio) * innerStrength * curColor.a;',
' float innerGlowStrength = min(1.0, innerGlowAlpha);',
'',
' vec4 innerColor = mix(curColor, glowColor, innerGlowStrength);',
'',
' float outerGlowAlpha = alphaRatio * outerStrength * (1. - curColor.a);',
' float outerGlowAlpha = alphaRatio * outerStrength * (1.0 - curColor.a);',
' float outerGlowStrength = min(1.0 - innerColor.a, outerGlowAlpha);',
'',
' vec4 outerGlowColor = outerGlowStrength * glowColor.rgba;',
@ -59,6 +62,7 @@ module.exports = [
' if (knockout)',
' {',
' float resultAlpha = outerGlowAlpha + innerGlowAlpha;',
'',
' gl_FragColor = vec4(glowColor.rgb * resultAlpha, resultAlpha);',
' }',
' else',

View file

@ -0,0 +1,33 @@
module.exports = [
'#define SHADER_NAME PIXELATE_FS',
'',
'precision mediump float;',
'',
'uniform sampler2D uMainSampler;',
'uniform vec2 resolution;',
'uniform float amount;',
'',
'varying vec2 outTexCoord;',
'',
'void main ()',
'{',
' float pixelSize = floor(2.0 + amount);',
'',
' vec2 center = pixelSize * floor(outTexCoord * resolution / pixelSize) + pixelSize * vec2(0.5, 0.5);',
'',
' vec2 corner1 = center + pixelSize * vec2(-0.5, -0.5);',
' vec2 corner2 = center + pixelSize * vec2(+0.5, -0.5);',
' vec2 corner3 = center + pixelSize * vec2(+0.5, +0.5);',
' vec2 corner4 = center + pixelSize * vec2(-0.5, +0.5);',
'',
' vec4 pixel = 0.4 * texture2D(uMainSampler, center / resolution);',
'',
' pixel += 0.15 * texture2D(uMainSampler, corner1 / resolution);',
' pixel += 0.15 * texture2D(uMainSampler, corner2 / resolution);',
' pixel += 0.15 * texture2D(uMainSampler, corner3 / resolution);',
' pixel += 0.15 * texture2D(uMainSampler, corner4 / resolution);',
'',
' gl_FragColor = pixel;',
'}',
''
].join('\n');

View file

@ -4,15 +4,17 @@ module.exports = [
'precision mediump float;',
'',
'uniform sampler2D uMainSampler;',
'uniform vec2 lightPosition;',
'uniform vec4 shadowColor;',
'uniform float decay;',
'uniform float power;',
'uniform float intensity;',
'',
'varying vec2 outTexCoord;',
'',
'const int SAMPLES = __SAMPLES__;',
'uniform vec2 lightPosition;',
'uniform vec4 color;',
'uniform float decay;',
'uniform float power;',
'uniform float intensity;',
'uniform int samples;',
'',
'const int MAX = 12;',
'',
'void main ()',
'{',
@ -21,15 +23,21 @@ module.exports = [
' vec2 pc = (lightPosition - outTexCoord) * intensity;',
'',
' float shadow = 0.0;',
' float limit = max(float(MAX), float(samples));',
'',
' for (int i = 0; i < SAMPLES; ++i)',
' for (int i = 0; i < MAX; ++i)',
' {',
' shadow += texture2D(uMainSampler, outTexCoord + float(i) * decay / float(SAMPLES) * pc).a * power;',
' if (i >= samples)',
' {',
' break;',
' }',
'',
' shadow += texture2D(uMainSampler, outTexCoord + float(i) * decay / limit * pc).a * power;',
' }',
'',
' float mask = 1.0 - texture.a;',
'',
' gl_FragColor = mix(texture, shadowColor, shadow * mask);',
' gl_FragColor = mix(texture, color, shadow * mask);',
'}',
''
].join('\n');

View file

@ -6,6 +6,7 @@ uniform sampler2D uMainSampler;
varying vec2 outTexCoord;
uniform float distance;
uniform float outerStrength;
uniform float innerStrength;
uniform vec2 resolution;
@ -13,12 +14,8 @@ uniform vec4 glowColor;
uniform bool knockout;
const float PI = 3.14159265358979323846264;
const float DIST = __DIST__;
const float SIZE = min(__SIZE__, PI * 2.0);
const float NUM = ceil(PI * 2.0 / SIZE);
const float MAX_TOTAL_ALPHA = NUM * DIST * (DIST + 1.0) / 2.0;
const float MAX_DIST = 128.0;
const float SIZE = 0.15;
void main ()
{
@ -33,24 +30,30 @@ void main ()
{
direction = vec2(cos(angle), sin(angle)) * px;
for (float curDistance = 0.0; curDistance < DIST; curDistance++)
for (float curDistance = 0.0; curDistance < MAX_DIST; curDistance++)
{
if (curDistance >= distance)
{
break;
}
displaced = outTexCoord + direction * (curDistance + 1.0);
totalAlpha += (DIST - curDistance) * texture2D(uMainSampler, displaced).a;
totalAlpha += (distance - curDistance) * texture2D(uMainSampler, displaced).a;
}
}
vec4 curColor = texture2D(uMainSampler, outTexCoord);
float alphaRatio = (totalAlpha / MAX_TOTAL_ALPHA);
float maxAlpha = 42.0 * distance * (distance + 1.0) / 2.0;
float alphaRatio = (totalAlpha / maxAlpha);
float innerGlowAlpha = (1.0 - alphaRatio) * innerStrength * curColor.a;
float innerGlowStrength = min(1.0, innerGlowAlpha);
vec4 innerColor = mix(curColor, glowColor, innerGlowStrength);
float outerGlowAlpha = alphaRatio * outerStrength * (1. - curColor.a);
float outerGlowAlpha = alphaRatio * outerStrength * (1.0 - curColor.a);
float outerGlowStrength = min(1.0 - innerColor.a, outerGlowAlpha);
vec4 outerGlowColor = outerGlowStrength * glowColor.rgba;
@ -58,6 +61,7 @@ void main ()
if (knockout)
{
float resultAlpha = outerGlowAlpha + innerGlowAlpha;
gl_FragColor = vec4(glowColor.rgb * resultAlpha, resultAlpha);
}
else

View file

@ -0,0 +1,30 @@
#define SHADER_NAME PIXELATE_FS
precision mediump float;
uniform sampler2D uMainSampler;
uniform vec2 resolution;
uniform float amount;
varying vec2 outTexCoord;
void main ()
{
float pixelSize = floor(2.0 + amount);
vec2 center = pixelSize * floor(outTexCoord * resolution / pixelSize) + pixelSize * vec2(0.5, 0.5);
vec2 corner1 = center + pixelSize * vec2(-0.5, -0.5);
vec2 corner2 = center + pixelSize * vec2(+0.5, -0.5);
vec2 corner3 = center + pixelSize * vec2(+0.5, +0.5);
vec2 corner4 = center + pixelSize * vec2(-0.5, +0.5);
vec4 pixel = 0.4 * texture2D(uMainSampler, center / resolution);
pixel += 0.15 * texture2D(uMainSampler, corner1 / resolution);
pixel += 0.15 * texture2D(uMainSampler, corner2 / resolution);
pixel += 0.15 * texture2D(uMainSampler, corner3 / resolution);
pixel += 0.15 * texture2D(uMainSampler, corner4 / resolution);
gl_FragColor = pixel;
}

View file

@ -3,15 +3,17 @@
precision mediump float;
uniform sampler2D uMainSampler;
uniform vec2 lightPosition;
uniform vec4 shadowColor;
uniform float decay;
uniform float power;
uniform float intensity;
varying vec2 outTexCoord;
const int SAMPLES = __SAMPLES__;
uniform vec2 lightPosition;
uniform vec4 color;
uniform float decay;
uniform float power;
uniform float intensity;
uniform int samples;
const int MAX = 12;
void main ()
{
@ -20,13 +22,19 @@ void main ()
vec2 pc = (lightPosition - outTexCoord) * intensity;
float shadow = 0.0;
float limit = max(float(MAX), float(samples));
for (int i = 0; i < SAMPLES; ++i)
for (int i = 0; i < MAX; ++i)
{
shadow += texture2D(uMainSampler, outTexCoord + float(i) * decay / float(SAMPLES) * pc).a * power;
if (i >= samples)
{
break;
}
shadow += texture2D(uMainSampler, outTexCoord + float(i) * decay / limit * pc).a * power;
}
float mask = 1.0 - texture.a;
gl_FragColor = mix(texture, shadowColor, shadow * mask);
gl_FragColor = mix(texture, color, shadow * mask);
}