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

 

Here is the procedural code:

#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;
};
 
// 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, "seed"));
 
   flake->count         = AiNodeGetInt(node, "count");
   flake->sphere_radius = AiNodeGetFlt(node, "sphere_radius");
   flake->flake_radius  = AiNodeGetFlt(node, "flake_radius") - flake->sphere_radius;
   flake->recursions    = AiNodeGetInt(node, "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("points", "flake", node);
   AiNodeSetArray(points_node, "points", point_array);
   AiNodeSetArray(points_node, "radius", radius_array);
   AiNodeSetStr  (points_node, "mode"  , "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;
}

 

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
}

 

The compilation commands:

c++ -o random_flake.os -c -fPIC -D_LINUX -fPIC -I. -I/opt/arnold/include random_flake.cpp
c++ -o librandom_flake.so -shared random_flake.os -L/opt/arnold/bin -lai

See Creating a Simple Plugin for further information about compilation commands.

  • No labels