Ilusión de perspectiva
El estilo, en un párrafo
Una cámara nivelada con un teleobjetivo muy largo (aproximadamente 2° desde 400 m de distancia) aplana una escena profunda hasta que parece un dibujo plano. Luego realiza un dolly zoom: la lente se abre mientras la cámara avanza, de modo que el sujeto mantiene el mismo tamaño pero el espacio detrás se estira y se abre. La cámara también gira hacia vistas en esquina. Cada una comienza plana y se abre en 3D. Mientras la vista es plana, los objetos se deslizan en profundidad donde nadie puede verlo, de modo que cuando la profundidad regresa, una fila se ha convertido en una diagonal. Todo está dibujado como línea de tinta limpia con sombreado plano de dos tonos, sombras proyectadas rayadas y un cielo pintado, y todo el conjunto se repite sin problemas.
Inicio rápido
mkdir -p ~/Movies/edits/<fecha>-<nombre>/stills && cd $_
printf '{"type":"module"}' > package.json && npm i [email protected] [email protected]
cp -r ~/simcraft/skills/video/perspective-illusion/templates/* .
cp scramble_night.html scene.html # o la plantilla más cercana (ver abajo), luego editar
node render.mjs scene.html x --stills=0.3,1.5,3,6,8.5 # fotogramas de cada toma, en stills/
node render_fast.mjs scene.html preview.mp4 --preview # bucle completo, mitad de tamaño, aproximadamente 1.5 min
node render_fast.mjs scene.html final.mp4 # calidad completa, aproximadamente 30 min para 10 s
python3 ~/simcraft/skills/video/vox-explainer/scripts/motion_check.py final.mp4
python3 ~/simcraft/skills/video/perspective-illusion/scripts/blink_map.py ffmpeg final.mp4 blink.png
Corrige problemas en los fotogramas y las previsualizaciones. Renderiza en calidad completa solo una vez que la previsualización esté aprobada. Configura $FFMPEG si ffmpeg no está en el PATH.
Plantillas
Comienza desde la plantilla más cercana a la nueva escena. Mantén su rig de cámara, línea de tiempo, pase de postprocesado y hooks de renderizado, y reemplaza solo la sección de la escena (todo entre // ---------- kit y // ---------- cameras).
| Plantilla | Escena | Renderizador |
|---|---|---|
street.html, street_plus.html | Calle diurna con postes, conos, un cruce y un cielo rojo. street_plus añade detalle y vistas en esquina. | render_smooth.mjs |
market.html, market_real.html | Pasillo de supermercado, dibujado y realista. | render_smooth.mjs (market.html: render.mjs) |
scramble_night.html | Un gran cruce nocturno bajo la lluvia, con multitud caminando, reflejos mojados y pantallas publicitarias. Tiene todas las optimizaciones de velocidad. | render_fast.mjs |
Para una escena nueva, scramble_night.html es la mejor base para el motor, porque es la única con los hooks del renderizador rápido. Elimina sus extras nocturnos (consulta reference.md) para un aspecto diurno.
Paso 1: Construir una escena que pueda engañar al ojo
Cualquier lugar funciona si tiene estos elementos:
- Un eje largo. Coloca la escena a lo largo de -z (una calle, un pasillo, un andén, un corredor), con la cámara mirando a lo largo de él. La profundidad a lo largo de este eje es lo que oculta el teleobjetivo.
- Cosas repetidas a diferentes profundidades. Postes, lámparas, estantes, columnas, carteles, árboles, personas. Con un teleobjetivo se apilan en un solo plano. Los postes alineados se fusionan en uno, y las pilas de carteles en fachadas lejanas se acumulan en una pared.
- Objetos intercambiables. De 3 a 6 objetos similares (conos, personas, contenedores, sillas, cajas) cuya disposición cambia entre una fila y una diagonal. También pueden dibujarse más grandes en la vista plana (
cs), para que la escala se lea correctamente al aplanarse. - Escala del mundo real en metros. Los lentes y los planos de enfoque solo tienen sentido a escala real: un cono de 0.75 m, personas de 1.7 m, una planta de 3.2 m.
- Detalle donde miran las vistas en esquina. Cada vista en esquina revela los lados de los edificios, las partes traseras de los objetos y el suelo. Llánalos: ventanas laterales, desagües, cables, bordillos, la sombra de contacto debajo de un coche. Las vistas planas ocultan el descuido y las vistas en esquina lo exponen.
- Haz que cada detalle sea parte de una superficie (texturas, mangas apiladas), o mantén las superficies separadas al menos 0.4 m de distancia. Desde 400 m de distancia, las superficies separadas por unos centímetros parpadean.
Paso 2: Coreografiar la cámara
La cámara nunca está inclinada. Permanece nivelada para que las verticales se mantengan verticales, como en un dibujo de arquitecto. Cada toma es un objeto simple:
{ x, y, zf, W, fov, H, el, yaw, lay, cs }
| Clave | Significado |
|---|---|
x, y | Posición lateral y altura de la cámara (m) |
zf, W | El plano de enfoque y el ancho enmarcado allí (m). Mantener W mientras se cambia fov es el dolly zoom. |
fov | Ángulo horizontal del lente. Aproximadamente 2° es plano, de 25 a 90° es profundo. |
H | Desplazamiento del lente: dónde se sitúa el horizonte (-1 abajo, 0 medio, 1 arriba). |
el | Mirar hacia abajo elevando la cámara y desplazando el lente, nunca inclinando. |
yaw | Girar alrededor del punto de enfoque para vistas en esquina. |
lay, cs | Disposición de objetos intercambiables (0 fila, 1 diagonal) y su escala en vista plana. |
Vocabulario de tomas que funciona:
| Toma | Configuración |
|---|---|
| Plana | fov aproximadamente 2, H 0. Toda la escena se lee como un alzado. |
| Ojo de gusano | Altura de rodilla, fov 90, horizonte bajo (H aproximadamente -0.7). |
| Alta | Cámara arriba con un lente medio, horizonte por encima del encuadre. |
| Vista en esquina | yaw ±18 a 35°, el 9 a 50°. Viene en par: primero una versión con teleobjetivo (fov 4), luego la misma vista con angular (fov 25 a 45). |
| Diagonal y baja | Lentes medios que muestran los objetos intercambiables en profundidad. |
Estructura del bucle. Plana, luego una vista profunda, luego una vista en esquina (plana y luego 3D), de vuelta a plana (intercambiar los objetos aquí), una diagonal alta, la otra vista en esquina, una toma baja, y luego plana de nuevo. El bucle termina en la misma toma en que comienza.
Temporización. Todos los parámetros se ejecutan a través de una curva cúbica monótona (Fritsch-Carlson) sobre los tiempos clave, por lo que la velocidad nunca salta. Interpola W y fov en espacio logarítmico. Añade pequeñas tomas de "deriva" (W × 0.96) para que las pausas respiren en lugar de congelarse. 10 s se siente suave, mientras que 7 s se siente apresurado. Para cambiar la duración, escala los tiempos clave en lugar de reescribirlos.
Truco mientras está plano. Cambia lay solo durante las pausas planas. Si la cámara se mueve en profundidad mientras los objetos se mueven, el truco se nota.
La primera vista en esquina debe ir hacia el lado que el objeto héroe (un coche, un cartel) no cubre. De esa manera, la revelación muestra algo nuevo.
Paso 3: El aspecto
La página renderiza cuatro pases: color, normales con profundidad, color plano (sin iluminación) y una máscara de sombra solo solar. Un sombreador final los combina.
- Líneas de tinta donde cambian la profundidad (segunda diferencia), las normales o el color plano. Haz las líneas más pesadas de cerca. Las marcas en el suelo reciben líneas grises finas. Atenúa las líneas de color donde un píxel cubre muchos metros, o las texturas lejanas se convierten en ruido.
- Sombreado de dos tonos. Luz versus sombra, sin degradados. Configura la luz de la máscara a
2*PI, porque three.js divide Lambert por pi, y sin eso todo se convierte en sombra. - Rayado solo en sombras proyectadas, con el rayado anclado en el espacio del mundo (nunca en el espacio de pantalla) y atenuado por
fwidth. - Pinta el cielo por dirección de visión, no por posición de pantalla, para que las nubes se mantengan fijas mientras la cámara se mueve. Mantén los bordes de las nubes suaves.
- Paleta. De 6 a 10 colores planos más un acento (un cielo rojo, un cartel amarillo). Para coincidir con una referencia, toma los colores de sus fotogramas con k-means.
- Luz. Un sol bajo y duro colocado de modo que los sujetos principales permanezcan iluminados y proyecten sombras largas en el suelo. Un sol desde el lado alto entierra la escena en una gran sombra.
Para una variante nocturna (luz de luna, ventanas iluminadas, reflejos mojados, brillo, lluvia, gradación), una multitud de caminantes, o pantallas publicitarias y carteles, consulta reference.md.
Para obtener un aspecto diurno de scramble_night.html:
- Cambia la luna por un sol cálido y eleva la luz ambiental.
- Elimina el bucle de luz puntual.
- Retira el bloque de suelo mojado, la línea de bruma y la gradación nocturna del sombreador de postprocesado.
- Elimina la lluvia y establece
emissiveIntensityen las ventanas a 0. - Pinta un
skyAtdiurno.
Paso 4: Pasos de detalle (lo que lo hace genial)
Las primeras versiones siempre se ven vacías. Las versiones geniales provienen de pasos deliberados, cada uno verificado en fotogramas antes de que comience el siguiente.
- Formas. Masas principales a escala real, el eje largo, los objetos intercambiables y la línea de tiempo de la cámara. Verifica que la toma plana realmente se lea plana y que cada movimiento revele algo.
- Estructura. Todo lo que tiene un lugar real, construido con lógica del mundo real. Para una calle, eso significa bordillos, desagües, tapas de alcantarilla, postes con sus brazos y cajas, carteles en ambas caras, vallas, contenedores, máquinas expendedoras, soportes para bicicletas, cables aéreos, y marcos de ventanas con alféizares y unidades de aire acondicionado. Mira fotos del tipo de lugar real y enumera lo que hay antes de construir.
- Desgaste e historia. Pequeñas cosas que lo hacen habitado: pegatinas, cinta adhesiva, carteles con texto dibujado con código, manchas de charcos, una bicicleta apoyada, ropa tendida, un gato, una taza de café. Apunta a 3 o más por superficie en la que la cámara se detiene.
- Auditoría de vistas en esquina. Para cada vista en esquina, renderiza el fotograma amplio y llena lo que se ve desnudo allí. Generalmente son los lados de los edificios, las partes traseras de los carteles, los techos (tanques, barandillas, antenas) y el suelo entre objetos.
- Luz y sombra. Asegúrate de que cada objeto proyecte una sombra legible, añade sombras de contacto debajo de las cosas estacionadas y lleva el lado sombreado de cada forma principal al paso de dos tonos. Las sombras que solo aparecen en una vista son una recompensa para el movimiento de la cámara.
- Vida (opcional). Caminantes, un tren o coches moviéndose a velocidad constante, pantallas, pájaros, banderas. Todo se repite un número entero de veces.
- Pulido. Armonía de la paleta, grosor de línea, cielo, viñeta y gradación. Luego la verificación de parpadeo en el resultado final.
Regla de densidad: desde el fotograma plano con teleobjetivo, el encuadre debe sentirse lleno de borde a borde, como una ilustración densa, sin grandes áreas vacías de pared o suelo a menos que sea un descanso deliberado para el ojo.
El estándar de calidad
Antes de cualquier renderizado completo, verifica los fotogramas en cada tiempo clave (y a medio camino entre movimientos) contra esta lista. Corrige cualquier cosa que falle, luego verifica de nuevo.
- La toma plana se lee como un dibujo plano. Nada revela la profundidad.
- Cada movimiento termina en una revelación: una diagonal oculta, una calle lateral, objetos que estaban lejos.
- Las vistas en esquina comienzan planas y se abren, y la primera se aleja del objeto héroe.
- Las líneas son nítidas y uniformes, sin líneas dobles, sin tinta en caras brillantes y sin ruido en texturas lejanas.
- El sombreado de dos tonos es claro en cada forma principal, y el rayado aparece solo en sombras proyectadas.
- Nada se lee como un primitivo desnudo. Cada caja tiene moldura, costuras, una base o una etiqueta.
- El texto es legible y está escrito correctamente, en fuentes reales, con marcas inventadas.
- El cielo coincide con la dirección de la luz y el color de la escena.
- La paleta es limitada y armoniosa, con un acento claro.
- La costura del bucle es invisible (el fotograma 0 es igual al último fotograma) y nada aparece de repente.
- La previsualización no tiene tartamudeos. En el resultado final, el recuento de
blink_mapes bajo, y lo que parpadea es movimiento intencionado (lluvia, pantallas).
Al revisar fotogramas, míralos como un ilustrador: nombra las tres áreas más débiles de cada fotograma, corrígelas y repite. No te detengas en "funciona".
Paso 5: Renderizar y verificar
render_fast.mjsrenderiza cada fotograma en la GPU como un promedio de sub-renderizados con jitter: 4 para antialiasing, y hasta 32 en movimientos rápidos para un desenfoque de movimiento continuo, dimensionados por cuánto se mueven los puntos dewindow.probeen pantalla. Usa--previewpara la mitad de tamaño a 30 fps.--from,--toy--fpstambién están disponibles.render_smooth.mjsofrece la misma calidad para páginas sinrenderMany, pero promedia en ffmpeg, por lo que es varias veces más lento.- Fusiona mallas estáticas por material al cargar (
BufferGeometryUtils.mergeGeometries). Una escena pasó de 7,654 llamadas de dibujo a 530, aproximadamente 7 veces más rápida. Mantén cualquier cosa que se mueva fuera de la fusión. - Verifica el resultado final con
motion_check.py(nada se congela) yblink_map.py, que cuenta los píxeles que parpadean en fotogramas casi estáticos. Compara el recuento antes y después de un cambio. Las previsualizaciones brillan por diseño, así que juzga el parpadeo solo en el resultado final.
El contrato de la página
Cualquier página de escena funciona con los renderizadores si proporciona:
| Hook | Qué hace |
|---|---|
#frame | El lienzo que se captura. |
window.READY | Se establece una vez que los assets y las fuentes están cargados. |
window.render(ms) | Dibuja el fotograma en el tiempo ms. |
window.probe(ms) | Devuelve posiciones de pantalla de algunos puntos del mundo, utilizadas para dimensionar el desenfoque de movimiento. |
window.setJitter(x, y) | Desplazamiento del lente de subpíxel en píxeles, establecido a través de projectionMatrix.elements[8] y [9]. |
window.renderMany([[ms, jx, jy], ...]) | Promedia varios renderizados en una sola llamada (necesario para render_fast). |
?scale=.5 | Renderiza a la mitad de tamaño. |
Todo lo animado debe ser una función pura de ms, periódica en la duración del bucle. Usa números enteros de ciclos por bucle para pasos, arrastres y lluvia.
Parpadeo y fallos
| Causa | Solución |
|---|---|
| Cualquier cosa en espacio de pantalla (bamboleo, rayado, ruido, bruma por distancia de cámara) | Anclarlo al mundo o a la dirección de visión. |
| Superficies separadas unos pocos cm | Fusionarlas en una superficie, o mantenerlas separadas 0.4 m o más. |
| Planos coplanares superpuestos (marcas viales, piezas de suelo) | Pintar las marcas en un solo plano, y hacer que las piezas se encuentren borde a borde. |
| Detalle de subpíxel (rieles, rejillas, cables, lluvia) | Jitter de antialiasing en cada fotograma, y atenuar líneas densas por metros por píxel. |
| Un renderizado por fotograma | Nunca hacer esto en resultados finales. |
| Bordes duros en nubes o reflejos | Suavizarlos y atenuar los efectos en los bordes de la pantalla. |
| Cosas en movimiento que se envuelven o aparecen | Envolverlas durante el movimiento de cámara más rápido del bucle, encontrado con window.probe. |
Errores comunes
- Un comentario
//añadido a una línea de código se traga el resto de la línea (unscene.add, una luz). Usa/* */. - Los helpers que añaden a la raíz de la escena (como
plane()) ignoran el grupo actual y pueden aterrizar encima de cosas. Construye dentro de grupos. - Mantén las caras emisivas (pantallas, paneles de carteles) fuera de los pases de color plano y sombra, o cada glifo se entintará y oscurecerá.
ffmpeg -stream_loop ... -c copyhace bucles que algunos reproductores detienen antes de tiempo. Vuelve a codificar en su lugar.- Para X, las imágenes lado a lado a 32:9 son demasiado anchas para subir. Apílalas verticalmente.
- Usa solo personajes originales y marcas inventadas. Copia la técnica de una referencia, nunca su obra.