Over the weekend I experimented with hot reloading 3d models from Blender into a WebGL application.

In case you’ve never heard the term, hot reloading is when you reload something while your application is running. So, in our case, we’re updating our 3d model vertex data without refreshing the page.

Hot reloading a 3d model from Blender (right) -> Web Browser (left)

The experiment ended up coming together, so in this post I’ll explain how it works in case you ever want to do something similar.

Motivation

One of the challenges that I face with 3D modeling is the amount of hoops that I need to go through in order to get my model from Blender into my work-in-progress WebGL game.

After working on a model, I needed to export it using the correct settings and then run my converter on the correct file. Then I’d view my model in my game and if there were any issues I’d need to repeat the entire process.

Having this friction led me to dread making changes because of the repetitive work that needed to be done in order to finally see my changes.

As someone who is learning to model and to create art in general, having such a de-motivator stunts my growth. I end up wanting to work on something else that I’m good at instead of trying to incrementally improve my asset creation skills.

With this in mind, over the weekend I experimented with fully automating the export process.

The goal was to be able to hit save in Blender and have my updated model automatically appear in my game, without any additional effort from me.

The parameters

Right now my engine deals with two types of models - skinned and non-skinned.

Skinned models are models such as humans, wolves, ents or anything that use bones to control its movements.

Non-skinned models are models such as a house, a fence or a wall that don’t have bones for bending and deforming.

My process for getting a skinned model into the game is a bit more involved than my process for getting a non-skinned model into the game, so for Part 1 of this experiment I focused on hot-reloading non-skinned models.

If you’re interested in Part 2, where I’ll be experimenting with hot reloading models that have skeletal animations, join my mailing list!

How it works

The data flow is as follows:

I start a Node.js script that watches for changes to *.blend files.

chokidar . watch ( './*.blend' , {}) . on ( 'change' , ...)

Whenever there’s a change, our Node.js script spawns a child process that runs the a headless Blender instance. The instance executes a script that exports the Blender file into an .obj file.

require ( 'child_process' ) .exec ( blender model.blend --background \ --python blender-to-obj.py -- model.obj, ... )

The next step is to convert the file into JSON using wavefront-obj-parser . I converted it in another child process using the wavefront-obj-parser CLI , but you could just read the .obj file into memory and convert it using the wavefront-obj-parser API.

require ( 'child_process' ) .exec ( cat model.obj | obj2json > model.json )

I have a websocker server running, so next I send the new model.json to open websockets.

websocket . send ( jsonFileBuffer . toString ())

Lastly, the client receives the JSON data and overwrites the previous vertex data buffers on the GPU

clientWebsocket . onmessage = function ( message ) { var newVertexData = JSON . parse ( message . data ) // ... gl . bindBuffer ( gl . ARRAY_BUFFER , vertexPosBuffer ) gl . bufferData ( gl . ARRAY_BUFFER , new Float32Array ( newVertexData . positions ), gl . STATIC_DRAW ) gl . vertexAttribPointer ( vertexPosAttrib , 3 , gl . FLOAT , false , 0 , 0 ) // ... }

And voila, the client’s render loop is now rendering the model using the new vertex data!

I didn’t do any official benchmarking, but a model with around 8,000 vertices took just under two seconds after Blender file save to show up in my browser. Not bad!

You can check out the the complete source on GitHub.

Next steps

The steps are a bit coupled in the experimental code, but that should be fine for me for now. In the future I might separate the Blender -> OBJ step from the OBJ -> JSON step from the JSON -> websocket step. But for now I’ll probably just copy this code right into my game’s tooling.

It could be useful to abstract some of this into a standalone repo(s), but I’m not too interested in thinking through the proper abstraction right now.

You should of course feel totally free to take any of the code and build your own library. Let me know if you make something!

Thanks for reading!

- CFN