教你如何使用blender+threejs搭建一个3d展厅平台 | 大帅老猿threejs特训

效果图

页面预览链接(服务器配置比较低,加载视频会比较慢,请耐心等候):https://static-8f957b23-c692-...

相关链接整理

  1. 胖达老师threejs、blender搭建元宇宙基础交互直播教学回放链接
  2. 大帅老师的代码仓库。
  3. threejs官方文档。
  4. 本文完整代码

需要实现的功能点

1. 使用blender3d建模导出一个展馆模型,人物模型。
2. 使用threejs技术导入展馆模型、把视频贴在模型的集合上显示。 
3. 导入人物模型,监听键盘事件让人物行动起来。
4. 碰撞检测。

blender导出模型

具体怎么使用blender制作3d模型可以看我的b站链接详细教学:https://www.bilibili.com/video/BV18K41127yq/?vd_source=1f95e9c4449f30df3c017d0e5a4cdd66

threejs创建场景、光线、相机、渲染器

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 200);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

camera.position.set(5, 10, 25);

scene.background = new THREE.Color(0.2, 0.2, 0.2);

const ambientLight = new THREE.AmbientLight(0xffffff, 0.1);
scene.add(ambientLight);//环境光没有方向

const directionLight = new THREE.DirectionalLight(0xffffff, 0.2);
scene.add(directionLight);

threejs导入视频并在场馆集合贴图

new GLTFLoader().load('../resources/models/zg.glb', (gltf) => {

    // console.log(gltf);
    scene.add(gltf.scene);

    const video02 = document.createElement('video');
    video02.src = "./resources/video02.mp4" ;
    video02.muted = true;
    video02.autoplay = "autoplay";
    video02.loop = true;
    video02.play();

    gltf.scene.traverse((child) => {
        // console.log(child.name);

        child.castShadow = true;
        child.receiveShadow = true;
        if (child.name === '2023') {
            const video = document.createElement('video');
            video.src = "./resources/yanhua.mp4";
            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;
        }
        if (child.name === '大屏幕01' ) {
            const video = document.createElement('video');
            video.src = "./resources/video01.mp4";
            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;
        }
        if (child.name === '曲面屏幕01'||child.name === '曲面屏幕02'||child.name === '操作台') {
            const videoTexture = new THREE.VideoTexture(video02);
            const videoMaterial = new THREE.MeshBasicMaterial({ map: videoTexture });

            child.material = videoMaterial;
        }
    })



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

})

threejs导入人物模型,监听键盘事件控制人物的行动,碰撞检测

new THREE.Raycaster()是射线碰撞检测的对象,当人物面向的方向有物体的时候,collisionResultsFrontObjs 的数组就为空。

let playerMesh;
let actionWalk, actionIdle;
const lookTarget = new THREE.Vector3(0, 2, 0);
new GLTFLoader().load('../resources/models/player.glb', (gltf) => {
    playerMesh = gltf.scene;
    scene.add(gltf.scene);

    playerMesh.traverse((child)=>{
        child.receiveShadow = true;
        child.castShadow = true;
    })

    playerMesh.position.set(0, 0, 11.5);
    playerMesh.rotateY(Math.PI);

    playerMesh.add(camera);
    camera.position.set(0, 2, -5);
    camera.lookAt(lookTarget);

    const pointLight = new THREE.PointLight(0xffffff, 1.5);
    playerMesh.add(pointLight);
    pointLight.position.set(0, 1.8, -1);

    playerMixer = new THREE.AnimationMixer(gltf.scene);

    const clipWalk = THREE.AnimationUtils.subclip(gltf.animations[0], 'walk', 0, 30);
    actionWalk = playerMixer.clipAction(clipWalk);
    // actionWalk.play();

    const clipIdle = THREE.AnimationUtils.subclip(gltf.animations[0], 'idle', 31, 281);
    actionIdle = playerMixer.clipAction(clipIdle);
    actionIdle.play();


    // const clips = gltf.animations; // 播放所有动画
    // clips.forEach(function (clip) {
    //     const action = mixer.clipAction(clip);
    //     action.loop = THREE.LoopOnce;
    //     // 停在最后一帧
    //     action.clampWhenFinished = true;
    //     action.play();
    // });
});

let isWalk = false;
const playerHalfHeight = new THREE.Vector3(0, 0.8, 0);
window.addEventListener('keydown', (e) => {
    if (e.key === 'w') {
        // playerMesh.translateZ(0.1);

        const curPos = playerMesh.position.clone();
        playerMesh.translateZ(1);
        const frontPos = playerMesh.position.clone();
        playerMesh.translateZ(-1);
        
        const frontVector3 = frontPos.sub(curPos).normalize()

        const raycasterFront = new THREE.Raycaster(playerMesh.position.clone().add(playerHalfHeight), frontVector3);
        const collisionResultsFrontObjs = raycasterFront.intersectObjects(scene.children);

        console.log(collisionResultsFrontObjs);


        if (collisionResultsFrontObjs && collisionResultsFrontObjs[0] && collisionResultsFrontObjs[0].distance > 1) {
            playerMesh.translateZ(1);
        }
        


        if (!isWalk) {
            crossPlay(actionIdle, actionWalk);
            isWalk = true;
        }
    }
    if (e.key === 's') {

        // const curPos = playerMesh.position.clone();
        playerMesh.translateZ(-1);
        // const frontPos = playerMesh.position.clone();
        // playerMesh.translateZ(1);
        
        // const frontVector3 = frontPos.sub(curPos).normalize()

        // const raycasterFront = new THREE.Raycaster(playerMesh.position.clone().add(playerHalfHeight), frontVector3);
        // const collisionResultsFrontObjs = raycasterFront.intersectObjects(scene.children);

        // console.log(collisionResultsFrontObjs);


        // if (collisionResultsFrontObjs && collisionResultsFrontObjs[0] && collisionResultsFrontObjs[0].distance > 1) {
        //     playerMesh.translateZ(-1);
        // }
        


        if (!isWalk) {
            crossPlay(actionIdle, actionWalk);
            isWalk = true;
        }
    }
})

window.addEventListener('keyup', (e) => {
    if (e.key === 'w'||e.key === 's') {
        crossPlay(actionWalk, actionIdle);
        isWalk = false;
    }
});

let preClientX;
window.addEventListener('mousemove', (e) => {

    if (preClientX && playerMesh) {
        playerMesh.rotateY(-(e.clientX - preClientX) * 0.01);
    }
    preClientX = e.clientX;
});

总结

这次通过学习项目后,我对blender建模、threejs有了初步的认识,基本可以自己手撸一个展厅平台。

最后推荐下大帅老师,确实很厉害,干货多多; 在公众号里搜 大帅老猿,他做技术外包很靠谱,感兴趣的可以加入猿创营 (v:dashuailaoyuan),一起交流学习。

你可能感兴趣的:(教你如何使用blender+threejs搭建一个3d展厅平台 | 大帅老猿threejs特训)