Skip to content

Instantly share code, notes, and snippets.

@ssube
Last active June 25, 2020 05:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssube/96e9db52bbb6e310fc4096872ed027bb to your computer and use it in GitHub Desktop.
Save ssube/96e9db52bbb6e310fc4096872ed027bb to your computer and use it in GitHub Desktop.
phaser-post

Phaser PostFX

A flexible, layered post-processing system for PhaserJS 3. Apply fullscreen shaders to select scene elements.

Contents

Features

  • multiple input and output textures
    • offscreen buffers
    • access to textures loaded by Phaser
  • reusable effects composed into layers
    • set uniforms per layer

Caveats

  • creates N+2 screen-sized textures for N effects
    • this can potentially be reduced to 2 or 3 (no N) by calling buffer.setPipeline() before stage.draw(buffer)
  • generating mipmaps for NP2 textures require a WebGL2 context
  • the exposure effect requires WebGL2
    • the Phaser game must be created with the same WebGL2 context
    • textureLod GLSL function requires #version 300 es shaders
    • the fragment and vertex program version must match within the same layer (a 300 es vertex shader is provided)
      • mixing shader versions on different layers is allowed

TODO

  • more effects
  • support binding sampler to TEXTURE0 other than stage buffer (bound by draw call)

API

EffectPipeline

Tint-based pipeline for shader effects with texture caching, sampler binding, and other niceties.

EffectPipeline.cacheTextures

Look up required textures from buffers and the scene's texture manager, and cache them with the uniform name to which they will be bound.

PostProcessor

Manages the buffers and pipelines for post-processing a particular scene. Methods match the scene lifecycle.

new PostProcessor

Create a new post-processing pipeline from data and bound to the scene.

PostProcessor.create

Set up the post-processing pipelines and render textures, compiling shaders and caching textures.

PostProcessor.update

Render a list of objects using the post-processing layers positioned for the camera.

PostProcessor.stop

Shut down the post-processing pipelines and clean up cached textures and WebGL resources.

Data

The data structure used to initialize a PostProcessor instance describes the layers and effects they use. An example is attached.

  • name
  • buffers
  • effects
  • layers
  • mipmaps
  • screen

Data buffers

Additional offscreen buffers to create. The scene and stage buffers will always be created.

  • name
  • size
    • x
    • y

Data effects

The effect pipelines with shader source.

  • name
  • fragment: fragment program source
  • samplers:
    • name: uniform name as it appears in the fragment program
    • source: buffer (scene, stage) or texture name
  • vertex: vertex program source

Data layers

A list of effects to be applied in order, with optional uniforms for each.

  • name: the effect name
  • uniforms:
    • name: the uniform name within the shader program
    • value: a float1 value

Data mipmaps

Update mipmaps for the scene and stage buffers each frame.

Note: This requires WebGL2 for NP2 framebuffers.

Data screen

The screen size.

  • x
  • y

Effects

Basics

Dither

TODO

Reduce color space to pseudo 8-bit and scatter pixels.

Edge Detect

TODO

Laplacian edge detection.

Invert

Invert colors per-channel.

Monochrome

Luminance-based grayscale.

Sepia

Partial desaturation with overall brown tint, like old-fashioned photographs.

Vignette

Gradiated desaturation with circular black edge.

Blend

Madd (Mult, Sum)

Add the previous stage to the original scene.

Diff

TODO

Blur

Basic

Separable blur of 7 pixels using the kernel [0.125, 0.25, 0.5, 1.0, 0.5, 0.25, 0.125].

Distort

Offset pixels from the previous stage using a normal map from another texture.

Gaussian

Separable blur of 7 pixels using the Gaussian kernel: [0.00038771, 0.01330373, 0.11098164, 0.22508352, 0.00038771, 0.01330373, 0.11098164].

Tone Mapping

Clamp

Clamp scene colors between the high and low points, then expand that to fill the full [0.0, 1.0) texture range.

This effect can act as a low-pass/high-cut or high-pass/low-cut filter by setting the range:

  • high pass:
    • uMin: desired cutoff
    • uMax: 1.0
  • low pass:
    • uMin: 0.0
    • uMax: desired cutoff

Curve

Adjust scene luminance using a 4-point curve.

Color

TODO

Adjust scene colors using a 2D tonemap texture, based on the biome tint/color palette effect from Crysis 1.

Exposure

A very rudimentary and not at all production-suitable fake HDR effect.

Note: Requires WebGL2 and GLSL version 300 for the textureLod function and generating mipmaps on NP2 textures.

Compound Effects

Most effects can be used on their own, but may not be useful. Including a few layers in the right order can produce much more complex effects.

Bloom

  • Clamp (high pass)
    • high: 1.0
    • low: 0.8
  • Blur (repeat as needed)
    • X
    • Y
  • Sum

License

This is not really much code, needs plenty of cleanup, and none of the shaders are original.

The postprocessor.ts and data.yml files are public domain, excluding the exposure effect's vertex shader, which was copied from Phaser's source.

pipelines:
- name: basic
buffers:
- name: offscreen
layers:
- name: clamp
targets:
- name: stage
- name: offscreen
uniforms:
- name: uMin
type: float
value: 0.5
- name: uMax
type: float
value: 0.9
- name: blurX
- name: blurY
- name: blurX
- name: blurY
- name: madd
- name: exposure
uniforms:
- name: uTarget
type: float
value: 0.5
- name: sepia
- name: vignette
- name: color-curve
uniforms:
- name: anchor1
type: vec2
value: [1.0, 0.0]
- name: anchor2
type: vec2
value: [0.0, 1.0]
- name: distort
- name: recombine
mipmaps: true
screen:
x: 1024
y: 768
effects:
- name: invert
samplers:
- name: foo
source: stage
fragment:
precision mediump float;
uniform sampler2D foo;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 uv = outTexCoord.xy;
vec4 texel = texture2D(foo, uv);
vec4 invert = vec4(1.0) - texel;
gl_FragColor = mix(invert, texel, step(uv.y, 0.6));
gl_FragColor.w = 1.0;
}
- name: monochrome
samplers:
- name: foo
source: stage
fragment:
precision mediump float;
uniform sampler2D foo;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 uv = outTexCoord.xy;
vec4 texel = texture2D(foo, uv);
float lum = (texel.x * 0.3) + (texel.y * 0.6) + (texel.z * 0.1);
vec4 lumtex = vec4(lum, lum, lum, 1.0);
gl_FragColor = mix(texel, lumtex, step(0.3, uv.y));
gl_FragColor.w = 1.0;
}
- name: blurX
samplers:
- name: foo
source: stage
fragment:
precision mediump float;
uniform sampler2D foo;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 pixel = vec2(1.0) / vec2(1024.0, 768.0);
vec2 uv = outTexCoord.xy;
vec4 texN3 = texture2D(foo, vec2(uv.x - (pixel.x * 3.0), uv.y)) * 0.125;
vec4 texN2 = texture2D(foo, vec2(uv.x - (pixel.x * 2.0), uv.y)) * 0.25;
vec4 texN1 = texture2D(foo, vec2(uv.x - (pixel.x * 1.0), uv.y)) * 0.5;
vec4 tex00 = texture2D(foo, uv);
vec4 texP1 = texture2D(foo, vec2(uv.x + (pixel.x * 1.0), uv.y)) * 0.5;
vec4 texP2 = texture2D(foo, vec2(uv.x + (pixel.x * 2.0), uv.y)) * 0.25;
vec4 texP3 = texture2D(foo, vec2(uv.x + (pixel.x * 3.0), uv.y)) * 0.125;
gl_FragColor = (texN3 + texN2 + texN1 + tex00 + texP1 + texP2 + texP3) / 2.75;
gl_FragColor.w = 1.0;
}
- name: blurY
samplers:
- name: foo
source: stage
fragment:
precision mediump float;
uniform sampler2D foo;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 pixel = vec2(1.0) / vec2(1024.0, 768.0);
vec2 uv = outTexCoord.xy;
vec4 texN3 = texture2D(foo, vec2(uv.x, uv.y - (pixel.y * 3.0))) * 0.125;
vec4 texN2 = texture2D(foo, vec2(uv.x, uv.y - (pixel.y * 2.0))) * 0.25;
vec4 texN1 = texture2D(foo, vec2(uv.x, uv.y - (pixel.y * 1.0))) * 0.5;
vec4 tex00 = texture2D(foo, uv);
vec4 texP1 = texture2D(foo, vec2(uv.x, uv.y + (pixel.y * 1.0))) * 0.5;
vec4 texP2 = texture2D(foo, vec2(uv.x, uv.y + (pixel.y * 2.0))) * 0.25;
vec4 texP3 = texture2D(foo, vec2(uv.x, uv.y + (pixel.y * 3.0))) * 0.125;
gl_FragColor = (texN3 + texN2 + texN1 + tex00 + texP1 + texP2 + texP3) / 2.75;
gl_FragColor.w = 1.0;
}
- name: exposure
samplers:
- name: foo
source: stage
- name: bar
source: scene
fragment: |
#version 300 es
precision mediump float;
uniform sampler2D foo;
uniform sampler2D bar;
uniform vec2 uResolution;
uniform float uTime;
uniform float uLum;
uniform float uTarget;
in vec2 outTexCoord;
in vec4 outTint;
out vec4 outColor;
void main()
{
vec2 uv = outTexCoord.xy;
vec2 luv = outTexCoord.xy;
luv.y = 1.0 - luv.y;
vec4 global_lum = textureLod(bar, luv, 7.0);
vec4 local_lum = textureLod(bar, luv, 3.0);
vec4 texel = texture(foo, uv);
outColor = mix((local_lum - global_lum) + texel, texel, step(uTarget, uv.x));
outColor.w = 1.0f;
}
vertex: |
#version 300 es
precision mediump float;
uniform mat4 uProjectionMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uModelMatrix;
in vec2 inPosition;
in vec2 inTexCoord;
in float inTintEffect;
in vec4 inTint;
out vec2 outTexCoord;
out float outTintEffect;
out vec4 outTint;
void main ()
{
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(inPosition, 1.0, 1.0);
outTexCoord = inTexCoord;
outTint = inTint;
outTintEffect = inTintEffect;
}
- name: clamp
samplers:
- name: stage
source: stage
fragment: |
precision mediump float;
uniform float uMin;
uniform float uMax;
uniform sampler2D stage;
varying vec2 outTexCoord;
void main() {
float range = uMax - uMin;
vec4 base = vec4(uMin);
vec4 texel = texture2D(stage, outTexCoord);
gl_FragColor = (clamp(texel, base, vec4(uMax)) - base) / range;
gl_FragColor.w = 1.0;
}
- name: madd
samplers:
- name: stage
source: stage
- name: scene
source: scene
fragment: |
precision mediump float;
uniform sampler2D scene;
uniform sampler2D stage;
varying vec2 outTexCoord;
void main() {
vec2 uv = outTexCoord.xy;
uv.y = 1.0 - uv.y;
vec4 texel = texture2D(stage, outTexCoord) + texture2D(scene, uv);
gl_FragColor = texel;
gl_FragColor.w = 1.0;
}
- name: sepia
samplers:
- name: stage
source: stage
fragment:
precision mediump float;
uniform sampler2D foo;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 uv = outTexCoord.xy;
vec4 texel = texture2D(foo, uv);
vec4 faded = texel;
/* kernel from https://gist.github.com/rasteron/2019a4890e0d6311297f */
faded.x = dot(texel.xyz, vec3(0.393, 0.769, 0.189));
faded.y = dot(texel.xyz, vec3(0.349, 0.686, 0.168));
faded.z = dot(texel.xyz, vec3(0.272, 0.534, 0.131));
gl_FragColor.xyz = mix(texel.xyz, faded.xyz, step(0.5, uv.x));
gl_FragColor.w = 1.0;
}
- name: vignette
samplers:
- name: stage
source: stage
fragment:
precision mediump float;
uniform sampler2D stage;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 uv = outTexCoord.xy;
vec4 texel = texture2D(stage, uv);
float lum = (texel.x * 0.3) + (texel.y * 0.6) + (texel.z * 0.1);
float dist = pow(uv.x - 0.5, 2.0) + pow(uv.y - 0.5, 2.0);
dist = 1.0 - clamp(dist, 0.0, 1.0);
gl_FragColor.xyz = mix(texel.xyz, vec3(lum), 1.0 - pow(dist, 9.0)); /* desat */
gl_FragColor.xyz = gl_FragColor.xyz * vec3(pow(dist, 3.0)); /* darken */
gl_FragColor.w = 1.0;
}
- name: distort
samplers:
- name: stage
source: stage
- name: normal
source: glass-normal
fragment:
precision mediump float;
uniform sampler2D stage;
uniform sampler2D normal;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 pixel = vec2(1.0 / 1024.0, 1.0 / 768.0);
vec2 uv = outTexCoord.xy;
vec2 normal = texture2D(normal, uv).xy;
normal = normalize(normal * 2.0 - 1.0);
vec2 offset = normal * pixel * 8.0;
vec4 texel = texture2D(stage, uv + offset);
vec4 original = texture2D(stage, uv);
gl_FragColor.xyz = (original.xyz + mix(texel.xyz, original.xyz, step(0.5, uv.x))) / 2.0;
gl_FragColor.w = 1.0;
}
- name: color-curve
samplers:
- name: stage
source: stage
fragment:
precision mediump float;
const vec3 lumF = vec3(0.2125, 0.7145, 0.0721);
const vec2 anchor0 = vec2(0.0, 0.0);
const vec2 anchor3 = vec2(1.0, 1.0);
uniform vec2 anchor1;
uniform vec2 anchor2;
uniform sampler2D stage;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 uv = outTexCoord.xy;
vec4 texel = texture2D(stage, uv);
float lum = dot(texel.xyz, lumF);
vec2 curve = /* 4-point bezier */
(pow(1.0 - lum, 3.0) * anchor0) +
(pow(1.0 - lum, 2.0) * anchor1 * 3.0 * lum) +
((1.0 - lum) * anchor2 * 3.0 * pow(lum, 2.0)) +
(pow(lum, 3.0) * anchor3);
float dist = 0.5 + pow(curve.y - lum, 0.5);
gl_FragColor.xyz = texel.xyz * dist;
/* gl_FragColor.xyz = vec3(dist); */
gl_FragColor.w = 1.0;
}
- name: gaussianX
samplers:
- name: foo
source: stage
fragment:
precision mediump float;
uniform sampler2D foo;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 pixel = vec2(1.0) / vec2(1024.0, 768.0);
vec2 uv = outTexCoord.xy;
vec4 texN3 = texture2D(foo, vec2(uv.x - (pixel.x * 3.0), uv.y)) * 0.00038771;
vec4 texN2 = texture2D(foo, vec2(uv.x - (pixel.x * 2.0), uv.y)) * 0.01330373;
vec4 texN1 = texture2D(foo, vec2(uv.x - (pixel.x * 1.0), uv.y)) * 0.11098164;
vec4 tex00 = texture2D(foo, uv) * 0.22508352;
vec4 texP1 = texture2D(foo, vec2(uv.x + (pixel.x * 1.0), uv.y)) * 0.11098164;
vec4 texP2 = texture2D(foo, vec2(uv.x + (pixel.x * 2.0), uv.y)) * 0.01330373;
vec4 texP3 = texture2D(foo, vec2(uv.x + (pixel.x * 3.0), uv.y)) * 0.00038771;
gl_FragColor = (texN3 + texN2 + texN1 + tex00 + texP1 + texP2 + texP3);
gl_FragColor.w = 1.0;
}
- name: gaussianY
samplers:
- name: foo
source: stage
fragment:
precision mediump float;
uniform sampler2D foo;
varying vec2 outTexCoord;
varying vec4 outTint;
void main()
{
vec2 pixel = vec2(1.0) / vec2(1024.0, 768.0);
vec2 uv = outTexCoord.xy;
vec4 texN3 = texture2D(foo, vec2(uv.x, uv.y - (pixel.y * 3.0))) * 0.00038771;
vec4 texN2 = texture2D(foo, vec2(uv.x, uv.y - (pixel.y * 2.0))) * 0.01330373;
vec4 texN1 = texture2D(foo, vec2(uv.x, uv.y - (pixel.y * 1.0))) * 0.11098164;
vec4 tex00 = texture2D(foo, uv) * 0.22508352;
vec4 texP1 = texture2D(foo, vec2(uv.x, uv.y + (pixel.y * 1.0))) * 0.11098164;
vec4 texP2 = texture2D(foo, vec2(uv.x, uv.y + (pixel.y * 2.0))) * 0.01330373;
vec4 texP3 = texture2D(foo, vec2(uv.x, uv.y + (pixel.y * 3.0))) * 0.00038771;
gl_FragColor = (texN3 + texN2 + texN1 + tex00 + texP1 + texP2 + texP3);
gl_FragColor.w = 1.0;
}
- name: recombine
samplers:
- name: stage
source: stage
- name: offscreen
source: offscreen
fragment:
precision mediump float;
uniform sampler2D stage;
uniform sampler2D offscreen;
varying vec2 outTexCoord;
void main() {
vec2 uv = outTexCoord.xy;
uv.y = 1.0 - uv.y;
vec4 texel = texture2D(stage, outTexCoord);
vec4 glow = texture2D(offscreen, uv);
gl_FragColor.xyz = texel.xyz + glow.xyz;
gl_FragColor.w = 1.0;
}
import { doesExist, mustExist, mustGet } from '@apextoaster/js-utils';
import { defaultTo } from 'lodash';
import * as Phaser from 'phaser';
import { CENTER_SPLIT } from '../constants';
import { Point } from '../entity';
export interface EffectSampler {
name: string;
source: string;
}
export interface EffectTarget {
name: string;
}
export interface EffectUniformSingle {
name: string;
type: 'float';
value: number;
}
export interface EffectUniformVector {
name: string;
type: 'float1' | 'float2' | 'float3' | 'float4' | 'vec1' | 'vec2' | 'vec3' | 'vec4';
value: Array<number>;
}
export type EffectUniform = EffectUniformSingle | EffectUniformVector;
export interface PipelineBuffer {
mipmaps: boolean;
name: string;
size: Point;
}
export interface PipelineEffect {
fragment: string;
name: string;
samplers: Array<EffectSampler>;
vertex: string;
}
export interface PipelineLayer {
name: string;
targets: Array<EffectTarget>;
uniforms: Array<EffectUniform>;
}
export interface PipelineData {
buffers: Array<PipelineBuffer>;
effects: Array<PipelineEffect>;
layers: Array<PipelineLayer>;
mipmaps: boolean;
name: string;
screen: Point;
}
export type CachedSampler = {
name: string;
type: 'buffer';
texture: Phaser.GameObjects.RenderTexture;
} | {
name: string;
type: 'texture';
texture: Phaser.Textures.Texture;
frame: string;
};
export interface FrameDict {
[key: string]: Phaser.Textures.Frame;
}
const REQUIRED_BUFFERS = [
'scene',
'stage',
];
const MAX_SAMPLERS = 8;
export type RenderObject = Phaser.GameObjects.GameObject & Phaser.GameObjects.Components.Transform;
class EffectPipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline {
protected samplers: Array<CachedSampler>;
constructor(game: Phaser.Game, effect: PipelineEffect) {
super({
fragShader: effect.fragment,
game,
renderer: game.renderer,
vertShader: effect.vertex,
});
this.samplers = [];
}
public onBind() {
super.onBind();
const program = this.program;
const renderer = this.renderer;
const sl = Math.min(MAX_SAMPLERS, this.samplers.length);
for (let i = 0; i < sl; ++i) {
const sampler = this.samplers[i];
if (sampler.type === 'buffer') {
renderer.setTexture2D(sampler.texture.glTexture, i);
} else {
const frameName = defaultTo(sampler.frame, sampler.texture.firstFrame);
const frame = (sampler.texture.frames as FrameDict)[frameName];
renderer.setTexture2D(frame.glTexture, i);
}
renderer.setInt1(program, sampler.name, i);
}
return this;
}
public cacheTextures(samplers: Array<CachedSampler>) {
this.samplers.splice(0);
this.samplers.push(...samplers);
}
}
export class PostProcessor {
protected readonly data: PipelineData;
protected readonly scene: Phaser.Scene;
protected readonly buffers: Map<string, Phaser.GameObjects.RenderTexture>;
protected readonly effects: Map<string, EffectPipeline>;
protected running: boolean;
protected fillRect?: Phaser.GameObjects.Rectangle;
constructor(data: PipelineData, scene: Phaser.Scene) {
this.data = data;
this.scene = scene;
this.running = false;
this.effects = new Map();
this.buffers = new Map();
}
public get screenBuffer() {
return this.getBuffer('stage');
}
public getBuffer(key: string) {
return mustGet(this.buffers, key);
}
public getContext(): WebGL2RenderingContext {
return this.renderer.gl as WebGL2RenderingContext;
}
public get renderer(): Phaser.Renderer.WebGL.WebGLRenderer {
return this.scene.game.renderer as Phaser.Renderer.WebGL.WebGLRenderer;
}
public create() {
this.fillRect = this.scene.add.rectangle(0, 0, this.data.screen.x, this.data.screen.y, 0x00, 1.0);
for (const buffer of REQUIRED_BUFFERS) {
const rt = this.createBuffer(buffer);
this.buffers.set(buffer, rt);
}
for (const buffer of this.data.buffers) {
const rt = this.createBuffer(buffer.name);
this.buffers.set(buffer.name, rt);
}
for (const effect of this.data.effects) {
const game = this.scene.game;
const pipeline = new EffectPipeline(game, effect);
this.effects.set(effect.name, pipeline);
const qn = this.qualifyName(effect.name);
this.renderer.addPipeline(qn, pipeline);
const rt = this.createBuffer(effect.name);
rt.setPipeline(qn);
this.buffers.set(effect.name, rt);
}
this.cacheTextures();
}
public cacheTextures() {
/* this should happen after all effects (and their buffers) have been created and registered */
for (const data of this.data.effects) {
const effect = mustGet(this.effects, data.name);
const cache: Array<CachedSampler> = [];
for (const { name, source } of data.samplers) {
if (this.buffers.has(source)) {
const texture = mustGet(this.buffers, source);
cache.push({
name,
texture,
type: 'buffer',
});
} else {
// source is not a name and should not be qualified
const texture = this.scene.textures.get(source);
cache.push({
frame: texture.firstFrame,
name,
texture,
type: 'texture',
});
}
}
effect.cacheTextures(cache);
}
this.running = true;
}
public createBuffer(name: string) {
const qn = this.qualifyName(name);
const rt = this.scene.make.renderTexture({
height: this.data.screen.y,
width: this.data.screen.x,
x: 0,
y: 0,
});
rt.setName(qn);
rt.setScrollFactor(0, 0);
rt.setVisible(false);
this.scene.textures.addRenderTexture(qn, rt);
return rt;
}
public update(objects: Array<RenderObject>, layers: Array<PipelineLayer>, camera: Phaser.Cameras.Scene2D.Camera) {
if (!this.running) {
return;
}
const sceneBuffer = mustGet(this.buffers, 'scene');
const stageBuffer = mustGet(this.buffers, 'stage');
const fillRect = mustExist(this.fillRect);
sceneBuffer.draw(fillRect, fillRect.displayWidth / CENTER_SPLIT, fillRect.displayHeight / CENTER_SPLIT);
for (const obj of objects) {
sceneBuffer.draw(obj, Math.floor(-camera.scrollX + obj.x), Math.floor(-camera.scrollY + obj.y));
}
if (this.data.mipmaps) {
this.updateMips(sceneBuffer);
}
stageBuffer.draw(sceneBuffer);
for (const layer of layers) {
this.updateLayer(layer, stageBuffer);
}
}
public updateLayer(layer: PipelineLayer, stageBuffer: Phaser.GameObjects.RenderTexture) {
const buffer = mustGet(this.buffers, layer.name);
const effect = mustGet(this.effects, layer.name);
if (doesExist(layer.uniforms)) {
for (const uniform of layer.uniforms) {
this.updateUniform(effect, uniform);
}
}
buffer.draw(stageBuffer);
if (doesExist(layer.targets)) {
for (const data of layer.targets) {
const target = mustGet(this.buffers, data.name);
target.draw(buffer);
}
} else {
stageBuffer.draw(buffer);
}
if (this.data.mipmaps) {
this.updateMips(stageBuffer);
}
}
public updateMips(buffer: Phaser.GameObjects.RenderTexture) {
// use the RenderTexture's context in case it is different
const gl = (buffer.renderer as Phaser.Renderer.WebGL.WebGLRenderer).gl as WebGL2RenderingContext;
const active = gl.getParameter(gl.ACTIVE_TEXTURE);
const levels = Math.ceil(Math.log2(Math.max(buffer.height, buffer.width)));
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, buffer.glTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, levels);
gl.generateMipmap(gl.TEXTURE_2D);
const err = gl.getError();
if (err > 0) {
/* eslint-disable-next-line */
console.warn('mipmap error', err);
}
gl.activeTexture(active);
}
public updateUniform(effect: EffectPipeline, uniform: EffectUniform) {
switch (uniform.type) {
case 'float1':
case 'vec1':
effect.setFloat1v(uniform.name, Float32Array.from(uniform.value));
break;
case 'float2':
case 'vec2':
effect.setFloat2v(uniform.name, Float32Array.from(uniform.value));
break;
case 'float3':
case 'vec3':
effect.setFloat3v(uniform.name, Float32Array.from(uniform.value));
break;
case 'float4':
case 'vec4':
effect.setFloat4v(uniform.name, Float32Array.from(uniform.value));
break;
case 'float':
default:
effect.setFloat1(uniform.name, uniform.value);
break;
}
}
public stop() {
this.running = false;
const fillRect = mustExist(this.fillRect);
this.scene.children.remove(fillRect);
for (const name of this.buffers.keys()) {
const qn = this.qualifyName(name);
this.renderer.removePipeline(qn);
this.scene.textures.remove(qn);
}
}
protected qualifyName(name: string): string {
return `postfx-${this.data.name}-${name}`;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment