Here is the source code for a simple perspective camera node with custom distortion and vignetting :

mycamera.cpp
#include <ai.h>
#include <string.h>

AI_CAMERA_NODE_EXPORT_METHODS(MyCameraMethods)

enum
{
   p_fov
};

struct MyCameraData
{
   float tan_fov;
};

node_parameters
{
   AiParameterFlt("fov", 60.0f);
}

node_initialize
{
   AiCameraInitialize(node);
   AiNodeSetLocalData(node, new MyCameraData());
}

node_update
{
   MyCameraData* data = (MyCameraData*)AiNodeGetLocalData(node);
   data->tan_fov = tanf(AiNodeGetFlt(node, "fov") * AI_DTOR / 2);
   AiCameraUpdate(node, false);
}

node_finish
{
   MyCameraData* data = (MyCameraData*)AiNodeGetLocalData(node);
   delete data;
}

camera_create_ray
{
   const MyCameraData* data = (MyCameraData*)AiNodeGetLocalData(node);
   const AtVector p(input.sx * data->tan_fov, input.sy * data->tan_fov, 1);

   // warp ray origin with a noise vector
   AtVector noise_point(input.sx, input.sy, 0.5f);
   noise_point *= 5;
   AtVector noise_vector = AiVNoise3(noise_point, 1, 0.f, 1.92f);
   output.origin = noise_vector * 0.04f;
   output.dir = AiV3Normalize(p - output.origin);

   // vignetting
   const float dist2 = input.sx * input.sx + input.sy * input.sy;
   output.weight = 1 - dist2;

   // now looking down -Z
   output.dir.z *= -1;
}

camera_reverse_ray
{
   const MyCameraData* data = (MyCameraData*)AiNodeGetLocalData(node);

   // Note: we ignore distortion to compute the screen projection
   // compute projection factor: avoid divide by zero and flips when crossing the camera plane
   float coeff = 1 / AiMax(fabsf(Po.z * data->tan_fov), 1e-3f);
   Ps.x = Po.x * coeff;
   Ps.y = Po.y * coeff;
   return true;
}

node_loader
{
   if (i != 0) return false;
   node->methods     = MyCameraMethods;
   node->output_type = AI_TYPE_UNDEFINED;
   node->name        = "mycamera";
   node->node_type   = AI_NODE_CAMERA;
   strcpy(node->version, AI_VERSION);
   return true;
}
In camera_create_ray you can also compute the direction and position derivatives. This is important for correct filtering. However, you can let Arnold compute the differentials automatically if you leave these fields set to the default 0.0 value.

The following example shows how to compute them in the camera node for a perspective camera. In this case the ray's origin is always the same, but the direction changes from pixel to pixel. Follows some sample code to compute the derivatives for a perspective camera (we do not take into account uv noise):

   float fov = data->fov;
   fov *= (float) (AI_DTOR * 0.5);
   float tan_fov = tanf(fov);

   ...
   // scale derivatives
   float dsx = input.dsx * tan_fov;
   float dsy = input.dsy * tan_fov;
   ...

   AtVector d = p;  // direction vector == point on the image plane
   double d_dot_d = AiV3Dot(d, d);
   double temp = 1.0 / sqrt(d_dot_d * d_dot_d * d_dot_d);

   // already initialized to 0's, only compute the non zero coordinates
   output.dDdx.x = (d_dot_d * dsx - (d.x * dsx) * d.x) * temp;
   output.dDdx.y = (              - (d.x * dsx) * d.y) * temp;
   output.dDdx.z = (              - (d.x * dsx) * d.z) * temp;
   output.dDdy.x = (              - (d.y * dsy) * d.x) * temp;
   output.dDdy.y = (d_dot_d * dsy - (d.y * dsy) * d.y) * temp;
   output.dDdy.z = (              - (d.y * dsy) * d.z) * temp;

   // output.dOd* is also initialized to 0s, the correct value

 

Example scene and render:

mycamera.ass
options
{
 xres 1024
 yres 1024
 AA_samples 6
 GI_diffuse_depth 4
 GI_specular_depth 4
}

mycamera
{
 name mycamera
 position 0 1 4
 look_at 0 -0.2 0
 up 0 1 0
}

skydome_light
{
 name myskydome
 intensity 1
 color 1 1 1
 camera 0.0
}

standard_surface
{
 name mystd
 base_color 0.4 0.8 0.4
}

sphere
{
 shader mystd
 matrix
  1 0 0 0
  0 1 0 0
  0 0 1 0
  -1.5 0 0 0
}

sphere
{
 shader mystd
 matrix
  1 0 0 0
  0 1 0 0
  0 0 1 0
  0 0 0 0
}

sphere
{
 shader mystd
 matrix
  1 0 0 0
  0 1 0 0
  0 0 1 0
  1.5 0 0 0
}

standard_surface
{
 name myfloor
 base_color 0.2 0.2 0.2
 specular 0
}

plane
{
 name myplane
 normal 0 1 0
 point 0 -0.5 0
 shader myfloor
}
  • No labels