shader graph support

This commit is contained in:
tamat
2020-07-02 00:00:53 +02:00
parent ab7bc960a0
commit 2f9c67295f
10 changed files with 1684 additions and 472 deletions

View File

@@ -52,6 +52,7 @@
return [["enabled", "boolean"]];
};
/*
Subgraph.prototype.onDrawTitle = function(ctx) {
if (this.flags.collapsed) {
return;
@@ -68,6 +69,7 @@
ctx.lineTo(x + w * 0.5, -w * 0.3);
ctx.fill();
};
*/
Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) {
var that = this;
@@ -76,6 +78,7 @@
}, 10);
};
/*
Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) {
if (
!this.flags.collapsed &&
@@ -88,6 +91,7 @@
}, 10);
}
};
*/
Subgraph.prototype.onAction = function(action, param) {
this.subgraph.onAction(action, param);

View File

@@ -660,7 +660,7 @@
u_texture: 0,
u_textureB: 1,
value: value,
texSize: [width, height],
texSize: [width, height,1/width,1/height],
time: time
})
.draw(mesh);
@@ -675,7 +675,7 @@
uniform sampler2D u_texture;\n\
uniform sampler2D u_textureB;\n\
varying vec2 v_coord;\n\
uniform vec2 texSize;\n\
uniform vec4 texSize;\n\
uniform float time;\n\
uniform float value;\n\
\n\
@@ -712,6 +712,20 @@
LGraphTextureOperation.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz");
LGraphTextureOperation.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0");
LGraphTextureOperation.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )");
LGraphTextureOperation.registerPreset("normalmap","\n\
float z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\n\
float z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\n\
float z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\n\
float z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\n\
float z4 = color.x;\n\
float z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\n\
float z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\n\
float z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\n\
float z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\n\
vec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\n\
normal.xy *= value;\n\
result.xyz = normalize(normal) * 0.5 + vec3(0.5);\n\
");
LGraphTextureOperation.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)");
//webglstudio stuff...
@@ -744,7 +758,7 @@
};
this.properties.code = LGraphTextureShader.pixel_shader;
this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec2.create(), time: 0 };
this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec4.create(), time: 0 };
}
LGraphTextureShader.title = "Shader";
@@ -910,6 +924,8 @@
}
uniforms.texSize[0] = w;
uniforms.texSize[1] = h;
uniforms.texSize[2] = 1/w;
uniforms.texSize[3] = 1/h;
uniforms.time = this.graph.getTime();
uniforms.u_value = this.properties.u_value;
uniforms.u_color.set( this.properties.u_color );
@@ -930,7 +946,7 @@
\n\
varying vec2 v_coord;\n\
uniform float time; //time in seconds\n\
uniform vec2 texSize; //tex resolution\n\
uniform vec4 texSize; //tex resolution\n\
uniform float u_value;\n\
uniform vec4 u_color;\n\n\
void main() {\n\
@@ -1580,6 +1596,69 @@ void main() {\n\
LGraphTextureDownsample
);
function LGraphTextureResize() {
this.addInput("Texture", "Texture");
this.addOutput("", "Texture");
this.properties = {
size: [512,512],
generate_mipmaps: false,
precision: LGraphTexture.DEFAULT
};
}
LGraphTextureResize.title = "Resize";
LGraphTextureResize.desc = "Resize Texture";
LGraphTextureResize.widgets_info = {
iterations: { type: "number", step: 1, precision: 0, min: 0 },
precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureResize.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;
}
var width = this.properties.size[0] | 0;
var height = this.properties.size[1] | 0;
if(width == 0)
width = tex.width;
if(height == 0)
height = tex.height;
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( !this._texture || this._texture.width != width || this._texture.height != height || this._texture.type != type )
this._texture = new GL.Texture( width, height, { type: type } );
tex.copyTo( this._texture );
if (this.properties.generate_mipmaps) {
this._texture.bind(0);
gl.generateMipmap(this._texture.texture_type);
this._texture.unbind(0);
}
this.setOutputData(0, this._texture);
};
LiteGraph.registerNodeType( "texture/resize", LGraphTextureResize );
// Texture Average *****************************************
function LGraphTextureAverage() {
this.addInput("Texture", "Texture");
@@ -2247,7 +2326,7 @@ void main() {\n\
this.addInput("Texture", "Texture");
this.addInput("Atlas", "Texture");
this.addOutput("", "Texture");
this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, texture: null };
this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, generate_mipmaps: false, texture: null };
if (!LGraphTextureEncode._shader) {
LGraphTextureEncode._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEncode.pixel_shader );
@@ -2323,6 +2402,12 @@ void main() {\n\
tex.toViewport(LGraphTextureEncode._shader, uniforms);
});
if (this.properties.generate_mipmaps) {
this._tex.bind(0);
gl.generateMipmap(this._tex.texture_type);
this._tex.unbind(0);
}
this.setOutputData(0, this._tex);
};

429
src/nodes/shaders.js Normal file
View File

@@ -0,0 +1,429 @@
(function(global) {
var LiteGraph = global.LiteGraph;
var LGraphCanvas = global.LGraphCanvas;
if (typeof GL == "undefined")
return;
//common actions to all shader node classes
function setShaderNode( node_ctor )
{
node_ctor.color = "#345";
}
function getShaderInputLinkName( node, slot )
{
if(!node.inputs)
return null;
var link = node.getInputLink( slot );
if( !link )
return null;
var origin_node = node.graph.getNodeById( link.origin_id );
if( !origin_node )
return null;
if(origin_node.getOutputVarName)
return origin_node.getOutputVarName(link.origin_slot);
//generate
return "link_" + origin_node.id + "_" + link.origin_slot;
}
function getShaderOutputLinkName( node, slot )
{
return "link_" + node.id + "_" + slot;
}
//used to host a shader body *******************
function LGShaderContext()
{
this.vs_template = "";
this.fs_template = "";
this._uniforms = {};
this._codeparts = {};
}
LGShaderContext.valid_types = ["float","vec2","vec3","vec4","sampler2D","mat3","mat4","int","boolean"];
LGShaderContext.prototype.clear = function()
{
this._uniforms = {};
this._codeparts = {};
}
LGShaderContext.prototype.addUniform = function( name, type )
{
this._uniforms[ name ] = type;
}
LGShaderContext.prototype.addCode = function( hook, code )
{
if(!this._codeparts[ hook ])
this._codeparts[ hook ] = code + "\n";
else
this._codeparts[ hook ] += code + "\n";
}
//generates the shader code from the template and the
LGShaderContext.prototype.computeShader = function( shader )
{
var uniforms = "";
for(var i in this._uniforms)
uniforms += "uniform " + this._uniforms[i] + " " + i + ";\n";
var parts = this._codeparts;
parts.uniforms = uniforms;
var vs_code = GL.Shader.replaceCodeUsingContext( this.vs_template, parts );
var fs_code = GL.Shader.replaceCodeUsingContext( this.fs_template, parts );
try
{
if(shader)
shader.updateShader( vs_code, fs_code );
else
shader = new GL.Shader( vs_code, fs_code );
return shader;
}
catch (err)
{
return null;
}
return null;//never here
}
// LGraphShaderGraph *****************************
// applies a shader graph to texture, it can be uses as an example
function LGraphShaderGraph() {
this.addOutput("out", "texture");
this.properties = { width: 0, height: 0, alpha: false, precision: 0 };
this.subgraph = new LiteGraph.LGraph();
this.subgraph._subgraph_node = this;
this.subgraph._is_subgraph = true;
var subnode = LiteGraph.createNode("shader/fragcolor");
this.subgraph.pos = [300,100];
this.subgraph.add( subnode );
this.size = [180,60];
this.redraw_on_mouse = true; //force redraw
this._uniforms = {};
this._shader = null;
this._context = new LGShaderContext();
this._context.vs_template = GL.Shader.SCREEN_VERTEX_SHADER;
this._context.fs_template = LGraphShaderGraph.template;
}
LGraphShaderGraph.template = "\n\
precision highp float;\n\
varying vec2 v_coord;\n\
{{varying}}\n\
{{uniforms}}\n\
void main() {\n\
vec2 uv = v_coord;\n\
vec4 color = vec4(0.0);\n\
{{fs_code}}\n\
gl_FragColor = color;\n\
}\n\
";
LGraphShaderGraph.widgets_info = {
precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }
};
LGraphShaderGraph.title = "ShaderGraph";
LGraphShaderGraph.desc = "Builds a shader using a graph";
LGraphShaderGraph.prototype.onSerialize = function(o)
{
o.subgraph = this.subgraph.serialize();
}
LGraphShaderGraph.prototype.onConfigure = function(o)
{
this.subgraph.configure(o.subgraph);
}
LGraphShaderGraph.prototype.onExecute = function() {
if (!this.isOutputConnected(0))
return;
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 texture = this._texture;
if ( !texture || texture.width != w || texture.height != h || texture.type != type ) {
texture = this._texture = new GL.Texture(w, h, {
type: type,
format: this.alpha ? gl.RGBA : gl.RGB,
filter: gl.LINEAR
});
}
var shader = this.getShader();
if(!shader)
return;
var uniforms = this._uniforms;
var tex_slot = 0;
if(this.inputs)
for(var i = 0; i < this.inputs.length; ++i)
{
var input = this.inputs[i];
var data = this.getInputData(i);
if(input.type == "texture")
{
if(!data)
data = GL.Texture.getWhiteTexture();
data = data.bind(tex_slot++);
}
if(data != null)
uniforms[ "u_" + input.name ] = data;
}
var mesh = GL.Mesh.getScreenQuad();
gl.disable( gl.DEPTH_TEST );
gl.disable( gl.BLEND );
texture.drawTo(function(){
shader.uniforms( uniforms );
shader.draw( mesh );
});
//use subgraph output
this.setOutputData(0, texture );
};
LGraphShaderGraph.prototype.onInputAdded = function( slot_info )
{
var subnode = LiteGraph.createNode("shader/uniform");
subnode.properties.name = slot_info.name;
subnode.properties.type = slot_info.type;
this.subgraph.add( subnode );
}
LGraphShaderGraph.prototype.onInputRemoved = function( slot, slot_info )
{
var nodes = this.subgraph.findNodesByType("shader/uniform");
for(var i = 0; i < nodes.length; ++i)
{
var node = nodes[i];
if(node.properties.name == slot_info.name )
this.subgraph.remove( node );
}
}
LGraphShaderGraph.prototype.computeSize = function()
{
var num_inputs = this.inputs ? this.inputs.length : 0;
var num_outputs = this.outputs ? this.outputs.length : 0;
return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT + 10];
}
LGraphShaderGraph.prototype.getShader = function()
{
//if subgraph not changed?
if(this._shader && this._shader._version == this.subgraph._version)
return this._shader;
//prepare context
this._context.clear();
//gets code from graph
this.subgraph.sendEventToAllNodes("onGetCode", this._context);
//compile shader
var shader = this._context.computeShader();
if(!shader)
{
this.boxcolor = "red";
return this._shader;
}
else
this.boxcolor = null;
this._shader = shader;
shader._version = this.subgraph._version;
return shader;
}
LGraphShaderGraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos)
{
if(this.flags.collapsed)
return;
var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;
//button
var over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT);
ctx.fillStyle = over ? "#555" : "#222";
ctx.beginPath();
ctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8);
ctx.fill();
//button
ctx.textAlign = "center";
ctx.font = "24px Arial";
ctx.fillStyle = over ? "#DDD" : "#999";
ctx.fillText( "+", this.size[0] * 0.5, y + 24 );
}
LGraphShaderGraph.prototype.onMouseDown = function(e, localpos, graphcanvas)
{
var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;
if(localpos[1] > y)
{
graphcanvas.showSubgraphPropertiesDialog(this);
}
}
LiteGraph.registerNodeType( "texture/shaderGraph", LGraphShaderGraph );
//Shader Nodes ***************************
//applies a shader graph to a code
function LGraphShaderUniform() {
this.addOutput("out", "");
this.properties = { name: "", type: "" };
}
LGraphShaderUniform.title = "Uniform";
LGraphShaderUniform.desc = "Input data for the shader";
LGraphShaderUniform.prototype.getTitle = function()
{
return "uniform " + this.properties.name;
}
LGraphShaderUniform.prototype.onGetCode = function( context )
{
var type = this.properties.type;
if( !type )
return;
if(type == "number")
type = "float";
else if(type == "texture")
type = "sampler2D";
if ( LGShaderContext.valid_types.indexOf(type) == -1 )
return;
context.addUniform( "u_" + this.properties.name, type );
}
LGraphShaderUniform.prototype.getOutputVarName = function(slot)
{
return "u_" + this.properties.name;
}
setShaderNode( LGraphShaderUniform );
LiteGraph.registerNodeType( "shader/uniform", LGraphShaderUniform );
function LGraphShaderAttribute() {
this.addOutput("out", "vec2");
this.properties = { name: "coord", type: "vec2" };
}
LGraphShaderAttribute.title = "Attribute";
LGraphShaderAttribute.desc = "Input data from mesh attribute";
LGraphShaderAttribute.prototype.getTitle = function()
{
return "att. " + this.properties.name;
}
LGraphShaderAttribute.prototype.onGetCode = function( context )
{
var type = this.properties.type;
if( !type || LGShaderContext.valid_types.indexOf(type) == -1 )
return;
if(type == "number")
type = "float";
if( this.properties.name != "coord")
context.addCode( "varying", " varying " + type +" v_" + this.properties.name );
}
LGraphShaderAttribute.prototype.getOutputVarName = function(slot)
{
return "v_" + this.properties.name;
}
setShaderNode( LGraphShaderAttribute );
LiteGraph.registerNodeType( "shader/attribute", LGraphShaderAttribute );
function LGraphShaderSampler2D() {
this.addInput("tex", "sampler2D");
this.addInput("uv", "vec2");
this.addOutput("rgba", "vec4");
}
LGraphShaderSampler2D.title = "Sampler2D";
LGraphShaderSampler2D.desc = "Reads a pixel from a texture";
LGraphShaderSampler2D.prototype.onGetCode = function( context )
{
var texname = getShaderInputLinkName( this, 0 );
var code;
if(texname)
{
var uvname = getShaderInputLinkName( this, 1 ) || "v_coord";
code = "vec4 " + getShaderOutputLinkName( this, 0 ) + " = texture2D("+texname+","+uvname+");";
}
else
code = "vec4 " + getShaderOutputLinkName( this, 0 ) + " = vec4(0.0);";
context.addCode( "fs_code", code );
}
setShaderNode( LGraphShaderSampler2D );
LiteGraph.registerNodeType( "shader/sampler2D", LGraphShaderSampler2D );
//*********************************
function LGraphShaderFragColor() {
this.addInput("color", "float,vec2,vec3,vec4");
this.block_delete = true;
}
LGraphShaderFragColor.title = "FragColor";
LGraphShaderFragColor.desc = "Pixel final color";
LGraphShaderFragColor.prototype.onGetCode = function( context )
{
var link_name = getShaderInputLinkName( this, 0 );
if(!link_name)
return;
var code = link_name;
var type = this.getInputDataType(0);
if(type == "float")
code = "vec4(" + code + ");";
else if(type == "vec2")
code = "vec4(" + code + ",0.0,1.0);";
else if(type == "vec3")
code = "vec4(" + code + ",1.0);";
context.addCode("fs_code", "color = " + code + ";\n");
}
setShaderNode( LGraphShaderFragColor );
LiteGraph.registerNodeType( "shader/fragcolor", LGraphShaderFragColor );
})(this);