2 puntos por GN⁺ 2024-05-03 | 1 comentarios | Compartir por WhatsApp

Crear un modelador 3D en C en una semana

  • Participé en el evento de programación de una semana llamado “Wheel Reinvention Jam” el otoño pasado.
  • El objetivo era repensar los sistemas de software existentes desde una nueva perspectiva.
  • Hice un modelador 3D llamado “ShapeUp”, y ver antes el video de demo de ShapeUp antes de leer este artículo ayuda a entenderlo.
  • ShapeUp se puede probar directamente en el navegador.

Elección del lenguaje: C

  • Me uní al Jam por la frustración con la lentitud del compilador de TypeScript.
  • Si se empezaba desde el parser de TypeScript de esbuild o Bun, parecía factible un proyecto para implementar un subconjunto rápido de TypeScript.
  • Pero comparando la velocidad de ejecución de comandos del terminal, no parecía una demo interesante, así que cambié el rumbo hacia un proyecto 3D.
  • Gracias a la técnica de ray marching con campos de distancia firmada (SDFs), parecía posible crear desde cero un proyecto 3D en una semana.
  • Una escena con SDF se puede implementar mucho más rápido que un renderizador equivalente basado en triángulos.
  • Había escrito shaders SDF antes, pero en un nivel muy básico, y editar código para modelar no se sentía natural.
  • Quería editar formas con el mouse y pensé que este Jam era la oportunidad para hacerlo realidad.
  • Llamé al proyecto ShapeUp.

Ventajas de usar C

  • C es un lenguaje muy simple y primitivo, y pensé que consumiría mucho tiempo compensar la falta de estructuras de datos incorporadas y arreglar bugs de punteros.
  • Pero la simplicidad de C se convirtió en una ventaja.
    • Compila rápido.
    • La sintaxis no oculta operaciones complejas.
    • Es simple, así que no hay que buscar la sintaxis constantemente.
    • Puede compilarse fácilmente a nativo y WebAssembly.
  • Las desventajas de C pueden evitarse con hábitos que he desarrollado durante 22 años usándolo.
  • ShapeUp es muy simple y está conformado por un solo archivo C pequeño.

Estructuras de datos de ShapeUp

  • El modelo se compone de un arreglo de una estructura llamada Shapes.
  • Shapes se guarda en un arreglo de asignación estática.
    • No hay riesgo de fallos de asignación ni fugas de memoria.
    • El límite de 100 Shapes no resultó realmente restrictivo.
    • Como faltó tiempo para optimizar el renderizador, la velocidad de frames probablemente habría caído antes de llegar a 100.
    • Con más tiempo habría dividido el modelo en bloques pequeños y realizado raymarching dentro de cada bloque.
  • Llamé a malloc solo en 3 lugares de memoria dinámica.
    • Guardado (asignar un buffer lo suficientemente grande para guardar el documento completo).
    • Exportación OBJ (asignar un buffer lo suficientemente grande para todos los vértices).
    • Generación de shader GLSL (buffer para el código fuente del shader).
  • En todos los casos hay un único free al final de la función.
  • Un ejemplo de que la gestión de memoria en C puede ser simple.
  • Lenguajes como C#, JavaScript y Python te fuerzan a una estructura de asignación donde haces malloc por cada Shape y guardas ese puntero en un arreglo dinámico.
  • En C es bueno poder controlar el diseño de memoria.

Interfaz de usuario

  • Implementada con interfaz de usuario de modo inmediato (IMGUI).
  • Me gusta la UI de tipo IMGUI porque:
    • Facilita mucho la depuración.
    • Usa un lenguaje de programación real para ubicar elementos (a diferencia de CSS, constraints, SwiftUI).
  • Como en la mayoría de las IMGUI, usa enums para rastrear en qué control está el foco y qué hace el mouse.
  • Este proyecto no necesitó arreglos dinámicos ni mapas hash; de haberlos necesitado, habría usado algo como stb_ds.h.

Problemas de la biblioteca raylib

  • Decidí usar C, pero raylib terminó siendo problemático.
  • Tiene decisiones de diseño extrañas que dañan la experiencia del desarrollador:
    • Usa int donde se esperaría un enum, evitando la verificación de tipos del compilador y sin que las funciones sean autodocumentadas.
    • No valida los parámetros por defecto (decisión de diseño).
    • No se responsabiliza de dependencias (no corrige issues de GLFW ni envía parches).
  • La librería UI raygui se queda corta:
    • No puede mostrar números de punto flotante.
    • No maneja el ruteo de eventos del mouse para elementos superpuestos o recortados.
    • No puede crear esquinas redondeadas.
    • No permite dar un estilo estético.
  • También tiene bugs:
    • Un bug que impide cambiar la fuente.
    • Las funciones de dibujo no comparten vértices entre triángulos, por lo que se generan espacios de píxeles.
  • Reporté cada problema que encontraba, pero la mayoría se cerró como “won’t fix”, y escribir reportes de bugs me llevó mucho tiempo, así que finalmente lo dejé.
  • Fue bueno que creara la ventana de OpenGL, pero pagué un precio alto por esa conveniencia.
  • Por suerte, encontré una salida al usar funciones de OpenGL directamente o implementar funcionalidades desde cero.
  • En adelante voy a usar sokol.

Proceso de desarrollo en una semana

  • ShapeUp tenía 4 partes principales que debía completar en 6 días:
    1. Interfaz de usuario (herramientas 3D, atajos de teclado, barra lateral, control de juego)
    2. Generador de shaders GLSL + renderizador de ray marching
    3. Selección con mouse basada en GPU
    4. Marching cubes for export
  • Ninguna fue difícil, pero fue complicado priorizar bien y no engancharse.
  • En problemas complicados o que consumían mucho tiempo, ayudaba resolverlos por diseño o usar una solución sencilla que funcionara en 90% de los casos.
  • A veces, al posponer una funcionalidad un día, podía encontrar la solución de forma casi inconsciente.
  • Mantener siempre un modelador 3D funcional y mejorarlo gradualmente en la medida que el tiempo lo permite fue mi objetivo.
  • Lo pensé como construir una pirámide: si la haces por capas, puede que al final no quede una pirámide completa, pero en cualquier etapa puedes hacer que lo sea.

Resultado del proyecto

  • Una semana después, tenía un programa 3D capaz de crear modelos 3D significativos y exportarlos como archivos .obj.
  • Funciona de manera multiplataforma y además tiene abrir/guardar archivos.
  • El proyecto consta de 2,024 líneas de C y 250 líneas de GLSL.
  • Me sorprendió un poco que con alrededor de 2,300 líneas sea posible representar un modelador 3D razonablemente útil.
  • Me pidieron mostrar una demo de ShapeUp en el resumen del Jam y en la conferencia Handmade Seattle.
  • Parecía que a la gente le impresionó ShapeUp, aunque no parece que fuera un gran logro; era un proyecto relativamente simple.
  • Lo más valioso de lo que hice fue el instinto para elegir qué construir, el conocimiento para hacerlo y la disciplina para lograrlo en menos de una semana.

Opinión de GN⁺

  • Es un proyecto interesante que muestra bien las ventajas de la simplicidad y la velocidad de C. Pero por el bajo nivel de abstracción de C, parece difícil usarlo tal cual en un proyecto comercial; se espera que implementarlo todo de una herramienta moderna de modelado 3D directamente en C requeriría un esfuerzo enorme.
  • Resulta impresionante haber logrado un programa funcional en una semana, pero en un horizonte de largo plazo, considerando mantenimiento y expansión de funcionalidades, puede ser mejor elegir un lenguaje como C++ o Rust.
  • La técnica de renderizado con SDF es rápida y sencilla, pero parece tener límites en libertad de modelado y calidad. Las herramientas de modelado comerciales usan principalmente modelado de superficie como SubD o NURBS. Sin embargo, en juegos, demos y escenarios donde la inmediatez importa, el renderizado con SDF sigue teniendo un alto valor.
  • Es un buen ejemplo de lo complicado que puede ser elegir una librería open source; hay que evaluar bien documentación, calidad de código y soporte disponible antes de elegir, y una implementación propia también puede ser una buena alternativa.
  • Construir primero un programa funcional y mejorarlo gradualmente también es muy útil en la práctica. Parece importante ajustar bien prioridades para terminar primero las funciones centrales y después pulir los detalles.

1 comentarios

 
GN⁺ 2024-05-03
Opinión en Hacker News
  • Estoy totalmente de acuerdo con el autor en cuanto a las limitaciones de Raylib.
    • Actualmente estoy desarrollando un juego estilo tower defense que empecé con Raylib, y me he encontrado con restricciones similares.
    • Hay problemas como inconsistencias al alternar a pantalla completa entre plataformas, no poder enumerar modos de pantalla, no poder cambiar la configuración de renderizado en tiempo de ejecución y no poder guardar shaders compilados.
    • Raylib es buena para hacer prototipos, pero más allá de eso es difícil si no aceptas restricciones graves.
    • El proyecto ya avanzó demasiado como para cambiar ahora de Raylib a SDL u otra librería.
  • Guardar las formas en un arreglo de asignación estática es una manera hermosa porque evita fallos de asignación y fugas de memoria. De hecho, el límite de 100 formas no se siente como una restricción.
  • Espero que este proyecto siga evolucionando. En unos meses podría convertirse en una alternativa seria para algunos casos de uso concretos de Blender/FreeCAD.
  • Me encantó mucho la demo en vivo del video. Sería imposible hacer un video así en una semana, mucho menos construir la app.
  • Un texto interesante al hablar sobre decisiones de procesamiento de memoria, etc. Al volver a estudiar C mientras me metía en Crafting Interpreters, parte 2, me recordó qué hace bien C.
  • Gracias por el esfuerzo de hacer posible este logro con solo 2024 líneas de código en C :)
  • Hay algo realmente poderoso en hacer algo increíble simplemente con herramientas que ya conoces bien. Leí el artículo con gusto.
  • Coincido profundamente con los argumentos sobre C, especialmente con “la sintaxis no oculta operaciones complejas, por lo que es sencilla y no hace falta estar consultando todo el tiempo”. También, si necesitas averiguar algo sobre C, es muy fácil y provechoso. Es la ventaja de un lenguaje simple y veterano.
  • A veces pienso que C es todo lo que necesitamos.
  • Impresionante velocidad de desarrollo. El video explicativo también estaba súper entretenido de ver.