Random Flake Procedural - Arnold Developer Guide
This is an example of making a geometry-generating procedural for Arnold. There are 2 files in this example; the code for the procedural, and a .ass scene file.
This procedural makes a random point cloud with fractal distribution in space. There are 5 controls for the generation of the cloud:
- count: the number of points per recursion to generate
- recursions: the number of recursive steps to descend
- flake_radius: the radius of the little spheres that make up the cloud
- cloud_radius: the overall radius of the point cloud
- seed: a random seed to generate a unique random cloud
The algorithm works like this: given a spherical region of radius R, scatter N points into it. For each point, scatter N random points within a radius of 1/2 the previous R, and so on, for X number of recursions, then hang a little sphere of radius S on the resulting points.
This algorithm creates poorly bound geometry and is likely to build a few points outside the bounding box.
![]() |
---|
Sample render of a point cloud with 2,015,539 points |
Example code
random_flake.cpp
#include <ai.h>
#include <cstring>
#include <cstdlib>
// Procedural parameters
struct RandomFlake
{
int count;
float flake_radius;
float sphere_radius;
int recursions;
int counter;
int num_points;
AtUniverse *universe; // Universe to add the point shape to
};
// returns a random vector in a unit sphere with a
// power function to bias it towards the center
static AtVector random_vector(float power)
{
AtVector out(drand48() - 0.5, drand48() - 0.5, drand48() - 0.5);
return AiV3Normalize(out) * pow(drand48(), power);
}
// recursive function that creates random flake with fractal clumping
static void make_cloud(RandomFlake *flake, AtArray *point_array, AtArray *radius_array, AtVector center, float radius, int recursions)
{
for (int i = 0; i < flake->count; i++)
{
AtVector new_center = random_vector(0.5) * radius;
AiArraySetVec(point_array, flake->counter, new_center + center);
AiArraySetFlt(radius_array, flake->counter, flake->sphere_radius);
flake->counter++;
if (recursions > 1)
make_cloud(flake, point_array, radius_array, new_center + center, radius * 0.5, recursions - 1);
}
}
AI_PROCEDURAL_NODE_EXPORT_METHODS(RandomFlakeMtd);
node_parameters
{
AiParameterInt("count" , 10);
AiParameterInt("recursions" , 5);
AiParameterFlt("sphere_radius", 0.01f);
AiParameterFlt("flake_radius" , 10.0f);
AiParameterInt("seed" , 0);
}
procedural_init
{
RandomFlake *flake = new RandomFlake();
*user_ptr = flake;
srand48(AiNodeGetInt(node, AtString("seed")));
// get the universe this procedural belongs to, and use it create all ginstances
flake->universe = AiNodeGetUniverse(node);
flake->count = AiNodeGetInt(node, AtString("count"));
flake->sphere_radius = AiNodeGetFlt(node, AtString("sphere_radius"));
flake->flake_radius = AiNodeGetFlt(node, AtString("flake_radius")) - flake->sphere_radius;
flake->recursions = AiNodeGetInt(node, AtString("recursions"));
flake->counter = 0;
flake->num_points = 0;
for (int i = 0; i < flake->recursions; i++)
flake->num_points += pow(flake->count, i);
AiMsgInfo("[random_flake] number of points: %d", flake->num_points);
return true;
}
procedural_cleanup
{
RandomFlake *flake = (RandomFlake*)user_ptr;
delete flake;
return true;
}
procedural_num_nodes
{
return 1;
}
procedural_get_node
{
RandomFlake *flake = (RandomFlake*)user_ptr;
AtArray *point_array = AiArrayAllocate(flake->num_points, 1, AI_TYPE_VECTOR);
AtArray *radius_array = AiArrayAllocate(flake->num_points, 1, AI_TYPE_FLOAT);
make_cloud(flake, point_array, radius_array, AI_V3_ZERO, flake->flake_radius, flake->recursions - 1);
// create node with procedural node as parent
AtNode *points_node = AiNode(flake->universe, AtString("points"), AtString("flake"), node);
AiNodeSetArray(points_node, AtString("points"), point_array);
AiNodeSetArray(points_node, AtString("radius"), radius_array);
AiNodeSetStr (points_node, AtString("mode") , AtString("sphere"));
return points_node;
}
node_loader
{
if (i>0)
return false;
node->methods = RandomFlakeMtd;
node->output_type = AI_TYPE_NONE;
node->name = "random_flake";
node->node_type = AI_NODE_SHAPE_PROCEDURAL;
strcpy(node->version, AI_VERSION);
return true;
}
Example scene
This is the .ass file that renders the above image:
options
{
AA_samples 6
outputs "RGB RGB myfilter mydriver"
xres 640
yres 480
GI_diffuse_depth 1
}
driver_jpeg
{
name mydriver
filename "randflake.jpg"
}
gaussian_filter
{
name myfilter
}
plane
{
name myplane
point 0 -10 0
normal 0 1 0
shader planeshader
}
random_flake
{
name myrandflake
shader flakeshader
count 6
recursions 9
sphere_radius 0.015
flake_radius 10
seed 4
}
lambert
{
name flakeshader
Kd 0.7
}
lambert
{
name planeshader
Kd_color .15 .2 .2
}
persp_camera
{
name mycamera
focus_distance 11
aperture_size .15
position -5 5 15
look_at 0 0 0
}
point_light
{
name key
position 100 200 100
radius 4
color 1 0.6 0.4
}
spot_light
{
name kicker
position -200 200 -500
look_at 0 0 0
cone_angle 2
color .1 0.2 3
}
skydome_light
{
name mysky
color 0.6 0.85 1
intensity .5
}
Compiling the example
Compile windows
> set ARNOLD_PATH="C:/Autodesk/arnold/7.1.0.0
> cl /LD random_flake.cpp /I %ARNOLD_PATH%/include %ARNOLD_PATH%/lib/ai.lib /link /out:random_flake.dll
Compile linux
> export ARNOLD_PATH=/opt/autodesk/arnold/7.1.0.0
> c++ random_flake.cpp -o random_flake.so -std=c++11 -Wall -O2 -shared -fPIC -I$ARNOLD_PATH/include -L$ARNOLD_PATH/bin -lai
See Creating a Simple Plugin for further information about compilation commands.