Réaliser un modeleur 3D en C en une semaine
- L’automne dernier, j’ai participé à un événement de programmation d’une semaine appelé « Wheel Reinvention Jam ».
- L’objectif était de revisiter les systèmes logiciels existants avec un regard différent.
- J’ai créé un modeleur 3D appelé ShapeUp, et regarder d’abord la vidéo de démonstration de ShapeUp avant de lire cet article aide à comprendre.
- ShapeUp peut être essayé directement dans le navigateur.
Choix du langage : C
- Je me suis inscrit au Jam parce que j’étais frustré par la lenteur du compilateur TypeScript.
- En partant du parseur TypeScript d’esbuild ou de Bun, il semblait possible de créer un projet implémentant un sous-ensemble rapide de TypeScript.
- Mais je pensais qu’une comparaison de vitesse d’exécution des commandes de terminal ne ferait pas une démonstration intéressante, donc j’ai réorienté le projet vers la 3D.
- Grâce à la technique de ray marching avec des champs de distance signés (SDF), il semblait possible de créer un projet 3D à partir de zéro en une semaine.
- Une scène avec SDF peut être implémentée beaucoup plus vite qu’un renderer équivalent basé sur des triangles.
- J’avais déjà écrit des shaders SDF auparavant, mais à un niveau très basique, et éditer du code pour modéliser ne me paraissait pas naturel.
- Je voulais pouvoir éditer les formes à la souris, et je voyais ce Jam comme une occasion de le faire.
- J’ai donc nommé le projet ShapeUp.
Avantages d’utiliser le C
- Le C est un langage très simple et primitif, donc je pensais qu’il faudrait passer beaucoup de temps à compenser le manque de structures de données intégrées et à corriger des bugs de pointeurs.
- Mais la simplicité du C est devenue un avantage.
- Compilation rapide.
- La syntaxe ne masque pas d’opérations complexes.
- Simple, donc pas besoin de consulter sans cesse la syntaxe.
- Peut être compilé facilement en natif et en WebAssembly.
- Les défauts du C peuvent être évités grâce aux habitudes acquises en 22 ans d’utilisation.
- ShapeUp se compose d’un seul petit fichier C, ce qui le rend très simple.
Structure des données de ShapeUp
- Le modèle est constitué d’un tableau de structures
Shapes.
Shapes est stocké dans un tableau alloué statiquement.
- Pas de risque d’échec d’allocation ou de fuite mémoire.
- La limite de 100
Shape ne s’est pas révélée restrictive.
- Avec un manque de temps pour optimiser le renderer, la cadence d’images aurait probablement chuté avant d’atteindre 100.
- Avec plus de temps, j’aurais découpé le modèle en petits blocs et effectué le ray marching dans chaque bloc.
- La mémoire dynamique n’alloue avec
malloc que 3 endroits.
- Stockage (allocation d’un buffer suffisamment grand pour contenir le document entier)
- Export OBJ (allocation d’un buffer suffisamment grand pour contenir tous les sommets)
- Génération de shader GLSL (buffer pour la source du shader)
- Chaque cas a un seul
free à la fin de la fonction.
- Un exemple qui montre que la gestion mémoire peut être simple en C.
- Les langages comme C#, JavaScript et Python imposent une allocation individuelle de
malloc par Shape et le stockage de ces pointeurs dans un tableau dynamique.
- C est bon parce qu’il permet de contrôler le layout mémoire.
Interface utilisateur
- Réalisée avec une IMGUI (immediate mode user interface).
- J’aime le mode interface utilisateur IMGUI.
- Le débogage est très facile.
- Utilisation d’un vrai langage de programmation pour positionner les éléments (contrairement à CSS, constraints ou SwiftUI).
- Comme la plupart des IMGUI, j’ai utilisé des enums pour suivre quel élément a le focus et quelle action la souris effectue.
- Ce projet n’avait pas besoin de tableau dynamique ou de hashmap, mais si c’était nécessaire, j’aurais utilisé quelque chose comme
stb_ds.h.
Problèmes de la bibliothèque Raylib
- La décision d’utiliser le C a bien été bonne, mais raylib a posé problème.
- Il y a des choix de design étranges qui affectent l’expérience développeur.
- Utilisation de
int là où un type enum est attendu, ce qui empêche la vérification de type du compilateur et des fonctions non auto-documentées
- Pas de validation des paramètres par défaut (choix de design)
- Pas de responsabilité sur les dépendances (ne corrige pas les issues GLFW ni ne soumet de patch)
- La bibliothèque UI raygui n’est qu’un jouet.
- Impossible d’afficher des nombres à virgule flottante.
- Pas de routage d’événements de la souris pour les éléments qui se chevauchent ou sont clipés.
- Impossible de créer des coins arrondis.
- Impossible de bien styliser les éléments.
- Il y a aussi des bugs.
- Un bug empêchant le changement de police.
- La fonction de dessin ne partage pas les sommets entre triangles, ce qui crée des trous de pixel.
- Chaque fois qu’un problème était trouvé, je l’ai signalé, mais la plupart ont été fermés en « won’t fix » ; rédiger des rapports de bug demandait trop de temps, j’ai donc abandonné.
- Il était bon que la fenêtre OpenGL soit créée, mais la facilité entraînait un coût élevé.
- Heureusement, j’ai trouvé une sortie en appelant directement les fonctions OpenGL ou en implémentant des fonctionnalités dès le départ.
- Je vais utiliser sokol à l’avenir.
Développement sur une semaine
- ShapeUp se compose de 4 parties majeures à finir en 6 jours.
- Interface utilisateur (outils 3D, raccourcis clavier, barre latérale, controleur de jeu)
- Générateur de shader GLSL + renderer ray marching
- Sélection de la souris basée sur le GPU
- Marching cubes pour l’export
- Chacune n’était pas difficile, mais il a été difficile de bien prioriser les tâches et de ne pas s’enfermer.
- Pour les problèmes difficiles ou chronophages, il était utile de les résoudre via la conception, ou d’utiliser une solution simpliste qui fonctionne dans 90 % des cas.
- Parfois, attendre une journée de recul sur une fonctionnalité permettait de trouver inconsciemment une solution.
- Je voulais avoir un modeleur 3D qui fonctionne en permanence et l’améliorer progressivement au fur et à mesure du temps disponible.
- J’ai pensé au projet comme à une pyramide. Construite étage par étage, la pyramide n’est pas terminée jusqu’au bout, mais doit pouvoir être une pyramide complète à toute étape d’arrêt.
Résultats du projet
- Une semaine plus tard, je me suis retrouvé avec un programme 3D capable de créer des modèles 3D significatifs et d’exporter au format
.obj.
- Il s’exécute en multiplateforme et propose aussi une ouverture/enregistrement de fichiers.
- Le projet contient 2 024 lignes de code C et 250 lignes de GLSL.
- Il est un peu étonnant de voir qu’un modeleur 3D assez utile peut être exprimé en environ 2 300 lignes.
- J’ai reçu des demandes pour montrer une démo de ShapeUp dans un récapitulatif du Jam et à la conférence Handmade Seattle.
- Les gens semblaient impressionnés par ShapeUp, bien que je ne pense pas que cela ait été une réussite majeure : c’était un projet relativement simple.
- S’il y a quelque chose de spécial dans ce que j’ai fait, c’était le sens du bon choix de ce qu’il fallait faire, les connaissances nécessaires pour le réaliser, et la discipline pour y parvenir en moins d’une semaine.
Avis de GN+
- Un projet intéressant qui montre bien les avantages de la simplicité et de la rapidité du C. Mais vu le faible niveau d’abstraction du C, il semble difficile de l’utiliser directement dans un projet commercial. Il est probable qu’il faudrait un effort énorme pour implémenter en C toutes les fonctionnalités d’un outil moderne de modélisation 3D.
- Le fait d’avoir produit un programme fonctionnel en une semaine est impressionnant. Mais sur le long terme, en considérant la maintenabilité du code et l’extension des fonctionnalités, choisir C++ ou Rust peut être un choix plus judicieux.
- La technique de rendu avec SDF est rapide et simple, mais semble limitée côté liberté de modélisation et de qualité. Les outils de modélisation commerciaux utilisent principalement des techniques de surface comme SubD ou NURBS. Mais pour les jeux, les démos et d’autres cas où le temps réel compte, le rendu SDF paraît encore avoir une forte valeur.
- Un bon exemple des difficultés à choisir une bibliothèque open source : il faut bien évaluer la documentation, la qualité du code et le niveau de support avant de choisir. Une implémentation maison peut aussi être une excellente alternative.
- Créer d’abord un programme qui fonctionne puis l’améliorer progressivement est très utile en pratique. Il semble important de bien doser la priorisation en terminant d’abord les fonctionnalités cœur, puis en améliorant les détails.
1 commentaires
Avis Hacker News