Cómo construir un mundo abierto en el navegador, parte 14: El mundo cobra vida
Por Oleg Sidorkin, CTO y cofundador de Cinevva
¿Acabas de llegar? Consulta la guía de la serie. Explica qué es un spike y enlaza todas las partes.
Después de la parte 13 ya podíamos esculpir. Elevar el terreno, excavar cuevas y suavizar acantilados, todo en tiempo real y manteniendo intactas las uniones. Pero el mundo parecía una demo técnica. Sombreado plano con colores de depuración, superposiciones de malla y geometría gris con colores de LOD para distinguir los chunks. Podías editarlo. No podías sentirlo.
Tres spikes cambiaron eso. No cambiamos la arquitectura. Usamos exactamente el mismo pipeline, los mismos búferes y el mismo cosido de uniones de la parte 13. Solo añadimos las capas que hacen que un terreno se sienta como un lugar: superficies que responden a su forma, vida que crece sobre ellas y un cuerpo que las recorre.
La diferencia entre «funciona técnicamente» y «quiero quedarme aquí» resultó ser sorprendentemente pequeña.
Esculpe un acantilado y observa cómo se convierte en roca
El spike 28 planteaba una pregunta concreta: ¿puede ejecutarse un material de cuatro capas con mapeado triplanar sobre terreno generado mediante cómputo sin agotar el presupuesto por fotograma? La respuesta fue sí, pero lo interesante fue lo que ocurrió después.
Abrir el spike 28 en una pestaña nueva ↗ · Ver el código fuente
Cuatro texturas procedurales —hierba, roca, arena y nieve— generadas a partir de ruido FBM al iniciar. Sin archivos externos ni pipeline de recursos, solo matemáticas y una DataTexture. Los pesos de los materiales proceden de la propia superficie: la pendiente y la altitud. Las zonas planas por debajo de la línea de árboles reciben hierba. Las caras empinadas reciben roca. Las zonas bajas reciben arena. Las cumbres altas reciben nieve. Cada peso bruto se obtiene mediante un smoothstep aplicado a la pendiente y la altitud; después, se normalizan por fragmento para que
El momento que nos convenció: elevas el terreno con el pincel para crear un acantilado empinado y la textura de roca aparece sobre la nueva cara en el mismo fotograma. Vuelves a aplanarlo y la hierba recupera la superficie. El material no sabe nada del pincel. Solo lee la posición en el mundo y la normal de la superficie, los mismos datos a partir de los cuales se construyó la geometría. El bucle de retroalimentación visual al esculpir es instantáneo y no depende de ningún script específico.
El spike 8 probó hace mes y medio el coste del material del terreno con una malla estática en WebGL. El spike 28 demuestra que funciona sobre terreno dinámico generado mediante cómputo, con todo el pipeline de esculpido ejecutándose por debajo. Lo habíamos contemplado en el presupuesto, pero verlo mantenerse a 60 fps con todo en ejecución siguió siendo un alivio.
80 000 matas de hierba y el techo de una cueva
En el spike 29 fue donde nuestros instintos de desarrollo de videojuegos tomaron el control.
Abrir el spike 29 en una pestaña nueva ↗ · Ver el código fuente
Queríamos la hierba de Breath of the Wild. No el número de polígonos, sino la sensación. Hierba que cubriera los lugares adecuados, se moviera con el viento y te diera ganas de correr entre ella.
Cada mata de hierba consta de tres quads que se cruzan con ángulos de 60 grados y tienen cuatro segmentos verticales para poder doblarse. Es una forma de cruz que parece volumétrica desde cualquier ángulo sin recurrir a trucos con billboards. Un InstancedMesh por chunk, 80 000 matas repartidas por el mundo y unos 2,4 millones de vértices de hierba en total.
La colocación es la parte importante. La CPU recorre una cuadrícula con variaciones aleatorias sobre cada chunk y evalúa la misma lógica de pendiente y altitud que utiliza el shader de materiales de la GPU. Donde el sistema de materiales dice «hierba», crece hierba. Donde predominan la roca o la arena, la densidad cae a cero. La transición es suave porque los pesos smoothstep subyacentes producen gradientes continuos en los límites de los biomas. No percibes ninguna frontera porque no la hay.
Después hicimos algo que no estaba seguro de que fuera a funcionar: hierba sobre superficies SDF. La función de dispersión recorre cada columna del volumen SDF y busca cruces por cero entre vóxeles adyacentes. Donde el SDF pasa de negativo a positivo, hay una superficie. La normal de la superficie es simplemente el gradiente normalizado del campo,
Excava una cueva con el pincel SDF en el spike 27. Vuelve al spike 29 y habrá hierba creciendo sobre la parte superior del techo de la cueva. El código de dispersión no sabe qué es una cueva. Solo ve una superficie con la pendiente adecuada a la altitud adecuada. Ese es el tipo de comportamiento emergente que hace tan satisfactorio construir sistemas de mundo abierto.
El viento es una onda sinusoidal en el shader de vértices TSL, modulada por la coordenada V de la brizna para que las puntas se balanceen y las raíces permanezcan plantadas. Pulsa V para alternar entre viento suave, fuerte y desactivado. El viento actúa simultáneamente sobre las 80 000 matas sin ningún coste para la CPU porque se ejecuta por completo en el shader de vértices.
En el spike 7 probamos 50 000 instancias de hierba y nos preocupaba alcanzar el límite. El spike 29 ejecuta 80 000 sobre el terreno generado mediante cómputo, el cosido de uniones, el texturizado multimaterial y los pinceles. Agruparlas en menos llamadas de dibujo de InstancedMesh sigue siendo más importante que reducir el número de vértices por brizna. La lección del spike 7 se mantuvo.
Algo que dejamos para más adelante: la hierba no se actualiza cuando esculpes debajo de ella. Las matrices de las instancias se fijan al dispersarlas. Si transformas una colina en un valle, la hierba flota en el aire hasta que pulsas G para volver a dispersarla. Es suficiente para un spike. En producción habrá que volver a dispersar la vegetación de los chunks modificados.
El primer paso
El spike 30 es al que no dejo de volver.
Abrir el spike 30 en una pestaña nueva ↗ · Ver el código fuente
Sin biblioteca de físicas. Una cápsula personalizada con un paso de tiempo fijo de 120 Hz. El código de producción utiliza Rapier en un web worker —el spike 2 demostró que la latencia es aceptable—, pero este spike tenía que validar las propias consultas de colisión. ¿Puede un personaje caminar sobre terreno de mapa de alturas, pasar a terreno SDF esculpido y no caer a través de él?
El núcleo es una función llamada terrainQuery(x, y, z). Comprueba si la posición está dentro de un chunk bloqueado en MC. Si es así, interpola trilinealmente la copia SDF de la CPU y devuelve el gradiente como normal de la superficie. En caso contrario, consulta el mapa de alturas y calcula la normal mediante diferencias centrales. El personaje no sabe sobre qué sistema de terreno se encuentra. Solo pregunta dónde está el suelo y obtiene una respuesta.
La colisión con SDF era la parte que esperaba que resultara difícil. Siete puntos de sondeo alrededor de la cápsula: inferior, central, superior y cuatro desplazamientos cardinales. Un punto de sondeo en la posición
Esto deja únicamente la componente que se desliza a lo largo de la superficie. No es un motor de físicas. Son consultas geométricas y una respuesta sencilla. Pero gestiona cuevas, salientes y túneles esculpidos sin ningún código específico para cada forma. Entras en una cueva que excavaste diez segundos antes y la cápsula sigue con exactitud el contorno del techo.
El modelo de movimiento comenzó siendo práctico y acabó siendo divertido. Caminar, correr, esprintar, saltar y deslizarse por pendientes superiores a 45 grados. Después añadí un parapente al estilo BotW y el spike se convirtió en algo que no quería cerrar.
Pulsa Espacio mientras estás en el aire. La gravedad baja de -30 a -4. La velocidad de caída se limita a -3. Una malla con forma de ala delta se despliega desde la cápsula mediante interpolación de escala. Gira a la izquierda y el ala se inclina. La orientación de la cápsula se alinea automáticamente con el vector de velocidad, por lo que siempre miras en la dirección en la que avanzas. Suelta Espacio para caer. Toca el suelo y vuelves a correr.
La cámara se aleja y adopta una vista en tercera persona —pulsa P para alternarla—. Sigue al jugador desde atrás con suavizado de guiñada. Un ray marching desde el jugador hasta la cámara comprueba el terreno en 20 pasos. Si vuelas hacia el interior de una cueva, el brazo de la cámara se acorta suavemente en lugar de atravesar la roca. Al salir por el otro lado, vuelve a extenderse.
Este es el momento que hizo que el spike mereciera la pena: esculpe un acantilado alto con el pincel del mapa de alturas. Cambia a la cámara en tercera persona. Corre hasta el borde. Salta. Despliega el planeador. Gira a la izquierda sobre el terreno que acabas de esculpir, con la hierba que cultivaste en el spike 29 ondeando debajo, la textura de roca del spike 28 sobre la cara del acantilado y las uniones de Transvoxel manteniéndose en cada límite entre chunks. Aterriza al otro lado. Todo funcionando en conjunto dentro de una sola pestaña del navegador.
Esculpir bajo tus propios pies
Había algo sobre lo que no estaba seguro: ¿qué ocurre cuando esculpes el terreno sobre el que está el personaje? Los cambios del mapa de alturas se propagan de inmediato a través de terrainQuery() porque lee directamente el búfer de la CPU. Los cambios del SDF se propagan a través de la copia SDF de la CPU. La cápsula resuelve la penetración en el siguiente tick de físicas, que a 120 Hz ocurre en menos de
Simplemente funciona. Eleva el suelo bajo el jugador y este sube con él. Excava el suelo y cae. Sin tratamiento especial. Las físicas se ejecutan con suficiente rapidez para que los cambios de terreno de un solo fotograma nunca produzcan penetraciones grandes. Fue un feliz accidente de haber elegido 120 Hz para el paso de tiempo. Lo elegimos para conseguir un movimiento fluido y, de paso, hizo que editar el terreno fuera seguro sin ningún coste adicional.
30 spikes después
Comenzamos esta serie con un mapa de alturas plano y 500 cubos. Ahora tenemos terreno esculpido con cuevas volumétricas, cosido de uniones mediante Transvoxel, texturizado multimaterial que responde a la forma de la superficie, 80 000 matas de hierba meciéndose con el viento y un personaje que camina, esprinta, salta y planea sobre todo ello.
Nada de esto está todavía en producción. El código de world/client/ sigue ejecutándose en WebGL con chunks sencillos de mapas de alturas. Todo lo creado en estos 30 spikes vive en páginas HTML independientes. El siguiente paso es la integración: migrar a WebGPURenderer, conectar la política híbrida HM/MC con el gestor de chunks y vincular los sistemas de pinceles y materiales con el multijugador.
Pero el sistema de renderizado ya no es el riesgo. Las preguntas abiertas tienen que ver con el flujo de datos: persistencia de las ediciones, sincronización de los trazos de pincel por red y esculpido colaborativo. Lo que ocurre entre los jugadores, no entre los triángulos.
Si has seguido esta serie desde la parte 1, gracias por acompañarnos durante las partes más complicadas. Si acabas de descubrirla, vuelve a la parte 1. Las lecciones están en los caminos equivocados.
Tecnología mencionada en este capítulo
TSL (Three Shading Language). El sistema de shaders basado en nodos de Three.js para el renderizador WebGPU. Los materiales se componen a partir de nodos (positionWorld, normalWorld, smoothstep, triplanarTexture) mediante composición de funciones en JavaScript. El grafo de shaders se compila a WGSL en tiempo de ejecución. TSL sustituye al ShaderMaterial de GLSL puro en los destinos WebGPU y permite la interoperabilidad entre las funciones estándar de los materiales de Three.js —luces, sombras y niebla— y la lógica personalizada por fragmento.
Mapeado triplanar. Una técnica de proyección de texturas que muestrea una textura tres veces —planos XY, XZ e YZ— y mezcla los resultados según la dirección de la normal de la superficie. Esto elimina el estiramiento UV en geometrías de malla arbitrarias, algo esencial para la salida de marching cubes, donde los triángulos no tienen coordenadas UV significativas. TSL proporciona triplanarTexture() como nodo integrado.
Vegetación instanciada con briznas de quads cruzados. Cada mata de hierba consta de tres quads que se cruzan con ángulos de 60 grados, lo que crea una apariencia volumétrica desde cualquier dirección. Los cuatro segmentos verticales de cada quad permiten una flexión suave para animar el viento. Todo el campo se renderiza como un único InstancedMesh por chunk. Se reserva un 25 % de capacidad adicional para que las nuevas instancias puedan ocupar los espacios reservados cuando se edite el terreno, sin tener que reasignar el búfer de la GPU. Consulta nuestra guía de generación de paisajes sobre vegetación. Colisión entre cápsula y SDF. Colisión del personaje con terreno volumétrico sin un motor de física. Se comprueba la cápsula en varios puntos respecto al SDF. Cuando el valor del campo es menor que el radio de la cápsula, el gradiente proporciona la normal hacia el exterior y la diferencia determina la profundidad de penetración. Esto permite manejar cuevas, salientes y túneles sin código específico para cada forma. Consulta colisiones con terreno SDF.
Controlador de personaje con paso de tiempo fijo. La física se actualiza a 120 Hz independientemente de la velocidad de fotogramas, acumulando tiempo real y consumiéndolo en pasos de tamaño fijo. Un número máximo de subpasos evita la espiral de la muerte en fotogramas lentos. El ajuste al suelo mantiene la cápsula adherida durante el desplazamiento por pendientes. El paso de tiempo fijo garantiza un comportamiento determinista para futuras repeticiones multijugador.
Parte 14 de 29. Anterior: Parte 13 - El esculpido del terreno y la muerte de la función matemática Siguiente: Parte 15 - Sustituye la base y luego sincronízala Guía de la serie: /es/blog/2026-02-25-open-world-browser-series-guide