.blend Won't Open on the Web — Blender to Web Is a Translation That Keeps Only What the GPU Can Eat
And why our 3D game ships with zero model files
Putting something made in Blender on the web isn't a file format conversion. It's a translation that keeps only what the GPU can consume directly and throws away the rest. What moves is the shape, not the picture. After this you'll know in advance what disappears on export, and be less surprised by "it looked great in Blender, why does it look like this on the web."
A 3D game with zero model files
Our 3D hide-and-seek game has 24 NPCs walking around a plaza, with players blending in among them. It runs on three.js. Search the repo, though, and there isn't a single .glb, .gltf, .fbx or .blend. The avatars are assembled from boxes in code.
const GEO = {
head: new THREE.BoxGeometry(0.42, 0.42, 0.4),
torso: new THREE.BoxGeometry(0.58, 0.68, 0.32),
limb: new THREE.BoxGeometry(0.17, 0.62, 0.2)
}
"Why not just make a person in Blender and drop it in?" That question skips four steps.
1. .blend was never meant for exchange
A .blend file is close to a direct dump of the structs Blender held in memory. Its layout is tied to the version, and in practice only Blender reads it properly. Browsers don't have a .blend loader not because nobody bothered, but because it was never designed for exchange. Same reason you don't put a Photoshop .psd straight on a web page.
2. glTF leans toward the GPU
glTF 2.0 (.gltf + .bin, or bundled as one .glb) didn't become the standard for its compression. Its storage layout already leans toward the GPU. Faces are split into triangles, and vertex data sits in binary buffers, so what you read can go to GPU buffers almost as is. Materials are a handful of PBR metallic-roughness values (describing a surface by how metallic and how rough it is).
| Format | On the web |
|---|---|
.blend |
won't open. Blender only |
| OBJ | opens, but no animation |
| FBX | Autodesk's proprietary format |
| glTF / GLB | the de facto standard. three.js and <model-viewer> read it directly |
3. Export is lossy. This is the important part
| In Blender | After export |
|---|---|
| Modifier stack (Subdivision, Array, Boolean) | baked into vertices or gone |
| Geometry Nodes, procedural materials | vanish unless baked to textures |
| Cycles node graph | flattened into a few PBR values |
| Physics, particles, simulation | none |
The picture in Blender's viewport is something EEVEE or Cycles (Blender's renderers) computed. The web receives vertices, textures and PBR constants, and the browser redraws the picture from scratch. So a render that looks different from Blender isn't a bug. That's just how it works.
4. From loader to GPU
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
Our repo has three 0.183.1, and this path works as is. GLTFLoader reads the JSON (scene layout) and the binary buffers and builds BufferGeometry, Material and Texture. Once those are on the GPU, it's draw calls every frame.
There are two ways to put it on a page. If you only need to show it, drop a .glb URL into the <model-viewer> web component and you're done; another app of ours does exactly that. If it goes into a scene or game where you control lighting, camera and collisions, it's three.js + GLTFLoader.
So why didn't our game use GLB
Not laziness. It came out of the game rules. The rule is "players can't do anything NPCs can't do." So the walk cycle is tied to accumulated distance, not time. Stop moving and the legs stop. Walking in place is the biggest giveaway there is.
const s = Math.sin(walkPhase) * swing // walkPhase comes from accumulated distance
avatar.legL.rotation.x = s
avatar.legR.rotation.x = -s
A walk animation baked in Blender and played through AnimationMixer runs on time. To tie it back to distance you'd feed mixer.setTime(walkPhase) yourself, and at that point most of the benefit of baking the animation in Blender is gone.
On top of that, everyone has to look identical. If the hider differs in any way, that's the answer key. With models, just changing shirt colors means splitting materials, which is more work, not less. The tighter the constraints, the more code-built geometry pays off.
Performance is still open
Right now each person has 6 parts, so there are around 150 meshes. Sharing geometry saves memory, but without InstancedMesh, every mesh is its own draw call. Switching to GLB skinned meshes would cut the mesh count to one per person but add skinning cost in the vertex shader. Which is cheaper needs measuring, and I haven't done the mobile FPS comparison yet.
If you're figuring out how to get 3D models into a web service or game, you can ask negabaro. The calls above are ones we made ourselves while building this game.
Key Points
.blend isn't an exchange format. Export to glTF / GLB for the web
Modifiers, Geometry Nodes, Cycles nodes and physics get baked or dropped on export
Just displaying it: <model-viewer>. Scenes and games: three.js + GLTFLoader
If animation must follow something other than time, code-built geometry can be the better choice