One of the exciting things about developing for the modern web is the rapid development of the underlying platform. Things like WebAssembly, WebGL, WebGPU, web workers, and more are unlocking experiences that were previously unimaginable for the typical web product. In the past couple of years we’ve seen products like Figma take advantage of this to create extremely compelling businesses and offerings.
WebGL, WebGPU, and 3D in general is one of these fundamental capabilities that has the most potential to push the web to the next level. Using these technologies directly, however, can be rather complex. In most cases, it makes sense to use a 3D web framework to speed up the development process. Today, there are two major open source frameworks that are capable enough to be considered seriously: Three.js and Babylon.js.
As 3D is a core component of our product, Spot, deciding between these two was a foundational technology decision. It was also important for our experience to feel “web native” and to have a fast load time, so using something like Unity and targeting a WASM build was off the table.
Between these two frameworks, Three.js is the oldest and most well known. According to Google trends, Three.js has significantly more interest and many new projects seem to be defaulting to it. The purpose of this post is to highlight our thinking in picking Babylon.js as our 3D framework of choice. Play canvas deserves an honorable mention here, but when we were originally making this decision, its core was not open source.
We are huge believers in TypeScript and are using it exclusively on both our front and back end. Babylon.js made the decision in 2014 to switch their codebase entirely to TypeScript. You can see a blog post outlining their reasoning here.
Going into this, we knew we were most likely going to have to heavily customize the underlying engine in order to build the experience we wanted. In our case, specific considerations included:
- Granularity of abstraction - We want significant control over the various aspects of the engine. This includes control over lighting, shadows, etc. and how they interact with the various objects in our scene. One part of Three.js that was somewhat off-putting was the “singleton” nature of the relationship between things like lights and shadow-maps and various objects in the same scene/layer. For example, each scene has a single `scene.shadowMap` property exposed, whereas Babylon.js has a ShadowGenerator class that can be constructed and associated selectively with certain objects. The same also applies to lighting.
- No render loop - Unlike a traditional 3D experience, one of our main goals is an extremely small footprint when it comes to the passive performance requirements of our application. We intentionally have designed the 3D scenes in our product to not change very frequently. Most of the time, simple 3D applications have a constant render loop running in the background, however in our case we want to only render after things have changed. Turns out, Babylon.js does not have a big leg up in this regard and we still have to do a lot of manual effort to get this to work properly, but this was something that was very important to us.
- Renderer vs game engine - Babylon.js seems to position itself more as a full-fledged game engine whereas Three.js positions itself more as just a rendering layer. In practice, Babylon.js still leaves a lot to be desired vs. something like Unity. Since the team at Spot does not have a strong background in 3D, having a deeper level of functionality tightly integrated into the core framework was desirable. This includes things like generating navigation meshes and advanced camera functionality. Three.js does have comparable support for these things, but generally in the form of external packages.
- WebGPU and WebXR - Given the nature of our application, having rails to guide development of an experience for VR devices was important. Both frameworks seem to do a good job at this. Given our performance-sensitivity, we were also interested in picking a library that purported to eventually support WebGPU. Again, both libraries seem to be moving in this direction, but Babylon.js seems to be a little further along. In particular, leveraging render bundles via snapshot rendering is very interesting to us, as it would allow us to reduce CPU usage significantly.
Babylon.js has relatively advanced tooling to help debug and understand the scene. The main tool we use is the inspector:
Unlike the Three.js editor, this tool can help us debug in the context of our actual application. We can select objects within our scene and directly inspect and manipulate properties. This can prove very handy for testing new changes as well as debugging.
Babylon.js also has a Blender addon, which aligns well with our own asset development workflow. We build our assets in Blender and have our own custom plugin that adds additional metadata to the output of the Babylon.js blender plugin.
Community and Support
One remarkable thing about Babylon.js and its community is the unparalleled access and support that comes directly from its core contributors and founders. When we first announced our product, we were able to meet with the original creator, David Catuhe, and got some direct feedback. In fact, David works at Microsoft, which is heavily investing in Babylon.js internally and has dedicated employees working on the project. Having a large company with real resources dedicated to the project was a big plus for us.
Of the handful of bugs that we have posted about in the Babylon.js forum, almost all of them have been fixed within days, with the updated code available on nightly builds. This is perhaps one of the friendliest open source communities I have been a part of. Not sure if it is an official policy, but “all bugs fixed within 24 hours” seems to be the mantra around here. This is exceedingly rare in most open source projects.
Documentation, on the other hand, is a little bit clunky compared to the Three.js equivalent. The existence of the playground, however, is indispensable for learning and consuming snippets of code.
A lot of the momentum in the Three.js community seems to be directed at react-three-fiber and it is worthwhile to mention it here. This is no surprise as the functional-reactive flavor of React has gained massive traction in the last couple of years. React-three-fiber gives developers the same React experience when it comes to developing 3D experiences on top of Three.js. There is also react-babylonjs, but it seems to have less traction.
At Spot, we are no strangers to this, as most of our UI is developed in React using these same paradigms. When it comes to the engine, however, we follow a more object-oriented approach. This is acceptable to us, as there is a lot of frame-by-frame logic that happens in a 3D engine as well as a lot of coupling across different aspects of the system (e.g. references to objects need to be passed to lights, shadow generators, navigation meshes, etc.).
For example, when rendering a normal React component, if we needed to perform any frame-by-frame updates (e.g. updating inside a `requestAnimationFrame` callback) we would specifically try to do this outside of the React render lifecycle for the sake of performance. These types of scenarios are much more common-place in a 3D application. I would be interested to see how this plays out in a very large react-three-fiber application.
For these reasons, this was not a driving factor in our decision, but this project is very interesting, particularly in the context of our above architectural requirement of only wanting to render when the scene changes. As a disclaimer, however, react-three-fiber was a lot less mature when we originally evaluated it.
In 2022, it must be said that both frameworks are very robust and comparable for the most part and picking either framework is a relatively safe bet. In reality, most of the above are relative nit-picks, but Babylon.js deserves serious consideration for non-trivial 3D web applications. This post shared some of our own reasoning in terms of making the decision of using Babylon.js for Spot.