three+tween+react实现视角切换观看模型角度

效果

第一步:添加基础three场景

ini 复制代码
    let scene, renderer, controls;
    // 初始化场景
    scene = new THREE.Scene();

    // 初始化相机
    const camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000
    );
    camera.position.set(2.5, 1, 2.5);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    cameraRef.current = camera;
    
     // 添加环境光
    const ambient = new THREE.AmbientLight(0xffffff, 1);
    scene.add(ambient);
    
    
        // 初始化渲染器
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);

    // 监听屏幕大小变化
    window.addEventListener("resize", () => {
      renderer.setSize(window.innerWidth, window.innerHeight);
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
    });
    
        // 将渲染器添加到页面
    document
      .querySelector(".canvasContainer")
      ?.appendChild(renderer.domElement);
      
      
          // 添加控制器
    controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;

    // 渲染函数
    const render = () => {
      circle.rotation.z -= 0.01;
      circle2.rotation.z -= 0.01;

      controls.update();
      renderer.render(scene, camera);
      requestAnimationFrame(render);
      TWEEN.update();
    };

    // 执行函数
    render();

我使用React写的所以写在了useEffect里面 并在最后可以写上 return () => { window.removeEventListener("resize", () => {}); };做简单优化

第二步:做点击事件以及tween补间动画

html加上按钮

ini 复制代码
 <div className="canvasContainer">
      <div className="btn">
        <Button
          type="primary"
          onClick={() => {
            handleButtonClick("you");
          }}
        >
          右转
        </Button>
        <Button
          type="primary"
          onClick={() => {
            handleButtonClick("zuo");
          }}
        >
          左转
        </Button>
        <Button
          type="primary"
          onClick={() => {
            handleButtonClick("xia");
          }}
        >
          下面
        </Button>
        <Button
          type="primary"
          onClick={() => {
            handleButtonClick("return");
          }}
        >
          初始
        </Button>
      </div>
    </div>

样式的话自己定义就好了,我这里用的antd默认样式

添加函数事件

ini 复制代码
  const cameraRef = useRef(null);
  const modelRef = useRef(null);
 const handleButtonClick = (status) => {
    const { current: camera } = cameraRef;
    const { current: model } = modelRef;
    if (!camera || !model) return;
    animateCameraToModel(camera, model, status);
  };

  const animateCameraToModel = (camera, model, status) => {
    const start = {
      x: camera.position.x,
      y: camera.position.y,
      z: camera.position.z,
    };
    var end;
    const positions = {
      you: { x: 4.5, y: 1, z: 2.5 },
      zuo: { x: 2.5, y: 1, z: 4.5 },
      xia: { x: 2.5, y: -2, z: 2.5 },
      default: { x: 2.5, y: 1, z: 2.5 },
    };
    var end = positions[status] || positions.default;
    // const end = { x: 4.5, y: 1, z: 2.5 };
    const duration = 1000;

    new TWEEN.Tween(start)
      .to(end, duration)
      .onUpdate(() => {
        camera.position.set(start.x, start.y, start.z);
        camera.lookAt(model.position);
      })
      .start();
      

这里有个注意第一步我是在useEffect里面写的,现在这些都是在useEffect外面写的,这样的话按钮的点击函数才可以直接访问到,而且我的tween是另外用npm下载的,其实three自带有tween,直接 import { TWEEN } from "three/examples/jsm/libs/tween.module.min"引入即可,最后文件名有可能有所不同,查看node_modules包找到具体文件引入即可。

结语

以上图片模型资料均等在老陈-陈鹏获取,侵权联系删除,下面展示全部源码:

ini 复制代码
import React, { useEffect, useRef } from "react";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { Button } from "antd";
import "./bear.css";
import img050 from "../../assets/imgs/050.jpg";
import img1 from "../../assets/imgs/1.png";
import gltf from "../../assets/model/bear.gltf";
import TWEEN from "@tweenjs/tween.js";

function Bear() {
  const cameraRef = useRef(null);
  const modelRef = useRef(null);

  useEffect(() => {
    let scene, renderer, controls;

    // 初始化场景
    scene = new THREE.Scene();

    // 初始化相机
    const camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000
    );
    camera.position.set(2.5, 1, 2.5);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    cameraRef.current = camera;

    // 加载背景纹理
    const loader = new THREE.TextureLoader();
    const bgTexture = loader.load(img050);
    bgTexture.mapping = THREE.EquirectangularRefractionMapping;

    scene.background = bgTexture;
    scene.environment = bgTexture;

    // 添加环境光
    const ambient = new THREE.AmbientLight(0xffffff, 1);
    scene.add(ambient);

    // 加载小熊模型
    const gltfLoader = new GLTFLoader();
    gltfLoader.load(gltf, (gltf) => {
      const model = gltf.scene.children[0];
      model.material = new THREE.MeshPhongMaterial({
        color: 0xffffff,
        envMap: bgTexture,
        refractionRatio: 0.7,
        reflectivity: 0.99,
      });
      modelRef.current = model;
      scene.add(model);
    });

    //创建平面
    const waterGeometry = new THREE.CircleGeometry(1, 64);
    const waterGeometry2 = new THREE.CircleGeometry(2, 64);
    const exture = new THREE.TextureLoader().load(img1);
    const material = new THREE.MeshBasicMaterial({
      map: exture,
      transparent: true,
    });
    const circle = new THREE.Mesh(waterGeometry, material);
    const circle2 = new THREE.Mesh(waterGeometry2, material);
    circle.rotation.x = -Math.PI / 2;
    circle2.rotation.x = -Math.PI / 2;
    scene.add(circle);
    scene.add(circle2);

    // 初始化渲染器
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);

    // 监听屏幕大小变化
    window.addEventListener("resize", () => {
      renderer.setSize(window.innerWidth, window.innerHeight);
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
    });

    // 将渲染器添加到页面
    document
      .querySelector(".canvasContainer")
      ?.appendChild(renderer.domElement);

    // 添加控制器
    controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;

    // 渲染函数
    const render = () => {
      circle.rotation.z -= 0.01;
      circle2.rotation.z -= 0.01;

      controls.update();
      renderer.render(scene, camera);
      requestAnimationFrame(render);
      TWEEN.update();
    };

    render();

    return () => {
      window.removeEventListener("resize", () => {});
    };
  }, []);

  const handleButtonClick = (status) => {
    const { current: camera } = cameraRef;
    const { current: model } = modelRef;
    if (!camera || !model) return;
    animateCameraToModel(camera, model, status);
  };

  const animateCameraToModel = (camera, model, status) => {
    const start = {
      x: camera.position.x,
      y: camera.position.y,
      z: camera.position.z,
    };
    var end;
    const positions = {
      you: { x: 4.5, y: 1, z: 2.5 },
      zuo: { x: 2.5, y: 1, z: 4.5 },
      xia: { x: 2.5, y: -2, z: 2.5 },
      default: { x: 2.5, y: 1, z: 2.5 },
    };
    var end = positions[status] || positions.default;
    // const end = { x: 4.5, y: 1, z: 2.5 };
    const duration = 1000;

    new TWEEN.Tween(start)
      .to(end, duration)
      .onUpdate(() => {
        camera.position.set(start.x, start.y, start.z);
        camera.lookAt(model.position);
      })
      .start();
  };

  return (
    <div className="canvasContainer">
      <div className="btn">
        <Button
          type="primary"
          onClick={() => {
            handleButtonClick("you");
          }}
        >
          右转
        </Button>
        <Button
          type="primary"
          onClick={() => {
            handleButtonClick("zuo");
          }}
        >
          左转
        </Button>
        <Button
          type="primary"
          onClick={() => {
            handleButtonClick("xia");
          }}
        >
          下面
        </Button>
        <Button
          type="primary"
          onClick={() => {
            handleButtonClick("return");
          }}
        >
          初始
        </Button>
      </div>
    </div>
  );
}

export default Bear;
相关推荐
答案answer21 小时前
不用写一行Three.js代码,也能搭出复杂数字孪生场景
github·three.js
纸片人2 天前
甲方只给了一张卫星截图,我用 Three.js + Blender 建了一座 3D 能源站
three.js·数据可视化
纸片人2 天前
从一座城市钻进一个房间:Three.js + Blender 做一个五级钻取的数字楼宇大屏
three.js·数据可视化
妖精的羽翼2 天前
Web3D(Three.js)应用分级 + 学习路线规划(由浅入深,贴合接单 + 求职)
学习·three.js
踩着两条虫3 天前
我在浏览器里“盖”了一个村:Vue + Three.js 大屏开发踩坑记
前端·vue.js·three.js
子龙_3 天前
来在网页上用threejs画个蛋白质吧
前端·webgl·three.js
沧沧凉凉10 天前
一段需求、零素材,Claude Code 派了 33 个代理给我做了个 3D 动作游戏
游戏·ai编程·three.js
牧艺15 天前
浏览器端 3D 看车重磅升级:从三款展车到可试驾的八车展厅
前端·three.js·交互设计
像我这样帅的人丶你还17 天前
📱iPhone Duo 开屏动画咋实现的?
javascript·webgl·three.js
夏天要喝冰可乐17 天前
Antigravity + Blender MCP(下):3D 智慧仓储数字孪生进阶实战
前端·webgl·three.js