Houdini 22.0 · POPs + Attribute Noise · Pyro · Karma XPU · MaterialX · Copernicus

Portal de Dr. Strange en Houdini

Paso a paso para construir el portal del sling ring: un anillo de chispas que nace pequeño, crece, se deforma con un mapa de ruido y lanza chispas naranjas en forma de rayas que rebotan en el piso. Lleva un humo leve en el aro, se renderiza con Karma y el glow se agrega en Copernicus.

Frames 1–240 (10 s a 24 fps), Karma XPU a 1024 × 1024 con humo, compuesto en Copernicus (glow, curva fílmica y saturación). Aquí reducido a 720 × 720. La marca de agua es de la licencia Apprentice.

Radio
2 m
Chispas
~4 300
Render
~10–18 s/frame
Armarlo
60–90 min

La idea en siete líneas

  1. Un círculo crece y se abre. Un mapa de ruido 3D deforma su borde para que no sea un círculo perfecto.
  2. Cada punto del círculo recibe una velocidad tangencial, que barre el borde y sale hacia afuera.
  3. Un POP Network suelta chispas, las hace girar, las frena y les aplica gravedad. Una fuerza Metaball limpia el hueco y un Ground Plane las hace rebotar.
  4. Después de simular, cada chispa se vuelve una línea orientada por su velocidad. Con puntos parecería un fuego artificial.
  5. Look: chispas y anillo emisivos (MaterialX), una luz naranja para el piso y render en Karma XPU.
  6. Humo (opcional): un Pyro Solver suelta humo leve desde el aro, que brilla naranja por sí mismo. Se prende y apaga con un interruptor.
  7. Composición: en Copernicus se agregan dos glows, una curva fílmica y saturación, y se guarda la secuencia final.
Regla de trabajo: no renderizar hasta que el movimiento esté bienRevisa la simulación con previews de OpenGL (paso 8) y pasa a Karma solo cuando el movimiento te convenza. Un render largo de algo que todavía está mal es tiempo perdido.

Antes de empezar

  • Versión: Houdini 22.0 Apprentice. Algunos nodos se llaman ::2.0 (POP Source, POP Solver, Attribute Noise); el menú Tab elige la versión nueva solo.
  • Unidades: 1 unidad = 1 metro. El portal mide 2 m de radio y está de pie en el plano XY, mirando hacia +Z, que es donde está la cámara.
  • Animación: 24 fps, frames 1 a 240 (10 s). Se configura en Global Animation Options, el reloj abajo a la derecha.
  • Cómo crear nodos: dentro de una red, pulsa Tab y escribe el nombre del nodo. Para entrar a un nodo, doble clic (o I); para salir, U.
  • Renombra cada nodo con el nombre de la guía. Varias expresiones apuntan a los nodos por nombre (por ejemplo ch("../anillo_emisor/radx")).
  • Flags: el display flag (azul) dice qué se ve en el viewport y el render flag (morado) qué se renderiza. En /obj, el display flag de un objeto también decide si Karma lo ve.
Los códigos VEX y sus slidersLos códigos usan chf("nombre") para que los valores sean sliders. Después de pegar el código en un Wrangle, pulsa el botón Create spare parameters. No tiene texto: es un ícono (un cuadrito con un deslizador y un +) arriba a la derecha del cuadro de código, a la altura de la palabra VEXpression; al pasar el ratón dice Creates spare parameters for each unique call of ch(). Si no se ve, ensancha el panel de parámetros. Aparecen los sliders en 0; luego escribe los valores de la tabla.
ExpresionesAlgunos parámetros llevan una expresión en vez de un número (por ejemplo -$F * 4). Escríbela tal cual en el campo; el campo se pone verde. $F es el frame actual y $T el tiempo en segundos.
Cómo leer las rutas y los nombres de esta guíaMuchas expresiones leen un valor de otro nodo. Por ejemplo, ch("../anillo_emisor/radx") se lee así:
  • ch("…") (o chf("…") dentro de un Wrangle) = «dame el valor de este parámetro».
  • radx = el nombre interno del parámetro Radius (campo X) del Circle. Cada parámetro tiene una etiqueta visible (Radius) y un nombre interno (radx, rady). Para verlo, pasa el ratón sobre la etiqueta del parámetro: el recuadro que aparece dice Parameter: radx.
  • anillo_emisor = el nombre del nodo donde está ese parámetro (por eso hay que renombrar los nodos igual que en la guía).
  • .. = «sube un nivel», como en las carpetas de una computadora. Desde un nodo dentro de /obj/portal, .. es la propia red /obj/portal, así que ../anillo_emisor es el nodo anillo_emisor que está a su lado. Desde dentro de chispas_sim hay que subir dos niveles (../../): uno para salir del nodo y otro para salir del DOP Network.
  • Es lo mismo que escribir la ruta completa: ch("/obj/portal/anillo_emisor/radx"). La relativa se usa porque sigue funcionando si renombras o copias el objeto portal.
  • Truco para no escribirlas: clic derecho sobre el parámetro de origen (por ejemplo Radius de anillo_emisor) → Copy Parameter; luego clic derecho sobre el parámetro de destino → Paste Relative References. Houdini escribe la expresión con la ruta correcta.
  • Siglas de los niveles de Houdini: SOP = redes de geometría (dentro de un objeto como portal); DOP = redes de simulación (chispas_sim); POP = los nodos de partículas que van dentro de un DOP; ROP = nodos de render en /out; COP = composición en /img (Copernicus). Los nodos Null llamados OUT_… o RENDER_… no hacen nada: marcan la salida de una etapa para que otros nodos la lean por su ruta.
  • En los códigos VEX: @P es la posición del punto y @ptnum su número. La letra antes de @ dice el tipo del atributo: f@ = número decimal, v@ = vector (x, y, z), i@ = número entero. @Frame y @Time son el frame y el tiempo actuales.
  • Variables en rutas de archivo: $HIP = la carpeta donde está guardado tu .hipnc; $F3 y $F4 = el número de frame con 3 o 4 dígitos (portal_073.jpg, portal.0073.exr).

Mapa de lo que vas a construir

Dentro de /obj/portal (nivel SOP):

anillo_emisor (Circle)
  → normal_radial (Wrangle) → mapa_de_ruido (Attribute Noise) → desplazar_por_ruido (Wrangle) → OUT_anillo (Null)
  → velocidad_tangencial (Wrangle) → OUT_emisor (Null)          ┄┄ lo lee fuente_anillo
hueco_metaball (Metaball)                                        ┄┄ lo lee la fuerza "hueco"
chispas_sim (DOP Network)
  ┄ importar_chispas (DOP Import) → color_por_edad (Wrangle) → estelas_lineas (Wrangle)
        ├→ OUT_chispas (Null, display flag: preview)
        └→ look_render (Wrangle) → RENDER_chispas (Null, render flag: Karma)

Dentro de chispas_sim (nivel DOP):

chispas (POP Object) ───────────────────────────────────────────────→ popsolver [entrada 1: Object]
fuente_anillo → vida_aleatoria → giro_portal → hueco → frenado → gravedad_y_ruido → popsolver [entrada 3]

piso (Ground Plane) ─┐ (izquierda)
popsolver ───────────┴→ colisiones (Merge) → output

En /obj: portal, anillo_brillante (copia el anillo deformado para la línea brillante), piso, luz_portal y cam_preview. Si llevas humo, también CONTROLES y humo. En /mat, tres materiales (cuatro con el humo); en /out, un ROP de OpenGL y uno de Karma.

Dentro de /obj/humo (paso 12, opcional):

traer_anillo (Object Merge) → fuente_humo (Wrangle) → rasterizar_fuente (Volume Rasterize Attributes)
  → sim_humo (Pyro Solver) → densidad_y_temperatura (Blast) → cache_humo (File Cache)
  → material_humo (Material) → OUT_humo (Null)

Dentro de /img/comp_portal (paso 13, Copernicus):

render_karma (File) → glow_cerca (Glow) → glow_lejos (Glow) → curva_filmica (Tonemap) → saturacion (HSV) → OUT_comp (Output)
guardar_comp (ROP Image) ┄┄ lee saturacion

Las flechas punteadas (┄) no son cables: el nodo lee la geometría por su SOP Path.

La red dentro de /obj/portal, ordenada en cajas por etapa: 1. anillo, 2. mapa de ruido, 3. velocidad, 4. simulación, 5. color y líneas, 6. look para Karma. irregularidad (gris, arriba a la izquierda) es una prueba descartada que no se conecta; no hace falta crearla.
La red dentro de chispas_sim. La fuente y las fuerzas van en cadena a la 3ª entrada del popsolver. El piso entra a la izquierda del merge colisiones.
El nivel /obj terminado. MCP_CAM_CENTER y MCP_CAMERA los creó la herramienta de previews de Claude; no hacen falta.
1

El objeto contenedor

  1. En /obj, Tab → Geometry. Llámalo portal.
  2. Entra (doble clic). Los pasos 2 a 7 van adentro.
2

El anillo emisor

El círculo del que nacen las chispas. Crece de 0.24 m a 2 m en 2.5 s, arrancando rápido y frenando al final (ease-out). También se abre como un arco y gira.

NodoNombreParámetros
Circleanillo_emisorPrimitive Type Polygon, Orientation XY Plane, Divisions 360, Arc Type Open Arc

Expresiones (escríbelas en el campo):

ParámetroExpresiónQué hace
Radius (X)2 * (0.12 + 0.88 * (1 - pow(1 - clamp(($F - 1) / 60, 0, 1), 3)))Crece hasta 2 m en el frame 61 (curva cúbica ease-out)
Radius (Y)ch("radx")Copia el valor de Radius X de este mismo nodo (radx es el nombre interno del campo X); así el círculo nunca se vuelve óvalo
Arc Angles (2º campo)fit($F, 1, 36, 1, 360)El arco se abre de 0° a 360° en 1.5 s
Rotate (Z)-$F * 4El anillo gira 4° por frame
anillo_emisor en el frame 73. Los campos verdes tienen expresión; se ve su valor en ese frame (radio 2, arco 360°). Reverse se marca solo.
3

El mapa de ruido (que no sea un círculo perfecto)

El anillo atraviesa un campo de ruido 3D animado que empuja cada punto hacia afuera o hacia adentro. Así el portal parece que "lucha" por mantenerse circular.

  1. Debajo de anillo_emisor, Tab → Attribute Wrangle, nombre normal_radial (Run Over: Points):
VEX · normal_radial
// Normal hacia afuera del portal: el ruido empuja el borde hacia afuera o hacia adentro
v@N = normalize(set(@P.x, @P.y, 0));
  1. Debajo, Tab → Attribute Noise, nombre mapa_de_ruido:
SecciónParámetroValor
GeneralAttribute NamesFloat, ruido
Noise ValueRange ValuesZero Centered
Amplitude0.38 (m de deformación)
Noise PatternNoise TypeSimplex
Element Size6.68 (tamaño de las manchas)
Offset (pulsa xyz para verlo por eje)X $T * 0.7, Y $T * -0.5, Z 0: el mapa viaja
AnimationAnimated✔, Pulse Duration 1.5 (qué tan rápido cambia)
FractalTypeStandard (fBm), Max Octaves 3, Roughness 0.45
mapa_de_ruido con Animation y Fractal abiertos.
  1. Debajo, Tab → Attribute Wrangle, nombre desplazar_por_ruido:
VEX · desplazar_por_ruido
// Empuja el borde por la normal radial segun el valor del mapa de ruido (en metros).
// Se escala con el radio: un portal pequeno se deforma proporcionalmente igual.
// chf("../anillo_emisor/radx") = el Radius X del nodo anillo_emisor (paso 2).
// ".." sube de este wrangle a /obj/portal, donde esta anillo_emisor.
float escala = chf("../anillo_emisor/radx") / 2.0;
@P += v@N * f@ruido * escala;
¿Por qué un wrangle y no "Noise Along Vector"?El Attribute Noise tiene una opción para desplazar a lo largo de un vector, pero con un atributo Float no movió los puntos. El wrangle hace lo mismo y además escala la deformación con el radio, así que el portal pequeño no se deforma de más.
  1. Debajo, Tab → Null, nombre OUT_anillo. Es la salida del anillo deformado; la usan las chispas y la línea brillante.
4

La velocidad inicial de las chispas

Cada punto del anillo guarda una velocidad v. La POP Source la hereda y así las chispas nacen ya en movimiento: barren el borde (dirección tangente) y salen hacia afuera.

Debajo de OUT_anillo, Tab → Attribute Wrangle, nombre velocidad_tangencial:

VEX · velocidad_tangencial
// Velocidad inicial de cada chispa: barre el borde del portal y sale hacia afuera
vector eje = {0, 0, 1};                 // normal del portal
vector r   = normalize(set(@P.x, @P.y, 0));
float giro   = chf("vel_giro");         // a lo largo del borde
float salida = chf("vel_salida");       // hacia afuera

// Aleatoriedad por chispa: la mayoria normales, pocas muy rapidas (cola larga).
// Asi el borde exterior no termina en un anillo parejo.
float sem = @ptnum * 13.7 + @Frame * 7.1;
float k = fit01(pow(rand(sem), chf("sesgo")), chf("vel_min"), chf("vel_max"));
// Rafagas: el ruido a lo largo del anillo hace zonas con mas y menos fuerza
float rafaga = fit(noise(set(@ptnum * 0.05, @Frame * 0.15, 0)), 0.3, 0.7, 0.6, 1.4);

v@v = (cross(eje, r) * giro + r * salida) * k * rafaga;
v@v += (rand(sem + 1) - 0.5) * chf("variacion");

// Portal pequeno = chispas lentas: la velocidad crece con el radio (1 a 2 m)
// Lee el Radius X del nodo anillo_emisor (paso 2): 0.24 al inicio, 2 al final
float escala_radio = chf("../anillo_emisor/radx") / 2.0;
v@v *= escala_radio;

Pulsa Create spare parameters (el ícono del recuadro de arriba) y pon los valores. escala_radio no genera slider: no es un valor nuevo, lee el radio de anillo_emisor.

SliderValorQué controla
vel_giro9Velocidad a lo largo del borde (m/s)
vel_salida3.5Velocidad hacia afuera (m/s)
variacion2Ruido aleatorio en la dirección
sesgo3Más alto = menos chispas rápidas (la "cola larga")
vel_min / vel_max0.6 / 2.2Rango del multiplicador por chispa

Debajo, Tab → Null, nombre OUT_emisor.

5

El hueco del centro

Una metaball del tamaño del hueco. No se ve; solo la usa la fuerza hueco (paso 6) para empujar hacia afuera las chispas que se acercan al centro. Es el truco que usó RISE FX en la película.

NodoNombreParámetros
Metaballhueco_metaballRadius X, Y y Z: ch("../anillo_emisor/radx") * 0.9, es decir, el 90% del radio del anillo (lee el Radius X de anillo_emisor, que está en la misma red). Así el hueco crece con el portal. No se conecta a nada
6

La simulación de chispas

Aquí nacen y se mueven las chispas. Todo pasa dentro de un DOP Network (una red de simulación): una fuente crea partículas sobre el anillo, varias fuerzas las empujan y un solver calcula su movimiento frame a frame.

Vas a crear 10 nodos. Esta es la lista; abajo va cada uno explicado, con sus valores pestaña por pestaña y su captura.

#Nodo (lo que escribes después de Tab)NombrePara qué sirve
6.2POP ObjectchispasEl «contenedor» de las partículas: cuánto rebotan y cuánto se frenan al chocar
6.3POP Sourcefuente_anilloCrea las chispas sobre los puntos del anillo
6.4POP Wranglevida_aleatoriaLe da a cada chispa una vida y un brillo distintos
6.5POP Axis Forcegiro_portalLas hace girar alrededor del centro del portal
6.6POP Metaball ForcehuecoLas saca del centro para que el hueco quede limpio
6.7POP DragfrenadoLas frena un poco, como el aire
6.8POP Forcegravedad_y_ruidoGravedad suave y un temblor aleatorio
6.9POP SolverpopsolverEl que calcula el movimiento
6.10Ground PlanepisoUn piso invisible donde rebotan
6.11MergecolisionesJunta el piso con las chispas para que choquen
Cómo llenar un campo «SOP» o «SOP Path» sin escribir la rutaVarios nodos de este paso leen geometría que está fuera del DOP Network (el anillo, la metaball). A la derecha del campo hay un botón que abre el selector de nodos (pasa el ratón encima y lo dice): navega a portal, elige el nodo (por ejemplo OUT_emisor) y acepta. Houdini escribe la ruta solo. Si la escribes a mano, recuerda que ../../ significa «sube dos niveles»: uno para salir del nodo en el que estás y otro para salir de chispas_sim, hasta /obj/portal (ver el recuadro Cómo leer las rutas al inicio).

6.1 Crea el DOP Network

  1. En /obj/portal, Tab → DOP Network, nombre chispas_sim.
  2. Entra en él (doble clic). Puede traer un nodo output adentro: déjalo, es la salida de la simulación.

6.2 chispas (POP Object)

Tab → POP Object, nombre chispas. Es el objeto que guarda todas las partículas.

  • Pestaña Physical: Bounce 0.35 (cuánto rebotan), Friction 0.5 y Dynamic Friction Scale 0.5 (cuánto se frenan al arrastrarse por el piso). El resto, por defecto.
chispas, pestaña Physical. El rebote se ajusta aquí y en el piso (6.10), porque los dos valores se combinan.

6.3 fuente_anillo (POP Source)

Tab → POP Source, nombre fuente_anillo. Crea chispas nuevas en cada frame sobre los puntos del anillo. Tiene tres pestañas que importan:

Pestaña Source (de dónde salen):

  • Emission Type: Points (una chispa por punto del anillo, no repartidas sobre una superficie).
  • SOP: ../../OUT_emisor. Es el Null OUT_emisor del paso 4, la salida del anillo con la velocidad ya calculada. Usa el botón del selector de nodos para elegirlo o escribe la ruta: ../../ sube de fuente_anillo a /obj/portal.
Pestaña Source: Emission Type Points y la ruta a OUT_emisor.

Pestaña Birth (cuántas y cuánto viven):

  • Const. Activation: 1 (la fuente está encendida).
  • Const. Birth Rate: 10 000 (chispas nuevas por segundo).
  • Life Expectancy: 0.5 (viven medio segundo)…
  • …Life Variance: 0.25 (…más o menos un cuarto de segundo, para que no mueran todas a la vez).
Pestaña Birth. La vida es corta a propósito: así las chispas de arriba no alcanzan a frenarse y caer como polvo.

Pestaña Attributes (con qué velocidad nacen):

  • Initial Velocity: Use inherited velocity (usar la velocidad v que guardó el anillo en el paso 4).
  • Inherit Velocity: 1 (toda esa velocidad, sin reducirla).
Pestaña Attributes. Sin Use inherited velocity, la velocidad del paso 4 no sirve de nada y las chispas nacen quietas.

6.4 vida_aleatoria (POP Wrangle)

Tab → POP Wrangle, nombre vida_aleatoria, y pega este código en el cuadro VEXpression. Le da a cada chispa, una sola vez al nacer, una vida y un brillo propios: la mayoría vive poco y unas pocas mucho, así no forman un anillo parejo.

VEX · vida_aleatoria
// Una sola vez por chispa: vida con cola larga para que no mueran todas a la misma distancia
if (i@vida_ok == 0) {
    float u = rand(i@id * 3.17 + 0.5);
    f@life *= fit01(pow(u, 2.5), 0.6, 1.5);
    f@brillo = fit01(rand(i@id * 1.91), 0.55, 1.25);
    i@vida_ok = 1;
}

Este código no usa sliders: no hace falta Create spare parameters.

6.5 giro_portal (POP Axis Force)

Tab → POP Axis Force, nombre giro_portal. Crea una fuerza que hace girar las partículas alrededor de un eje, como un remolino.

Pestaña Shape (dónde actúa la fuerza):

  • Shape: Sphere (actúa dentro de una esfera).
  • Center: 0, 0, 0 (el centro del portal).
  • Axis: 0, 0, 1 (el eje Z: el portal está de pie mirando a la cámara, así que gira alrededor de Z).
  • Radius: escribe la expresión ch("../../anillo_emisor/radx") * 1.3. Quiere decir: el radio del anillo × 1.3, para que la fuerza siempre cubra un poco más que el anillo mientras crece. (../../ sube de giro_portal a /obj/portal; radx es el Radius X del Circle anillo_emisor.)

Pestaña Speed (qué tan fuerte gira):

  • Orbit Speed: escribe la expresión 1.5 * ch("../../anillo_emisor/radx") / 2. Vale 1.5 cuando el anillo mide 2 m y menos cuando es chico, así el portal pequeño gira más lento.
  • Lift Speed: 0 (no las empuja a lo largo del eje).
  • Suction Speed: 0 (no las jala hacia el centro).
giro_portal, pestaña Speed en el frame 73: el campo Orbit Speed muestra 1.5 porque ahí el anillo ya mide 2 m. Los campos con expresión se ponen en color.

6.6 hueco (POP Metaball Force)

Tab → POP Metaball Force, nombre hueco. Empuja hacia afuera las chispas que entran a la metaball del paso 5, para que el centro del portal quede limpio.

  • Geometry Source: SOP (usar una geometría de afuera del DOP).
  • SOP Path: ../../hueco_metaball (la metaball del paso 5; elígela con el botón del selector de nodos).
  • Force Scale: 30 (qué tan fuerte empuja).

6.7 frenado (POP Drag)

Tab → POP Drag, nombre frenado. Simula la resistencia del aire.

  • Air Resistance: 0.3. Bajo a propósito: con mucho frenado las chispas se detienen y, en el paso 7, sus líneas se encogen a puntos.

6.8 gravedad_y_ruido (POP Force)

Tab → POP Force, nombre gravedad_y_ruido. Una sola fuerza hace dos cosas:

  • Force: 0, -2, 0. Una gravedad suave hacia abajo (la real sería -9.8; aquí se quiere que caigan poco).
  • Pestaña Noise: Amplitude 1.5 (qué tanto tiemblan) y Swirl Size 0.5 (qué tan chicos son los remolinos del temblor).

6.9 popsolver (POP Solver)

Tab → POP Solver, nombre popsolver. No se cambia nada. Es el nodo que calcula cómo se mueven las partículas con todas las fuerzas. Tiene tres entradas (de izquierda a derecha): 1 Object, 2 Pre-Solve y 3 Sources.

6.10 piso (Ground Plane)

Tab → Ground Plane, nombre piso. Es un piso infinito e invisible para que las chispas reboten; el piso que se ve en el render es otro objeto (paso 9).

  • Center Y: -2.6 (a esa altura está el suelo).
  • Pestaña Physical: Bounce 0.35, Friction 0.5, Dynamic Friction Scale 0.5 (los mismos valores que chispas).

6.11 colisiones (Merge)

Tab → Merge, nombre colisiones. Junta el piso y las chispas. Se deja por defecto: su relación es Collide, así que lo que entra a la izquierda (el piso) choca con lo de la derecha (las chispas).

6.12 Conecta todo

Arrastra desde la salida de un nodo (abajo) hasta la entrada de otro (arriba). El orden de las entradas importa:

  1. La cadena de la fuente: fuente_anillo → vida_aleatoria → giro_portal → hueco → frenado → gravedad_y_ruido, y de ahí a la 3ª entrada (la de la derecha, Sources) del popsolver.
  2. El objeto: chispas → 1ª entrada (la de la izquierda, Object) del popsolver.
  3. El piso: piso → 1ª entrada (izquierda) de colisiones; popsolver → 2ª entrada de colisiones. Si los inviertes, las chispas atraviesan el piso.
  4. La salida: colisiones → output. Pon el display flag (azul) en output.
Así queda la red dentro de chispas_sim. La cadena de la fuente llega a la 3ª entrada del popsolver y el piso a la izquierda de colisiones.
CompruebaDale Play desde el frame 1. Desde los primeros frames deberían salir chispas del anillo, girando. Si no aparece nada: revisa la ruta de fuente_anillo (6.3) y que la cadena llegue a la 3ª entrada del popsolver. Si cambias un valor y no ves el cambio, pulsa Reset Simulation (o regresa al frame 1).
7

Sacar las chispas a SOPs, color y líneas

Sal del DOP Network (U) y, en /obj/portal:

  1. Tab → DOP Import, nombre importar_chispas: DOP Network ../chispas_sim (el DOP Network del paso 6; aquí solo se sube un nivel porque importar_chispas está en /obj/portal, junto a él), Objects chispas (el POP Object de adentro), Import Style Fetch Geometry from DOP Network.
  1. Debajo, Attribute Wrangle color_por_edad. El color depende de la edad de la chispa:
VEX · color_por_edad
// Color y tamano de cada chispa segun su edad (0 = nace, 1 = muere)
float t = clamp(f@age / max(f@life, 1e-4), 0, 1);
vector amarillo = {1.0, 0.75, 0.3};
vector naranja  = {1.0, 0.3, 0.03};
vector rojo     = {0.6, 0.05, 0.0};
v@Cd = t < 0.12 ? lerp(amarillo, naranja, t / 0.12)
     : t < 0.55 ? naranja
     : lerp(naranja, rojo, (t - 0.55) / 0.45);
v@Cd *= f@brillo;   // cada chispa con su propio brillo
f@pscale = 0.012 * (1 - t);
f@Alpha = 1 - t * t;
  1. Debajo, Attribute Wrangle estelas_lineas. La clave del look: cada partícula se vuelve una línea corta hacia atrás, según su velocidad.
VEX · estelas_lineas
// Convierte cada chispa en una linea: de su posicion hacia atras, segun su velocidad.
// Asi se ve como una chispa (raya por la velocidad) y no como un punto de fuego artificial.
float largo = chf("largo_seg");                 // segundos de recorrido que muestra la estela
float vmin  = chf("largo_min");                 // largo minimo en metros
vector dir = v@v * largo;
if (length(dir) < vmin) dir = normalize(v@v + {0,1e-5,0}) * vmin;
int cola = addpoint(0, @P - dir);
setpointattrib(0, "Cd", cola, v@Cd * chf("brillo_cola"));
setpointattrib(0, "Alpha", cola, 0.0);
setpointattrib(0, "pscale", cola, f@pscale * 0.3);
addprim(0, "polyline", @ptnum, cola);

Sliders: largo_seg 0.07, largo_min 0.12, brillo_cola 0.15.

  1. Debajo, Null OUT_chispas con el display flag.
CompruebaVe al frame 73. Deberías ver un anillo de ~2 m con rayas naranjas que salen girando y un centro limpio. En el Geometry Spreadsheet de importar_chispas hay ~4300 puntos. Si no pasa nada, revisa las conexiones del solver y el SOP Path de fuente_anillo.
8

Revisar el movimiento (OpenGL)

  1. En /obj, Tab → Camera, nombre cam_preview: Translate (0, 0, 16), Resolution 1280 × 720 (focal 50, por defecto). El formato final cuadrado se pone en los ROPs.
  1. En /out, Tab → OpenGL, nombre preview_opengl: Camera /obj/cam_preview, Valid Frame Range Render Frame Range de 1 a 240, Override Camera Resolution ✔ 1024 × 1024, Output Picture $HIP/render/preview_opengl_1024/portal_$F3.jpg. Render.
Cuadrado en OpenGL vs. KarmaAl pasar a cuadrado, OpenGL conserva el ancho del encuadre y agrega alto (el portal se ve más chico, con espacio arriba). Karma conserva el alto y recorta los lados (el portal llena el cuadro). Para revisar movimiento no importa; el encuadre que vale es el de Karma.
  1. Arma el video y revísalo (opcional, con ffmpeg):
Terminal
ffmpeg -framerate 24 -i portal_%03d.jpg -c:v libx264 -crf 18 -pix_fmt yuv420p preview.mp4

Tarda ~10 s para los 240 frames. Ajusta la simulación hasta que el movimiento te guste y solo entonces sigue con el look.

Reset SimulationSi cambias un parámetro del POP y no ves el cambio, entra a chispas_sim y pulsa Reset Simulation (o cambia de frame al 1). Si no, se ve la caché vieja.
9

Look (anillo brillante, piso y luz)

  1. Chispas para render. En /obj/portal, debajo de estelas_lineas (en paralelo a OUT_chispas), Attribute Wrangle look_render:
VEX · look_render
// Look para Karma: color HDR (emision) y grosor de las curvas
float a = f@Alpha;
v@Cd *= chf("intensidad") * a;          // la cola (Alpha 0) queda negra = se desvanece
f@width = max(f@pscale, 0.0005) * chf("grosor");

Sliders: intensidad 3.5, grosor 0.6. Debajo, Null RENDER_chispas con el render flag (morado). El display flag se queda en OUT_chispas.

  1. Anillo brillante. En /obj, Tab → Geometry anillo_brillante. Adentro:
    • Object Merge traer_anillo: Object 1 /obj/portal/OUT_anillo.
    • Attribute Wrangle look_anillo, con Run Over Detail (only once). Rehace el borde como 6 hebras con ruido suave, para que se vea deshilachado y no como una línea limpia:
VEX · look_anillo
// Anillo deshilachado: varias hebras alrededor del borde, cada una con ruido propio.
// El ruido se calcula sobre el ANGULO (cos, sin) y con baja frecuencia:
// las hebras ondulan suave, sin zigzag, y el circulo cierra sin corte.
int hebras = chi("hebras");
int npts = npoints(0);
for (int h = 0; h < hebras; h++) {
    int prev = -1;
    for (int i = 0; i < npts; i++) {
        vector p = point(0, "P", i);
        vector r = normalize(set(p.x, p.y, 0));
        vector d = r * chf("ondas") + set(h * 3.1, h * 7.7, @Time * chf("velocidad"));
        float n  = noise(d);                         // ondulacion de la hebra (suave)
        float n2 = noise(d * 4 + set(0, 0, @Time * 6)); // titileo del brillo (rapido, no mueve la forma)
        vector q = p + r * (n - 0.5) * chf("deshilachado") + {0,0,1} * (n2 - 0.5) * 0.03;
        int pt = addpoint(0, q);
        float b = h == 0 ? 1.0 : fit01(rand(h * 4.3), 0.25, 0.7);   // hebra 0 = nucleo
        vector c = lerp({1.0, 0.42, 0.08}, {1.0, 0.72, 0.32}, b);
        setpointattrib(0, "Cd", pt, c * fit(n2, 0.3, 0.7, 1.5, 4) * b);
        setpointattrib(0, "width", pt, (h == 0 ? 0.014 : 0.006) * fit(n2, 0.3, 0.7, 0.7, 1.3));
        if (prev >= 0) addprim(0, "polyline", prev, pt);
        prev = pt;
    }
}
// quita el circulo original y sus puntos (solo quedan las hebras)
removeprim(0, 0, 0);
for (int i = 0; i < npts; i++) removepoint(0, i);

Sliders: hebras 6, deshilachado 0.12, ondas 3, velocidad 1.5.

    • Null RENDER_anillo con display y render flag.
  1. Piso visible. En /obj, Tab → Geometry piso; adentro, un Grid de 300 × 300 con Center Y -2.6. Déjale el display flag encendido: si lo apagas, Karma no lo renderiza.
  2. Luz. En /obj, Tab → Point Light, nombre luz_portal: Translate (0, -1.7, 0.3), Color (1, 0.4, 0.1), Exposure 4.5, Intensity (1 - pow(1 - clamp(($F - 1) / 60, 0, 1), 3)) (crece con el portal). Las chispas son tan delgadas que casi no iluminan el piso; esta luz lo simula.
10

Materiales (MaterialX)

  1. En /mat, Tab → Karma Material Builder, tres veces: chispas_emisivo, anillo_emisivo y piso_oscuro.
  2. En chispas_emisivo y anillo_emisivo, entra y:
    • En mtlxstandard_surface: Base 0, Specular 0, Emission 1.
    • Tab → MtlX Geometry Property Value, nombre color_Cd: Signature Vector 3, Geomprop displayColor. Conecta su salida a emission_color del mtlxstandard_surface.
  3. En piso_oscuro, en mtlxstandard_surface: Base Color (0.12, 0.1, 0.09), Specular 0.5, Specular Roughness 0.55.
  4. Asigna cada material en la pestaña Render → Material del objeto: portal → /mat/chispas_emisivo, anillo_brillante → /mat/anillo_emisivo, piso → /mat/piso_oscuro.
Dentro de chispas_emisivo: color_Cd → emission_color del Standard Surface.
displayColor, no CdAl pasar a USD (Karma), el color Cd de SOPs se llama displayColor. Si pones Cd en Geomprop, las chispas salen negras.
11

Render con Karma

  1. En /out, Tab → Karma, nombre karma_look: Camera /obj/cam_preview, Resolution 1024 × 1024, Rendering Engine XPU (CPU si no tienes NVIDIA RTX), Path Traced Samples 256, Output Picture $HIP/render/karma/portal.$F4.exr.
  2. Pestaña Rendering → Camera Effects: desactiva Motion Blur. La estela ya está hecha en la geometría (paso 7); con blur quedaría doble.
  3. Pestaña Objects: Exclude Objects MCP_* (solo si tienes esas cámaras de Claude).
  4. Prueba un solo frame (Start/End 73 73) y revisa el look. Luego pon 1 a 240 y Render to Disk.
karma_look configurado para el frame de prueba (73). Para el render completo, Start/End 1 y 240.

Para renderizar sin abrir Houdini (en la Venator, con el script render_karma.py):

Terminal
hython ../render_karma.py "experimento de houdini 5 (portal de dr strange).hipnc" --rop /out/karma_look --frames 1 240 --out render/karma_v1

Para ver un EXR como PNG: hoiiotool frame.exr --ch R,G,B --colorconvert linear sRGB -o frame.png.

Cuánto tardaCon una RTX 4060 (XPU) y un Ryzen 5 5600G, un frame suelto tarda ~12 s (incluye simular hasta ese frame) y la secuencia ~10.7 s por frame: ~43 min los 240 frames. El cuello de botella es el procesador (simular y pasar miles de curvas a USD), no la tarjeta. Para renders repetidos conviene un File Cache después de estelas_lineas, así el render solo lee geometría. Con el humo del paso 12 sube a ~18 s por frame en promedio (de ~9 s al inicio a ~22 s al final): ~75 min.

El glow de la referencia no se hace en el render: se agrega en composición (paso 13).

12

Humo leve en el aro (opcional)

Un humo 3D suave que sale del aro y brilla naranja. Es opcional: las chispas lo tapan bastante, y se nota sobre todo después de la composición, como un halo rojizo y una bruma. Por eso lleva un interruptor para prenderlo y apagarlo.

  1. Interruptor. En /obj, Tab → Null, nombre CONTROLES. Engrane → Edit Parameter Interface → arrastra un Toggle: Name humo, Label Humo en el aro, Default encendido → Accept.
  2. Objeto. En /obj, Tab → Geometry, nombre humo. En su parámetro Display (pestaña Transform, abajo) escribe la expresión ch("../CONTROLES/humo"): lee el interruptor humo del Null CONTROLES (paso 12.1). .. sube del objeto humo a /obj, donde está CONTROLES. Vale 1 si está encendido y 0 si no. Como en /obj el display también es la visibilidad en Karma, apagado no aparece ni se renderiza, y no cuesta tiempo.
  3. Entra a humo y crea:
NodoNombreParámetros
Object Mergetraer_anilloObject 1 /obj/portal/OUT_anillo (el aro deformado, que crece con el portal)
Attribute Wranglefuente_humoEl código de abajo
Volume Rasterize Attributesrasterizar_fuenteAttributes density temperature v, Voxel Size 0.04
Pyro Solversim_humoVoxel Size 0.04. Shape: Dissipation 0.35, Buoyancy Scale 0.4, Disturbance 0.4, Turbulence 0.3 (Swirl Size 0.5). Sparse activado (por defecto)
Blastdensidad_y_temperaturaGroup Type Primitives, Group @name=flame @name=vel.*. Quita lo que no se renderiza para que la caché pese menos. No borres temperature
File Cachecache_humoBase Folder $HIP/cache, Base Name humo_portal, frames 1–240. Save to Disk y después activa Load from Disk
Materialmaterial_humoMaterial /mat/humo_pyro
NullOUT_humoDisplay y render flag
VEX · fuente_humo
// Puntos fuente del humo sobre el aro: densidad leve con ruido y un empuje hacia afuera
vector r = normalize(set(@P.x, @P.y, 0));
float n = noise(set(@P.x * 2, @P.y * 2, @Time * 0.8));
f@density = chf("densidad") * fit(n, 0.35, 0.65, 0, 1);   // manchas: no sale parejo en todo el aro
f@temperature = chf("temperatura");
v@v = r * chf("salida") + cross({0,0,1}, r) * chf("giro");
f@pscale = chf("tamano");

Sliders: densidad 0.6, temperatura 0.4, salida 0.6, giro 1, tamano 0.08.

  1. Material. En /mat, Tab → Karma Material Builder, nombre humo_pyro. Adentro, borra el Standard Surface, crea un Karma Pyro Shader (shader_humo) y conecta su salida a volume de Material_Outputs_and_AOVs:
    • Smoke Color (1, 0.6, 0.35), Smoke Brightness 3, Density 1.
    • Enable Fire ✔: Intensity Scale 4, Source Range de intensidad y de color 0 a 0.4, Enable Mask ✔.
    Con el fuego activado, el humo brilla naranja por sí mismo y no hace falta otra luz.
  2. Vuelve a renderizar con el paso 11, con karma_look sin cambios. El humo entra solo, porque el objeto humo está visible.
Solo el humo (con portal, anillo y piso apagados), frame 73.
Frame 73 compuesto: sin humo y con humo leve.
Tres cosas que no funcionan
  1. El material en el objeto. Si pones el material en Render → Material del objeto humo, Karma lo ignora en volúmenes. Tiene que ir en el Material SOP de adentro.
  2. Una luz solo para el humo. El Light Mask del objeto no se respeta con el Karma de /out, y la luz aclara también el piso. Usa la emisión del fuego.
  3. Fuego sin efecto. El fuego lee temperature en un rango de 0 a 1, pero aquí la temperatura llega a 0.4. Pon el Source Range en 0–0.4.
13

Composición en Copernicus (glow)

El glow, el tono y el color se agregan después del render, dentro de Houdini, con Copernicus (los COPs nuevos). Así se ajusta en segundos sin volver a renderizar.

  1. En /img, Tab → Copernicus Network, nombre comp_portal. Entra y crea:
NodoNombreParámetros
Filerender_karmaFile Name $HIP/render/karma/portal.$F4.exr (la salida del paso 11). En la lista de AOVs: File AOV C, Channel Type RGB (no RGBA). Border Constant
Glowglow_cercaGlow Threshold 0.7, Brightness 0.7, Size 0.01, Tint (1, 0.7, 0.4): halo apretado en el anillo y las chispas
Glowglow_lejosGlow Threshold 0.35, Brightness 1, Size 0.1, Tint (1, 0.35, 0.08): resplandor naranja grande, también hacia el hueco
Tonemapcurva_filmicaOperator Hable Filmic, Exposure 1.3: suaviza las luces altas para que el piso no se queme
HSVsaturacionHue Shift −9, Saturation Scale 1.35: el tonemap empuja el naranja hacia dorado, y esto lo regresa
OutputOUT_compConectado a saturacion
  1. Guardar la secuencia. En la misma red, Tab → ROP Image, nombre guardar_comp:
    • COP Path /img/comp_portal/saturacion (el nodo con la imagen, no el OUT_comp).
    • AOV Name hsv_adjust: el nombre de la salida de ese nodo (pasa el ratón sobre su salida para verlo).
    • Output File $HIP/render/comp/portal_comp.$F4.png.
    • Valid Frame Range Render Frame Range, 1 a 240. Render.
  2. Video con ffmpeg:
Terminal
ffmpeg -framerate 24 -i render/comp/portal_comp.%04d.png -c:v libx264 -crf 18 -pix_fmt yuv420p portal_final.mp4
Frame 73: el render crudo de Karma y la composición.

Para componer sin abrir Houdini (en la Venator, con el script comp_copernicus.py, después de render_karma.py):

Terminal
hython ../comp_copernicus.py "experimento de houdini 5 (portal de dr strange).hipnc" --entrada render/karma_final_humo --salida render/comp_final --nombre portal_final

Tarda ~35 s los 240 frames (con la RTX 4060 por OpenCL).

Lo que costó
  1. "No valid output AOVs specified". El ROP Image necesita el COP Path del nodo con la imagen y, en AOV Name, el nombre de su salida (glow, tonemap, hsv_adjust…). El File también necesita un AOV declarado (C), o no tiene salidas.
  2. Bloques y una costura a la mitad (arriba sin glow, abajo con glow): pasa al cargar el EXR como RGBA. Cárgalo como RGB; el fondo es negro y el alfa no hace falta.
  3. El piso aparece arriba de la imagen: el Border por defecto (Wrap) repite la imagen al hacer el blur. Usa Constant.
  4. Lento o con aviso "Falling back to built-in CPU OpenCL driver": Copernicus usa OpenCL. En Linux con NVIDIA instala opencl-nvidia (sudo pacman -S opencl-nvidia en Arch); clinfo -l debe mostrar la tarjeta.
El error de la costura (EXR cargado como RGBA).

Cómo sé que me salió bien

En el frame 73 deberías tener, más o menos:

  • ~4300 chispas vivas (importar_chispas) y el mismo número de líneas en estelas_lineas.
  • El borde del anillo entre 1.9 y 2.15 m del centro: no es un círculo perfecto y la forma cambia con el tiempo.
  • El centro limpio y unas ~400–500 chispas sobre el piso (y = -2.6).
  • En Karma: chispas naranjas hacia afuera (no amarillas), un anillo amarillo deshilachado y un brillo naranja suave en el piso.
  • Con humo: una bruma rojiza leve alrededor del aro, que se nota más después de la composición.
  • En la composición: un halo naranja alrededor del anillo y de las chispas que entra un poco al hueco, sin costuras ni bloques, y el piso sin quemarse.

Si algo sale mal

SíntomaCausa probableSolución
Las chispas parecen fuegos artificiales (puntos)Falta estelas_lineasPaso 7.3. Las chispas reales se ven como rayas
Las líneas parecen pelo pegado al anilloDemasiado drag o chispas lentasAir Resistance 0.3, vel_giro 9, vel_salida 3.5
Las chispas de arriba se frenan y caen como polvoViven demasiadoVida corta (0.5 ± 0.25 s, multiplicador 0.6–1.5) y más Birth Rate para compensar
El anillo brillante es una línea quebrada (zigzag)Ruido de alta frecuencia por número de punto en look_anilloRuido por ángulo y de baja frecuencia, como en el código del paso 9
Las chispas no se mueven al nacerNo heredan la velocidadfuente_anillo → Attributes → Use inherited velocity
Chispas dentro del huecoFuerza hueco débil o metaball pequeñaForce Scale 30 y radio de la metaball = radio del anillo (radx de anillo_emisor) × 0.9
Atraviesan el pisoGround plane a la derecha del mergepiso en la 1ª entrada (izquierda) de colisiones
Un "chorro" enorme al inicioLa velocidad no escala con el radioLas últimas líneas de velocidad_tangencial (las que leen el radio de anillo_emisor)
Las chispas salen negras en KarmaGeomprop CdUsa displayColor
Las chispas salen amarillas en KarmaEl HDR satura el naranjaColores más saturados e intensidad 3.5, no 6
El piso no sale en KarmaDisplay flag del objeto piso apagadoEnciéndelo: en /obj el display flag es la visibilidad
Reflejo vertical muy marcado en el pisoPiso muy lisoSpecular Roughness 0.55
Cambio un valor y no pasa nadaCaché de la simulaciónReset Simulation en chispas_sim
No hay sliders / están en 0No se crearon los parámetrosCreate spare parameters y escribe los valores
look_anillo sale vacíoBorré el círculo con removeprim(0, 0, 1)Usa removeprim(0, 0, 0) y luego removepoint como en el código
El humo sale con otro color o grisMaterial asignado en el objetoAsígnalo con un Material SOP dentro de humo (paso 12)
El humo no brillaRango de fuego 0–1 o temperature borradaSource Range 0–0.4 y no borres temperature en el Blast
El humo no sale en el renderInterruptor apagado o caché vacíaCONTROLES → Humo en el aro ✔ y cache_humo con Load from Disk tras guardarla
ROP Image: "No valid output AOVs specified"COP Path o AOV Name malCOP Path al nodo saturacion y AOV Name hsv_adjust
Glow con bloques o una costura horizontalEXR cargado como RGBAChannel Type RGB en el File
El piso aparece arriba en la compBorder WrapBorder Constant en el File

Para entenderlo

  • Por qué líneas: una chispa real se mueve muy rápido y el ojo (o la cámara) ve su recorrido. estelas_lineas dibuja ese recorrido como un motion blur hecho en geometría. Así se ve desde el viewport y se controla el largo.
  • Velocidad tangencial: cross(eje, r) da la dirección que "barre" el borde del círculo. Sumarle algo de r (hacia afuera) hace que salgan en espiral.
  • Cola larga: con pow(rand, 3) la mayoría de las chispas tiene valores bajos y unas pocas valores altos. Así unas pocas vuelan lejos y el borde exterior se ve irregular, no como un anillo parejo.
  • Mapa de ruido vs. fórmula: antes de usar el mapa de ruido se probaron deformaciones por ángulo (respiración, óvalo, temblor, abolladuras), y se veían demasiado matemáticas. Un ruido 3D que se mueve por el espacio se ve más orgánico.
  • Fuerza Metaball: RISE FX la usó en la película para que las chispas no entraran al círculo. Un objeto de colisión en el centro funciona peor y no debe verse en el render.
  • Por qué el glow en composición: en el render cada cambio de glow costaría ~45 min; en Copernicus se ajusta mirando el resultado y se aplica a los 240 frames en segundos. Dos glows (uno chico y fuerte, otro grande y suave) se parecen más a cómo una cámara real ve una luz muy intensa que uno solo.
  • Por qué el humo brilla solo: con una luz extra se iluminaba también el piso. La emisión del fuego del Pyro Shader solo afecta al humo.

Variaciones para probar

  • Más caótico: sube Amplitude en mapa_de_ruido (0.5–0.6) o baja Pulse Duration.
  • Más chispas lejanas: baja sesgo (2) o sube vel_max.
  • Portal más grande: cambia el 2 * de la expresión del radio (todo lo demás lo sigue).
  • Sin piso: desactiva el piso (DOP) y el objeto piso; las chispas caen al vacío.
  • Otro color (magia verde, azul): cambia los tres colores de color_por_edad y los del anillo en look_anillo.