Arnold allows shaders to displace vertices in a polymesh node. The vertices are displaced in the direction and magnitude of the vector returned by the shader.

In this example shader, we compute a nonlinear displacement based on a noise function. This can displace along the normal of a surface like a traditional noise displacement shader, but it also has the option of blooming out the peaks and valleys of the displacement.

 

This is done by computing a noise function and its delta in the direction of two surface derivatives. This delta determines the amount the derivatives would be deflected by the displacement. By repeatedly deflecting the derivatives, we create a non-linear displacement that arcs out from the surface. This is equivalent to displacing the surface, computing the normal, and displacing again repeatedly.

Here is an animation showing the effect: vimeo / download.

Source Code

/*
 * nonlinear noise displacement shader
 */
#include <ai.h>
#include <string.h>

enum
{
   p_octaves,
   p_freq,
   p_amplitude,
   p_bloom,
   p_type
};

AI_SHADER_NODE_EXPORT_METHODS(NonlinNzMethods);

#define ENUM_SCALAR_TYPES { "perlin", "abs_perlin", "recursive", "abs_recursive", NULL };
#define PERLIN        0
#define ABS_PERLIN    1
#define RECURSIVE     2
#define ABS_RECURSIVE 3
const char *types_enum[] = ENUM_SCALAR_TYPES;

node_parameters
{
   AiParameterInt ("octaves"  , 3);
   AiParameterFlt ("freq"     , 1);
   AiParameterFlt ("amplitude", 1);
   AiParameterFlt ("bloom"    , 1);   
   AiParameterEnum("type"     , PERLIN, types_enum);
}

float scalarfunc(AtVector P, int type, int octaves)
{
   float doubler = 1;
   float NzAccum = 0;

   switch (type)
   {
      case PERLIN:
         return AiPerlin3(P);
      case ABS_PERLIN:
         return fabs(AiPerlin3(P));
      case RECURSIVE:
         for (int i = 0; i < octaves; i++) {
            NzAccum += AiPerlin3(P*doubler) / doubler;
            doubler *= 2;
         }
         return NzAccum;
      case ABS_RECURSIVE:
         for (int i = 0; i < octaves; i++) {
            NzAccum += fabs(AiPerlin3(P*doubler)) / doubler;
            doubler *= 2;
         }
         return NzAccum;
   }

   return AiPerlin3(P);
}

shader_evaluate
{
   AtVector Ploc, Uloc, Vloc; // noise sample location, and over in U and V locations
   float Np, Nu, Nv;         // noise at P, noise at location over in U and V
   float Udelt, Vdelt;       // delta in the noise over in U and V
   float delta = .01;        // distance delta for noise samples

   int octaves = AiShaderEvalParamInt(p_octaves);
   float freq = AiShaderEvalParamFlt(p_freq);
   float amplitude = AiShaderEvalParamFlt(p_amplitude);
   float bloom = AiShaderEvalParamFlt(p_bloom);
   int type = AiShaderEvalParamInt(p_type);

   AtVector U, V;
   if (sg->dPdu != AI_V3_ZERO && sg->dPdv != AI_V3_ZERO)
   {
      // tangents available, use them
      U = sg->dPdu;
      V = sg->dPdv;
   }
   else
   {
      // no tangents given, compute a pair
      AiV3BuildLocalFramePolar(U, V, sg->N);
   }

   if (type > ABS_PERLIN)
   {
      // adjust delta to highest frequency in recursive noise
      delta *= pow(.5,  octaves) * 2;
   }
   Ploc = sg->Po * freq;
   Uloc = Ploc + U * delta;
   Vloc = Ploc + V * delta;

   // noise sampled at P, and over in U and V
   Np = scalarfunc(Ploc, type, octaves);
   Nu = scalarfunc(Uloc, type, octaves);
   Nv = scalarfunc(Vloc, type, octaves);

   Udelt = (Nu - Np) * bloom;
   Vdelt = (Nv - Np) * bloom;

   AtVector Pstepped = sg->P;   
   int steps = 10;
   float stepscale = amplitude / steps;

   for (int i = 0; i < steps; i++)
   {
      // stepdir is the cross product of the derivatives
      AtVector stepdir = AiV3Cross(U,V);
      // deflect the derivatives
      U = AiV3Normalize(U + (stepdir * Udelt * stepscale));
      V = AiV3Normalize(V + (stepdir * Vdelt * stepscale));
      Pstepped += stepdir * Np * stepscale;
   }

   sg->out.VEC() = Pstepped - sg->P;
}

node_initialize
{
}

node_update
{
}

node_finish
{
}

node_loader
{
   if (i > 0) return false;
   
   node->methods      = NonlinNzMethods;
   node->output_type  = AI_TYPE_VECTOR;
   node->name         = "nonlinear_noise";
   node->node_type    = AI_NODE_SHADER;
   strcpy(node->version, AI_VERSION);
   return true;
}

 

And here is the .ass file that uses this shader:

options
{
 AA_samples 3
 xres 640
 yres 480
 GI_diffuse_depth 1
 GI_diffuse_samples 3
}

plane
{
 name myplane
 point 0 -8 0
 normal 0 1 0
 shader groundshader
}

polymesh
{
 name mysph
 nsides 6 1 UINT 4 4 4 4 4 4 
 vidxs 24 1 UINT 
  0 4 5 1 1 5 6 2 2 6 7 3 3 7 4 0 3 0 1 2 4 7 6 5 
 vlist 8 1 b64VECTOR 
  AAB6wwAAAAAAAHrDAAB6QwAAAAAAAHrDAAB6QwAAAAAAAHpDAAB6wwAAAAAAAHpDAAB6wwAA+kMAAHrDAAB6QwAA+kMAAHrDAAB6QwAA+kMAAHpDAAB6wwAA+kMAAHpD
 smoothing on
 subdiv_type catclark
 subdiv_iterations 7
 disp_map sphere_disp
 disp_padding 50
 matrix 
  0.94693 0 0.321439 0
  0 1 0 0
  -0.321439 0 0.94693 0
  0 0 0 1 
 shader sphere_surf
}

standard_surface
{
 name sphere_surf
 base 0.3
 base_color 0.8 0.8 1
 specular 1
 specular_color 0.8 0.8 1
 specular_roughness 0.3
 subsurface 0.5
 subsurface_color 1 0.05 0.2
 subsurface_radius 80 80 80
}

nonlinear_noise
{
 name sphere_disp
 type perlin
 freq 0.025
 amplitude 80
 bloom 1
}

standard_surface
{
 name groundshader
 base 1
 base_color 0.4 0.4 0.4
 specular 0
}

persp_camera
{
 name mycamera
 fov 11 
 position 3677.0129 1039.1904 597.0592 
 look_at 0 250 0 
 up 0 1 0 
}

point_light
{
 name key
 position -6000 10000 6000 
 radius 400
 color 1 0.7 0.2
 intensity 1
 exposure 28
}

skydome_light
{
 name mysky
 color 0.7 0.8 0.9
 intensity 0.9
}
 

Some examples of looks that can be obtained with this shader, along with the shader settings from the .ass file:

Lumpy Nodules

 type abs_perlin
 freq 0.025
 amplitude 80

 

Pomegranate

 type recursive
 freq 0.025
 amplitude 40
 bloom 3

 

Cauliflower

 type abs_recursive
 freq 0.025
 amplitude 40
 bloom 3

 

Brain Coral

 type abs_perlin
 freq 0.025
 amplitude -40
 bloom -4
  • No labels