Slim Custom Nodes

Slim is a node-base shader developing environment, dedicated to Renderman. It resembles Hypershade to some extends, but it's integrity with Renderman Shading Language (RSL) empowers artists to create and manipulate custom nodes. It this page I tried to create some image manipulation filters using TCL scripting and RSL.


Interface

Slim interface consists of three main sections:
The leftmost part is the Palette View which is for viewing and managing palettes and packages.
The middle section is the Network View. Here we can create, manipulate, connect and arrange nodes and shaders.
The rightmost part is the Appearance View which enables us to see and manipulate parameters of the selected node.


Writing Custom Nodes



One of the benefits of Slim which makes it flexible for the artists, is that if gives the users the ability of writing custom nodes. "Slim" files are written in TCL (Tool Command Language) and consist of two main parts: The first (or the top) part is for designing the interface of the node and the second (or the bottom) part is the main RSL function. The RSL function is mainly a void function with one output argument the type of which varies depending on the node we are writing. The important point is the synchronization of top part with the bottom part in terms of the order of the parameters.

My Very Own Slim Node!

I decided to write a color node which behaves like some of the Photoshop filters. One of the most user-friendly tools for writing tcl codes is Cutter by Prof. Malcolm Kesson. The changes on the node could be reflected into Slim instantly just by executing the code in Cutter. You can find the code on the left. This node consists of three sections:
Color Manipulation, Pixelation and Quantization.


The Slim Code:


slim 1 extensions cutter {
    extensions fundza ali {
        template color ColorFilter {
        previewinfo {
            shadingrate 1
            objectsize 1
            objectshape Plane
            frame 1
            }
        #__custom parameters Begin____________
        collection void ColorFilter {
        
            state open
            drawmode all
            label {Color Filter}
            description {Color Filter}
        
        
        parameter string tex_name {
            label "Texture Label"
            description "Texture Map"
            provider variable
            subtype texture
            default "/home/aseiff20/mount/stuhome/maya/projects/default/textures/man.tex"
            }
        
        parameter float useMap {
            label "Use Texture Map"
            description "No description."
            subtype switch
            range {0.0 1.0}
            provider variable
            default 1
            }
  
  
        parameter color input {
            label "Input Color"
            description "No description."
            default {.9 .5 .1}
            detail varying
            }
            
        collection void colorManip {
            state open
            drawmode all
            label {Color Manipulation}
            description {Color Manipulation}
            
            
        parameter float hue {
            label "Hue Shift"
            description "Hue Shift"
            subtype slider
            range {0 1}
            detail varying
            default 0
            }
            
        parameter float rShift {
            label "Red Shift"
            description "Red Shift"
            subtype slider
            range {0 1}
            detail varying
            default 0
            }
  
        parameter float gShift {
            label "Green Shift"
            description "Green Shift"
            subtype slider
            range {0 1}
            detail varying
            default 0
            }        
            
        parameter float bShift {
            label "Blue Shift"
            description "Blue Shift"
            subtype slider
            range {0 1}
            detail varying
            default 0
            }    
                    
        parameter float sat {
            label "Saturation"
            description "Saturation"
            subtype slider
            range {0 1}
            detail varying
            default 1
            }
            
        parameter float rSat {
            label "Red Intensity"
            description "Red Intensity"
            subtype slider
            range {0 1}
            detail varying
            default 1
            }    
                    
        parameter float gSat {
            label "Green Intensity"
            description "Green Intensity"
            subtype slider
            range {0 1}
            detail varying
            default 1
            }    
                    
        parameter float bSat {
            label "Bue Intensity"
            description "Blue Intensity"
            subtype slider
            range {0 1}
            detail varying
            default 1
            }
            
        parameter float gray {
            label "Grayscale"
            description "Grayscale"
            subtype switch
            range {0.0 1.0}
            provider variable
            default 0
            }
        }
        
        collection void pixelation {
            state open
            drawmode all
            label {Pixalation}
            description {Pixalation}
            
            
        parameter float pixelate {
            label "Pixelate"
            description "Pixelate"
            subtype switch
            range {0.0 1.0}
            provider variable
            default 0
            }
            
        parameter float rows {
            label "Rows"
            description "Rows"
            subtype slider
            range {2 100 1}
            detail varying
            default 10
            }
            
        parameter float cols {
            label "Columns"
            description "Columns"
            subtype slider
            range {2 100 1}
            detail varying
            default 10
            }
            
        parameter float jitter {
            label "Jitter"
            description "Jitter"
            subtype slider
            range {0 1}
            detail varying
            default 0.75
            }
            
        parameter float seed {
            label "Seed"
            description "Seed"
            subtype slider
            range {0 10000 1}
            detail varying
            default 1234
            }
        
        parameter float gapWidth {
            label "Cell Border Width"
            description "Cell Border Width"
            subtype slider
            range {0 1}
            detail varying
            default 0.05
            }
  
        parameter color gapColor {
            label "Gap Color"
            description "Gap Color"
            default {0 0 0}
            detail varying
            }
        
        parameter float gapOpacity {
            label "Gap Opacity"
            description "Gap Opacity"
            subtype slider
            range {0 1}
            detail varying
            default 1
            }
  
            }
        
        
        
        collection void quantization {
        
            state open
            drawmode all
            label {Quantization}
            description {Quantization}
        
        
        parameter float quantize {
            label "Quantize"
            description "Quantize"
            subtype switch
            range {0.0 1.0}
            provider variable
            default 0
            }
        
  
        parameter float steps {
            label "Quantization Steps"
            description "Quantization Steps"
            subtype slider
            range {2 20 1}
            detail varying
            default 5
            }
        }    
    }
            
            
  
  
        #__custom parameters End______________
        
# =======================================================================
  
        parameter color result {
            access output
            display hidden
            }
        RSLFunction {
        
    void voronoi(    
            uniform    float    numS, numT;
                    float    jitter;
                    float    seed;
            output    float    ss, tt;
            output    float    border;
                )
    
    {
        float SS = ss * numS;
        float TT = tt * numT;
    
        float sthiscell = floor(SS)+0.5;
        float tthiscell = floor(TT)+0.5;
        
        float f1 = 10000;
        float f2 = 10000;
        
        uniform float i, j;
        
        for (i = -1;  i <= 1;  i += 1) 
        {
            float stestcell = sthiscell + i;    
    
            for (j = -1;  j <= 1;  j += 1) 
            {
                float ttestcell = tthiscell + j;
                
                float spos = stestcell + jitter * (cellnoise(stestcell + seed, ttestcell + seed) - 0.5);
                float tpos = ttestcell + jitter * (cellnoise(stestcell + seed + 23, ttestcell + seed - 87) - 0.5);
                
                float soffset = spos - SS;
                float toffset = tpos - TT;
                
                float dist = soffset*soffset + toffset*toffset;
                
                if (dist < f1) 
                {
                    f2 = f1;
                    f1 = dist;
                    ss =  spos/numS;
                    tt =  tpos/numT;
                } 
                else if (dist < f2)
                {
                    f2 = dist;
                }
                
            } // j loop
            
        } // i loop
    
        border = sqrt(f2) - sqrt(f1);
    
    }
    
    void aliColorFilter (
        
            string    tex_name;
            float    useMap;
            color    input;
            float    hue, rShift, gShift, bShift;
            float    sat, rSat, gSat, bSat;
            float    gray;            
            
    uniform float    pixelate;        
    uniform    float    rows;
    uniform    float    cols;
    uniform float    jitter;
    uniform float    seed;
    uniform float    gapWidth;
    uniform color    gapColor;
    uniform float    gapOpacity;            
            
            float    quantize;
            float    steps;
  
    output color    result;
            )
            
        {
        
        color vorColor;
        
        float r = input[0];
        float g = input[1];
        float b = input[2];
        
        
        if (useMap == 1)
        {
            if (tex_name != "")
            {
                r = texture(tex_name[0]);    
                g = texture(tex_name[1]);
                b = texture(tex_name[2]);
            }
                
            
            if (pixelate == 1)
                {
                    float border;
                    color C1, C2;
                
                    float ss = s;
                    float tt = t;
                    
                    voronoi (rows, cols, jitter, seed, ss, tt, border);
                    ss = clamp(abs(ss),0,1);
                    tt = clamp(abs(tt),0,1);
                    
                    C1 = texture (tex_name, ss, tt);
                    float gap = smoothstep (gapWidth-0.01, gapWidth, border);
                    C2 = C1;
                    C1 = mix(gapColor, C1, gap);
                    C1 = mix(C2, C1, gapOpacity);
                    
                    r = comp(C1, 0);
                    g = comp(C1, 1);
                    b = comp(C1, 2);
  
                }            
            }    
  
        color CRGB = color(r,g,b);
        color CHSV = ctransform("rgb", "hsv", CRGB);
        
        float hh = comp (CHSV, 0);
        float ss = comp (CHSV, 1);
        float vv = comp (CHSV, 2);
        
  
        hh = mod (hh + hue, 1);
        ss = ss * sat;
        
        
        CRGB = color(hh, ss, vv);
        CRGB = ctransform("hsv", "rgb", CRGB);    
        
        if (r == 1 && rSat == 1) {r = CRGB[0];}
        else {r = mod(CRGB[0] + rShift, 1) * rSat;}
        
        if (g == 1 && gSat == 1) {g = CRGB[1];}
        else {g = mod(CRGB[1] + gShift, 1) * gSat;}
        
        if (b == 1 && bSat == 1) {b = CRGB[2];}
        else {b = mod(CRGB[2] + bShift, 1) * bSat;}
        
  
        
  
        float w = (r + g + b) / 3;
        float scale = steps - 1;
  
        if (quantize == 1)
        {
            r = round(scale * r)/(scale);
            g = round(scale * g)/(scale);
            b = round(scale * b)/(scale);
            w = round(scale * w)/(scale);
        }
        
        if (gray == 1)
        {
            result = color(w,w,w);
        }
        else
        {
            result = color(r,g,b);
        }
    }
}
}}}

In the Color Manipulation section, I embedded Hue Shift and Saturation for the whole colors and also for each color channel separately. The key is reading the color and converting it from RGB into HSV. For Hue shift I used the mod() function to confine the hue value to 0 to 1. After doing the manipulation I converted the colors back to RGB and output the result.




The Pixelation was the most challenging part for me. I wanted to use Voronoi diagram to give more variety to the filter, because in a special case of Voronoi which the feature points are located on a regular grid, the diagram turns into a usual pixelated image. So how does the filter work?


Basically we need a bunch of points scattered on the surface. When shading a surface, each point is shaded completely isolated from other points; therefore we have no access to other shading points. So we need to find a way to assign one single coordinated to a group of points. noise() function could not be very helpful because it varies from point to point. But cellnoise() offers a great solution: it returns one single random number to any number between two integers, i.e. it quantizes the space. Imaging a space diced into 1x1x1 cubes or in our case a 1x1 square. For all of the points inside that region we need one single random point which would be our feature. Then by multiplying the number of cells, we can have more features.


After dicing the space we need to test which of 9 features around each shaded point is the nearest. We need two nested loops to go through each surrounding feature and compare the distance to the previous one. For the first feature we just need to compare it to a very large initial number. In order to get a better and more precise result we can test the two nearest features and compare their distances. The output parameters of our Voronoi function are the coordinates of the nearest feature points and the borders of the cell. Then later on in the body of our main function we can use the s and t outputs of Voronoi to choose the colors of our texture for the whole cell. Easy, yea?

The third part of the filter is Color Quantization. An easy approach that I took to avoid complicated calculations was multiplying the whole space of each color component to the number of the steps, rounding up the value and shrinking down the space back into 0 to 1 value.