效果
第一步:添加基础three场景
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加上按钮
<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默认样式
添加函数事件
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包找到具体文件引入即可。
结语
以上图片模型资料均等在老陈-陈鹏获取,侵权联系删除,下面展示全部源码:
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;