Babylon.js vs Three.js in 2026: Three Years Building VXLVERSE Across Both Engines
Published April 30, 2026 · Engineering review
I started VXLVERSE in March 2023. ChatGPT was four months old and barely useful for code; Copilot occasionally guessed right. So I read a lot of docs and picked the engine that fit the project I had in mind: a no-code 3D game editor that runs in a browser tab.
I picked Three.js, mostly because React Three Fiber (R3F) existed and I was already a React person. I built two complete versions on Three.js across two and a half years, both open today: vxlverse-old (the first attempt) and vxlverse (the second). Both shipped, both grew unmaintainable in the same ways, both got shelved. By summer 2025 I knew the next step wasn't another Three.js rewrite.
In September 2025 I started over for the third time, this time on Babylon.js. Seven months in, it's the first version I'm actually proud of and the one running at vxlverse.com today.
This article is the engineering review I wish I'd read in 2023. It covers why I picked each engine, the specific bugs I hit during the Babylon rewrite, and a clear framework for which engine to pick if you're choosing today.
Why I started with Three.js + R3F
R3F was the killer reason. Composing 3D scenes as React components — <mesh>, <light>, <OrbitControls> — felt like cheating after years of imperative WebGL. Hooks like useFrame bridged React's render cycle into Three.js's render loop without ceremony. @react-three/drei shipped most of the helpers I needed for free.
The first VXLVERSE level editor went out in a few weekends. State stayed in React. Scene stayed in Three.js. The two layers cooperated through R3F's reconciler. For a small editor with a handful of props, this was the best 3D DX I'd ever had.
Where the cracks appeared (in my codebase, not Three.js)
VXLVERSE grew. Multiple scenes per game. Hundreds of GLB props. Real physics. NPC dialogue. Custom post-processing. By 2024 the codebase had three different state systems fighting each other and a custom asset cache that had drifted out of sync with R3F about who owned which texture.
The recurring categories of friction:
- Scene-graph divergence. R3F manages mounting via React reconciliation. Imperative spawning of dozens of meshes outside the React tree (game-time entity creation) drifted out of sync. Disposing a mesh while R3F still held a ref produced either a crash or a leak depending on the day.
- Asset cache lifecycle. I built my own cache because
useGLTFdidn't fit my "load 4,000 candidates, instance the picked ones, dispose the rest" pattern. The two systems disagreed about who owned a texture. - Post-processing assembly. Wiring bloom + DOF + FXAA + sharpen + grain + vignette + chromatic aberration manually with
EffectComposerworked, but the pass order was a thing I had to think about every time. - Cascaded shadow maps. CSM in Three.js needed a community implementation. I'd patched its quirks in three places by mid-2025.
- Physics integration. Cannon and Rapier are great libraries on their own, but the bridge code between Three.js meshes and rigid body simulation was a constant maintenance tax.
None of these are Three.js's fault. Three.js gives you the primitives; the system you build on top is yours. Mine was getting harder to maintain as the product got more ambitious. By summer 2025 I'd hit a ceiling I couldn't push through with the architecture I had.
Why I switched to Babylon.js in September 2025
Three days of prototyping, reading docs, and lurking the Babylon forum made the call clear. Babylon felt like an engine where Three.js had felt like a library. Both descriptions are accurate — both choices are valid. For VXLVERSE in 2025, I needed the engine.
The render pipeline as a single object
Babylon's DefaultRenderingPipeline bundles bloom, DOF, FXAA, sharpen, grain, vignette, chromatic aberration, and tone mapping into one configurable object. I deleted the post-FX assembly code and replaced it with property assignments.
First-class shadows, asset cache, and physics
Babylon's CascadedShadowGenerator just works for variable scene sizes. AssetContainer is the exact "load once, instantiate N times, dispose ref-counted" abstraction I'd been building badly. HavokPlugin turned my physics bridge code into a single import. The Inspector ships in-engine and lets me debug any node, material, or post-process by pressing a key.
A toon outline I didn't have to write
The Babylon post-processes library includes EdgeDetectionPostProcess, a screen-space toon outline that reads the depth + normal G-buffer. Three lines to integrate, almost no bundle weight. Wrote a separate post about it.
Real bugs I hit during the Babylon.js rewrite
Most engine reviews skip this section. It's the part you actually want. Seven months in production, here's what bit me — and how each got fixed. None are Babylon's fault; most are me misusing the engine while learning it.
- DOF + SSAO at 1.5× retina DPR pushed GPU frame time past 100 ms. The FPS counter still showed 60 because Babylon throttles the render loop, but
requestAnimationFramebacked up and the editor's fly cam felt sluggish. Fix: gate both effects off in editor mode, on in play mode. EdgeDetectionPostProcessattaches to one camera. My editor toggles between an orbit cam and a fly cam, so the toon outline disappeared on switch. Fix: ascene.onActiveCameraChangedobserver that rebuilds the post-process on the new active camera.InstancedMeshshares animation state with its source. Batched 10 identical zombie NPCs into one draw call; they all walked in lockstep. Fix: only batch entities with no animation groups (props and scenery). Characters get full meshes.- Custom material clamp observer fired during scene dispose and crashed the teardown. Fix: explicit observer cleanup in the dispose path of the scene render state.
scene.freezeActiveMeshes()too early froze skinned characters' animations. The skeletons weren't fully prepared yet. Fix: don't freeze active meshes when the scene contains skinned meshes, or freeze after a few render frames.- Geometry buffer not enabled when
EdgeDetectionPostProcessneeded depth + normal. Babylon enables the G-buffer lazily; you can hit a frame where the buffer isn't ready. Fix: explicitscene.enableGeometryBufferRenderer()before constructing dependent post-processes. - Default toon outline at 0.2/0.2 was too thick. Babylon's stock values gave a Borderlands look; we wanted Genshin Impact hairline. Tuned to 0.08/0.125 (intensity / width). Now the editor default.
Every one of these had a clean fix. None took more than a day to diagnose. Several would have taken much longer if the Babylon source weren't readable — which is a thing I now do regularly and never had to do with Three.js.
What I still miss from Three.js + R3F
- The declarative model. Composing scenes from React components is genuinely beautiful when it fits the project.
useFrame,dreihelpers, the React scope — I miss them. - Bundle size. Three.js + R3F + drei is meaningfully lighter than
@babylonjs/core. For an editor it's noise; for a landing-page demo it would matter. - Community example density. "How to do X in Three.js" → CodeSandbox results. "How to do X in Babylon.js" → forum thread, sometimes complete. The Babylon forum response time is genuinely shocking — frequently a maintainer replies within hours — but the public corpus of demos is smaller.
- Aesthetic gravity. Three.js demos look gorgeous because the community curates beauty. Babylon demos are functional. You'll do more art-direction yourself in Babylon.
Babylon.js vs Three.js: which to pick in 2026
The honest framing isn't "Three.js vs Babylon.js." It's "what kind of project, at what stage." Both engines are actively maintained and have responsive communities. The choice depends on what you're building.
Pick Three.js (especially with React Three Fiber) if
- You're building a portfolio piece, art project, data viz, or a tightly-scoped product
- You're already in React and want declarative scene composition
- You want the smallest possible bundle
- You value a thriving demo corpus and community examples
- You're comfortable assembling shadows, post-FX, and physics from primitives
Pick Babylon.js if
- You're building a game or a game engine and want the pipeline pre-assembled
- You need PBR + cascaded shadows + post-FX + physics integrated, not glued
- You want one configurable engine instead of assembling your stack from primitives
- You'd rather read API docs than reverse-engineer examples
- You value the in-engine Inspector for debugging more than a CodeSandbox gallery
If you can't decide, the cheap test is to prototype the smallest version of your product in both engines for three days each. You'll know which one fits.
The lesson, three years in
I treated the engine choice as identity for too long. "I'm a Three.js dev" was a fine starting frame and a bad sticking frame. The engine is the tool you used to ship the thing. The thing is what counts.
VXLVERSE finally feels like the product I imagined in March 2023. It just took switching engines once, shelving the codebase a few times, and learning a lot in between. Three years on, second attempt, first one I'm actually proud of.
If you want to see what came out of three years of iteration, VXLVERSE is live. Browse 4,000+ free 3D models, write NPC dialogue in plain text, publish a playable game from your browser. Built on Babylon.js, designed in Astro + Alpine, very much shipping.