Le pitch
VOID PIXEL est un générateur procédural de planètes pixel, directement dans le navigateur. Un clic produit une planète unique : type, palette, éclairage, vitesse de rotation, couches visibles. Le tout rendu en WebGL avec les shaders GLSL originaux du projet Godot de Deep-Fold, pixel pour pixel.
Utiliser : presque.cool/voidpixel/
Origine
Tout part d’une découverte sur itch.io : PixelPlanets, un générateur de planètes pixel publié par Deep-Fold et construit sous Godot. L’outil est simple, visuellement plaisant, et le code est disponible en open-source. J’ai voulu voir si je pouvais le faire tourner dans un navigateur.
V1 : 2022, vanilla JS
La première version sort en avril 2022. C’est un port vanilla JS, limité à une poignée de types de planètes traduits en Canvas 2D. Les shaders Godot sont retranscrits à la main en JS pur — lent, approximatif sur certains types, mais fonctionnel.
Ce n’était pas une version complète. Quelques types de planètes, pas d’export, pas de personnalisation fine. Un prototype publié tel quel.
V2 : 2026, port initial React
En février 2026, je reprends le projet de zéro. Le constat : la v1 traduit les shaders en JS alors que Godot tourne nativement en GLSL. Le bon chemin, c’est de porter les shaders directement via Three.js + GLSL. Le projet s’appelle encore “pixelplanets” à ce stade — un port basique, pas encore complet.
V3 : 2026, voidpixel
En avril 2026, le projet bascule. Le renderer passe de WebGPU/TSL à WebGL standard. Le projet est rebrandé “voidpixel” et les 12 types de planètes du projet original sont couverts : terran (humide et sec), îles, monde glacé, monde de lave, sans atmosphère, deux variantes de géantes gazeuses, étoile, astéroïde, trou noir, galaxie.
La v3 ajoute ce que la v1 n’avait pas : export PNG, GIF animé et spritesheet, sauvegarde locale, annuler/rétablir, plein écran, raccourcis clavier, i18n EN/FR, et support PWA.
Certaines fonctionnalités vont aussi au-delà du projet Godot original : générateur de nom et désignation de planète, éclairage activable/désactivable, zoom, anneaux paramétrables par type.
La fidelité GLSL
Le vrai sujet technique de ce projet, c’est le port des shaders.
Le projet Godot original tourne avec des shaders GLSL écrits pour son moteur de rendu. Les porter dans un navigateur demande de reconstituer exactement les mêmes conditions : même caméra orthographique, même espace UV, même clock de départ.
Un détail illustre bien la contrainte : dans Planet.gd, Godot initialise le temps à 1000.0 — pour que les planètes n’apparaissent pas dans un état initial artificiel au démarrage. J’ai reproduit ce comportement dans le moteur Three.js : la clock démarre à 1000 secondes, et le même seed produit exactement le même premier frame que dans Godot.
L’orthographic camera est mappée sur [-0.5, 0.5]², de sorte qu’un quad unité remplit exactement le viewport — c’est la condition que les shaders Godot attendent implicitement. Chaque shader GLSL a été adapté à cet espace sans modifier la logique de génération.
Stack technique
- React 19 + TypeScript (strict, zéro any)
- Vite 8 comme bundler (avec plugin-react)
- Tailwind CSS v4 en mode CSS-first (OKLCH)
- Zustand pour le state : settings, toasts
- Three.js + WebGL comme moteur de rendu
- GLSL natif — 14 shaders portés depuis le projet Godot de Deep-Fold
- gifenc pour l'encodage GIF côté client (export GIF animé, spritesheet)
- PWA via vite-plugin-pwa (Workbox, installable, offline)
- i18n maison (français/anglais)
- CSP avec hashes auto-générés
- Compression Brotli + Gzip
Architecture : logique de rendu dans domain/engine.ts, définition des planètes dans domain/planets/, shaders dans domain/shaders/. Le moteur expose un slot “planète courante” ; l’UI React pilote ce slot via usePlanetConfig et useThreeRenderer.
Évolutions
| Date | Version | Notes |
|---|---|---|
| Avr 2022 | 1.0 | Port vanilla JS, quelques types de planètes, rendu Canvas 2D |
| Fév 2026 | 2.0 | Port initial React + Three.js + GLSL (“pixelplanets”) |
| Avr 2026 | 3.0 | Rebrand voidpixel, WebGL, 12 types, export GIF/spritesheet, PWA, i18n |
Bilan
Ce qui a fonctionné :
- Partir directement des shaders GLSL du projet Godot plutôt que de retranscrire la logique en JS. La v1 avait fait cette erreur. La v3 traite les shaders comme des boîtes noires à brancher dans WebGL — moins de travail, bien meilleure fidélité.
- Le panneau de couches, qui permet de montrer ou masquer chaque couche de shader indépendamment. Un outil de débogage que j’ai gardé dans l’interface finale parce qu’il est utile.
- La séparation capture/encodage dans l’export GIF. La capture des frames reste sur le thread principal (WebGL + getImageData l’exigent), mais la quantification et l’encodage sont déportés dans un Worker via transferables. 60 frames × 256² × 4 octets (~15 Mo) transférés sans copie — l’interface ne gèle plus pendant l’export.
Ce que je changerais :
- La tentative WebGPU/TSL : une semaine de travail qui n’a pas abouti avant de revenir à WebGL standard pour la compatibilité. La décision de WebGPU était prématurée. À refaire, j’aurais commencé WebGL directement.
The pitch
VOID PIXEL is a procedural pixel planet generator, directly in the browser. One click produces a unique planet: type, palette, lighting, rotation speed, layer visibility. Everything rendered in WebGL using Deep-Fold’s original Godot project GLSL shaders, pixel for pixel.
Use it: presque.cool/voidpixel/
Origin
It started with a discovery on itch.io: PixelPlanets, a pixel planet generator built by Deep-Fold in Godot. Simple, visually sharp, and open source. I wanted to see if it could run in a browser.
V1: 2022, vanilla JS
The first version shipped in April 2022. A vanilla JS port, limited to a handful of planet types translated manually into Canvas 2D. Godot shaders rewritten as plain JS — slow, approximate on some types, but working.
It was not a complete version. A few planet types, no export, no fine-grained controls. A prototype published as-is.
V2: 2026, initial React port
In February 2026, I started over from scratch. The core issue: the v1 translated shaders to JS while Godot runs them natively as GLSL. The right approach is to port the shaders directly through Three.js + GLSL. The project was still called “pixelplanets” at this point — a basic port, not yet complete.
V3: 2026, voidpixel
In April 2026, the project shifted. The renderer moved from WebGPU/TSL to standard WebGL. The project was rebranded “voidpixel” and all 12 planet types from the original were covered: terran (wet and dry), islands, ice world, lava world, no atmosphere, two gas giant variants, star, asteroid, black hole, galaxy.
V3 adds everything v1 lacked: PNG export, animated GIF, spritesheet, local save, undo/redo, fullscreen, keyboard shortcuts, EN/FR i18n, and PWA support.
Some features also go beyond the original Godot project: planet name and designation generator, toggleable lighting, zoom, parametric rings per planet type.
GLSL fidelity
The real technical subject of this project is the shader port.
The original Godot project runs with GLSL shaders written for its rendering engine. Porting them to a browser means reconstructing exactly the same conditions: same orthographic camera, same UV space, same starting clock.
One detail illustrates the constraint well: in Planet.gd, Godot initializes time at 1000.0 — so planets don’t appear in an artificial initial state on startup. I reproduced this in the Three.js engine: the clock starts at 1000 seconds, so the same seed produces exactly the same first frame as in Godot.
The orthographic camera is mapped to [-0.5, 0.5]², so a unit quad fills the viewport exactly — that is the condition the Godot shaders implicitly require. Each GLSL shader was adapted to this space without touching the generation logic.
Tech stack
- React 19 + TypeScript (strict, zero any)
- Vite 8 as bundler (with plugin-react)
- Tailwind CSS v4 in CSS-first mode (OKLCH)
- Zustand for state: settings, toasts
- Three.js + WebGL as the rendering engine
- Native GLSL — 14 shaders ported from Deep-Fold's Godot project
- gifenc for client-side GIF encoding (animated GIF export, spritesheet)
- PWA via vite-plugin-pwa (Workbox, installable, offline)
- i18n home-built (English/French)
- CSP with auto-generated hashes
- Compression Brotli + Gzip
Architecture: rendering logic in domain/engine.ts, planet definitions in domain/planets/, shaders in domain/shaders/. The engine exposes a “current planet” slot; the React UI drives that slot via usePlanetConfig and useThreeRenderer.
Changelog
| Date | Version | Notes |
|---|---|---|
| Apr 2022 | 1.0 | Vanilla JS port, a few planet types, Canvas 2D rendering |
| Feb 2026 | 2.0 | Initial React + Three.js + GLSL port (“pixelplanets”) |
| Apr 2026 | 3.0 | Rebrand voidpixel, WebGL, 12 types, GIF/spritesheet export, PWA, i18n |
Retrospective
What worked:
- Going directly from the Godot project’s GLSL shaders rather than rewriting the logic in JS. V1 made that mistake. V3 treats shaders as black boxes to wire into WebGL — less work, much better fidelity.
- The layers panel, which lets you show or hide each shader layer independently. A debugging tool that ended up staying in the final UI because it is genuinely useful.
- Splitting capture and encoding in the GIF export. Frame capture stays on the main thread (WebGL + getImageData require it), but quantization and encoding are offloaded to a Worker via transferables. 60 frames × 256² × 4 bytes (~15 MB) transferred without copying — the UI no longer freezes during export.
What I’d change:
- The WebGPU/TSL attempt: a week of work that went nowhere before reverting to standard WebGL for compatibility. The decision to use WebGPU was premature. Starting with WebGL directly would have been the right call.