Build Immersive Web VR Panoramas with Three.js: A Step‑by‑Step Guide
This article explains the fundamentals of virtual reality, the WebGL and OpenGL APIs, and demonstrates how to create interactive VR panoramas for houses and cars using three.js, covering scene setup, cameras, renderers, geometry, textures, materials, lighting, and user interaction with concise code examples.
Web VR Panorama Implementation
This article, authored by members of the ByteDance Education Frontend team, explores the principles of virtual reality and provides practical demos of Web VR panoramas using three.js.
Preface
VR concepts have become popular, with applications such as VR house tours, car showcases, and travel experiences. This guide investigates the implementation principles of Web VR panoramas and presents two demos to help developers apply these techniques in real projects.
What Is VR?
Virtual reality (VR) is a computer‑generated three‑dimensional environment that provides users with immersive visual and sensory experiences, allowing them to feel present in a simulated world.
Unlike augmented reality (AR), which overlays digital content onto the real world, VR requires a completely reconstructed environment.
Principles of Virtual Reality
Human depth perception relies on projecting a 3D world onto a 2D retina. By presenting images with correct perspective, viewers perceive depth. VR builds on this principle, and any medium that can simulate a 3D space—phones, computers, large screens, VR headsets, or even projected air—can serve as a VR platform.
Simulating 3D Space on the Web
To render 3D graphics on the web, we use the OpenGL API, which defines an abstract set of functions for drawing 2D and 3D shapes. In browsers, the JavaScript binding of OpenGL is WebGL.
What Is OpenGL?
OpenGL (Open Graphics Library) is a cross‑language, cross‑platform API for rendering 2D and 3D vector graphics.
It is widely used in CAD, scientific visualization, and game development.
Can We Use OpenGL on the Web?
Yes, via WebGL , which provides a JavaScript API for interactive 2D/3D graphics without plugins.
What Is WebGL?
WebGL is a JavaScript API that enables GPU‑accelerated rendering of interactive 2D and 3D graphics within any compatible web browser.
WebGL integrates with the HTML5 canvas element, allowing WebGL content to mix with other page elements.
// Create a canvas element
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
// Get a WebGL rendering context
const gl = canvas.getContext('webgl');
// Use the WebGL API to draw graphics
// ...Popular WebGL 3D engines such as three.js and babylon.js provide high‑level abstractions that simplify development.
Three.js Implementation of VR Panoramas
Basic Concepts
In three.js, rendering a 3D scene requires three core components:
Scene : a container for all objects except the renderer.
Camera : defines the viewpoint and projection.
Renderer : draws the scene onto a canvas.
Scene
A container that holds everything in the 3D world except the renderer. It uses a right‑handed coordinate system (X right, Y up, Z out of the screen).
// Create a three.js scene
const scene = new THREE.Scene();Camera
Like a human eye, a camera can look in any direction and its parameters control field of view and depth.
three.js provides PerspectiveCamera and OrthographicCamera. Perspective projection mimics human vision, while orthographic projection keeps object size constant regardless of distance.
PerspectiveCamera (fov, aspect, near, far)
OrthographicCamera (left, right, top, bottom, near, far)
PerspectiveCamera Parameters
fov – field of view angle.
aspect – aspect ratio of the viewport.
near – nearest visible distance.
far – farthest visible distance.
OrthographicCamera Parameters
left, right, top, bottom – distances to the respective clipping planes.
near – start of the rendering range (often negative).
far – end of the rendering range (often positive).
Renderer
Renders the view from the camera onto the canvas.
const renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
const canvas = renderer.domElement;
document.body.appendChild(canvas);
renderer.render(scene, camera);Geometry
All objects are composed of points forming faces, which together form geometry.
Common built‑in geometries include:
SphereGeometry CylinderGeometry BoxGeometry ConeGeometry TorusGeometry TorusKnotGeometryTexture
Applying an image to a geometry is like wrapping a piece of paper around a shape and drawing on it.
Example of applying a duck texture to a cube:
// Box geometry
const boxGeometry = new THREE.BoxGeometry(2, 2, 2);
// Load texture
const texture = new THREE.TextureLoader().load('textures/duck.png');
const material = new THREE.MeshBasicMaterial({ map: texture });
const cube = new THREE.Mesh(boxGeometry, material);
scene.add(cube);Video textures can be applied similarly.
const video = document.getElementById('video');
const texture = new THREE.VideoTexture(video);
const material = new THREE.MeshBasicMaterial({ map: texture });
const cube = new THREE.Mesh(boxGeometry, material);
scene.add(cube);Material
Materials define how surfaces interact with light, similar to using different types of paper for drawing.
Common materials used in the demos: MeshBasicMaterial – no lighting. MeshMatcapMaterial – simulates lighting without actual light sources. MeshPhongMaterial – shiny, suitable for ceramic or lacquer. MeshToonMaterial – cartoon style. MeshLambertMaterial – non‑shiny, good for wood or stone.
Lighting
Without explicit lights, a default ambient light is added.
Typical lights: AmbientLight – uniform illumination, no shadows. DirectionalLight – parallel light, simulating sunlight. PointLight – emits from a point in all directions, like a bulb. SpotLight – cone‑shaped light from a point.
VR House Panorama Implementation
Principle
Use a camera as the viewer’s eye.
Map six textures onto the faces of a cube to represent the interior of a house.
Place the camera inside the cube and move it to simulate walking.
Assets
Code Steps
Create scene, camera, and orbit controls.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(1, 1, 4);
const controls = new THREE.OrbitControls(camera, renderer.domElement);Load six textures and apply them to a cube.
const textures = getTexturesFromAtlasFile('textures/house.jpeg', 6);
const materials = [];
for (let i = 0; i < 6; i++) {
materials.push(new THREE.MeshBasicMaterial({ map: textures[i] }));
}
const skyBox = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), materials);
scene.add(skyBox);
function getTexturesFromAtlasFile(atlasImgUrl, tilesNum) {
const textures = [];
for (let i = 0; i < tilesNum; i++) {
textures[i] = new THREE.Texture();
}
new THREE.ImageLoader().load(atlasImgUrl, (image) => {
const tileWidth = image.height;
for (let i = 0; i < textures.length; i++) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = canvas.height = tileWidth;
context.drawImage(image, tileWidth * i, 0, tileWidth, tileWidth, 0, 0, tileWidth, tileWidth);
textures[i].image = canvas;
textures[i].needsUpdate = true;
}
});
return textures;
}Flip the cube geometry so textures face inward.
skyBox.geometry.scale(1, 1, -1);Move the camera inside the cube.
camera.position.set(0, 0, -0.01);Listen for keyboard events to move the camera.
window.addEventListener('keydown', onKeydown);
function onKeydown(e) {
switch (e.keyCode) {
case 37: // left
if (camera.position.x > -0.5) camera.position.x -= 0.01;
break;
case 38: // up
if (camera.position.z > -0.5) camera.position.z -= 0.01;
break;
case 39: // right
if (camera.position.x < 0.5) camera.position.x += 0.01;
break;
case 40: // down
if (camera.position.z < 0.5) camera.position.z += 0.01;
break;
}
}VR Car Panorama Implementation
Principle
The challenge is to load a 3D car model, apply materials, and enable interactive color changes and wheel rotation.
Assets
Code Steps
Create scene, camera, and orbit controls.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(4.25, 1.4, -4.5);
const controls = new THREE.OrbitControls(camera, container);Load the GLTF car model and assign custom materials.
const bodyMaterial = new THREE.MeshPhysicalMaterial({ color: 0xff0000, metalness: 1.0, roughness: 0.5, clearcoat: 1.0, clearcoatRoughness: 0.03, sheen: 0.5 });
const detailsMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff, metalness: 1.0, roughness: 0.5 });
const glassMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0.25, roughness: 0, transmission: 1.0 });
const dracoLoader = new THREE.DRACOLoader();
dracoLoader.setDecoderPath('draco/');
const loader = new THREE.GLTFLoader();
loader.setDRACOLoader(dracoLoader);
const wheels = [];
loader.load('glb/car.glb', function (gltf) {
const carModel = gltf.scene.children[0];
carModel.getObjectByName('body').material = bodyMaterial;
carModel.getObjectByName('rim_fl').material = detailsMaterial;
carModel.getObjectByName('rim_fr').material = detailsMaterial;
carModel.getObjectByName('rim_rr').material = detailsMaterial;
carModel.getObjectByName('rim_rl').material = detailsMaterial;
carModel.getObjectByName('trim').material = detailsMaterial;
carModel.getObjectByName('glass').material = glassMaterial;
wheels.push(carModel.getObjectByName('wheel_fl'), carModel.getObjectByName('wheel_fr'), carModel.getObjectByName('wheel_rl'), carModel.getObjectByName('wheel_rr'));
// Add shadow plane
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(0.655 * 4, 1.3 * 4), new THREE.MeshBasicMaterial({ map: shadow, blending: THREE.MultiplyBlending, toneMapped: false, transparent: true }));
mesh.rotation.x = -Math.PI / 2;
mesh.renderOrder = 2;
carModel.add(mesh);
scene.add(carModel);
});Animate wheel rotation.
function render() {
controls.update();
const time = -performance.now() / 1000;
for (let i = 0; i < wheels.length; i++) {
wheels[i].rotation.x = time * Math.PI * 2;
}
grid.position.z = -(time) % 1;
renderer.render(scene, camera);
stats.update();
}Provide color pickers to change body, details, and glass colors.
// HTML color pickers (simplified)
<div id="info">
<span>Body <input id="body-color" type="color" value="#ff0000"></span>
<span>Details <input id="details-color" type="color" value="#ffffff"></span>
<span>Glass <input id="glass-color" type="color" value="#ffffff"></span>
</div>
// JavaScript listeners
const bodyColorInput = document.getElementById('body-color');
bodyColorInput.addEventListener('input', function () { bodyMaterial.color.set(this.value); });
const detailsColorInput = document.getElementById('details-color');
detailsColorInput.addEventListener('input', function () { detailsMaterial.color.set(this.value); });
const glassColorInput = document.getElementById('glass-color');
glassColorInput.addEventListener('input', function () { glassMaterial.color.set(this.value); });Conclusion
This brief exploration demonstrates how to create Web VR panoramas with three.js. The same techniques can be extended to many other immersive 3D scenarios.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
