These days I'm playing with three.js again. I'm not an expert but I enjoy playing with graphics.
Conveniently, a friend of mine sent me this Dribble, and I thought it would be a perfect exercise to try making it. I still haven't done it, but I did some exploration on how to built it.
My plan was to draw the parallaxing layers as vectors, import it in three.js and use ExtrudeGeometry
to give them a third dimension. To three.js ninjas this might be funny, but it took me some time.
I haven't found well documented way, plus there are a couple of gotchas. And that is the reason I want to share this process with you.
Before we start
I'll assume you have a basic understanding of three.js, and how to setup a scene. If you are just starting with it, I recommend going through their excellent Getting Started guide first.
If you just want to see the end result, feel free to jump to the code or the live demo.
SVGLoader
Three.js provides us with SVGLoader, but it is not the part of the main library. It is an extra, you have to include yourself.
If you are using three.js script file directly, make sure you include this file as well.
However if you are using npm
module, you can import it like this:
;
Like it's name suggest this class loads SVG from an URL and parses it into three.js entries. If you already have SVG markup as a string, it is easy to parse it, but it is also easy to miss how to do it in the documentation.
SVGLoader
extends base Loader
class, which contains .parse()
method. That means we can do this:
// Get SVG's markup
;
;
;
Now when we know how to get SVG data to three.js, let's try to extrude it.
Extrude
You'll need a SVG, I used my logo:
To get to the shapes we can extrude, we need to parse the SVG. Get paths' data by calling .paths()
method. It will return an array of ShapePaths. Each of these has .toShapes(true)
method. Which will return another array, which in our case will always include only one item. (If you set second parameter noHoles
to true
it will return all holes as separate shapes. Read more in toShapes documention.)
Finally we got to the shapes {{ sidenote(text="we can use ExtrudeGeometry
on.", note=" You'll notice that every geometry in three.js has a "Buffer" version (e.g. ExtrudeBufferGeometry
). Those are more optimized versions you should use on complex projects. If performance is not an issue (e.g. while learning), we can use more user friendly non-buffer versions. ") }}
;
;
;
// Group that will contain all of our paths
;
;
// Loop through all of the parsed paths
;
// Add our group to the scene (you'll need to create a scene)
svgGroup;
Our progress will look something like this, and we got to our first gotcha:
SVG paths are inverted on Y axis
Our image is rendered upside down! This happens in the process of mapping SVG's 2d to three.js' 3d coordinate system. SVG coordinate system has a center in the top left corner and positive values on Y axis are drawn downwards.
Three.js renderer draws paths using values from SVG paths. But in 3d space, positive Y values are drawn upwards and our image gets inverted.
We can fix this by simply inverting the group that contain our objects:
svgGroup.scale.y *= -1;
Every object has (0, 0, 0) position
Shapes we got from SVG are rendered in correct positions, but for some reason they all have position set to (0, 0, 0), meaning each object is relative to itself. If you log mesh.position
you'll get:
Vector3
What confuses me is that they are obviously not at position (0, 0, 0) in the scene. If you can explain this and how to get their actual position in the scene, please leave a comment.
Rotating object around it's center
To show our object in it's full 3d glory let's add a rotation around Y axis. But it doesn't look good. It is rotating around its left edge (top left corner to be exact) instead of it's center.
September 2020 update: if you have only one path in the SVG you can use the tip from tuseroni's comment. Basically you just need to call geometry.center()
before creating the mesh, to center it based on the bounding box. Unfortunately if you have multiple paths, this won't work.
Usual way of changing the rotation pivot is by offsetting the object's geometry. We can't do that, as Group
class doesn't have geometry, but we can offset all of it's children. Now it comes handy that children are relative to themselves. We are just going to offset each child object for the half of the width and height of the whole group.
// Meshes we got are all relative to themselves
// meaning they have position set to (0, 0, 0)
// which makes centering them in the group easy
// Get group's size
;
;
size;
;
;
// Offset all of group's elements, to center them
;
Finally, we got what we wanted, so let's wrap things up.
Putting it all together
This post ended up longer than I expected, and I hope it wasn't too slow of a write up.
Code
Here is the code used, and beneath it you'll find the live demo.
// You'll need to create a three.js scene yourself
// Get SVG markup from DOM
;
// SVG Loader is not a part of the main three.js bundle
// we need to load it by hand from:
// https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/SVGLoader.js
;
;
// Group we'll use for all SVG paths
;
// When importing SVGs paths are inverted on Y axis
// it happens in the process of mapping from 2d to 3d coordinate system
svgGroup.scale.y *= -1;
;
// Loop through all of the parsed paths
;
// Meshes we got are all relative to themselves
// meaning they have position set to (0, 0, 0)
// which makes centering them in the group easy
// Get group's size
;
;
size;
;
;
// Offset all of group's elements, to center them
;
// Finally we add svg group to the scene
svgGroup;
Comments (4)