Cycles in the shader graph


bbannayan@...
 

Consider the following:

 

Shader A has parameters: diffdiff_tex, and diff_value

Shader B has parameters: valuetex, and output

 

I'd like to connect in this way:

 

A.diffuse -> B.value

A.diffuse_tex -> B.tex

 

B.output -> A.diffuse_value

 

The A parameters are not "outputs" from the shader.  They are merely parameters with values that I'd like carried forward to B so that the output can be calculated and sent back to A.

 

The OSL code of A references only the diff_value parameter so there's no actual cycle when you get down to the final evaluation.

 

Something like this:

surface

A(float diff = 1,

  string diff_tex = "",

  float diff_value = 1)

{

  Ci = diff_value * diffuse(N);

}

 

 

shader

B(float value = 1,

  string tex = "",

  output float out = 1,

  float st[2] = { 0.0, 0.0 })

{

  out = value * texture(tex, st[0], st[1]);

}

 

The idea for me is that I can have the B shader doing some work but I only ever have to interact with the parameters of A when editing my scene (I'm doing this in a renderer that allows modifying the parameters of shaders during a render).  In my particular case A represents a node in my DCC scene and B is a convenience/implementation detail. So when my DCC says "A.diff" has been changed I can change "A.diff" in my render scene without having to figure out if B exists.

 


Larry Gritz
 

So, underneath, it's really all flattened to just a simple list of shaders in the group (that's why we sometimes call them "layers"). Connections can only go from outputs of earlier numbered layers to inputs of later numbered layers. Thus, there are no cycles in the graph.

I think I understand what you're getting at here. You don't need the shader to execute cyclically, you just want to, essentially, make a single value supply both an input to one shader node and also supply the default value of the output of another shader (which happens to be later in the layer list).

But you're imaging kind of a special case where it's all simple. In general, there are a lot of complications. For one thing, the value of output parameters (even for their default values) aren't stored in those variables until the node runs. And some parameters get their values in particularly complicated ways (like interpolated vertex values). Also, the order that nodes are evaluated more complex than the flat list implies, because we actually do it lazily (layers are run out of order, if and when only when their results are needed). So it would be a lot of additional logical complexity to allow what you're suggesting -- and ensuring it was correct for all these corner cases.

It feels like... maybe your problem can be handled outside of OSL, at the level where you are specifying the shader group to OSL in the first place? You want the same value to end up a few different places, so maybe there's some logic in the material assignment or something where it can ensure that this happens, without needing to explicitly tell OSL that these nodes are connected in that odd way?

-- lg


On Aug 16, 2021, at 2:35 AM, bbannayan@... wrote:

Consider the following:

 

Shader A has parameters: diffdiff_tex, and diff_value

Shader B has parameters: valuetex, and output

 

I'd like to connect in this way:

 

A.diffuse -> B.value

A.diffuse_tex -> B.tex

 

B.output -> A.diffuse_value

 

The A parameters are not "outputs" from the shader.  They are merely parameters with values that I'd like carried forward to B so that the output can be calculated and sent back to A.

 

The OSL code of A references only the diff_value parameter so there's no actual cycle when you get down to the final evaluation.

 

Something like this:

surface

A(float diff = 1,

  string diff_tex = "",

  float diff_value = 1)

{

  Ci = diff_value * diffuse(N);

}

 
 

shader

B(float value = 1,

  string tex = "",

  output float out = 1,

  float st[2] = { 0.0, 0.0 })

{

  out = value * texture(tex, st[0], st[1]);

}

 

The idea for me is that I can have the B shader doing some work but I only ever have to interact with the parameters of A when editing my scene (I'm doing this in a renderer that allows modifying the parameters of shaders during a render).  In my particular case A represents a node in my DCC scene and B is a convenience/implementation detail. So when my DCC says "A.diff" has been changed I can change "A.diff" in my render scene without having to figure out if B exists.

 

--
Larry Gritz





bbannayan@...
 

This definitely can be handled on the DCC end but it's cumbersome to do so since I'm working in a context where I will be making continuous edits and these "utility" nodes may or may not exist or have different types/parameters depending on the context.  I was hoping to find a way to output the graph once but interact with the parameters in only one place (the OSL node that is one-to-one with a Maya node, in this case) and let the OSL graph sort out the rest.

After further reflection and discussion I think this may be related to Zap's request for callable shaders but in a far more constrained form.  What I'm trying to do is offload some small blocks of work to other shaders.  I *could* (currently do) use includes and directly called functions for this work but every time these utility shaders change it requires compiling everything.

What I really want to do is to be able to call my little utility shaders from inside another shader and have the oso loaded and executed at that time.

The big difference with what I'm doing vs what Zap was proposing is that that there's no need of "globals".. everything needed by the shader/function is passed in. (though maybe I'm making undue assumptions about what happens behind the scenes in a "texture()" call?).

 

So my earlier example could be reduced to something like this.

surface

A(float diff = 1,
  string diff_tex = "",
  float st[2] = { 0.0, 0.0 })
)

{

  float diff_value;

  execute("B.oso", value, diff_tex, diff_value, st);

  Ci = diff_value * diffuse(N);

}

 
 

shader

B(float value = 1,

  string tex = "",

  output float out = 1,

  float st[2] = { 0.0, 0.0 })

{

  out = value * texture(tex, st[0], st[1]);

}

Basically a callable shader that's outside the node graph.  Just loaded and executed right there with only the data that's passed in.  The main downside is that now my choice of the "B.oso" to execute is hard coded in my shader rather than my being able to select it at scene translation time, though there are definitely ways around this too.

 

I'm not sure that this side steps any of the technical issues you outline.. but it's definitely a different working method that takes the problem away from the scene graph.

Berj


On Tue, Aug 17, 2021 at 11:21 PM, Larry Gritz wrote:

So, underneath, it's really all flattened to just a simple list of shaders in the group (that's why we sometimes call them "layers"). Connections can only go from outputs of earlier numbered layers to inputs of later numbered layers. Thus, there are no cycles in the graph.
 
I think I understand what you're getting at here. You don't need the shader to execute cyclically, you just want to, essentially, make a single value supply both an input to one shader node and also supply the default value of the output of another shader (which happens to be later in the layer list).
 
But you're imaging kind of a special case where it's all simple. In general, there are a lot of complications. For one thing, the value of output parameters (even for their default values) aren't stored in those variables until the node runs. And some parameters get their values in particularly complicated ways (like interpolated vertex values). Also, the order that nodes are evaluated more complex than the flat list implies, because we actually do it lazily (layers are run out of order, if and when only when their results are needed). So it would be a lot of additional logical complexity to allow what you're suggesting -- and ensuring it was correct for all these corner cases.
 
It feels like... maybe your problem can be handled outside of OSL, at the level where you are specifying the shader group to OSL in the first place? You want the same value to end up a few different places, so maybe there's some logic in the material assignment or something where it can ensure that this happens, without needing to explicitly tell OSL that these nodes are connected in that odd way?
 
-- lg