HSStudio Part 1/3: Using Three.js for In-Browser 3D
The rebel wunderkind of surfboard shaping, Haydenshapes is known for their progressive attitude toward design and craftsmanship — an ethic that is exemplified by their made-to-order custom surfboards. As longtime fans, we’re stoked to launch Haydenshapes Studio, a surfboard configurator that lets you spec, spin, flip, and buy your own Haydenshapes custom. HSStudio raises the bar for customer experience and performance, and we did some pretty trick things to pull it off.
Let’s pull back the curtain and take a peek at how we solved for some of the technical challenges we encountered. In Part 1, we’re going to share how we used Three.js for in-browser 3D. In Part 2, we’ll break down how we leveraged React.js for speed on mobile. Finally, Part 3 will uncover how we made it work in Shopify’s e-commerce environment.
Putting Three.js to work
Three.js is a powerful tool that makes webGL much easier to work with. Out of the box, there are scenes, objects, cameras and lights, all wrapped in an intuitive syntax with many impressive examples. We can’t thank the team and community enough for all their effort.
We had many early successes using Blender to import the client’s actual obj files, so the board accuracy is very high. We also used a Blender-Three.js export tool, a lighting rig we threw together for optimizing light on surfboard curves and Three.js’ materials to apply the image assets to the model. We quickly put together a proof-of-concept the client was stoked about.
Model complexity presented a major challenge because the models have over 40,000 vertices, which surprisingly wasn’t a problem on desktop or mobile devices. However, when adding multiple materials, we frequently crashed browsers on devices. Turns out Three.js’ decals operate by creating additional meshes, which would normally work quite well, but the number of our vertices was problematic on devices. This prohibited us from reliably creating the materials, especially on older devices.
Our solution: Create a Material Manager class that uses a canvas element to merge the materials into a single texture. For instance, to draw our fin image, flipped, into our canvas context:
context.drawImage(finImage, 0, 0, w, h);
One advantage to this approach is we can downsample the images for handheld devices, drawing images to the context with different dimensions, similar to responsively displaying smaller images on smaller viewports. Additionally, we can perform complex operations to merge the images, such as using blend modes.
Our Three.js key takeaways
- Performance with Three.js is not a concern. We considered leveraging React’s shouldComponentUpdate function to increase performance. Threeact.js, anyone? However, Three.js is already well optimized, so we didn’t have to do anything here.
- Smooth camera paths are easy to create, which you can see by using the controls in the bottom right of the desktop app. Simply set your camera, console.log its position, then use TweenLite to tween the camera.position to the new values.
- It’s very helpful to incorporate dat.gui for things like positioning lights so the model is well lit. When reviewing with our client, tweaking values in real-time meant the team was engaged and productive. Plus, it’s fun.
Putting it all together
We accomplished our client’s goals of empowering users to customize their own surfboards with accurate, in-browser 3D models, combining strong functionality and in a beautiful space. To see it in action, head to HSStudio and build your perfect custom.
This project would not have been successful without the hard work of many individuals. Our senior front-end engineer, Eric, did a great building the Bravenec© GUI, and Alex Roper nailed some iron-clad last-minute weekend coding. We’d like to thank our client, Hayden, for having the vision and trust while we sorted out how to accomplish his goals, as well as his team for building thousands of assets. It’s also important to recognize the efforts of the Three.js and Shopify communities. Without such excellent resources, we would have been adrift.