three.js - Import Models

  • Formats - many 3D models formats, each one responding a problem
  • Popular 3D Models Formats
    • OBJ 简单、高效
    • FBX
    • STL
    • PLY
    • COLLADA
    • 3DS
    • GLTF 目前使用最多的
  • GLTF
    • a format is becoming a standard and should cover most of our needs
    • it's for GL transmission format
    • it supports different sets of data like geometries, materials, cameras, lights, scene graph, animations, etc.
    • you can export various formats like json, binary, embed texture, etc.
    • most 3D softwares, game engines and libraries support it
    • the gltf team provides various models glTF-Sample-Models
  • GLTF Formats - a GLTF file can have different formats
    • 我们可以参考这个 Duck Model,对应了解一下以下几种主要格式
    1. GLTF
    • the default format
    • .gltf - is a JSON that contains cameras, lights, scenes, materials, objects transformations, but no geometries nor textures
    • .bin - is a binary that usually contains the data like geometries(vertices position, UV coordinates, colors, etc.)
    • .png - is the texture
    • we load the .gltf file and the other files will be loaded automatically
    1. GLTF-BINARY
    • only one file
    • contains the all the data we talk about
    • binary
    • usually lighter
    • easier to load because only one file
    • hard to alter its data
    1. GLTF-DRACO
    • like the gltf default format, but the buffer data is compressed using the Draco algorithm
    • much lighter
    1. GLTF-EMBEDDED
    • one file can read
    • JSON
    • heavier
  • Set up 场景配置(plane + lights),axesHelper非必选
    • 当我们导入一个gltf文件时,gltf会有真实的model,这些model通常会是 PBR material,PBR material 将会在three.js中创建 MeshStandardMaterial,所以我们需要 lights
  import * as THREE from 'three'
  import {OrbitControls} from 'three/addons/controls/OrbitControls.js'

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

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

  /**
   * light
  */
  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, 0)
  scene.add(directionalLight)

  /**
   * camera
  */
  const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    100
  )
  camera.position.set(2, 2, 2)

  /**
   * renderer
  */
  const renderer = new THREE.WebGLRenderer()
  renderer.shadowMap.enabled = true
  renderer.shadowMap.type = THREE.PCFSoftShadowMap
  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) 
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))   
  })

  /**
   * axesHelper
  */
  const axesHelper = new THREE.AxesHelper(5)
  scene.add(axesHelper)

  /**
   * control
  */
  const controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true

  /**
   * render
  */
  const clock = new THREE.Clock()
  const tick = () => {
    let elapsedTime = clock.getElapsedTime()

    controls.update()
    requestAnimationFrame(tick)
    renderer.render(scene, camera)
  }
  tick()
set up.png
  • Load the model in three.js, use the GLTFLoader
    • 这里用的是一个 Duck Model 作为例子展示,内置参数的结构可参考下图
    • the Mesh should be our duck
    • we don't need the PerspectiveCamera
    • the camera and the duck are in an Object3D
    • the Object3D has a scale set to small value
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

  /**
   * scene
  */
  ...

  /**
   * model
  */
  const gltfLoder = new GLTFLoader()
  gltfLoder.load(
    '../public/models/Duck/glTF/Duck.gltf',
    (gltf) => {
      // success
      console.log(gltf);
    }
  )
gltf 结构.png
  • There are many ways can add the duck to our scene
    • add the whole scene in our scene
    • add the children of the scene to our scene and ignore the PerspectiveCamera
    • filter the children before adding to the scene
    • add only the Mesh and end up with a duck with a wrong scale, position and rotation
    • open the file at a 3D software clean it and export again
  • Add the Object3D to our scene and ignore the unused PerspectiveCamera
    1. mesh 结构较为单一,例如上面的 Duck Model
    // duck
    gltfLoader.load(
      '../public/models/Duck/glTF/Duck.gltf',
      (gltf) => {
        // console.log(gltf.scene);
    
        scene.add(gltf.scene.children[0])
      }
    )
    
    Duck model.png
    1. mesh 结构复杂的,也就是有多个mesh组成的 FlightHelmet Model
    // flightHelmet
    gltfLoader.load(
      '../public/models/FlightHelmet/glTF/FlightHelmet.gltf',
      (gltf) => {
        // console.log(gltf.scene);
    
        // 不可用,取值过程会移动原数组数据,导致循环混乱
        // for (const child of gltf.scene.children) {  
        //   scene.add(child)
        // }
    
        // 可用
        // while(gltf.scene.children.length) {
        //   scene.add(gltf.scene.children[0])
        // }
    
        const children = [...gltf.scene.children]
        for (const child of children) {
          scene.add(child)
        }
      }
    )
    
    FlightHelmet结构.png

    FlightHelmet model.png
  • Draco Compression
    • the draco verion can be lighter than the default version(gltf)
    • compression is applied to the buffer data(typically the geometry)
    • draco is not exclusive to gltf but they got the popular at the same time and implementation went faster with gltf exports
    • Take the /draco/ folder and copy it to your statistic floder (node_modules/three/examples/jsm/libs/draco)
    • when we use the DRACOLoader, the geometries are lighter but has to load the DRACOLoader and the decoder(上一步,我们需要移动至public文件夹的部分)
  import {DRACOLoader} from 'three/addons/loaders/DRACOLoader.js'

  /**
   * model
   * **/
  const dracoLoader = new DRACOLoader()  // 必须写在实例化gltfLoader之前
  dracoLoader.setDecoderPath('/public/draco/')  // 根据自己项目的结构 provide the path to our dracoLoader

  const gltfLoader = new GLTFLoader()
  gltfLoader.setDRACOLoader(dracoLoader)  // provide the dracoLoader instance to gltfLoader

  // duck
  gltfLoader.load(
    '../public/models/Duck/glTF-Draco/Duck.gltf',
    (gltf) => {  
      scene.add(gltf.scene.children[0])
    }
  )
Draco loader.png
  • Animations - gltf can load animation Fox Model
    • to load a gltf object contains a animations property composed of multiple AnimationClip - 可以理解为关键帧的合集
    • we need to create an AnimationMixer, it's like a player 播放器
    • add on of the AnimationClips to the mixer and then call the play()
    • update the mixer on each frame
      animation.png
  /**
   * model
   */
  ...
  let mixer = null
  // fox
  gltfLoader.load(
    '../public/models/Fox/glTF/Fox.gltf',
    (gltf) => {
      // console.log(gltf);

      gltf.scene.scale.set(0.025, 0.025, 0.025)
      scene.add(gltf.scene)

      // animation
      mixer = new THREE.AnimationMixer(gltf.scene)
      const action = mixer.clipAction(gltf.animations[0])
      // console.log(action);
      action.play()
    }
  )
/**
 * render
 */
const clock = new THREE.Clock()
let previousTime = 0
const tick = () => {
  const elapsedTime = clock.getElapsedTime()
  const deltaTime = elapsedTime - previousTime
  previousTime = elapsedTime

  // update mixer
  mixer && mixer.update(deltaTime)

  ...
}
tick()
animation fox model.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容