效果
完整代码
<template>
<div class="home">
<div class="convas_container" ref="canvasDom" style="">
</div>
</div>
</template>
<script setup>
//
import * as THREE from 'three'
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
import { Water } from 'three/examples/jsm/objects/Water'
import gsap from 'gsap'
// 导入dat.gui
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader' // 引入加载器
// 引入控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { onMounted, reactive, ref } from 'vue';
// 添加反射
import { Reflector } from 'three/examples/jsm/objects/Reflector'
// 相机跟踪
import * as TWEEN from '@tweenjs/tween.js';
const canvasDom = ref(null)
let controls = null // 控制器
// 初始化场景
const scene = new THREE.Scene()
// 设置时钟
const clock = new THREE.Clock()
// 创建纹理加载器
const textureLoader = new THREE.TextureLoader();
// 初始化相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
// 设置相机的位置
camera.position.set(0, 10, 6)
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true,
// 设置光照
})
renderer.setSize(window.innerWidth, window.innerHeight)
// 设置色调映射
renderer.toneMapping = THREE.ACESFilmicToneMapping
// 色调亮度
renderer.toneMappingExposure = 2
// 加阴影
renderer.toneMappingExposure = 0.5
renderer.shadowMap.enabled = true
renderer.physicallyCorrectLights = true
document.body.appendChild(renderer.domElement)
const render = () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
controls && controls.update()
renderer.render(scene, camera);
requestAnimationFrame(render)
}
onMounted(() => {
canvasDom.value.appendChild(renderer.domElement)
// 初始化渲染器 渲染背景
renderer.setClearColor('#000')
render()
// // 添加轨道控制器
controls = new OrbitControls(camera, canvasDom.value)
controls.enableDamping = true // 添加阻尼
controls.update()
})
// 添加辅助坐标轴
const axes = new THREE.AxesHelper(5)
scene.add(axes)
// 创建rgbe加载器
let hdrloader = new RGBELoader()
hdrloader.load('./model/satara_night_no_lamps_1k.hdr', (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping
scene.background = texture
scene.environment = texture
})
// 添加模型
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./draco/gltf/')
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
// 加载地图模型
const doorColorTexture = textureLoader.load('./model/1.jpg') // 纹理路径
// 材质
const basicMaterial = new THREE.MeshBasicMaterial({
color: '#c8c8c8',
map: doorColorTexture,// 添加纹理(贴图)
})
let model;
gltfLoader.load('./model/shelves.glb', function (glb) {
model = glb.scene;
// 遍历模型的子元素
model.traverse(function (child) {
if (child.isMesh) {
// 给子元素添加纹理
child.material = child.material.clone();
child.material.map = doorColorTexture;
child.material.needsUpdate = true;
child.material.transparent = true;
child.material.opacity = 0.2;
child.material.color.set(0xffcccc);
}
});
//将模型添加至场景
scene.add(glb.scene)
model.position.set(10,2,10)
//设置模型位置
camera.position.set(20,10, 20)
})
// 添加灯管
const light = new THREE.DirectionalLight(0xffffff, 100)
light.position.set(10, 10, 10)
scene.add(light)
const light1 = new THREE.DirectionalLight(0xffffff, 100)
light1.position.set(-10, 10, 10)
scene.add(light1)
const light2 = new THREE.DirectionalLight(0xffffff, 100)
light2.position.set(20, 10, 10)
scene.add(light2)
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 10);
scene.add(ambientLight);
// 添加光阵
// let video = document.createElement('video')
// video.src = './model/v天空.mp4'
// video.loop = true
// video.muted = true
// video.play()
// let videoTexture = new THREE.VideoTexture(video)
// const videoPlane = new THREE.PlaneBufferGeometry(30, 30)
// const videoMaterial = new THREE.MeshBasicMaterial({
// map: videoTexture,
// transparent: true,
// side: THREE.DoubleSide,
// alphaMap: videoTexture
// })
// const videoMesh = new THREE.Mesh(videoPlane, videoMaterial)
// videoMesh.position.set(0, 0.2, 0)
// videoMesh.rotation.set(-Math.PI / 2, 0, 0)
// scene.add(videoMesh)
// 添加镜面反射
// let reflectorGeometry = new THREE.PlaneBufferGeometry(1000, 1000)
// let reflectorPlane = new Reflector(reflectorGeometry, {
// textureWidth: window.innerWidth,
// textureHeight: window.innerHeight,
// color: 0xc8c8c8,
// recursion: 1,
// })
// reflectorPlane.rotation.x = -Math.PI / 2
// scene.add(reflectorPlane)
// 设置Raycaster和鼠标事件处理
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
function onMouseClick(event) {
// 将鼠标点击位置转换为标准化设备坐标 (-1 to +1)
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// 更新射线
raycaster.setFromCamera(mouse, camera);
// 计算物体和射线的交集
if (model) {
const intersects = raycaster.intersectObject(model, true);
if (intersects.length > 0) {
const intersectedObject = intersects[0].object;
if (intersectedObject.name.indexOf('Cube') != -1) return
// 将所有子元素设为透明
model.traverse((child) => {
if (child.isMesh) {
if (child.name == intersectedObject.name) {
intersectedObject.material.transparent = false;
intersectedObject.material.opacity = 1.0;
intersectedObject.material.color.set(0xff0000);
moveCameraToObject(child);
} else {
child.material.transparent = true;
child.material.opacity = 0.2;
child.material.color.set(0xffcccc);
}
}
});
}
}
}
window.addEventListener('click', onMouseClick, false);
function moveCameraToObject(target) {
const targetPosition = new THREE.Vector3();
target.getWorldPosition(targetPosition);
// 使用Tween.js进行平滑过渡
new TWEEN.Tween(camera.position)
.to({ x: targetPosition.x, y: targetPosition.y + 10, z: targetPosition.z + 10 }, 1000 * 1)
.easing(TWEEN.Easing.Quadratic.InOut)
.start();
// 更新相机在过渡期间的lookAt
new TWEEN.Tween(camera.lookAt)
.to({ x: targetPosition.x, y: targetPosition.y + 10, z: targetPosition.z + 10 }, 1000 * 1)
.easing(TWEEN.Easing.Quadratic.InOut)
.start();
}
// 设置渲染帧率
const fps = 20;
const interval = 1000 / fps;
let lastTime = Date.now();
function animate() {
requestAnimationFrame(animate);
const currentTime = Date.now();
const deltaTime = currentTime - lastTime;
if (deltaTime > interval) {
// 更新时间戳
lastTime = currentTime - (deltaTime % interval);
// 更新Tween动画
TWEEN.update();
// 渲染场景
renderer.render(scene, camera);
}
}
animate();
</script>
<style>
* {
margin: 0;
padding: 0;
}
canvas {
width: 100vw;
height: 100vh;
}
.convas_container {
width: 100vw;
height: 100vh;
}
</style>
点击变色代码
先给模型的子元素赋值一个纹理,这里需要遍历每个子元素,不然相同的子元素会公用同一个纹理
let model;
gltfLoader.load('./model/shelves.glb', function (glb) {
model = glb.scene;
// 遍历模型的子元素
model.traverse(function (child) {
if (child.isMesh) {
// 给子元素添加纹理
child.material = child.material.clone();
child.material.map = doorColorTexture;
child.material.needsUpdate = true;
child.material.transparent = true;
child.material.opacity = 0.2;
child.material.color.set(0xffcccc);
}
});
//将模型添加至场景
scene.add(glb.scene)
model.position.set(10, 2, 10)
//设置模型位置
camera.position.set(20, 10, 20)
})
点击子模型时,做条件判断,符合则进行更改颜色
// 设置Raycaster和鼠标事件处理
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
function onMouseClick(event) {
// 将鼠标点击位置转换为标准化设备坐标 (-1 to +1)
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// 更新射线
raycaster.setFromCamera(mouse, camera);
// 计算物体和射线的交集
if (model) {
const intersects = raycaster.intersectObject(model, true);
if (intersects.length > 0) {
const intersectedObject = intersects[0].object;
if (intersectedObject.name.indexOf('Cube') != -1) return
// 将所有子元素设为透明
model.traverse((child) => {
if (child.isMesh) {
if (child.name == intersectedObject.name) {
intersectedObject.material.transparent = false;
intersectedObject.material.opacity = 1.0;
intersectedObject.material.color.set(0xff0000);
moveCameraToObject(child);
} else {
child.material.transparent = true;
child.material.opacity = 0.2;
child.material.color.set(0xffcccc);
}
}
});
}
}
}
window.addEventListener('click', onMouseClick, false);
镜头跟随
引入模块
// 相机跟踪
import * as TWEEN from '@tweenjs/tween.js';
function moveCameraToObject(target) {
const targetPosition = new THREE.Vector3();
target.getWorldPosition(targetPosition);
// 使用Tween.js进行平滑过渡
new TWEEN.Tween(camera.position)
.to({ x: targetPosition.x, y: targetPosition.y + 10, z: targetPosition.z + 10 }, 1000 * 1)
.easing(TWEEN.Easing.Quadratic.InOut)
.start();
// 更新相机在过渡期间的lookAt
new TWEEN.Tween(camera.lookAt)
.to({ x: targetPosition.x, y: targetPosition.y + 10, z: targetPosition.z + 10 }, 1000 * 1)
.easing(TWEEN.Easing.Quadratic.InOut)
.start();
}
// 设置渲染帧率
const fps = 20;
const interval = 1000 / fps;
let lastTime = Date.now();
function animate() {
requestAnimationFrame(animate);
const currentTime = Date.now();
const deltaTime = currentTime - lastTime;
if (deltaTime > interval) {
// 更新时间戳
lastTime = currentTime - (deltaTime % interval);
// 更新Tween动画
TWEEN.update();
// 渲染场景
renderer.render(scene, camera);
}
}
animate();
项目所需的npm包
版本号视项目而定
"@tweenjs/tween.js": "^23.1.2",
"core-js": "^3.6.5",
"gsap": "^3.11.4",
"three": "^0.150.1",
"tree": "^0.1.3",
"vue": "^3.0.0"