Experimenting with 3D web graphics to build a logo using three.js

With a bit of spare time the other month I decided to learn how to create some awesome 3D graphics in a web browser. I’d seen examples in the past of some cool stuff at Chrome Experiments website. So with a bit of research I decided to pick three.js as my Javascript library. I was surprised to find that partial support for WebGL sits at around 75% according to CanIUse.com and full support sitting at 49% (as of January 2015). I thought it would be much lower as I haven’t seen it around the web much.

I decided to make Mezzanine-media‘s logo, since thats where I work and initially started looking into three.js.

In the section above, we basically create a new div, test to see if the browser supports WebGL using Detector, initialise the THREE.WebGLRenderer and add the renderer to the new div we created. We finally need to declare a new scene, we will be adding all our components to this scene.

We have added two lights to the scene, set the positioning and in the second instance, set the intensity of the light before adding it to the scene. Relatively straight forward. Refer to the DirctionalLight documentation on threejs.org for a full set of options.

Creating the grid

With a little bit of maths, we’re going to create a nice simple little grid to give some perspective to our environment.

Declare the Geometry object which we then conduct a for loop to push new vertices that will determine our grid,

Create a Line object and pass in our geometry, line_material and the type (LinePieces, in this case),

Apply multiply scalar to the line,

Set the position and then add it to the scene.

Adding the camera pan control

We also need to initialise the Camera’s drag-pan controls. This allows the end user to move around the scene from a fixed point. We will use the cameraControls variable later on in our tutorial to update the scene on window resizing events and on the render() function.

//Add the camera pan control
cameraControls = new THREEx.DragPanControls(camera);

Load the Mezzanine logo

We haven’t build it yet, but lets place the following line of code so when we start constructing our logo, so we can see our progress.

2. The actual object (Mezzanine logo)

Our logo was created in Adobe Illustrator. This makes it relatively painless to get an object up and running. You’ll need to download and install an awesome Illustrator plugin called Ai -> Canvas plugin created by Mike Swanson. Once you’ve done that, you’ll need to open your AI file and remove any artboards / content that you don’t want to appear in your final render. (My first pass resulted in about 8 different versions of the Mezzanine logo getting output!). Once you’ve exported the file using Ai->Canvas, create a new JavaScript file and add a function called yourLogo(). Then go and open the HTML file you exported from Adobe Illustrator and view the source of the page and copy everything inside function draw(ctx) and paste into the yourLogo() function. In the new .js file, go through and comment out all of the following occurrences:

ctx.beginPath()

ctx.closePath()

ctx.fill()

cta.restore()

Now you’ll need to go through and create variables for each path in your file. You can work out when a new path occurs when you see ctx.beginPath(). You can refer to the mezzanine-three.js file as to how I set it up (it’s by no means perfect but gives you a rough idea). You may have noticed in the mezzanine-three.js file that there were a couple of references to ctxXX.holes.push(ctxXXSubtract). This basically, as the function reads, allows you to punch holes in your paths. Now you probably want to see what it will look like as you’re converting your paths. Create a function with the following:

For each new ctx variable you have, you’ll need to add another addShape function. You’ll also need to create some extrude settings to make your elements appear in 3D. You’ll notice at the top of the mezzanine-three.js file I have a couple of variables called extrudeSettingsX which contain the following parameters with various values:

These two script tags will be used to create our gradient. We will now need to place the following script after the last piece of script we wrote at the end of part 1. This will initialise the gradient.

Walking through the script above, we get the two script elements that we placed at the top of the document. We then initialise the uniforms variable object. Within this object we declare the topColor with a type of “c” for THREE.Color and the value of the gradient being “new THREE.Color(0x666666)”. We then repeat this for bottomColor changing the value to a different colour. We then set the offset and exponent values with a type of “f” to indicate a float value.

We then declare skyGeo and create a new THREE.SphereGeometry passing in the radius, widthSegments (number of horizontal segments. Minimum value is 3, and default is 8) and heightSegments (number of vertical segments. Minimum value is 2, and the default is 6).

We now need to create the THREE.ShaderMaterial and pass in the variables we have declared previously:

vertexShader

fragmentShader

uniforms

THREE.BackSide (Defines which faces will be rendered – options are front, back or both.

After this we declare sky and initialise a new THREE.Mesh(skyGeo, skyMat) passing in the previous two variables declared. We then need to set the position of the sky and finally add the sky to our scene.

Adding the composer passes

Please note the following is not part of the officially supported three.js library. However, it is used in many of their examples.

We now can add the composer passes which will provide a cleaner finish to our scene.

After adding the sky to our scene we need to call our function that has the composer elements.

Looking at the initPostProcessing function, we create a renderModel = new THREE.RenderPass, followed by a copyPass = new THREE.ShaderPass and composer = new THREE.EffectComposer. We then addPass the renderModel and copyPass variables to composer. We then set the copyPass.renderToScreen variable to true, this ensures that our passes are actually rendered to the screen. We finally trigger the toggleEffects function. We could put the contents of this function inside the initPostProcessing function, however, for testing purposes I’ve split them.

Moving onto the toggleEffects function, we initialise a new THREE.EffectComposer and add the scene and camera to the new pass. We are then going to add four passes:

Resizing the window

While not required, we should add a function that updates the scene when the browser window is resized. It’s a relatively simple function. We need to first add an event listener and pass it a function:

The function declares a windowHalfX and windowHalfY which are then used to set the camera.aspect property. We then update the camera.updateProjectionMatrix(). Finally we set the size of the renderer viewport to the current width and height.

4. The final touches

We’re so close to the finish line. We just need to create two more tiny functions and then call them!

The animate function is called at the bottom of our script. The first line, requestAnimationFrame(animate); is a browser-based API which was pass in the animate variable as a callback. We then call the render() function on the second line.

The render function consists of just two lines, the first, cameraControls.update() to update the position of the camera relative to user input. The second line to update the scene and camera. Note we have used the composer variable (with all our effect passes) as opposed to the renderer variable.

We finally init() our three.js animation and call the animate() function to trigger the drawing of the scene and camera.

Conclusion

You should now have a fully functional three.js animation running in your browser. Please feel free to post any suggestions or comments below.

In my example, I created the logo in Adobe Illustrator. You’ll need to follow Part 2 to convert it into the required format. I have not supplied the logo illustrator file as this is specific to a particular business. You can use pretty much any illustrator file provided you follow the guidelines :)