Aligning arrows to follow a pyro sim... don't mind that it's on a shoe (loud video):
Ls_AF1TransonicRegime_v01_8mb.mp4
Unwrapping layers of papyrus from CT scans of the Herculaneum scrolls - could see the crosshatching of the reeds woven together but no ink or writing (loud video):
Ls_AncientScrolls_v01.mp4
APEX node graphs are stored as geometry so you can do silly things like move the nodes themselves around with attribnoise:
Ls_ApexLol_v01.mp4
Using the vague concept of a "volumetric tangent space" for blendshaping so it doesn't move linearly, but follows the surface of the sphere to get to its final point (left is normal linear blendshape with obvious intersection and volume problems):
Screen_Recording_2024-07-08_at_21.37.49.mp4
Mantra rendered caustics in a quite different way to how Karma now does and they can still be fun to play with... just set photon target on a caustic light to the transparent object and add a zero to the default photon count, then constantly hit render in the IPR to update the map:
Semi-serious try at 3D colour correction with 5 from/to pickers using radial basis functions to stay smooth, kinda similar to the mesh-like colour warping in Baselight/Flame/Resolve:
Ls_ColourBooper_v01.mp4
Using OpenCL global atomic add tricks to render a buffer of point positions as single pixel particles super fast, a bit like Krakatoa or higx point render:
Ls_AtomicAdd2c.mp4
#bind layer src read
#bind layer dummy write opt
#bind layer dst write
// https://violetspace.github.io/blog/atomic-float-addition-in-opencl.html
inline void atomic_add_f(volatile __global float* addr, const float val) {
#if defined(cl_nv_pragma_unroll)
float ret; asm volatile("atom.global.add.f32 %0,[%1],%2;":"=f"(ret):"l"(addr),"f"(val):"memory");
#elif defined(__opencl_c_ext_fp32_global_atomic_add)
atomic_fetch_add_explicit((volatile global atomic_float*)addr, val, memory_order_relaxed);
#elif __has_builtin(__builtin_amdgcn_global_atomic_fadd_f32)
__builtin_amdgcn_global_atomic_fadd_f32(addr, val);
#else
float old = val; while((old=atomic_xchg(addr, atomic_xchg(addr, 0.0f)+old))!=0.0f);
#endif
}
@KERNEL {
int2 xy = convert_int2(@src.xy * @dst.res);
int idx = _linearIndex(@dst.stat, xy);
atomic_add_f((global float *)_bound_dst + idx, 0.01);
}Crude demo of 6 million particles simulating and rendering at 60fps all on the GPU in COPs, using a simulate block with position and velocity buffers and the above atomic add trick:
Ls_Cop3GPUparticles_v01.smal.mp4
Similar to above but also cheaply rasterizing the particles onto an image layer inside the simulate block then using the derivatives of that image to get a force towards the local concentration of particles, like hard-to-control but very fast flocking or the particle/grid transfer that forms the basis of FLIP:
Ls_Cop3GPUparticles_v03.smol2.mp4
There's no way to apply a 3D LUT in new-style COPs apart from the OCIO node (which is CPU-only) but if you encode one in an image you can do a reasonable job with OpenCL:
Binding a 3@matrix attribute into OpenCL using float9, then loading it with mat3load(). Admittedly it's confusing you can't access the mat3 as an array of 9 floats since it's actually an array of fpreal3s (each of which is 4 floats wide even more confusingly):
#bind layer src? val=0
#bind layer !&dst
#bind point mat float9 port=geo
@KERNEL
{
// Load 3@ matrix attrib from point chosen by pixel y coord
mat3 m;
mat3load(0, @mat.tupleAt(@iy), m);
@dst.set((float4)(m[0].x, m[0].y, m[0].z, m[1].x));
}The Prefix Sum COP makes summed area tables that let you do box filters of any size pretty much instantly, so you can add up tons of them to approximate the non-separable kernels of exponential or fibonacci glows (which look uncomfortably like having smudged glasses if you get the shape just right):
#bind layer src
#bind layer psum
#bind layer !&dst
#bind parm size float
#bind parm shape float
#bind parm layers int
@KERNEL {
float w = @size / 20;
float4 acc = 0.0;
for(int i = 0; i < @layers; i++) {
// https://en.wikipedia.org/wiki/Summed-area_table
acc += (@psum(@P + (float2)(w, w)) + @psum(@P + (float2)(-w, -w)) - @psum(@P + (float2)(-w, w)) - @psum(@P + (float2)(w, -w)))
/ (float4)(2 * w * @res.x * w * @res.y);
w *= 1 + @shape;
}
@dst.set(@src + acc / (float4)@layers);
}The Smooth Fill COP can be used for seamless edge blending, useful for tiled textures:
Camera projection in COPs by simply projecting UVs from camera as a uv2 attrib in SOPs, then baking from uv2 to uv:
Extrapolating a curve using Taylor polynomials (can enable gradient3 and gradient4 as well but it gets a bit spicy):
Screen_Recording_2024-07-15_at_17.17.56.mp4
Velocity extrapolation weirdness in DOPs... when velocity sampling is set to streak, velocity at the border is reflected back into the system. Changing velocity sampling to corner keeps it stable, as does turning closed boundaries on (but changes the look a lot). Bizarrely it also stays stable if you enable OpenCL on Gas Enforce Boundary DOP:
Screen_Recording_2025-08-10_at_13.48.02.mp4
Easily memorable way to transfer a sculpt or other shape change from a rest pose to a different pose by storing the difference in tangent space, simply using dot products with N/tangentu/tangentv without needing to remember which bit is which in a TBN matrix (may not match the MikkT standard though):
Splits a mesh into low and high frequency parts, then uses the tangent space transfer trick above to offset the high frequencies in UV space:
Ls_GeoFreqSep_v02_8mb.mp4
Demo of strangely warped reflections at close-to-tangent ray incidence - goes away in Karma XPU or when changing vertex order/normal direction:
Generates a list of OpenCL functions in Houdini's include folder using the cpp preprocessor and https://ctags.io/ - output looks like Ls_houCLfuncs.cl
The HDK comes with a Karma procedural that renders a mandelbulb in VEX without having to create any geometry (sadly it crashes as soon as you change any of the parameters in 20.5.361 unless you switch the renderer from Karma CPU to something else and then back again):
Probably the best we can do in 21.0 to render PNGs from Karma with an OCIO view transform baked in, by applying it with a slapcomp block and passing --ocio 0 to husk so it doesn't do another transform... it matches rendering an EXR and applying the view transform afterwards in COPs:
You can sorta render a sunset using a uniform volume on a sphere as big as the entire earth with a geo-referenced DEM terrain, letting the atmosphere both scatter and absorb the same colour so the sky gradient from the low-angle sun appears naturally:
Pruning primitives outside of an animated camera's accumulated viewing volume by sampling a SOPs VDB at the center of each prim in LOPs to check if it's outside:
vector center = usd_getbbox_center(0, @primpath, @primpurpose);
float sdf = volumesample('op:/stage/sdf/frustrum/FrustrumVDB', 0, center);
if(sdf > 0.0) {
usd_setactive(0, @primpath, 0);
}Extracts a best-fit 3x3 matrix from two Macbeth chart images similarly to mmColorTarget using everyone's favourite, the Linear Solver SOP:
Interpolating between two matrices using slerp() so the transform stays rigid and doesn't introduce weird shears like interpolating the matrix values directly does:
Soap bubble simulated as 2D smoke with a noise texture mapped to the advected "single rest" field in COPs, rendered with Arnold's thin film shader - doing that keeps far more detail than trying to advect the noise texture directly, and resizing the rest field up 3x before mapping makes it look as if the whole thing was done with an extra 3x3 worth of antialiasing:
Ls_OldBubbleSim_v07_8mb.mp4
Keeps only packed prims that are visible from a point by casting rays - it may seem surprising that the Ray SOP can trace against packed prims since most SOPs only treat them as a single point, but thinking of them as render time instances hints that it should work and be fast... the rays record hitprim from the packed geo, then findattribvalcount() checks if each piece had a hit recorded:
Silly experiment advecting the control points of beziers with a pyro sim:
Ls_PyroType_v03.mp4
Simplifying colour ramps that have way too many points by treating them as 3D paths in RGB space:
Ls_RampRefine.mp4
Visualizing the main result of https://arxiv.org/abs/2502.14367v3 by showing rotation vectors in SO(3) for 50 walks as their angle scales increase over time, both on the left walking once (which never returns to the origin) and on the right the same walk repeated twice (which returns to the origin many times):
Ls_RotationWalksHome_v03.mp4
2D ink simulation from scratch using only microsolvers, blending back the effect of divergence-free projection to avoid excessive mushroom shapes:
Ls_Semicompressible_Ink2d_v01.mp4
Faking shadows using geometry raycast away from a point light source.
VEX snippet to squash some primitives and accumulate the offset to maintain the spacing of the others.
int squashcount = 0;
for(int i = 0; i < nprimitives(0); i++) {
if(inprimgroup(0, "squash", i-1)) {
squashcount++;
}
foreach(int p; primpoints(0, i)) {
vector oldpos = point(0, 'P', p);
vector newpos = oldpos;
newpos.x -= squashcount * chf('squashfactor');
setpointattrib(0, 'P', p, newpos);
}
}Cheap trick to make meshes squish away from each other when in contact without any simulation:
Ls_Squish_v01.mp4
Generates a 2D straight skeleton by advecting points toward the SDF center, similar to Labs Straight Skeleton 2D:
Creates a smooth terrain from contour lines using thin plate spline interpolation, adapted from the RBF blendshape example in the Linear Solver SOP help page:
Extracts the Q-criterion isosurface beloved by aerospace CFD people from a smoke or pyro sim (it's the difference between the squared frobenius norms of the symmetric and anti-symmetric parts of the velocity gradient tensor, see https://www.m4-engineering.com/q-criterion-for-vortex-visualization):
Ls_TurbulenceQ_v01.mp4
Builds a geometry network with friend/following relations from a social media graph and simulates it to see which parts cluster naturally together (big mess, for more see https://github.com/lcrs/twarea):
Ls_TwitterArea_v04_8mb.mp4
Showing how deceptive the viewport can be when working with fast-moving cameras - the capy and camera are in sync when hitting play, but when scrubbing the timeline he's all over the shop:
capyyyy.mp4
Using tag visualizers in dummy geo objects parented under the camera. This allows color control, and is easy to move around rather than being stuck in the corner.
Everything else in here I have zero memory of don't @ me 🤍 lewis.saunders@gmail.com
(Older stuff might be on https://lewisinthelandofmachines.tumblr.com)