GLTF DRACO
Video Lecture
Description
The DRACO loader is used to load geometry compressed with the Draco library.
Draco is an open source library for compressing and decompressing 3D meshes and point clouds.
glTF files can also be compressed using the DRACO library, and they can also be loaded using the glTF loader.
We can configure the glTF
loader to use the DRACOLoader
to decompress the file in such cases.
In this lesson, we will compress the Eve model from the last lesson using the glTF Transform CLI tool.
Install it
npm install --global @gltf-transform/cli
List the usage help
Now compress your model using the most common optimizations.
gltf-transform optimize <input> <output> --compress draco --texture-compress webp
If using PowerShell, add the .cmd
suffix.
gltf-transform.cmd optimize <input> <output> --compress draco --texture-compress webp
Example usage for the file named eve$@walk.glb
.
gltf-transform.cmd optimize '.\public\models\eve$@walk.glb' '.\public\models\eve$@walk_compressed.glb' --compress draco --texture-compress webp
In our script, we can import the DRACOLoader class.
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
We then instantiate using
const draco = new DRACOLoader()
The DRACOLoader, will then create a worker process that imports and executes the actual decoder scripts.
Since the Draco scripts will run in their own worker process, they cannot be bundled. They will need to be loaded into the web browser at runtime.
We can either direct the web browser to download the worker process components from a CDN, or host them ourselves somewhere under the ./public
folder.
Using a CDN,
draco.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/')
or hosting locally,
dracoLoader.setDecoderPath('jsm/libs/draco/') // loading from own webserver
If hosting locally, be sure to copy all the files from ./node_modules/three/examples/jsm/libs/draco/
to ./public/jsm/libs/draco/
.
Now a Caveat, compressing a file doesn't necessarily mean that the file will be presented in the scene faster. While compressed data can result in a significantly smaller file size, the users web browser will use more CPU will and time while decoding the file. Not only that, the extra components used by the Draco loader worker process to decompress, are also needed to be downloaded at runtime.
See below example showing that the compressed file appears later in the scene than the uncompressed version.
All files and applications are different, you will need to compare whether using Draco compression or not, will benefit your application.
Compressed (DRACO)
|
Uncompressed
|
Resources
The 3D model and animations used in this lesson can easily be downloaded from Mixamo and converted using Blender. If you don't want to download and convert, then you can download the converted files using the link below. Extract the contents of the zip file into your ./public/models/
folder.
gltf-draco.zip
Lesson Script
./src/main.ts
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199 | import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
//import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
import Stats from 'three/addons/libs/stats.module.js'
class CharacterController {
keyMap: { [key: string]: boolean } = {}
wait = false
animationActions: { [key: string]: THREE.AnimationAction }
activeAction = ''
speed = 0
constructor(animationActions: { [key: string]: THREE.AnimationAction }) {
this.animationActions = animationActions
document.addEventListener('keydown', this.onDocumentKey)
document.addEventListener('keyup', this.onDocumentKey)
}
onDocumentKey = (e: KeyboardEvent) => {
this.keyMap[e.code] = e.type === 'keydown'
}
dispose() {
document.removeEventListener('keydown', this.onDocumentKey)
document.removeEventListener('keyup', this.onDocumentKey)
}
setAction(action: string) {
if (this.activeAction != action) {
this.animationActions[this.activeAction].fadeOut(0.25)
this.animationActions[action].reset().fadeIn(0.25).play()
this.activeAction = action
switch (action) {
case 'walk':
this.speed = 1
break
case 'run':
case 'jump':
this.speed = 4
break
case 'pose':
case 'idle':
this.speed = 0
break
}
}
}
update() {
if (!this.wait) {
let actionAssigned = false
if (this.keyMap['Space']) {
this.setAction('jump')
actionAssigned = true
this.wait = true // blocks further actions until jump is finished
setTimeout(() => (this.wait = false), 1000)
}
if (!actionAssigned && this.keyMap['KeyW'] && this.keyMap['ShiftLeft']) {
this.setAction('run')
actionAssigned = true
}
if (!actionAssigned && this.keyMap['KeyW']) {
this.setAction('walk')
actionAssigned = true
}
if (!actionAssigned && this.keyMap['KeyQ']) {
this.setAction('pose')
actionAssigned = true
}
!actionAssigned && this.setAction('idle')
}
}
}
class Grid {
gridHelper = new THREE.GridHelper(100, 100)
speed = 0
constructor(scene: THREE.Scene) {
scene.add(this.gridHelper)
}
lerp(from: number, to: number, speed: number) {
const amount = (1 - speed) * from + speed * to
return Math.abs(from - to) < 0.001 ? to : amount
}
update(delta: number, toSpeed: number) {
this.speed = this.lerp(this.speed, toSpeed, delta * 10)
this.gridHelper.position.z -= this.speed * delta
this.gridHelper.position.z = this.gridHelper.position.z % 10
}
}
const scene = new THREE.Scene()
new RGBELoader().load('img/venice_sunset_1k.hdr', (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping
scene.environment = texture
scene.background = texture
scene.backgroundBlurriness = 1
})
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100)
camera.position.set(0.1, 1, 1)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.target.set(0, 0.75, 0)
const stats = new Stats()
document.body.appendChild(stats.dom)
let mixer: THREE.AnimationMixer
let animationActions: { [key: string]: THREE.AnimationAction } = {}
const characterController = new CharacterController(animationActions)
const grid = new Grid(scene)
//const dracoLoader = new DRACOLoader()
//dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/') // loading from a CDN
//dracoLoader.setDecoderPath('jsm/libs/draco/') // loading from own webserver
const glTFLoader = new GLTFLoader()
//glTFLoader.setDRACOLoader(dracoLoader)
glTFLoader.load('models/eve$@walk_compressed.glb', (gltf) => {
mixer = new THREE.AnimationMixer(gltf.scene)
mixer.clipAction(gltf.animations[0]).play()
scene.add(gltf.scene)
})
// async function loadEve() {
// const [eve, idle, run, jump, pose] = await Promise.all([
// glTFLoader.loadAsync('models/eve$@walk_compressed.glb'),
// glTFLoader.loadAsync('models/eve@idle.glb'),
// glTFLoader.loadAsync('models/eve@run.glb'),
// glTFLoader.loadAsync('models/eve@jump.glb'),
// glTFLoader.loadAsync('models/eve@pose.glb')
// ])
// mixer = new THREE.AnimationMixer(eve.scene)
// animationActions['idle'] = mixer.clipAction(idle.animations[0])
// animationActions['walk'] = mixer.clipAction(eve.animations[0])
// animationActions['run'] = mixer.clipAction(run.animations[0])
// animationActions['jump'] = mixer.clipAction(jump.animations[0])
// animationActions['pose'] = mixer.clipAction(pose.animations[0])
// animationActions['idle'].play()
// characterController.activeAction = 'idle'
// scene.add(eve.scene)
// }
// await loadEve()
const clock = new THREE.Clock()
let delta = 0
function animate() {
requestAnimationFrame(animate)
delta = clock.getDelta()
controls.update()
//characterController.update()
mixer && mixer.update(delta)
grid.update(delta, characterController.speed)
renderer.render(scene, camera)
stats.update()
}
animate()
|
Troubleshooting
Q. Error : gltf-transform.ps1 cannot be loaded because running scripts is disabled on this system
You try to run gltf-transform
from the command line, but you get the error,
gltf-transform.ps1 cannot be loaded because running scripts is disabled on this system
A. You are using PowerShell. You can add the .cmd
suffix to the command.
E.g.,
gltf-transform.cmd --help
Useful Links
glTF-Transform
DRACO 3D Data Compression
DRACOLoader (Official Documentation)
WebAssembly (MDN)
WebAssembly (Official)