ThreeJsViewer Guide
(This example was written in june 2024)
This page provides an step by step guide of how to work with the ThreeJsViewer in a project to set up a simple product viewer with orbit controls. It's a lot of code, but you can basically copy and paste it into your project and adjust it to your needs.
File structure
The file structure of the demo project looks like this:
project-root
├── models
│ └── generic
│ ├── textures
│ ├── scene.bin
│ └── scene.gltf
├── viewers
│ ├── GenericViewer.js
│ └── GenericModel.js
├── GltfModel.js
├── ThreeJsViewer.js
├── ThreeJsViewerCamera.js
├── index.html
├── main.js
└── README.md
Defining the html element and the viewer
In your html file, define a div element with an id that will be used to render the viewer.
<!-- index.html -->
<div id="product-viewer"></div>
In your javascript file, create a new instance of the ThreeJsViewer class and pass the element. In this case our custom viewer class is called ProductViewer. We'll make this class in a next step.
// main.js
const productViewer = new ProductViewer({
$el: document.querySelector('#product-viewer'),
debug: false,
transparentBackground: false,
qualityPreset: 'high-performance',
});
Set camera properties
The camera properties are passed to the ProductViewer in the camera object. The properties are the same as the ones in the ThreeJsViewerCamera class.
camera: {
fov: 35,
near: 0.1,
far: 100,
},
Limit orbitControls
The orbitControlProps object can be used to limit the orbitControls. The properties are the same as the ones in the OrbitControls class. We use these ones to prevent the camera from disappearing under the ground plane.
camera: {
orbitControlProps: {
minPolarAngle: 0,
maxPolarAngle: Math.PI / 1.8,
minDistance: 7,
maxDistance: 7,
enablePan: false,
},
}
Creating a custom viewer class
The ProductViewer class extends the ThreeJsViewer class. In the constructor, we define the entities that will be added to the scene. In this case, we add a GenericModel instance. We also add an ambient light and a directional light to the scene.
Add entities
The entities you want in the scene are defined in args.entities. In this case, we add a GenericModel instance. We'll make this class in a next step.
After defining the entities, we call the super constructor with the args object. This will create the scene, renderer, and camera.
Add lights
We add an ambient light and a directional light to the scene. The ambient light has a color of white and an intensity of 0. The directional light has a color of white and an intensity of 1.2. We set the position of the light and the target of the light. We also set the name of the light object.
Warning
Setting a name of a light is somehow not possible during construction. We have to add the light to the scene, find the light object by its uuid and set the name afterwards.
Set the camera position
We set the initial camera position of the cameraManager in the ProductViewer class. The camera is positioned at x: 8, y: 2, z: 8.
Shadows
Directional can cast shadows. by setting castShadow to true and defining the height and width of the shadow map. The shadow map size can be set using the getShadowMapSize method. The method returns the shadow map size based on the quality preset.
// GenericViewer.js
import GenericModel from "../GenericModel.js";
import * as THREE from "three";
import ThreeJsViewer from "../ThreeJsViewer.js";
export default class GenericViewer extends ThreeJsViewer {
constructor(args = {}) {
args.entities = [
new GenericModel({
name: "phone",
url: "models/generic/scene.gltf",
modelScale: 0.2,
position: {
x: 0,
y: 0,
z: 0,
},
rotation: {
x: 0,
y: -Math.PI / 2,
z: 0,
},
debug: args.debug,
}),
// other models can be added here
];
super(args);
let light;
let temp;
// Ambient light
light = new THREE.AmbientLight(0xffffff, 0);
temp = light.uuid;
this._scene.add(light);
this._scene.getObjectByProperty("uuid", temp).name = "AmbientLight";
// Directional light
light = new THREE.DirectionalLight(0xffffff, 1.2);
light.position.set(5, 3, 0);
light.target.position.set(0, 0, 0);
temp = light.uuid;
this._scene.add(light);
this._scene.getObjectByProperty("uuid", temp).name = "WhiteLightRight";
this._scene.background = new THREE.Color(0xffffff);
// set the initial camera position
this.cameraManager.setPosition(8, 2, 8);
// add the entities to the scene
this.addEntities(args);
// draw the axes of the scene if debug mode is enabled
if (args.debug) {
this._scene.add(new THREE.AxesHelper(5));
}
}
addEntities(args) {
if (!args.entities) {
console.log("No entities defined");
return;
}
args.entities.forEach((temp) => {
this._scene.add(temp);
});
}
tick() {
super.tick();
}
getShadowMapSize(preset) {
switch (preset) {
case "low-power":
return 512;
case "default":
return 1024;
case "high-performance":
return 2048;
default:
return 1024;
}
}
}
Creating a custom model class
This class extends from the GltfModel class. In the constructor, we call the super constructor with the args object. We set the position and scale of the model.
The GltfModel class will load the model and process its materials automatically.
In case we are not happy with how the standard materials from the gltf file look, or in case we need to make some changes. We can pass a customMaterials object to the load method. This object maps the name of the material from the gltf, to a Three.js material. Usually these names will be clear, unless the gltf file is not made by us.
Lastly, we set the position and scale of the model to the values passed in the args object.
// GltfModel.js
import * as THREE from "three";
import GltfModel from "./GltfModel";
import _defaultsDeep from "lodash/defaultsDeep";
const DEFAULTS = {
position: { x: 0, y: 0, z: 0 },
modelScale: 1,
};
export default class GenericModel extends GltfModel {
constructor(args = {}) {
super(args);
this.load({
customMaterials: {
"Some_Material": new THREE.MeshStandardMaterial({
color: 0xff5550,
metalness: 0.75,
roughness: 0.3,
}),
},
..._defaultsDeep(args, DEFAULTS),
});
this.position.set(args.position.x, args.position.y, args.position.z);
this.scale.set(args.modelScale, args.modelScale, args.modelScale);
}
}