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
- Un círculo en el piso suelta partículas.
- 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.
- Un Vortex Force hace girar y subir las partículas alrededor de esa línea.
- 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.
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.- Rutas como
/obj/tornado_vortex/emisor: son la «dirección» de un nodo, como las carpetas de una computadora./objes el nivel de objetos,tornado_vortexel objeto del paso 1 yemisorel 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
merge1ogravity1: son los que Houdini pone por defecto; no hace falta cambiarlos. - En los códigos VEX:
@Pes la posición del punto y@ptnumsu número. La letra antes de@dice el tipo del atributo:f@= número decimal,v@= vector (x, y, z),i@= número entero.@Framey@Timeson 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.
/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.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).
El objeto contenedor/obj
- En
/obj, Tab → Geometry. Llámalotornado_vortex. - Entra con doble clic. Todo lo que sigue va adentro, salvo la cámara, las luces y el render.
/obj al final: tornado_vortex, la cámara y las dos luces. (tornado es la prueba 1; no hace falta.)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.
| Nodo | Nombre | Parámetros |
|---|---|---|
| Grid | piso_visual | Size 120 × 120, Rows 2, Columns 2 |
| Color | color_piso | Color (0.18, 0.2, 0.24). Conéctalo debajo de piso_visual |
El emisor/obj/tornado_vortex
Un círculo acostado en el piso. Las partículas nacen sobre su superficie. No se conecta a nada.
| Nodo | Nombre | Parámetros |
|---|---|---|
| Circle | emisor | Primitive Type Polygon, Orientation ZX Plane, Radius 4, 4, Divisions 48 |
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.
- Tab → Line, nombre
eje_tornado: Length 45, Points 16. La dirección (0, 1, 0) es la de por defecto.
- Debajo, Tab → Attribute Wrangle, nombre
atributos_vortex(Run Over: Points). Pega el código:
// 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;- Pulsa Create spare parameters y escribe los valores:
| Slider | Valor | Qué controla |
|---|---|---|
| radio_base | 2.5 | Radio del embudo en el piso (m) |
| radio_tope | 18 | Radio arriba (m) |
| curvatura | 1.6 | Mayor que 1: el embudo se abre más cerca de la punta |
| velocidad_orbita | 18 | Velocidad de giro (m/s) |
| lift | 30 | Empuje hacia arriba |
| alcance | 1.6 | La fuerza llega hasta radio × 1.6 |
| ondas | 1.5 | Cuántas curvas tiene la columna |
| velocidad_onda | 0.4 | Qué tan rápido se mueven esas curvas |
| serpenteo | 8 | Cuánto se desplaza la punta (m) |
atributos_vortex terminado: el código y, arriba, los sliders creados.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.
La red de simulación/obj/tornado_vortex
Tab → DOP Network, nombre sim_tornado. Entra en él: los pasos 6 y 7 son adentro.
Los nodos de la simulación/obj/tornado_vortex/sim_tornado
| Nodo (Tab →) | Nombre | Parámetros |
|---|---|---|
| POP Object | particulas | Por defecto |
| POP Source | emitir_base | Pestañ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 Force | ruido_turbulencia | Force 0, 0, 0 (solo ruido). Pestaña Noise: Amplitude 8, Swirl Size 6, Pulse Length 2 |
| POP Wrangle | punta_dispersa | El código de abajo |
| POP Solver | pop_solver | Por defecto |
| Ground Plane | ground_plane | Por defecto: piso de colisión en y = 0 |
| Merge | merge1 | Por defecto |
| SOP Geometry | curva_eje | Use External SOP activado, SOP Path /obj/tornado_vortex/atributos_vortex. Time se queda en $T |
| Vortex Force | fuerza_vortex | Lift Radius Multiplier 3. Los nombres de atributos se quedan por defecto |
| Gravity Force | gravity1 | Por 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):
// 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;| Slider | Valor |
|---|---|
| altura_min | 30 |
| altura_max | 50 |
| zona_fade | 8 |
| empuje_afuera | 25 |
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.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.
particulas→ 1ª entrada (Object) depop_solver.emitir_base→ruido_turbulencia→punta_dispersa→ 3ª entrada (Sources) depop_solver. La 2ª (Pre-Solve) queda vacía.ground_plane→ 1ª entrada (izquierda) demerge1;pop_solver→ 2ª entrada.merge1→ 1ª entrada defuerza_vortex.curva_eje→ 2ª entrada defuerza_vortex.fuerza_vortex→gravity1→output. El display flag va enoutput.
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- 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.
fuerza_vortex se ven los anillos guía de las órbitas.Sacar las partículas a SOPs/obj/tornado_vortex
Sal de sim_tornado con U y crea:
| Nodo | Nombre | Parámetros |
|---|---|---|
| DOP Import | importar_particulas | DOP Network /obj/tornado_vortex/sim_tornado, Objects particulas, Import Style Fetch Geometry from DOP Network |
| Attribute Wrangle | color_y_tamano | Debajo de importar_particulas. Código de abajo; slider escala_render 3 |
| Merge | particulas_y_piso | Entrada 1: color_y_tamano; entrada 2: color_piso |
| Null | OUT | Debajo del merge. Display flag aquí |
// 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.Cámara y luces/obj
| Nodo | Nombre | Parámetros |
|---|---|---|
| Camera | cam_tornado | Translate (0, 30, 135), Rotate X −2, Focal Length 35, Resolution 1280 × 720 |
| Distant Light | luz_sol | Rotate (−50, 30, 0) |
| Environment Light | luz_ambiente | Intensity 0.35 (cielo azul suave) |
Render con Karma/out
- En
/out, Tab → Karma, nombrekarma_tornado. - Valid Frame Range Render Frame Range, frames 1 a 168. Camera
/obj/cam_tornado, Resolution 1280 × 720. - Output Picture: una carpeta tuya, p. ej.
$HIP/render/tornado_karma.$F4.jpg. - Rendering Engine: CPU si tu equipo no tiene GPU NVIDIA; XPU si tienes una RTX (mucho más rápido).
- Render to Disk.
Para armar el video con los frames (opcional, con ffmpeg):
ffmpeg -framerate 24 -i tornado_karma.%04d.jpg -c:v libx264 -crf 23 -pix_fmt yuv420p tornado.mp4Có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.
Si algo sale mal
| Síntoma | Causa probable | Solución |
|---|---|---|
| Giran pero no suben | El lift no les llega | Lift Radius Multiplier en 3. Con 1 casi ninguna partícula recibe el empuje |
| Salen disparadas al cielo | lift muy alto o falta punta_dispersa | lift en 30 y revisa que punta_dispersa esté conectado |
| Se forma una tapa plana arriba | Todas mueren o se frenan a la misma altura | altura_min y altura_max deben ser distintos (30 y 50) |
| Atraviesan el piso | Ground plane a la derecha del merge | Pásalo a la 1ª entrada (izquierda) |
| No nace ninguna partícula | Fuente mal conectada o ruta mal escrita | punta_dispersa va a la 3ª entrada del solver; revisa la ruta a emisor |
| La columna no se mueve | curva_eje no lee la curva cada frame | Su Time debe ser $T |
| El tornado se desarma | Ruido muy fuerte | Amplitude en 8. Con 15, la mitad se queda abajo o sale volando |
| Sliders en 0 o sin efecto | No se crearon los parámetros | Pulsa Create spare parameters y escribe los valores |
| Karma no muestra las partículas | pscale muy chico | Sube 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_baseyradio_tope. - Más violento: sube
velocidad_orbitay 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.