Three.js - Import Models: Unterschied zwischen den Versionen

Aus Wikizone
Wechseln zu: Navigation, Suche
 
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt)
Zeile 144: Zeile 144:
 
=== Draco Compression ===
 
=== Draco Compression ===
 
* Benötigt den Dracoloader
 
* Benötigt den Dracoloader
 +
* Bessere Performance mit der Webassembly / Walker Version (eigener Thread, assembliert)
 +
** einfach aus den Examples den Draco Folder kopieren, wenn du in Webpack unterwegs bist und keine Automatik hast.
 
* As we saw when browsing the files, the Draco version can be much lighter than the default version. Compression is applied to the buffer data (typically the geometry). It doesn't matter if you are using the default glTF, the binary glTF or the embedded glTF.
 
* As we saw when browsing the files, the Draco version can be much lighter than the default version. Compression is applied to the buffer data (typically the geometry). It doesn't matter if you are using the default glTF, the binary glTF or the embedded glTF.
 
* It's not even exclusive to glTF, and you can use it with other formats. But both glTF and Draco got popular simultaneously, so the implementation went faster with glTF exporters.
 
* It's not even exclusive to glTF, and you can use it with other formats. But both glTF and Draco got popular simultaneously, so the implementation went faster with glTF exporters.
 
* Google develops the algorithm under the open-source Apache License
 
* Google develops the algorithm under the open-source Apache License
 +
<syntaxhighlight lang="javascript">
 +
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
 +
//...
 +
// create a dracoLoader and set path so it can find webassembly version
 +
const dracoLoader = new DRACOLoader()
 +
dracoLoader.setDecoderPath("/draco/") // get webassembly version (faster)
 +
// use normal loader
 +
const gltfLoader = new GLTFLoader()
 +
// setDracoLoader and three.js will use it (will only loaded when nessecary)
 +
gltfLoader.setDRACOLoader(dracoLoader)
 +
gltfLoader.load(
 +
    '/models/Fox/glTF/Fox.gltf',
 +
    (gltf) => {
 +
        scene.add(gltf.scene)
 +
    },
 +
    () => {console.log('progress')},
 +
    (error) => {console.log(error)}
 +
)
 +
</syntaxhighlight>
  
== Import Animations - komplettes Beispiel ==
+
== Import Animations ==
 +
<syntaxhighlight lang="javascript">
 +
//...
 +
 
 +
// Animation Mixer - funktioniert wie ein Player für Animationen
 +
// Außerhalb des .load Callback anlegen, damit er in der tick() zur Verfügung steht
 +
let mixer = null
 +
 
 +
//...
 +
 
 +
/**
 +
* Models
 +
* */
 +
 
 +
const gltfLoader = new GLTFLoader()
 +
gltfLoader.load(
 +
    '/models/Fox/glTF/Fox.gltf',
 +
    (gltf) => {
 +
        // mixer erstellen und scene übergeben
 +
        mixer = new THREE.AnimationMixer(gltf.scene)
 +
        // in mixer Animation auswählen > gibt action zurück
 +
        const action = mixer.clipAction(gltf.animations[0])
 +
        // action kann abgespielt werden benötigt aber zeitbasierte updates
 +
        action.play()
 +
        console.log(action)
 +
        scene.add(gltf.scene)
 +
    }
 +
)
 +
// ...
 +
 
 +
/**
 +
* Animate
 +
*/
 +
const clock = new THREE.Clock()
 +
let previousTime = 0
 +
 
 +
const tick = () =>
 +
{
 +
    const elapsedTime = clock.getElapsedTime()
 +
    const deltaTime = elapsedTime - previousTime
 +
    previousTime = elapsedTime
 +
 
 +
    // Update mixer
 +
 
 +
    // animations need time to load so be sure its anready loaded
 +
    // maybe a on load for the whole tick would be better?
 +
    if(mixer != null){
 +
        mixer.update(deltaTime)
 +
    }
 +
 
 +
    // Update controls
 +
    controls.update()
 +
 
 +
    // Render
 +
    renderer.render(scene, camera)
 +
 
 +
    // Call tick again on the next frame
 +
    window.requestAnimationFrame(tick)
 +
}
 +
 
 +
tick()
 +
</syntaxhighlight>
 +
 
 +
== Komplettes Beispiel ==
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
import './style.css'
 
import './style.css'

Aktuelle Version vom 5. Januar 2022, 15:41 Uhr

Wie importiert man Models in Three.js und welche Dateiformate sind sinnvoll?

Quickstart[Bearbeiten]

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
//...
const gltfLoader = new GLTFLoader()
gltfLoader.load(
    '/models/Duck/glTF/Duck.gltf',
    () => {console.log('success')},
    () => {console.log('progress')},
    (error) => {console.log(error)}
)

Links[Bearbeiten]

ThreeJS - Snippets
https://threejs-journey.com/lessons/23 - englischsprachige Teile in diesem Artikel und Beispiele

Others

https://en.wikipedia.org/wiki/List_of_file_formats#3D_graphics Formats (wiki)
https://threejs.org/editor/ Three.js editor

GLTF sample models

https://github.com/KhronosGroup/glTF-Sample-Models Repository
https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Duck Duck
https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Fox Fox

Draco

https://github.com/google/draco Repository
https://google.github.io/draco/ Website

Three.js documentation

https://threejs.org/docs/#api/en/objects/Mesh Mesh
https://threejs.org/docs/#api/en/materials/MeshStandardMaterial MeshStandardMaterial
https://threejs.org/docs/#api/en/lights/AmbientLight AmbientLight
https://threejs.org/docs/#api/en/lights/DirectionalLight DirectionalLight
https://threejs.org/docs/#examples/en/loaders/GLTFLoader GLTFLoader
https://threejs.org/docs/index.html#api/en/loaders/TextureLoader TextureLoader
https://threejs.org/docs/#api/en/loaders/managers/LoadingManager LoadingManager
https://threejs.org/docs/#api/en/objects/Group Group
https://threejs.org/docs/#api/en/core/Object3D Object3D
https://threejs.org/docs/#api/en/cameras/PerspectiveCamera PerspectiveCamera
https://threejs.org/docs/#examples/en/loaders/DRACOLoader DracoLoader
https://threejs.org/docs/#api/en/objects/Bone Bone
https://threejs.org/docs/#api/en/objects/SkinnedMesh SkinnedMesh
https://threejs.org/docs/#api/en/animation/AnimationClip AnimationClip
https://threejs.org/docs/#api/en/animation/AnimationMixer AnimationMixer
https://threejs.org/docs/#api/en/animation/AnimationAction AnimationAction

Tipps[Bearbeiten]

  • Nach dem Import immer über Console checken was drin ist.
  • Wichtig Scale und Transform Values checken. Dann weißt du gleich wo du suchen mußt wenn du nichts siehts.

Dateiformate[Bearbeiten]

GLTF[Bearbeiten]

  • Von der Khronos Group (OpenGL, WebGL...) erfüllt viele Zwecke gerade wenn man im Web unterwegs ist.
  • Kann einen Scene Graph mit übernehmen
  • Kann JSON, binary, embeded textures mit einbinden
  • Stand 2021 quasi Standard - funktioniert auch mit Unity, Blender etc.

andere können aber auch sinnvoll sein: obj, effizient - ply klein schnelle dekompression....

Verschiedene Varianten

glTF - Standard, Alle Assets einzeln in einem Ordner
glTF-Binary - alle Assets in einer Binären Datei
glTF-Draco - Eine Datei mit Draco Kompression (sehr klein muss aber entpackt werden)
glTF-Embedded - Alle Dateien aus Standard Base64(?) Kodiert in einer Text-Datei

In den Beispielen nutzen wir gltf

Loader[Bearbeiten]

Add the loaded model to our scene[Bearbeiten]

Wo ist was?

If you look at the object logged in the console, you'll find a lot of elements. The most important part is the scene property because we have only one scene in the exported model.

This scene contains everything we need. But it also includes more. Always start by studying what is available in it and watch the scale property of the different Groups, Object3D, and Mesh.

We get something like this:

THREE.Group: scene
└───Array: children
    └───THREE.Object3D
        └───Array: children
            ├───THREE.PerspectiveCamera
            └───THREE.Mesh

The Mesh should be our duck. We don't really care about the PerspectiveCamera. Both the camera and the duck seem to be in the first and only Object3D in the scene's children array. Even worst, that Object3D has a scale set to a minimal value.

As you can see, it's a little complex even to get our duck, and it's where most beginners get lost.

All we want is to get our duck in the scene. We have multiples ways of doing it:

   Add the whole scene in our scene. We can do that because even if its name is scene, it's in fact a Group.
   Add the children of the scene to our scene and ignore the unused PerspectiveCamera.
   Filter the children before adding to the scene to remove the unwanted objects like the PerspectiveCamera.
   Add only the Mesh but end up with a duck that could be wrongly scaled, positioned or rotated.
   Open the file in a 3D software and remove the PerspectiveCamera then export it again.

Because our model structure is simple, we will add the Object3D to our scene, and ignore the unused PerspectiveCamera inside. In future lessons, we will add the whole scene as one object

gltfLoader.load(
    '/models/Duck/glTF/Duck.gltf',
    (gltf) =>
    {
        scene.add(gltf.scene.children[0])
    }
)


Mehrere Children[Bearbeiten]

Vorsicht wenn es mehrere Children gibt die man alle importieren möchte:

Die erste Lösung funktioniert nicht richtig. Bei jedem Schleifendurchlauf wird das Element aus dem (besonderen) gltf Array entnommen. Das Array verkürzt sich. Der Index der Schleife bleibt aber gleich, daher werden Objekte ausgelassen.

// NOT WORKING
for(const child of gltf.scene.children){ // not working properly
  scene.add(child)
}
// WORKING
while(gltf.scene.children.length){
  scene.add(gltf.scene.children[0])
}
// WORKING TOO
const children = [...gltf.scene.children] // duplicate
for(const child of children){
  scene.add(child)
}
// WORKING TOO
for(let i = gltf.scene.children.length; i > 0; i--){
  scene.add(gltf.scene.children[0])
}

Draco Compression[Bearbeiten]

  • Benötigt den Dracoloader
  • Bessere Performance mit der Webassembly / Walker Version (eigener Thread, assembliert)
    • einfach aus den Examples den Draco Folder kopieren, wenn du in Webpack unterwegs bist und keine Automatik hast.
  • As we saw when browsing the files, the Draco version can be much lighter than the default version. Compression is applied to the buffer data (typically the geometry). It doesn't matter if you are using the default glTF, the binary glTF or the embedded glTF.
  • It's not even exclusive to glTF, and you can use it with other formats. But both glTF and Draco got popular simultaneously, so the implementation went faster with glTF exporters.
  • Google develops the algorithm under the open-source Apache License
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
//...
// create a dracoLoader and set path so it can find webassembly version
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath("/draco/") // get webassembly version (faster)
// use normal loader
const gltfLoader = new GLTFLoader()
// setDracoLoader and three.js will use it (will only loaded when nessecary)
gltfLoader.setDRACOLoader(dracoLoader) 
gltfLoader.load(
    '/models/Fox/glTF/Fox.gltf',
    (gltf) => { 
        scene.add(gltf.scene)
    },
    () => {console.log('progress')},
    (error) => {console.log(error)}
)

Import Animations[Bearbeiten]

//...

// Animation Mixer - funktioniert wie ein Player für Animationen
// Außerhalb des .load Callback anlegen, damit er in der tick() zur Verfügung steht
let mixer = null

//...

/** 
 * Models 
 * */

const gltfLoader = new GLTFLoader()
gltfLoader.load(
    '/models/Fox/glTF/Fox.gltf',
    (gltf) => { 
        // mixer erstellen und scene übergeben
        mixer = new THREE.AnimationMixer(gltf.scene)
        // in mixer Animation auswählen > gibt action zurück
        const action = mixer.clipAction(gltf.animations[0])
        // action kann abgespielt werden benötigt aber zeitbasierte updates
        action.play()
        console.log(action)
        scene.add(gltf.scene)
    }
)
// ...

/**
 * Animate
 */
const clock = new THREE.Clock()
let previousTime = 0

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()
    const deltaTime = elapsedTime - previousTime
    previousTime = elapsedTime

    // Update mixer

    // animations need time to load so be sure its anready loaded
    // maybe a on load for the whole tick would be better?
    if(mixer != null){ 
        mixer.update(deltaTime)
    }

    // Update controls
    controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()

Komplettes Beispiel[Bearbeiten]

import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as dat from 'lil-gui'

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'

// Debug
const gui = new dat.GUI()

// Canvas
const canvas = document.querySelector('canvas.webgl')

// Scene
const scene = new THREE.Scene()

// Animation Mixer
let mixer = null

/** 
 * Models 
 * */

// draco loader will only be loaded if needed by three.js
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath("/draco/") // get webassembly version (faster)

const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load(
    '/models/Fox/glTF/Fox.gltf',
    (gltf) => { 
        console.log(gltf) //success - show what we got

        mixer = new THREE.AnimationMixer(gltf.scene)
        const action = mixer.clipAction(gltf.animations[0])
        action.play()
        console.log(action)

        gltf.scene.scale.set(0.025,0.025,0.025)
        scene.add(gltf.scene)
    },
    () => {console.log('progress')},
    (error) => {console.log(error)}
)


/**
 * Floor
 */
const floor = new THREE.Mesh(
    new THREE.PlaneGeometry(10, 10),
    new THREE.MeshStandardMaterial({
        color: '#444444',
        metalness: 0,
        roughness: 0.5
    })
)
floor.receiveShadow = true
floor.rotation.x = - Math.PI * 0.5
scene.add(floor)

/**
 * Lights
 */
const ambientLight = new THREE.AmbientLight(0xffffff, 0.8)
scene.add(ambientLight)

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6)
directionalLight.castShadow = true
directionalLight.shadow.mapSize.set(1024, 1024)
directionalLight.shadow.camera.far = 15
directionalLight.shadow.camera.left = - 7
directionalLight.shadow.camera.top = 7
directionalLight.shadow.camera.right = 7
directionalLight.shadow.camera.bottom = - 7
directionalLight.position.set(5, 5, 5)
scene.add(directionalLight)

/**
 * Sizes
 */
const sizes = {
    width: window.innerWidth,
    height: window.innerHeight
}

window.addEventListener('resize', () =>
{
    // Update sizes
    sizes.width = window.innerWidth
    sizes.height = window.innerHeight

    // Update camera
    camera.aspect = sizes.width / sizes.height
    camera.updateProjectionMatrix()

    // Update renderer
    renderer.setSize(sizes.width, sizes.height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.set(2, 2, 2)
scene.add(camera)

// Controls
const controls = new OrbitControls(camera, canvas)
controls.target.set(0, 0.75, 0)
controls.enableDamping = true

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas
})
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

/**
 * Animate
 */
const clock = new THREE.Clock()
let previousTime = 0

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()
    const deltaTime = elapsedTime - previousTime
    previousTime = elapsedTime

    // Update mixer
    if(mixer != null){
        mixer.update(deltaTime)
    }
     

    // Update controls
    controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()