Welcome to the 3D world: My ThreeJs journey
The last time I tried to enter the 3d world was back when I was in college. I took classes about computer graphics, artificial intelligence, and artificial life. Back then we learned algorithms about how computers render a line, how the images were represented by pixels, different AI theories, and algorithms, and so on and so forth. However, every time we tried to create something that looked cool it was really difficult, we worked mostly in Java and C++ at the time. One day we discovered the processing library that allows us to create and animate objects in 2d and 3d creating Java Applets (When applets were a thing!!!). We used it for the class and it was fine, but the projects ended with the classes.
I thought I was never going to explore the 3d world like that again…
Today, as an engineering and web developer I was shocked when I discovered the Bruno Simon portfolio. The idea to have such an impressive 3D world on the web is something amazing. After exploring Bruno’s page and a little of healthy internet stalking I discovered that he created a course to teach ThreeJS the library he used in his portfolio. The course is titled ThreeJS Journey and it is a journey!!!. Bruno did a great job explaining from scratch how to create 3D scenes, different methods, and techniques used, physics, basic 3D modeling, shaders OMG SHADERS that is really something. I’m not going to spoil the course too much here. If you are interested in 3D on the web I definitely recommend you to take it.
So, too much talk, let’s bring the code…
I’ll share with you how easy is to create a 3D scene with a small example I created https://threejs-solar-system.netlify.app/
The first thing to do is create the scene, you will need a canvas element and ThreeJS. Then you add a renderer to render your scene and that’s it. You should have an empty scene in the browser.
import * as THREE from "three";// Canvas
const canvas = document.querySelector("canvas.webgl");// Scene
const scene = new THREE.Scene()/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
Now let’s add some objects for the basic shape of our solar system. I’ll add some cubes, one of the basic shapes to add in ThreeJS. I define 3 cubes, the sun, earth, and moon, and give each of them a color. As you can see the code is really simple.
// Distances
const distances = {
earth: 3,
moon: 1,
};
/**
* Objects
*/
const geometrySun = new THREE.BoxGeometry(1, 1, 1);
const materialSun = new THREE.MeshBasicMaterial({ color: "#FFC300" });
const cubeSun = new THREE.Mesh(geometrySun, materialSun);
scene.add(cubeSun);
const geometryEarth = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const materialEarth = new THREE.MeshBasicMaterial({ color: "#3399FF" });
const cubeEarth = new THREE.Mesh(geometryEarth, materialEarth);
const geometryMoon = new THREE.BoxGeometry(0.2, 0.2, 0.2);
const materialMoon = new THREE.MeshBasicMaterial({ color: "#C1C3C5" });
const cubeMoon = new THREE.Mesh(geometryMoon, materialMoon);
cubeMoon.position.y = distances.moon;
const earthGroup = new THREE.Group();
earthGroup.position.x = distances.earth;
earthGroup.add(cubeEarth);
earthGroup.add(cubeMoon);
scene.add(earthGroup);
Now let’s add some motion to the square planets. Basically this tick function is considered the animation loop, is an infinite loop that uses requestAnimationFrame to update the elements.
Inside I change the rotation of each cube and its position to emulate a primitive orbit.
/**
* Animate
*/
const clock = new THREE.Clock();
const tick = () => {
const elapsedTime = clock.getElapsedTime();
cubeSun.rotation.y = (elapsedTime * Math.PI) / 4;
cubeEarth.rotation.y = (elapsedTime * Math.PI) / 6;
cubeMoon.rotation.y = (elapsedTime * Math.PI) / 8;
earthGroup.position.z = Math.sin(elapsedTime) * distances.earth;
earthGroup.position.x = Math.cos(elapsedTime) * distances.earth;
cubeMoon.position.y = Math.sin(elapsedTime / 2) * distances.moon;
cubeMoon.position.x = Math.cos(elapsedTime / 2) * distances.moon;
// Update controls
controls.update();
// Render
renderer.render(scene, camera);
// Call tick again on the next frame
window.requestAnimationFrame(tick);
};
tick();
Well it is not bad, but moving cubes are not a solar system. After having this basic structure I start to work on improvements and details. I changed the cubes with spheres and added some textures to them.
const textureLoader = new THREE.TextureLoader();
const lavaColorTexture = textureLoader.load(
"/textures/lava/Lava_002_COLOR.png"
);
const lavaNormalTexture = textureLoader.load("/textures/lava/Lava_002_NRM.png");
const lavaHeightTexture = textureLoader.load(
"/textures/lava/Lava_002_DISP.png"
);
const lavaAOTexture = textureLoader.load("/textures/lava/Lava_002_OCC.png");// Other textures are loaded in the same way
We tried adding multiple textures to each planet to have a realistic effect. It took me a while to search for textures and find some cools ones
const geometrySun = new THREE.SphereGeometry(1, 32, 32);
const materialSun = new THREE.MeshStandardMaterial({ map: lavaColorTexture });
materialSun.normalMap = lavaNormalTexture;
materialSun.aoMap = lavaAOTexture;
materialSun.displacementMap = lavaHeightTexture;
materialSun.displacementScale = 0.1;
// ...
const geometryEarth = new THREE.SphereGeometry(0.5, 32, 32);
const materialEarth = new THREE.MeshStandardMaterial({
map: waterColorTexture,
});
materialEarth.normalMap = waterNormalTexture;
materialEarth.aoMap = waterAOTexture;
materialEarth.displacementMap = waterHeightTexture;
materialEarth.displacementScale = 0.05;
// ...
const geometryMoon = new THREE.SphereGeometry(0.2, 32, 32);
const materialMoon = new THREE.MeshStandardMaterial({ map: rockColorTexture });
materialMoon.normalMap = rockNormalTexture;
materialMoon.aoMap = rockAOTexture;
materialMoon.displacementMap = rockHeightTexture;
materialMoon.displacementScale = 0.05;
materialMoon.roughnessMap = rockRoughnessTexture;
To practice light and shadows I added a point light inside the sun sphere and allowed the objects to cast and receive shadows. Given that the light is inside the sun sphere when I tried to cast or receive shadow from it didn’t work, so I remove it. If you have other ideas for the light please let me know!
/**
* Lights
*/
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.x = 2;
pointLight.position.y = 3;
pointLight.position.z = 4;
scene.add(pointLight);
const sunLight = new THREE.PointLight(0xffffff, light.sun);
scene.add(sunLight);/**
* Shadows
*/
renderer.shadowMap.enabled = true;
sunLight.castShadow = true;
// sphereSun.castShadow = true;
// sphereSun.receiveShadow = true;
sphereEarth.castShadow = true;
sphereEarth.receiveShadow = true;
sphereMoon.castShadow = true;
sphereMoon.receiveShadow = true;
I added some tweaks using dat.gui to change the distances and the lights. And here is the result.
The scene was good but it was a little empty so I added some asteroids with random circular-ish shapes. And in that way, I create the cookie way! Yeah, they looked like cookies!
I was really happy with the result but decided to add a couple more details. Added an intro 3d text and some particles to fill the empty space. Visit https://threejs-solar-system.netlify.app/ to see the final result.
More examples
- https://threejs-solar-system.netlify.app/ (Solar system explained in this post)
- https://raging-sea.netlify.app/ (Expansion of the course’s raging sea lesson)
- https://threejs-cloud.netlify.app/ (Cloud using shaders)
- https://threejs-solar-system-shaders.netlify.app/ (Solar system using shaders)
Conclusion
The 3D world is no longer out of reach for the web. As developers/designers, we can create awesome scenes to bring live our sites. For me, ThreeJs is really an amazing library that facilitates all our work. I only scratch the surface of the library but I’m really excited about the possibilities that create for us. To create awesome 3D experiences I still have more to learn and will love to work with designers, animators, 3D modelers to do so.
The 3D world is looking awesome and promising. And I’m ready to explore it.