根底筹备一、什么是threeThree.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); }}这就是这次展厅我的项目的代码总结。相干的介绍都在代码正文外面~~欢送大家一起来交流学习~~~
...