Preview

Houdini

Three-tier asset pipeline: Sub-COPhda \rightarrow Main-COPhda \rightarrow SOPhda Wrapper.

Pipeline Breakdown

1. Innermost Layer: Sub-COP HDA (Python Line Drawing)

Sub-COP HDA
Innermost Layer: Core Python-based COP HDA sketches the initial sci-fi panel layouts.

Recursively grows branching lines on the 2D buffer using line length as spawning weights.

Lines are rasterized using a custom Bresenham’s Line Algorithm.
Border hits trigger coordinate wrapping for tileable output; intersections terminate the trace to prevent overlaps.

Bresenham's Line Rasterization with Canvas Wrapping python
# =============== pix_line_bresenham =============== 
ix1, iy1 = int(round(x1)), int(round(y1))
ix2, iy2 = int(round(x2)), int(round(y2))
dx = abs(ix2 - ix1)
dy = -abs(iy2 - iy1)
sx = 1 if ix1 < ix2 else -1
sy = 1 if iy1 < iy2 else -1
err = dx + dy

i = 0
while i < 99999:
  ix_actual, iy_actual = ix1 % w, iy1 % h
  # Wrap canvas coordinates for tileable output
  if not (0 <= ix1 < w and 0 <= iy1 < h) \
      and (ix_actual == 0 or ix_actual == w-1 or iy_actual == 0 or iy_actual == h-1):
      add(ids, startid)
      if kwargs['tileable']:
          startid = XYtoId(ix_actual, iy_actual)
          add(ids, startid)
      else:
          stopflag = 1
          break
  
  # Stop tracing if we hit an existing panel line
  if i != 0 and pixs[iy_actual * w + ix_actual] >= .999:
      startid = XYtoId(ix_actual, iy_actual)
      add(ids, startid)
      stopflag = 2
      break
  
  startid = draw_pix(ix_actual, iy_actual)
  if ix1 == ix2 and iy1 == iy2:
      break
  e2 = 2 * err
  if e2 >= dy:
      err += dy
      ix1 += sx
  if e2 <= dx:
      err += dx
      iy1 += sy
  
  i += 1
# =============== pix_line_bresenham ===============

2. Intermediate Layer: Main-COP HDA (Details and coloring)

Main-COP HDA
Second Layer: Main COP HDA applies noise, height layers, detail maps, and coloring.

To convert the rasterized lines into clean, beveled plates without rounding off structural corners, the custom Dilate/Erode node implements Manhattan and Chebyshev distance metrics instead of standard Euclidean.

Custom DilateErode Metrics opencl
// Custom Distance Metrics for Hard Surface Bevels
float len2;
if (method == 0)
{
  len2 = dx * dx + dy * dy;        // Euclidean (Round bevels)
}
else if (method == 1)
{
  len2 = abs(dx) + abs(dy);       // Manhattan (45-degree bevels)
  len2 *= len2;
}
else if (method == 2)
{
  len2 = max(abs(dx), abs(dy));    // Chebyshev (90-degree sharp grid)
  len2 *= len2;
}

Supports custom falloff profiles (linear, smoothstep, or ramp lookup):

Custom DilateErode Falloffs opencl
if (@falloff_type == 0)
  falloff = 1 - falloff;
else if (@falloff_type == 1) //default
{
  //smoothstep y = (1-x^2)^2 
  falloff = 1 - falloff*falloff;
  falloff *= falloff;
}
else
  falloff = @ramp.getAt(falloff);

Panel segmentation is done via connectivity analysis.
Shuffling the island IDs is performed on the GPU using Kensler’s stateless CMJ permutation.
This avoids memory allocation and thread synchronization. See Stateless Permutation for details.

Correlated Multi-Jittered Stateless Permutation opencl
uint CMJPermute(uint i, uint l, uint p)
{
  uint w = l - 1;
  w |= w >> 1;
  w |= w >> 2;
  w |= w >> 4;
  w |= w >> 8;
  w |= w >> 16;
  do
  {
      i ^= p; 
      i *= 0xe170893d;
      i ^= p >> 16;
      i ^= (i & w) >> 4;
      i ^= p >> 8; 
      i *= 0x0929eb3f;
      i ^= p >> 23;
      i ^= (i & w) >> 1; 
      i *= 1 | p >> 27;
      i *= 0x6935fa69;
      i ^= (i & w) >> 11; 
      i *= 0x74dcb303;
      i ^= (i & w) >> 2; 
      i *= 0x9e501cc3;
      i ^= (i & w) >> 2; 
      i *= 0xc860a3df;
      i &= w;
      i ^= i >> 5;
  }
  while (i >= l);
  return (i + p) % l;
}

3. Outermost Layer: SOP HDA (A Wrapper For Unreal)

SOP HDA Wrapper
Outermost Layer: SOP wrapper adds collision geometry and maps textures for Unreal displacement.

The outermost SOP wrapper generates the grid mesh and handles the Nanite/displacement parameters.
Since Houdini Engine’s default viewport proxy meshes don’t support displacement previews, assigning overrides forces Unreal to cook full static meshes for real-time viewport feedback.

SOP Attributes for Unreal Engine Displacement vex
// Enable Nanite and set displacement height from HDA interface parameter
i@unreal_nanite_enabled = 1;
f@unreal_material_parameter_magnitude = `ch('../height')`;
f@unreal_material_parameter_center = 0;

// Force full mesh preview in Unreal viewport by disabling proxy static meshes
i@unreal_uproperty_bOverrideGlobalProxyStaticMeshSettings = 1;
i@unreal_uproperty_bEnableProxyStaticMeshOverride = 0;