Building a Three.js Hero Section with Environment Maps, PMREMGenerator, and Post‑Processing Effects
This article walks through building a Three.js hero section by setting up a basic scene, loading a GLTF model, applying environment maps with PMREMGenerator, adding bloom post‑processing, and animating the envMap rotation to create a dynamic, visually striking 3D header.
The tutorial introduces a "Hero Section" as a prominent banner area in web design and explains its importance for user engagement. It then guides readers through creating a Three.js project that displays a 3D hero header.
0. Prerequisites – Readers should be familiar with Three.js but not with shaders. The article provides a live preview link.
1. Environment Setup – The basic Three.js elements (Scene, Camera, Renderer) are instantiated, and a responsive canvas is configured.
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const device = {
width: window.innerWidth,
height: window.innerHeight,
pixelRatio: window.devicePixelRatio
};
export default class Three {
constructor(canvas) {
this.canvas = canvas;
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(
75,
device.width / device.height,
0.1,
100
);
this.camera.position.set(0, 0, 2);
this.scene.add(this.camera);
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvas,
alpha: true,
antialias: true,
preserveDrawingBuffer: true
});
this.renderer.setSize(device.width, device.height);
this.renderer.setPixelRatio(Math.min(device.pixelRatio, 2));
this.controls = new OrbitControls(this.camera, this.canvas);
this.clock = new THREE.Clock();
this.setLights();
this.setGeometry();
this.render();
this.setResize();
}
setLights() {
this.ambientLight = new THREE.AmbientLight(new THREE.Color(1, 1, 1, 1));
this.scene.add(this.ambientLight);
}
setGeometry() {
this.planeGeometry = new THREE.PlaneGeometry(1, 1, 128, 128);
this.planeMaterial = new THREE.MeshBasicMaterial({
color: 0x00_ff_00
});
this.planeMesh = new THREE.Mesh(this.planeGeometry, this.planeMaterial);
this.scene.add(this.planeMesh);
}
render() {
const elapsedTime = this.clock.getElapsedTime();
this.planeMesh.rotation.x = 0.2 * elapsedTime;
this.planeMesh.rotation.y = 0.1 * elapsedTime;
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.render.bind(this));
}
setResize() {
window.addEventListener('resize', this.onResize.bind(this));
}
onResize() {
device.width = window.innerWidth;
device.height = window.innerHeight;
this.camera.aspect = device.width / device.height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(device.width, device.height);
this.renderer.setPixelRatio(Math.min(device.pixelRatio, 2));
}
}2. Loading a GLTF Model – A GLTFLoader is added, the plane geometry is replaced with a dog head model, and the model is added to the scene.
// In the constructor add a model loader
this.gltfLoader = new GLTFLoader();
setObject() {
this.gltfLoader.load('goutou.glb', (gltf) => {
this.object = gltf.scene;
this.scene.add(this.object);
});
}
// Call the method in the constructor
this.setLights();
this.setObject();
// this.setGeometry(); // removed
this.render();
this.setResize();3. Using PMREMGenerator for Environment Maps – The article explains how to create a PMREMGenerator, load an equirectangular texture, and apply the generated envMap to a PBR material.
const pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
const textureLoader = new THREE.TextureLoader();
textureLoader.load('path/to/envmap.jpg', (texture) => {
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
});
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
metalness: 1,
roughness: 0.2,
envMap: envMap
});
pmremGenerator.dispose();In the class, the envMap is loaded and assigned to the dog material:
this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
this.pmremGenerator.compileEquirectangularShader();
this.envMap = new THREE.TextureLoader().load('./img/white.png', (texture) => {
this.envMap = this.pmremGenerator.fromEquirectangular(texture).texture;
this.envMap.encoding = THREE.SRGBColorSpace;
this.dogMaterial = new THREE.MeshStandardMaterial({
color: '#a7c6b1',
metalness: 1,
roughness: 0.28
});
this.dogMaterial.envMap = this.envMap;
this.gltfLoader.load('./goutou.glb', (gltf) => {
const { scene } = gltf;
this.dog = scene.children[0];
this.dog.geometry.center();
this.dog.material = this.dogMaterial;
this.scene.add(this.dog);
});
this.pmremGenerator.dispose();
});4. Post‑Processing with UnrealBloomPass – The tutorial adds a bloom effect using EffectComposer, RenderPass, and UnrealBloomPass.
setPost() {
this.renderScene = new RenderPass(this.scene, this.camera);
this.bloomPass = new UnrealBloomPass(
new THREE.Vector2(device.width, device.height),
0.06,
0.4,
0.85
);
this.outputPass = new OutputPass();
this.composer = new EffectComposer(this.renderer);
this.composer.addPass(this.renderScene);
this.composer.addPass(this.bloomPass);
this.composer.addPass(this.outputPass);
}
render() {
const elapsedTime = this.clock.getElapsedTime();
this.composer.render(this.scene, this.camera);
requestAnimationFrame(this.render.bind(this));
}
onResize() {
// ... same as before ...
this.composer.setSize(device.width, device.height);
this.composer.setPixelRatio(Math.min(device.pixelRatio, 2));
}5. Rotating the Environment Map – By updating envMapRotation each frame, the lighting appears to move around the model.
render() {
const elapsedTime = this.clock.getElapsedTime();
if (this.dog) {
this.dog.material.envMapRotation = new THREE.Euler(
0 + elapsedTime * 3,
0 + elapsedTime * 2,
0
);
this.dog.material.needsUpdate = true;
}
this.renderer.render(this.scene, this.camera);
this.composer.render(this.scene, this.camera);
requestAnimationFrame(this.render.bind(this));
}6. Final Touches – Adding a starfield background and mouse‑move interaction for subtle parallax.
setBackground() {
const textureLoader = new THREE.TextureLoader();
const starSprite = textureLoader.load('./img/circle.png');
const stars = getStarfield({ numStars: 4500, sprite: starSprite });
this.scene.add(stars);
}
setMouseMoveEvent() {
document.addEventListener('mousemove', function (event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
});
}The article concludes with a link to the full source repository ( https://github.com/hexianWeb/hero-profile.git ) and encourages readers to follow the upcoming "Hero Section" series for more 3D front‑end examples.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.