WebGL gives JavaScript developers direct access to the GPU, enabling real-time 3D graphics in the browser without any plugins. Three.js wraps the raw WebGL API in a developer-friendly layer, so you can build complex 3D scenes with far less boilerplate.
1. Installation and Project Setup
npm install three
# Optional React bindings
npm install @react-three/fiber @react-three/drei2. Core Concepts: Scene, Camera, Renderer
import * as THREE from 'three';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0f);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1.5, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});3. Geometries, Materials, and Meshes
const geometry = new THREE.BoxGeometry(1, 1, 1, 4, 4, 4);
const material = new THREE.MeshStandardMaterial({
color: 0x6366f1,
metalness: 0.3,
roughness: 0.4,
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);4. Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(5, 10, 7.5);
dirLight.castShadow = true;
dirLight.shadow.mapSize.set(2048, 2048);
scene.add(dirLight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;5. The Animation Loop
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
cube.rotation.x += 0.5 * delta;
cube.rotation.y += 0.8 * delta;
renderer.render(scene, camera);
}
animate();6. OrbitControls — Interactive Camera
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;7. Loading 3D Models (GLTF + Draco)
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
gltfLoader.load('/models/robot.glb', (gltf) => {
scene.add(gltf.scene);
});8. Custom GLSL Shaders
const shaderMaterial = new THREE.ShaderMaterial({
uniforms: { uTime: { value: 0 }, uColor: { value: new THREE.Color(0x6366f1) } },
vertexShader: /* glsl */ `
uniform float uTime;
varying float vElevation;
void main() {
vec3 pos = position;
pos.z = sin(pos.x * 4.0 + uTime) * 0.2 + sin(pos.y * 3.0 + uTime * 1.3) * 0.15;
vElevation = pos.z;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: /* glsl */ `
uniform vec3 uColor;
varying float vElevation;
void main() {
vec3 col = mix(uColor, vec3(0.13, 0.83, 0.93), vElevation + 0.5);
gl_FragColor = vec4(col, 1.0);
}
`,
});9. Post-Processing Effects
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.2, 0.4, 0.85));
// Replace renderer.render() with composer.render() in the loop10. Performance Optimisation
- Dispose of geometries, materials, and textures you no longer use — geometry.dispose(), material.dispose(), texture.dispose() — to free GPU memory.
- Use InstancedMesh when rendering hundreds of identical objects instead of separate Mesh instances.
- Cap devicePixelRatio at 2 — renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)).
- Use renderer.info to inspect draw calls, triangles, and textures in real time during development.
- For mobile, reduce shadow map resolution and disable post-processing passes on low-end devices.
Pro tip: Use Spector.js (browser extension) or the Chrome GPU debugger to profile WebGL draw calls and identify bottlenecks before they ship to production.
Want to add immersive 3D visuals or WebGL-powered product configurators to your website? BitPixel Coders builds custom Three.js and WebGL experiences — reach out for a free consultation.
Get a Free Consultation