mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 14:54:37 +00:00
4529 lines
147 KiB
JavaScript
Executable File
4529 lines
147 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"]
|
|
];
|
|
};
|
|
|
|
//used to replace shader code
|
|
LGraphTexture.replaceCode = function( code, context )
|
|
{
|
|
return code.replace(/\{\{[a-zA-Z0-9_]*\}\}/g, function(v){
|
|
v = v.replace( /[\{\}]/g, "" );
|
|
return context[v] || "";
|
|
});
|
|
}
|
|
|
|
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, uvcode must be vec2, is optional</p>\
|
|
<p><strong>uv:</strong> tex. coords</p><p><strong>color:</strong> texture <strong>colorB:</strong> textureB</p><p><strong>time:</strong> scene time <strong>value:</strong> input value</p><p>For multiline you must type: result = ...</p>";
|
|
|
|
this.properties = {
|
|
value: 1,
|
|
pixelcode: "color + colorB * value",
|
|
uvcode: "",
|
|
precision: LGraphTexture.DEFAULT
|
|
};
|
|
|
|
this.has_error = false;
|
|
}
|
|
|
|
LGraphTextureOperation.widgets_info = {
|
|
uvcode: { widget: "code" },
|
|
pixelcode: { widget: "code" },
|
|
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.onPropertyChanged = function()
|
|
{
|
|
this.has_error = false;
|
|
}
|
|
|
|
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 ( !this.has_error && (!shader || this._shader_code != uvcode + "|" + pixelcode) ) {
|
|
|
|
var final_pixel_code = LGraphTexture.replaceCode( LGraphTextureOperation.pixel_shader, { UV_CODE:uvcode, PIXEL_CODE:pixelcode });
|
|
|
|
try {
|
|
shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, final_pixel_code );
|
|
this.boxcolor = "#00FF00";
|
|
} catch (err) {
|
|
console.log("Error compiling shader: ", err, final_pixel_code );
|
|
this.boxcolor = "#FF0000";
|
|
this.has_error = true;
|
|
return;
|
|
}
|
|
this._shader = shader;
|
|
this._shader_code = uvcode + "|" + pixelcode;
|
|
}
|
|
|
|
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, //to avoid stalls
|
|
high_quality: false //to use as much pixels as possible
|
|
};
|
|
|
|
this._uniforms = {
|
|
u_texture: 0,
|
|
u_mipmap_offset: 0
|
|
};
|
|
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 256 random numbers and stores them in two mat4
|
|
var samples = new Float32Array(16);
|
|
for (var i = 0; i < samples.length; ++i) {
|
|
samples[i] = Math.random(); //poorly distributed samples
|
|
}
|
|
//upload only once
|
|
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
|
|
});
|
|
}
|
|
|
|
this._uniforms.u_mipmap_offset = 0;
|
|
|
|
if(this.properties.high_quality)
|
|
{
|
|
if( !this._temp_pot2_texture || this._temp_pot2_texture.type != type )
|
|
this._temp_pot2_texture = new GL.Texture(512, 512, {
|
|
type: type,
|
|
format: gl.RGBA,
|
|
minFilter: gl.LINEAR_MIPMAP_LINEAR,
|
|
magFilter: gl.LINEAR
|
|
});
|
|
|
|
tex.copyTo( this._temp_pot2_texture );
|
|
tex = this._temp_pot2_texture;
|
|
tex.bind(0);
|
|
gl.generateMipmap(GL_TEXTURE_2D);
|
|
this._uniforms.u_mipmap_offset = 9;
|
|
}
|
|
|
|
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\
|
|
//random average\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 Filter *****************************************
|
|
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
|
|
) {
|
|
this._temp_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 *****************************************
|
|
function LGraphTextureXDoGFilter() {
|
|
this.addInput("Texture", "Texture");
|
|
this.addOutput("Filtered", "Texture");
|
|
this.properties = {
|
|
sigma: 1.4,
|
|
k: 1.6,
|
|
p: 21.7,
|
|
epsilon: 79,
|
|
phi: 0.017
|
|
};
|
|
}
|
|
|
|
LGraphTextureXDoGFilter.title = "XDoG Filter";
|
|
LGraphTextureXDoGFilter.desc =
|
|
"Filters a texture giving an artistic ink style";
|
|
|
|
LGraphTextureXDoGFilter.max_radius = 10;
|
|
LGraphTextureXDoGFilter._shaders = [];
|
|
|
|
LGraphTextureXDoGFilter.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
|
|
) {
|
|
this._temp_texture = new GL.Texture(tex.width, tex.height, {
|
|
type: tex.type,
|
|
format: gl.RGBA,
|
|
filter: gl.LINEAR
|
|
});
|
|
}
|
|
|
|
if (!LGraphTextureXDoGFilter._xdog_shader) {
|
|
LGraphTextureXDoGFilter._xdog_shader = new GL.Shader(
|
|
Shader.SCREEN_VERTEX_SHADER,
|
|
LGraphTextureXDoGFilter.xdog_pixel_shader
|
|
);
|
|
}
|
|
var shader = LGraphTextureXDoGFilter._xdog_shader;
|
|
var mesh = GL.Mesh.getScreenQuad();
|
|
|
|
var sigma = this.properties.sigma;
|
|
var k = this.properties.k;
|
|
var p = this.properties.p;
|
|
var epsilon = this.properties.epsilon;
|
|
var phi = this.properties.phi;
|
|
tex.bind(0);
|
|
this._temp_texture.drawTo(function() {
|
|
shader
|
|
.uniforms({
|
|
src: 0,
|
|
sigma: sigma,
|
|
k: k,
|
|
p: p,
|
|
epsilon: epsilon,
|
|
phi: phi,
|
|
cvsWidth: tex.width,
|
|
cvsHeight: tex.height
|
|
})
|
|
.draw(mesh);
|
|
});
|
|
|
|
this.setOutputData(0, this._temp_texture);
|
|
};
|
|
|
|
//from https://github.com/RaymondMcGuire/GPU-Based-Image-Processing-Tools/blob/master/lib_webgl/scripts/main.js
|
|
LGraphTextureXDoGFilter.xdog_pixel_shader =
|
|
"\n\
|
|
precision highp float;\n\
|
|
uniform sampler2D src;\n\n\
|
|
uniform float cvsHeight;\n\
|
|
uniform float cvsWidth;\n\n\
|
|
uniform float sigma;\n\
|
|
uniform float k;\n\
|
|
uniform float p;\n\
|
|
uniform float epsilon;\n\
|
|
uniform float phi;\n\
|
|
varying vec2 v_coord;\n\n\
|
|
float cosh(float val)\n\
|
|
{\n\
|
|
float tmp = exp(val);\n\
|
|
float cosH = (tmp + 1.0 / tmp) / 2.0;\n\
|
|
return cosH;\n\
|
|
}\n\n\
|
|
float tanh(float val)\n\
|
|
{\n\
|
|
float tmp = exp(val);\n\
|
|
float tanH = (tmp - 1.0 / tmp) / (tmp + 1.0 / tmp);\n\
|
|
return tanH;\n\
|
|
}\n\n\
|
|
float sinh(float val)\n\
|
|
{\n\
|
|
float tmp = exp(val);\n\
|
|
float sinH = (tmp - 1.0 / tmp) / 2.0;\n\
|
|
return sinH;\n\
|
|
}\n\n\
|
|
void main(void){\n\
|
|
vec3 destColor = vec3(0.0);\n\
|
|
float tFrag = 1.0 / cvsHeight;\n\
|
|
float sFrag = 1.0 / cvsWidth;\n\
|
|
vec2 Frag = vec2(sFrag,tFrag);\n\
|
|
vec2 uv = gl_FragCoord.st;\n\
|
|
float twoSigmaESquared = 2.0 * sigma * sigma;\n\
|
|
float twoSigmaRSquared = twoSigmaESquared * k * k;\n\
|
|
int halfWidth = int(ceil( 1.0 * sigma * k ));\n\n\
|
|
const int MAX_NUM_ITERATION = 99999;\n\
|
|
vec2 sum = vec2(0.0);\n\
|
|
vec2 norm = vec2(0.0);\n\n\
|
|
for(int cnt=0;cnt<MAX_NUM_ITERATION;cnt++){\n\
|
|
if(cnt > (2*halfWidth+1)*(2*halfWidth+1)){break;}\n\
|
|
int i = int(cnt / (2*halfWidth+1)) - halfWidth;\n\
|
|
int j = cnt - halfWidth - int(cnt / (2*halfWidth+1)) * (2*halfWidth+1);\n\n\
|
|
float d = length(vec2(i,j));\n\
|
|
vec2 kernel = vec2( exp( -d * d / twoSigmaESquared ), \n\
|
|
exp( -d * d / twoSigmaRSquared ));\n\n\
|
|
vec2 L = texture2D(src, (uv + vec2(i,j)) * Frag).xx;\n\n\
|
|
norm += kernel;\n\
|
|
sum += kernel * L;\n\
|
|
}\n\n\
|
|
sum /= norm;\n\n\
|
|
float H = 100.0 * ((1.0 + p) * sum.x - p * sum.y);\n\
|
|
float edge = ( H > epsilon )? 1.0 : 1.0 + tanh( phi * (H - epsilon));\n\
|
|
destColor = vec3(edge);\n\
|
|
gl_FragColor = vec4(destColor, 1.0);\n\
|
|
}";
|
|
|
|
LiteGraph.registerNodeType("texture/xDoG", LGraphTextureXDoGFilter);
|
|
|
|
// 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);
|