Files
ComfyUI_frontend/src/nodes/gltextures.js
2019-04-01 20:15:17 +02:00

3556 lines
105 KiB
JavaScript
Executable File

(function(global){
var LiteGraph = global.LiteGraph;
//Works with Litegl.js to create WebGL nodes
global.LGraphTexture = null;
if(typeof(GL) != "undefined")
{
LGraphCanvas.link_type_colors["Texture"] = "#987";
function LGraphTexture()
{
this.addOutput("Texture","Texture");
this.properties = { name:"", filter: true };
this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size];
}
global.LGraphTexture = LGraphTexture;
LGraphTexture.title = "Texture";
LGraphTexture.desc = "Texture";
LGraphTexture.widgets_info = {"name": { widget:"texture"}, "filter": { widget:"checkbox"} };
//REPLACE THIS TO INTEGRATE WITH YOUR FRAMEWORK
LGraphTexture.loadTextureCallback = null; //function in charge of loading textures when not present in the container
LGraphTexture.image_preview_size = 256;
//flags to choose output texture type
LGraphTexture.PASS_THROUGH = 1; //do not apply FX
LGraphTexture.COPY = 2; //create new texture with the same properties as the origin texture
LGraphTexture.LOW = 3; //create new texture with low precision (byte)
LGraphTexture.HIGH = 4; //create new texture with high precision (half-float)
LGraphTexture.REUSE = 5; //reuse input texture
LGraphTexture.DEFAULT = 2;
LGraphTexture.MODE_VALUES = {
"pass through": LGraphTexture.PASS_THROUGH,
"copy": LGraphTexture.COPY,
"low": LGraphTexture.LOW,
"high": LGraphTexture.HIGH,
"reuse": LGraphTexture.REUSE,
"default": LGraphTexture.DEFAULT
};
//returns the container where all the loaded textures are stored (overwrite if you have a Resources Manager)
LGraphTexture.getTexturesContainer = function()
{
return gl.textures;
}
//process the loading of a texture (overwrite it if you have a Resources Manager)
LGraphTexture.loadTexture = function(name, options)
{
options = options || {};
var url = name;
if(url.substr(0,7) == "http://")
{
if(LiteGraph.proxy) //proxy external files
url = LiteGraph.proxy + url.substr(7);
}
var container = LGraphTexture.getTexturesContainer();
var tex = container[ name ] = GL.Texture.fromURL(url, options);
return tex;
}
LGraphTexture.getTexture = function(name)
{
var container = this.getTexturesContainer();
if(!container)
throw("Cannot load texture, container of textures not found");
var tex = container[ name ];
if(!tex && name && name[0] != ":")
return this.loadTexture(name);
return tex;
}
//used to compute the appropiate output texture
LGraphTexture.getTargetTexture = function( origin, target, mode )
{
if(!origin)
throw("LGraphTexture.getTargetTexture expects a reference texture");
var tex_type = null;
switch(mode)
{
case LGraphTexture.LOW: tex_type = gl.UNSIGNED_BYTE; break;
case LGraphTexture.HIGH: tex_type = gl.HIGH_PRECISION_FORMAT; break;
case LGraphTexture.REUSE: return origin; break;
case LGraphTexture.COPY:
default: tex_type = origin ? origin.type : gl.UNSIGNED_BYTE; break;
}
if(!target || target.width != origin.width || target.height != origin.height || target.type != tex_type )
target = new GL.Texture( origin.width, origin.height, { type: tex_type, format: gl.RGBA, filter: gl.LINEAR });
return target;
}
LGraphTexture.getTextureType = function( precision, ref_texture )
{
var type = ref_texture ? ref_texture.type : gl.UNSIGNED_BYTE;
switch( precision )
{
case LGraphTexture.HIGH: type = gl.HIGH_PRECISION_FORMAT; break;
case LGraphTexture.LOW: type = gl.UNSIGNED_BYTE; break;
//no default
}
return type;
}
LGraphTexture.getWhiteTexture = function()
{
if(this._white_texture)
return this._white_texture;
var texture = this._white_texture = GL.Texture.fromMemory(1,1,[255,255,255,255],{ format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST });
return texture;
}
LGraphTexture.getNoiseTexture = function()
{
if(this._noise_texture)
return this._noise_texture;
var noise = new Uint8Array(512*512*4);
for(var i = 0; i < 512*512*4; ++i)
noise[i] = Math.random() * 255;
var texture = GL.Texture.fromMemory(512,512,noise,{ format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST });
this._noise_texture = texture;
return texture;
}
LGraphTexture.prototype.onDropFile = function(data, filename, file)
{
if(!data)
{
this._drop_texture = null;
this.properties.name = "";
}
else
{
var texture = null;
if( typeof(data) == "string" )
texture = GL.Texture.fromURL( data );
else if( filename.toLowerCase().indexOf(".dds") != -1 )
texture = GL.Texture.fromDDSInMemory(data);
else
{
var blob = new Blob([file]);
var url = URL.createObjectURL(blob);
texture = GL.Texture.fromURL( url );
}
this._drop_texture = texture;
this.properties.name = filename;
}
}
LGraphTexture.prototype.getExtraMenuOptions = function(graphcanvas)
{
var that = this;
if(!this._drop_texture)
return;
return [ {content:"Clear", callback:
function() {
that._drop_texture = null;
that.properties.name = "";
}
}];
}
LGraphTexture.prototype.onExecute = function()
{
var tex = null;
if(this.isOutputConnected(1))
tex = this.getInputData(0);
if(!tex && this._drop_texture)
tex = this._drop_texture;
if(!tex && this.properties.name)
tex = LGraphTexture.getTexture( this.properties.name );
if(!tex)
return;
this._last_tex = tex;
if(this.properties.filter === false)
tex.setParameter( gl.TEXTURE_MAG_FILTER, gl.NEAREST );
else
tex.setParameter( gl.TEXTURE_MAG_FILTER, gl.LINEAR );
this.setOutputData(0, tex);
for(var i = 1; i < this.outputs.length; i++)
{
var output = this.outputs[i];
if(!output)
continue;
var v = null;
if(output.name == "width")
v = tex.width;
else if(output.name == "height")
v = tex.height;
else if(output.name == "aspect")
v = tex.width / tex.height;
this.setOutputData(i, v);
}
}
LGraphTexture.prototype.onResourceRenamed = function(old_name,new_name)
{
if(this.properties.name == old_name)
this.properties.name = new_name;
}
LGraphTexture.prototype.onDrawBackground = function(ctx)
{
if( this.flags.collapsed || this.size[1] <= 20 )
return;
if( this._drop_texture && ctx.webgl )
{
ctx.drawImage( this._drop_texture, 0,0,this.size[0],this.size[1]);
//this._drop_texture.renderQuad(this.pos[0],this.pos[1],this.size[0],this.size[1]);
return;
}
//Different texture? then get it from the GPU
if(this._last_preview_tex != this._last_tex)
{
if(ctx.webgl)
{
this._canvas = this._last_tex;
}
else
{
var tex_canvas = LGraphTexture.generateLowResTexturePreview(this._last_tex);
if(!tex_canvas)
return;
this._last_preview_tex = this._last_tex;
this._canvas = cloneCanvas(tex_canvas);
}
}
if(!this._canvas)
return;
//render to graph canvas
ctx.save();
if(!ctx.webgl) //reverse image
{
ctx.translate(0,this.size[1]);
ctx.scale(1,-1);
}
ctx.drawImage(this._canvas,0,0,this.size[0],this.size[1]);
ctx.restore();
}
//very slow, used at your own risk
LGraphTexture.generateLowResTexturePreview = function(tex)
{
if(!tex)
return null;
var size = LGraphTexture.image_preview_size;
var temp_tex = tex;
if(tex.format == gl.DEPTH_COMPONENT)
return null; //cannot generate from depth
//Generate low-level version in the GPU to speed up
if(tex.width > size || tex.height > size)
{
temp_tex = this._preview_temp_tex;
if(!this._preview_temp_tex)
{
temp_tex = new GL.Texture(size,size, { minFilter: gl.NEAREST });
this._preview_temp_tex = temp_tex;
}
//copy
tex.copyTo(temp_tex);
tex = temp_tex;
}
//create intermediate canvas with lowquality version
var tex_canvas = this._preview_canvas;
if(!tex_canvas)
{
tex_canvas = createCanvas(size,size);
this._preview_canvas = tex_canvas;
}
if(temp_tex)
temp_tex.toCanvas(tex_canvas);
return tex_canvas;
}
LGraphTexture.prototype.getResources = function(res)
{
res[ this.properties.name ] = GL.Texture;
return res;
}
LGraphTexture.prototype.onGetInputs = function()
{
return [["in","Texture"]];
}
LGraphTexture.prototype.onGetOutputs = function()
{
return [["width","number"],["height","number"],["aspect","number"]];
}
LiteGraph.registerNodeType("texture/texture", LGraphTexture );
//**************************
function LGraphTexturePreview()
{
this.addInput("Texture","Texture");
this.properties = { flipY: false };
this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size];
}
LGraphTexturePreview.title = "Preview";
LGraphTexturePreview.desc = "Show a texture in the graph canvas";
LGraphTexturePreview.allow_preview = false;
LGraphTexturePreview.prototype.onDrawBackground = function(ctx)
{
if(this.flags.collapsed)
return;
if(!ctx.webgl && !LGraphTexturePreview.allow_preview)
return; //not working well
var tex = this.getInputData(0);
if(!tex)
return;
var tex_canvas = null;
if(!tex.handle && ctx.webgl)
tex_canvas = tex;
else
tex_canvas = LGraphTexture.generateLowResTexturePreview(tex);
//render to graph canvas
ctx.save();
if(this.properties.flipY)
{
ctx.translate(0,this.size[1]);
ctx.scale(1,-1);
}
ctx.drawImage(tex_canvas,0,0,this.size[0],this.size[1]);
ctx.restore();
}
LiteGraph.registerNodeType("texture/preview", LGraphTexturePreview );
//**************************************
function LGraphTextureSave()
{
this.addInput("Texture","Texture");
this.addOutput("","Texture");
this.properties = {name:""};
}
LGraphTextureSave.title = "Save";
LGraphTextureSave.desc = "Save a texture in the repository";
LGraphTextureSave.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex)
return;
if(this.properties.name)
{
//for cases where we want to perform something when storing it
if( LGraphTexture.storeTexture )
LGraphTexture.storeTexture( this.properties.name, tex );
else
{
var container = LGraphTexture.getTexturesContainer();
container[ this.properties.name ] = tex;
}
}
this.setOutputData(0, tex);
}
LiteGraph.registerNodeType("texture/save", LGraphTextureSave );
//****************************************************
function LGraphTextureOperation()
{
this.addInput("Texture","Texture");
this.addInput("TextureB","Texture");
this.addInput("value","number");
this.addOutput("Texture","Texture");
this.help = "<p>pixelcode must be vec3</p>\
<p>uvcode must be vec2, is optional</p>\
<p><strong>uv:</strong> tex. coords</p><p><strong>color:</strong> texture</p><p><strong>colorB:</strong> textureB</p><p><strong>time:</strong> scene time</p><p><strong>value:</strong> input value</p>";
this.properties = {value:1, uvcode:"", pixelcode:"color + colorB * value", precision: LGraphTexture.DEFAULT };
}
LGraphTextureOperation.widgets_info = {
"uvcode": { widget:"textarea", height: 100 },
"pixelcode": { widget:"textarea", height: 100 },
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureOperation.title = "Operation";
LGraphTextureOperation.desc = "Texture shader operation";
LGraphTextureOperation.prototype.getExtraMenuOptions = function(graphcanvas)
{
var that = this;
var txt = !that.properties.show ? "Show Texture" : "Hide Texture";
return [ {content: txt, callback:
function() {
that.properties.show = !that.properties.show;
}
}];
}
LGraphTextureOperation.prototype.onDrawBackground = function(ctx)
{
if(this.flags.collapsed || this.size[1] <= 20 || !this.properties.show)
return;
if(!this._tex)
return;
//only works if using a webgl renderer
if(this._tex.gl != ctx)
return;
//render to graph canvas
ctx.save();
ctx.drawImage(this._tex, 0, 0, this.size[0], this.size[1]);
ctx.restore();
}
LGraphTextureOperation.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!this.isOutputConnected(0))
return; //saves work
if(this.properties.precision === LGraphTexture.PASS_THROUGH)
{
this.setOutputData(0, tex);
return;
}
var texB = this.getInputData(1);
if(!this.properties.uvcode && !this.properties.pixelcode)
return;
var width = 512;
var height = 512;
if(tex)
{
width = tex.width;
height = tex.height;
}
else if (texB)
{
width = texB.width;
height = texB.height;
}
var type = LGraphTexture.getTextureType( this.properties.precision, tex );
if(!tex && !this._tex )
this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });
else
this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision );
var uvcode = "";
if(this.properties.uvcode)
{
uvcode = "uv = " + this.properties.uvcode;
if(this.properties.uvcode.indexOf(";") != -1) //there are line breaks, means multiline code
uvcode = this.properties.uvcode;
}
var pixelcode = "";
if(this.properties.pixelcode)
{
pixelcode = "result = " + this.properties.pixelcode;
if(this.properties.pixelcode.indexOf(";") != -1) //there are line breaks, means multiline code
pixelcode = this.properties.pixelcode;
}
var shader = this._shader;
if(!shader || this._shader_code != (uvcode + "|" + pixelcode) )
{
try
{
this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureOperation.pixel_shader, { UV_CODE: uvcode, PIXEL_CODE: pixelcode });
this.boxcolor = "#00FF00";
}
catch (err)
{
console.log("Error compiling shader: ", err);
this.boxcolor = "#FF0000";
return;
}
this.boxcolor = "#FF0000";
this._shader_code = (uvcode + "|" + pixelcode);
shader = this._shader;
}
if(!shader)
{
this.boxcolor = "red";
return;
}
else
this.boxcolor = "green";
var value = this.getInputData(2);
if(value != null)
this.properties.value = value;
else
value = parseFloat( this.properties.value );
var time = this.graph.getTime();
this._tex.drawTo(function() {
gl.disable( gl.DEPTH_TEST );
gl.disable( gl.CULL_FACE );
gl.disable( gl.BLEND );
if(tex) tex.bind(0);
if(texB) texB.bind(1);
var mesh = Mesh.getScreenQuad();
shader.uniforms({u_texture:0, u_textureB:1, value: value, texSize:[width,height], time: time}).draw(mesh);
});
this.setOutputData(0, this._tex);
}
LGraphTextureOperation.pixel_shader = "precision highp float;\n\
\n\
uniform sampler2D u_texture;\n\
uniform sampler2D u_textureB;\n\
varying vec2 v_coord;\n\
uniform vec2 texSize;\n\
uniform float time;\n\
uniform float value;\n\
\n\
void main() {\n\
vec2 uv = v_coord;\n\
UV_CODE;\n\
vec4 color4 = texture2D(u_texture, uv);\n\
vec3 color = color4.rgb;\n\
vec4 color4B = texture2D(u_textureB, uv);\n\
vec3 colorB = color4B.rgb;\n\
vec3 result = color;\n\
float alpha = 1.0;\n\
PIXEL_CODE;\n\
gl_FragColor = vec4(result, alpha);\n\
}\n\
";
LiteGraph.registerNodeType("texture/operation", LGraphTextureOperation );
//****************************************************
function LGraphTextureShader()
{
this.addOutput("out","Texture");
this.properties = {code:"", width: 512, height: 512, precision: LGraphTexture.DEFAULT };
this.properties.code = "\nvoid main() {\n vec2 uv = v_coord;\n vec3 color = vec3(0.0);\n//your code here\n\ngl_FragColor = vec4(color, 1.0);\n}\n";
this._uniforms = { in_texture:0, texSize: vec2.create(), time: 0 };
}
LGraphTextureShader.title = "Shader";
LGraphTextureShader.desc = "Texture shader";
LGraphTextureShader.widgets_info = {
"code": { type:"code" },
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureShader.prototype.onPropertyChanged = function(name, value)
{
if(name != "code")
return;
var shader = this.getShader();
if(!shader)
return;
//update connections
var uniforms = shader.uniformInfo;
//remove deprecated slots
if(this.inputs)
{
var already = {};
for(var i = 0; i < this.inputs.length; ++i)
{
var info = this.getInputInfo(i);
if(!info)
continue;
if( uniforms[ info.name ] && !already[ info.name ] )
{
already[ info.name ] = true;
continue;
}
this.removeInput(i);
i--;
}
}
//update existing ones
for(var i in uniforms)
{
var info = shader.uniformInfo[i];
if(info.loc === null)
continue; //is an attribute, not a uniform
if(i == "time") //default one
continue;
var type = "number";
if( this._shader.samplers[i] )
type = "texture";
else
{
switch(info.size)
{
case 1: type = "number"; break;
case 2: type = "vec2"; break;
case 3: type = "vec3"; break;
case 4: type = "vec4"; break;
case 9: type = "mat3"; break;
case 16: type = "mat4"; break;
default: continue;
}
}
var slot = this.findInputSlot(i);
if(slot == -1)
{
this.addInput(i,type);
continue;
}
var input_info = this.getInputInfo(slot);
if(!input_info)
this.addInput(i,type);
else
{
if(input_info.type == type)
continue;
this.removeInput(slot,type);
this.addInput(i,type);
}
}
}
LGraphTextureShader.prototype.getShader = function()
{
//replug
if(this._shader && this._shader_code == this.properties.code)
return this._shader;
this._shader_code = this.properties.code;
this._shader = new GL.Shader(Shader.SCREEN_VERTEX_SHADER, LGraphTextureShader.pixel_shader + this.properties.code );
if(!this._shader) {
this.boxcolor = "red";
return null;
}
else
this.boxcolor = "green";
return this._shader;
}
LGraphTextureShader.prototype.onExecute = function()
{
if(!this.isOutputConnected(0))
return; //saves work
var shader = this.getShader();
if(!shader)
return;
var tex_slot = 0;
var in_tex = null;
//set uniforms
for(var i = 0; i < this.inputs.length; ++i)
{
var info = this.getInputInfo(i);
var data = this.getInputData(i);
if(data == null)
continue;
if(data.constructor === GL.Texture)
{
data.bind(tex_slot);
if(!in_tex)
in_tex = data;
data = tex_slot;
tex_slot++;
}
shader.setUniform( info.name, data ); //data is tex_slot
}
var uniforms = this._uniforms;
var type = LGraphTexture.getTextureType( this.properties.precision, in_tex );
//render to texture
var w = this.properties.width|0;
var h = this.properties.height|0;
if(w == 0)
w = in_tex ? in_tex.width : gl.canvas.width;
if(h == 0)
h = in_tex ? in_tex.height : gl.canvas.height;
uniforms.texSize[0] = w;
uniforms.texSize[1] = h;
uniforms.time = this.graph.getTime();
if(!this._tex || this._tex.type != type || this._tex.width != w || this._tex.height != h )
this._tex = new GL.Texture( w, h, { type: type, format: gl.RGBA, filter: gl.LINEAR });
var tex = this._tex;
tex.drawTo(function() {
shader.uniforms( uniforms ).draw( GL.Mesh.getScreenQuad() );
});
this.setOutputData( 0, this._tex );
}
LGraphTextureShader.pixel_shader = "precision highp float;\n\
\n\
varying vec2 v_coord;\n\
uniform float time;\n\
";
LiteGraph.registerNodeType("texture/shader", LGraphTextureShader );
// Texture Scale Offset
function LGraphTextureScaleOffset()
{
this.addInput("in","Texture");
this.addInput("scale","vec2");
this.addInput("offset","vec2");
this.addOutput("out","Texture");
this.properties = { offset: vec2.fromValues(0,0), scale: vec2.fromValues(1,1), precision: LGraphTexture.DEFAULT };
}
LGraphTextureScaleOffset.widgets_info = {
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureScaleOffset.title = "Scale/Offset";
LGraphTextureScaleOffset.desc = "Applies an scaling and offseting";
LGraphTextureScaleOffset.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!this.isOutputConnected(0) || !tex)
return; //saves work
if(this.properties.precision === LGraphTexture.PASS_THROUGH)
{
this.setOutputData(0, tex);
return;
}
var width = tex.width;
var height = tex.height;
var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT;
if (this.precision === LGraphTexture.DEFAULT)
type = tex.type;
if(!this._tex || this._tex.width != width || this._tex.height != height || this._tex.type != type )
this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR });
var shader = this._shader;
if(!shader)
shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureScaleOffset.pixel_shader );
var scale = this.getInputData(1);
if(scale)
{
this.properties.scale[0] = scale[0];
this.properties.scale[1] = scale[1];
}
else
scale = this.properties.scale;
var offset = this.getInputData(2);
if(offset)
{
this.properties.offset[0] = offset[0];
this.properties.offset[1] = offset[1];
}
else
offset = this.properties.offset;
this._tex.drawTo(function() {
gl.disable( gl.DEPTH_TEST );
gl.disable( gl.CULL_FACE );
gl.disable( gl.BLEND );
tex.bind(0);
var mesh = Mesh.getScreenQuad();
shader.uniforms({u_texture:0, u_scale: scale, u_offset: offset}).draw( mesh );
});
this.setOutputData( 0, this._tex );
}
LGraphTextureScaleOffset.pixel_shader = "precision highp float;\n\
\n\
uniform sampler2D u_texture;\n\
uniform sampler2D u_textureB;\n\
varying vec2 v_coord;\n\
uniform vec2 u_scale;\n\
uniform vec2 u_offset;\n\
\n\
void main() {\n\
vec2 uv = v_coord;\n\
uv = uv / u_scale - u_offset;\n\
gl_FragColor = texture2D(u_texture, uv);\n\
}\n\
";
LiteGraph.registerNodeType("texture/scaleOffset", LGraphTextureScaleOffset );
// Warp (distort a texture) *************************
function LGraphTextureWarp()
{
this.addInput("in","Texture");
this.addInput("warp","Texture");
this.addInput("factor","number");
this.addOutput("out","Texture");
this.properties = { factor: 0.01, precision: LGraphTexture.DEFAULT };
}
LGraphTextureWarp.widgets_info = {
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureWarp.title = "Warp";
LGraphTextureWarp.desc = "Texture warp operation";
LGraphTextureWarp.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!this.isOutputConnected(0))
return; //saves work
if(this.properties.precision === LGraphTexture.PASS_THROUGH)
{
this.setOutputData(0, tex);
return;
}
var texB = this.getInputData(1);
var width = 512;
var height = 512;
var type = gl.UNSIGNED_BYTE;
if(tex)
{
width = tex.width;
height = tex.height;
type = tex.type;
}
else if (texB)
{
width = texB.width;
height = texB.height;
type = texB.type;
}
if(!tex && !this._tex )
this._tex = new GL.Texture( width, height, { type: this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT, format: gl.RGBA, filter: gl.LINEAR });
else
this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision );
var shader = this._shader;
if(!shader)
shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureWarp.pixel_shader );
var factor = this.getInputData(2);
if(factor != null)
this.properties.factor = factor;
else
factor = parseFloat( this.properties.factor );
this._tex.drawTo(function() {
gl.disable( gl.DEPTH_TEST );
gl.disable( gl.CULL_FACE );
gl.disable( gl.BLEND );
if(tex) tex.bind(0);
if(texB) texB.bind(1);
var mesh = Mesh.getScreenQuad();
shader.uniforms({u_texture:0, u_textureB:1, u_factor: factor }).draw( mesh );
});
this.setOutputData(0, this._tex);
}
LGraphTextureWarp.pixel_shader = "precision highp float;\n\
\n\
uniform sampler2D u_texture;\n\
uniform sampler2D u_textureB;\n\
varying vec2 v_coord;\n\
uniform float u_factor;\n\
\n\
void main() {\n\
vec2 uv = v_coord;\n\
uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor;\n\
gl_FragColor = texture2D(u_texture, uv);\n\
}\n\
";
LiteGraph.registerNodeType("texture/warp", LGraphTextureWarp );
//****************************************************
// Texture to Viewport *****************************************
function LGraphTextureToViewport()
{
this.addInput("Texture","Texture");
this.properties = { additive: false, antialiasing: false, filter: true, disable_alpha: false, gamma: 1.0 };
this.size[0] = 130;
}
LGraphTextureToViewport.title = "to Viewport";
LGraphTextureToViewport.desc = "Texture to viewport";
LGraphTextureToViewport.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex)
return;
if(this.properties.disable_alpha)
gl.disable( gl.BLEND );
else
{
gl.enable( gl.BLEND );
if(this.properties.additive)
gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
else
gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
}
gl.disable( gl.DEPTH_TEST );
var gamma = this.properties.gamma || 1.0;
if( this.isInputConnected(1) )
gamma = this.getInputData(1);
tex.setParameter( gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST );
if(this.properties.antialiasing)
{
if(!LGraphTextureToViewport._shader)
LGraphTextureToViewport._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.aa_pixel_shader );
var viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT);
var mesh = Mesh.getScreenQuad();
tex.bind(0);
LGraphTextureToViewport._shader.uniforms({u_texture:0, uViewportSize:[tex.width,tex.height], u_igamma: 1 / gamma, inverseVP: [1/tex.width,1/tex.height] }).draw(mesh);
}
else
{
if(gamma != 1.0)
{
if(!LGraphTextureToViewport._gamma_shader)
LGraphTextureToViewport._gamma_shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureToViewport.gamma_pixel_shader );
tex.toViewport(LGraphTextureToViewport._gamma_shader, { u_texture:0, u_igamma: 1 / gamma });
}
else
tex.toViewport();
}
}
LGraphTextureToViewport.prototype.onGetInputs = function()
{
return [["gamma","number"]];
}
LGraphTextureToViewport.aa_pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform vec2 uViewportSize;\n\
uniform vec2 inverseVP;\n\
uniform float u_igamma;\n\
#define FXAA_REDUCE_MIN (1.0/ 128.0)\n\
#define FXAA_REDUCE_MUL (1.0 / 8.0)\n\
#define FXAA_SPAN_MAX 8.0\n\
\n\
/* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\
vec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\
{\n\
vec4 color = vec4(0.0);\n\
/*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\
vec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\
vec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\
vec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\
vec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\
vec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\
vec3 luma = vec3(0.299, 0.587, 0.114);\n\
float lumaNW = dot(rgbNW, luma);\n\
float lumaNE = dot(rgbNE, luma);\n\
float lumaSW = dot(rgbSW, luma);\n\
float lumaSE = dot(rgbSE, luma);\n\
float lumaM = dot(rgbM, luma);\n\
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\
\n\
vec2 dir;\n\
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\
\n\
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\
\n\
float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\
dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\
\n\
vec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\
texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\
vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\
texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\
\n\
//return vec4(rgbA,1.0);\n\
float lumaB = dot(rgbB, luma);\n\
if ((lumaB < lumaMin) || (lumaB > lumaMax))\n\
color = vec4(rgbA, 1.0);\n\
else\n\
color = vec4(rgbB, 1.0);\n\
if(u_igamma != 1.0)\n\
color.xyz = pow( color.xyz, vec3(u_igamma) );\n\
return color;\n\
}\n\
\n\
void main() {\n\
gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\
}\n\
";
LGraphTextureToViewport.gamma_pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform float u_igamma;\n\
void main() {\n\
vec4 color = texture2D( u_texture, v_coord);\n\
color.xyz = pow(color.xyz, vec3(u_igamma) );\n\
gl_FragColor = color;\n\
}\n\
";
LiteGraph.registerNodeType("texture/toviewport", LGraphTextureToViewport );
// Texture Copy *****************************************
function LGraphTextureCopy()
{
this.addInput("Texture","Texture");
this.addOutput("","Texture");
this.properties = { size: 0, generate_mipmaps: false, precision: LGraphTexture.DEFAULT };
}
LGraphTextureCopy.title = "Copy";
LGraphTextureCopy.desc = "Copy Texture";
LGraphTextureCopy.widgets_info = {
size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]},
precision: { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureCopy.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex && !this._temp_texture)
return;
if(!this.isOutputConnected(0))
return; //saves work
//copy the texture
if(tex)
{
var width = tex.width;
var height = tex.height;
if(this.properties.size != 0)
{
width = this.properties.size;
height = this.properties.size;
}
var temp = this._temp_texture;
var type = tex.type;
if(this.properties.precision === LGraphTexture.LOW)
type = gl.UNSIGNED_BYTE;
else if(this.properties.precision === LGraphTexture.HIGH)
type = gl.HIGH_PRECISION_FORMAT;
if(!temp || temp.width != width || temp.height != height || temp.type != type )
{
var minFilter = gl.LINEAR;
if( this.properties.generate_mipmaps && isPowerOfTwo(width) && isPowerOfTwo(height) )
minFilter = gl.LINEAR_MIPMAP_LINEAR;
this._temp_texture = new GL.Texture( width, height, { type: type, format: gl.RGBA, minFilter: minFilter, magFilter: gl.LINEAR });
}
tex.copyTo(this._temp_texture);
if(this.properties.generate_mipmaps)
{
this._temp_texture.bind(0);
gl.generateMipmap(this._temp_texture.texture_type);
this._temp_texture.unbind(0);
}
}
this.setOutputData(0,this._temp_texture);
}
LiteGraph.registerNodeType("texture/copy", LGraphTextureCopy );
// Texture Downsample *****************************************
function LGraphTextureDownsample()
{
this.addInput("Texture","Texture");
this.addOutput("","Texture");
this.properties = { iterations: 1, generate_mipmaps: false, precision: LGraphTexture.DEFAULT };
}
LGraphTextureDownsample.title = "Downsample";
LGraphTextureDownsample.desc = "Downsample Texture";
LGraphTextureDownsample.widgets_info = {
iterations: { type:"number", step: 1, precision: 0, min: 0 },
precision: { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureDownsample.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex && !this._temp_texture)
return;
if(!this.isOutputConnected(0))
return; //saves work
//we do not allow any texture different than texture 2D
if(!tex || tex.texture_type !== GL.TEXTURE_2D )
return;
if( this.properties.iterations < 1)
{
this.setOutputData(0,tex);
return;
}
var shader = LGraphTextureDownsample._shader;
if(!shader)
LGraphTextureDownsample._shader = shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureDownsample.pixel_shader );
var width = tex.width|0;
var height = tex.height|0;
var type = tex.type;
if(this.properties.precision === LGraphTexture.LOW)
type = gl.UNSIGNED_BYTE;
else if(this.properties.precision === LGraphTexture.HIGH)
type = gl.HIGH_PRECISION_FORMAT;
var iterations = this.properties.iterations || 1;
var origin = tex;
var target = null;
var temp = [];
var options = {
type: type,
format: tex.format
};
var offset = vec2.create();
var uniforms = {
u_offset: offset
};
if( this._texture )
GL.Texture.releaseTemporary( this._texture );
for(var i = 0; i < iterations; ++i)
{
offset[0] = 1/width;
offset[1] = 1/height;
width = width>>1 || 0;
height = height>>1 || 0;
target = GL.Texture.getTemporary( width, height, options );
temp.push( target );
origin.setParameter( GL.TEXTURE_MAG_FILTER, GL.NEAREST );
origin.copyTo( target, shader, uniforms );
if(width == 1 && height == 1)
break; //nothing else to do
origin = target;
}
//keep the last texture used
this._texture = temp.pop();
//free the rest
for(var i = 0; i < temp.length; ++i)
GL.Texture.releaseTemporary( temp[i] );
if(this.properties.generate_mipmaps)
{
this._texture.bind(0);
gl.generateMipmap(this._texture.texture_type);
this._texture.unbind(0);
}
this.setOutputData(0,this._texture);
}
LGraphTextureDownsample.pixel_shader = "precision highp float;\n\
precision highp float;\n\
uniform sampler2D u_texture;\n\
uniform vec2 u_offset;\n\
varying vec2 v_coord;\n\
\n\
void main() {\n\
vec4 color = texture2D(u_texture, v_coord );\n\
color += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\n\
color += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\n\
color += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\n\
gl_FragColor = color * 0.25;\n\
}\n\
";
LiteGraph.registerNodeType("texture/downsample", LGraphTextureDownsample );
// Texture Average *****************************************
function LGraphTextureAverage()
{
this.addInput("Texture","Texture");
this.addOutput("tex","Texture");
this.addOutput("avg","vec4");
this.addOutput("lum","number");
this.properties = { use_previous_frame: true, mipmap_offset: 0, low_precision: false };
this._uniforms = { u_texture: 0, u_mipmap_offset: this.properties.mipmap_offset };
this._luminance = new Float32Array(4);
}
LGraphTextureAverage.title = "Average";
LGraphTextureAverage.desc = "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture";
LGraphTextureAverage.prototype.onExecute = function()
{
if( !this.properties.use_previous_frame )
this.updateAverage();
var v = this._luminance;
this.setOutputData(0, this._temp_texture );
this.setOutputData(1, v );
this.setOutputData(2,(v[0] + v[1] + v[2]) / 3);
}
//executed before rendering the frame
LGraphTextureAverage.prototype.onPreRenderExecute = function()
{
this.updateAverage();
}
LGraphTextureAverage.prototype.updateAverage = function()
{
var tex = this.getInputData(0);
if(!tex)
return;
if(!this.isOutputConnected(0) && !this.isOutputConnected(1) && !this.isOutputConnected(2))
return; //saves work
if(!LGraphTextureAverage._shader)
{
LGraphTextureAverage._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureAverage.pixel_shader);
//creates 32 random numbers and stores the, in two mat4
var samples = new Float32Array(32);
for(var i = 0; i < 32; ++i)
samples[i] = Math.random();
LGraphTextureAverage._shader.uniforms({u_samples_a: samples.subarray(0,16), u_samples_b: samples.subarray(16,32) });
}
var temp = this._temp_texture;
var type = gl.UNSIGNED_BYTE;
if(tex.type != type) //force floats, half floats cannot be read with gl.readPixels
type = gl.FLOAT;
if(!temp || temp.type != type )
this._temp_texture = new GL.Texture( 1, 1, { type: type, format: gl.RGBA, filter: gl.NEAREST });
var shader = LGraphTextureAverage._shader;
var uniforms = this._uniforms;
uniforms.u_mipmap_offset = this.properties.mipmap_offset;
gl.disable( gl.DEPTH_TEST );
gl.disable( gl.BLEND );
this._temp_texture.drawTo(function(){
tex.toViewport( shader, uniforms );
});
if(this.isOutputConnected(1) || this.isOutputConnected(2))
{
var pixel = this._temp_texture.getPixels();
if(pixel)
{
var v = this._luminance;
var type = this._temp_texture.type;
v.set( pixel );
if(type == gl.UNSIGNED_BYTE)
vec4.scale( v,v, 1/255 );
else if(type == GL.HALF_FLOAT || type == GL.HALF_FLOAT_OES)
{
//no half floats possible, hard to read back unless copyed to a FLOAT texture, so temp_texture is always forced to FLOAT
}
}
}
}
LGraphTextureAverage.pixel_shader = "precision highp float;\n\
precision highp float;\n\
uniform mat4 u_samples_a;\n\
uniform mat4 u_samples_b;\n\
uniform sampler2D u_texture;\n\
uniform float u_mipmap_offset;\n\
varying vec2 v_coord;\n\
\n\
void main() {\n\
vec4 color = vec4(0.0);\n\
for(int i = 0; i < 4; ++i)\n\
for(int j = 0; j < 4; ++j)\n\
{\n\
color += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\
color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\
}\n\
gl_FragColor = color * 0.03125;\n\
}\n\
";
LiteGraph.registerNodeType("texture/average", LGraphTextureAverage );
function LGraphTextureTemporalSmooth()
{
this.addInput("in","Texture");
this.addInput("factor","Number");
this.addOutput("out","Texture");
this.properties = { factor: 0.5 };
this._uniforms = { u_texture: 0, u_textureB: 1, u_factor: this.properties.factor };
}
LGraphTextureTemporalSmooth.title = "Smooth";
LGraphTextureTemporalSmooth.desc = "Smooth texture over time";
LGraphTextureTemporalSmooth.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex || !this.isOutputConnected(0))
return;
if(!LGraphTextureTemporalSmooth._shader)
LGraphTextureTemporalSmooth._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureTemporalSmooth.pixel_shader);
var temp = this._temp_texture;
if(!temp || temp.type != tex.type || temp.width != tex.width || temp.height != tex.height )
{
this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.NEAREST });
this._temp_texture2 = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.NEAREST });
tex.copyTo( this._temp_texture2 );
}
var tempA = this._temp_texture;
var tempB = this._temp_texture2;
var shader = LGraphTextureTemporalSmooth._shader;
var uniforms = this._uniforms;
uniforms.u_factor = 1.0 - this.getInputOrProperty("factor");
gl.disable( gl.BLEND );
gl.disable( gl.DEPTH_TEST );
tempA.drawTo(function(){
tempB.bind(1);
tex.toViewport( shader, uniforms );
});
this.setOutputData(0, tempA );
//swap
this._temp_texture = tempB;
this._temp_texture2 = tempA;
}
LGraphTextureTemporalSmooth.pixel_shader = "precision highp float;\n\
precision highp float;\n\
uniform sampler2D u_texture;\n\
uniform sampler2D u_textureB;\n\
uniform float u_factor;\n\
varying vec2 v_coord;\n\
\n\
void main() {\n\
gl_FragColor = mix( texture2D( u_texture, v_coord ), texture2D( u_textureB, v_coord ), u_factor );\n\
}\n\
";
LiteGraph.registerNodeType("texture/temporal_smooth", LGraphTextureTemporalSmooth );
// Image To Texture *****************************************
function LGraphImageToTexture()
{
this.addInput("Image","image");
this.addOutput("","Texture");
this.properties = {};
}
LGraphImageToTexture.title = "Image to Texture";
LGraphImageToTexture.desc = "Uploads an image to the GPU";
//LGraphImageToTexture.widgets_info = { size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]} };
LGraphImageToTexture.prototype.onExecute = function()
{
var img = this.getInputData(0);
if(!img)
return;
var width = img.videoWidth || img.width;
var height = img.videoHeight || img.height;
//this is in case we are using a webgl canvas already, no need to reupload it
if(img.gltexture)
{
this.setOutputData(0,img.gltexture);
return;
}
var temp = this._temp_texture;
if(!temp || temp.width != width || temp.height != height )
this._temp_texture = new GL.Texture( width, height, { format: gl.RGBA, filter: gl.LINEAR });
try
{
this._temp_texture.uploadImage(img);
}
catch(err)
{
console.error("image comes from an unsafe location, cannot be uploaded to webgl: " + err);
return;
}
this.setOutputData(0,this._temp_texture);
}
LiteGraph.registerNodeType("texture/imageToTexture", LGraphImageToTexture );
// Texture LUT *****************************************
function LGraphTextureLUT()
{
this.addInput("Texture","Texture");
this.addInput("LUT","Texture");
this.addInput("Intensity","number");
this.addOutput("","Texture");
this.properties = { intensity: 1, precision: LGraphTexture.DEFAULT, texture: null };
if(!LGraphTextureLUT._shader)
LGraphTextureLUT._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureLUT.pixel_shader );
}
LGraphTextureLUT.widgets_info = {
"texture": { widget:"texture"},
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureLUT.title = "LUT";
LGraphTextureLUT.desc = "Apply LUT to Texture";
LGraphTextureLUT.prototype.onExecute = function()
{
if(!this.isOutputConnected(0))
return; //saves work
var tex = this.getInputData(0);
if(this.properties.precision === LGraphTexture.PASS_THROUGH )
{
this.setOutputData(0,tex);
return;
}
if(!tex)
return;
var lut_tex = this.getInputData(1);
if(!lut_tex)
lut_tex = LGraphTexture.getTexture( this.properties.texture );
if(!lut_tex)
{
this.setOutputData(0,tex);
return;
}
lut_tex.bind(0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.bindTexture(gl.TEXTURE_2D, null);
var intensity = this.properties.intensity;
if( this.isInputConnected(2) )
this.properties.intensity = intensity = this.getInputData(2);
this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
//var mesh = Mesh.getScreenQuad();
this._tex.drawTo(function() {
lut_tex.bind(1);
tex.toViewport( LGraphTextureLUT._shader, {u_texture:0, u_textureB:1, u_amount: intensity} );
});
this.setOutputData(0,this._tex);
}
LGraphTextureLUT.pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform sampler2D u_textureB;\n\
uniform float u_amount;\n\
\n\
void main() {\n\
lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(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 vec4 newColor1 = texture2D(u_textureB, texPos1);\n\
lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\
lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\
gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\
}\n\
";
LiteGraph.registerNodeType("texture/LUT", LGraphTextureLUT );
// Texture Channels *****************************************
function LGraphTextureChannels()
{
this.addInput("Texture","Texture");
this.addOutput("R","Texture");
this.addOutput("G","Texture");
this.addOutput("B","Texture");
this.addOutput("A","Texture");
this.properties = { use_luminance: true };
if(!LGraphTextureChannels._shader)
LGraphTextureChannels._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureChannels.pixel_shader );
}
LGraphTextureChannels.title = "Texture to Channels";
LGraphTextureChannels.desc = "Split texture channels";
LGraphTextureChannels.prototype.onExecute = function()
{
var texA = this.getInputData(0);
if(!texA) return;
if(!this._channels)
this._channels = Array(4);
var format = this.properties.use_luminance ? gl.LUMINANCE : gl.RGBA;
var connections = 0;
for(var i = 0; i < 4; i++)
{
if(this.isOutputConnected(i))
{
if(!this._channels[i] || this._channels[i].width != texA.width || this._channels[i].height != texA.height || this._channels[i].type != texA.type || this._channels[i].format != format )
this._channels[i] = new GL.Texture( texA.width, texA.height, { type: texA.type, format: format, filter: gl.LINEAR });
connections++;
}
else
this._channels[i] = null;
}
if(!connections)
return;
gl.disable( gl.BLEND );
gl.disable( gl.DEPTH_TEST );
var mesh = Mesh.getScreenQuad();
var shader = LGraphTextureChannels._shader;
var masks = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]];
for(var i = 0; i < 4; i++)
{
if(!this._channels[i])
continue;
this._channels[i].drawTo( function() {
texA.bind(0);
shader.uniforms({u_texture:0, u_mask: masks[i]}).draw(mesh);
});
this.setOutputData(i, this._channels[i]);
}
}
LGraphTextureChannels.pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform vec4 u_mask;\n\
\n\
void main() {\n\
gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\
}\n\
";
LiteGraph.registerNodeType("texture/textureChannels", LGraphTextureChannels );
// Texture Channels to Texture *****************************************
function LGraphChannelsTexture()
{
this.addInput("R","Texture");
this.addInput("G","Texture");
this.addInput("B","Texture");
this.addInput("A","Texture");
this.addOutput("Texture","Texture");
this.properties = { precision: LGraphTexture.DEFAULT, R:1,G:1,B:1,A:1 };
this._color = vec4.create();
this._uniforms = { u_textureR:0, u_textureG:1, u_textureB:2, u_textureA:3, u_color: this._color };
}
LGraphChannelsTexture.title = "Channels to Texture";
LGraphChannelsTexture.desc = "Split texture channels";
LGraphChannelsTexture.widgets_info = {
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphChannelsTexture.prototype.onExecute = function()
{
var white = LGraphTexture.getWhiteTexture();
var texR = this.getInputData(0) || white;
var texG = this.getInputData(1) || white;
var texB = this.getInputData(2) || white;
var texA = this.getInputData(3) || white;
gl.disable( gl.BLEND );
gl.disable( gl.DEPTH_TEST );
var mesh = Mesh.getScreenQuad();
if(!LGraphChannelsTexture._shader)
LGraphChannelsTexture._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphChannelsTexture.pixel_shader );
var shader = LGraphChannelsTexture._shader;
var w = Math.max( texR.width, texG.width, texB.width, texA.width );
var h = Math.max( texR.height, texG.height, texB.height, texA.height );
var type = this.properties.precision == LGraphTexture.HIGH ? LGraphTexture.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE;
if( !this._texture || this._texture.width != w || this._texture.height != h || this._texture.type != type )
this._texture = new GL.Texture(w,h,{ type: type, format: gl.RGBA, filter: gl.LINEAR });
var color = this._color;
color[0] = this.properties.R;
color[1] = this.properties.G;
color[2] = this.properties.B;
color[3] = this.properties.A;
var uniforms = this._uniforms;
this._texture.drawTo( function() {
texR.bind(0);
texG.bind(1);
texB.bind(2);
texA.bind(3);
shader.uniforms( uniforms ).draw(mesh);
});
this.setOutputData(0, this._texture );
}
LGraphChannelsTexture.pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_textureR;\n\
uniform sampler2D u_textureG;\n\
uniform sampler2D u_textureB;\n\
uniform sampler2D u_textureA;\n\
uniform vec4 u_color;\n\
\n\
void main() {\n\
gl_FragColor = u_color * vec4( \
texture2D(u_textureR, v_coord).r,\
texture2D(u_textureG, v_coord).r,\
texture2D(u_textureB, v_coord).r,\
texture2D(u_textureA, v_coord).r);\n\
}\n\
";
LiteGraph.registerNodeType("texture/channelsTexture", LGraphChannelsTexture );
// Texture Color *****************************************
function LGraphTextureColor()
{
this.addOutput("Texture","Texture");
this._tex_color = vec4.create();
this.properties = { color: vec4.create(), precision: LGraphTexture.DEFAULT };
}
LGraphTextureColor.title = "Color";
LGraphTextureColor.desc = "Generates a 1x1 texture with a constant color";
LGraphTextureColor.widgets_info = {
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureColor.prototype.onDrawBackground = function( ctx )
{
var c = this.properties.color;
ctx.fillStyle = "rgb(" + Math.floor(Math.clamp(c[0],0,1)*255) + "," + Math.floor(Math.clamp(c[1],0,1)*255) + "," + Math.floor(Math.clamp(c[2],0,1)*255) + ")";
if(this.flags.collapsed)
this.boxcolor = ctx.fillStyle;
else
ctx.fillRect(0,0,this.size[0],this.size[1]);
}
LGraphTextureColor.prototype.onExecute = function()
{
var type = this.properties.precision == LGraphTexture.HIGH ? LGraphTexture.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE;
if(!this._tex || this._tex.type != type )
this._tex = new GL.Texture(1,1,{ format: gl.RGBA, type: type, minFilter: gl.NEAREST });
var color = this.properties.color;
if(this.inputs)
for(var i = 0; i < this.inputs.length; i++)
{
var input = this.inputs[i];
var v = this.getInputData(i);
if(v === undefined)
continue;
switch(input.name)
{
case 'RGB':
case 'RGBA':
color.set(v);
break;
case 'R': color[0] = v; break;
case 'G': color[1] = v; break;
case 'B': color[2] = v; break;
case 'A': color[3] = v; break;
}
}
if( vec4.sqrDist( this._tex_color, color) > 0.001 )
{
this._tex_color.set( color );
this._tex.fill( color );
}
this.setOutputData(0, this._tex);
}
LGraphTextureColor.prototype.onGetInputs = function()
{
return [["RGB","vec3"],["RGBA","vec4"],["R","number"],["G","number"],["B","number"],["A","number"]];
}
LiteGraph.registerNodeType("texture/color", LGraphTextureColor );
// Texture Channels to Texture *****************************************
function LGraphTextureGradient()
{
this.addInput("A","color");
this.addInput("B","color");
this.addOutput("Texture","Texture");
this.properties = { angle: 0, scale: 1, A:[0,0,0], B:[1,1,1], texture_size:32 };
if(!LGraphTextureGradient._shader)
LGraphTextureGradient._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureGradient.pixel_shader );
this._uniforms = { u_angle: 0, u_colorA: vec3.create(), u_colorB: vec3.create()};
}
LGraphTextureGradient.title = "Gradient";
LGraphTextureGradient.desc = "Generates a gradient";
LGraphTextureGradient["@A"] = { type:"color" };
LGraphTextureGradient["@B"] = { type:"color" };
LGraphTextureGradient["@texture_size"] = { type:"enum", values:[32,64,128,256,512] };
LGraphTextureGradient.prototype.onExecute = function()
{
gl.disable( gl.BLEND );
gl.disable( gl.DEPTH_TEST );
var mesh = GL.Mesh.getScreenQuad();
var shader = LGraphTextureGradient._shader;
var A = this.getInputData(0);
if(!A)
A = this.properties.A;
var B = this.getInputData(1);
if(!B)
B = this.properties.B;
//angle and scale
for(var i = 2; i < this.inputs.length; i++)
{
var input = this.inputs[i];
var v = this.getInputData(i);
if(v === undefined)
continue;
this.properties[ input.name ] = v;
}
var uniforms = this._uniforms;
this._uniforms.u_angle = this.properties.angle * DEG2RAD;
this._uniforms.u_scale = this.properties.scale;
vec3.copy( uniforms.u_colorA, A );
vec3.copy( uniforms.u_colorB, B );
var size = parseInt( this.properties.texture_size );
if(!this._tex || this._tex.width != size )
this._tex = new GL.Texture( size, size, { format: gl.RGB, filter: gl.LINEAR });
this._tex.drawTo( function() {
shader.uniforms(uniforms).draw(mesh);
});
this.setOutputData(0, this._tex);
}
LGraphTextureGradient.prototype.onGetInputs = function()
{
return [["angle","number"],["scale","number"]];
}
LGraphTextureGradient.pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform float u_angle;\n\
uniform float u_scale;\n\
uniform vec3 u_colorA;\n\
uniform vec3 u_colorB;\n\
\n\
vec2 rotate(vec2 v, float angle)\n\
{\n\
vec2 result;\n\
float _cos = cos(angle);\n\
float _sin = sin(angle);\n\
result.x = v.x * _cos - v.y * _sin;\n\
result.y = v.x * _sin + v.y * _cos;\n\
return result;\n\
}\n\
void main() {\n\
float f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\
vec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\
gl_FragColor = vec4(color,1.0);\n\
}\n\
";
LiteGraph.registerNodeType("texture/gradient", LGraphTextureGradient );
// Texture Mix *****************************************
function LGraphTextureMix()
{
this.addInput("A","Texture");
this.addInput("B","Texture");
this.addInput("Mixer","Texture");
this.addOutput("Texture","Texture");
this.properties = { factor: 0.5, precision: LGraphTexture.DEFAULT };
this._uniforms = { u_textureA:0, u_textureB:1, u_textureMix:2, u_mix: vec4.create() };
}
LGraphTextureMix.title = "Mix";
LGraphTextureMix.desc = "Generates a texture mixing two textures";
LGraphTextureMix.widgets_info = {
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureMix.prototype.onExecute = function()
{
var texA = this.getInputData(0);
if(!this.isOutputConnected(0))
return; //saves work
if(this.properties.precision === LGraphTexture.PASS_THROUGH )
{
this.setOutputData(0,texA);
return;
}
var texB = this.getInputData(1);
if(!texA || !texB )
return;
var texMix = this.getInputData(2);
var factor = this.getInputData(3);
this._tex = LGraphTexture.getTargetTexture( texA, this._tex, this.properties.precision );
gl.disable( gl.BLEND );
gl.disable( gl.DEPTH_TEST );
var mesh = Mesh.getScreenQuad();
var shader = null;
var uniforms = this._uniforms;
if(texMix)
{
shader = LGraphTextureMix._shader_tex;
if(!shader)
shader = LGraphTextureMix._shader_tex = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureMix.pixel_shader, {"MIX_TEX":""});
}
else
{
shader = LGraphTextureMix._shader_factor;
if(!shader)
shader = LGraphTextureMix._shader_factor = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureMix.pixel_shader );
var f = factor == null ? this.properties.factor : factor;
uniforms.u_mix.set([f,f,f,f]);
}
this._tex.drawTo( function() {
texA.bind(0);
texB.bind(1);
if(texMix)
texMix.bind(2);
shader.uniforms( uniforms ).draw(mesh);
});
this.setOutputData(0, this._tex);
}
LGraphTextureMix.prototype.onGetInputs = function()
{
return [["factor","number"]];
}
LGraphTextureMix.pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_textureA;\n\
uniform sampler2D u_textureB;\n\
#ifdef MIX_TEX\n\
uniform sampler2D u_textureMix;\n\
#else\n\
uniform vec4 u_mix;\n\
#endif\n\
\n\
void main() {\n\
#ifdef MIX_TEX\n\
vec4 f = texture2D(u_textureMix, v_coord);\n\
#else\n\
vec4 f = u_mix;\n\
#endif\n\
gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), f );\n\
}\n\
";
LiteGraph.registerNodeType("texture/mix", LGraphTextureMix );
// Texture Edges detection *****************************************
function LGraphTextureEdges()
{
this.addInput("Tex.","Texture");
this.addOutput("Edges","Texture");
this.properties = { invert: true, threshold: false, factor: 1, precision: LGraphTexture.DEFAULT };
if(!LGraphTextureEdges._shader)
LGraphTextureEdges._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEdges.pixel_shader );
}
LGraphTextureEdges.title = "Edges";
LGraphTextureEdges.desc = "Detects edges";
LGraphTextureEdges.widgets_info = {
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureEdges.prototype.onExecute = function()
{
if(!this.isOutputConnected(0))
return; //saves work
var tex = this.getInputData(0);
if(this.properties.precision === LGraphTexture.PASS_THROUGH )
{
this.setOutputData(0,tex);
return;
}
if(!tex) return;
this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
gl.disable( gl.BLEND );
gl.disable( gl.DEPTH_TEST );
var mesh = Mesh.getScreenQuad();
var shader = LGraphTextureEdges._shader;
var invert = this.properties.invert;
var factor = this.properties.factor;
var threshold = this.properties.threshold ? 1 : 0;
this._tex.drawTo( function() {
tex.bind(0);
shader.uniforms({u_texture:0, u_isize:[1/tex.width,1/tex.height], u_factor: factor, u_threshold: threshold, u_invert: invert ? 1 : 0}).draw(mesh);
});
this.setOutputData(0, this._tex);
}
LGraphTextureEdges.pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform vec2 u_isize;\n\
uniform int u_invert;\n\
uniform float u_factor;\n\
uniform float u_threshold;\n\
\n\
void main() {\n\
vec4 center = texture2D(u_texture, v_coord);\n\
vec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\
vec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\
vec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\
vec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\
vec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\
diff *= u_factor;\n\
if(u_invert == 1)\n\
diff.xyz = vec3(1.0) - diff.xyz;\n\
if( u_threshold == 0.0 )\n\
gl_FragColor = vec4( diff.xyz, center.a );\n\
else\n\
gl_FragColor = vec4( diff.x > 0.5 ? 1.0 : 0.0, diff.y > 0.5 ? 1.0 : 0.0, diff.z > 0.5 ? 1.0 : 0.0, center.a );\n\
}\n\
";
LiteGraph.registerNodeType("texture/edges", LGraphTextureEdges );
// Texture Depth *****************************************
function LGraphTextureDepthRange()
{
this.addInput("Texture","Texture");
this.addInput("Distance","number");
this.addInput("Range","number");
this.addOutput("Texture","Texture");
this.properties = { distance:100, range: 50, only_depth: false, high_precision: false };
this._uniforms = {u_texture:0, u_distance: 100, u_range: 50, u_camera_planes: null };
}
LGraphTextureDepthRange.title = "Depth Range";
LGraphTextureDepthRange.desc = "Generates a texture with a depth range";
LGraphTextureDepthRange.prototype.onExecute = function()
{
if(!this.isOutputConnected(0))
return; //saves work
var tex = this.getInputData(0);
if(!tex) return;
var precision = gl.UNSIGNED_BYTE;
if(this.properties.high_precision)
precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT;
if(!this._temp_texture || this._temp_texture.type != precision ||
this._temp_texture.width != tex.width || this._temp_texture.height != tex.height)
this._temp_texture = new GL.Texture( tex.width, tex.height, { type: precision, format: gl.RGBA, filter: gl.LINEAR });
var uniforms = this._uniforms;
//iterations
var distance = this.properties.distance;
if( this.isInputConnected(1) )
{
distance = this.getInputData(1);
this.properties.distance = distance;
}
var range = this.properties.range;
if( this.isInputConnected(2) )
{
range = this.getInputData(2);
this.properties.range = range;
}
uniforms.u_distance = distance;
uniforms.u_range = range;
gl.disable( gl.BLEND );
gl.disable( gl.DEPTH_TEST );
var mesh = Mesh.getScreenQuad();
if(!LGraphTextureDepthRange._shader)
{
LGraphTextureDepthRange._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader );
LGraphTextureDepthRange._shader_onlydepth = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureDepthRange.pixel_shader, { ONLY_DEPTH:""} );
}
var shader = this.properties.only_depth ? LGraphTextureDepthRange._shader_onlydepth : LGraphTextureDepthRange._shader;
//NEAR AND FAR PLANES
var planes = null;
if( tex.near_far_planes )
planes = tex.near_far_planes;
else if( window.LS && LS.Renderer._main_camera )
planes = LS.Renderer._main_camera._uniforms.u_camera_planes;
else
planes = [0.1,1000]; //hardcoded
uniforms.u_camera_planes = planes;
this._temp_texture.drawTo( function() {
tex.bind(0);
shader.uniforms( uniforms ).draw(mesh);
});
this._temp_texture.near_far_planes = planes;
this.setOutputData(0, this._temp_texture );
}
LGraphTextureDepthRange.pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform vec2 u_camera_planes;\n\
uniform float u_distance;\n\
uniform float u_range;\n\
\n\
float LinearDepth()\n\
{\n\
float zNear = u_camera_planes.x;\n\
float zFar = u_camera_planes.y;\n\
float depth = texture2D(u_texture, v_coord).x;\n\
depth = depth * 2.0 - 1.0;\n\
return zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\
}\n\
\n\
void main() {\n\
float depth = LinearDepth();\n\
#ifdef ONLY_DEPTH\n\
gl_FragColor = vec4(depth);\n\
#else\n\
float diff = abs(depth * u_camera_planes.y - u_distance);\n\
float dof = 1.0;\n\
if(diff <= u_range)\n\
dof = diff / u_range;\n\
gl_FragColor = vec4(dof);\n\
#endif\n\
}\n\
";
LiteGraph.registerNodeType("texture/depth_range", LGraphTextureDepthRange );
// Texture Blur *****************************************
function LGraphTextureBlur()
{
this.addInput("Texture","Texture");
this.addInput("Iterations","number");
this.addInput("Intensity","number");
this.addOutput("Blurred","Texture");
this.properties = { intensity: 1, iterations: 1, preserve_aspect: false, scale:[1,1], precision: LGraphTexture.DEFAULT };
}
LGraphTextureBlur.title = "Blur";
LGraphTextureBlur.desc = "Blur a texture";
LGraphTextureBlur.widgets_info = {
precision: { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureBlur.max_iterations = 20;
LGraphTextureBlur.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex)
return;
if(!this.isOutputConnected(0))
return; //saves work
var temp = this._final_texture;
if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
{
//we need two textures to do the blurring
//this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
temp = this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
}
//iterations
var iterations = this.properties.iterations;
if( this.isInputConnected(1) )
{
iterations = this.getInputData(1);
this.properties.iterations = iterations;
}
iterations = Math.min( Math.floor(iterations), LGraphTextureBlur.max_iterations );
if(iterations == 0) //skip blurring
{
this.setOutputData(0, tex);
return;
}
var intensity = this.properties.intensity;
if( this.isInputConnected(2) )
{
intensity = this.getInputData(2);
this.properties.intensity = intensity;
}
//blur sometimes needs an aspect correction
var aspect = LiteGraph.camera_aspect;
if(!aspect && window.gl !== undefined)
aspect = gl.canvas.height / gl.canvas.width;
if(!aspect)
aspect = 1;
aspect = this.properties.preserve_aspect ? aspect : 1;
var scale = this.properties.scale || [1,1];
tex.applyBlur( aspect * scale[0], scale[1], intensity, temp );
for(var i = 1; i < iterations; ++i)
temp.applyBlur( aspect * scale[0] * (i+1), scale[1] * (i+1), intensity );
this.setOutputData(0, temp );
}
/*
LGraphTextureBlur.pixel_shader = "precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform vec2 u_offset;\n\
uniform float u_intensity;\n\
void main() {\n\
vec4 sum = vec4(0.0);\n\
vec4 center = texture2D(u_texture, v_coord);\n\
sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\n\
sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\n\
sum += texture2D(u_texture, v_coord + u_offset * -2.0) * 0.12/0.98;\n\
sum += texture2D(u_texture, v_coord + u_offset * -1.0) * 0.15/0.98;\n\
sum += center * 0.16/0.98;\n\
sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\n\
sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\n\
sum += texture2D(u_texture, v_coord + u_offset * 2.0) * 0.12/0.98;\n\
sum += texture2D(u_texture, v_coord + u_offset * 1.0) * 0.15/0.98;\n\
gl_FragColor = u_intensity * sum;\n\
}\n\
";
*/
LiteGraph.registerNodeType("texture/blur", LGraphTextureBlur );
// Texture Glow *****************************************
//based in https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/
function LGraphTextureGlow()
{
this.addInput("in","Texture");
this.addInput("dirt","Texture");
this.addOutput("out","Texture");
this.addOutput("glow","Texture");
this.properties = { enabled: true, intensity: 1, persistence: 0.99, iterations:16, threshold:0, scale: 1, dirt_factor: 0.5, precision: LGraphTexture.DEFAULT };
this._textures = [];
this._uniforms = { u_intensity: 1, u_texture: 0, u_glow_texture: 1, u_threshold: 0, u_texel_size: vec2.create() };
}
LGraphTextureGlow.title = "Glow";
LGraphTextureGlow.desc = "Filters a texture giving it a glow effect";
LGraphTextureGlow.weights = new Float32Array( [0.5,0.4,0.3,0.2] );
LGraphTextureGlow.widgets_info = {
"iterations": { type:"number", min: 0, max: 16, step: 1, precision: 0 },
"threshold": { type:"number", min: 0, max: 10, step: 0.01, precision: 2 },
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureGlow.prototype.onGetInputs = function(){
return [["enabled","boolean"],["threshold","number"],["intensity","number"],["persistence","number"],["iterations","number"],["dirt_factor","number"]];
}
LGraphTextureGlow.prototype.onGetOutputs = function(){
return [["average","Texture"]];
}
LGraphTextureGlow.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex)
return;
if(!this.isAnyOutputConnected())
return; //saves work
if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.getInputOrProperty("enabled" ) === false )
{
this.setOutputData(0,tex);
return;
}
var width = tex.width;
var height = tex.height;
var texture_info = { format: tex.format, type: tex.type, minFilter: GL.LINEAR, magFilter: GL.LINEAR, wrap: gl.CLAMP_TO_EDGE };
var type = LGraphTexture.getTextureType( this.properties.precision, tex );
var uniforms = this._uniforms;
var textures = this._textures;
//cut
var shader = LGraphTextureGlow._cut_shader;
if(!shader)
shader = LGraphTextureGlow._cut_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.cut_pixel_shader );
gl.disable( gl.DEPTH_TEST );
gl.disable( gl.BLEND );
uniforms.u_threshold = this.getInputOrProperty("threshold");
var currentDestination = textures[0] = GL.Texture.getTemporary( width, height, texture_info );
tex.blit( currentDestination, shader.uniforms(uniforms) );
var currentSource = currentDestination;
var iterations = this.getInputOrProperty("iterations");
iterations = Math.clamp( iterations, 1, 16) | 0;
var texel_size = uniforms.u_texel_size;
var intensity = this.getInputOrProperty("intensity");
uniforms.u_intensity = 1;
uniforms.u_delta = this.properties.scale; //1
//downscale/upscale shader
var shader = LGraphTextureGlow._shader;
if(!shader)
shader = LGraphTextureGlow._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.scale_pixel_shader );
var i = 1;
//downscale
for (;i < iterations; i++) {
width = width>>1;
if( (height|0) > 1 )
height = height>>1;
if( width < 2 )
break;
currentDestination = textures[i] = GL.Texture.getTemporary( width, height, texture_info );
texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height;
currentSource.blit( currentDestination, shader.uniforms(uniforms) );
currentSource = currentDestination;
}
//average
if(this.isOutputConnected(2))
{
var average_texture = this._average_texture;
if(!average_texture || average_texture.type != tex.type || average_texture.format != tex.format )
average_texture = this._average_texture = new GL.Texture( 1, 1, { type: tex.type, format: tex.format, filter: gl.LINEAR });
texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height;
uniforms.u_intensity = intensity;
uniforms.u_delta = 1;
currentSource.blit( average_texture, shader.uniforms(uniforms) );
this.setOutputData( 2, average_texture );
}
//upscale and blend
gl.enable( gl.BLEND );
gl.blendFunc( gl.ONE, gl.ONE );
uniforms.u_intensity = this.getInputOrProperty("persistence");
uniforms.u_delta = 0.5;
for (i -= 2; i >= 0; i--) // i-=2 => -1 to point to last element in array, -1 to go to texture above
{
currentDestination = textures[i];
textures[i] = null;
texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height;
currentSource.blit( currentDestination, shader.uniforms(uniforms) );
GL.Texture.releaseTemporary( currentSource );
currentSource = currentDestination;
}
gl.disable( gl.BLEND );
//glow
if(this.isOutputConnected(1))
{
var glow_texture = this._glow_texture;
if(!glow_texture || glow_texture.width != tex.width || glow_texture.height != tex.height || glow_texture.type != type || glow_texture.format != tex.format )
glow_texture = this._glow_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR });
currentSource.blit( glow_texture );
this.setOutputData( 1, glow_texture);
}
//final composition
if(this.isOutputConnected(0))
{
var final_texture = this._final_texture;
if(!final_texture || final_texture.width != tex.width || final_texture.height != tex.height || final_texture.type != type || final_texture.format != tex.format )
final_texture = this._final_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR });
var dirt_texture = this.getInputData(1);
var dirt_factor = this.getInputOrProperty("dirt_factor");
uniforms.u_intensity = intensity;
shader = dirt_texture ? LGraphTextureGlow._dirt_final_shader : LGraphTextureGlow._final_shader;
if(!shader)
{
if(dirt_texture)
shader = LGraphTextureGlow._dirt_final_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.final_pixel_shader, { USE_DIRT: "" } );
else
shader = LGraphTextureGlow._final_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.final_pixel_shader );
}
final_texture.drawTo( function(){
tex.bind(0);
currentSource.bind(1);
if(dirt_texture)
{
shader.setUniform( "u_dirt_factor", dirt_factor );
shader.setUniform( "u_dirt_texture", dirt_texture.bind(2) );
}
shader.toViewport( uniforms );
});
this.setOutputData( 0, final_texture );
}
GL.Texture.releaseTemporary( currentSource );
}
LGraphTextureGlow.cut_pixel_shader = "precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform float u_threshold;\n\
void main() {\n\
gl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\
}"
LGraphTextureGlow.scale_pixel_shader = "precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform vec2 u_texel_size;\n\
uniform float u_delta;\n\
uniform float u_intensity;\n\
\n\
vec4 sampleBox(vec2 uv) {\n\
vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\
vec4 s = texture2D( u_texture, uv + o.xy ) + texture2D( u_texture, uv + o.zy) + texture2D( u_texture, uv + o.xw) + texture2D( u_texture, uv + o.zw);\n\
return s * 0.25;\n\
}\n\
void main() {\n\
gl_FragColor = u_intensity * sampleBox( v_coord );\n\
}"
LGraphTextureGlow.final_pixel_shader = "precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform sampler2D u_glow_texture;\n\
#ifdef USE_DIRT\n\
uniform sampler2D u_dirt_texture;\n\
#endif\n\
uniform vec2 u_texel_size;\n\
uniform float u_delta;\n\
uniform float u_intensity;\n\
uniform float u_dirt_factor;\n\
\n\
vec4 sampleBox(vec2 uv) {\n\
vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\
vec4 s = texture2D( u_glow_texture, uv + o.xy ) + texture2D( u_glow_texture, uv + o.zy) + texture2D( u_glow_texture, uv + o.xw) + texture2D( u_glow_texture, uv + o.zw);\n\
return s * 0.25;\n\
}\n\
void main() {\n\
vec4 glow = sampleBox( v_coord );\n\
#ifdef USE_DIRT\n\
glow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\
#endif\n\
gl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\
}"
LiteGraph.registerNodeType("texture/glow", LGraphTextureGlow );
// Texture Blur *****************************************
function LGraphTextureKuwaharaFilter()
{
this.addInput("Texture","Texture");
this.addOutput("Filtered","Texture");
this.properties = { intensity: 1, radius: 5 };
}
LGraphTextureKuwaharaFilter.title = "Kuwahara Filter";
LGraphTextureKuwaharaFilter.desc = "Filters a texture giving an artistic oil canvas painting";
LGraphTextureKuwaharaFilter.max_radius = 10;
LGraphTextureKuwaharaFilter._shaders = [];
LGraphTextureKuwaharaFilter.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex)
return;
if(!this.isOutputConnected(0))
return; //saves work
var temp = this._temp_texture;
if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
{
//we need two textures to do the blurring
this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
//this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
}
//iterations
var radius = this.properties.radius;
radius = Math.min( Math.floor(radius), LGraphTextureKuwaharaFilter.max_radius );
if(radius == 0) //skip blurring
{
this.setOutputData(0, tex);
return;
}
var intensity = this.properties.intensity;
//blur sometimes needs an aspect correction
var aspect = LiteGraph.camera_aspect;
if(!aspect && window.gl !== undefined)
aspect = gl.canvas.height / gl.canvas.width;
if(!aspect)
aspect = 1;
aspect = this.properties.preserve_aspect ? aspect : 1;
if(!LGraphTextureKuwaharaFilter._shaders[ radius ])
LGraphTextureKuwaharaFilter._shaders[ radius ] = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureKuwaharaFilter.pixel_shader, { RADIUS: radius.toFixed(0) });
var shader = LGraphTextureKuwaharaFilter._shaders[ radius ];
var mesh = GL.Mesh.getScreenQuad();
tex.bind(0);
this._temp_texture.drawTo( function() {
shader.uniforms({ u_texture: 0, u_intensity: intensity, u_resolution: [tex.width, tex.height], u_iResolution: [1/tex.width,1/tex.height]}).draw(mesh);
});
this.setOutputData(0, this._temp_texture);
}
//from https://www.shadertoy.com/view/MsXSz4
LGraphTextureKuwaharaFilter.pixel_shader = "\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform float u_intensity;\n\
uniform vec2 u_resolution;\n\
uniform vec2 u_iResolution;\n\
#ifndef RADIUS\n\
#define RADIUS 7\n\
#endif\n\
void main() {\n\
\n\
const int radius = RADIUS;\n\
vec2 fragCoord = v_coord;\n\
vec2 src_size = u_iResolution;\n\
vec2 uv = v_coord;\n\
float n = float((radius + 1) * (radius + 1));\n\
int i;\n\
int j;\n\
vec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\
vec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\
vec3 c;\n\
\n\
for (int j = -radius; j <= 0; ++j) {\n\
for (int i = -radius; i <= 0; ++i) {\n\
c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
m0 += c;\n\
s0 += c * c;\n\
}\n\
}\n\
\n\
for (int j = -radius; j <= 0; ++j) {\n\
for (int i = 0; i <= radius; ++i) {\n\
c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
m1 += c;\n\
s1 += c * c;\n\
}\n\
}\n\
\n\
for (int j = 0; j <= radius; ++j) {\n\
for (int i = 0; i <= radius; ++i) {\n\
c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
m2 += c;\n\
s2 += c * c;\n\
}\n\
}\n\
\n\
for (int j = 0; j <= radius; ++j) {\n\
for (int i = -radius; i <= 0; ++i) {\n\
c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\
m3 += c;\n\
s3 += c * c;\n\
}\n\
}\n\
\n\
float min_sigma2 = 1e+2;\n\
m0 /= n;\n\
s0 = abs(s0 / n - m0 * m0);\n\
\n\
float sigma2 = s0.r + s0.g + s0.b;\n\
if (sigma2 < min_sigma2) {\n\
min_sigma2 = sigma2;\n\
gl_FragColor = vec4(m0, 1.0);\n\
}\n\
\n\
m1 /= n;\n\
s1 = abs(s1 / n - m1 * m1);\n\
\n\
sigma2 = s1.r + s1.g + s1.b;\n\
if (sigma2 < min_sigma2) {\n\
min_sigma2 = sigma2;\n\
gl_FragColor = vec4(m1, 1.0);\n\
}\n\
\n\
m2 /= n;\n\
s2 = abs(s2 / n - m2 * m2);\n\
\n\
sigma2 = s2.r + s2.g + s2.b;\n\
if (sigma2 < min_sigma2) {\n\
min_sigma2 = sigma2;\n\
gl_FragColor = vec4(m2, 1.0);\n\
}\n\
\n\
m3 /= n;\n\
s3 = abs(s3 / n - m3 * m3);\n\
\n\
sigma2 = s3.r + s3.g + s3.b;\n\
if (sigma2 < min_sigma2) {\n\
min_sigma2 = sigma2;\n\
gl_FragColor = vec4(m3, 1.0);\n\
}\n\
}\n\
";
LiteGraph.registerNodeType("texture/kuwahara", LGraphTextureKuwaharaFilter );
// Texture Webcam *****************************************
function LGraphTextureWebcam()
{
this.addOutput("Webcam","Texture");
this.properties = { texture_name: "", facingMode: "user" };
this.boxcolor = "black";
this.version = 0;
}
LGraphTextureWebcam.title = "Webcam";
LGraphTextureWebcam.desc = "Webcam texture";
LGraphTextureWebcam.is_webcam_open = false;
LGraphTextureWebcam.prototype.openStream = function()
{
if (!navigator.getUserMedia) {
//console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags');
return;
}
this._waiting_confirmation = true;
// Not showing vendor prefixes.
var constraints = { audio: false, video: { facingMode: this.properties.facingMode } };
navigator.mediaDevices.getUserMedia( constraints ).then( this.streamReady.bind(this) ).catch( onFailSoHard );
var that = this;
function onFailSoHard(e) {
LGraphTextureWebcam.is_webcam_open = false;
console.log('Webcam rejected', e);
that._webcam_stream = false;
that.boxcolor = "red";
that.trigger("stream_error");
};
}
LGraphTextureWebcam.prototype.closeStream = function()
{
if(this._webcam_stream)
{
var tracks = this._webcam_stream.getTracks();
if(tracks.length)
{
for(var i = 0;i < tracks.length; ++i)
tracks[i].stop();
}
LGraphTextureWebcam.is_webcam_open = false;
this._webcam_stream = null;
this._video = null;
this.boxcolor = "black";
this.trigger("stream_closed");
}
}
LGraphTextureWebcam.prototype.streamReady = function(localMediaStream)
{
this._webcam_stream = localMediaStream;
//this._waiting_confirmation = false;
this.boxcolor = "green";
var video = this._video;
if(!video)
{
video = document.createElement("video");
video.autoplay = true;
video.srcObject = localMediaStream;
this._video = video;
//document.body.appendChild( video ); //debug
//when video info is loaded (size and so)
video.onloadedmetadata = function(e) {
// Ready to go. Do some stuff.
LGraphTextureWebcam.is_webcam_open = true;
console.log(e);
};
}
this.trigger("stream_ready",video);
}
LGraphTextureWebcam.prototype.onPropertyChanged = function(name,value)
{
if(name == "facingMode")
{
this.properties.facingMode = value;
this.closeStream();
this.openStream();
}
}
LGraphTextureWebcam.prototype.onRemoved = function()
{
if(!this._webcam_stream)
return;
var tracks = this._webcam_stream.getTracks();
if(tracks.length)
{
for(var i = 0;i < tracks.length; ++i)
tracks[i].stop();
}
this._webcam_stream = null;
this._video = null;
}
LGraphTextureWebcam.prototype.onDrawBackground = function(ctx)
{
if(this.flags.collapsed || this.size[1] <= 20)
return;
if(!this._video)
return;
//render to graph canvas
ctx.save();
if(!ctx.webgl) //reverse image
ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]);
else
{
if(this._video_texture)
ctx.drawImage(this._video_texture, 0, 0, this.size[0], this.size[1]);
}
ctx.restore();
}
LGraphTextureWebcam.prototype.onExecute = function()
{
if(this._webcam_stream == null && !this._waiting_confirmation)
this.openStream();
if(!this._video || !this._video.videoWidth)
return;
var width = this._video.videoWidth;
var height = this._video.videoHeight;
var temp = this._video_texture;
if(!temp || temp.width != width || temp.height != height )
this._video_texture = new GL.Texture( width, height, { format: gl.RGB, filter: gl.LINEAR });
this._video_texture.uploadImage( this._video );
this._video_texture.version = ++this.version;
if(this.properties.texture_name)
{
var container = LGraphTexture.getTexturesContainer();
container[ this.properties.texture_name ] = this._video_texture;
}
this.setOutputData(0,this._video_texture);
for(var i = 1; i < this.outputs.length; ++i)
{
if(!this.outputs[i])
continue;
switch( this.outputs[i].name )
{
case "width": this.setOutputData(i,this._video.videoWidth);break;
case "height": this.setOutputData(i,this._video.videoHeight);break;
}
}
}
LGraphTextureWebcam.prototype.onGetOutputs = function()
{
return [["width","number"],["height","number"],["stream_ready",LiteGraph.EVENT],["stream_closed",LiteGraph.EVENT],["stream_error",LiteGraph.EVENT]];
}
LiteGraph.registerNodeType("texture/webcam", LGraphTextureWebcam );
//from https://github.com/spite/Wagner
function LGraphLensFX()
{
this.addInput("in","Texture");
this.addInput("f","number");
this.addOutput("out","Texture");
this.properties = { enabled: true, factor: 1, precision: LGraphTexture.LOW };
this._uniforms = { u_texture: 0, u_factor: 1 };
}
LGraphLensFX.title = "Lens FX";
LGraphLensFX.desc = "distortion and chromatic aberration";
LGraphLensFX.widgets_info = {
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphLensFX.prototype.onGetInputs = function() { return [["enabled","boolean"]]; }
LGraphLensFX.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex)
return;
if(!this.isOutputConnected(0))
return; //saves work
if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.getInputOrProperty("enabled" ) === false )
{
this.setOutputData(0, tex );
return;
}
var temp = this._temp_texture;
if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
temp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
var shader = LGraphLensFX._shader;
if(!shader)
shader = LGraphLensFX._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphLensFX.pixel_shader );
var factor = this.getInputData(1);
if(factor == null)
factor = this.properties.factor;
var uniforms = this._uniforms;
uniforms.u_factor = factor;
//apply shader
gl.disable( gl.DEPTH_TEST );
temp.drawTo(function(){
tex.bind(0);
shader.uniforms(uniforms).draw( GL.Mesh.getScreenQuad() );
});
this.setOutputData(0,temp);
}
LGraphLensFX.pixel_shader = "precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform float u_factor;\n\
vec2 barrelDistortion(vec2 coord, float amt) {\n\
vec2 cc = coord - 0.5;\n\
float dist = dot(cc, cc);\n\
return coord + cc * dist * amt;\n\
}\n\
\n\
float sat( float t )\n\
{\n\
return clamp( t, 0.0, 1.0 );\n\
}\n\
\n\
float linterp( float t ) {\n\
return sat( 1.0 - abs( 2.0*t - 1.0 ) );\n\
}\n\
\n\
float remap( float t, float a, float b ) {\n\
return sat( (t - a) / (b - a) );\n\
}\n\
\n\
vec4 spectrum_offset( float t ) {\n\
vec4 ret;\n\
float lo = step(t,0.5);\n\
float hi = 1.0-lo;\n\
float w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\n\
ret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\n\
\n\
return pow( ret, vec4(1.0/2.2) );\n\
}\n\
\n\
const float max_distort = 2.2;\n\
const int num_iter = 12;\n\
const float reci_num_iter_f = 1.0 / float(num_iter);\n\
\n\
void main()\n\
{ \n\
vec2 uv=v_coord;\n\
vec4 sumcol = vec4(0.0);\n\
vec4 sumw = vec4(0.0); \n\
for ( int i=0; i<num_iter;++i )\n\
{\n\
float t = float(i) * reci_num_iter_f;\n\
vec4 w = spectrum_offset( t );\n\
sumw += w;\n\
sumcol += w * texture2D( u_texture, barrelDistortion(uv, .6 * max_distort*t * u_factor ) );\n\
}\n\
gl_FragColor = sumcol / sumw;\n\
}";
LiteGraph.registerNodeType("texture/lensfx", LGraphLensFX );
//simple exposition, but plan to expand it to support different gamma curves
function LGraphExposition()
{
this.addInput("in","Texture");
this.addInput("exp","number");
this.addOutput("out","Texture");
this.properties = { exposition: 1, precision: LGraphTexture.LOW };
this._uniforms = { u_texture: 0, u_exposition: 1 };
}
LGraphExposition.title = "Exposition";
LGraphExposition.desc = "Controls texture exposition";
LGraphExposition.widgets_info = {
"exposition": { widget:"slider", min:0,max:3 },
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphExposition.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex)
return;
if(!this.isOutputConnected(0))
return; //saves work
var temp = this._temp_texture;
if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
temp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
var shader = LGraphExposition._shader;
if(!shader)
shader = LGraphExposition._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphExposition.pixel_shader );
var exp = this.properties.exposition;
var exp_input = this.getInputData(1);
if(exp_input != null)
exp = this.properties.exposition = exp_input;
var uniforms = this._uniforms;
//apply shader
temp.drawTo(function(){
gl.disable( gl.DEPTH_TEST );
tex.bind(0);
shader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad());
});
this.setOutputData(0,temp);
}
LGraphExposition.pixel_shader = "precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform float u_exposition;\n\
\n\
void main() {\n\
vec4 color = texture2D( u_texture, v_coord );\n\
gl_FragColor = vec4( color.xyz * u_exposition, color.a );\n\
}";
LiteGraph.registerNodeType("texture/exposition", LGraphExposition );
function LGraphToneMapping()
{
this.addInput("in","Texture");
this.addInput("avg","number,Texture");
this.addOutput("out","Texture");
this.properties = { enabled: true, scale:1, gamma: 1, average_lum: 1, lum_white: 1, precision: LGraphTexture.LOW };
this._uniforms = {
u_texture: 0,
u_lumwhite2: 1,
u_igamma: 1,
u_scale: 1,
u_average_lum: 1
};
}
LGraphToneMapping.title = "Tone Mapping";
LGraphToneMapping.desc = "Applies Tone Mapping to convert from high to low";
LGraphToneMapping.widgets_info = {
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphToneMapping.prototype.onGetInputs = function() {
return [["enabled","boolean"]];
}
LGraphToneMapping.prototype.onExecute = function()
{
var tex = this.getInputData(0);
if(!tex)
return;
if(!this.isOutputConnected(0))
return; //saves work
if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.getInputOrProperty("enabled" ) === false )
{
this.setOutputData(0, tex );
return;
}
var temp = this._temp_texture;
if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type )
temp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR });
var avg = this.getInputData(1);
if(avg == null)
avg = this.properties.average_lum;
var uniforms = this._uniforms;
var shader = null;
if( avg.constructor === Number )
{
this.properties.average_lum = avg;
uniforms.u_average_lum = this.properties.average_lum;
shader = LGraphToneMapping._shader;
if(!shader)
shader = LGraphToneMapping._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphToneMapping.pixel_shader );
}
else if( avg.constructor === GL.Texture )
{
uniforms.u_average_texture = avg.bind(1);
shader = LGraphToneMapping._shader_texture;
if(!shader)
shader = LGraphToneMapping._shader_texture = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphToneMapping.pixel_shader, { AVG_TEXTURE:"" } );
}
uniforms.u_lumwhite2 = this.properties.lum_white * this.properties.lum_white;
uniforms.u_scale = this.properties.scale;
uniforms.u_igamma = 1/this.properties.gamma;
//apply shader
gl.disable( gl.DEPTH_TEST );
temp.drawTo(function(){
tex.bind(0);
shader.uniforms(uniforms).draw( GL.Mesh.getScreenQuad() );
});
this.setOutputData(0,this._temp_texture);
}
LGraphToneMapping.pixel_shader = "precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform float u_scale;\n\
#ifdef AVG_TEXTURE\n\
uniform sampler2D u_average_texture;\n\
#else\n\
uniform float u_average_lum;\n\
#endif\n\
uniform float u_lumwhite2;\n\
uniform float u_igamma;\n\
vec3 RGB2xyY (vec3 rgb)\n\
{\n\
const mat3 RGB2XYZ = mat3(0.4124, 0.3576, 0.1805,\n\
0.2126, 0.7152, 0.0722,\n\
0.0193, 0.1192, 0.9505);\n\
vec3 XYZ = RGB2XYZ * rgb;\n\
\n\
float f = (XYZ.x + XYZ.y + XYZ.z);\n\
return vec3(XYZ.x / f,\n\
XYZ.y / f,\n\
XYZ.y);\n\
}\n\
\n\
void main() {\n\
vec4 color = texture2D( u_texture, v_coord );\n\
vec3 rgb = color.xyz;\n\
float average_lum = 0.0;\n\
#ifdef AVG_TEXTURE\n\
vec3 pixel = texture2D(u_average_texture,vec2(0.5)).xyz;\n\
average_lum = (pixel.x + pixel.y + pixel.z) / 3.0;\n\
#else\n\
average_lum = u_average_lum;\n\
#endif\n\
//Ld - this part of the code is the same for both versions\n\
float lum = dot(rgb, vec3(0.2126, 0.7152, 0.0722));\n\
float L = (u_scale / average_lum) * lum;\n\
float Ld = (L * (1.0 + L / u_lumwhite2)) / (1.0 + L);\n\
//first\n\
//vec3 xyY = RGB2xyY(rgb);\n\
//xyY.z *= Ld;\n\
//rgb = xyYtoRGB(xyY);\n\
//second\n\
rgb = (rgb / lum) * Ld;\n\
rgb = pow( rgb, vec3( u_igamma ) );\n\
gl_FragColor = vec4( rgb, color.a );\n\
}";
LiteGraph.registerNodeType("texture/tonemapping", LGraphToneMapping );
function LGraphTexturePerlin()
{
this.addOutput("out","Texture");
this.properties = { width: 512, height: 512, seed:0, persistence: 0.1, octaves: 8, scale: 1, offset: [0,0], amplitude: 1, precision: LGraphTexture.DEFAULT };
this._key = 0;
this._texture = null;
this._uniforms = { u_persistence: 0.1, u_seed: 0, u_offset: vec2.create(), u_scale: 1, u_viewport: vec2.create() };
}
LGraphTexturePerlin.title = "Perlin";
LGraphTexturePerlin.desc = "Generates a perlin noise texture";
LGraphTexturePerlin.widgets_info = {
precision: { widget:"combo", values: LGraphTexture.MODE_VALUES },
width: { type: "Number", precision: 0, step: 1 },
height: { type: "Number", precision: 0, step: 1 },
octaves: { type: "Number", precision: 0, step: 1, min: 1, max: 50 }
};
LGraphTexturePerlin.prototype.onGetInputs = function()
{
return [["seed","Number"],["persistence","Number"],["octaves","Number"],["scale","Number"],["amplitude","Number"],["offset","vec2"]];
}
LGraphTexturePerlin.prototype.onExecute = function()
{
if(!this.isOutputConnected(0))
return; //saves work
var w = this.properties.width|0;
var h = this.properties.height|0;
if(w == 0) w = gl.viewport_data[2]; //0 means default
if(h == 0) h = gl.viewport_data[3]; //0 means default
var type = LGraphTexture.getTextureType( this.properties.precision );
var temp = this._texture;
if(!temp || temp.width != w || temp.height != h || temp.type != type )
temp = this._texture = new GL.Texture( w, h, { type: type, format: gl.RGB, filter: gl.LINEAR });
var persistence = this.getInputOrProperty("persistence");
var octaves = this.getInputOrProperty("octaves");
var offset = this.getInputOrProperty("offset");
var scale = this.getInputOrProperty("scale");
var amplitude = this.getInputOrProperty("amplitude");
var seed = this.getInputOrProperty("seed");
//reusing old texture
var key = "" + w + h + type + persistence + octaves + scale + seed + offset[0] + offset[1] + amplitude;
if(key == this._key)
{
this.setOutputData( 0, temp );
return;
}
this._key = key;
//gather uniforms
var uniforms = this._uniforms;
uniforms.u_persistence = persistence;
uniforms.u_octaves = octaves;
uniforms.u_offset.set( offset );
uniforms.u_scale = scale;
uniforms.u_amplitude = amplitude;
uniforms.u_seed = seed * 128;
uniforms.u_viewport[0] = w;
uniforms.u_viewport[1] = h;
//render
var shader = LGraphTexturePerlin._shader;
if(!shader)
shader = LGraphTexturePerlin._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTexturePerlin.pixel_shader );
gl.disable( gl.BLEND );
gl.disable( gl.DEPTH_TEST );
temp.drawTo( function() {
shader.uniforms( uniforms ).draw( GL.Mesh.getScreenQuad() );
});
this.setOutputData( 0, temp );
}
LGraphTexturePerlin.pixel_shader = "precision highp float;\n\
varying vec2 v_coord;\n\
uniform vec2 u_offset;\n\
uniform float u_scale;\n\
uniform float u_persistence;\n\
uniform int u_octaves;\n\
uniform float u_amplitude;\n\
uniform vec2 u_viewport;\n\
uniform float u_seed;\n\
#define M_PI 3.14159265358979323846\n\
\n\
float rand(vec2 c){ return fract(sin(dot(c.xy ,vec2( 12.9898 + u_seed,78.233 + u_seed))) * 43758.5453); }\n\
\n\
float noise(vec2 p, float freq ){\n\
float unit = u_viewport.x/freq;\n\
vec2 ij = floor(p/unit);\n\
vec2 xy = mod(p,unit)/unit;\n\
//xy = 3.*xy*xy-2.*xy*xy*xy;\n\
xy = .5*(1.-cos(M_PI*xy));\n\
float a = rand((ij+vec2(0.,0.)));\n\
float b = rand((ij+vec2(1.,0.)));\n\
float c = rand((ij+vec2(0.,1.)));\n\
float d = rand((ij+vec2(1.,1.)));\n\
float x1 = mix(a, b, xy.x);\n\
float x2 = mix(c, d, xy.x);\n\
return mix(x1, x2, xy.y);\n\
}\n\
\n\
float pNoise(vec2 p, int res){\n\
float persistance = u_persistence;\n\
float n = 0.;\n\
float normK = 0.;\n\
float f = 4.;\n\
float amp = 1.0;\n\
int iCount = 0;\n\
for (int i = 0; i<50; i++){\n\
n+=amp*noise(p, f);\n\
f*=2.;\n\
normK+=amp;\n\
amp*=persistance;\n\
if (iCount >= res)\n\
break;\n\
iCount++;\n\
}\n\
float nf = n/normK;\n\
return nf*nf*nf*nf;\n\
}\n\
void main() {\n\
vec2 uv = v_coord * u_scale * u_viewport + u_offset * u_scale;\n\
vec4 color = vec4( pNoise( uv, u_octaves ) * u_amplitude );\n\
gl_FragColor = color;\n\
}";
LiteGraph.registerNodeType("texture/perlin", LGraphTexturePerlin );
function LGraphTextureCanvas2D()
{
this.addOutput("out","Texture");
this.properties = { code: "", width: 512, height: 512, precision: LGraphTexture.DEFAULT };
this._func = null;
this._temp_texture = null;
}
LGraphTextureCanvas2D.title = "Canvas2D";
LGraphTextureCanvas2D.desc = "Executes Canvas2D code inside a texture or the viewport";
LGraphTextureCanvas2D.widgets_info = {
precision: { widget:"combo", values: LGraphTexture.MODE_VALUES },
code: { type: "code" },
width: { type: "Number", precision: 0, step: 1 },
height: { type: "Number", precision: 0, step: 1 }
};
LGraphTextureCanvas2D.prototype.onPropertyChanged = function(name, value)
{
if(name == "code" && LiteGraph.allow_scripts )
{
this._func = null;
try
{
this._func = new Function( "canvas", "ctx", "time", "script", value );
this.boxcolor = "#00FF00";
}
catch (err)
{
this.boxcolor = "#FF0000";
console.error("Error parsing script");
console.error(err);
}
}
}
LGraphTextureCanvas2D.prototype.onExecute = function()
{
var func = this._func;
if(!func || !this.isOutputConnected(0))
return;
if(!global.enableWebGLCanvas)
{
console.warn("cannot use LGraphTextureCanvas2D if Canvas2DtoWebGL is not included");
return;
}
var width = this.properties.width || gl.canvas.width;
var height = this.properties.height || gl.canvas.height;
var temp = this._temp_texture;
if(!temp || temp.width != width || temp.height != height )
temp = this._temp_texture = new GL.Texture( width, height, { format: gl.RGBA, filter: gl.LINEAR });
var that = this;
var time = this.graph.getTime();
temp.drawTo(function(){
gl.start2D();
try
{
if(func.draw)
func.draw.call( that, gl.canvas, gl, time, func );
else
func.call( that, gl.canvas, gl, time, func );
that.boxcolor = "#00FF00";
}
catch (err)
{
that.boxcolor = "#FF0000";
console.error("Error executing script");
console.error(err);
}
gl.finish2D();
});
this.setOutputData( 0, temp );
}
LiteGraph.registerNodeType("texture/canvas2D", LGraphTextureCanvas2D );
function LGraphTextureMatte()
{
this.addInput("in","Texture");
this.addOutput("out","Texture");
this.properties = { key_color: vec3.fromValues(0.,1.,0.), threshold: 0.8, slope: 0.2, precision: LGraphTexture.DEFAULT };
}
LGraphTextureMatte.title = "Matte";
LGraphTextureMatte.desc = "Extracts background";
LGraphTextureMatte.widgets_info = {
"key_color": { widget:"color" },
"precision": { widget:"combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureMatte.prototype.onExecute = function()
{
if(!this.isOutputConnected(0))
return; //saves work
var tex = this.getInputData(0);
if(this.properties.precision === LGraphTexture.PASS_THROUGH )
{
this.setOutputData(0,tex);
return;
}
if(!tex)
return;
this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
gl.disable( gl.BLEND );
gl.disable( gl.DEPTH_TEST );
if(!this._uniforms)
this._uniforms = { u_texture: 0, u_key_color: this.properties.key_color, u_threshold: 1, u_slope: 1 };
var uniforms = this._uniforms;
var mesh = Mesh.getScreenQuad();
var shader = LGraphTextureMatte._shader;
if(!shader)
shader = LGraphTextureMatte._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureMatte.pixel_shader );
uniforms.u_key_color = this.properties.key_color;
uniforms.u_threshold = this.properties.threshold;
uniforms.u_slope = this.properties.slope;
this._tex.drawTo( function() {
tex.bind(0);
shader.uniforms( uniforms ).draw( mesh );
});
this.setOutputData( 0, this._tex );
}
LGraphTextureMatte.pixel_shader = "precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform vec3 u_key_color;\n\
uniform float u_threshold;\n\
uniform float u_slope;\n\
\n\
void main() {\n\
vec3 color = texture2D( u_texture, v_coord ).xyz;\n\
float diff = length( normalize(color) - normalize(u_key_color) );\n\
float edge = u_threshold * (1.0 - u_slope);\n\
float alpha = smoothstep( edge, u_threshold, diff);\n\
gl_FragColor = vec4( color, alpha );\n\
}";
LiteGraph.registerNodeType("texture/matte", LGraphTextureMatte );
//***********************************
//Cubemap reader (to pass a cubemap to a node that requires cubemaps and no images)
function LGraphCubemap()
{
this.addOutput("Cubemap","Cubemap");
this.properties = {name:""};
this.size = [LGraphTexture.image_preview_size, LGraphTexture.image_preview_size];
}
LGraphCubemap.title = "Cubemap";
LGraphCubemap.prototype.onDropFile = function(data, filename, file)
{
if(!data)
{
this._drop_texture = null;
this.properties.name = "";
}
else
{
if( typeof(data) == "string" )
this._drop_texture = GL.Texture.fromURL(data);
else
this._drop_texture = GL.Texture.fromDDSInMemory(data);
this.properties.name = filename;
}
}
LGraphCubemap.prototype.onExecute = function()
{
if(this._drop_texture)
{
this.setOutputData(0, this._drop_texture);
return;
}
if(!this.properties.name)
return;
var tex = LGraphTexture.getTexture( this.properties.name );
if(!tex)
return;
this._last_tex = tex;
this.setOutputData(0, tex);
}
LGraphCubemap.prototype.onDrawBackground = function(ctx)
{
if( this.flags.collapsed || this.size[1] <= 20)
return;
if(!ctx.webgl)
return;
var cube_mesh = gl.meshes["cube"];
if(!cube_mesh)
cube_mesh = gl.meshes["cube"] = GL.Mesh.cube({size:1});
//var view = mat4.lookAt( mat4.create(), [0,0
}
LiteGraph.registerNodeType("texture/cubemap", LGraphCubemap );
} //litegl.js defined
})(this);