(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 = "
pixelcode must be vec3, uvcode must be vec2, is optional
\uv: tex. coords
color: texture colorB: textureB
time: scene time value: input value
For multiline you must type: result = ...
"; 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