RSL2
Soft Sticky Blobby Noise


return to main index



Introduction

To enable patterns based on noise to "stick" to the ellipsoids of a Blobby, shaders can access a parameter named Pref that contains a copy of the standard P global variable transformed by a user supplied matrix. For example, in rib we might define a volumetric Blobby and its associated Pref matrices as follows.

    Blobby 3
        [8
         1001 0
         1001 16
         1001 32
         0 3   0 1 2]
         [1 0 0 0  0 1 0 0  0 0 1 0  0.5  0.2  0.0 1
          1 0 0 0  0 1 0 0  0 0 1 0  0.9  0.6  0.0 1
          1 0 0 0  0 1 0 0  0 0 1 0  0.5 -0.5  0.0 1]
         [""]
         # Generally the Pref matrices WILL NOT 
         # match the ellipsoid transforms.
         "vertex mpoint Pref"
         [1 0 0 0  0 1 0 0  0 0 1 0   0 0 0 1
          1 0 0 0  0 1 0 0  0 0 1 0   0 0 0 1
          1 0 0 0  0 1 0 0  0 0 1 0   0 0 0 1]

Although the matrices of the blobs will change during an animation, say a Blobby driven by a Maya particle system, the values of the vertex mpoint Pref matrices generally remain constant. For convenience they are shown above as indentity matrices. Detailed nformation about this topic can be found in Pixar's Application Note #31.

A shader assigned to the Blobby might use Pref as follows.


    class
    no_swimming(varying point Pref = 0) {
  
        float fw = max(sqrt(area(P)), (2e-6));
        float ns = wnoise(Pref, fw, "octaves", 5, 
                                    "lacunarity", 1.8);
        ns = (1 + ns) * 0.5;
        Oi = ns * VolumeField;
        Ci = Oi * color(0,0,0);
        }
    }

As shown in figure 1 the two moving ellipsoids do not exhibit swimming noise patterns. However, once they begin to blend with the stationary ellipsoid their patterns show a considerable amount of stretching.


Figure 1


Soft Noise Shader

The "soft noise" shader presented next follows a similar approach to PRMan's built-in support for reference coordinate systems (aka Pref) but without supplying a "vertex mpoint Pref" primvar. Instead, the shader reads two user attributes, namely, "matrices" and "tumble".

    AttributeBegin
        # Generally these matrices MUST match the
        # ellipsoid transforms of the Blobby.
        Attribute "user" "matrix[3] matrices" [
                 1 0 0 0  0 1 0 0  0 0 1 0  0  0.75 0 1
                 1 0 0 0  0 1 0 0  0 0 1 0  0  0.0  0 1
                 1 0 0 0  0 1 0 0  0 0 1 0  0 -0.75 0 1]
        # Arbitary x,y,z rotations that tumble the noise
        # pattern for each ellipsoid. These values generally 
        # remain constant during and animation.
        Attribute "user" "float[9] tumble" [
                20 -30 200
                230 150 30
                166 88 180]
        
        Surface "softnoise" "float maxdist" 1.0 "float Kd" -1
        #Surface "no_swimming"
        Blobby 3
            [8
             1001 0
             1001 16
             1001 32
             0 3   0 1 2]
            [1 0 0 0  0 1 0 0  0 0 1 0  0  0.75 0 1
             1 0 0 0  0 1 0 0  0 0 1 0  0  0.0  0 1
             1 0 0 0  0 1 0 0  0 0 1 0  0 -0.75 0 1]
            [""]
    AttributeEnd

Figure 2 shows the shader also exhibits sticky patterns but with far less distortion of the pattern when the ellipsoids blend. Because the shader applies unique tumble angles to the "matrices" data before calculating the noise pattern it avoids the "cloned" look seen in figure 1.

Figure 2


Soft Noise Algorithm

The shader relies on receiving an array of matrices via a user attribute named "matrices" (during an animation the values of the user attribute will match the changing values of the ellipsoid transformations). The shader also receives an array of floats from a user attribute called "tumble".

The construct() method,
    - converts the "tumble" input array to three arrays named xrot,yrot and zrot
    - makes a matrix representing the Blobby's current transformation ("shader" space),
    - uses the "matrices" and "tumble" data to create instances of the Matrix struct.

Instance of the Matrix struct use their copies of the "matrices" and "tumble" data to,
    - extract the matrix xyz position as a point transformed into "shader" space.
    - rotate the matrix about its origin,
    - "undo" PRMan's implicit "camera" space conversion of the matrix,
    - calculate the average scaling factor of the matrix.

The surface() method loops over the array of Matrix instances and for each it,
    - finds its distance ("dist") to shading point P,
    - ignores instances whose "dist" is greater than "maxdist",
    - transforms a copy of P transformed into matrix space,
    - calculates, and accumulates, a noise value scaled by "dist",

The accumated noise value is used to set Oi and Ci. Because the construct() method is invoked only once per Blobby the cost of creating instances of the Matrix struct is relatively low. Most of the workload is incurred in the surface() method when all the instances of Matrix must be tested for proximity to the shading point P.


Listing 1 (softnoise.sl)


// Instances of the Matrix struct receive a copy of the transformation 
// matrix read from the "matrices" user attribute. 
struct Matrix {
    uniform matrix   mat = 1;
    uniform point    origin = 0; // xyz translations of mat
    uniform float    aveScale = 1;
  
    void init(uniform matrix original; 
              uniform float xrot, yrot, zrot;
              uniform matrix shader_space) {
        // Extract the xyz translation values from the original matrix
        // and create a point transformed to "shader" space. Use the
        // new xyz values to reset our copy of the matrix.
        uniform float x = comp(original,3,0);
        uniform float y = comp(original,3,1);
        uniform float z = comp(original,3,2);
        origin = transform("world", "shader", point(x,y,z));
        
        // Apply the angle offsets - they "tumble" the matrix about its 
        // origin so that noise values derived from P transformed by the 
        // matrix do not replicate noise values generated by other matrices
        // of the same scale.
        uniform vector vec = vector(x,y,z);
        mat = translate(original, -vec);
        mat = rotate(mat, xrot * 2 * PI, vector(1,0,0) );
        mat = rotate(mat, yrot * 2 * PI, vector(0,1,0) );
        mat = rotate(mat, zrot * 2 * PI, vector(0,0,1) );            
        mat = translate(mat, vec);
        
        // When the original matrix data was read from the rib stream PRMan
        // performs an implicit conversion to "camera" space. Here we "undo" 
        // the conversion.
        mat = shader_space/mat;
        
        // Noise values derived from P transformed by our matrix will be weighted
        // by the distance of the matrix origin to P. The weighting diminishes to
        // to zero at a user defined "maxdist". However, "maxdist" depends on the 
        // average of the scaling factors of our matrix.
        uniform float xscale;
        uniform float yscale;
        uniform float zscale;
        xscale = length(vector(comp(mat,0,0),comp(mat,0,1),comp(mat,0,2)));
        yscale = length(vector(comp(mat,1,0),comp(mat,1,1),comp(mat,1,2)));
        zscale = length(vector(comp(mat,2,0),comp(mat,2,1),comp(mat,2,2)));
        aveScale = 1.0 / ((xscale + yscale + zscale)/3);
        }
    }
  
class 
softnoise(float maxdist = 1, Kd = 0.95) {
constant Matrix m_matricesDB[];
  
public void construct() {
    uniform float n;
    uniform float angles[];
    uniform float xrot[], yrot[], zrot[];
    
    if(attribute("user:tumble", angles)) {
        for(n = 0; n < arraylength(angles); n += 3) {
            push(xrot, radians(angles[n]));
            push(yrot, radians(angles[n+1]));
            push(zrot, radians(angles[n+2]));
            }
        }
    // Get a matrix of the "shader" space. It is used by instances of the Matrix
    // struct to "undo" the implicit transformation performed by PRMan of the 
    // user matrices to "current" space.
    uniform matrix ssm = transform("current", "shader", matrix(1));
    uniform matrix original[];
    if(attribute("user:matrices", original)) {
        for(n = 0; n < arraylength(original); n += 1) {
            Matrix mat;
            mat->init(original[n], xrot[n], yrot[n], zrot[n], ssm);
            push(m_matricesDB, mat);
            }
        }
    }
  
public void begin() {
    // Nothing to do here.
    }
  
public void surface(output color Ci, Oi) {
    float accumulatedNoise = 0, fw;
    uniform float n;
    point p = transform("shader", P);
    float dist, weight, scaledMaxdist;
  
    float ns;
    for(n = 0; n < arraylength(m_matricesDB); n += 1) {
        dist = distance(p, m_matricesDB[n]->origin);
        if(dist < maxdist) {
            scaledMaxdist = maxdist * m_matricesDB[n]->aveScale;
            weight = 1 - smoothstep(0, scaledMaxdist, dist);
  
            fw = max(sqrt(area(P)), (2e-6));
            ns = (1 + wnoise(transform(m_matricesDB[n]->mat, P), fw, 
                                    "octaves", 5, 
                                    "lacunarity", 1.8))/2 * weight;
            accumulatedNoise += ns;                    
            }
        }
    Oi = accumulatedNoise * VolumeField;
    Ci = Oi * Kd;
    }
}



© 2002- Malcolm Kesson. All rights reserved.