PDA

View Full Version : color array inputs and evaluation


bart
November 28th, 2006, 03:50
Ever want to have a color array as an input, but be able to evaluate a shader graph as one of the color slots in the array?

For example, say you were combining separate components of illumination, and wanted to sum some arbitrary number of them together for output into your primary framebuffer. (Say each component was passed through a user framebuffer before entering the combining shader.)

Anyway, here is a simple example of how to evaluate each color separately in a color array input. I have an add_colors shader which takes in just a single input, an array of colors. The declaration might look like this:

declare shader
color "add_colors" (
array color "colors"
)
version 1
end declare

Now, the loop which iterates on the colors has to be careful in its setup, so that each slot in the array will do a separate mi_eval_color. Here is the code for the shader:

struct add_colors {
int i_colors;
int n_colors;
miColor colors[1];
};


miBoolean add_colors(
miColor *result, miState *state, struct add_colors *params)
{
int n_colors = *mi_eval_integer(&params->n_colors);
int i_colors = *mi_eval_integer(&params->i_colors);
miColor *color;
int i;


for &#40;i = 0; i < n_colors; i++&#41; &#123;
color = mi_eval_color&#40;params->colors + i_colors + i&#41;;

result->r += color->r;
result->g += color->g;
result->b += color->b;
&#125;
return miTRUE;
&#125;


Note that I'm ignoring alpha in this simple example. Also, I'm taking advantage of the fact that inside mental ray my allocation is done automatically when using mi_eval_color. If I wanted to create a copy of a color array, I would have to allocate some memory.

bart
November 28th, 2006, 03:54
A cool thing about this is that you can now create Phenomena that can have an explicit number of args for inputs, but reuse just the one actual shader. For example:

declare phenomenon
color "add_2_colors" &#40;color "color1" default 0 0 0 1,
color "color2" default 0 0 0 1
&#41;
shader "sum"
"add_colors" &#40;
"colors"
&#91; = interface "color1",
= interface "color2"
&#93;
&#41;
root = "sum"
version 1
apply texture, material
end declare

declare phenomenon
color "add_3_colors" &#40;color "color1" default 0 0 0 1,
color "color2" default 0 0 0 1,
color "color3" default 0 0 0 1
&#41;
shader "sum"
"add_colors" &#40;
"colors"
&#91; = interface "color1",
= interface "color2",
= interface "color3"
&#93;
&#41;
root = "sum"
version 1
apply texture, material
end declare

bart
February 6th, 2007, 23:15
Made this sticky, because I think it is so important with respect to making clean component shader libraries.

Ian_
May 7th, 2009, 09:09
Hello, since my questions are closely related to your example, I hope it is still ok to ask it here, if not I'm sorry. Pretty much I don't really understand where and how each item in the array are "added" together, and I wanted to know how you would have all the items calculated with different expressions. Like for example, having the result from subtraction, or a multiplication of every item, or having the result be an average of all the items.Another example would be to have a result of the first item raised to the power of the next item, and so on.

While trying to test subtraction, I have tried changing the result section from-
result->r += color->r;
result->g += color->g;
result->b += color->b;
to-
result->r -= color->r;
result->g -= color->g;
result->b -= color->b;
Although this just seems to make a totally black shader. And I wouldn't even know where to start with using a pow() statement, or doing an average. Although maybe an average would simply be-
result->r += color->r / n_colors;
result->g += color->g / n_colors;
result->b += color->b / n_colors;


So pretty much what I am trying to achieve is different types of layering arrays. And when I decide on all of the different expressions I want to include, maybe I could use another array integers called of "modes" for each layer,where each modes item has its own switch statement. Although how I'll achieve either of these, is beyond me right now...

bart
May 7th, 2009, 15:14
Although a layer version of this is fully do-able, I would highly suggest to make separate components for different operations.

I have come to this conclusion after teaching about shader networks, because it is much faster to recognize on visual inspection how the network combines shader nodes. With a mode input,it is harder to recognize what the shader is actually doing, and thus, harder to work with a team.

Now, for more clarity you don't have to use += or -=, for each component, you could do
result = result + added_value, or
result = result - subtracted_value, then if result < 0, result = 0, or similar to check for bounds

But what does subtract really mean for you, are all the array elements subtracted, or is there a notion of a first base element that is not?

For average, I'd divide the sum by n_colors after adding all the elements, for clarity.

bart
May 7th, 2009, 15:20
And regarding bounds checking, remember that with modern color and lighting pipelines, we use component values greater than one.

bart
May 7th, 2009, 15:28
Another thing to note, is that this shader could be used in a shader list.

If result already has something in it from a previous shader in a shader list, then the elements would be added to that.

Shader lists are not typically supported by the 3D applications for material shaders, but if you put them in a Phenomena, it will work in the 3D application.

Ian_
May 7th, 2009, 20:39
Hello, bart, thanks for your replies.:) I do agree that it is hard to see what the shader is doing from visual inspection, that is why it is nice to be able to check the out of range value at a certain pixel, like mental mill can do on each node. For example-
http://forum.mentalimages.com/attachment.php?attachmentid=1088&stc=1&d=1241725961
I will have to test your solution instead of using +=. To clarify, I did mean to have the first array element, or "color1" to be the start of the calculation, so if there were 3 colors it would be color1 - color2 - color3, and for raising to the power, pow(color1, pow(color2, color3)),.Also, how would the average look like when the sum is divided afterward? Would it be-
result = (result + added_value) / n_colors;
or could you write-
result = (result + added_value);
result = result / n_colors;
Thanks again for all your help. :)

Ian_
May 7th, 2009, 21:33
Ok, your suggestion seams to work, but I have to do it for each component-
result->r = result->r + color->r;
result->g = result->g + color->g;
result->b = result->b + color->b;
result->a = result->a + color->a;
If I just use result = result + color; the compiler gives an error saying that you can't add 2 pointers, and if I use - or * instead of + it says-
error C2440: '=' : cannot convert from 'int' to 'miColor *'
Here is the main code-
int n_colors = *mi_eval_integer(&paras->n_colors);
int i_colors = *mi_eval_integer(&paras->i_colors);
miColor *color;
int i;
for (i = 0; i < n_colors; i++) {
color = mi_eval_color(paras->colors + i_colors + i);
result = result - color;
}
return(miTRUE);
}
}
Also with subtracting color from result, it does seem start with 0, that's why visually, you can tell that using + does anything, whereas multiplying results in 0 and subtraction also starts with 0. So I do need a way of starting from the first color item.

bart
May 7th, 2009, 22:57
Sorry about leaving out the components, I was just giving a psuedo-code example, not spelling it out exactly. Typically, I'd make a code block if I meant it to be compiled. Also, I try not to put in a code block, I haven't compiled. Also, I'm leaving out the DLLEXPORT needed for Windows compiling. These following compile, for example:

miBoolean average_colors(
miColor *result, miState *state, struct average_colors *params)
{
int n_colors = *mi_eval_integer(&params->n_colors);
int i_colors = *mi_eval_integer(&params->i_colors);
miColor *color;
int i;

for (i = 0; i < n_colors; i++) {
color = mi_eval_color(params->colors + i_colors + i);

result->r += color->r;
result->g += color->g;
result->b += color->b;
}
result->r /= n_colors;
result->g /= n_colors;
result->b /= n_colors;

return miTRUE;
}

Note that I divided after the summing loop.

miBoolean multiply_colors(
miColor *result, miState *state, struct multiply_colors *params)
{
int n_colors = *mi_eval_integer(&params->n_colors);
int i_colors = *mi_eval_integer(&params->i_colors);
miColor *color;
int i;

for (i = 0; i < n_colors; i++) {
color = mi_eval_color(params->colors + i_colors + i);

result->r *= color->r;
result->g *= color->g;
result->b *= color->b;
}
return miTRUE;
}

bart
May 7th, 2009, 23:05
For subtraction, I'd like to ask you how you envision using it in a shader graph. Please give me a practical example. Then, from that, it leads to how you'd implement it.

In a shader list, a positive result could have been passed in through the result color, as opposed to all 0s, when it is not a shader list.

I'd label my non-subtract input base_color, instead of color1, or similar. But if it is the first item in the array, rather than a separate input, then you'd initialize to the first element and start the loop from 1 instead of 0, ie

color = mi_eval_color(params->colors + i_colors)
result->r += color->r;
result->g += color->g;
result->b += color->b;

for (i = 1; i < n_colors; i++) {
color = mi_eval_color(params->colors + i_colors + i);

result->r -= color->r;
result->g -= color->g;
result->b -= color->b;
}


Also, you could adjust the naming in the Phenomena.

bart
May 7th, 2009, 23:13
As for the power function, I don't think I can imagine where having an array of colors beyond two would be clear in a shader graph.

Ian_
May 8th, 2009, 07:47
Thanks so much bart, for walking me through this, I didn't expect arrays to be that tricky, since I mainly knew how to write metaSL decently, and some hlsl. As far as a use for a subtractive array, I really can't think of a specific use other than a layered shader that might need to subtract a bunch of textures that are inverted, so you would use subtract instead of multiply. There's probably someone that would eventually need a phenomenon with 50 color or float inputs.:) Really, I think it would be nice to have a huge library of shaders that has every node or shader that would need, even if you wouldn't use every single thing, its just nice to have. I would like to eventually have such a library for MR and in metaSL.

Ian_
May 8th, 2009, 09:07
Thanks again bart, works like a charm! :D I think I probably will keep with the first item in the array, instead of using a separate input, for the base color, (which I did come to realize I could just use a separate input instead of trying to figure out how to use the first item), and I'll just rename it in phenomenon to "Base Color" or "Base Float", as I would like to have math shaders with different data types..

BTW, one last question, is it better for memory and rendering performance, to have 3 dll's with one shader in each, or is it better to have one dll with 3 shaders, or option 3, if the 3 shaders are very similar, is it better to have the 3 shaders contained in one, where you could have a switch statement to change between the 3?

bart
May 8th, 2009, 15:33
No significant difference regarding memory and performance, however, I'd suggest one dll with respect to asset management and maintenance. From a software management perspective, you might want to keep the c files separate, but link them all together into the same dll, using the same mi declaration file.

As I mentioned earlier, make your code clear and simple. The more you combine, the more you may be making it harder for others (or even yourself after some time as passed) to understand quickly.

Even if you make a combination multi-purpose layer shader, I'd have separate Phenomena, so that it is very clear for look dev artists using it in shader graphs.

bart
May 8th, 2009, 15:36
BTW, I'd give this same advice whether you used C shaders or MetaSL. This is common production sense from a management perspective. It doesn't matter how fast or effective something is, if it is hard or unclear to use it correctly. People time trumps render time. :)