FCurves As Shading Tools…

March 29th, 2007 by Stefano Jannuzzo - Viewed 9355 times -




Let’s look at a short technique that will allow you to export any custom function curve or scripting math function to a texturable node to be used in the rendertree.

Let’s get a curve

We start by taking a poly strip, with a 100×1 subdivisions. This will be our “drawing” table. We then make a cluster of the whole set of points, and give it a weightmap. The range for the weights is set as wide as possible (-100, 100) to allow for both large positive and negative values. We then apply to the grid a custom property with a profile function curve, which you can then shape as you wish.

Here is a script that will do the steps mentioned above.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function CreateGrid()
{
    //create a poly strand, 100x1
    var grid = CreatePrim("Grid", "MeshSurface", null, null);
    SetValue("grid.grid.ulength", 10, null);
    SetValue("grid.grid.vlength", 1, null);
    SetValue("grid.polymsh.geom.subdivu", 100, null);
    SetValue("grid.polymsh.geom.subdivv", 1, null);
 
    //create the cluster
    SelectGeometryComponents("grid.pnt[*]");
    CreateCluster(null);
    //apply a weightmap
    CreateWeightMap(null, null, "Weight_Map", null, null);
    SetValue("grid.polymsh.cls.Point.Weight_Map.red", 0, null);
    SetValue("grid.polymsh.cls.Point.Weight_Map.blue", 0, null);
    SetValue("grid.polymsh.cls.Point.Weight_Map.green", 0, null);
 
    //allow for large min and max weights
    SetValue("grid.polymsh.cls.Point.Weight_Map.weightmapop.wmin", -100, null);
    SetValue("grid.polymsh.cls.Point.Weight_Map.weightmapop.wmax", 100, null);
 
    return grid;
}
 
 
function ApplyProfile(grid)
{
    var oCPSet = grid.AddProperty("CustomProperty", false, "ProfilePSet");
    var oFCParam = oCPSet.AddFCurveParameter("ProfileFC");
 
    var oFCurve = oFCParam.Value;
 
    oFCurve.SetKeys(new Array(0, 0, 0.25, 1, 0.5, -0.5, 0.75, 0.8, 1, 0));
}
 
var grid = CreateGrid();
ApplyProfile(grid);

fm1.jpg

Sampling the curve

This is done by the following script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function SampleFunction(pSet)
{
    var par = pSet.NestedObjects(0);
    var fC = par.value;
 
    var pArray = [0,0];
    var vArray = [0,0];
 
    //fcurve min and max range
    var rangeMin = 0;
    var rangeMax = 1;
 
    var range = rangeMax - rangeMin;
 
    var x, y;
 
    //number of samples, equal to the grid number of points along x
    var nbSamples = 100;
 
    for (i=0; i< =nbSamples; i++)
    {
        //i-th column of points
        pArray[0] = i*2;
        pArray[1] = i*2 + 1;
 
        x = rangeMin + range * (i/nbSamples);
        y = fC.Eval(x); //value of the function at x
 
        vArray[0] = vArray[1] = y; //values for the weight map
 
        //set the weights for the i-th column of points
        PaintWeights("grid.polymsh.cls.Point.Weight_Map", pArray, vArray, 1, 0);
    }
}
 
//select the ProfilePSet first
var profilePSet = selection(0);
SampleFunction(profilePSet);

You just have to take care of setting the current range min and max correctly and then run it with the ProfilePSet. The script will simply sample the curve with the same rate as the grid subdivisions along x. The grid has 101 column of points along the x. We take the corresponding function values and stick them on the weightmap. Note that we’re using 100 samples, but we could have used much more for both the grid and the sampling rate.

Plotting the weightmap

fm2.jpg

We apply a color sampler lightmap shader. The input color to be sampled comes from a scalar lookup of the weightmap.

The writable map must be a hdr one (float .ct is my choice), in order to allow for large positive and negative values to be stored. Only the X resolution matters, since we are basically plotting a mono dimensional texture. The map is applied with a xz projection over the grid. You now just need to fire a tiny render region to have the lighmap written.

Reusing the map

As said, the map is one dimensional, so only the u matters when you look it up. So, if you pick a share vector node and connect it to an image lookup, the x coordinate of the vector acts as the input you are remapping by the original function curve. In this example, the lookup feeds the displacement, being the value to be remapped coming from a standard uv projection over the new grid.

fm3.jpg

What is important is to remember the range the function was originally sampled over, that must match the expected range of the value (the u coordinate in this example) you are going to remap.

Sampling a scripting function

As mentioned, you can also use this approach to paint any math function provided by your scripting function. Instead of using a function curve, you can simply loop and evaluate the desired function.

For instance, this is how you sample and weight paint a sin function in the -pi +pi range:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function SampleSin()
{
    var pArray = [0,0];
    var vArray = [0,0];
 
    var rangeMin = -3.14159;
    var rangeMax = 3.14159;
 
    var range = rangeMax - rangeMin;
 
    var x, y;
 
    var nbSamples = 100;
 
    for (i=0; i< =nbSamples; i++)
    {
        pArray[0] = i*2;
        pArray[1] = i*2 + 1;
 
        x = rangeMin + range * (i/nbSamples);
        y = Math.sin(x); //value of the function at x
 
        vArray[0] = vArray[1] = y; //values for the weight map
 
        PaintWeights("grid.polymsh.cls.Point.Weight_Map", pArray, vArray, 1, 0);
    }
}

3 Responses to “FCurves As Shading Tools…”

  1. Vince Fortin says:

    Very nice technique, Stefano!
    I got to test the speed and see if this can be used dynamically.
    Thanks for the script examples.

  2. Steven Caron says:

    this is clever, thanks for sharing your boundless rendering techniques with us…

  3. Ajlan Altug says:

    Very resourceful! Will most definitely make a note of this technique! Thanx :)