README
AFRAME.Effects
A VR Ready Post processing framework for Three.js and A-Frame
A modular api is provided where effects can act as both input and output for other effects. Effect shader chunks and uniforms are fused together, as possible, in uber shaders for performance. The effect fusion mechanism allows efficient setups of high complexity to be implemented effortlessly in declarative fashion.
The framework is also VR Ready. Mechanisms are provided to deal with the issues stemming from the stereo rendering setup required, and all core effects utilize them to ensure proper post processing operations in VR.
AFrame example, Three.js example
Usage
Downloads
To embed this library in your project, include this file:
For the unminified version for local development (with source maps), include this file:
npm
First install from npm:
npm install aframe-effects
And in your Browserify/Webpack modules, simply require the module(after three.js or aframe.js):
require('aframe-effects')
Instructions
The chain of effects to apply can be defined through the effects system. All component instances specified as a token in the chain, must be attached somewhere in the scene or previously instantiated under threejs.
<a-scene effects="bloom, fxaa" bloom="radius: 0.66" fxaa>
Check the documentation for the effects system and effect components
Standalone Three.js
The framework is primarily designed for AFRAME but doesn't actually require it. To utilize the effects under a standalone threejs context, you'll need to include aframe-effects.js after three.js but without aframe.js.
A minimal AFRAME shim will be provided by aframe-effects itself. You can then register effect components as usual with AFRAME.registerComponent and instantiate effect containers with the AFRAME.Effects constructor.
These containers expose a .renderTarget for the renderer to render into. They also expose a .render(time) to process their effect pipeline and output to the canvas. The provided .renderTarget(and all internal targets expressed as a ratio of it) will get automatically resized according to the renderer's size.
The A-Frame example from above would translate to:
var effects = new AFRAME.Effects(renderer, scene, camera);
// you can change the camera, scene, renderer as effects.scene = ...
// effects.camera = [leftCamera, rightCamera]; // for setting vr mode
// effects specified in chains must be instantiated before use
effects.init("fxaa");
// effects.update() will also init the effect if it wasn't available
// vector objects are passed by reference so no need calling update()
// after the firt time to reflect their values in the uber shaders
// All other property types should be modified with effects.update()
effects.update( "bloom", { radius: 0.66 } );
// this is equivalent to <a-scene effect="bloom, fxaa">
// but doesn't accept strings, only arrays under threejs
effects.chain( [ "bloom", "fxaa" ] );
function render(time) {
// if the chain is empty, effects.renderTarget will be null
// and effects.render will be a no op, so no need for flags
// to toggle post processing. Just set the chain accordingly.
renderer.render(scene, camera, effects.renderTarget);
// process the effect chain and output to canvas
effects.render(time);
}
// effects.remove("bloom"); // for releasing resources
Effect instances
Effects specified in chains are instances of registered components. In aframe this is done by adding attributes on the scene element, in threejs via init/update. You can have multiple of them by appending "__" and an id when instantiating them.
<a scene="bloom bloom__more" bloom bloom__more>
Each instance carries its own uniforms and can be accessed by all chains in the same scope(some effects can also use chains of their own for input filtering, see below). Chain scope for aframe is the scene element, for threejs the THREE.Effects container instance.
Effect exports
Effect components can also export a set of effect instances (usually those used internally for the parent effects operations). All the effect component author has to do is expose an Object map(key=>fusable) .exports property.
AFRAME.registerComponent("myeffect",
init: function () {
// We define it here and not the prototype
// so as to have a set of uniforms per instance
this.exports = {
filter: {
uniforms: ...
fragment: ...
}
}
}
As long as the instance is attached/inited its sub effects can be accesed in any chain definition like this:
<a-scene effects="myeffect.filter bloom" myeffect bloom>
Script effects
effects.chain() also accepts ids of script elements like this:
<script type="not-js" id="funky" data-diffuse>
void $main(inout vec4 c, vec4 o, vec2 uv, float d){
c.rgb = sin(c.gbr + time) * o.bgr;
}
</script>
<a-scene effects="#funky">
These script elements serve as lightweight fusable effects that don't have uniforms of their own, though they have access to the default fused uniforms like time above. Script effects can also be statically patched(check effects arguments) if they define a data-defaults attribute.
Script effects don't export other fusables. Accesing a sub token(eg #myinput.value) on a script effect will use the sub token as the element property to retrieve the fragment chunk from instead of "textContent".
If the value of this property also happens to be a DOM Document instance, then that documents body.textContent will be used as the fragment chunk. This way chunks can be fetched from scripts, iframes, inputs and link[rel=import] elements
Effect arguments
Effects can be selectively patched during fusion by exposing an array property .defaults containing several strings and corresponding placeholders $0, $1 etc in the fragment source. These defaults can then be overriden by providing space separated arguments to chain tokens like this:
<script type="nojs" id="mix" data-diffuse
data-defaults="0.0 0.0 0.0 1.0">
void $main(inout vec4 c, vec4 o, vec2 uv, float d){
c.rgb = mix(c.rgb, vec3($0, $1, $2), $3);
}
</script>
<a-scene effects="#mix(0.12 0.33 0.66 0.66)">
The special single char argument "