Displaying a 3D Model in a Web Page

28 Jul 2024

Files for my basic demo can be found here on GitHub.

This is all new to me.

three.js is a JavaScript 3D library built on top of WebGL.

WebGL is:

a JavaScript API for rendering high-performance interactive 3D and 2D graphics within any compatible web browser without the use of plug-ins. WebGL does so by introducing an API that closely conforms to OpenGL ES 2.0 that can be used in HTML <canvas> elements.

three.js is described as follows (see here):

The aim of the project is to create an easy-to-use, lightweight, cross-browser, general-purpose 3D library. The current builds only include a WebGL renderer but WebGPU (experimental), SVG and CSS3D renderers are also available as addons.

Other libraries and products which may be of interest:

I am not building a game, though. I just want to display, in a web browser, a model which was originally built in a CAD tool. In my case the tool is Vectorworks and the model has been exported from Vectorworks using glTF (Graphics Library Transmission Format), which is:

a royalty-free specification for the efficient transmission and loading of 3D scenes and models by engines and applications.

The model I want to display in my web page is a very simple piece of architecture - a pier (a stone column used to support an arch or roof) similar to those frequently found in churches or cathedrals.

For three.js, I also wanted to install all relevant files locally, using the absolute minimum installation I could actually get to work.

three.js comes with a set of JavaScript files and folders.

The CDNs for the /build and /jsm folders I needed are here:

  • https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js
  • https://cdn.jsdelivr.net/npm/three@<version>/examples/jsm/

The latest version is 0.166.1 (release 166) - so I had to replace <version> in the above URLs with 0.166.1.

See further installation notes.

See also more CDN resources.

To actually download the relevant artifacts, for local use:

For three.module.js or three.module.min.js:

https://github.com/mrdoob/three.js/tree/r166/build

For the related jsm directory:

https://github.com/mrdoob/three.js/tree/r166/examples/jsm

A key point to note here:

tip
In my GitHub repo you will only find a small subset of the three.js library files available to be downloaded from the above links. These GitHub files are the minimum set needed for my basic demo. You may need more files - or you may want all of them - for your specific needs.

The glTF artifacts exported from Vectorworks consisted of two files:

  • pier.gltf
  • pier.bin

To display the model represented by these two files, I created a pier.html page as follows:

HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
    <head>
        <title>Pier Demo</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
            body { margin: 10px; }
            canvas { width: 100%; height: 100% }
        </style>
    </head>
    <body>
        <div id="model_container"></div>
        <script type="importmap">
            {
              "imports": {
                "three": "./build/three.module.js",
                "three/addons/": "./jsm/"
              }
            }
        </script>
        <script type="module" src="pier.js"></script>
    </body>
</html>

I also created a pier.js script as follows - which is where the three.js magic happens:

JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import * as THREE from 'three';

import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
import WebGL from 'three/addons/capabilities/WebGL.js';

function init() {
  // camera perspective:
  camera = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 1, 20000);
  camera.position.set(1, 1, 20);

  // 3D scene:
  scene = new THREE.Scene();

  // renderer:
  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setClearColor(0xEBEBEA);
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 1;
  container.appendChild(renderer.domElement);

  // orbit controller:
  const controls = new OrbitControls(camera, renderer.domElement);

  // lighting:
  const ambientLight = new THREE.AmbientLight(0xcccccc);
  scene.add(ambientLight);

  const directionalLight = new THREE.DirectionalLight(0xffffff);
  directionalLight.position.set(0, 1, 1).normalize();
  scene.add(directionalLight);

  // glTf 2.0 loader:
  const loader = new GLTFLoader().setPath('models/gltf/pier/');
  loader.load('pier.gltf', async function (gltf) {
    const model = gltf.scene;
    // wait until the model can be added to the scene
    // without blocking due to shader compilation
    await renderer.compileAsync(model, camera, scene);
    gltf.scene.scale.set(1, 1, 1);
    gltf.scene.position.x = 0;
    gltf.scene.position.y = 0;
    gltf.scene.position.z = 0;

    scene.add(model);
  });

  window.addEventListener('resize', onWindowResize);
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  render();
}

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

function animate() {
  render();
  requestAnimationFrame(animate);
}

//
// ------ entry point ------
//

let camera, scene, renderer;

// div container for web page:
const container = document.getElementById('model_container');

if (WebGL.isWebGL2Available()) {
  init();
  animate(); // animation loop
} else {
  // graphics card does not support WebGL 2
  // https://get.webgl.org/get-a-webgl-implementation/
  const warning = WebGL.getWebGL2ErrorMessage();
  container.appendChild(warning);
}

All the other artifacts I needed are shown in Git.

The HTML page needs to be run in a web server. To do this, I used:

1
npx serve .

You can read about serve here.

And npx is described here. You get npx by installing Node.js and npm (see here).

The end result I see in my browser at http://localhost:3000/pier:

And I can use my mouse to zoom in and out, and to rotate around the pier:

This is all very minimal - deliberately so.

You can see some far more sophisticated showcase examples - for example, this one.