Shader graph and include system

With some added free time over Christmas I’ve been able to pick up something I was playing with much earlier in the (previous!) year: shader graph WIP

There are several dropdown tabs up top and a few of their selections will put a box on screen, like the “Input” and “Output” ones already there. You can drag these boxes around. Further, by dragging off the blue or red nodes on the sides of them, you can make connections with nodes of the other color, assuming one of the boxes is “unresolved” (has “?” nodes). Connections may be broken by clicking the blue dot in the middle of the connecting curves, or by clicking the red dot in a box’s corner, destroying it.

As you build a chain off the “Output” box, shader code will be dumped to the scroll view in the lower-left corner. I’m hoping soon to actually build these shaders and apply them to a rect object, but I want to add a few controls to hand-tune inputs, since right now the chains tend to culminate in default 0 values. :slight_smile:

There is much left to do and quirks abound.

Code for the above is currently staged here.

The node and linking stuff is not new to this project. (This is a major understatement!) I’ve actually been able to synthesize code flow using all of this, akin to what UE4 does. Unfortunately, all of that had gotten hopelessly snarled while sussing out how everything has to work. Adding new stuff (undo-redo, for one thing, or the wildcard resolution you see here) become too complex. This was a reboot of sorts, or at least approaching the problem from another angle. Down the road, I plan to pick up and revamp those things.

Orthogonal to all of that, I’ve also made a small module aimed at reusing snippets of code among shaders, along with a gradually accumulating stable of said snippets. An example, giving a lava lamp-ish effect (slightly adapted from real code, not quite tested in this form):

local includer = require("includer") local iq = require("snippets.noise.iq") local qualifiers = require("snippets.utils.qualifiers") local effect = { language = "glsl", category = "filter", group = "filler", name = "lava\_lamp" } effect.vertexData = { { name = "seed", index = 0, default = 0, min = 0, max = 1023 } } effect.isTimeDependent = true local Code = [[\_POS\_ vec2 GetPosition (\_UV\_ float epoch) { \_POS\_ vec2 p = vec2(epoch) + vec2(0., -3.1); return vec2(IQ(p.xy), IQ(p.yx)); } \_UV\_ float GetContribution (\_UV\_ vec2 uv, P\_UV float epoch) { \_POS\_ vec2 p = GetPosition(epoch); return smoothstep(.375, 0., distance(p, uv)); } P\_COLOR vec4 FragmentKernel (P\_UV vec2 uv) { \_UV\_ float epoch = (CoronaVertexUserData.x + CoronaTotalTime) / 2., contrib = 0.; contrib += GetContribution(uv, epoch) \* .125; contrib += GetContribution(uv, epoch + 1.) \* .125; contrib += GetContribution(uv, epoch + 2.) \* .125; P\_COLOR vec4 color = CoronaColorScale(texture2D(CoronaSampler0, uv + contrib \* .00625)); color.rgb \*= pow(.87 + contrib, 1.43); return min(color, vec4(1.)); }]] Code = Code:gsub("\_POS\_", qualifiers.DefaultPrecisionOr("P\_POSITION")) Code = Code:gsub("\_UV\_", qualifiers.DefaultPrecisionOr("P\_UV")) includer.AugmentKernels({ requires = { iq.IQ1 }, fragment = Code }, effect) graphics.defineEffect(effect) local effect\_name = "filter.filler.lava\_lamp" local rect = display.newRect(display.contentCenterX, display.contentCenterY, 300, 300) rect:setFillColor(0, 0, 1) rect.fill.effect = effect\_name

A future goal will be to make these two ideas play together, say offering ways to import snippets into the shader graph and providing integration hints from those snippets.

Awesome!  I’ve had unreliable results getting filters and composites to play nicely together in multi-pass shading - often getting unpredictable results.  I’m wondering if this project might potentially hold a solution for that problem?

Here is an example of a multi-pass shader kernel with a composite and a filter that works reliably and predictable for me.  I’m not sure why some other combinations don’t work as problems with the shader module cause the simulator to crash with no explanation.

[lua]

– multi-pass shader

local kernel = {}

kernel.language = “glsl”

kernel.category = “composite”

kernel.name = “normalBloom”

kernel.graph =

{

  nodes = {

    lightNormal = { effect=“composite.normalMapWith1DirLight”, input1=“paint1”, input2=“paint2” },           

    bloom       = {effect = “filter.bloom”, input1=“lightNormal”}, 

  },

 output = “bloom”,

}

print( "Loading kernel . . . ", kernel.name )

return kernel

[/lua]

Keep us posted on your project.  I’d like to play around with it in the future.

Thanks!

Truth to tell, I’m not sure how multi-pass will ultimately be integrated, but then that’s the case with many of the remaining steps.  :slight_smile:

The shader machinery used by defineEffect () involves a separate Lua state. I think it’s not set up quite as robustly, so some errors can go uncaught and unhandled, thus bringing down the simulator. I’ve noticed this after accidentally adding wrong table fields. Unless something esoteric is actually going on, I imagine the needed fixes would be pretty basic.