Three.js offline GLTF character showcase
When to use
- User pastes a Three.js HTML that is broken (uses removed
examples/js/global scripts) or shows a white screen. - User provides a
.gltf/.glb/.zipmodel and wants it shown as a 3D character. - Any task where Three.js must run in a preview that cannot reach a CDN.
Core rules (learned the hard way)
- Never rely on a CDN in preview.
examples/js/were REMOVED after r148 (404). Always vendor Three.js locally. file://cannot load ES modules (CORS). You MUST serve viahttp://localhost(e.g.python3 -m http.server 8123).- Always add error resilience so a failure is never a silent white screen:
- globalwindow.error+unhandledrejectionlisteners
- a visible<div id="err">overlay that shows the message
- a WebGL preflight check before building the scene - Match vendored addon version to the core
three.module.jsREVISION (check withgrep -o "REVISION = '..*'" vendor/three.module.js).
Setup: vendor Three.js r160 locally
Download into vendor/ from unpkg (agent env has internet; the preview does not):
vendor/three.module.js # https://unpkg.com/three@0.160.0/build/three.module.js
vendor/jsm/controls/OrbitControls.js
vendor/jsm/postprocessing/{EffectComposer,RenderPass,UnrealBloomPass,ShaderPass,MaskPass,Pass}.js
vendor/jsm/shaders/{CopyShader,LuminosityHighPassShader}.js
vendor/jsm/loaders/GLTFLoader.js # for models
vendor/jsm/utils/BufferGeometryUtils.js # GLTFLoader's relative dep (imports from 'three' only)
vendor/jsm/environments/RoomEnvironment.js # for PBR metal reflections (imports from 'three' only)
importmap:
<script type="importmap">{ "imports": {
"three": "./vendor/three.module.js",
"three/addons/": "./vendor/jsm/"
} }</script>
Import addons as three/addons/loaders/GLTFLoader.js etc. GLTFLoader internally imports '../utils/BufferGeometryUtils.js' — that file MUST exist at vendor/jsm/utils/.
Loading a GLTF model (auto-fit)
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
// PBR env so metallic armor isn't black:
scene.environment = new THREE.PMREMGenerator(renderer).fromScene(new RoomEnvironment(), 0.04).texture;
const character = new THREE.Group(); character.position.y = H/2; scene.add(character);
new GLTFLoader().load('./model/scene.gltf', (gltf) => {
const model = gltf.scene;
let box = new THREE.Box3().setFromObject(model);
let size = box.getSize(new THREE.Vector3()), center = box.getCenter(new THREE.Vector3());
if (size.y < size.z * 0.7) { model.rotation.x = Math.PI/2; model.updateMatrixWorld(true);
box = new THREE.Box3().setFromObject(model); size = box.getSize(new THREE.Vector3()); center = box.getCenter(new THREE.Vector3()); }
model.position.sub(center); // center at origin
character.add(model);
character.scale.setScalar(TARGET_HEIGHT / size.y); // TARGET_HEIGHT ~ 3.2
model.traverse(o => { if (o.isMesh) o.material.envMapIntensity = 1.1; });
}, (e)=>{ if(e.total) setProgress(e.loaded/e.total); }, (err)=>showErr('模型加载失败: '+err.message));
- Sketchfab exports often have a double Z-flip root matrix — GLTFLoader applies node matrices, so the model is normally upright already; the
size.y < size.z*0.7guard handles the rare lie-flat case. - Non-Draco, non-KTX2, non-meshopt models need NO extra decoder. Only add DRACOLoader/KTX2Loader if the glTF
extensionsRequiredlists them. - Put model files (
scene.gltf+scene.bin+textures/) under the served dir so relative URIs resolve.
Magic-effects layer (keep it for the "wow" factor)
- 4
TorusGeometryrings,MeshBasicMaterial+AdditiveBlending+depthWrite:false, each with its own tilt/speed. - A
CanvasTexturerune circle on a groundPlaneGeometry(rotated-PI/2). THREE.Pointsstarfield (600 pts) with a radial-gradientCanvasTexturesprite.- A
Spritehand-glow orb, pulsing scale. EffectComposer+RenderPass+UnrealBloomPass(res, 1.7, 0.55, 0.12);renderer.toneMapping = ACESFilmicToneMapping, exposure ~1.15.controls.autoRotate = truefor the orbit; gently floatcharacter.position.y = H/2 + sin(t)*0.08.
Verify
Start server, then curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8123/<file>.html AND curl EVERY dependency (three.module.js, each jsm file, model files) — all must be 200. Cannot run a headless browser here, so 200 on every asset + a clean import graph is the bar.