Bruno Simon — Portfolio (case study)


To be fully honest, I never thought people would like it this much and I still cannot figure out why, but I’m really glad and grateful for all the feedback and kind words that I’ve received.
Some developers asked me to explain how I built it, and it’s just a bunch of simple techniques that I’ll try to explain here.

This article should be readable by any of you who has a digital background, as I tried to make it understandable as much as possible, even if you’re not a developer.

I hope you’ll enjoy this little “behind the scene” of my portfolio!


At first, I wanted to make something original, entertaining, playable and understandable.

As I’ve been using WebGL a lot last years and as a huge gamer, going for a game concept was an obvious choice. Since I was a kid, I’ve always liked playing with remote controlled cars and I’ve spent lots of time playing Micro Machine on Playstation. So as a result, I decided to build a universe where you could drive around with a remote car.

I’ve always found that physics wasn’t used enough in the web, that’s why I wanted the elements composing my universe to be “bump-able”.

Everything had to be part of the 3D world, but I also had to find ways to guide the user. Also, I chose to have no interface so that the experience could be as immersive as possible.

And for the inspiration, as a fan of Gustavo Henrique, I’ve decided to try this tiny and colorful architectures with a lot of flatness but slightly rounded edges style.

I’ve always wanted my portfolio to be really personal, so I decided to produce it entirely alone.

Since I knew that this project was going to take time, I also made sure that I could have fun myself (and believe me, I’ve lost too many time doing tricks with the car…).
What I really believe, is:

the best game you can build, is the one you spend too much time playing on.


The experience starts with the universe popping up from the ground, and the car falling down from above with a little bounce, so that the user notices the physics.

The only thing written are the instructions, directly on the ground, inviting the user to use his arrow keys to move (it’s not indicated anywhere, but you can actually use WQSD or ZQSD (french keyboard) controls for players used to play PC games 😉).

Placement of the car wasn’t a random choice: I really wanted to ensure that by bumping the title “BRUNO SIMON” at the beginning of the experience, it would maximise understandability of elements being moveable.

In order to help and guide the user to the right directions, I used some simple tricks such as:

The tiles which work like a path, to help user understand easily that there is more to see in that direction.

The walls as boundaries.

Panels to… well, you know, they’re panels 😅.

Interactive areas that expand when driving on it but you can also hover them using your mouse.

At the end, the universe is sectioned into 5 parts:

  • The intro to learn how to navigate
  • The crossroads that act as the guide and link between other sections
  • The playground to have fun with the physics
  • The projects I’ve been working on (let’s not forget it’s a portfolio…)
  • Complementary information with how to contact me

After publishing my portfolio, one of the most asked question was how to do the jump in the playground. While it was quite obvious for me to use the SHIFT key, it wasn’t at all for non-gamer users.
So I decided to add more instructions (but not visible directly in the intro to prevent information flood).


To find the best color, gravity, speed, mass, etc. it’s important to have a debug panel. As an old school developer, I used the well known Dat.GUI and I kept it until the end of the project (minor few options).

From the beginning to the end, I kept tweaking the values to find the perfect combination.

Using this kind of debug tool can have bad result on performance and you should make sure to deactivate it completely on production. One good trick is to have it enabled only for specific URL like this one (still working if you’d like to play with it as well).


For the WebGL rendering, I used Three.js. I’ve been using this library for many years and I couldn’t have done this project without it. Native WebGL enables great optimisations, but there is too much more work to do to achieve the same result.

Samuel Honigstein (@samsyyyy) provided me an optimised version using VAO (Vertex Array Object), which drastically reduced the WebGL calls.

I used many tricks to obtain this aspect with a decent framerate, but one important thing to understand is that there are no lights, nor shadows in the scene. At least no “common” lights nor shadows. It’s just illusions.


A very old but efficient technique I used is called Matcap. Matcaps are textures that serve as reference to “color” the object.

It uses normal relatives to the camera to pick the color on the provided texture.

The downside of this technique is that you can’t have dynamic lights and if you turn around the object, the light will always come from the same direction, relative to the camera.


The floor is composed of a simple plane always filling the screen. A simple 2x2 texture created directly in JS is sent and the shaders uses the UVs to color each corner. Those colors are then naturally interpolated for the whole surface.

Light bounce

To add more realism and a better lighting effect, I wanted to add a light bounce. As it is currently almost impossible to have true WebGL light bounce with a good framerate, I had to find a solution.

As the floor is the most prominent shape in the scene, and as the orange color is vivid, I’ve faked the light bounce of the floor only.

I simply calculated the distance of the vertex to the ground and multiplied it by the dot product between the “absolute normal” and the up axis.
Basically, to make it simpler: the more the face is facing the ground and is close to it, the more orange it gets.


There are two types of shadows.

1 — Static shadows for static elements

I simply baked those shadows in Blender and saved them in PNG-8 files, well compressed and as small as possible.

2 — Dynamic shadows for moving elements

These ones were a little more tricky to implement.
I added a plane underneath each moving element, and updated each one according to:

  • the associated object rotation,
  • its position,
  • its distance to the ground,
  • the sun orientation

Those planes had shadow textures on it, and I also reduced the opacity the higher the object was.


To improve the style, I added a cheap vertical and horizontal blur on top and bottom of the screen.
I also added a huge light flare on the left, matching the supposed orientation of the sun light, to add more realism and a warm and good feeling.


The camera is a simple perspective one looking at the car and moving with it.
Then I added a simple easing to smooth the movement and to let the user feel the speed.

Many testers told me that the projects section wasn’t readable enough. As a solution, I simply tweened the position of the camera, above the car and looking downward, when the user is entering the section.

I also added a zoom in and out feature using the wheel to improve readability.


For the physics, I decided to go for a 3D engine. One way of doing it could have been to use a 2D engine and keep objects on the floor, but I wanted more realism.

Cannon.js is a pretty good library and very useful, but it lacks examples and I spent a lot of time in the library code to understand what I should do. I also had a framerate drop after playing few minutes in the game. It was due to the objects not going to sleep by default. Meaning that if you bump into every objets, each one will be tested for collision on every frames even if they are barely moving.

To simplify the physics, I created primitive shapes matching the more detailed models. Cannon.js was handling those primitive shapes, and I updated the Three.js universe on each frame according to the primitive coordinates.

I did the antenna animation without the physics in order to do exactly what I had in mind. I rotated the antenna according to the opposite acceleration of the car and then applied a force pushing it back to its center. The furthest the antenna was, the stronger the push back force was.


I used Blender to do the modeling. I’ve been using it for a year, mostly for web projects, and I think it’s amazing. It’s not the most accessible one, but once you get used to it, the controls, the shortcuts and the interface make lots of sense.

I’m not a 3D expert so I thought this portfolio was a really good occasion to practice. I modeled everything: trees, myself, bowling ball, my dog… yeah, I’ve put him in my portfolio (and by the way his name is Sudo, hope you get it 😉).

For the process, I modeled the 5 sections and some specific objects separately.

I used Eevee most of the time. Eevee is the new real time rendering engine of Blender. It uses the GPU and it’s usually way faster than Cycles. Unfortunately, it has some limitations, but my scene was very simple so it wasn’t a problem.

I exported each part using GLTF with Draco compression. In this case, Draco was a real game changer. Files went from 50% to 70% lighter depending on the geometry. I exported the models with normals and the collision primitives with as minimum data as possible.

Eevee doesn’t support baking for technical reasons. Fortunately, both Eevee and Cycles have very similar render. I just had to switch to Cycles to do the floor shadows baking that I saved in PNG-8, and compressed it using tinyPNG.


As you may have noticed, I’m not a sound designer. But I truly believe that it has to be done in order to have a complete immersive experience in a digital production. And as I said before, I wanted to do everything by myself 🙂.

To add realism, I needed to add randomness in the sounds. For instance, in real life, if you try to hit a brick twice with the same speed on the same spot, the sound triggered will never be exactly the same.
I knew that Howler.js allow speed control of the sound so I decided to use it.

Punctual sounds like falling bricks are made by:

  • detecting collisions in the physics,
  • testing the velocity,
  • playing random sounds from a set of multiple similar sounds,
  • adjusting the volume and the speed with a bit of randomness

Then, I also added a limit for simultaneous sounds playing, to prevent ears bleeding!

For the engine, I tested multiple repeating sound of engine and tried different speed.
I added a speed variation depending on the acceleration of the car.
To be really honest, I’m still not fully satisfied of the result, but I believe it’s a good start for a non sound designer…

Unfortunately, some modern browsers prevent any sound to be played until user interaction with the page, which is limited to touch and click (sadly, no keyboard interaction). And that’s the story behind the start button at the beginning. But at the end, it was the perfect occasion to add a loader, even if the full website is pretty light.

And obviously, I also had to find a sound for the intro to the world. During my quest to find the best sound effect, I found this cool paper unwrap sound. I tried to edit it a little, and then created an animation where all the objects were going up from the ground while the sound was playing. It came up pretty cool in my opinion.


I’m a big fan of the Metal Gear Solid series and one thing Hideo Kojima (director and producer of MGS) taught me is to spend extra time on details even if not every user would notice them. For instance:


Did you notice that car back lights are lighting up according to the user reversing or using the brake?

Animated <title>

Maybe you saw this one, the page title is animated. If you go forward, the emoji car will go forward and vice versa. Unfortunately, the title cannot be updated at high frequency.

Brick walls

I eased the walls building by creating simple functions to make different wall shapes. And it was actually at this exact moment that I decided to add a playground section. Going full throttle through walls was just too satisfying to keep it for myself!
I added some randomness on the bricks positions for more realism.


Tiles on the floor were used to create paths and guide the user. I simply created 5 different tiles, rotated them randomly, and made sure to never have the same tiles next to each other.

Easter eggs

Wait… easter eggs?


I was really surprised when I tested it on mobile because the framerate weren’t that bad. However, as a matter of performance and usability, I applied some optimisations such as removing the blur, clamping the pixel ratio and deactivate matrix auto update for Three.js objets.

For the controls, I added simple buttons to control the speed and a joystick to control the steering. I know I know, I said “no interface” but I couldn’t find any intuitive solution other than that for mobile…


To conclude, as lots of you kept asking me, for the curious ones, here are some indicators (as for now)


How many months it took me. I mostly worked during evening and weekends.


The number of Mo the full website weights. And it’s mostly because of projects images.


The number of visits in the first week


The average number of seconds spent on the website

1,700 and 7,100

The number of retweets and likes my announce tweet had. It’s probably not much, but it’s my personal record.


The number of new twitter followers I got in the first week.


I’d like to thank all the people that sent me kind messages and also the bug diggers who allowed me to improve the site day by day. I’m really thankful for all the positive impact that this portfolio has done, and feel great that sacrificing my social life during 3 months was completely worth it 💪

I hope this article was fully understandable.





Creative developer living in Paris, freelancer, former lead developer at Immersive Garden, former developer at Uzik and teacher.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Sonar cloud integration with Github repository for React Native App — Part1.

Do you procrastinate?

Short TypeScript Import Paths in Angular9

Vue Skeleton Loading Screen using Suspense Components — Daily Vue #4

Reaching 88mph! Optimizing website performance on Ruby on Rails

API Testing with Postman — Getting Started

Learning at Lightning Speed is Easy, Trust yourself and Things Start to Happen — Common JS Utility…

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store


Creative developer living in Paris, freelancer, former lead developer at Immersive Garden, former developer at Uzik and teacher.

More from Medium

Early Owl — My 3 Cents on how to start a New Game Project

How Acting Skills Helps Create User Centered Design

The 5 best free Unity SDK for Mobile Unity 3D Developers

In-depth Analysis of Unity Loading Module Ⅱ: Shader