three.js 载入3D模型,实现植物生长动画

34 阅读3分钟

下载一个免费的3D模型

sketchfab.com/3d-models?d… three.js官方推荐使用glTF(gl传输格式)

建立场景、相机和渲染器

 // 创建场景
        const scene = new THREE.Scene();
        // 创建纹理加载器
        // const textureLoader = new THREE.TextureLoader();

        // // 加载纹理
        // const texture = textureLoader.load('/background.jpeg');

        // // 设置背景为纹理
        // scene.background = texture;
        scene.background = new THREE.Color(0xa7b3cc);
        // 创建相机
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 0, 5);
        // 创建渲染器
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight - 200);
       
        // 渲染器添加到容器中
        containerRef.current?.appendChild(renderer.domElement);

设置场景背景

        // const textureLoader = new THREE.TextureLoader();

        // // 加载纹理
        // const texture = textureLoader.load('/background.jpeg');

        // // 设置背景为纹理
        // scene.background = texture;
        scene.background = new THREE.Color(0xa7b3cc);

设置相机位置

camera.position.set(0, 0, 5);

渲染器添加到容器中

 containerRef.current?.appendChild(renderer.domElement);

建立光源

        // 添加灯光
        // AmbientLight的第二个参数是光源的强度
        const ambientLight = new THREE.AmbientLight(0xffffff, 2);
        //  增加光源的强度
        // ambientLight.intensity = 2
        scene.add(ambientLight);

        // 添加方向光源
        const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
        directionalLight.position.set(0, 0, 5);
        // directionalLight.intensity = 2
        scene.add(directionalLight);

调整光源的强度

ambientLight.intensity = 2 // 或者设置THREE.AmbientLight()的第二个参数

调整光源的位置

directionalLight.position.set(0, 0, 5);

载入3D模型

        // 导入植物模型
        let flowerScene: THREE.Object3D;
        loader.load('/orchid_flower/scene.gltf', function (flower) {
            // 调整模型大小
            flower.scene.scale.set(0.1, 0.1, 0.1);
            // 调整模型位置
            flower.scene.position.set(-4, 1, 7);
            scene.add(flower.scene);
            flowerScene = flower.scene
        }, undefined, function (error) {
            console.error(error);
        });

调整模型大小

gltf.scene.scale.set(0.5, 0.5, 0.5);

调整模型位置

gltf.scene.position.set(-2, -2, 0);

调整模型旋转

gltf.scene.rotation.y = - Math.PI / 2;

辅助功能:添加辅助线

 // AxesHelper是Three.js 库中的一个辅助对象,用于在三维场景中可视化地表示三个坐标轴(X、Y 和 Z 轴)。它通常用于调试和演示目的,帮助开发者理解场景中物体的方向和空间关系。
        const axesHelper = new THREE.AxesHelper(5);
        scene.add(axesHelper);

辅助功能:添加轨道控制器

 // 添加轨道控制器,允许用户通过鼠标拖动模型,旋转和缩放
const controls = new OrbitControls(camera, renderer.domElement);

渲染场景

 let requestId: number;
        // 渲染循环
        function animate() {
            requestId = requestAnimationFrame(animate);
            renderer.render(scene, camera);
            controls.update();
        }

        animate();

渲染容器

<main>
   <div ref={containerRef} >
   </div>
 </main>

实现鼠标点击,植物变大

实现植物变大的动画效果,需要安装一个createjs库。

        // 导入createjs
        import 'createjs/builds/1.0.0/createjs.min.js'

       // 处理鼠标点击事件
        const handleClick = (event: MouseEvent) => {
            // 计算鼠标在屏幕上的位置
            const mouse = new THREE.Vector2();
            mouse.x = ((event.clientX - renderer.domElement.offsetLeft) / renderer.domElement.clientWidth) * 2 - 1;
            mouse.y = -((event.clientY - renderer.domElement.offsetTop) / renderer.domElement.clientHeight) * 2 + 1;

            // 射线投射
            const raycaster = new THREE.Raycaster();
            raycaster.setFromCamera(mouse, camera);
            

            const intersects = raycaster.intersectObjects([flowerScene], true);
            
            if (intersects.length > 0) {
                if (flowerScene) {
                    // 植物变大的动画
                    // THREE.Vector3 是 Three.js 库中的一个类,用于表示三维空间中的向量。向量是具有大小和方向的量,在三维空间中,一个向量通常由三个分量组成,分别对应 x、y、z 轴上的数值。
                    // THREE.Vector3 类提供了许多方法来操作和处理这些向量,例如加法、减法、标量乘法、点积、叉积等。
                    const targetScale = new THREE.Vector3(0.3, 0.3, 0.2); // 目标缩放比例
                    const duration = 20000; // 动画持续时间(毫秒)
                    createjs.Tween.get(flowerScene.scale)
                        .to({
                            x: targetScale.x,
                            y: targetScale.y,
                            z: targetScale.z,
                            onStart() {
                                console.log('start')
                            },
                            onUpdate: () => {
                                renderer.render(scene, camera);
                            }
                        }, duration)
                }
            }
        }

        window.addEventListener('click', handleClick)

注意: 设置容器DOM节点style={{ position: 'relative' }}导致的raycaster射线检测不到或者不准确

实现悬浮物品展示文字效果

  1. 在渲染容器节点同级下增加新的节点
<div ref={tooltipRef} style={{ position: 'fixed', zIndex: 1000, top: 0, left: 0, display: 'none', backgroundColor: 'white' }}></div>
  1. js实现
    let tooltipRef = useRef<HTMLDivElement>(null);
    const hanldeMouseMove = (event: MouseEvent) => {
                // 计算鼠标在屏幕上的位置
            const mouse = new THREE.Vector2();
            mouse.x = ((event.clientX - renderer.domElement.offsetLeft) / renderer.domElement.clientWidth) * 2 - 1;
            mouse.y = -((event.clientY - renderer.domElement.offsetTop) / renderer.domElement.clientHeight) * 2 + 1;
            // 射线投射
            const raycaster = new THREE.Raycaster();
            raycaster.setFromCamera(mouse, camera);
            // 检查是否点击了模型
            const intersects = raycaster.intersectObjects([flowerScene], true);
            if (intersects.length > 0) {
                // 点击了模型,显示提示
                if (tooltipRef.current) {
                    tooltipRef.current.style.display = 'block';
                    tooltipRef.current.style.top = event.clientY + 'px';
                    tooltipRef.current.style.left = event.clientX + 'px';
                    tooltipRef.current.innerText = 'flower'
                }
            } else {
                if (tooltipRef.current) {
                    tooltipRef.current.style.display = 'none';
                }
            }
        }
 window.addEventListener('mousemove', hanldeMouseMove)

页面销毁时,清理场景、相机和渲染器,事件监听

            cancelAnimationFrame(requestId)
            window.removeEventListener('mousemove', hanldeMouseMove)
            window.removeEventListener('click', handleClick)
            scene.clear()
            camera.clear()
            renderer.clear()
            renderer.dispose()