Rendering emissive and refractive shapes using Monte Carlo integration and ray marching in a Copernicus signed distance field.
This Houdini Copernicus 2D raymarching kernel executes multi-bounce path tracing across signed distance field layers using a fixed-size stack to evaluate primary light rays, reflections, and refractions per sample. It renders a flat slice of light transport rather than a camera-projected view, using @P as the spatial origin. Layer bindings handle the pipeline inputs: the objects layer drives the signed distance field marching loop, the gradient layer supplies precomputed analytical surface normals, and the materials layer provides reflectivity, index of refraction, Beer-Lambert absorption coefficients, and emissive intensity. Rays accumulate radiance and transmittance iteratively, applying Fresnel reflectance and total internal reflection checks before writing out the final averaged pixel value to the rendering layer.
// LAYERS and // PARAMETERS
The code establishes input layers including objects for distance fields, gradient for precomputed normals, materials for surface properties, and rendering for the output buffer. Uniform parameters control sample counts, bounce limits, and step counts.
// INCLUDES and // CONSTANTS
External random number utilities are included alongside configuration definitions such as max travel distance, surface thresholds, intersection bias, and stack depth bounds.
// STRUCT and // FUNCTIONS
A Ray structure bundles position, direction, recursion depth, and weight. Helper routines compute vector reflections, Snell law refractions, Fresnel coefficients, and Beer-Lambert attenuation.
// KERNEL and // RAY SETUP
The main kernel loop initiates stochastic sampling. It generates randomized primary light ray directions from the spatial origin point and initializes a local stack array to manage recursion iteratively.
// MARCHING
Rays march iteratively across the signed distance field until they cross the surface threshold or exceed the maximum distance limit.
// MATERIALS
When an intersection occurs, the materials layer lookup supplies reflectivity, index of refraction, absorption, and emissive properties.
// NORMAL
The gradient layer supplies precomputed surface vectors, which are normalized and oriented correctly based on the distance sign.
// SHADING AND SCATTERING
Segment transmittance is evaluated via Beer-Lambert absorption, and surface emission is accumulated into the running sample sum.
// REFLECTION AND REFRACTION
Secondary rays for reflections and refractions are calculated using Fresnel weighting and pushed back onto the evaluation stack.
// LAYERS
#bind layer objects float
#bind layer gradient float2
#bind layer materials float4
#bind layer rendering noread write float
// PARAMETERS
#bind parm samples int val=32
#bind parm bounces int val=3
#bind parm steps int val=64
// INCLUDES
#include <random.h>
// CONSTANTS
#define MAX_DISTANCE 5.0f
#define EPSILON 1e-6f
#define BIAS 1e-4f
#define STACK_SIZE 12
// STRUCT
typedef struct {
float2 o;
float2 d;
int depth;
float weight;
} Ray;
// FUNCTIONS
void reflect(float ix, float iy, float nx, float ny, float* rx, float* ry) {
float idotn2 = (ix * nx + iy * ny) * 2.0f;
*rx = ix - idotn2 * nx;
*ry = iy - idotn2 * ny;
}
int refract(float ix, float iy, float nx, float ny, float eta, float* rx, float* ry) {
float idotn = ix * nx + iy * ny;
float k = 1.0f - eta * eta * (1.0f - idotn * idotn);
if (k < 0.0f) return 0;
float a = eta * idotn + sqrt(k);
*rx = eta * ix - a * nx;
*ry = eta * iy - a * ny;
return 1;
}
float fresnel(float cosi, float cost, float etai, float etat) {
float rs = (etat * cosi - etai * cost) / (etat * cosi + etai * cost);
float rp = (etai * cosi - etat * cost) / (etai * cosi + etat * cost);
return (rs * rs + rp * rp) * 0.5f;
}
float beerLambert(float a, float d) {
return exp(-a * d);
}
// KERNEL
@KERNEL
{
float sum = 0.0f;
for (int sample = 0; sample < @samples; sample++) {
// Primary ray
float u = VEXrand_4_1(@ix, @iy, sample, 0) * M_PI_F * 2.0f;
float2 dir = (float2)(cos(u), sin(u));
Ray stack[STACK_SIZE];
int sp = 0;
stack[sp].o = @P;
stack[sp].d = dir;
stack[sp].depth = 0;
stack[sp].weight = 1.0f;
sp++;
while (sp > 0) {
sp--;
Ray ray = stack[sp];
if (ray.weight < 1e-4f || ray.depth > @bounces)
continue;
float2 pos = ray.o;
float2 d = ray.d;
float t = 0.0f;
float sign = @objects.imageSample(pos) > 0.0f ? 1.0f : -1.0f;
bool hit = false;
float2 hitPos;
// MARCHING
for (int step = 0; step < @steps && t < MAX_DISTANCE; step++) {
float2 p = pos + d * t;
float dist = @objects.imageSample(p);
if (dist * sign < EPSILON) {
hit = true;
hitPos = p;
break;
}
t += dist * sign;
}
if (!hit) continue;
// MATERIALS
float4 mats = @materials.imageSample(hitPos);
float reflectivity = mats.x;
float eta = mats.y;
float absorption = mats.z;
float emissive = mats.w;
// NORMAL
float2 n = @gradient.imageSample(hitPos);
float len2 = n.x * n.x + n.y * n.y;
if (len2 > 0.0f) {
float inv = 1.0f / sqrt(len2);
n.x *= sign * inv;
n.y *= sign * inv;
}
// Absorption (Beer-Lambert)
float atten = beerLambert(absorption, t);
// Emission
sum += ray.weight * emissive * atten;
if (ray.depth == @bounces)
continue;
// Reflection / Refraction
float refl = reflectivity;
float rx, ry;
// Refraction
if (eta > 0.0f) {
float ior = (sign < 0.0f) ? eta : 1.0f / eta;
if (refract(d.x, d.y, n.x, n.y, ior, &rx, &ry)) {
float cosi = -(d.x * n.x + d.y * n.y);
float cost = -(rx * n.x + ry * n.y);
refl = (sign < 0.0f) ? fresnel(cosi, cost, eta, 1.0f)
: fresnel(cosi, cost, 1.0f, eta);
if (sp < STACK_SIZE && (1.0f - refl) > 1e-4f) {
stack[sp].o = hitPos - n * BIAS;
stack[sp].d = (float2)(rx, ry);
stack[sp].depth = ray.depth + 1;
stack[sp].weight = ray.weight * (1.0f - refl) * atten;
sp++;
}
} else {
refl = 1.0f; // Total internal reflection
}
}
// Reflection
if (refl > 1e-4f && sp < STACK_SIZE) {
reflect(d.x, d.y, n.x, n.y, &rx, &ry);
stack[sp].o = hitPos + n * BIAS;
stack[sp].d = (float2)(rx, ry);
stack[sp].depth = ray.depth + 1;
stack[sp].weight = ray.weight * refl * atten;
sp++;
}
}
}
sum /= (float)@samples;
@rendering.set(sum);
}
"Light Rendering in 2D" by Milo Yip
https://github.com/miloyip/light2d