前言
Three.js 基础概念
Sence 场景
Axes 坐标轴
Camera 摄像机
Mesh 网格
Geometry 几何体
Material 材质
Light 光源
Shadow 阴影
DirectionalLight 平行光源
PointLight 点光源
SpotLight 聚光灯光源
// 遍历子 Mesh 开启阴影
object.traverse(function(child) {
if (child instanceof THREE.Mesh) {
child.castShadow = true
child.receiveShadow = true
}
})
glTF 模型格式
glTF 模型格式介绍
glTF 模型格式文件组成
glTF 模型格式导出
3DS Max Exporter
Maya Exporter
Blender glTF 2.0 Exporter
...
Three.js 使用 glTF 模型
var gltfLoader = new THREE.gltfLoader()
gltfLoader.load('./assets/box.gltf', function(sence) {
var object = scene.gltf // 模型对象
scene.add(object) // 将模型添加到场景中
})
glTF 模型动画
let gltfLoader = new THREE.gltfLoader()
let mixer = null
gltfLoader.load('./assets/box.gltf', function(sence) {
let object = scene.gltf
let animations = sence.animations // 动画数据
if (animations && animations.length) {
mixer = new THREE.AnimationMixer(object) // 对动画进行控制
for (let i = 0; i < animations.length; i++) {
mixer.clipAction(animations[i]).play() // 播放所有动画
}
}
scene.add(object)
})
function update() {
let delta = clock.getDelta(mixer)
mixer.update(delta) // 更新动画片段
}
let tween = new TimelineMax()
tween
.to(box.scale, 1, { // 从 1 缩放至 2,花费 1 秒
x: 2,
y: 2,
z: 2,
ease: Power0.easeInOut, // 速度曲线
onStart: function() { // 监听动画开始 },
onUpdate: function() { // 监听动画过程 },
onComplete: function() { // 监听动画结束 }
})
.to(box.position, 1, { // 缩放结束后,位移 x 至 10,花费 1 秒
x: 10, y: 0, z: 0
})
Draco 3D 模型压缩工具
$ npm install -g gltf-pipeline // 安装 gltf-pipeline 工具
$ gltf-pipeline -i model.gltf -o model.glb // 指定某个 .gltf 文件转为 .glb 格式
// 实例化 loader
let loader = new THREE.GLTFLoader()
// Draco 解码库
THREE.DRACOLoader.setDecoderPath('/examples/js/libs/draco')
loader.setDRACOLoader(new THREE.DRACOLoader())
// 加载 glTF 模型
loader.load('models/gltf/box.gltf', function(gltf) {
scene.add(gltf.scene)
})
Cannon.js 3D 物理引擎
Cannon.js 的特性
使用 Cannon.js
let world = new CANNON.World()
world.gravity.set(0, -10, 0)
world.broadphase = new CANNON.NaiveBroadphase()
创建形状
为形状添加刚体
将刚体添加到世界
let sphereShape = new CANNON.Sphere(1) // Step 1
let sphereBody = new CANNON.Body({ // Step 2
mass: 5,
position: new CANNON.Vec3(0, 10, 0),
shape: sphereShape
})
world.add(sphereBody) // Step 3
// 平面 Body
let groundShape = new CANNON.Plane()
let groundBody = new CANNON.Body({
mass: 0,
shape: groundShape
})
// setFromAxisAngle 旋转 X 轴 -90 度
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -1.5707963267948966)
world.add(groundBody)
// 平面网格
let groundGeometry = new THREE.PlaneGeometry(20, 20, 32)
let groundMaterial = new THREE.MeshStandardMaterial({
color: 0x7f7f7f,
side: THREE.DoubleSide
})
let ground = new THREE.Mesh(groundGeometry, groundMaterial)
scene.add(ground)
// 球网格
let sphereGeometry = new THREE.SphereGeometry(1, 32, 32)
let sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xffff00 })
let sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
scene.add(sphere)
function update() {
requestAnimationFrame(update)
world.step(1 / 60)
if (sphere) {
sphere.position.copy(sphereBody.position)
sphere.quaternion.copy(sphereBody.quaternion)
}
}
其他:
// 平面
let ground_cm = new CANNON.Material() // Step 1 : 实例 CANNON.Material
let groundBody = new CANNON.Body({
material: groundMaterial // Step 2 : 使用该物理材质
})
// 球
let sphere_cm = new CANNON.Material()
let sphereBody = new CANNON.Body({
material: sphere_cm
})
let sphere_ground = new CANNON.ContactMaterial(ground_cm, sphere_cm, { // Step 3 : 定义两个刚体相遇后会发生什么
friction: 1,
restitution: 0.4
})
world.addContactMaterial(sphere_ground) // Step 4 : 添加到世界中
let tween = new TimelineMax()
tween.to(boxBody.position, 2, { x: 0, y: 10, z: 0,
update: function() {
// 归 0 设置
boxBody.velocity.setZero()
boxBody.initVelocity.setZero()
boxBody.angularVelocity.setZero()
boxBody.initAngularVelocity.setZero()
}
})
boxBody.collisionResponse = false
boxBody.updateMassProperties()
let tween = new TimelineMax()
tween.to(sphereBody.shapes[0], 2, {
radius: 0.2 // 缩放至 0.2
})
点击交互
let raycaster = new THREE.Raycaster()
let mouse = new THREE.Vector2()
function onTouchEnd(ev) {
// 点击获取屏幕坐标
var event = ev.changedTouches[0]
mouse.x = (event.clientX / window.innerWidth) * 2 - 1
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
raycaster.setFromCamera(mouse, camera)
let intersects = raycaster.intersectObjects(scene, true)
for (let i = 0; i < intersects.length; i++) {
console.log(intersects[i]) // 与射线发生碰撞的物体
}
}
性能方面
var n = navigator.userAgent
if (/iPad|iPhone|iPod/.test(n) && !window.MSStream) { } // 针对 iOS 系统使用阴影
renderer.antialias = true // 开启抗锯齿
renderer.setPixelRatio(2) // 推荐
renderer.setPixelRatio(window.devicePixelRatio) // 不推荐
工具推荐
new THREE.OrbitControls(camera, renderer.domElement)
let camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000)
let helper = new THREE.CameraHelper(camera)
scene.add(helper)
let light = new THREE.DirectionalLight(0xffffff)
let helper = new THREE.DirectionalLightHelper(0xffffff)
scene.add(helper)
let axesHelper = new THREE.AxesHelper(10)
scene.add(axesHelper)
let cannonDebugRenderer = new THREE.CannonDebugRenderer(scene, world)
function render() {
requestAnimationFrame(render)
cannonDebugRenderer.update() // Update the debug renderer
}
let opts = { x: 0, y: 0, scale: 1 }
let gui = new dat.GUI()
gui.add(opts, 'x', -3, 3)
gui.add(opts, 'y', -3, 3)
gui.add(opts, 'scale', 1, 3)
function loop() {
cube.position.x = opt.x
cube.position.y = opt.y
cube.scale.set(opts.scale, opts.scale, opts.scale)
requestAnimationFrame()
}
var stats = new Stats()
stats.showPanel(1)
document.body.appendChild(stats.dom)
function animate() {
requestAnimationFrame(animate)
}
requestAnimationFrame(animate)
尾巴
参考
《Learning Three.js》
threejs
babylonjs
glTF 2.0 README
Cannon.js
Debug renderer for Three.js
three-gltf-viewer
stats.js
本文分享自微信公众号 - 凹凸实验室(AOTULabs)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。