WebGL Tutorial: Building Interactive 3D Websites
WebGL is transforming the web. From our BMW M4 3D model viewer to complex e-commerce product configurators, WebGL enables truly interactive 3D experiences in the browser. This tutorial covers everything you need to build your own.
What is WebGL?
WebGL (Web Graphics Library) is a JavaScript API that renders 2D and 3D graphics in the browser using your GPU. It's based on OpenGL ES and works in all modern browsers without plugins.
Key advantages of WebGL:
- No plugins required - runs natively in browsers
- GPU-accelerated - smooth 60 FPS performance
- Cross-platform - works on desktop, mobile, tablets
- Free and open - no licensing costs
- Widespread support - Chrome, Firefox, Safari, Edge all support it
Real-World WebGL Applications
WebGL isn't just for demos. It powers real business applications across industries:
1. E-Commerce Product Configurators
Companies like Nike, Tesla, and BMW use WebGL to let customers customize products in 3D. Users can change colors, materials, and options while seeing real-time updates. This increases engagement and reduces returns.
Example: Car Configurator
Just like our BMW M4 3D model, car manufacturers let buyers customize their vehicle online - choosing paint colors, wheels, interior options - all rendered in real-time 3D.
2. Real Estate Virtual Tours
Real estate companies use WebGL for virtual property tours. Buyers can walk through homes, look around rooms, and get a sense of space without visiting in person. This saves time and reaches remote buyers.
3. Data Visualization
Complex data becomes understandable in 3D. Financial dashboards, scientific visualizations, and geographic mapping all benefit from WebGL's ability to render millions of data points smoothly.
4. Gaming and Entertainment
Browser games have evolved far beyond Flash. WebGL powers sophisticated 3D games that rival native applications, all running in a browser tab.
5. Education and Training
Medical schools use 3D anatomy models. Engineering programs use interactive simulations. Museums offer virtual exhibits. WebGL makes learning interactive and engaging.
Getting Started with Three.js
Raw WebGL is complex. Three.js is a JavaScript library that makes WebGL development accessible. It's what we used to build the BMW M4 3D model viewer.
Basic Three.js Setup
// Import Three.js
import * as THREE from 'three';
// Create scene - the container for everything
const scene = new THREE.Scene();
// Create camera - your viewpoint into the scene
const camera = new THREE.PerspectiveCamera(
75, // Field of view
window.innerWidth / window.innerHeight, // Aspect ratio
0.1, // Near clipping plane
1000 // Far clipping plane
);
// Create renderer - draws everything to the screen
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Position camera
camera.position.z = 5;
// Animation loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
This creates an empty 3D scene. Now let's add something to see:
Adding 3D Objects
// Create a cube
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// Rotate it in the animation loop
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
Adding Lighting
For realistic materials, you need lights:
// Ambient light - soft overall illumination
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// Directional light - like the sun
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
// Now use a material that responds to light
const material = new THREE.MeshStandardMaterial({
color: 0x0077ff,
metalness: 0.5,
roughness: 0.5
});
Complete Guide to Three.js Lighting
Lighting is what makes 3D scenes come alive. Poor lighting = flat, boring visuals. Great lighting = dramatic, realistic renders. Here's everything you need to know:
1. AmbientLight - Global Fill
AmbientLight illuminates all objects equally from all directions. It has no position or direction - it's everywhere.
const ambientLight = new THREE.AmbientLight(color, intensity);
// Parameters:
// color: Hex color (0xffffff = white)
// intensity: Brightness (0.0 to 1.0+ typical)
When to Use Ambient Light
- Use it: To fill in dark shadows and simulate indirect light bouncing
- Don't overuse: Too much ambient light makes scenes look flat and washed out
- Typical values: 0.1 to 0.5 intensity
| Intensity | Effect | Use Case |
|---|---|---|
| 0.0 - 0.2 | Very dark, dramatic | Horror, night scenes |
| 0.3 - 0.5 | Balanced, natural | Product viewers, games |
| 0.6 - 1.0 | Bright, soft shadows | Bright outdoor, cartoon |
2. SpotLight - Focused Beam
SpotLight emits light in a cone shape from a single point. Perfect for dramatic effects, car showrooms, and stage lighting.
const spotLight = new THREE.SpotLight(color, intensity, distance, angle, penumbra, decay);
// Parameters:
// color: Light color (0xffffff = white)
// intensity: Brightness (0 to 10000+ for physically correct)
// distance: Max range (0 = infinite)
// angle: Cone angle in radians (0 to Math.PI/2)
// penumbra: Edge softness (0 = hard, 1 = soft)
// decay: How fast light dims (2 = realistic)
spotLight.position.set(x, y, z); // Where the light is
spotLight.target.position.set(x, y, z); // Where it points
Our BMW M4 3D Model SpotLight Settings
Here are the exact parameters we use for the car showcase:
const spotLight = new THREE.SpotLight(
0xffffff, // White light
9000, // High intensity for showroom effect
100, // 100 unit range
0.22, // ~12.6° cone angle
1 // Soft edges (full penumbra)
);
spotLight.position.set(0, 25, 0); // 25 units above center
spotLight.castShadow = true;
SpotLight Angle Explained
The angle parameter controls how wide the light cone spreads:
| Angle (radians) | Degrees | Effect |
|---|---|---|
| 0.05 | ~3° | Very tight laser beam |
| 0.15 | ~8.5° | Focused spotlight |
| 0.22 | ~12.6° | Our M4 setting - showroom feel |
| 0.5 | ~28.6° | Wide flood light |
| 1.0 | ~57° | Very wide coverage |
SpotLight Intensity for Physically Correct Rendering
Three.js uses physically correct lighting by default. Intensity is measured in candela (cd), so values are much higher than old-style lighting:
| Intensity | Real-World Equivalent |
|---|---|
| 100 - 500 | Candle, dim lamp |
| 1000 - 3000 | Room light, desk lamp |
| 5000 - 10000 | Stage light, showroom |
| 9000 | Our M4 setting |
| 50000+ | Bright sun, stadium |
3. DirectionalLight - Sun/Moon
DirectionalLight simulates distant light sources like the sun. All rays are parallel - position only affects shadow direction, not intensity.
const directionalLight = new THREE.DirectionalLight(color, intensity);
directionalLight.position.set(5, 10, 5); // Direction it comes FROM
directionalLight.castShadow = true;
// Shadow camera setup for directional light
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
4. PointLight - Lightbulb
PointLight emits light in all directions from a single point, like a bare lightbulb.
const pointLight = new THREE.PointLight(color, intensity, distance, decay);
pointLight.position.set(0, 5, 0);
5. HemisphereLight - Sky + Ground
HemisphereLight simulates outdoor lighting with different colors from sky (above) and ground (below).
const hemiLight = new THREE.HemisphereLight(
0x87ceeb, // Sky color (light blue)
0x3d2817, // Ground color (brown)
0.6 // Intensity
);
Recommended Lighting Setups
🚗 Car Showroom (What We Use)
// Single dramatic spotlight from above
const spotLight = new THREE.SpotLight(0xffffff, 9000, 100, 0.22, 1);
spotLight.position.set(0, 25, 0);
spotLight.castShadow = true;
scene.add(spotLight);
// NO ambient light = dramatic shadows
// The spotlight creates the classic "car reveal" look
🏠 Product Viewer (Softer)
// Ambient for base illumination
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);
// Key light
const keyLight = new THREE.DirectionalLight(0xffffff, 1);
keyLight.position.set(5, 5, 5);
scene.add(keyLight);
// Fill light (dimmer, opposite side)
const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);
fillLight.position.set(-5, 3, -5);
scene.add(fillLight);
🌳 Outdoor Scene
// Hemisphere for natural sky/ground bounce
const hemi = new THREE.HemisphereLight(0x87ceeb, 0x3d2817, 0.6);
scene.add(hemi);
// Sun
const sun = new THREE.DirectionalLight(0xfff5e6, 1.5);
sun.position.set(50, 100, 50);
sun.castShadow = true;
scene.add(sun);
Shadow Settings
Shadows add realism but cost performance. Here's how to configure them:
// Enable shadows on renderer
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadows
// Shadow map types (performance vs quality):
// THREE.BasicShadowMap - Fast, hard edges
// THREE.PCFShadowMap - Softer, medium cost
// THREE.PCFSoftShadowMap - Smoothest, higher cost
// THREE.VSMShadowMap - Best quality, highest cost
// Light shadow settings
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 1024; // Higher = sharper (512, 1024, 2048)
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.bias = -0.0001; // Prevents shadow acne
// Objects must opt-in to shadows
mesh.castShadow = true; // Object creates shadows
mesh.receiveShadow = true; // Object shows shadows on it
Loading 3D Models
Real applications use detailed 3D models, not basic shapes. Here's how to load a GLTF model (the format we use for our BMW M4 3D model):
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load(
'model.gltf', // Path to model
function (gltf) { // Success callback
scene.add(gltf.scene);
console.log('Model loaded!');
},
function (xhr) { // Progress callback
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
function (error) { // Error callback
console.error('Error loading model:', error);
}
);
Where to Get 3D Models
- Sketchfab - Large marketplace of free and paid models
- TurboSquid - Professional quality models
- Free3D - Free models for personal/commercial use
- Blender - Create your own (free software)
- Clara.io - Online 3D modeling tool
Making It Interactive
Static 3D isn't enough. Users expect to interact with 3D content. Three.js provides OrbitControls for camera interaction:
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Create controls
const controls = new OrbitControls(camera, renderer.domElement);
// Configure controls
controls.enableDamping = true; // Smooth movement
controls.dampingFactor = 0.05;
controls.enableZoom = true; // Allow zoom
controls.enablePan = false; // Disable panning
controls.minDistance = 2; // Minimum zoom
controls.maxDistance = 10; // Maximum zoom
controls.autoRotate = true; // Auto-rotate when idle
controls.autoRotateSpeed = 1.0;
// Update in animation loop
function animate() {
requestAnimationFrame(animate);
controls.update(); // Required for damping
renderer.render(scene, camera);
}
animate();
This is exactly how our BMW M4 3D model viewer works - you can rotate, zoom, and explore the car from any angle.
Click/Touch Interaction with Raycasting
Want users to click on 3D objects? Use raycasting:
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
function onMouseClick(event) {
// Convert mouse position to normalized coordinates
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// Cast ray from camera through mouse position
raycaster.setFromCamera(mouse, camera);
// Check for intersections
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const clickedObject = intersects[0].object;
console.log('Clicked:', clickedObject.name);
// Do something with the clicked object
clickedObject.material.color.set(0xff0000);
}
}
window.addEventListener('click', onMouseClick);
Performance Optimization
3D is resource-intensive. Here's how to keep it smooth:
1. Limit Pixel Ratio
// Cap at 2x for performance on high-DPI displays
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
2. Use Simpler Shadows
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.BasicShadowMap; // Faster than PCFSoftShadowMap
3. Reduce Geometry Complexity
// Use fewer segments for simple shapes
const sphere = new THREE.SphereGeometry(1, 16, 16); // Not 64, 64
4. Dispose Unused Resources
// When removing objects, clean up memory
geometry.dispose();
material.dispose();
texture.dispose();
5. Use Level of Detail (LOD)
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // Close up
lod.addLevel(mediumDetailMesh, 50); // Medium distance
lod.addLevel(lowDetailMesh, 100); // Far away
scene.add(lod);
Building a Complete 3D Website
Here's the architecture for a full 3D website like our BMW M4 3D model viewer:
File Structure
project/
├── index.html # Main page
├── style.css # Styles
├── main.js # Three.js code
├── models/
│ └── car.gltf # 3D model
├── textures/
│ └── metal.jpg # Textures
└── lib/
└── three.module.js # Three.js library
HTML Structure
<!DOCTYPE html>
<html>
<head>
<title>3D Product Viewer</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
#loading { position: fixed; /* loading overlay */ }
#ui { position: fixed; /* UI controls */ }
</style>
</head>
<body>
<div id="loading">Loading...</div>
<div id="ui">
<button onclick="changeColor('red')">Red</button>
<button onclick="changeColor('blue')">Blue</button>
</div>
<script type="module" src="main.js"></script>
</body>
</html>
Advanced Features
Environment Maps (Reflections)
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
new RGBELoader().load('environment.hdr', function(texture) {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
scene.background = texture;
});
Post-Processing Effects
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/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),
0.5, // Strength
0.4, // Radius
0.85 // Threshold
));
// Use composer.render() instead of renderer.render()
Resources for Learning More
- Three.js Documentation - Official docs with examples
- Three.js Examples - Hundreds of live examples
- WebGL Fundamentals - Deep dive into raw WebGL
- Discover Three.js - Free online book
- Three.js GitHub - Source code and issues
Conclusion
WebGL and Three.js open up incredible possibilities for web development. From simple product viewers like our BMW M4 3D model to complex virtual worlds, 3D on the web is becoming mainstream.
Start with the basics - a simple scene, a loaded model, orbit controls. Then gradually add features like lighting, shadows, and interactions. Before you know it, you'll be building immersive 3D experiences.
The code behind our BMW M4 3D model viewer uses everything covered in this tutorial. Try it yourself and see WebGL in action.