Houdini 22.0 · POPs + Vortex Force · Karma

Tornado de partículas en Houdini

Paso a paso para construir un embudo de partículas que gira, sube, serpentea y se deshace en la punta, sobre un piso, y renderizarlo con Karma. Es la prueba 3b del experimento de la clase de Simulación.

Frames 1–168 (7 s a 24 fps), Karma XPU, 1280 × 720. La marca de agua es de la licencia Apprentice.

Altura
~50 m
Partículas
~34 000
Simulación
~30 s
Armarlo
45–60 min

La idea en cuatro líneas

  1. Un círculo en el piso suelta partículas.
  2. Una línea vertical es el eje del tornado. Cada punto de la línea dice qué tan ancho es el embudo a esa altura.
  3. Un Vortex Force hace girar y subir las partículas alrededor de esa línea.
  4. Ruido y una regla de "muerte" por altura lo vuelven irregular, como un tornado de verdad.

Antes de empezar

  • Versión: Houdini 22.0 Apprentice. En Indie o FX es igual; en versiones anteriores algunos nodos pueden verse distintos.
  • Unidades: 1 unidad = 1 metro. El eje Y apunta hacia arriba.
  • Animación: 24 fps, frames 1 a 240 (el reloj abajo a la derecha, Global Animation Options).
  • Crear nodos: dentro de una red pulsa Tab y escribe el nombre (p. ej. grid). Doble clic o I para entrar a un nodo; U para salir.
  • Renombra cada nodo como dice la guía (doble clic en el nombre). Algunas rutas (SOP Path) usan esos nombres.
  • Display flag (la bandera azul a la derecha del nodo): indica qué nodo se ve en el viewport.
Los códigos VEXLos tres códigos usan chf("nombre") para que cada valor sea un slider. Después de pegar el código en un Wrangle, pulsa 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. Los sliders aparecen en 0: escribe los valores de la tabla.
Cómo leer las rutas y los nombres de esta guía
  • Rutas como /obj/tornado_vortex/emisor: son la «dirección» de un nodo, como las carpetas de una computadora. /obj es el nivel de objetos, tornado_vortex el objeto del paso 1 y emisor el nodo de adentro. Se escriben en campos como SOP Path para que un nodo lea la geometría de otro sin cable. Para no escribirlas a mano, pulsa el botón que está a la derecha del campo (abre un selector de nodos) y elige el nodo.
  • Siglas de los niveles de Houdini: SOP = redes de geometría (dentro de tornado_vortex); DOP = redes de simulación (sim_tornado); POP = los nodos de partículas que van dentro de un DOP; ROP = nodos de render en /out.
  • Object Mask (en el DOP Import) es el nombre del objeto de la simulación que quieres sacar: aquí particulas, el POP Object del paso 7.
  • Nombres como merge1 o gravity1: son los que Houdini pone por defecto; no hace falta cambiarlos.
  • 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: $T = el tiempo actual en segundos; $HIP = la carpeta donde está guardado tu .hipnc; $F4 = el número de frame con 4 dígitos (0073).

Mapa de lo que vas a construir/obj/tornado_vortex y /obj/tornado_vortex/sim_tornado

Dos redes: la de geometría (SOPs), con el piso, el emisor y el eje; y la de simulación (DOPs), donde se mueven las partículas.

Red SOP dentro de /obj/tornado_vortex. El display flag está en OUT. El aviso amarillo del merge es normal: las partículas y el piso tienen atributos distintos.
Red DOP dentro de sim_tornado. punta_dispersa llega a la 3ª entrada del solver, ground_plane a la izquierda del merge y curva_eje al costado del Vortex Force.

emisor y atributos_vortex no tienen cables hacia la simulación: los nodos de adentro los leen por su ruta (SOP Path).

1

El objeto contenedor/obj

  1. En /obj, Tab → Geometry. Llámalo tornado_vortex.
  2. Entra con doble clic. Todo lo que sigue va adentro, salvo la cámara, las luces y el render.
El nivel /obj al final: tornado_vortex, la cámara y las dos luces. (tornado es la prueba 1; no hace falta.)
2

El piso visual/obj/tornado_vortex

Este es el piso que se ve. El que choca con las partículas es el Ground Plane del paso 6, y ese no aparece en el render.

NodoNombreParámetros
Gridpiso_visualSize 120 × 120, Rows 2, Columns 2
Colorcolor_pisoColor (0.18, 0.2, 0.24). Conéctalo debajo de piso_visual
3

El emisor/obj/tornado_vortex

Un círculo acostado en el piso. Las partículas nacen sobre su superficie. No se conecta a nada.

NodoNombreParámetros
CircleemisorPrimitive Type Polygon, Orientation ZX Plane, Radius 4, 4, Divisions 48
Reverse se activa solo al elegir ZX Plane.
4

El eje del tornado/obj/tornado_vortex

Es el paso clave. Una línea vertical de 45 m cuyos puntos le dicen al Vortex Force qué tan ancho es el embudo a cada altura, qué tan rápido gira y cuánto empuja hacia arriba. Además se mueve con ruido para que la columna serpentee.

  1. Tab → Line, nombre eje_tornado: Length 45, Points 16. La dirección (0, 1, 0) es la de por defecto.
  1. Debajo, Tab → Attribute Wrangle, nombre atributos_vortex (Run Over: Points). Pega el código:
VEX · atributos_vortex
// h: 0 en el piso, 1 arriba del tornado
float h = @P.y / 45.0;
// silueta del embudo: angosto abajo, ancho arriba
f@orbitrad  = chf("radio_base") + (chf("radio_tope") - chf("radio_base")) * pow(h, chf("curvatura"));
f@orbitvel  = chf("velocidad_orbita");   // m/s tangencial
f@orbitlift = chf("lift");               // fuerza hacia arriba (la punta la controla punta_dispersa)
f@orbitmaxd = f@orbitrad * chf("alcance"); // hasta donde llega la fuerza
// la columna serpentea: el ruido crece con la altura (la base queda fija en el piso)
vector n = noise(set(h * chf("ondas"), @Time * chf("velocidad_onda"), 0.0)) - 0.5;
vector n2 = noise(set(h * chf("ondas") + 17.3, @Time * chf("velocidad_onda"), 5.1)) - 0.5;
@P.x += n.x * 2 * chf("serpenteo") * h;
@P.z += n2.x * 2 * chf("serpenteo") * h;
  1. Pulsa Create spare parameters y escribe los valores:
SliderValorQué controla
radio_base2.5Radio del embudo en el piso (m)
radio_tope18Radio arriba (m)
curvatura1.6Mayor que 1: el embudo se abre más cerca de la punta
velocidad_orbita18Velocidad de giro (m/s)
lift30Empuje hacia arriba
alcance1.6La fuerza llega hasta radio × 1.6
ondas1.5Cuántas curvas tiene la columna
velocidad_onda0.4Qué tan rápido se mueven esas curvas
serpenteo8Cuánto se desplaza la punta (m)
atributos_vortex terminado: el código y, arriba, los sliders creados.
CompruebaPon el display flag en atributos_vortex y mueve la línea de tiempo: la línea se dobla y se mueve, con la base fija en el origen. En el Geometry Spreadsheet aparecen orbitrad, orbitvel, orbitlift y orbitmaxd.

Los nombres de los atributos tienen que ser exactamente esos: son los que el Vortex Force busca por defecto.

5

La red de simulación/obj/tornado_vortex

Tab → DOP Network, nombre sim_tornado. Entra en él: los pasos 6 y 7 son adentro.

6

Los nodos de la simulación/obj/tornado_vortex/sim_tornado

Nodo (Tab →)NombreParámetros
POP ObjectparticulasPor defecto
POP Sourceemitir_basePestaña Source: Emission Type Scatter onto Surfaces, Geometry Source Use Parameter Values, SOP /obj/tornado_vortex/emisor. Pestaña Birth: Const. Birth Rate 6000, Life Expectancy 8, Life Variance 1.5
POP Forceruido_turbulenciaForce 0, 0, 0 (solo ruido). Pestaña Noise: Amplitude 8, Swirl Size 6, Pulse Length 2
POP Wranglepunta_dispersaEl código de abajo
POP Solverpop_solverPor defecto
Ground Planeground_planePor defecto: piso de colisión en y = 0
Mergemerge1Por defecto
SOP Geometrycurva_ejeUse External SOP activado, SOP Path /obj/tornado_vortex/atributos_vortex. Time se queda en $T
Vortex Forcefuerza_vortexLift Radius Multiplier 3. Los nombres de atributos se quedan por defecto
Gravity Forcegravity1Por defecto (−9.81)
emitir_base, pestaña Source.
emitir_base, pestaña Birth.
ruido_turbulencia: Force en 0 y los valores en la pestaña Noise.

Código de punta_dispersa (después pulsa Create spare parameters):

VEX · punta_dispersa
// Cada particula tiene su propia altura de "muerte" entre altura_min y altura_max,
// asi la punta queda irregular y no se apilan en una tapa.
float limite = fit01(rand(@id * 13.7), chf("altura_min"), chf("altura_max"));
float zona   = fit(@P.y, limite - chf("zona_fade"), limite, 0, 1);
// encogen al acercarse a su limite (se desvanecen en vez de cortarse)
f@pscale = 0.04 * (1 - zona);
// cerca del limite se abren hacia afuera, como el humo que sale por arriba
vector rad = set(@P.x, 0, @P.z);
v@force += normalize(rad) * chf("empuje_afuera") * zona;
if (@P.y > limite) i@dead = 1;
SliderValor
altura_min30
altura_max50
zona_fade8
empuje_afuera25
punta_dispersa con su código y sliders.
curva_eje. El campo Time sale en verde porque tiene la expresión $T: muestra el tiempo actual.
fuerza_vortex. Abajo se ven sus dos entradas: merge1 y curva_eje.
7

Conectar la simulación/obj/tornado_vortex/sim_tornado

Aquí es donde es más fácil equivocarse. Sigue el orden y compara con la captura de la red DOP del mapa.

  1. particulas → 1ª entrada (Object) de pop_solver.
  2. emitir_base → ruido_turbulencia → punta_dispersa → 3ª entrada (Sources) de pop_solver. La 2ª (Pre-Solve) queda vacía.
  3. ground_plane → 1ª entrada (izquierda) de merge1; pop_solver → 2ª entrada.
  4. merge1 → 1ª entrada de fuerza_vortex.
  5. curva_eje → 2ª entrada de fuerza_vortex.
  6. fuerza_vortex → gravity1 → output. El display flag va en output.
particulas ─────────────────────────────────────→ pop_solver  [1: Object]
emitir_base → ruido_turbulencia → punta_dispersa → pop_solver  [3: Sources]

ground_plane ─┐  (izquierda)
pop_solver ───┴→ merge1 → fuerza_vortex → gravity1 → output
                              ↑ [2]
                          curva_eje
Los dos errores típicos
  • El ground plane va a la izquierda del merge. En DOPs lo de la izquierda afecta a lo de la derecha. Si lo pones a la derecha, las partículas atraviesan el piso.
  • El Vortex Force va después del merge, en la cadena de objetos como la gravedad, no en la cadena de POPs. No es una fuerza POP, pero el POP Solver la respeta igual.
CompruebaDale Play. Desde el frame ~20 las partículas giran y suben; hacia el frame 100 ya hay un embudo de 45–50 m. Con el display en fuerza_vortex se ven los anillos guía de las órbitas.
8

Sacar las partículas a SOPs/obj/tornado_vortex

Sal de sim_tornado con U y crea:

NodoNombreParámetros
DOP Importimportar_particulasDOP Network /obj/tornado_vortex/sim_tornado, Objects particulas, Import Style Fetch Geometry from DOP Network
Attribute Wranglecolor_y_tamanoDebajo de importar_particulas. Código de abajo; slider escala_render 3
Mergeparticulas_y_pisoEntrada 1: color_y_tamano; entrada 2: color_piso
NullOUTDebajo del merge. Display flag aquí
VEX · color_y_tamano
// color por altura: polvo oscuro abajo, gris claro arriba
float h = fit(@P.y, 0, 30, 0, 1);
v@Cd = lerp({0.32,0.26,0.20}, {0.75,0.75,0.78}, h);
// pscale viene de la simulacion (punta_dispersa); se agranda para Karma
f@pscale *= chf("escala_render");

escala_render en 3 hace las partículas más gruesas para que se vean en Karma. Para OpenGL puedes dejarlo en 1.

importar_particulas.
color_y_tamano.
9

Cámara y luces/obj

NodoNombreParámetros
Cameracam_tornadoTranslate (0, 30, 135), Rotate X −2, Focal Length 35, Resolution 1280 × 720
Distant Lightluz_solRotate (−50, 30, 0)
Environment Lightluz_ambienteIntensity 0.35 (cielo azul suave)
Pestaña Transform.
Pestaña View.
10

Render con Karma/out

  1. En /out, Tab → Karma, nombre karma_tornado.
  2. Valid Frame Range Render Frame Range, frames 1 a 168. Camera /obj/cam_tornado, Resolution 1280 × 720.
  3. Output Picture: una carpeta tuya, p. ej. $HIP/render/tornado_karma.$F4.jpg.
  4. Rendering Engine: CPU si tu equipo no tiene GPU NVIDIA; XPU si tienes una RTX (mucho más rápido).
  5. Render to Disk.
La ruta de Output Picture de la captura es la de otra computadora: cambia la ruta por una tuya.

Para armar el video con los frames (opcional, con ffmpeg):

Terminal
ffmpeg -framerate 24 -i tornado_karma.%04d.jpg -c:v libx264 -crf 23 -pix_fmt yuv420p tornado.mp4
Cuánto tardaEn una laptop (CPU) cada frame tarda 6–13 s y va subiendo, porque hay más partículas. Con una RTX 4060 (XPU) baja a ~3.6 s por frame, aunque los primeros ~60 frames fueron lentos. 168 frames: ~23 min en XPU y ~32 min en CPU. Antes de un render largo, haz una prueba rápida con un ROP OpenGL con la misma cámara para revisar el encuadre.

Cómo saber si te salió bien

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

  • ~34 000 partículas vivas (Geometry Spreadsheet de importar_particulas).
  • Altura máxima de ~50 m, sin un anillo o "tapa" arriba: la cantidad baja poco a poco con la altura.
  • Muchas partículas en la base (el polvo) y casi ninguna a más de 30 m del centro.
  • La columna se dobla y se mueve; la base se queda en su lugar.
Frame 192, render OpenGL.

Si algo sale mal

SíntomaCausa probableSolución
Giran pero no subenEl lift no les llegaLift Radius Multiplier en 3. Con 1 casi ninguna partícula recibe el empuje
Salen disparadas al cielolift muy alto o falta punta_dispersalift en 30 y revisa que punta_dispersa esté conectado
Se forma una tapa plana arribaTodas mueren o se frenan a la misma alturaaltura_min y altura_max deben ser distintos (30 y 50)
Atraviesan el pisoGround plane a la derecha del mergePásalo a la 1ª entrada (izquierda)
No nace ninguna partículaFuente mal conectada o ruta mal escritapunta_dispersa va a la 3ª entrada del solver; revisa la ruta a emisor
La columna no se muevecurva_eje no lee la curva cada frameSu Time debe ser $T
El tornado se desarmaRuido muy fuerteAmplitude en 8. Con 15, la mitad se queda abajo o sale volando
Sliders en 0 o sin efectoNo se crearon los parámetrosPulsa Create spare parameters y escribe los valores
Karma no muestra las partículaspscale muy chicoSube escala_render en color_y_tamano

Para entenderlo

No hace falta para replicarlo, pero ayuda a modificarlo.

¿Qué hace el Vortex Force?

Hace orbitar las partículas alrededor de una curva. De cada punto lee el radio (orbitrad), la velocidad (orbitvel), el empuje hacia arriba (orbitlift) y el alcance (orbitmaxd). Por eso la forma del embudo se dibuja con una fórmula: cambia radio_base, radio_tope o curvatura y cambia la silueta.

¿Por qué POP Force y no POP Wind?

POP Wind funciona como viento: lleva la velocidad de las partículas hacia la del viento, y eso frenaría el giro. POP Force solo suma una aceleración, así que agrega ruido sin estorbar.

¿Por qué cada partícula muere a una altura distinta?

Si todas se frenaran a la misma altura, ahí se juntarían en una tapa, porque el lift y la gravedad se igualan. rand(@id) le da a cada partícula su propio límite, y la punta queda irregular.

Variaciones para probar
  • Tornado inclinado: cambia la dirección de eje_tornado, p. ej. (0.3, 1, 0).
  • Más ancho o más delgado: radio_base y radio_tope.
  • Más violento: sube velocidad_orbita y un poco la Amplitude del ruido.
  • Más denso: sube Const. Birth Rate. Más partículas hacen más lenta la simulación y el render.