In this step-by-step guide, we’re going to create a 3D version of the Treehouse logo using three.js, which is a 3D graphics framework built on top of WebGL. Click and drag your mouse to orbit the camera! You can also use your mousewheel to zoom in and out.
See the Demo Download the Code View on CodePen
Note: You’ll need the desktop version of either Chrome, Firefox, or Safari. See browser compatibility below.
3D graphics can be difficult, especially 3D in the browser. Frameworks like three.js make it a bit easier, but the official documentation is still under construction and there are a few quirks that can stop beginners from ever getting started. If you’re new to 3D, this guide will help you get started.
Even though three.js might look complex at first, it would actually take even more code to write the same thing in pure WebGL, mostly because we’d need to write a rendering engine. All the heavy lifting is done with three.js without sacrificing much flexibility.
Contents
Browser Compatibility
For this tutorial, you’ll need the desktop version of either Chrome, Firefox, or Safari. Unfortunately, WebGL doesn’t work on mobile browsers yet, and it won’t be available in Internet Explorer until version 11.
Also, if you’re using Safari, you need to enable WebGL first. Here’s how to enable WebGL in Safari:
- Open the Preferences menu.
- Click on the Advanced tab.
- Click the checkbox that says Show Develop menu in menu bar.
- Open the Develop menu from the menu bar and select Enable WebGL.
Here’s the caniuse.com matrix for WebGL compatibility. Hopefully support will pick up in the future, because this is a really cool technology!
Getting Started
Download three.js
Head over to http://threejs.org/ and click the “Download” link on the left side of your screen. Once the zip has finished downloading, open it up and go to the build folder. Inside, you’ll find a file called three.min.js and, if you’re following along, you should copy this file into your local development directory.
For this tutorial, you’ll also need a file called OrbitControls.js which is included in the three.js download. Here’s the file path:
threejs folder > examples > js > controls > OrbitControls.js
If you’d rather just grab the two files you need, they’re included with the example code for this tutorial.
Setup the Local Environment
JavaScript has a security feature called the same-origin policy, which means you cannot load externally hosted files inside of your JavaScript code. This can be slightly problematic, because three.js needs to load geometry, textures, and other files. In order to circumvent this issue, you’ll need a local http server so that your files come from the same origin. Simply opening the index.html file directly in the browser isn’t going to work.
Fortunately, there’s a three.js FAQ with an excellent guide on how to run three.js locally using either Python, Ruby, or adjusting your browser settings. It’s easier than it sounds, so if you’re scratching your head wondering why some files aren’t loading, check out the guide.
Create 3D Assets
I’ve already created a 3D version of the Treehouse logo that you’re welcome to use for learning purposes (you can find the mesh inside the code download), but if you’d like to create your own meshes, I recommend you use Blender. It’s a wonderful 3D modeling and rendering package that’s free, open source, and cross-platform. There’s also plenty of educational material out there (free and paid) to help you get started modeling. I used Blender for the first time and had my finished mesh in about an hour. There’s probably some optimizations I could have made (the mesh topology is actually a bit messy) but it works for this demo.
In order to export a mesh from Blender for use in three.js, you’ll need to open the utility folder in three.js and install the exporter. Here are instructions on how to export from Blender to three.js.
The HTML
Alright. Once you’ve got your files in place and your local environment setup, it’s time to start coding. Let’s get the HTML out of the way first, because that’s the easy part. You just need a basic template like this to get going. This also assumes your JavaScript is stored in a folder called js
, so check your file paths just in case.
index.html
<!doctype html>
Like I said, nothing special here. The magic happens between the script tags.
Using three.js to Create 3D Scenes
We could write our JavaScript externally, but since there aren’t any HTML elements inside of the body, I figured it would help make this example a bit more clear if inline script tags were used.
Global Variables and Functions
Inside our script tags, we want to set up some global variables and then call some functions, all of which will be defined later on:
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
init();
animate();
Create the Scene
Three.js uses the concept of a scene to define an area where you can place things like geometry, lights, cameras, and so on. In the following code, we start writing our initialization function by creating a scene. Then, we store the width and height of the browser window in the variables WIDTH
and HEIGHT
. We’ll need them in more than once place later on, so it’s good to just grab them once and store them.
// Globals from the previous step go here...
// Sets up the scene.
function init() {
// Create the scene and set the scene size.
scene = new THREE.Scene();
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
// More code goes here next...
}
Create the Renderer
Next, we set up a three.js renderer. We could use the SVG or canvas renderers, but we want to use the WebGL renderer because it’s able to take advantage of the GPU, which makes it several orders of magnitude more performant. After creating the renderer, we append it to the DOM via the body element. This will make three.js create a canvas
inside the body element that will be used to render our scene.
// Sets up the scene.
function init() {
// Code from previous steps goes here...
// Create a renderer and add it to the DOM.
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(WIDTH, HEIGHT);
document.body.appendChild(renderer.domElement);
// More code goes here next...
}
Create a Camera
Once our scene and renderer are in place, we can create a camera. The PerspectiveCamera
takes a few parameters. They are:
- FOV – We’re using 45 degrees for our field of view.
- Apsect – We’re simply dividing the browser width and height to get an aspect ratio.
- Near – This is the distance at which the camera will start rendering scene objects.
- Far – Anything beyond this distance will not be rendered. Perhaps more commonly known as the draw distance.
After our camera is created, we set the position by using some simply XYZ coordinates. The default is 0,0,0
but I’ve set the Y value to 6 just to get some distance between our view and the mesh.
Finally, we need to add the camera to the scene.
// Sets up the scene.
function init() {
// Code from previous steps goes here...
// Create a camera, zoom it out from the model a bit, and add it to the scene.
camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 20000);
camera.position.set(0,6,0);
scene.add(camera);
// More code goes here next...
}
Update the Viewport on Resize
This is all well and good, but what happens when the site visitor resizes the browser window? For that, we’ll need to add an event listener. When the browser is resized, a couple of things happen. First, we resample the new width and height of the browser and store it in a variable that’s scoped to the function. Then, we use those values to set the new size of our renderer as well as recalculate the aspect ratio of the camera. In addition, we need to call updateProjectionMatrix()
on the camera object so that our scene will actually update with the new parameters. This is computationally expensive in the context of real-time 3D rendering, but once the browser is resized, things click back to their normal frame rates.
// Sets up the scene.
function init() {
// Code from previous steps goes here...
// Create an event listener that resizes the renderer with the browser window.
window.addEventListener('resize', function() {
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
// More code goes here next...
}
Add Lighting
Now it’s time to start crafting our scene a bit. By calling the setClearColorHex
function on our WebGLRenderer
object, we’re able to set the background color of our scene to the Treehouse grey hex color with an opacity of 1.
Next, we’ll need a light in order to see our 3D objects, so we’ll add a PointLight
to the scene and set its position. There are several other kinds of lights you can add to a scene, so be sure to check out the linked documentation.
// Sets up the scene.
function init() {
// Code from previous steps goes here...
// Set the background color of the scene.
renderer.setClearColorHex(0x333F47, 1);
// Create a light, set its position, and add it to the scene.
var light = new THREE.PointLight(0xffffff);
light.position.set(-100,200,100);
scene.add(light);
// More code goes here next...
}
Load Geometry
Our mesh has been exported from Blender using the three.js JSON exporter, so we need to use the JSONLoader
to get the geometry into the scene. A callback is used inside the loader to set the material on the mesh. In this case, we’re using a basic LambertMaterial
to set the mesh to Treehouse’s green color. For completeness, I should note here that the green you’re seeing in the final render isn’t quite the same as Treehouse’s logo green. That’s because the point light is skewing the brightness slightly, but we won’t worry about it for this demo.
Before leaving the callback function, we create a new mesh with our geometry and the material as parameters, then we add the mesh to the scene.
// Sets up the scene.
function init() {
// Code from previous steps goes here...
// Load in the mesh and add it to the scene.
var loader = new THREE.JSONLoader();
loader.load( "models/treehouse_logo.js", function(geometry){
var material = new THREE.MeshLambertMaterial({color: 0x55B663});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
// More code goes here next...
}
Add Controls
The last thing in our initialization function is the orbit controls we included earlier. These aren’t totally necessary, but they do allow us to drag the mouse across the mesh and orbit around it. It also allows the mousewheel to be used to zoom in and out of the mesh.
// Sets up the scene.
function init() {
// Code from previous steps goes here...
// Add OrbitControls so that we can pan around with the mouse.
controls = new THREE.OrbitControls(camera, renderer.domElement);
}
// More code goes here next...
Render the Scene
After our initialization function, we need to finish up with our animation function. It may not seem like anything is really “animated” here in the traditional sense, but we do need to redraw when the camera orbits around the mesh.
The requestAnimationFrame()
function uses a newer browser API that delegates redraws to the browser. This has some pretty cool benefits, but primarily it makes sure the browser isn’t drawing your animation unnecessarily if that tab isn’t currently selected. Paul Irish wrote an excellent blog post on requestAnimationFrame that explains this in more detail.
After that, we need to render our scene through the camera we added earlier and then update the orbit controls.
// Sets up the scene.
function init() {
// Code from previous steps goes here...
}
// Renders the scene and updates the render as needed.
function animate() {
// Read more about requestAnimationFrame at http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
requestAnimationFrame(animate);
// Render the scene.
renderer.render(scene, camera);
controls.update();
}
To see what the final result looks like, be sure to download the code. Try changing some of the parameters around and see what happens!
What’s Next?
I think three.js is an amazing project that unlocks the power of WebGL for people that aren’t 3D graphics gurus (like me). Browser support is still picking up, but I feel like some of the most practical applications of WebGL sit with product demos: Imagine exploring a new car in full 3D from the comfort of your browser.
However, such a low the barrier to entry makes it possible to create cool logos or make music videos without spending years writing renderers or other complex code. If you’re a little more dedicated, you could even make games and build worlds. It makes the browser a much more experiential place, which I think is pretty awesome. Who wants to make documents? I want to make places.
I really don’t understand the coordinate system of Three.js.
Hi there,
I followed the tutorial, and it worked great once I figured out how to convert the json exported from blender to js. I want to use this as a background in my website with text and a button overlaid. This may be really simple and I’m just missing something, but is it possible to do this? I was thinking of nesting the three.js scene in a div and styling that with css, but that didn’t work.
any guidance would be much appreciated,
Thanks!
How did you convert json to js. I’m breaking my head to do that. Help pls!
I just tried this in Codepen using a cdn version of threejs.min
https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js
All I get is a black screen until near the end when I insert this line
requestAnimationFrame(animate);
Then it turns white. Help – what am I doing wrong?
I also changed setClearColorHex to setClearColor.
Still seeing just a blank white screen
I can’t find the file three.min.js what to do please
Hi just wondering. About the rotation bit, seems you can spin it left and or right continually, but when you do it up or down, it rotates… but doesn’t keep rotating ? Any chance you can help with that ? Tried figuring it out… Cheers
When I do this it seems to work except I cannot see the model, I even downloaded the code and ran the index.html file but it still cannot read the treehouse_logo.js.
Inspector gives me these errors:
1) XMLHttpRequest cannot load file:///Users/anthonylowenfeld/Downloads/threejs_logo_example/models/treehouse_logo.js. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.
2) THREE.JSONLoader: [models/treehouse_logo.js] seems to be unreachable or file there is empty
Help please?
Hi Anthony! As this is an older post, I’d recommend posting your question in the Treehouse Community. Treehouse students and teachers are always around and happy to help!
Hi Anthony, I think you’ll have to enable CORS in your browser or copy the code to a web server like apache and launch it from there. I went with the second solution from a apache and it works fine for me.
Since its loading a external file. you sould run this file in a server! otherwise it wont work.
you can use a local server as well. I used XAMPP local server.
Thanks
After changing the the code from the example code setClearColorHex to setClearColor, still got error message for Cross Origin Request in Chrome. The Demo at the top of the page, and the CodePen (both on-line and running the downloaded zipped files on my server) worked correctly. I tried both NPM server and Python, with still had same issue with the example code.
Then I tried Firefox and it runs perfectly. Haven’t tried Safari yet. Any ideas on how to make this run in Chrome?
Note that the line in index.html that reads “renderer.setClearColorHex(0x333F47, 1);” should now be “renderer.setClearColor(0x333F47, 1);” for it to work (at least with the latest version of three.js that I’m using).
In Safari, my ‘Developer’ menu item does not have ‘Enable WebGL.’ I thought maybe this is active now in 2016 but I failed to load a site that worked in Chrome just fine.
Any idea on if and how I can enable WebGL on Safari v9.0.3?
I’ve tried and run this tutorial. but the model that i did din’t come up on the screen. only the background color showed up. Please help
Nice little tutorial, helped me get enough of a start in three.js to teach myself most of the rest of what I wanted to know.
Just a note though, this will not work with the latest version of three.js. It took a while but eventually I figured out the .setClearColorHex method is deprecated, just need to replace with .setClearColor. Hopefully this will keep others from getting frustrated at a black screen like me.
I couldnt help but reflect after trying this. And I’m sure it is stated over and over by javascript lovers but, …. What it achieves with two libraries and a small .htm file is staggering. I’ve come from C, Java and C++, and the’yre brilliant, but you cant do anything as brilliant as this in them, so simply.
Hi, I tried your sample project and it worked fine.
Now I need to know whether I can include this html file into one of my java projects. I am calling a html file in that project. So will this work if I replace that .html file with treehouse .html file ? I will include the supporting files also into the project.
Actually I tried with another three.js project where the 3d model is in .BDF format. Thus I replaced the html file. The file is calling correctly, but the 3d model is not showing over the screen. (only the comments written in the body part of the html file is showing).
Three.js is great but writing whole games and worlds in three.js can become tedious.
There are other webgl/javascript frameworks more suitable for this.
The one I find the most interesting and promising is http://www.cloudparty.com.
The amount of works these guys have done is amazing – mesh, material, lightning,sound, animation, inworld creation tools, networking, scripting – you name it they have it.
All in webgl and javascript.
I just finsihed writing this simple 3d pacman game there https://www.cloudparty.com/loc/2977392759/-41.4,1,11.1,-1.7 :))
this can do with a whole bunch of improvement, but its pretty good, gonna figure out if i can use this for my page.
see demo page error, chrome 29 linux mint 13 xfce, fix please:
TypeError: Cannot call method ‘getExtension’ of null [http://treehouse-code-samples.s3.amazonaws.com/threejs_logo_example/js/three.min.js:421]
This definetely must be a course in treehouse.
as designed the logo in 3d the file http://codepen.io/nickpettit/pen/nqyaK.js
Yep! I figured out on CodePen that you can store JSON data in another pen and then link it to an existing pen where you have your three.js scene loaded.
FYI, Udacity has a 3D graphics course that is ALL about three.js. Starts of trivially easy, gets pretty interesting, then WHAM I have been stuck on Lesson 5 (matrix math) for a while now.
Huh, sounds like Treehouse should probably make a better course. If only someone would tell them to get on it. 😉
I’ll have to work through this sometime; I’ve been meaning to check out three.js for awhile. I was actually tinkering around with Babylon.js a bit this weekend, which is another 3D framework for WebGL. Have you or anyone else worked with both? Three.js seems like the obvious leader in WebGL frameworks, but I’d love to hear what other people think about it vs. similar frameworks.
I haven’t tried Babylon.js, although it looks like there’s no documentation for it. Just examples. 😛 Am I missing something?
No, you’re definitely right, documentation is lacking for babylon atm. There are some entry level tutorials if you dig into the repo’s wiki, but it’s nowhere near as supported as three.js.
I was curious as to what people thought of other frameworks, but that’s the only one I’ve played with. I like playing with the less popular libraries, and three.js seems kinda mainstream :p