根底筹备
一、什么是 three
Three.js 是基于 WebGL 的 javascript 开源框架,是一个能够实现 3D 成果的 JS 库
Three.js 由场景(scene)、相机(camera)和渲染器(renderer)这三个最根底的组成。
场景是展现内容的容器,而相机则是用来拍摄的工具,渲染器则是将 Canvas 进行绑定。
二、介绍模板下载的网站
https://sketchfab.com/
这是一个有着大量资源的模型网站,这里有很多付费的收费的模型供咱们去筛选应用
三、介绍动作绑定的网站
https://www.mixamo.com/
是一个能够商用的收费模型动画库
我的项目介绍
一、这次作为 three 的练手我的项目,这次练手的我的项目实现目标有:
① 一个展示厅的搭建
② 视频绑定的播放
③ 人物的行走
④ 碰撞的检测
二、介绍我的项目的根本须要的模型
① 展示厅的模型
② 人物的模型
三、代码介绍
我的项目总览
import * as THREE from 'three';
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader";
let mixer;
let playerMixer;
let deltaTime;
const player = {
mesh: null,
actionIdle: null,
actionWalk: null,
}
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 50);
const renderer = new THREE.WebGLRenderer({antialias: true});
const clock = new THREE.Clock()
initBase()
initShadow()
initMesh()
initEvent()
animate();
①代码的初始化
/**
* 初始化
*/
function initBase() {renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene.background = new THREE.Color(0.2, 0.2, 0.2);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.1);
scene.add(ambientLight);
}
②代码暗影的初始化
* 初始化暗影
*/
function initShadow() {
// 1. 关上 render 暗影
renderer.shadowMap.enabled = true
// 2.1 关上灯光暗影,设置灯光暗影大小
const directionLight = new THREE.DirectionalLight(0xffffff, 1.5);
directionLight.position.set(10, 10, 10);
directionLight.castShadow = true
scene.add(directionLight);
// 2.2 设置灯管暗影贴图大小
directionLight.shadow.mapSize.width = 5120
directionLight.shadow.mapSize.height = 5120
// 3 设置暗影体 远近 大小,不在这之内的显示不进去
const shadowDistance = 25
directionLight.shadow.camera.near = 0.2
directionLight.shadow.camera.far = 50
directionLight.shadow.camera.left = -shadowDistance
directionLight.shadow.camera.right = shadowDistance
directionLight.shadow.camera.top = shadowDistance
directionLight.shadow.camera.bottom = -shadowDistance
directionLight.shadow.bias = -0.001
}
③模型加载的初始化
/**
* 初始化模型
*/
function initMesh() {
// 加载人物模型,缓存相干动画。设置视角
new GLTFLoader().load('../resources/models/player.glb', (gltf) => {console.log("gltf")
console.log(gltf)
console.log("gltf")
// 初始化模型地位
player.mesh = gltf.scene
scene.add(player.mesh)
player.mesh.position.set(0, 0, 11.5)
player.mesh.rotateY(Math.PI)
// 增加相机地位
player.mesh.add(camera)
camera.position.set(0, 2, -6)
camera.lookAt(new THREE.Vector3(0, 0, 1))
// 增加灯光
const pointLight = new THREE.PointLight(0xffffff, 1)
scene.add(pointLight)
player.mesh.add(pointLight)
pointLight.position.set(0, 2, -1)
playerMixer = new THREE.AnimationMixer(gltf.scene)
// 缓存静止的动画
const clipIdle = THREE.AnimationUtils.subclip(gltf.animations[0], 'idle', 32, 62)
player.actionIdle = playerMixer.clipAction(clipIdle)
// 缓存静止的动画
const clipWalk = THREE.AnimationUtils.subclip(gltf.animations[0], 'walk', 0, 31)
player.actionWalk = playerMixer.clipAction(clipWalk)
// 设置初始的动画状态
player.actionIdle.play()});
// 加载展馆模型
new GLTFLoader().load('../resources/models/zhanguan.glb', (gltf) => {scene.add(gltf.scene);
gltf.scene.traverse((child) => {
// 4. 设置 mesh 投射暗影,接管暗影
child.castShadow = true;
child.receiveShadow = true;
if (child.name === '2023') {addVideo(child, "./resources/yanhua.mp4")
}
if (child.name === '大屏幕 01' || child.name === '大屏幕 02' || child.name === '操作台屏幕' || child.name === '环形屏幕 2') {addVideo(child, "./resources/video01.mp4")
}
if (child.name === '环形屏幕') {addVideo(child, "./resources/video02.mp4")
}
if (child.name === '柱子屏幕') {addVideo(child, "./resources/yanhua.mp4")
}
})
mixer = new THREE.AnimationMixer(gltf.scene);
const clips = gltf.animations; // 播放所有动画
clips.forEach(function (clip) {const action = mixer.clipAction(clip);
action.loop = THREE.LoopOnce;
// 停在最初一帧
action.clampWhenFinished = true;
action.play();});
})
}
/**
* 给模型增加动画
*/
function addVideo(child, src) {const video = document.createElement('video');
video.src = src;
video.muted = true;
video.autoplay = "autoplay";
video.loop = true;
video.play();
const videoTexture = new THREE.VideoTexture(video);
const videoMaterial = new THREE.MeshBasicMaterial({map: videoTexture});
child.material = videoMaterial;
}
④事件的注册
/**
* 初始化事件
*/
function initEvent() {
// 按键按下的解决
let isChangeToWork = true
const playerHalfHeight = new THREE.Vector3(0, 1, 0)
window.addEventListener('keydown', (e) => {if (e.key === 'w') {const curPos = player.mesh.position.clone()
player.mesh.translateZ(1)
const frontPos = player.mesh.position.clone()
player.mesh.translateZ(-1)
// 角色碰撞检测
const frontVector3 = frontPos.sub(curPos).normalize()
const raycasterFront = new THREE.Raycaster(player.mesh.position.clone().add(playerHalfHeight), frontVector3)
const collisionResultsFrontObjs = raycasterFront.intersectObjects(scene.children)
console.log(collisionResultsFrontObjs)
if (collisionResultsFrontObjs && collisionResultsFrontObjs[0] && collisionResultsFrontObjs[0].distance > 1) {player.mesh.translateZ(3 * deltaTime)
}
if (collisionResultsFrontObjs && collisionResultsFrontObjs.length === 0) {player.mesh.translateZ(3 * deltaTime)
}
if (isChangeToWork) {crossPlay(player.actionIdle, player.actionWalk)
isChangeToWork = false
}
}
if (e.key === 's') {player.mesh.translateZ(-0.1)
}
})
// 按键松开的解决
window.addEventListener('keyup', (e) => {if (e.key === 'w') {if (!isChangeToWork) {crossPlay(player.actionWalk, player.actionIdle)
isChangeToWork = true
}
}
});
// 鼠标挪动,视角解决
let prePos
window.addEventListener('mousemove', (e) => {if (prePos && player.mesh) {player.mesh.rotateY((prePos - e.clientX) * 0.01)
}
prePos = e.clientX
});
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}, false)
}
/**
* 动画切换优化解决
*/
function crossPlay(curAction, newAction) {curAction.fadeOut(0.3);
newAction.reset();
newAction.setEffectiveWeight(1);
newAction.play();
newAction.fadeIn(0.3);
}
⑤我的项目的启动
function animate() {requestAnimationFrame(animate);
renderer.render(scene, camera);
deltaTime = clock.getDelta()
if (mixer) {mixer.update(0.02);
}
if (playerMixer) {playerMixer.update(0.015);
}
}
这就是这次展厅我的项目的代码总结。相干的介绍都在代码正文外面~~
欢送大家一起来交流学习~~~