From our sponsor:
Create gorgeous model belongings with the assistance of our AI-driven Artistic Assistant. Get began as we speak.
On this tutorial, we’ll discover the way to create a Cyberpunk like Three.js scene, impressed by the background animation discovered on Pipe’s web site. We’ll information you thru the method of coding a dynamic scene utilizing Three.js, full with post-processing results and dynamic lighting, all with no need any shader experience.
I’ve used this scene to create the interactive background of Month-to-month Discuss.
Take a look on the supply code. Try the video of what we can be making as we speak:
Desk of Contents
- The 3D Mannequin
- Creating the Three.js scene
- Let’s change the mannequin
- Including Publish-processing
- Fixing the Alias
- Including the Chromatic Aberration
- Coloration calibration with Hue and Saturation
- Including digicam Parallax
- Implementing mild Parallax motion
- Closing ideas
Let’s begin with taking a look at how the mannequin is created in Blender!
The 3D Mannequin
The mannequin was created utilizing Blender3D and consists of primary rectangular shapes. These shapes have been first solidified and subsequently duplicated quite a few instances, with changes made to their rotation and place with a purpose to obtain a visually interesting round sample. It’s essential to make sure that the middle of mass for all objects aligns with the scene’s middle since they are going to later be animated inside Three.js.
First, let’s add a round form to our scene. This would be the place to begin for our 3D mannequin.

Subsequent, you’ll need to change the parameters for the circle creation, which will be discovered within the bottom-left nook of the display.

Now, it’s time to enter Edit Mode (press TAB), choose some vertices, and delete them utilizing the X key in your keyboard.

With the remaining vertices, go forward and extrude them alongside the Z axis to offer the form some depth.

When you’ve completed that, exit Edit Mode and add a Solidify Modifier to offer the form some thickness.

Now, press SHIFT+D to duplicate the mesh, and press R then Z to lock the rotation on the Z axis and rotate the copy.

Preserve repeating the method till you’ve gotten a bunch of meshes. Don’t neglect to use supplies with totally different colours to make it extra fascinating! You’ll be able to at all times change a few of these settings later inside Three.js, however it’s a good suggestion to set some parameters inside Blender. Additionally, I’ve added a bevel modifier on every mesh to offer them a pleasant, smooth edge.

Now it’s time to export your creation! Choose every little thing by urgent A in your keyboard, then export your mannequin as a GLB / GLTF file.

Lastly, it’s vital to test a few issues earlier than you finalize the export. Be certain to incorporate “Chosen Objects” solely and apply modifiers below Mesh Settings. You can too use compression to cut back the file dimension.

Creating the Three.js scene
With the mannequin prepared, it’s time to create a Three.js scene to load it. Begin through the use of a Three.js Boilerplate. On this case, I’ll be using my very own boilerplate, which you’re welcome to make use of as nicely.

To save lots of time and keep away from constructing a scene from scratch, we’ll benefit from my boilerplate. It’s fairly easy and comes with line-by-line documentation that can assist you perceive the aim of every line.
It’s a standard false impression that the one solution to make the most of Three.js is by way of a package deal supervisor. Nevertheless, that’s not the case. By leveraging ES6 Modules, you need to use Three.js with out the necessity for a builder. My boilerplate is already set as much as operate on this method.
As talked about on the Three.js set up web page, all it’s essential do is instantiate it accurately. For the reason that library depends on ES modules, any script referencing it should use kind=”module”. Moreover, it’s essential outline an import map that resolves to the easy module specifier ‘three’.
As import maps usually are not but universally supported by browsers, it’s essential to incorporate the polyfill es-module-shims.js as nicely.
The core of Three.js focuses on the important elements of a 3D engine. Many different precious elements—similar to controls, loaders, and post-processing results—are a part of the examples/jsm listing. These are known as “addons” as a result of they can be utilized as-is or custom-made to fit your wants.
Whereas addons don’t must be put in individually, they do must be imported individually. When utilizing the CDN model, we will add the mandatory objects to the “import map.” It’s essential to make sure all information use the identical model; keep away from importing totally different addons from varied variations or utilizing addons from a distinct model than the Three.js library itself.
On this occasion, we’re importing some further addons and extra libraries that we’ll want for our venture.
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
<script kind="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.145.0/construct/three.module.js",
"three/addons/": "https://unpkg.com/three@0.145.0/examples/jsm/",
}
}
</script>
Subsequent, be sure to embody the scripts file with the “module” kind in your HTML file.
<physique>
<script kind="module" src="./src/index.js"></script>
</physique>
That’s it! As you possibly can see, we now have a completely functioning Three.js venture. You’ll be able to check out the index.js file to know what every line does. As beforehand talked about, I gained’t delve into the main points right here, as there are many different tutorials accessible on the way to create a scene like this one.
Let’s change the mannequin
With our scene arrange, it’s time to load the mannequin we exported from Blender. All we have to do is add the exported “.glb” mannequin to the code sandbox and modify a single line of code to make use of the brand new mannequin.

///// LOADING GLB/GLTF MODEL FROM BLENDER /////
loader.load('https://03fltx.csb.app/belongings/mannequin/cyberpunk_model.glb', operate (gltf) {
scene.add(gltf.scene);
});
Since we’re utilizing a static web page with none bundler, it’s essential to make use of the complete path for the mannequin. And identical to that, our mannequin is now a part of the scene.
Including Publish-processing
Publish-processing is perhaps a brand new idea to some, however it’s what makes Three.js really exceptional. It permits you to create an additional layer of results within the Three.js rendering system. Consider it like making use of an Instagram filter to your pictures. There’s a variety of results to select from, and the proper mixture is what units Three.js scenes aside.
To start, let’s set up the required library to allow post-processing results in our venture. Head over to the index and add the library to the import parameters. Be certain to put it under the “Addons” line, as proven right here:
<script kind="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.145.0/construct/three.module.js",
"three/addons/": "https://unpkg.com/three@0.145.0/examples/jsm/",
"postprocessing": "https://cdn.jsdelivr.internet/npm/postprocessing@6.30.0/construct/postprocessing.esm.js"
}
}
</script>
Now, return to your index.js and add the import script.
import * as THREE from 'three';
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
import {TWEEN} from 'three/addons/libs/tween.module.min.js';
import {DRACOLoader} from 'three/addons/loaders/DRACOLoader.js';
import {RGBELoader} from 'three/addons/loaders/RGBELoader.js';
import {EffectComposer, RenderPass, EffectPass, BloomEffect } from 'postprocessing';
We’re importing 4 issues from this new library:
- EffectComposer – This part is answerable for creating a brand new renderer for our scene. It combines all the consequences we add right into a single “picture” that can be rendered, bypassing the default Three.js rendering system.
- RenderPass – This part defines which scene can be rendered. It primarily represents the unique results of the default Three.js render earlier than any results are utilized.
- EffectPass – This part defines an impact cross that we need to add. Consider it as one of many Instagram filters utilized to the rendered scene.
- BloomEffect – It is a kind of impact that simulates the “glow” impact on luminous objects within the scene.
Now that we’ve imported all the mandatory elements for the consequences, it’s time to create cases of them in our venture. Head over to the index.js file and add the post-processing there.
//// POST PROCESSING ////
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, digicam));
composer.addPass(new EffectPass(digicam, new BloomEffect(
{
depth: 3.2,
mipmapBlur: true
}
)));
What is going on right here
- We create a brand new occasion of EffectComposer and specify the unique scene (renderer on this case).
- We add a brand new RenderPass in order that the unique scene serves as the muse for making use of results.
- We add a brand new EffectPass to use the specified filter. We embody some extra parameters, such because the depth of the impact, and activate mipMapBlur, a smoothing method utilized to textures in pc graphics. MipMapBlur makes use of totally different ranges of texture decision (mipmaps) to generate a extra environment friendly blurred picture, which may help scale back aliasing in textures at varied distances and viewing angles.
We’re virtually there. We now have every little thing wanted to render the scene with post-processing. The ultimate step is to truly render the scene. Remark out or take away the previous renderer, “renderer.render(scene, digicam)”, and exchange it with “composer.render()” within the render loop, as proven under:
//// RENDER LOOP FUNCTION ////
operate rendeLoop() {
controls.replace();
composer.render() //render the scene with Publish Processing Results
//renderer.render(scene, digicam); //render the scene with out the composer
requestAnimationFrame(rendeLoop);
}
Voilà! We now have a post-processed scene. As you possibly can see, the reflections of the lights create gorgeous lighting results after they work together with the glass elements of the mannequin.
We will take the impact even additional. Let’s add extra layers of glow to the post-processing system for an enhanced Bloom impact! All we have to do is create new results by declaring some variables after which including the consequences sequentially in EffectPass, much like how we might stack a number of layers of a photograph filter in a picture enhancing program.
//// POST PROCESSING ////
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, digicam));
const bloom = new BloomEffect({depth: 1.9, mipmapBlur: true, luminanceThreshold: 0.1, radius: 1.1});
const bloom2 = new BloomEffect({depth: 3.2, mipmapBlur: true, luminanceThreshold: 0.1, radius: 0.5});
const bloom3 = new BloomEffect({depth: 1.2, mipmapBlur: true, luminanceThreshold: 0.1, radius: 0.5});
composer.addPass(new EffectPass(digicam, bloom, bloom2, bloom3));
//// RENDER LOOP FUNCTION ////
operate rendeLoop() {
// ...
Carried out. The result’s significantly better now, isn’t it?
Fixing the alias
One of many unintended effects of utilizing post-processing is that the anti-aliasing of the expertise vanishes. The best way the post-processing pipeline works creates a brand new render occasion with out the alias the scene initially had. This leads to the jagged edges on the objects we’ve noticed to date.
Thankfully, we will repair this by including a brand new EffectPass devoted to calculating the alias.
import {BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAPreset, SMAAEffect} from 'postprocessing';
We’ve added two new part imports right here.
One is the SMAAPreset, which comprises a set of settings associated to the standard of the SMAA impact we’re going to make use of. The opposite is the SMAA impact itself. SMAA (Subpixel Morphological Antialiasing) is an antialiasing method utilized in pc graphics to clean jagged and pixelated edges (aliasing) in real-time photos similar to video games.
SMAA works by analyzing the picture and making use of an edge detection filter to find out which edge pixels must be smoothed. It then makes use of a mathematical morphology algorithm to use delicate corrections to these edges, smoothing them with out compromising the sharpness and readability of the picture.
Fortunately, utilizing that is a lot less complicated than attempting to know the way it works 🙂
//// POST PROCESSING ////
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, digicam));
const bloom = new BloomEffect({depth: 1.9, mipmapBlur: true, luminanceThreshold: 0.1, radius: 1.1});
const bloom2 = new BloomEffect({depth: 3.2, mipmapBlur: true, luminanceThreshold: 0.1, radius: 0.5});
const bloom3 = new BloomEffect({depth: 1.2, mipmapBlur: true, luminanceThreshold: 0.1, radius: 0.5});
const smaaAliasEffect = new SMAAEffect({preset: SMAAPreset.ULTRA});
composer.addPass(new EffectPass(digicam, bloom, bloom2, bloom3, smaaAliasEffect));
As you possibly can see, we’ve merely added another impact layer for SMAA and utilized the SMAAPreset.Extremely possibility to realize the very best decision for the sting smoothing impact.
Including the Chromatic Aberration
Chromatic aberration is an optical phenomenon that happens when totally different colours of sunshine have various wavelengths, inflicting them to deviate to totally different levels as they cross by a lens. This may end up in photos with coloured borders round objects, significantly high-contrast borders. We’ll use this impact to distort the sides of our picture with another post-processing impact accessible within the library, which could be very easy to make use of. Let’s begin by importing this new impact from the library:
import {
BloomEffect,
EffectComposer,
EffectPass,
RenderPass,
SMAAPreset,
SMAAEffect,
ChromaticAberrationEffect, //We added this
} from 'postprocessing';
// The formatting modified to a number of strains as a result of we're doing lots of imports. However we solely add right here the brand new impact that we need to use.
Now let’s add another variable that can retailer the chromatic aberration. In it, we’ll create a brand new impact and cross some Offset, RadialModulation, and ModulationOffset parameters.
“offset”: is a two-dimensional vector that defines the displacement (offset) of the inexperienced and pink shade channels of the picture. The upper the worth, the extra the offset, and subsequently the colour distortion will increase.
“radialModulation”: signifies whether or not the chromatic aberration impact ought to have radial modulation or not. If set to “true”, the impact can be utilized within the type of concentric circles across the middle of the picture.
“modulationOffset”: is a numerical worth that defines the scale of the radial modulation of the chromatic aberration impact. The bigger the worth, the bigger the radius of the concentric circles.
Lastly, we create a brand new cross within the results pipeline, in order that ChromaticAberration impacts all earlier “passes”.
//// POST PROCESSING ////
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, digicam));
const bloom = new BloomEffect({depth: 1.9, mipmapBlur: true, luminanceThreshold: 0.1, radius: 1.1});
const bloom2 = new BloomEffect({depth: 3.2, mipmapBlur: true, luminanceThreshold: 0.1, radius: 0.5});
const bloom3 = new BloomEffect({depth: 1.2, mipmapBlur: true, luminanceThreshold: 0.1, radius: 0.5});
const smaaAliasEffect = new SMAAEffect({preset: SMAAPreset.ULTRA});
const chromaticAberration = new ChromaticAberrationEffect({
offset: new THREE.Vector2(0.002, 0.02),
radialModulation: true,
modulationOffset: 0.7,
});
composer.addPass(new EffectPass(digicam, bloom, bloom2, bloom3, smaaAliasEffect));
composer.addPass(new EffectPass(digicam, chromaticAberration));
As you possibly can see, the chromatic aberration impact distorts the sides of the picture, as if an actual digicam have been filming the scene.
Coloration Calibration with Hue and Saturation
Up to now, we’ve used the default Three.js colours. There are a number of methods to regulate colours in Three.js, and modifying the colour system within the renderer with ToneMapping is one methodology.
One other methodology is to make use of another post-processing cross to regulate the hue and saturation of the scene. This manner, we will work with extra acquainted controls that we’re used to utilizing in different picture enhancing software program.
Let’s import another part from the library:
import {
BloomEffect,
EffectComposer,
EffectPass,
RenderPass,
SMAAPreset,
SMAAEffect,
ChromaticAberrationEffect,
HueSaturationEffect,
} from 'postprocessing';
Let’s create a brand new variable to retailer the saturation and hue impact with the specified parameters. On this case, we’ll modify the hue by a sure variety of levels to create a barely bluer shade tone. Moreover, we’ll enhance the saturation to realize a extra “CyberPunk” look, which is the impact we’re aiming for.
//// POST PROCESSING ////
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, digicam));
const bloom = new BloomEffect({depth: 1.9, mipmapBlur: true, luminanceThreshold: 0.1, radius: 1.1});
const bloom2 = new BloomEffect({depth: 3.2, mipmapBlur: true, luminanceThreshold: 0.1, radius: 0.5});
const bloom3 = new BloomEffect({depth: 1.2, mipmapBlur: true, luminanceThreshold: 0.1, radius: 0.5});
const smaaAliasEffect = new SMAAEffect({preset: SMAAPreset.ULTRA});
const chromaticAberration = new ChromaticAberrationEffect({
offset: new THREE.Vector2(0.002, 0.02),
radialModulation: true,
modulationOffset: 0.7,
});
const hueSaturationEffect = new HueSaturationEffect({
hue: -0.1,
saturation: 0.25,
});
composer.addPass(new EffectPass(digicam, bloom, bloom2, bloom3, smaaAliasEffect));
composer.addPass(new EffectPass(digicam, chromaticAberration, hueSaturationEffect));
Because of this, we now have a extra saturated scene with colours which are extra intense and nearer to our desired end result.
Including Digicam Parallax
Including motion to the digicam primarily based on mouse motion with inertia can add extra dynamism to a Three.js scene. This creates a way of interactivity and makes the scene really feel extra alive. Thankfully, implementing this impact isn’t tough.
composer.addPass(new EffectPass(digicam, chromaticAberration, hueSaturationEffect));
//// ON MOUSE MOVE TO GET MOUSE POSITION ////
const cursor = new THREE.Vector3(); // creates a brand new vector to retailer the mouse place
doc.addEventListener(
'mousemove',
(occasion) => {
occasion.preventDefault();
cursor.x = occasion.clientX / window.innerWidth - 0.5;
cursor.y = occasion.clientY / window.innerHeight - 0.5;
},
false,
);
//// RENDER LOOP FUNCTION ////
// ...
I like to recommend putting this operate earlier than the render loop, instantly after the post-processing results. Firstly, we declare a variable named “cursor” to retailer a two-dimensional vector, which is able to maintain the X and Y coordinates of the mouse.
Subsequent, we connect an occasion listener to the doc to trace mouse motion. When this occasion is triggered, we create a operate that captures the mouse positions and shops them in our “cursor” variable. To stop the default conduct, we use the occasion.preventDefault methodology.
As for the calculation, it’s designed to shift the mouse place from the nook to the middle of the display, which isn’t the default. To attain this, we divide the mouse place within the doc by the display dimension after which subtract half of that (-0.5). This provides us the mouse place in the midst of the display. If you wish to see the calculated end result, you possibly can create a console.log(cursor) inside this operate.
Nevertheless, this operate alone doesn’t have an effect on something in our scene. To attain that, let’s proceed by shifting the digicam. To do that, we first create a bunch and place the digicam inside it.
///// CAMERAS CONFIG /////
const digicam = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100);
scene.add(digicam);
digicam.place.set(0, 3, 9);
///// ADD A GROUP AND PUT THE CAMERA INSIDE OF IT /////
const cameraGroup = new THREE.Group();
cameraGroup.add(digicam);
...
For the reason that digicam is already being noticed by OrbitControls, we can’t straight modify its place. Nevertheless, we will create a bunch and place the digicam inside it. This manner, we will modify the place of the group, which is able to end result within the digicam’s motion.
To attain this impact, we will merely modify the place of the digicam group primarily based on the worth of the cursor variable, which is monitoring the mouse place. On this case, we’ll use the Y coordinate of the cursor variable.
//// RENDER LOOP FUNCTION ////
operate rendeLoop() {
controls.replace();
composer.render();
cameraGroup.place.y = cursor.y * 8; //transfer the cameraGroup utilizing the cursor place
requestAnimationFrame(rendeLoop);
}
Nice! We have now efficiently applied the digicam motion primarily based on the mouse place. Nevertheless, the motion lacks inertia and feels inflexible. Let’s enhance it.
//// RENDER LOOP FUNCTION ////
const cursor = new THREE.Vector3();
const lerpedPosition = new THREE.Vector3(); //creates one other vector to retailer the inertia mouse place
operate rendeLoop() {
lerpedPosition.lerp(cursor, 0.01); //makes use of lerp operate to create inertia motion
TWEEN.replace();
controls.replace();
composer.render();
cameraGroup.place.y = lerpedPosition.y * 8; // strikes the digicam group utilizing the lerped place
requestAnimationFrame(rendeLoop);
}
rendeLoop();
//...
We will use a built-in threejs operate known as “lerp” so as to add inertia to the digicam motion. Firstly, we declare a brand new Vector3 object named “lerpedPosition”. This object can be used to retailer the mouse place with an inertial impact.
Then, contained in the renderLoop operate, we calculate the intermediate place of the “cursor” Vector3 object in relation to the “lerpedPosition” Vector3 object. We accomplish this calculation by using the lerp() operate from the Three.js library, which linearly interpolates between two values. The second parameter (0.01) specifies the interpolation charge. On this case, the interpolation is about to happen at a charge of 1% (0.01) per body.
Lastly, we modify the place of the cameraGroup utilizing the lerpedPosition, which easily updates the y place primarily based on the present y place of the mouse interpolated with the lerp operate. This creates a parallax movement impact that’s relative to the mouse place within the scene.
Wonderful! We have now efficiently applied a lovely parallax motion with inertia.
Implementing Mild Parallax Motion
Now that we’ve efficiently applied the parallax motion with the mouse, we will additionally make the sunshine current within the scene reply to mouse motion.
To attain this impact, we merely want so as to add the place of the sunshine contained in the render loop and lock the horizontal place to the x-axis of the smoothed mouse place and the vertical place to the y-axis of the smoothed mouse place.
//// RENDER LOOP FUNCTION ////
const cursor = new THREE.Vector3();
const lerpedPosition = new THREE.Vector3();
operate rendeLoop() {
lerpedPosition.lerp(cursor, 0.01);
controls.replace();
composer.render();
cameraGroup.place.y = lerpedPosition.y * 8;
mild.place.x = -lerpedPosition.x * 50; //strikes the horizontal mild place utilizing the lerped place
mild.place.y = lerpedPosition.y * 60; //strikes the vertical mild place utilizing the lerped place
requestAnimationFrame(rendeLoop);
}
rendeLoop(); //begin rendering
//...
Because of this, you possibly can see that the sunshine additionally follows the mouse with inertia, creating a lovely and dynamic impact within the scene’s lighting.
Closing Ideas
As you possibly can see, creating stunning and dynamic results utilizing Three.js is usually a enjoyable journey. Though the method could really feel prolonged as a result of want to elucidate every step, it’s spectacular you can create one thing this stunning with simply 100 strains of code.
When you have React data, you would possibly discover my just lately launched course on Udemy of explicit curiosity. The course concentrates on utilizing React Three Fiber to create configurators using Three.js. The course hyperlink is offered right here: Lovely React Three.js Fiber Configurator – Design & Code
I hope you loved the tutorial and realized one thing new. In that case, you should definitely observe me on Twitter or YouTube. I repeatedly put up new content material and tutorials about Three.js, creativity, and expertise.