导入模型
Three.js 提供了很多原始模型,但如果咱们须要更简单的模型,最好应用 3D 软件建模,而后导入到场景中。本节咱们就来学学如何导入一个做好的 3D 模型。
3D 模型的各种格局
3D 模型有各种各样的格局,详情可参考维基百科 List_of_file_formats#3D_graphics。这些格局各有特点。接下来咱们列举一些比拟常见和风行的。
OBJ
FBX
STL
PLY
COLLADA
3DS
GLTF
咱们不会关怀所有的模型。因为 GLTF 模型曾经逐步变为规范,并且能应答绝大部分你遇到的场景。
GLTF
GLTF 是 GL Transmission Format 的缩写。由 Khronos Group 发明(他们还发明了 OpenGL, WebGL, Vulkan, Collada 并且有很多成员在 AMD / ATI, Nvidia, Apple, id Software, Google, Nintendo, etc 公司)。
GLTF 在近些年曾经变得越来越风行。它能够反对各种数据集,你能够在其格局中应用几何体和材质,同时也能够蕴含相机、光照、场景、动画、骨骼等。同时反对各种文件格式,例如 json、二进制 binary、embed texture 嵌入纹理等。
GLTF 曾经成为了实时渲染的规范,并且也正在成为大部分 3D 软件、游戏引擎和库的规范模型。这意味着你能够轻松的在各个环境中纯熟应用它。
但这并不是说 GLTF 能够笼罩所有场景,如果你仅仅是须要一个几何体,那么能够抉择 OBJ、FBX、STL 或 PLY 格局。
寻找一个模型
咱们后续会学习在 Blender 中创立模型,但当初咱们先寻找一个创立好的模型。咱们能够在 GLTF 团队的示例中看到各种各样的模型。链接为 glTF Sample Models。首先咱们动一个简略的小黄鸭模型作为示例开始动手。
GLTF formats
尽管 GLTF 就是一种格局,然而其外部蕴含了其余格局。咱们会发现有很多文件夹,如下图
咱们来说说这些都是什么
glTF
glTF-Binary
glTF-Draco
glTF-Embedded
glTF
glTF 是默认格局。Duck.gltf 是一个 JSON 文件。蕴含了各种信息,蕴含相机、光照、场景、材质等,但没有几何体或纹理贴图。Duck0.bin 是一个二进制文件。通常蕴含了几何体和 UV 贴图坐标、法线坐标等。DuckCM.png 是鸭子的纹理贴图。
当咱们载入 Duck.gltf 时,它会主动载入其余两个文件。
glTF-Binary
蕴含了所有上述的数据,是个二进制文件,不能间接关上。
这个文件格式会更轻量化一些,只有一个文件,也易于载入。但不太不便批改外部的数据。例如你想批改纹理贴图,换一张更压缩的贴图时,就会比拟麻烦,因为这些数据都是被汇合在了一起,同一个二进制文件中。
glTF-Draco
有点像说的第一个格局,不过应用了 Draco algorithm 来压缩几何体的数据。如果你比照 .bin 文件的大小,你就会发现这个会更小一点。
glTF-Embedded
这个格局有点像 glTF-Binary 因为也是只有一个文件。但这个文件是一个 JSON 因而你能够在编辑器里关上。
抉择适合的模型格局
依据不同场景做出不同的抉择才是最优计划。
如果你想批改 textures 或导出的光线坐标,最好抉择第一个默认的 glTF。它还具备别离加载不同文件的劣势,从而进步了加载速度。
如果想要每个模型一个文件,并且不关怀模型内的素材批改,那么二进制 glTF-Binary 更适宜。
在这两种状况下,您都必须决定是否要应用 Draco 压缩,但咱们稍后会介绍这部分。
导入模型的实际
筹备
咱们筹备一个空白的立体、环境光和平行光。初始代码如下:
import * as THREE from ‘three’
import ‘./style.css’
import {OrbitControls} from ‘three/examples/jsm/controls/OrbitControls’
import * as dat from ‘lil-gui’
import stats from ‘../common/stats’
import {listenResize} from ‘../common/utils’
// Canvas
const canvas = document.querySelector(‘#mainCanvas’) as HTMLCanvasElement
// Scene
const scene = new THREE.Scene()
// Gui
const gui = new dat.GUI()
// Size
const sizes = {
width: window.innerWidth,
height: window.innerHeight,
}
// Camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.set(4, 4, 12)
// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
controls.zoomSpeed = 0.3
controls.target = new THREE.Vector3(0, 3, 0)
/**
- Objects
*/
// plane
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(15, 15),
new THREE.MeshStandardMaterial({
color: '#607D8B',
})
)
plane.rotateX(-Math.PI / 2)
plane.receiveShadow = true
scene.add(plane)
/**
- Light
*/
const directionLight = new THREE.DirectionalLight()
directionLight.castShadow = true
directionLight.position.set(5, 5, 6)
directionLight.shadow.camera.near = 1
directionLight.shadow.camera.far = 20
directionLight.shadow.camera.top = 10
directionLight.shadow.camera.right = 10
directionLight.shadow.camera.bottom = -10
directionLight.shadow.camera.left = -10
const directionLightHelper = new THREE.DirectionalLightHelper(directionLight, 2)
directionLightHelper.visible = false
scene.add(directionLightHelper)
const directionalLightCameraHelper = new THREE.CameraHelper(directionLight.shadow.camera)
directionalLightCameraHelper.visible = false
scene.add(directionalLightCameraHelper)
const ambientLight = new THREE.AmbientLight(new THREE.Color(‘#ffffff’), 0.3)
scene.add(ambientLight, directionLight)
// Renderer
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.shadowMap.enabled = true
// Animations
const tick = () => {
stats.begin()
controls.update()
// Render
renderer.render(scene, camera)
stats.end()
requestAnimationFrame(tick)
}
tick()
listenResize(sizes, camera, renderer)
gui.add(directionLightHelper, ‘visible’).name(‘lightHelper visible’)
gui.add(directionalLightCameraHelper, ‘visible’).name(‘lightCameraHelper visible’)
gui.add(controls, ‘autoRotate’)
复制代码
导入模型
import {GLTFLoader} from ‘three/examples/jsm/loaders/GLTFLoader’
// …
/**
- Models
*/
const gltfLoader = new GLTFLoader()
gltfLoader.load(
‘../assets/models/Duck/glTF/Duck.gltf’,
(gltf) => {
console.log('success')
console.log(gltf)
},
(progress) => {
console.log('progress')
console.log(progress)
},
(error) => {
console.log('error')
console.log(error)
},
)
复制代码
能够看到模型曾经被失常载入,接下来让咱们将它增加到场景中吧
增加到场景
能够看到导入的模型 scene 的目录构造大抵如下,能够看到除了模型之外还有很多其余的对象。
THREE.Group: scene
└───Array: children
└───THREE.Object3D
└───Array: children
├───THREE.PerspectiveCamera
└───THREE.Mesh
复制代码
咱们有以下几种形式将模型增加到场景
将模型的整个 scene 增加到咱们的场景里。尽管它的名字是 scene,实际上是一个 Three.Group
将 scene 下的 children 增加到咱们本人的 scene 中,并疏忽用不到的 PerspectiveCamera
过滤 children 的内容,移除掉不须要的对象,如 PerspectiveCamera
仅增加 Mesh 到场景里,但有可能会有谬误的缩放、地位、角度等问题
关上 3D 软件将 PerspectiveCamera 移除,再从新导出模型
因为咱们的模型很简略,因而咱们能够将其返回的 scene.children[0] 整体增加到咱们的 Three 场景中(即上述的第二种形式)。代码如下
/**
- Models
*/
const gltfLoader = new GLTFLoader()
gltfLoader.load(
‘../assets/models/Duck/glTF/Duck.gltf’,
(gltf) => {
console.log('success')
console.log(gltf)
scene.add(gltf.scene.children[0])
},
(progress) => {
console.log('progress')
console.log(progress)
},
(error) => {
console.log('error')
console.log(error)
},
)
复制代码
咱们也能够尝试其余的格局导入,除了 Draco 压缩格局外,其余都失效了,成果如上图。Draco 咱们后续会说的,它须要一个非凡的 loader。
/**
- Models
*/
const gltfLoader = new GLTFLoader()
gltfLoader.load(
// ‘../assets/models/Duck/glTF/Duck.gltf’,
// ‘../assets/models/Duck/glTF-Binary/Duck.glb’,
‘../assets/models/Duck/glTF-Embedded/Duck.gltf’,
(gltf) => {
console.log('success')
console.log(gltf)
scene.add(gltf.scene.children[0])
},
(progress) => {
console.log('progress')
console.log(progress)
},
(error) => {
console.log('error')
console.log(error)
},
)
复制代码
接下来咱们导入 FlightHelmet 飞行员头盔模型
/**
- Models
*/
const gltfLoader = new GLTFLoader()
gltfLoader.load(
‘../assets/models/FlightHelmet/glTF/FlightHelmet.gltf’,
(gltf) => {
console.log('success')
console.log(gltf)
scene.add(gltf.scene.children[0])
},
(progress) => {
console.log('progress')
console.log(progress)
},
(error) => {
console.log('error')
console.log(error)
},
)
复制代码
能够看到只导入了模型的一部分
这是因为这个模型被拆成了多个局部,如果想要残缺导入,咱们须要增加它的父节点,同时再批改一下缩放比例
/**
- Models
*/
const gltfLoader = new GLTFLoader()
gltfLoader.load(
‘../assets/models/FlightHelmet/glTF/FlightHelmet.gltf’,
(gltf) => {
console.log('success')
console.log(gltf)
gltf.scene.scale.set(10, 10, 10)
scene.add(gltf.scene)
},
(progress) => {
console.log('progress')
console.log(progress)
},
(error) => {
console.log('error')
console.log(error)
},
)
复制代码