- A medida que crecieron los proyectos de escenas y niveles 3D que corren en el navegador, se fueron acumulando herramientas reutilizables que aplican técnicas procedurales y generativas a elementos específicos del nivel, en vez de generar mundos completos
- El trabajo de texturas se centra en Triplanar Mapping y Hex Tiling, usados para aplicar texturas sin UV o para ocultar patrones repetitivos
- En ambas técnicas se aplica
pow()a los pesos de interpolación para aumentar la influencia del eje dominante o del resultado de muestreo predominante, y se reduce la carga de rendimiento al omitir algunas consultas de textura - Los fragment shaders costosos pueden mitigarse con Depth Pre-Pass, y en escenas con mucho overdraw el rendimiento puede mejorar en más de 30%
- Del lado de mallas y geometría, hay una línea de trabajo que busca ampliar la decoración, los fondos y la representación de daño mediante terrain con LoD, pipelines de procesamiento de mallas en tiempo de ejecución y, más adelante, Constructive Solid Geometry
Herramientas procedurales acumuladas en escenas 3D para navegador
- Durante varios años, al crear escenas y niveles 3D que corren en el navegador, los demos independientes centrados en shaders personalizados fueron creciendo hasta volverse algo más parecido a juegos conectados entre sí
- El enfoque común ha sido aplicar técnicas procedurales y generativas a partes específicas del nivel, más que construir mundos completamente generados de forma procedural
- De forma natural se fueron acumulando herramientas y efectos procedurales o semiprocedurales reutilizados en varios niveles
Shaders y texturas
- La mayoría de las texturas son seamless textures que hacen tiling sin costuras en dos ejes, pero al extenderlas sobre áreas grandes los patrones repetitivos pueden volverse evidentes
- Se mejoró el soporte de seamless texturing agregando varias funciones a shaders personalizados que extienden
MeshPhysicalMaterialde Three.JS -
Triplanar Mapping
- Triplanar Mapping es la herramienta clave de texturizado usada en casi todos los niveles
- Permite texturizar mallas con seamless textures sin un UV map predefinido, por lo que resulta útil en casos como terrain generado proceduralmente, donde el modelador no tiene oportunidad de definir el UV mapping
- Funciona bien tanto con mallas generadas como con mallas modeladas a mano
- La implementación es ligera y simple, y hay una implementación de referencia en triplanarMapping.ts
-
Mejoras a Triplanar Mapping
- El Triplanar Mapping tradicional mezcla linealmente las consultas de textura de los tres ejes según la normal del fragmento
- En áreas donde la normal no está cerca de un solo eje, la textura puede verse como si estuviera en capas
- Si se aplica
pow()con un exponente alto a los pesos y luego se normalizan otra vez, aumenta la contribución del eje dominante y se reduce la zona de transición - Con esta transformación, en la mayor parte de la malla el peso de un eje queda cerca de 1 y los otros dos quedan cerca de 0
- Al omitir consultas de textura para pesos por debajo de un umbral, la carga de rendimiento de Triplanar Mapping puede reducirse hasta quedar apenas por encima del texturizado tradicional basado en UV
- El manejo de normal maps requiere atención aparte en el código del shader, y se usa el método de GPU Gems
- Los detalles de implementación pueden verse en Normal Mapping for a Triplanar Shader
-
Hex Tiling
- Hex Tiling es un algoritmo para ocultar el tiling y la repetición visible de seamless textures
- Con solo agregar una opción de configuración al material, una escena puede pasar de verse como un mockup de baja fidelidad a algo cercano a lo semirrealista
- La implementación inicial se basó en el Shadertoy de Fabrice Neyret, luego se adaptó al sistema de materiales de Three.JS y se integró en el shader principal de materiales del proyecto
- Después, con permiso, se portó a una librería independiente, three-hex-tiling, que permite agregar Hex Tiling a los materiales built-in de proyectos con Three.JS
- A diferencia de Triplanar Mapping, requiere UV mapping predefinido
- Usar ambas técnicas juntas no es práctico, ya que el máximo de texture fetches por fragmento puede subir hasta 27 por cada map
- Como Hex Tiling también interpola linealmente tres resultados de consulta por fragmento, la técnica de pesos con
pow()usada en Triplanar Mapping también puede mejorar tanto el rendimiento como la calidad del resultado
-
Depth Pre-Pass
- Las técnicas avanzadas de texturizado pueden producir fragment shaders costosos en escenas grandes
- Depth Pre-Pass consiste en renderizar primero toda la escena con un material muy simple y barato para registrar la profundidad de cada píxel
- Aunque implica el overhead de renderizar la escena dos veces, en escenas con mucho overdraw normalmente el beneficio supera el costo
- Cuando hay mucho overdraw, agregar un Depth Pre-Pass puede mejorar el rendimiento en más de 30%
- Si se cambia la configuración del pre-pass, se pueden renderizar solo los fragmentos ocultos para visualizar cuáles serían omitidos al usar el pre-pass
- Los detalles de implementación y configuración en Three.JS pueden verse en este artículo dedicado
-
Síntesis de texturas PBR con IA
- Las texturas generadas por IA se usan en casi todas las escenas
- Si se usan con moderación, los resultados pueden verse bastante bien, y todas las texturas de las escenas de ejemplo fueron generadas por IA
- El proceso de generar texturas, crear mapas PBR y combinarlos en seamless textures 4K sin upscale se trata en otro artículo
- El sitio web mencionado allí para generar mapas PBR ya no está disponible
- Actualmente se usa DeepBump para generar normal maps y, cuando hace falta, herramientas no basadas en IA como Materialize para otros mapas
-
Niebla/nubes volumétricas
- El renderizado volumétrico es un área de interés porque puede dar efectos únicos a una escena
- Se creó un shader relativamente genérico que puede agregar nubes o niebla a cualquier escena de Three.JS
- Inspirado en el Shadertoy de Inigo Quilez, se hizo un shader básico de nubes volumétricas con consultas de ruido LoD similares, y después se amplió a una forma más general y configurable
- Este shader es útil para llenar espacios vacíos en niveles dispersos y para añadir una sensación dinámica a niveles estáticos mediante nubes o niebla en movimiento
- También se reutiliza parte del código y del enfoque desarrollado por n8programs en el proyecto
three-good-godrays three-good-godraystambién se usa con frecuencia y añade una atmósfera muy particular a los niveles
Mallas y geometría
- La generación de mallas en tiempo de ejecución es un área que cada vez se trabaja más
- Aunque gusta la idea de que un mundo crezca a partir de una semilla de software, se busca evitar esa sensación de “infinito pero vacío” que muestran algunos juegos centrados en generación procedural
- Por eso, en vez de generar proceduralmente toda la experiencia principal, el enfoque está sobre todo en agregar decoración, fondos y flourishes procedurales a los niveles
-
Terrain con LoD
- La generación de terrain es un área representativa del desarrollo procedural de videojuegos, y su implementación en sí no tiene nada especialmente inusual
- Como en la mayoría de los enfoques, se crea un heightmap de terrain con una función de ruido y luego se tesela en triángulos para renderizarlo
- Para el texturizado se usa Triplanar Mapping o Hex Tiling
- La clave está en el sistema de LoD, donde el terrain se genera por tiles y cada tile se crea en varias resoluciones
- Según la distancia entre cada tile y la cámara, se intercambian dinámicamente distintas resoluciones
- Este sistema de generación de terrain se reutiliza con frecuencia y, gracias a su flexibilidad y eficiencia, puede aplicarse a muchos niveles con poco esfuerzo
-
Pipeline de procesamiento y manipulación procedural de mallas
- La parte en la que más se ha trabajado recientemente es un pipeline de procesamiento procedural de mallas
- El objetivo inicial era subdividir y deformar proceduralmente mallas low-poly, incluidas mallas generadas dinámicamente
- La idea era hacer que mallas simples como plataformas, rocas grandes o estructuras voluminosas se vieran más realistas o interesantes al colocarlas en un nivel
- Ese trabajo llevó a un pipeline de software que toma datos geométricos en bruto en el runtime del navegador, los modifica libremente y los vuelve a exportar en un formato renderizable
- En este proceso hacen falta consideraciones detalladas, especialmente en el manejo de normales
- Los detalles de implementación están en el artículo subdividing meshes for displacement
Siguiente candidato para experimentar: Constructive Solid Geometry
- La mayoría de las herramientas listadas aquí empezaron como implementaciones de una sola vez para casos de uso específicos, pero luego se reutilizaron repetidamente en otros niveles y contextos
- La siguiente gran idea que se quiere probar es Constructive Solid Geometry
- Constructive Solid Geometry es un sistema que aplica boolean operators en el espacio 3D
- Permite unir dos mallas arbitrarias
- Permite recortar chunks de una malla
- También permite realizar otras manipulaciones similares
- csg.js implementa un toolkit de CSG en un único archivo JavaScript comentado de unas 500 LoC, con primitivas de malla, boolean operators y una API limpia
- En algún momento se planea portar esta librería a Rust para entender mejor cómo funciona
- Usar CSG junto con el pipeline actual de procesamiento de mallas tiene mucho potencial para producir resultados interesantes
- En particular, se quiere probar la capacidad de dañar mallas de forma procedural
- Recortando chunks de edificios o puentes para simular desgaste o erosión
- Generando grietas en muros o caminos
1 comentarios
Opiniones de Hacker News
Hace tiempo experimenté un poco con la generación procedural, en particular intentando crear árboles que se vieran bien, y la pieza que me faltaba era una forma sencilla de conectar geometría.
Hacer dos cilindros es fácil, pero unirlos de manera natural resultó muy difícil.
En teoría, CSG podría cubrir esa área, pero no era fácil pensar el problema de esa forma. No se puede ver simplemente como un bucle que agrega vértices, porque hay que modelar todo como formas 3D.
También intenté crear una rutina que tomara dos bucles de vértices y agregara caras con heurísticas para conectarlos, pero elegir qué vértices conectar fue mucho más difícil de lo esperado, y era fácil terminar con uniones feas.
Algún día me gustaría hacer un juego donde distintos sistemas modulares de generación procedural colaboren manteniendo la improvisación. Por ejemplo, que los sistemas “ocupen” partes del mundo, deleguen esas partes a otros sistemas o las conecten de forma natural con los elementos cercanos.
Hace poco hubo un video [0] sobre creación generativa de árboles, y la solución era simplemente dejar que los cilindros se intersectaran entre sí. Ese tipo de enfoque hacky también puede funcionar bien y dar resultados aceptables.
Como mencionas, CSG también es posible, pero puede ser excesivamente complejo. Otro método es crear el esqueleto del árbol y luego hacer lofting, combinándolo con CSG si hace falta para crear el tronco y la corteza.
Una buena biblioteca abre posibilidades. Si encuentras una biblioteca de geometría 3D que haga operaciones booleanas como unir cilindros o restar geometría, podrás probar muchas ideas nuevas. He usado algunas, pero la única que más o menos me gustó fue JSCAD [1].
[0] https://youtu.be/8zMbJmuwEUc?si=KQclrVPeSrIRmsbA
[1] https://github.com/jscad/OpenJSCAD.org
Se parece a describir y transformar formas 3D de manera funcional.
Para ver qué ha hecho la gente con esto en la demoscene, recomiendo mucho buscar Mercury Delight en YouTube. También hay muchos ejemplos en Shadertoy, y hay cosas realmente sorprendentes.
https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Condition...
También vale la pena ver el siguiente ejemplo:
https://github.com/MaxBondABE/batteries/blob/master/src/geom...
Una buena generación de terreno, ya sea a escala de mundo o a escala humana, no es nada trivial.
El enfoque simple, como se ve en el artículo, produce un mapa de alturas irregular, se parece muy poco a un terreno real y tampoco es particularmente interesante de explorar.
Por ejemplo, Dwarf Fortress parte de un desplazamiento de punto medio básico, pero después aplica mucho posprocesamiento personalizado.
Nada de lo que aparece en este artículo es trivial, ni siquiera vagamente. El autor, al menos en renderizado con shaders de GPU, está claramente cerca de ser un desarrollador 100x comparado con casi cualquier persona en la Tierra.
¿“Enfoque simple”? ¿De verdad es simple?
De 8 mil millones de personas, ¿qué porcentaje podría siquiera implementar el paso de “Hello World” necesario para empezar este artículo? ¿Y cuántas personas saben siquiera qué es un shader? Tampoco es que haya tantos trabajos de OpenGL. Situaciones como “¿Qué es OpenGL? Nosotros solo usamos Unity” son comunes.
¿Y los gamers en línea? Al 28 de julio de 2024 a la 1:22 p. m. EST, entre las 1,021,282 personas [1] conectadas a Counter Strike 2, ¿qué porcentaje podría implementar siquiera el primer paso de los shaders necesarios para el juego que están jugando?
¿Qué porcentaje podría compilar un programa simple de C++ en línea de comandos o escribir un script de JavaScript aún más simple en el navegador? En realidad esta pregunta tiene algo de trampa, porque la mayoría de las personas apenas puede manejar el correo electrónico.
[1] https://steamdb.info/app/730/charts/
Igual que con la IA, la mejor generación de contenido procedural también tendrá posprocesamiento personalizado en el resultado final.
Es un artículo muy útil porque organiza bien varias técnicas procedurales usadas en la web.
Si te interesa el editor procedural de niveles RPG en el que estoy trabajando, puedes ver https://github.com/gamedevgrunt/3D-Action-RPG-JavaScript.
Mi experiencia usando un pre-pass de profundidad ha sido mixta.
Las varias veces que lo probé, no vi mejoras de rendimiento notables en GPU de escritorio de gama media a alta.
No sé la razón exacta, pero supongo que puede deberse a que el rechazo Z temprano reducía las invocaciones del pixel shader. Normalmente renderizo las mallas opacas en orden de adelante hacia atrás.
Eso sí, mis experimentos fueron en el contexto de aplicaciones CAD/CAM, no de juegos. Las escenas también eran bastante distintas de un entorno típico de juego: poca textura y geometría con un conteo de polígonos muy alto.
Un pre-pass de profundidad a menudo también es el primer paso del occlusion culling, pero eso igualmente depende del contexto. Es probable que sea mucho más útil en un paisaje urbano complejo que en un modelo CAD.
Es más útil cuando la complejidad de profundidad es alta y los fragment shaders son costosos. Para ser justos, la mayoría de los juegos caen en esa categoría.
En renderizadores diferidos normalmente no es imprescindible, pero en forward+ suele aportar una mejora bastante buena.