Important!
This Tutorial is for my Filters Plugin, it can be applied to PIXI.js in general but some concepts will be specific to my Filters Plugin.
You can find my Filters Plugin here; arpgmaker.com/viewtopic.php?p=930983
IDE Recommendation
As a JS development IDE I highly recommend the multi-platform IDE "Visual Studio Code" (Available for Windows, OS X and Linux). code.visualstudio.com
Below are framework Filters. They are bare-bones with what they do.
First filter is a single-pass "multiplier" which will multiply the RGB components of the input by a fixed number.
Second filter is an example multi-pass filter, which will perform the multiply operation of the first filter twice in separate passes.
Included are an example of a custom Filter with animation and a custom texture (Refract.js) and an example of a custom multi-pass Filter (Retro.js).
Basic Custom Filter
BasicCustomFilter.js
basic_custom { "multiplier" : 1.0 }
Multi Pass Custom Filter
MultiPassCustomFilter.js
multi_pass_custom { "multiplier" : [1.0, 1.0] }
Uniform Values
Custom Texture Uploads
Things get more advanced when you need custom textures (one example would be a refraction shader).
The Filters plugin can accept strings as uniforms. The string will switch from source state to destination state at the mid-point of the Filter interpolation animation.
An example getter/setter for a customer texture would be;
Animation
The Filters Plugin will attempt to set a uTime property if it exists. To make uTime visible a getter/setter needs to be created;
Notes
fragmentSrc is where the GLSL lives. This is the most important part as this is the shader code itself. You must know GLSL to be able to write this (it is rather different to Javascript!). You can do an awful lot with this.
Refraction Filter
This is an example custom filter which features animation (set the speed) and the use of a custom texture (set the refractMap to the name of a .png image in the Pictures folder).
Version 2.0 of this Filter will be part of Filter Pack 1.
Refract.js
refract { "refractMap" : null, "strength" : 0.1, "speed" : { "x" : 0, "y" : 0 }, "uTime" : 0 }
Retro Filter
This is an example custom multi-pass filter which both pixelates and reduces the colour of the screen.
Retro.js
retro { "step" : 3.779, "size" : 2 }
This Tutorial is for my Filters Plugin, it can be applied to PIXI.js in general but some concepts will be specific to my Filters Plugin.
You can find my Filters Plugin here; arpgmaker.com/viewtopic.php?p=930983
IDE Recommendation
As a JS development IDE I highly recommend the multi-platform IDE "Visual Studio Code" (Available for Windows, OS X and Linux). code.visualstudio.com
Below are framework Filters. They are bare-bones with what they do.
First filter is a single-pass "multiplier" which will multiply the RGB components of the input by a fixed number.
Second filter is an example multi-pass filter, which will perform the multiply operation of the first filter twice in separate passes.
Included are an example of a custom Filter with animation and a custom texture (Refract.js) and an example of a custom multi-pass Filter (Retro.js).
Basic Custom Filter
BasicCustomFilter.js
basic_custom { "multiplier" : 1.0 }
Code:
(function() {
// BasicCustomFilter construction function
BasicCustomFilter = function() {
PIXI.AbstractFilter.call( this ); // Call parent constructor
this.passes = [this]; // A Filter can be multi-pass (See MultiPassFilter below)
// Uniforms are sent to the GPU shader (See Uniform Values table below)
this.uniforms = {
multiplier: { type: '1f', value : 1.0 } // multiplier uniform is a float with a default value of 1.0
};
// Filters only use the fragment shader (usually)
// This is the source for the GPU program, performed per-pixel
// It is a string array that is joined at a later stage before uploading to the GPU
this.fragmentSrc = [
'precision mediump float;', // Precision is the accuracy of the result - lowp is faster but less accurate, highp is slower but more accurate
'varying vec2 vTextureCoord;', // DEFAULT - input from MV for the input texture UV
'varying vec4 vColor;', // DEFAULT - input from MV from the vertex shader (can be ignored)
'uniform sampler2D uSampler;', // DEFAULT - input from MV for the input texture itself
// A Filters Plugin default input is uTime, which requires a getter/setter to be written (See multiplier getter/setter above for an example)
'uniform float multiplier;', // This is our custom uniform value (see above)
// GLSL main entry point
'void main(void) {',
' gl_FragColor = texture2D( uSampler, vTextureCoord );', // Read the colour of our source image
' gl_FragColor.rgb *= multiplier;', // Do some operation on it (multiply by the multiplier uniform)
'}'
];
}
// Prototype setup
BasicCustomFilter.prototype = Object.create( BasicCustomFilter.prototype );
BasicCustomFilter.prototype.constructor = BasicCustomFilter;
// BasicCustomFilter.multiplier getter and setter (notice the use of .value)
// This is to avoid accessing the uniform directly and lets us do cool things later
Object.defineProperty( BasicCustomFilter.prototype, 'multiplier', {
// Getter
get: function() {
return this.uniforms.multiplier.value;
},
// Setter
set: function( value ) {
this.uniforms.multiplier.value = value;
}
});
if ( Filter ) {
// Filters Plugin standard naming is to remove the "Filter" and switch from camel case to lower-case with underscore
// BasicCustomFilter -> basic_custom
Filter.add( "basic_custom", BasicCustomFilter );
}
})();
Multi Pass Custom Filter
MultiPassCustomFilter.js
multi_pass_custom { "multiplier" : [1.0, 1.0] }
Code:
(function() {
// MultiPassCustomFilter construction function
function MultiPassCustomFilter() {
this.firstFilter = new BasicCustomFilter(); // Create our first Filter instance
this.secondFilter = new BasicCustomFilter(); // Create our second Filter instance
this.passes = [this.firstFilter, this.secondFilter]; // The passes are set here
}
// Prototype setup
MultiPassCustomFilter.prototype = Object.create( MultiPassCustomFilter.prototype );
MultiPassCustomFilter.prototype.constructor = MultiPassCustomFilter;
// Forward the properties from both our passes to this interface
Object.defineProperty( MultiPassCustomFilter.prototype, 'multiplier', {
// Getter
get: function() {
// Returns an array containing the first and second multiplier uniforms
return [ this.firstFilter.multiplier, this.secondFilter.multiplier ];
},
// Setter
set: function( value ) {
this.firstFilter.multiplier = value[0];
this.secondFilter.multiplier = value[1];
}
});
if ( Filter ) {
// Filters Plugin standard naming is to remove the "Filter" and switch from camel case to lower-case with underscore
// MultiPassCustomFilter -> multi_pass_custom
Filter.add( "multi_pass_custom", MultiPassCustomFilter );
}
})();
Uniform Values
name | GLSL type | JS type | example
----------|-----------|---------|---------
sampler2D | sampler2D | Bitmap | ImageManager.loadPicture( "my_picture" )
1f | float | Number | 1.0
2f | vec2 | Object | { x : 1.0, y : 2.0 }
3f | vec3 | Object | { x : 1.0, y : 2.0, z : 3.0 }
4f | vec4 | Object | { x : 1.0, y : 2.0, z : 3.0, w : 4.0 }
2fv | vec2 | Array | [ 1.0, 2.0 ]
3fv | vec3 | Array | [ 1.0, 2.0, 3.0 ]
4fv | vec4 | Array | [ 1.0, 2.0, 3.0, 4.0 ]
mat2 | mat2 | Array | [ 1.0, 2.0, 3.0, 4.0 ]
mat3 | mat3 | Array | [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 ]
mat4 | mat4 | Array | [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0 ]
Custom Texture Uploads
Things get more advanced when you need custom textures (one example would be a refraction shader).
The Filters plugin can accept strings as uniforms. The string will switch from source state to destination state at the mid-point of the Filter interpolation animation.
An example getter/setter for a customer texture would be;
Code:
Object.defineProperty( TextureCustomFilter.prototype, 'customTexture', {
// Returns the texture url
get: function() {
if ( this.uniforms.customTexture.value === null ) {
return null;
}
return this.uniforms.customTexture.value.texture_url;
},
// value is a string (texture name within the pictures folder)
set: function( value ) {
if ( this.uniforms.customTexture.value === null || this.uniforms.customTexture.value.texture_url != value ) {
this.uniforms.customTexture.value = ImageManager.loadPicture( value );
this.uniforms.customTexture.value.texture_url = value;
}
}
});
Animation
The Filters Plugin will attempt to set a uTime property if it exists. To make uTime visible a getter/setter needs to be created;
Code:
Object.defineProperty( AnimatedCustomFilter.prototype, 'uTime', {
get: function() {
return this.uniforms.uTime.value * 60.0; // 60.0 is used to control the animation speed
},
set: function( value ) {
this.uniforms.uTime.value = value / 60.0; // The multiply/divide value should be consistent with getter/setter
}
});
Notes
fragmentSrc is where the GLSL lives. This is the most important part as this is the shader code itself. You must know GLSL to be able to write this (it is rather different to Javascript!). You can do an awful lot with this.
Refraction Filter
This is an example custom filter which features animation (set the speed) and the use of a custom texture (set the refractMap to the name of a .png image in the Pictures folder).
Version 2.0 of this Filter will be part of Filter Pack 1.
Refract.js
refract { "refractMap" : null, "strength" : 0.1, "speed" : { "x" : 0, "y" : 0 }, "uTime" : 0 }
Code:
//=============================================================================
// Refract.js
//=============================================================================
/*:
* @plugindesc Refract Filter. For Xilefian's Filter Plugin.
* @author Felix "Xilefian" Jones
*
* @help
*
* Version 1.0
* Website [url=http://www.hbgames.org]http://www.hbgames.org[/url]
*
* Filter:
* refract { "refractMap":null, "strength":0.1, "speed":{ "x":0, "y":0 }, "uTime":0 }
*
* Change log:
* Version 1.0:
* Initial version.
*
*/
(function() {
/**
* @author Felix "Xilefian" Jones
* The RefractFilter
*
* @class RefractFilter
* @extends AbstractFilter
* @constructor
*/
RefractFilter = function() {
PIXI.AbstractFilter.call( this );
this.passes = [this];
this.uniforms = {
refractMap: {type: 'sampler2D', value:null },
strength: {type: '1f', value:0.1},
speed: {type: '2f', value:{x:0,y:0}},
uTime: {type: '1f', value:0}
};
this.fragmentSrc = [
'precision mediump float;',
'varying vec2 vTextureCoord;',
'varying vec4 vColor;',
'uniform sampler2D uSampler;',
'uniform float uTime;',
'uniform sampler2D refractMap;',
'uniform float strength;',
'uniform vec2 speed;',
'void main(void) {',
' vec2 ruv = fract( vTextureCoord.xy + ( speed * uTime ) );',
' vec3 offset = texture2D( refractMap, ruv ).rgb;',
' offset -= ( offset / 2.0 );',
' offset *= 2.0;',
' offset.z *= strength;',
' gl_FragColor = texture2D( uSampler, vTextureCoord.xy + offset.xy * offset.z );',
'}'
];
};
RefractFilter.prototype = Object.create( RefractFilter.prototype );
RefractFilter.prototype.constructor = RefractFilter;
/**
* Sets the refract texture
*
* @property refractMap
* @type String
* @default null
*/
Object.defineProperty( RefractFilter.prototype, 'refractMap', {
get: function() {
if ( this.uniforms.refractMap.value === null ) {
return null;
}
return this.uniforms.refractMap.value.xi_url;
},
set: function( value ) {
if ( this.uniforms.refractMap.value === null || this.uniforms.refractMap.value.xi_url != value ) {
this.uniforms.refractMap.value = ImageManager.loadPicture( value );
this.uniforms.refractMap.value.xi_url = value;
}
}
});
/**
* Sets the power of the refraction
*
* @property strength
* @type Number
* @default 0.1
*/
Object.defineProperty( RefractFilter.prototype, 'strength', {
get: function() {
return this.uniforms.strength.value;
},
set: function( value ) {
this.uniforms.strength.value = value;
}
});
/**
* Animation time
*
* @property uTime
* @type Number
* @default 0
*/
Object.defineProperty( RefractFilter.prototype, 'uTime', {
get: function() {
return this.uniforms.uTime.value * 60.0;
},
set: function( value ) {
this.uniforms.uTime.value = value / 60.0;
}
});
/**
* Animation speed
*
* @property speed
* @type Point
* @default 0, 0
*/
Object.defineProperty( RefractFilter.prototype, 'speed', {
get: function() {
return this.uniforms.speed.value;
},
set: function( value ) {
this.uniforms.speed.value = value;
}
});
/**
* Register filter class with the Filter Plugin (if available)
*/
if ( Filter ) {
Filter.add( "refract", RefractFilter );
}
})();
Retro Filter
This is an example custom multi-pass filter which both pixelates and reduces the colour of the screen.
Retro.js
retro { "step" : 3.779, "size" : 2 }
Code:
//=============================================================================
// Retro.js
//=============================================================================
/*:
* @plugindesc Retro Filter. For Xilefian's Filter Plugin.
* @author Felix "Xilefian" Jones
*
* @help
*
* Version 1.0
* Website [url=http://www.hbgames.org]http://www.hbgames.org[/url]
*
* Filter:
* retro { "step":3.779, "size":2 }
*
* Change log:
* Version 1.0:
* Initial version.
*
*/
(function() {
/**
* @author Felix "Xilefian" Jones
* The RetroFilter
*
* @class RetroFilter
* @extends AbstractFilter
* @constructor
*/
RetroFilter = function() {
this.colorStep = new PIXI.ColorStepFilter(); // To reduce the colours
this.pixelate = new PIXI.PixelateFilter(); // To reduce the sampling resolution
this.passes = [this.colorStep, this.pixelate]; // We reduce colour first, then resolution (avoids an anti-aliasing blur)
this.step = 3.779;
this.size = 2.0;
};
RetroFilter.prototype = Object.create( RetroFilter.prototype );
RetroFilter.prototype.constructor = RetroFilter;
/**
* Sets the colorStep reduction
*
* @property step
* @type Number
* @default 3.779
*/
Object.defineProperty( RetroFilter.prototype, 'step', {
get: function() {
return this.colorStep.step;
},
set: function( value ) {
this.colorStep.step = value;
}
});
/**
* Sets the pixelate size
*
* @property size
* @type Number
* @default 2.0
*/
Object.defineProperty( RetroFilter.prototype, 'size', {
get: function() {
return this.pixelate.size.x;
},
set: function( value ) {
this.pixelate.size.x = value;
this.pixelate.size.y = value;
}
});
/**
* Register filter class with the Filter Plugin (if available)
*/
if ( Filter ) {
Filter.add( "retro", RetroFilter );
}
})();