Build a Navigable Blender Scene

Export a Blender room as a GLB and publish a first-person web walkthrough.

Introduction

30 Second Summary

A room shown in a picture only reveals one angle. Letting someone choose where to look turns that same space into a place they can explore.

In this project, you will turn a gallery room from Blender into a navigable webpage with Three.js. You will store its files in Amazon S3 before publishing the finished walkthrough with AWS Amplify Hosting.

What You'll Build

Open your public HTTPS link for a first-person tour through the Blender gallery using mouse look and WASD movement.

By the end of this project, you'll have:

  • Exported your Blender gallery as one walkthrough.glb file that keeps the room and its landmarks together.
  • Entered a local browser walkthrough in Microsoft Edge to look around with the mouse and move across the room with WASD.
  • Shared a public HTTPS walkthrough that visitors can load from a link and explore outside your computer.
  • Secret Mission: Hold Shift to sprint through the gallery while the HUD shows whether you are walking or sprinting.

Are there any prerequisites?

You need a Windows computer with Blender and Visual Studio Code. You also need internet access and an AWS account.

No prior Three.js experience is required.

Before We Start

Before any modeling or coding begins, this is your moment to commit to the experience you want to create. You are turning a compact gallery room into a navigable browser walkthrough that other people can explore.

Set Up the CDN Walkthrough

Your walkthrough needs to fetch its 3D file through a local web server. Opening the HTML file directly would bypass the delivery setup that the browser uses to load the asset.

The Live Server extension provides that local server. An import map points browser ES modules to a pinned Three.js release on the jsDelivr CDN. This keeps package managers out of the project.

In this step, get ready to:
  • Install Live Server 5.7.10 in Visual Studio Code.
  • Create the blender-scene-walkthrough folder with its three web files.
  • Serve the entry screen locally in Microsoft Edge.
Install Live Server

Live Server turns your project folder into a local website. Visual Studio Code shows the installed extension version on its details page.

  • Press the Windows key to open Windows search.
  • Type Visual Studio Code into the search box.
  • Press Enter to open Visual Studio Code.
  • Select the square extension icon in the left Activity Bar.
  • Search for Live Server in the extension search box.
  • Check the version shown on the Live Server details page.

✔️ I see the required version

Your extension details show 5.7.10. Live Server is ready to serve the walkthrough.

ⓧ I see an older version

The older release needs to be replaced so this project follows the pinned Live Server workflow.

  • Remove the older Live Server copy using the removal control on its extension details page.
  • Press Ctrl+P to open Quick Open.
  • Install the current release by running this command in Quick Open:
ext install ritwickdey.liveserver

What does this command do?

The command asks Visual Studio Code to install the Live Server extension published under ritwickdey.liveserver. The installed details page should show 5.7.10.

  • Confirm the Live Server details page now shows 5.7.10.

Still seeing the older version?

Close Visual Studio Code after the installation completes.

Reopen Visual Studio Code to refresh the extension details.

Still stuck? Help me update Live Server to the required version.

ⓧ Live Server is not installed

Live Server is missing from Visual Studio Code. Quick Open can install the extension without a package manager.

  • Press Ctrl+P to open Quick Open.
  • Install Live Server by running this command in Quick Open:
ext install ritwickdey.liveserver

What does this command do?

The command installs the Live Server extension inside Visual Studio Code. The current extension details should show 5.7.10 after installation.

  • Confirm the Live Server details page shows 5.7.10.

Live Server not installing?

Confirm that Visual Studio Code can access the internet.

Run the installation command again after the connection is restored.

Still stuck? Help me install Live Server in Visual Studio Code.

Create the walkthrough files

The walkthrough uses one file for page structure. A second file handles presentation. The JavaScript module becomes the 3D engine after you export the gallery.

  • Click File in the Visual Studio Code menu bar.
  • Select Open Folder.
  • Choose Desktop in the Windows folder dialog.
  • Select the New folder control.
  • Enter blender-scene-walkthrough as the folder name.
  • Select the blender-scene-walkthrough folder.
  • Choose Select Folder to open it in Visual Studio Code.

The Explorer sidebar now shows blender-scene-walkthrough as the open folder.

  • Create index.html inside blender-scene-walkthrough with the Explorer sidebar's New File control.
  • Create styles.css inside blender-scene-walkthrough with the same control.
  • Create main.js inside blender-scene-walkthrough with the same control.

You should see all three files listed together in the Explorer sidebar.

  • Add the placeholder for the future walkthrough logic to main.js by pasting this comment:
// Three.js walkthrough logic will be added after walkthrough.glb is exported.

Why use a placeholder?

The module path can be connected before the 3D asset exists. This comment keeps main.js valid until the walkthrough logic is added.

  • Press Ctrl+S to save main.js.

The unsaved indicator disappears from the main.js tab.

✔️ Awesome, I've got everything!

Your JavaScript module is saved with its temporary walkthrough comment.

ⓧ I'd like to double check the full code

// Three.js walkthrough logic will be added after walkthrough.glb is exported.
  • Build the document shell in index.html by replacing its empty contents with this code:
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Blender Walkthrough</title>
  <link rel="stylesheet" href="./styles.css">
  <script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.186.0/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.0/examples/jsm/"
      }
    }
  </script>
</head>
<body>
  <canvas id="scene" aria-label="Navigable Blender scene"></canvas>

  <script type="module" src="./main.js"></script>
</body>
</html>

What does this page shell do?

  • The import map assigns browser-friendly names to the pinned Three.js files delivered by jsDelivr.
  • The scene canvas gives the renderer a full-page drawing surface.
  • The module script loads main.js after the import map is available.
  • Press Ctrl+S to save index.html.
  • Right-click index.html in the Explorer sidebar.
  • Choose Open with Live Server.

Microsoft Edge opens a localhost URL. The browser tab shows Blender Walkthrough as the page title.

Local page not opening?

Confirm that Live Server is enabled on its extension details page.

Confirm that index.html is inside the open blender-scene-walkthrough folder.

Still stuck? Help me open index.html with Live Server.

  • Place the cursor on the blank line below the scene canvas in index.html.
  • Add the entry menu plus movement HUD by pasting this code before the module script:
  <section id="menu" class="menu">
    <p class="eyebrow">Blender to browser</p>
    <h1>Gallery Walkthrough</h1>
    <p id="status">Loading walkthrough.glb...</p>
    <button id="start" type="button" disabled>Enter Scene</button>
    <p class="help">WASD to move. Mouse to look. Esc releases the cursor.</p>
  </section>

  <aside class="hud" aria-label="Movement controls">
    <span>WASD Move</span>
    <span>Mouse Look</span>
    <span>Esc Unlock</span>
  </aside>

What does this interface add?

  • The menu section holds the loading state plus the disabled entry button.
  • The hud lists the controls that visitors use once navigation is connected.
  • The fixed IDs give the future Three.js logic exact elements to update.
  • Press Ctrl+S to save index.html.
  • Refresh the localhost page in Microsoft Edge.

You should see the Gallery Walkthrough text plus a disabled Enter Scene button. The controls appear as plain text until the stylesheet is added.

Entry text missing?

Confirm that the menu markup sits inside the body element.

Confirm that the module script remains below the HUD.

Still stuck? Help me find the missing entry screen markup.

✔️ Awesome, I've got everything!

Your complete page structure now includes the import map plus the entry interface.

ⓧ I'd like to double check the full code

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Blender Walkthrough</title>
  <link rel="stylesheet" href="./styles.css">
  <script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.186.0/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.0/examples/jsm/"
      }
    }
  </script>
</head>
<body>
  <canvas id="scene" aria-label="Navigable Blender scene"></canvas>

  <section id="menu" class="menu">
    <p class="eyebrow">Blender to browser</p>
    <h1>Gallery Walkthrough</h1>
    <p id="status">Loading walkthrough.glb...</p>
    <button id="start" type="button" disabled>Enter Scene</button>
    <p class="help">WASD to move. Mouse to look. Esc releases the cursor.</p>
  </section>

  <aside class="hud" aria-label="Movement controls">
    <span>WASD Move</span>
    <span>Mouse Look</span>
    <span>Esc Unlock</span>
  </aside>

  <script type="module" src="./main.js"></script>
</body>
</html>
Build and serve the entry screen

The canvas must fill the browser before it can display the gallery. The interface also needs a fixed overlay so it stays readable above the future 3D scene.

  • Add the page foundation to styles.css by pasting this code:
* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
}

body {
  overflow: hidden;
  background: #07111f;
  color: #f8fafc;
  font-family: "Segoe UI", Arial, sans-serif;
}

#scene {
  display: block;
  width: 100vw;
  height: 100vh;
}

What does this foundation control?

  • The universal selector keeps element dimensions predictable.
  • The body removes browser spacing plus establishes the walkthrough colours.
  • The canvas fills the complete browser viewport.
  • Press Ctrl+S to save styles.css.
  • Refresh the localhost page in Microsoft Edge.

You should see the viewport change to a dark navy canvas.

Background still white?

Confirm that index.html links to ./styles.css.

Confirm that styles.css is saved inside the same folder.

Still stuck? Help me connect styles.css to my Live Server page.

  • Add the centered menu treatment below the existing rules in styles.css by pasting this code:
.menu {
  position: fixed;
  inset: 0;
  display: grid;
  place-content: center;
  gap: 0.9rem;
  padding: 2rem;
  text-align: center;
  background: rgba(2, 6, 23, 0.78);
  backdrop-filter: blur(8px);
}

.menu[hidden] {
  display: none;
}

.eyebrow {
  margin: 0;
  color: #7dd3fc;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

How does the menu stay readable?

  • Fixed positioning stretches the menu across the viewport.
  • The grid centers the entry content over the canvas.
  • The translucent background separates the text from the future scene.
  • Press Ctrl+S to save styles.css.
  • Refresh the localhost page in Microsoft Edge.

You should see the entry content centered over a tinted full-screen overlay. The eyebrow text appears in light blue.

Menu not centered?

Confirm that the HTML section uses class="menu".

Confirm that the .menu rule includes fixed positioning plus grid layout.

Still stuck? Help me center the walkthrough menu.

  • Add the title plus button styling below the menu rules by pasting this code:
h1,
p {
  margin: 0;
}

h1 {
  font-size: clamp(2.5rem, 8vw, 5rem);
}

button {
  justify-self: center;
  padding: 0.85rem 1.25rem;
  border: 0;
  border-radius: 999px;
  background: #38bdf8;
  color: #082f49;
  font: inherit;
  font-weight: 800;
  cursor: pointer;
}

button:disabled {
  cursor: wait;
  opacity: 0.45;
}

.help {
  color: #cbd5e1;
  font-size: 0.9rem;
}

What changes here?

  • The heading scales with the viewport while staying inside readable limits.
  • The button becomes a rounded control with a visible disabled state.
  • The helper line uses a muted colour to keep attention on the title.
  • Press Ctrl+S to save styles.css.
  • Refresh the localhost page in Microsoft Edge.

You should see a large Gallery Walkthrough heading. The disabled entry button appears as a faded blue pill.

Button still looks like plain text?

Confirm that the HTML uses a button element for Enter Scene.

Confirm that the button rules appear after the earlier page rules.

Still stuck? Help me apply the walkthrough button styles.

  • Finish the HUD layout below the button rules by pasting this code:
.hud {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  display: flex;
  gap: 0.5rem;
  pointer-events: none;
}

.hud span {
  padding: 0.5rem 0.7rem;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  background: rgba(2, 6, 23, 0.72);
  font-size: 0.75rem;
}

@media (max-width: 600px) {
  .hud {
    left: 1rem;
    flex-wrap: wrap;
  }
}

How does the HUD adapt?

  • The HUD stays pinned to the lower corner above the canvas.
  • Each control label becomes a compact translucent pill.
  • The media rule lets the pills wrap on narrow browser windows.
  • Press Ctrl+S to save styles.css.
  • Refresh the localhost page in Microsoft Edge.

You should see three movement pills in the lower-right corner. They move toward the left plus wrap when the browser width drops below 600px.

HUD pills missing?

Confirm that the HTML aside uses class="hud".

Confirm that every control label is inside a span element.

Still stuck? Help me display the movement HUD.

✔️ Awesome, I've got everything!

Your complete stylesheet now covers the canvas plus the entry interface.

ⓧ I'd like to double check the full code

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
}

body {
  overflow: hidden;
  background: #07111f;
  color: #f8fafc;
  font-family: "Segoe UI", Arial, sans-serif;
}

#scene {
  display: block;
  width: 100vw;
  height: 100vh;
}

.menu {
  position: fixed;
  inset: 0;
  display: grid;
  place-content: center;
  gap: 0.9rem;
  padding: 2rem;
  text-align: center;
  background: rgba(2, 6, 23, 0.78);
  backdrop-filter: blur(8px);
}

.menu[hidden] {
  display: none;
}

.eyebrow {
  margin: 0;
  color: #7dd3fc;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

h1,
p {
  margin: 0;
}

h1 {
  font-size: clamp(2.5rem, 8vw, 5rem);
}

button {
  justify-self: center;
  padding: 0.85rem 1.25rem;
  border: 0;
  border-radius: 999px;
  background: #38bdf8;
  color: #082f49;
  font: inherit;
  font-weight: 800;
  cursor: pointer;
}

button:disabled {
  cursor: wait;
  opacity: 0.45;
}

.help {
  color: #cbd5e1;
  font-size: 0.9rem;
}

.hud {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  display: flex;
  gap: 0.5rem;
  pointer-events: none;
}

.hud span {
  padding: 0.5rem 0.7rem;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  background: rgba(2, 6, 23, 0.72);
  font-size: 0.75rem;
}

@media (max-width: 600px) {
  .hud {
    left: 1rem;
    flex-wrap: wrap;
  }
}

Before you relaunch the page, do you expect to see raw text or a centered full-screen entry screen?

  • Right-click index.html in the Visual Studio Code Explorer sidebar.
  • Choose Open with Live Server.

Microsoft Edge opens the project at a localhost URL. You should see the centered Gallery Walkthrough entry screen with a loading message plus a disabled blue button.

That's the delivery layer working: your browser now serves the walkthrough shell with pinned CDN modules. Next, you'll build the gallery room that fills this screen.

Build and Export a Starter Blender Scene

Your local entry screen is ready. It now needs a spatial environment that makes movement easy to judge.

You’ll use Blender to shape a gallery with clear boundaries. Two landmark blocks will make every movement direction easy to recognise later.

In this step, get ready to:
  • Shape the default cube into the gallery floor.
  • Build three walls plus two landmark blocks.
  • Export the finished room as one browser-ready asset.
Shape the gallery floor

The default cube already has the geometry you need for the floor. Flattening it creates a wide surface without adding another object.

  • Press the Windows key to open the search bar.
  • Type Blender into the search bar.
  • Press Enter to open Blender.
  • Dismiss any startup splash screen to reach the default scene.
  • Confirm Blender is in Object Mode with the default cube selected.
  • Open the Sidebar in the 3D Viewport.
  • Select Transform in the Sidebar.
  • Set Dimensions to 12, 12, 0.2.
  • Set Location to 0, 0, -0.1.

You should see a broad square floor centred on the scene origin. Its top surface sits level with the ground plane.

Floor dimensions look wrong?

  • Confirm the Dimensions values appear in the order 12, 12, 0.2.
  • Confirm the Location values appear as 0, 0, -0.1.
  • Still stuck? Help me correct the Blender floor transform.
Build the walls and landmarks

The floor gives you a reliable source object for the rest of the room. Duplicated geometry keeps the scene compact while you reshape each copy.

  • Select the floor in the 3D Viewport.
  • Open Object from the top menu.
  • Choose Duplicate Objects.

The duplicate is selected in the same position as the floor.

  • Set its Dimensions to 12, 0.2, 4.
  • Set its Location to 0, 6, 2.

You should see a back wall rising from the far edge of the floor.

  • Select the back wall.
  • Open Object from the top menu.
  • Choose Duplicate Objects.

The new wall keeps the back wall’s dimensions.

  • Set its Dimensions to 0.2, 12, 4.
  • Set its Location to -6, 0, 2.

You should see the left wall run along one side of the floor.

  • Select the left wall.
  • Open Object from the top menu.
  • Choose Duplicate Objects.

The duplicate already has the correct side-wall dimensions.

  • Set its Location to 6, 0, 2.

You should now see a floor enclosed by one back wall plus two side walls.

  • Select the floor again.
  • Open Object from the top menu.
  • Choose Duplicate Objects.

This duplicate becomes the first landmark inside the gallery.

  • Use Dimensions in Transform to resize the duplicate into a block that is shorter than the walls.
  • Use Location to place the block inside the room.

You should see one distinct block standing away from the walls.

  • Select the first landmark block.
  • Open Object from the top menu.
  • Choose Duplicate Objects.

The second landmark starts with the same shape as the first.

  • Change its Location to place it in a separate part of the room.

You should see two separate blocks that make the room’s layout easy to recognise.

A material gives the exported geometry a visible surface finish. The Base Color controls the main colour carried into the browser asset.

  • Select the floor.
  • Open Material Properties.
  • Create one material for the selected floor.

You should see the material settings for the Principled BSDF shader.

  • Select Base Color.
  • Choose a colour that stands out against the viewport background.
  • Switch the viewport shading to Material Preview.

The floor should now display the material colour beneath the walls plus landmark blocks.

Why do landmarks matter?

Large flat surfaces can look similar from every angle. Landmark blocks give movement a visual reference.

Their relative positions will change as the browser camera moves. That visible shift helps you confirm direction.

Export the browser-ready GLB

A glTF Binary file packages the room geometry into one asset. It carries the material with it.

That single file can sit beside your webpage files. The browser can fetch it through Live Server in the next step.

  • Click File in Blender’s top menu.
  • Select Import/Export.
  • Select glTF 2.0 (.glb, .gltf).

You should see Blender’s glTF export browser with the export settings available.

  • Select glTF Binary (.glb) as the export format.
  • Enter walkthrough.glb as the file name.

The export is now configured to produce one binary asset with the required file name.

  • Navigate to the blender-scene-walkthrough folder that is open in Visual Studio Code.

The selected export folder should be the same folder that contains index.html.

  • Keep Y Up enabled.
  • Leave Cameras unselected.

The asset now uses the expected vertical orientation without carrying Blender’s camera.

  • Leave Punctual Lights unselected.
  • Choose Export to create the asset.

Blender writes the gallery into walkthrough.glb inside your web project folder.

Why use these export settings?

Y Up converts Blender’s vertical orientation for glTF. This keeps the exported room aligned for browser rendering.

Excluding Cameras keeps the browser camera under your control. Excluding Punctual Lights lets Three.js provide its own lighting.

  • Orbit around the finished gallery room in Blender.

You should see the floor, three enclosing walls, two separate landmark blocks, and the visible material finish.

Before you verify the export, where do you expect walkthrough.glb to sit compared with index.html?

  • Switch back to Visual Studio Code from earlier.
  • Look in the blender-scene-walkthrough file list.

You should see walkthrough.glb beside index.html.

Cannot find the exported GLB?

  • Return to Blender’s export browser to confirm the selected folder is blender-scene-walkthrough.
  • Confirm the exported file name is exactly walkthrough.glb.
  • Confirm the export format is glTF Binary (.glb).
  • Still stuck? Help me find or re-export my Blender GLB file.

That is the modelling milestone complete: your gallery is now a single browser-ready asset. Next, you’ll load it with Three.js so Microsoft Edge can render the room.

Load the Blender Scene

Your Blender gallery is now bundled into a glTF Binary file named walkthrough.glb beside the webpage. A browser still needs JavaScript to turn that file into a visible 3D scene.

The existing import map lets ES modules resolve Three.js plus GLTFLoader in the browser. GLTFLoader then brings the exported room into the page through asynchronous asset loading.

In this step, get ready to:
  • Create a lit Three.js scene with a browser camera.
  • Load walkthrough.glb into the scene.
  • Render the room inside a responsive browser view.
Create the browser scene

A renderer turns the Three.js scene into pixels on the existing canvas. The perspective camera places the viewer at eye height inside the room.

  • In main.js from earlier, select the placeholder comment.
  • Replace the selected comment by pasting the code below:
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const canvas = document.querySelector('#scene');
const status = document.querySelector('#status');
const startButton = document.querySelector('#start');

const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x07111f);

const camera = new THREE.PerspectiveCamera(
  65,
  window.innerWidth / window.innerHeight,
  0.1,
  100
);
camera.position.set(0, 1.7, 4);

What does this code do?

  • The imports make the Three.js namespace plus GLTFLoader available to main.js.
  • The renderer draws into the existing scene canvas. Its pixel ratio is capped to keep dense displays from doing unnecessary work.
  • The scene holds everything that Three.js renders.
  • The camera starts at an eye-level position inside the exported room.
  • Save main.js.
  • Switch back to the local page in Microsoft Edge.
  • Refresh the page.

You'll see the entry screen render over the dark full-screen canvas.

Page failing after the refresh?

  • Confirm that the new code replaced the placeholder comment in main.js.
  • Check that the import map in index.html still appears before the module script.
  • Still stuck? Help me troubleshoot the initial Three.js scene setup.
Load the GLB asset

The room loads separately from the webpage. A success callback adds the finished asset only after the browser has fetched it.

  • In main.js, place your cursor below camera.position.set(0, 1.7, 4).
  • Paste the lighting plus loader code below:
const ambientLight = new THREE.AmbientLight(0xffffff, 1.8);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
directionalLight.position.set(4, 8, 4);
scene.add(directionalLight);

const loader = new GLTFLoader();
loader.load(
  './walkthrough.glb',
  (gltf) => {
    scene.add(gltf.scene);
    status.textContent = 'Scene ready';
    startButton.disabled = false;
  },
  undefined,
  (error) => {
    status.textContent = 'Could not load walkthrough.glb. Check the file name and Live Server.';
    console.error(error);
  }
);

How does the asset loader work?

  • The ambient light provides soft illumination across the room.
  • The directional light gives the walls plus landmark blocks a clearer sense of shape.
  • The loader.load() call fetches ./walkthrough.glb from the same folder as the webpage.
  • The success callback adds gltf.scene to the Three.js scene. It also updates the status plus enables the entry button.
  • The error callback replaces the loading message when the browser cannot fetch the asset.
  • Save main.js.
  • Switch back to the local page in Microsoft Edge.

Before you refresh, what should the status tell you if the browser finds the exported file?

  • Refresh the page.

You'll see Scene ready after the asset loads. The Enter Scene button is now enabled.

That's the asset handoff complete: your Blender export is now part of the browser scene.

Loading message staying on screen?

  • Confirm that walkthrough.glb sits beside index.html in the blender-scene-walkthrough folder.
  • Check that the asset name uses lowercase letters plus the .glb extension.
  • Confirm that you are viewing the page served by Live Server.
  • Still stuck? Help me find why walkthrough.glb is not loading.
Render and test the room

The loaded room becomes visible when the renderer draws the scene from the camera's viewpoint. The resize handler keeps that view proportional when the browser dimensions change.

  • In main.js, place your cursor after the block that starts with loader.load(.
  • Paste the render loop plus resize handler below:
function animate() {
  renderer.render(scene, camera);
}

renderer.setAnimationLoop(animate);

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

How does the render loop work?

  • The animate() function draws the current scene from the camera's viewpoint.
  • The setAnimationLoop() call repeats that draw so newly loaded content appears.
  • The resize handler recalculates the camera's aspect ratio. It also matches the renderer to the new browser dimensions.
  • Save main.js.
  • Switch back to the local page in Microsoft Edge.

Before you test the finished file, do you think a loaded 3D scene responds to mouse plus keyboard input automatically?

  • Refresh the page.

You've crossed the Blender-to-browser boundary: the gallery room now renders behind a Scene ready status. The Enter Scene button is enabled.

  • Resize the Microsoft Edge window.

You'll see the canvas continue to fill the available window without stretching the room.

  • Click Enter Scene.
  • Move the mouse over the imported room.
  • Press W.

The view stays fixed when you click the button or try to move. This is the intended limitation: asset loading plus camera input are separate systems.

Room missing after the final refresh?

  • Confirm that animate() appears after the complete loader.load( block.
  • Check that renderer.setAnimationLoop(animate) appears before the resize handler.
  • Verify that the camera position still uses 0, 1.7, 4.
  • Still stuck? Help me troubleshoot the missing Blender room.

✔️ Awesome, I've got everything!

Great. Double-check that main.js is saved before you continue.

ⓧ I'd like to double check the full code

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const canvas = document.querySelector('#scene');
const status = document.querySelector('#status');
const startButton = document.querySelector('#start');

const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x07111f);

const camera = new THREE.PerspectiveCamera(
  65,
  window.innerWidth / window.innerHeight,
  0.1,
  100
);
camera.position.set(0, 1.7, 4);

const ambientLight = new THREE.AmbientLight(0xffffff, 1.8);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
directionalLight.position.set(4, 8, 4);
scene.add(directionalLight);

const loader = new GLTFLoader();
loader.load(
  './walkthrough.glb',
  (gltf) => {
    scene.add(gltf.scene);
    status.textContent = 'Scene ready';
    startButton.disabled = false;
  },
  undefined,
  (error) => {
    status.textContent = 'Could not load walkthrough.glb. Check the file name and Live Server.';
    console.error(error);
  }
);

function animate() {
  renderer.render(scene, camera);
}

renderer.setAnimationLoop(animate);

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

Your exported room now loads and renders in the browser. Next, you'll add pointer lock plus keyboard state to turn the fixed viewpoint into a first-person walkthrough.

Add First-Person Movement

Your Blender gallery now renders in Microsoft Edge from the local server. The fixed camera keeps every landmark out of reach.

Three.js provides PointerLockControls to turn mouse movement into camera rotation through pointer lock.

Keyboard state drives the camera across the room. Elapsed time keeps that movement smooth across different frame rates.

In this step, get ready to:
  • Capture the pointer for mouse look.
  • Track WASD keyboard state.
  • Scale camera movement with elapsed time.
Capture the pointer for mouse look

Pointer lock sends mouse movement to the camera while hiding the cursor. Lock events also give the entry menu a clear time to disappear or return.

  • In main.js, find the opening block that contains the imports plus DOM references.
  • Use this current block as your starting point:
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const canvas = document.querySelector('#scene');
const status = document.querySelector('#status');
const startButton = document.querySelector('#start');

What Are You Replacing?

This opening currently imports the scene loader. It also stores the page elements used by the walkthrough.

  • Replace that opening block with the updated version below:
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';

const canvas = document.querySelector('#scene');
const menu = document.querySelector('#menu');
const status = document.querySelector('#status');
const startButton = document.querySelector('#start');

What Does This Setup Add?

  • The PointerLockControls import makes first-person mouse look available to the module.
  • The menu reference gives the lock events access to the entry screen.
  • Save main.js.
  • Return to Microsoft Edge.
  • Confirm the page reaches Scene ready after Live Server refreshes it.

The loaded room confirms that the new import resolves without breaking the existing scene.

Does the Scene Stop Loading?

Compare the new import with the snippet above. A missing quote or character prevents the module from running.

Confirm that the menu reference uses the existing #menu element.

Still stuck? Help me debug the PointerLockControls import in my Three.js walkthrough.

The imported class needs a controls instance connected to the camera. The entry button can then request pointer lock from a direct click.

  • In main.js, find scene.add(directionalLight);.
  • Add the controls setup directly below that line by copying this code:
const controls = new PointerLockControls(camera, document.body);

startButton.addEventListener('click', () => {
  controls.lock();
});

controls.addEventListener('lock', () => {
  menu.hidden = true;
});

controls.addEventListener('unlock', () => {
  menu.hidden = false;
});

What Does This Code Do?

  • The controls instance connects the camera to mouse input from the page.
  • The click handler calls controls.lock() from the entry button.
  • The lock event hides the entry menu once the mouse is captured.
  • The unlock event restores the menu when pointer lock ends.
  • Save main.js.
  • Switch back to Microsoft Edge.

Before you test it, do you expect the entry menu to remain visible after the mouse is captured?

  • Click Enter Scene.
  • Move the mouse across the screen.
  • Press Esc.

You'll see the menu disappear when pointer lock starts. The camera follows your mouse movement.

Pressing Esc releases the cursor. The entry menu returns immediately.

Does Mouse Look Stay Inactive?

Confirm that you clicked the enabled Enter Scene button. Browsers require a direct interaction before they capture the pointer.

Check that the controls block sits below the camera setup. The camera must exist before the controls instance uses it.

Still stuck? Help me troubleshoot pointer lock in my local walkthrough.

Track keyboard state

First-person movement needs a memory of which keys are currently held. The movement object stores that state until a matching key release clears it.

  • In main.js, find const loader = new GLTFLoader();.
  • Insert the keyboard state block directly above the loader by copying this code:
const movement = {
  KeyW: false,
  KeyA: false,
  KeyS: false,
  KeyD: false
};

function updateMovement(event, isPressed) {
  if (event.code in movement) {
    movement[event.code] = isPressed;
    event.preventDefault();
  }
}

window.addEventListener('keydown', (event) => {
  updateMovement(event, true);
});

window.addEventListener('keyup', (event) => {
  updateMovement(event, false);
});

window.addEventListener('blur', () => {
  Object.keys(movement).forEach((code) => {
    movement[code] = false;
  });
});

How Does Keyboard State Work?

  • The movement object starts each supported key in the released state.
  • The updateMovement() function changes only the four movement keys.
  • The keydown listener records a pressed key.
  • The keyup listener records a released key.
  • The blur listener clears every key when the browser loses focus.
  • Save main.js.
  • Return to Microsoft Edge.
  • Refresh the local page.
  • Confirm the loading message changes to Scene ready.

Seeing the room load again confirms that the keyboard listeners run without stopping the module.

Does the Room Stop at Loading?

Check every brace in updateMovement() against the snippet. One missing closing brace prevents the loader code below it from running.

Confirm that each movement property matches its keyboard event code exactly.

Still stuck? Help me fix the keyboard state block in main.js.

Move with elapsed time

Browsers can render a different number of frames each second. Movement based on elapsed seconds covers a consistent distance across those frame rates.

  • In main.js, find the current animate() function.
  • Use this existing function as your reference:
function animate() {
  renderer.render(scene, camera);
}

What Is Missing?

The current animation loop renders the scene from the same camera position every frame. It does not use the keyboard state to change that position.

  • Replace the current function with this timing-aware block:
let previousTime = performance.now();

function animate() {
  const currentTime = performance.now();
  const delta = Math.min((currentTime - previousTime) / 1000, 0.05);
  previousTime = currentTime;

  if (controls.isLocked) {
    const forward = Number(movement.KeyW) - Number(movement.KeyS);
    const sideways = Number(movement.KeyD) - Number(movement.KeyA);
    const speed = 3;

    controls.moveForward(forward * speed * delta);
    controls.moveRight(sideways * speed * delta);
  }

  renderer.render(scene, camera);
}

How Does Movement Stay Smooth?

  • The timestamps measure the seconds between the current frame and the previous frame.
  • The delta value scales each movement distance by that elapsed time.
  • The forward value combines the forward key with the backward key.
  • The sideways value combines the right key with the left key.
  • The controls.isLocked check limits movement to the active walkthrough.
  • The speed value keeps movement at 3 units per second.
  • Save main.js.
  • Switch back to Microsoft Edge.
  • Refresh the local page.

Before you enter the room, which camera actions do you expect the mouse plus the four movement keys to control?

  • Click Enter Scene.
  • Move the mouse to look around the room.
  • Hold W to move forward.
  • Hold A to move left.
  • Hold S to move backward.
  • Hold D to move right.
  • Press Esc to release the cursor.

You'll see the menu disappear when you enter. The camera follows the direction of your mouse.

Each movement key moves the camera smoothly across the room's XZ plane. Pressing Esc restores the entry menu.

  • Resize Microsoft Edge to a narrower window.
  • Click Enter Scene again.
  • Test the movement keys again.

You'll see the gallery fit the resized window. Movement remains smooth at the new viewport size.

Movement Scope

This walkthrough keeps the camera at a fixed eye height. Movement stays open across the scene's XZ plane.

Wall and floor collision detection belong in a follow-on project.

Does Movement Feel Wrong?

Confirm that previousTime sits outside animate(). It must keep its value between frames.

Check that every movement distance includes delta. Missing it makes speed depend on the frame rate.

Still stuck? Help me debug smooth WASD movement in my Three.js walkthrough.

  • Compare your finished main.js with the complete reference in the tabs below.

✔️ Awesome, I've got everything!

Your pointer lock controls, keyboard state, elapsed-time movement, loading logic, render loop, and resize handling are all in place.

ⓧ I'd like to double check the full code

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';

const canvas = document.querySelector('#scene');
const menu = document.querySelector('#menu');
const status = document.querySelector('#status');
const startButton = document.querySelector('#start');

const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x07111f);

const camera = new THREE.PerspectiveCamera(
  65,
  window.innerWidth / window.innerHeight,
  0.1,
  100
);
camera.position.set(0, 1.7, 4);

const ambientLight = new THREE.AmbientLight(0xffffff, 1.8);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
directionalLight.position.set(4, 8, 4);
scene.add(directionalLight);

const controls = new PointerLockControls(camera, document.body);

startButton.addEventListener('click', () => {
  controls.lock();
});

controls.addEventListener('lock', () => {
  menu.hidden = true;
});

controls.addEventListener('unlock', () => {
  menu.hidden = false;
});

const movement = {
  KeyW: false,
  KeyA: false,
  KeyS: false,
  KeyD: false
};

function updateMovement(event, isPressed) {
  if (event.code in movement) {
    movement[event.code] = isPressed;
    event.preventDefault();
  }
}

window.addEventListener('keydown', (event) => {
  updateMovement(event, true);
});

window.addEventListener('keyup', (event) => {
  updateMovement(event, false);
});

window.addEventListener('blur', () => {
  Object.keys(movement).forEach((code) => {
    movement[code] = false;
  });
});

const loader = new GLTFLoader();
loader.load(
  './walkthrough.glb',
  (gltf) => {
    scene.add(gltf.scene);
    status.textContent = 'Scene ready';
    startButton.disabled = false;
  },
  undefined,
  (error) => {
    status.textContent = 'Could not load walkthrough.glb. Check the file name and Live Server.';
    console.error(error);
  }
);

let previousTime = performance.now();

function animate() {
  const currentTime = performance.now();
  const delta = Math.min((currentTime - previousTime) / 1000, 0.05);
  previousTime = currentTime;

  if (controls.isLocked) {
    const forward = Number(movement.KeyW) - Number(movement.KeyS);
    const sideways = Number(movement.KeyD) - Number(movement.KeyA);
    const speed = 3;

    controls.moveForward(forward * speed * delta);
    controls.moveRight(sideways * speed * delta);
  }

  renderer.render(scene, camera);
}

renderer.setAnimationLoop(animate);

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

This reference shows the exact cumulative file for the local first-person walkthrough.

That's the fixed-camera limitation solved. Next, you'll publish the navigable gallery through Amazon S3 and AWS Amplify Hosting.

Publish the Walkthrough Through AWS

Your local Blender walkthrough now responds to mouse look plus WASD movement. Its Live Server address only works on your computer, so nobody else can visit it.

The AWS Management Console lets you place the site files in Amazon S3. AWS Amplify Hosting turns those files into a public HTTPS walkthrough delivered through a CDN.

In this step, get ready to:
  • Store the four walkthrough files in a uniquely named S3 bucket.
  • Publish the bucket through AWS Amplify Hosting.
  • Test every navigation control at the public address.
Upload the walkthrough to Amazon S3

A general purpose S3 bucket keeps the webpage files together with the GLB asset. Amplify reads this bucket as the source for the public deployment.

Check Your AWS Allowance

AWS Amplify Hosting is usage-based. Eligible AWS Free Tier usage includes up to 5 GB stored on the CDN.

Eligible usage also includes 15 GB of data transfer out per month. Usage beyond the allowance is billed.

This project can stay at $0 with eligible allowances or credits. A quick account check keeps the cost predictable.

  • Confirm that your AWS account has Free Tier eligibility or credits before creating the resources.
  • Switch back to Microsoft Edge.
  • Navigate to the AWS Management Console.
  • Sign in to your AWS account.

You'll reach the AWS Management Console after authentication.

  • Open the region selector in the top navigation.
  • Select US East (N. Virginia).

The selected Region should now show US East (N. Virginia). Your resources will be created in us-east-1.

Choose a Globally Unique Name

S3 bucket names must be unique within the AWS partition. Use 3 to 63 characters.

Choose lowercase letters, numbers, periods, or hyphens. Make sure the name begins with a letter or number.

  • Use the console search bar to search for Amazon S3.
  • Select Amazon S3 from the search results.
  • Choose General purpose buckets.
  • Choose Create bucket.
  • Enter your unique bucket name in the Bucket name field.
  • Leave the remaining settings at their defaults.
  • Choose Create bucket.

You've cleared the first cloud milestone: the bucket is ready for your walkthrough files.

The HTML uses relative paths to reach the stylesheet, JavaScript, and gallery asset. Upload all four files at the top level of the same bucket.

  • Open your unique bucket name from the General purpose buckets list.
  • Select the Objects tab.
  • Choose Upload.
  • Choose Add files.
  • Select index.html, styles.css, main.js, and walkthrough.glb from the blender-scene-walkthrough folder.
  • Choose Upload.
  • Return to the Objects tab after the upload completes.

You'll see index.html, styles.css, main.js, and walkthrough.glb in the object list.

Missing a Walkthrough File?

  • Confirm that you selected the four files inside the blender-scene-walkthrough folder.
  • Upload any missing file through the Objects tab.
  • Still stuck? Help me check the files in my S3 walkthrough bucket.
Deploy the S3 site with AWS Amplify Hosting

Amplify Hosting publishes the S3 files to a globally available CDN powered by Amazon CloudFront. It also generates the public HTTPS address you can share.

Why Amplify Hosting for This Site?

Amazon S3 stores the walkthrough files. Amplify Hosting handles public HTTPS delivery through a CDN.

This approach avoids managing a public S3 website endpoint. The result is a shareable address backed by Amazon CloudFront.

  • Select the Properties tab in the bucket from earlier.
  • Scroll to Static website hosting.
  • Choose Create Amplify app.
  • Enter blender-scene-walkthrough in the App name field.
  • Enter production in the Branch name field.
  • Confirm that the S3 location shows your unique bucket name.
  • Choose Save and deploy.

Amplify shows deployment progress while it publishes the files to its CDN.

  • Wait for the deployment to reach its completed state.
  • Record the generated public address here: your Amplify HTTPS URL.

Your walkthrough now has a public HTTPS home. The production branch is serving the files from your S3 bucket.

Deployment Not Starting?

  • Confirm that the S3 bucket belongs to the same AWS account as the Amplify app.
  • Confirm that the selected Region is still US East (N. Virginia).
  • Still stuck? Help me troubleshoot my S3 to Amplify Hosting deployment.
Test the public walkthrough

The public test checks asset loading, pointer lock, movement, cursor release, and responsive sizing. Each result proves the deployed copy matches the local walkthrough.

Before you open the public address, do you expect every local control to behave the same way through the CDN?

  • Open your Amplify HTTPS URL in a new Microsoft Edge tab.
  • Wait for the loading state to finish.

You should see Scene ready. The Enter Scene button should become active.

  • Click Enter Scene.
  • Move the mouse to test the viewing direction.
  • Test each movement key in turn: W, A, S, D.
  • Press Esc.
  • Narrow the Microsoft Edge window.

The camera should follow your mouse smoothly. Each movement key should move the camera across the room's XZ plane.

After pressing Esc, you'll see the entry menu again. The HUD should wrap when Microsoft Edge becomes narrow.

You've completed the full journey from Blender room to public browser walkthrough. Anyone with the HTTPS address can enter the gallery.

Public Walkthrough Not Loading?

  • Return to the bucket object list from earlier.
  • Confirm that all four filenames exactly match index.html, styles.css, main.js, and walkthrough.glb.
  • Confirm that the Amplify deployment reached its completed state.
  • Still stuck? Help me diagnose my deployed Blender walkthrough.

Secret mission

Add a Sprint Mode

Your walkthrough currently moves at one steady pace. In this secret mission, you will add Shift-to-sprint controls and a movement-mode indicator that changes between Walking and Sprinting.

Clean Up Your Resources

Clean Up Your Resources

Your local files have no ongoing cost. The public walkthrough uses Amazon S3 plus AWS Amplify Hosting. Decide whether to keep the deployment available, pause public access, or delete every project resource.

Cost warning

Eligible AWS Free Tier usage includes up to 5 GB stored on the CDN plus 15 GB of data transfer out per month. Usage beyond your allowance or credits is billed.

Without Free Tier coverage, storage is $0.023 per GB per month. Data transfer out is $0.15 per GB served. Choose Pause or Delete below if you do not want ongoing usage.

Resources you used:

  • A local blender-scene-walkthrough folder containing index.html, styles.css, main.js, and walkthrough.glb.
  • A local Live Server session serving the walkthrough.
  • A uniquely named general purpose Amazon S3 bucket containing the deployment files.
  • An AWS Amplify Hosting app named blender-scene-walkthrough with a public HTTPS deployment.

Keep everything running

No action needed. Choose this if you want the walkthrough to remain available through its public HTTPS URL.

  • Keep the blender-scene-walkthrough Amplify app deployed.
  • Keep the Amazon S3 bucket available as the deployment source.
  • Monitor your AWS usage to stay within your allowance or credits.
  • Stop the local Live Server session when you finish testing.

Your public URL remains available. The local blender-scene-walkthrough folder stays ready for future updates.

Pause - I'll come back to this later

Pause public access by deleting the Amplify app. Your Amazon S3 bucket keeps the deployment files so you can publish them again later.

  • Return to the AWS Management Console.
  • Open AWS Amplify Hosting.
  • Select the blender-scene-walkthrough app.
  • Select General settings under App settings.
  • Choose Delete app.
  • Enter delete in the confirmation field.
  • Choose Delete app.

Your public HTTPS URL stops serving the walkthrough. The Amazon S3 bucket remains in your account and can still incur storage charges.

  • Return to Visual Studio Code.
  • Select the Live Server status control in the status bar to stop the local server.

Your local files remain intact. You can restart Live Server whenever you return to the project.

Delete - I don't want to use this again

Deleting these resources is permanent. Your public URL stops working after the Amplify app is removed.

  • Return to the AWS Management Console.
  • Open AWS Amplify Hosting.
  • Select the blender-scene-walkthrough app.
  • Select General settings under App settings.
  • Choose Delete app.
  • Enter delete in the confirmation field.
  • Choose Delete app.

The Amplify app is now removed. Next, empty the Amazon S3 bucket before deleting it.

  • Open Amazon S3.
  • Select General purpose buckets.
  • Select the bucket containing your walkthrough files.
  • Choose Empty.
  • Enter the bucket name shown in the confirmation field.
  • Choose Empty.

The bucket now contains no deployment files. You can delete the empty bucket.

  • Select the empty walkthrough bucket.
  • Choose Delete.
  • Enter the bucket name shown in the confirmation field.
  • Choose Delete bucket.

Your AWS deployment resources are now removed. Removing the local folder also permanently deletes the web files plus walkthrough.glb.

  • Return to Visual Studio Code.
  • Select the Live Server status control in the status bar to stop the local server.
  • Delete the local blender-scene-walkthrough folder by running this command in the integrated terminal:
cmd /c "cd .. && rmdir /s /q blender-scene-walkthrough"

What does this command do?

  • The command starts a temporary Windows Command Prompt from your current terminal.
  • It moves to the parent location before permanently removing the blender-scene-walkthrough folder plus everything inside it.
  • Check the folder's parent location in Windows File Explorer.

You should no longer see the blender-scene-walkthrough folder.

Folder still present?

Make sure Live Server has stopped. Close any terminal process that is still using the project folder before running the deletion command again.

If the folder still remains, help me remove the local walkthrough folder safely.

Nice Work!

Nice Work!

You made it! Your Blender gallery now runs as a navigable browser walkthrough with a public HTTPS address.

You've learned how to:

  • Create a browser-ready gallery in Blender. Export the complete room as one glTF Binary asset called walkthrough.glb.
  • Turn a fixed Three.js view into a first-person walkthrough with pointer lock. Use keyboard state plus delta-time movement to keep WASD navigation smooth.
  • Publish a public HTTPS walkthrough from Amazon S3 through AWS Amplify Hosting. Your scene is delivered through a CDN for visitors outside your local setup.
  • Complete the optional Secret Mission by adding a Shift-to-sprint mode. The HUD now switches between Walking and Sprinting as the movement speed changes.

Ready to quiz yourself?