关于three.js的展厅项目的研究与总结 | 大帅老猿threejs特训

基础准备

一、什么是three

Three.js是基于WebGL的javascript开源框架,是一个可以实现3D效果的JS库
Three.js由场景(scene)、相机(camera)和渲染器(renderer)这三个最基础的组成。
场景是展示内容的容器,而相机则是用来拍摄的工具,渲染器则是将Canvas进行绑定。

二、介绍模板下载的网站

https://sketchfab.com/
这是一个有着大量资源的模型网站,这里有很多付费的免费的模型供我们去挑选使用

三、介绍动作绑定的网站

https://www.mixamo.com/
是一个可以商用的免费模型动画库

项目介绍

一、这次作为three的练手项目,这次练手的项目实现目标有:

① 一个展示厅的搭建
② 视频绑定的播放
③ 人物的行走
④ 碰撞的检测

二、介绍项目的基本需要的模型

① 展示厅的模型

关于three.js的展厅项目的研究与总结 | 大帅老猿threejs特训_第1张图片

② 人物的模型

关于three.js的展厅项目的研究与总结 | 大帅老猿threejs特训_第2张图片

三、代码介绍

项目总览

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);
    }
}

这就是这次展厅项目的代码总结。相关的介绍都在代码注释里面~~
欢迎大家一起来交流学习~~~

你可能感兴趣的:(3d学习three.js)