///@INFO: UNCOMMON
/** FXStack
* Helps apply a stack of FXs to a texture with as fewer render calls as possible with low memory footprint
* Used by CameraFX and FrameFX but also available for any other use
* You can add new FX to the FX pool if you want.
* @class FXStack
*/
function FXStack( o )
{
this.apply_fxaa = false;
this.filter = true;
this.fx = [];
this._uniforms = { u_aspect: 1, u_viewport: vec2.create(), u_iviewport: vec2.create(), u_texture: 0, u_depth_texture: 1, u_random: vec2.create() };
this._passes = null;
this._must_update_passes = true;
if(o)
this.configure(o);
}
FXStack.available_fx = {
"brightness_contrast": {
name: "Brightness & Contrast",
uniforms: {
brightness: { name: "u_brightness", type: "float", value: 1, step: 0.01 },
contrast: { name: "u_contrast", type: "float", value: 1, step: 0.01 }
},
code:"color.xyz = (color.xyz * u_brightness@ - vec3(0.5)) * u_contrast@ + vec3(0.5);"
},
"hue_saturation": {
name: "Hue & Saturation",
functions: ["HSV"],
uniforms: {
hue: { name: "u_hue", type: "float", value: 0, step: 0.01 },
saturation: { name: "u_saturation", type: "float", value: 1, step: 0.01 },
brightness: { name: "u_brightness", type: "float", value: 0, step: 0.01 }
},
code:"color.xyz = rgb2hsv(color.xyz); color.xz += vec2(u_hue@,u_brightness@); color.y *= u_saturation@; color.xyz = hsv2rgb(color.xyz);"
},
"invert": {
name: "Invert color",
code:"color.xyz = vec3(1.0) - color.xyz;"
},
"threshold": {
name: "Threshold",
uniforms: {
threshold: { name: "u_threshold", type: "float", value: 0.5, min: 0, max: 2, step: 0.01 },
threshold_width: { name: "u_threshold_width", type: "float", value: 0.01, min: 0, max: 1, step: 0.001 }
},
code:"color.xyz = vec3( smoothstep( u_threshold@ - u_threshold_width@ * 0.5, u_threshold@ + u_threshold_width@ * 0.5, length(color.xyz) ));"
},
"colorize": {
name: "Colorize",
uniforms: {
colorize: { name: "u_colorize", type: "color3", value: [1,1,1] },
vibrance: { name: "u_vibrance", type: "float", value: 0.0, min: 0, max: 2, step: 0.01 }
},
code:"color.xyz = color.xyz * (u_colorize@ + vec3(u_vibrance@ * 0.1)) * (1.0 + u_vibrance@);"
},
"color_add": {
name: "Color add",
uniforms: {
color_add: { name: "u_coloradd", type: "color3", value: [0.1,0.1,0.1] }
},
code:"color.xyz = color.xyz + u_coloradd@;"
},
"fog":{
name:"fog",
uniforms: {
fog_color: { name: "u_fog_color", type: "color3", value: [0.1,0.1,0.1] },
fog_start: { name: "u_fog_start", type: "float", value: 10 },
fog_density: { name: "u_fog_density", type: "float", precision: 0.00001, value: 0.001, step: 0.00001 }
},
code:"float z_n@ = 2.0 * texture2D( u_depth_texture, v_coord).x - 1.0;" +
"float cam_dist@ = 2.0 * u_depth_range.x * u_depth_range.y / (u_depth_range.y + u_depth_range.x - z_n@ * (u_depth_range.y - u_depth_range.x));" +
"float fog_factor@ = 1. - 1.0 / exp(max(0.0,cam_dist@ - u_fog_start@) * u_fog_density@);" +
"color.xyz = mix( color.xyz, u_fog_color@, fog_factor@ );"
},
"vigneting": {
name: "Vigneting",
uniforms: {
radius: { name: "u_radius", type: "float", value: 1 },
intensity: { name: "u_vigneting", type: "float", value: 1, min: 0, max: 2, step: 0.01 }
},
code:"color.xyz = mix( color.xyz * max( 1.0 - (dist_to_center * u_radius@ / 0.7071), 0.0), color.xyz, u_vigneting@);"
},
"aberration": {
name: "Chromatic Aberration",
break_pass: true,
uniforms: {
difraction: { name: "u_difraction", type: "float", value: 1 }
},
code: "color.x = texture2D(u_texture, uv - to_center * 0.001 * u_difraction@ ).x;" +
"color.z = texture2D(u_texture, uv + to_center * 0.001 * u_difraction@ ).z;"
},
"halftone": {
name: "Halftone",
uniforms: {
"Halftone angle": { name: "u_halftone_angle", type: "float", value: 0, step: 0.01 },
"Halftone size": { name: "u_halftone_size", type: "float", value: 1, step: 0.01 }
},
functions: ["pattern"],
code:"color.x = ( (color.x * 10.0 - 5.0) + pattern( u_halftone_angle@, u_halftone_size@ ) );" +
"color.y = ( (color.y * 10.0 - 5.0) + pattern( u_halftone_angle@ + 0.167, u_halftone_size@ ) );" +
"color.z = ( (color.z * 10.0 - 5.0) + pattern( u_halftone_angle@ + 0.333, u_halftone_size@ ) );"
},
"halftoneBN": {
name: "Halftone B/N",
uniforms: {
"Halftone angle": { name: "u_halftone_angle", type: "float", value: 0, step: 0.01 },
"Halftone size": { name: "u_halftone_size", type: "float", value: 1, step: 0.01 }
},
functions: ["pattern"],
code:"color.xyz = vec3( (length(color.xyz) * 10.0 - 5.0) + pattern( u_halftone_angle@, u_halftone_size@ ) );"
},
"lens": {
name: "Lens Distortion",
break_pass: true,
uniforms: {
lens_k: { name: "u_lens_k", type: "float", value: -0.15 },
lens_kcube: { name: "u_lens_kcube", type: "float", value: 0.8 },
lens_scale: { name: "u_lens_scale", type: "float", value: 1 }
},
uv_code:"float r2 = u_aspect * u_aspect * (uv.x-0.5) * (uv.x-0.5) + (uv.y-0.5) * (uv.y-0.5); float distort@ = 1. + r2 * (u_lens_k@ + u_lens_kcube@ * sqrt(r2)); uv = vec2( u_lens_scale@ * distort@ * (uv.x-0.5) + 0.5, u_lens_scale@ * distort@ * (uv.y-0.5) + 0.5 );"
},
"image": {
name: "Image",
uniforms: {
image_texture: { name: "u_image_texture", type: "sampler2D", widget: "Texture", value: "" },
image_alpha: { name: "u_image_alpha", type: "float", value: 1, step: 0.001 },
image_scale: { name: "u_image_scale", type: "vec2", value: [1,1], step: 0.001 }
},
code:"vec4 image@ = texture2D( u_image_texture@, (uv - vec2(0.5)) * u_image_scale@ + vec2(0.5)); color.xyz = mix(color.xyz, image@.xyz, image@.a * u_image_alpha@ );"
},
"warp": {
name: "Warp",
break_pass: true,
uniforms: {
warp_amp: { name: "u_warp_amp", type: "float", value: 0.01, step: 0.001 },
warp_offset: { name: "u_warp_offset", type: "vec2", value: [0,0], step: 0.001 },
warp_scale: { name: "u_warp_scale", type: "vec2", value: [1,1], step: 0.001 },
warp_texture: { name: "u_warp_texture", type: "sampler2D", widget: "Texture", value: "" }
},
uv_code:"uv = uv + u_warp_amp@ * (texture2D( u_warp_texture@, uv * u_warp_scale@ + u_warp_offset@ ).xy - vec2(0.5));"
},
"LUT": {
name: "LUT",
functions: ["LUT"],
uniforms: {
lut_intensity: { name: "u_lut_intensity", type: "float", value: 1, step: 0.01 },
lut_texture: { name: "u_lut_texture", type: "sampler2D", filter: "nearest", wrap: "clamp", widget: "Texture", value: "" }
},
code:"color.xyz = mix(color.xyz, LUT( color.xyz, u_lut_texture@ ), u_lut_intensity@);"
},
"pixelate": {
name: "Pixelate",
uniforms: {
width: { name: "u_width", type: "float", value: 256, step: 1, min: 1 },
height: { name: "u_height", type: "float", value: 256, step: 1, min: 1 }
},
uv_code:"uv = vec2( floor(uv.x * u_width@) / u_width@, floor(uv.y * u_height@) / u_height@ );"
},
"quantize": {
name: "Quantize",
uniforms: {
levels: { name: "u_levels", type: "float", value: 8, step: 1, min: 1 }
},
code:"color.xyz = floor(color.xyz * u_levels@) / u_levels@;"
},
"edges": {
name: "Edges",
break_pass: true,
uniforms: {
"Edges factor": { name: "u_edges_factor", type: "float", value: 1 }
},
code:"vec4 color@ = texture2D(u_texture, uv );\n\
vec4 color_up@ = texture2D(u_texture, uv + vec2(0., u_iviewport.y));\n\
vec4 color_right@ = texture2D(u_texture, uv + vec2(u_iviewport.x,0.));\n\
vec4 color_down@ = texture2D(u_texture, uv + vec2(0., -u_iviewport.y));\n\
vec4 color_left@ = texture2D(u_texture, uv + vec2(-u_iviewport.x,0.));\n\
color = u_edges_factor@ * (abs(color@ - color_up@) + abs(color@ - color_down@) + abs(color@ - color_left@) + abs(color@ - color_right@));"
},
"depth": {
name: "Depth",
uniforms: {
"near": { name: "u_near", type: "float", value: 0.01, step: 0.1 },
"far": { name: "u_far", type: "float", value: 1000, step: 1 }
},
code:"color.xyz = vec3( (2.0 * u_near@) / (u_far@ + u_near@ - texture2D( u_depth_texture, uv ).x * (u_far@ - u_near@)) );"
},
"logarithmic": {
name: "Logarithmic",
uniforms: {
"Log. A Factor": { name: "u_logfactor_a", type: "float", value: 2, step: 0.01 },
"Log. B Factor": { name: "u_logfactor_b", type: "float", value: 2, step: 0.01 }
},
code:"color.xyz = log( color.xyz * u_logfactor_a@ ) * u_logfactor_b@;"
},
"ditherBN": {
name: "dither B/N",
functions: ["dither"],
code:"color.xyz = vec3( dither( color.x ) );"
},
"dither": {
name: "Dither",
functions: ["dither"],
code:"color.xyz = vec3( dither( color.x ), dither( color.y ), dither( color.z ) );"
},
"gamma": {
name: "Gamma",
uniforms: {
"Gamma": { name: "u_gamma", type: "float", value: 2.2, step: 0.01 }
},
code:"color.xyz = pow( color.xyz, vec3( 1.0 / u_gamma@) );"
},
"noiseBN": {
name: "Noise B&N",
functions: ["noise"],
uniforms: {
"noise": { name: "u_noise", type: "float", value: 0.1, step: 0.01 }
},
code:"color.xyz += u_noise@ * vec3( noise( (u_random + v_coord) * u_viewport) );"
}
/*
"blur": {
name: "Blur",
break_pass: true,
uniforms: {
"blur_intensity": { name: "u_blur_intensity", type: "float", value: 0.1, step: 0.01 }
},
local_callback: FXStack.applyBlur
}
}
*/
//median: https://github.com/patriciogonzalezvivo/flatLand/blob/master/bin/data/median.frag
};
//functions that could be used
FXStack.available_functions = {
pattern: "float pattern(float angle, float size) {\n\
float s = sin(angle * 3.1415), c = cos(angle * 3.1415);\n\
vec2 tex = v_coord * u_viewport.xy;\n\
vec2 point = vec2( c * tex.x - s * tex.y , s * tex.x + c * tex.y ) * size;\n\
return (sin(point.x) * sin(point.y)) * 4.0;\n\
}\n\
",
dither: "float dither(float v) {\n\
vec2 pixel = v_coord * u_viewport;\n\
int i = int(floor(clamp(v,0.0,1.0) * 16.0 + 0.5));\n\
if(i < 1)\n\
return 0.0;\n\
if(i >= 15)\n\
return 1.0;\n\
float x = floor(pixel.x);\n\
float y = floor(pixel.y);\n\
bool xmod4 = mod(x, 4.0) == 0.0;\n\
bool ymod4 = mod(y, 4.0) == 0.0;\n\
bool xmod2 = mod(x, 2.0) == 0.0;\n\
bool ymod2 = mod(y, 2.0) == 0.0;\n\
bool xmod4_2 = mod(x + 2.0, 4.0) == 0.0;\n\
bool ymod4_2 = mod(y + 2.0, 4.0) == 0.0;\n\
bool xmod2_1 = mod(x + 1.0, 2.0) == 0.0;\n\
bool ymod2_1 = mod(y + 1.0, 2.0) == 0.0;\n\
bool xmod4_1 = mod(x + 1.0, 4.0) == 0.0;\n\
bool ymod4_1 = mod(y + 1.0, 4.0) == 0.0;\n\
bool xmod4_3 = mod(x + 3.0, 4.0) == 0.0;\n\
bool ymod4_3 = mod(y + 3.0, 4.0) == 0.0;\n\
\n\
if(i < 9)\n\
{\n\
if(i >= 1 && xmod4 && ymod4 )\n\
return 1.0;\n\
if(i >= 2 && xmod4_2 && ymod4_2)\n\
return 1.0;\n\
if(i >= 3 && xmod4_2 && ymod2 )\n\
return 1.0;\n\
if(i >= 4 && xmod2 && ymod2 )\n\
return 1.0;\n\
if(i >= 5 && xmod4_1 && ymod4_1 )\n\
return 1.0;\n\
if(i >= 6 && xmod4_3 && ymod4_3 )\n\
return 1.0;\n\
if(i >= 7 && xmod4_1 && ymod4_3 )\n\
return 1.0;\n\
if(i >= 8 && xmod4_3 && ymod4_1 )\n\
return 1.0;\n\
return 0.0;\n\
}\n\
else\n\
{\n\
if(i < 15 && xmod4_1 && ymod4 )\n\
return 0.0;\n\
if(i < 14 && xmod4_3 && ymod4_2)\n\
return 0.0;\n\
if(i < 13 && xmod4_3 && ymod2 )\n\
return 0.0;\n\
if(i < 12 && xmod2_1 && ymod2 )\n\
return 0.0;\n\
if(i < 11 && xmod4_2 && ymod4_1 )\n\
return 0.0;\n\
if(i < 10 && xmod4 && ymod4_3 )\n\
return 0.0;\n\
return 1.0;\n\
}\n\
}\n\
",
LUT: "vec3 LUT(in vec3 color, in sampler2D textureB) {\n\
lowp vec3 textureColor = clamp( color, vec3(0.0), vec3(1.0) );\n\
mediump float blueColor = textureColor.b * 63.0;\n\
mediump vec2 quad1;\n\
quad1.y = floor(floor(blueColor) / 8.0);\n\
quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\
mediump vec2 quad2;\n\
quad2.y = floor(ceil(blueColor) / 8.0);\n\
quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\
highp vec2 texPos1;\n\
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\
texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\
highp vec2 texPos2;\n\
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\
texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\
lowp vec3 newColor1 = texture2D(textureB, texPos1).xyz;\n\
lowp vec3 newColor2 = texture2D(textureB, texPos2).xyz;\n\
lowp vec3 newColor = mix(newColor1, newColor2, fract(blueColor));\n\
return newColor.rgb;\n\
}",
noise: "\n\
float hash(float n) { return fract(sin(n) * 1e4); }\n\
float hash(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); }\n\
float noise(float x) {\n\
float i = floor(x);\n\
float f = fract(x);\n\
float u = f * f * (3.0 - 2.0 * f);\n\
return mix(hash(i), hash(i + 1.0), u);\n\
}\n\
float noise(vec2 x) {\n\
vec2 i = floor(x);\n\
vec2 f = fract(x);\n\
float a = hash(i);\n\
float b = hash(i + vec2(1.0, 0.0));\n\
float c = hash(i + vec2(0.0, 1.0));\n\
float d = hash(i + vec2(1.0, 1.0));\n\
vec2 u = f * f * (3.0 - 2.0 * f);\n\
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;\n\
}\n\
",
HSV: "vec3 rgb2hsv(vec3 c)\n\
{\n\
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n\
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n\
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n\
\n\
float d = q.x - min(q.w, q.y);\n\
float e = 1.0e-10;\n\
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n\
}\n\
\n\
vec3 hsv2rgb(vec3 c)\n\
{\n\
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n\
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n\
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n\
}"
}
/**
* Returns the first component of this container that is of the same class
* @method configure
* @param {Object} o object with the configuration info from a previous serialization
*/
FXStack.prototype.configure = function(o)
{
this.apply_fxaa = !!o.apply_fxaa;
if(o.fx)
this.fx = o.fx.concat();
this._must_update_passes = true;
}
FXStack.prototype.serialize = FXStack.prototype.toJSON = function()
{
return {
apply_fxaa: this.apply_fxaa,
fx: this.fx.concat()
};
}
FXStack.prototype.getResources = function(res)
{
var fxs = this.fx;
for(var i = 0; i < fxs.length; i++)
{
var fx = fxs[i];
var fx_info = FXStack.available_fx[ fx.name ];
if(!fx_info)
continue;
if(!fx_info.uniforms)
continue;
for(var j in fx_info.uniforms)
{
var uniform = fx_info.uniforms[j];
if(uniform.type == "sampler2D" && fx[j])
res[ fx[j] ] = GL.Texture;
}
}
return res;
}
FXStack.prototype.onResourceRenamed = function(old_name, new_name, resource)
{
var fxs = this.fx;
for(var i = 0; i < fxs.length; i++)
{
var fx = fxs[i];
var fx_info = FXStack.available_fx[ fx.name ];
if(!fx_info)
continue;
if(!fx_info.uniforms)
continue;
for(var j in fx_info.uniforms)
{
var uniform = fx_info.uniforms[j];
if(uniform.type == "sampler2D" && fx[j] == old_name )
fx[j] = new_name;
}
}
}
//attach a new FX to the FX Stack
FXStack.prototype.addFX = function( name )
{
if(!name)
return;
if( !FXStack.available_fx[ name ] )
{
console.warn( "FXStack not found: " + name );
return;
}
this.fx.push({ name: name });
this._must_update_passes = true;
}
//returns the Nth FX in the FX Stack
FXStack.prototype.getFX = function(index)
{
return this.fx[ index ];
}
//rearranges an FX
FXStack.prototype.moveFX = function( fx, offset )
{
offset = offset || -1;
var index = this.fx.indexOf(fx);
if( index == -1 )
return;
this.fx.splice(index,1);
index += offset;
if(index >= 0 && index < this.fx.length)
this.fx.splice(index,0,fx);
else
this.fx.push(fx);
this._must_update_passes = true;
}
//removes an FX from the FX stack
FXStack.prototype.removeFX = function( fx )
{
for(var i = 0; i < this.fx.length; i++)
{
if(this.fx[i] !== fx)
continue;
this.fx.splice(i,1);
this._must_update_passes = true;
return;
}
}
//extract the number of passes to do according to the fx enabled
FXStack.prototype.buildPasses = function()
{
var fxs = this.fx;
var passes = [];
var current_pass = {
fxs:[],
uniforms:{},
shader:null,
first_fx_id: 0
};
var uv_code = "";
var color_code = "";
var uniforms_code = "";
var included_functions = {};
var is_first = true;
var fx_id = 0;
for(var i = 0; i < fxs.length; i++)
{
//the FX settings
var fx = fxs[i];
fx_id = i;
//the FX definition
var fx_info = FXStack.available_fx[ fx.name ];
if(!fx_info)
continue;
//break this pass
if( fx_info.break_pass && !is_first)
{
current_pass.uv_code = uv_code;
current_pass.color_code = color_code;
current_pass.uniforms_code = uniforms_code;
current_pass.included_functions = included_functions;
passes.push(current_pass);
this.buildPassShader( current_pass );
uv_code = "";
color_code = "";
uniforms_code = "";
included_functions = {};
current_pass = {
fxs:[],
uniforms:{},
first_fx_id: fx_id
};
is_first = true;
}
else
is_first = false;
if(fx_info.functions)
for(var z in fx_info.functions)
included_functions[ fx_info.functions[z] ] = true;
if( fx_info.code )
color_code += fx_info.code.split("@").join( fx_id ) + ";\n";
if( fx_info.uv_code )
uv_code += fx_info.uv_code.split("@").join( fx_id ) + ";\n";
if(fx_info.uniforms)
for(var j in fx_info.uniforms)
{
var uniform = fx_info.uniforms[j];
var varname = uniform.name + fx_id;
uniforms_code += "uniform " + uniform.type + " " + varname + ";\n";
}
current_pass.fxs.push( fx );
}
if(!is_first)
{
current_pass.uv_code = uv_code;
current_pass.color_code = color_code;
current_pass.included_functions = included_functions;
passes.push( current_pass );
this.buildPassShader( current_pass );
}
this._passes = passes;
}
FXStack.prototype.buildPassShader = function( pass )
{
var functions_code = "";
for(var i in pass.included_functions)
{
var func = FXStack.available_functions[ i ];
if(!func)
{
console.error("FXStack: Function not found: " + i);
continue;
}
functions_code += func + "\n";
}
var fullcode = "\n\
#extension GL_OES_standard_derivatives : enable\n\
precision highp float;\n\
#define color3 vec3\n\
#define color4 vec4\n\
uniform sampler2D u_texture;\n\
uniform sampler2D u_depth_texture;\n\
varying vec2 v_coord;\n\
uniform vec2 u_viewport;\n\
uniform vec2 u_iviewport;\n\
uniform float u_aspect;\n\
uniform vec2 u_depth_range;\n\
uniform vec2 u_random;\n\
vec2 uv;\n\
" + pass.uniforms_code + "\n\
" + functions_code + "\n\
void main() {\n\
uv = v_coord;\n\
vec2 to_center = vec2(0.5) - uv;\n\
float dist_to_center = length(to_center);\n\
" + pass.uv_code + "\n\
vec4 color = texture2D(u_texture, uv);\n\
float temp = 0.0;\n\
" + pass.color_code + "\n\
gl_FragColor = color;\n\
}\n\
";
this._must_update_passes = false;
pass.shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, fullcode );
return pass.shader;
}
FXStack.prototype.applyFX = function( input_texture, output_texture, options )
{
var color_texture = input_texture;
var depth_texture = options.depth_texture;
var global_uniforms = this._uniforms;
global_uniforms.u_viewport[0] = color_texture.width;
global_uniforms.u_viewport[1] = color_texture.height;
global_uniforms.u_iviewport[0] = 1 / color_texture.width;
global_uniforms.u_iviewport[1] = 1 / color_texture.height;
global_uniforms.u_aspect = color_texture.width / color_texture.height;
global_uniforms.u_random[0] = Math.random();
global_uniforms.u_random[1] = Math.random();
if(!this._passes || this._must_update_passes )
this.buildPasses();
if(!this._passes.length)
{
if(output_texture)
input_texture.copyTo( output_texture );
else
{
var fxaa_shader = GL.Shader.getFXAAShader();
fxaa_shader.setup();
input_texture.toViewport( this.apply_fxaa ? fxaa_shader : null );
}
return;
}
var w = output_texture ? output_texture.width : input_texture.width;
var h = output_texture ? output_texture.height : input_texture.height;
var origin_texture = GL.Texture.getTemporary( w, h, { type: input_texture.type, format: input_texture.format } );
var target_texture = GL.Texture.getTemporary( w, h, { type: input_texture.type, format: input_texture.format } );
input_texture.copyTo( origin_texture );
var fx_id = 0;
for(var i = 0; i < this._passes.length; i++)
{
var pass = this._passes[i];
var texture_slot = 2;
var uniforms = pass.uniforms;
//gather uniform values
for(var j = 0; j < pass.fxs.length; ++j)
{
var fx = pass.fxs[j];
fx_id = pass.first_fx_id + j;
//the FX definition
var fx_info = FXStack.available_fx[ fx.name ];
if(!fx_info)
continue;
if(!fx_info.uniforms)
continue;
for(var k in fx_info.uniforms)
{
var uniform = fx_info.uniforms[k];
var varname = uniform.name + fx_id;
if(uniform.type == "sampler2D")
{
uniforms[ varname ] = texture_slot;
var tex = this.getTexture( fx[k] );
if(tex)
{
tex.bind( texture_slot );
if(uniform.filter == "nearest")
{
gl.texParameteri( tex.texture_type, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( tex.texture_type, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
}
if(uniform.wrap == "clamp")
{
gl.texParameteri( tex.texture_type, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( tex.texture_type, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
}
}
else
{
//bind something to avoid problems
tex = LS.Renderer._missing_texture;
if(tex)
tex.bind( texture_slot );
}
texture_slot++;
}
else
uniforms[ varname ] = fx[j] !== undefined ? fx[j] : uniform.value;
}
}
//apply pass
var shader = pass.shader;
//error compiling shader
if(!shader)
{
input_texture.toViewport(); //what about output_texture?
break;
}
//set the depth texture for some FXs like fog or depth
if(depth_texture && shader.hasUniform("u_depth_texture"))
{
depth_texture.bind(1);
if(depth_texture.near_far_planes)
uniforms.u_depth_range = depth_texture.near_far_planes;
}
//apply FX and accumulate in secondary texture ***************
shader.uniforms( global_uniforms );
origin_texture.copyTo( target_texture, shader, uniforms );
//swap
var tmp = origin_texture;
origin_texture = target_texture;
target_texture = tmp;
}
//to the screen or the output_texture
var final_texture = target_texture;
final_texture.setParameter( gl.TEXTURE_MAG_FILTER, this.filter ? gl.LINEAR : gl.NEAREST );
final_texture.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.disable( gl.DEPTH_TEST );
gl.disable( gl.BLEND );
gl.disable( gl.CULL_FACE );
//to screen
if( this.apply_fxaa )
{
var fx_aa_shader = GL.Shader.getFXAAShader();
fx_aa_shader.setup();
if(!output_texture)
final_texture.toViewport( fx_aa_shader );
else
final_texture.copyTo( output_texture, fx_aa_shader );
}
else
{
if(!output_texture)
final_texture.toViewport();
else
{
shader.uniforms( uniforms );
final_texture.copyTo( output_texture, shader );
}
}
//release textures back to the pool
GL.Texture.releaseTemporary( origin_texture );
GL.Texture.releaseTemporary( target_texture );
}
//executes the FX stack in the input texture and outputs the result in the output texture (or the screen)
FXStack.prototype.applyFX = function( input_texture, output_texture, options )
{
var color_texture = input_texture;
var depth_texture = options.depth_texture;
var fxs = this.fx;
var update_shader = this._must_update_passes;
this._must_update_passes = false;
var uniforms = this._uniforms;
uniforms.u_viewport[0] = color_texture.width;
uniforms.u_viewport[1] = color_texture.height;
uniforms.u_iviewport[0] = 1 / color_texture.width;
uniforms.u_iviewport[1] = 1 / color_texture.height;
uniforms.u_aspect = color_texture.width / color_texture.height;
uniforms.u_random[0] = Math.random();
uniforms.u_random[1] = Math.random();
var uv_code = "";
var color_code = "";
var included_functions = {};
var uniforms_code = "";
var texture_slot = 2;
var fx_id = 0;
for(var i = 0; i < fxs.length; i++)
{
//the FX settings
var fx = fxs[i];
fx_id = i;
//the FX definition
var fx_info = FXStack.available_fx[ fx.name ];
if(!fx_info)
continue;
if(update_shader)
{
if(fx_info.functions)
for(var z in fx_info.functions)
included_functions[ fx_info.functions[z] ] = true;
if( fx_info.code )
color_code += fx_info.code.split("@").join( fx_id ) + ";\n";
if( fx_info.uv_code )
uv_code += fx_info.uv_code.split("@").join( fx_id ) + ";\n";
}
if(fx_info.uniforms)
for(var j in fx_info.uniforms)
{
var uniform = fx_info.uniforms[j];
var varname = uniform.name + fx_id;
if(update_shader)
{
uniforms_code += "uniform " + uniform.type + " " + varname + ";\n";
}
if(uniform.type == "sampler2D")
{
uniforms[ varname ] = texture_slot;
var tex = this.getTexture( fx[j] );
if(tex)
{
tex.bind( texture_slot );
if(uniform.filter == "nearest")
{
gl.texParameteri( tex.texture_type, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( tex.texture_type, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
}
if(uniform.wrap == "clamp")
{
gl.texParameteri( tex.texture_type, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( tex.texture_type, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
}
}
else
{
//bind something to avoid problems
tex = LS.Renderer._missing_texture;
if(tex)
tex.bind( texture_slot );
}
texture_slot++;
}
else
uniforms[ varname ] = fx[j] !== undefined ? fx[j] : uniform.value;
}
}
var shader = null;
if(update_shader)
{
var functions_code = "";
for(var i in included_functions)
{
var func = FXStack.available_functions[ i ];
if(!func)
{
console.error("FXStack: Function not found: " + i);
continue;
}
functions_code += func + "\n";
}
var fullcode = "\n\
#extension GL_OES_standard_derivatives : enable\n\
precision highp float;\n\
#define color3 vec3\n\
#define color4 vec4\n\
uniform sampler2D u_texture;\n\
uniform sampler2D u_depth_texture;\n\
varying vec2 v_coord;\n\
uniform vec2 u_viewport;\n\
uniform vec2 u_iviewport;\n\
uniform float u_aspect;\n\
uniform vec2 u_depth_range;\n\
uniform vec2 u_random;\n\
vec2 uv;\n\
" + uniforms_code + "\n\
" + functions_code + "\n\
void main() {\n\
uv = v_coord;\n\
vec2 to_center = vec2(0.5) - uv;\n\
float dist_to_center = length(to_center);\n\
" + uv_code + "\n\
vec4 color = texture2D(u_texture, uv);\n\
float temp = 0.0;\n\
" + color_code + "\n\
gl_FragColor = color;\n\
}\n\
";
this._last_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, fullcode );
}
shader = this._last_shader;
gl.disable( gl.DEPTH_TEST );
gl.disable( gl.BLEND );
gl.disable( gl.CULL_FACE );
//error compiling shader
if(!shader)
{
input_texture.toViewport();
return;
}
//set the depth texture for some FXs like fog or depth
if(shader.hasUniform("u_depth_texture") && depth_texture )
{
depth_texture.bind(1);
if(depth_texture.near_far_planes)
uniforms.u_depth_range = depth_texture.near_far_planes;
}
color_texture.setParameter( gl.TEXTURE_MAG_FILTER, this.filter ? gl.LINEAR : gl.NEAREST );
color_texture.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR );
if( this.apply_fxaa )
{
if(!this.temp_tex || this.temp_tex.width != gl.viewport_data[2] || this.temp_tex.height != gl.viewport_data[3])
this.temp_tex = new GL.Texture(gl.viewport_data[2],gl.viewport_data[3]);
this.temp_tex.drawTo(function(){
color_texture.toViewport( shader, uniforms );
});
var fx_aa_shader = GL.Shader.getFXAAShader();
fx_aa_shader.setup();
if(!output_texture)
this.temp_tex.toViewport( fx_aa_shader );
else
this.temp_tex.copyTo( output_texture, fx_aa_shader );
}
else
{
this.temp_tex = null;
if(!output_texture)
color_texture.toViewport( shader, uniforms );
else
{
shader.uniforms( uniforms );
color_texture.copyTo( output_texture, shader );
}
}
}
FXStack.prototype.getTexture = function( name )
{
return LS.ResourcesManager.getTexture( name );
}
FXStack.prototype.getPropertyInfoFromPath = function( path )
{
if(path.length < 2)
return null;
var fx_num = parseInt( path[0] );
//fx not active
if(fx_num >= this.fx.length)
return null;
var fx = this.fx[ fx_num ];
var fx_info = FXStack.available_fx[ fx.name ];
if(!fx_info)
return null;
var varname = path[1];
if(varname == "name")
return null;
var uniform = fx_info.uniforms[ varname ];
if(!uniform)
return null;
var type = uniform.type;
if(type == "float")
type = "number";
else if(type == "sampler2D")
type = "texture";
return {
target: fx,
name: varname,
value: fx[ varname ],
type: uniform.type || "number"
};
}
FXStack.prototype.setPropertyValueFromPath = function( path, value, offset )
{
offset = offset || 0;
if( path.length < (offset+1) )
return null;
var fx_num = parseInt( path[offset] );
if(fx_num >= this.fx.length)
return null;
var fx = this.fx[ fx_num ];
if(!fx)
return null;
var varname = path[offset+1];
if (fx[ varname ] === undefined )
return null;
//to avoid incompatible types
if( fx[ varname ] !== undefined && value !== undefined && fx[ varname ].constructor === value.constructor )
fx[ varname ] = value;
}
//static method to register new FX in the system
FXStack.registerFX = function( name, fx_info )
{
if( !fx_info.name )
fx_info.name = name;
if( fx_info.code === undefined )
throw("FXStack must have a code");
if( fx_info.uniforms && Object.keys( fx_info.uniforms ) && fx_info.code && fx_info.code.indexOf("@") == -1 )
console.warn("FXStack using uniforms must use the character '@' at the end of every use to avoid collisions with other variables with the same name.");
FXStack.available_fx[ name ] = fx_info;
}
//for common functions shared among different FXs...
FXStack.registerFunction = function( name, code )
{
FXStack.available_functions[name] = code;
}
LS.FXStack = FXStack;
LS.TextureFX = FXStack; //LEGACY