![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/13b9193d6738428791fc1ff112e03627.png
加载模型的时候需要把模型放在public文件下面
创建场景
this.scene = new THREE.Scene()
创建相机
this.camera = new THREE.PerspectiveCamera(
45,
this.viewNode.clientWidth / this.viewNode.clientHeight,
1,
9999999
)
调整相机位置
this.camera.position.set(600, 800, 1000)
this.camera.lookAt(0, 0, 0)
创建渲染器
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
})
this.renderer.setSize(this.viewNode.clientWidth, this.viewNode.clientHeight)
this.renderer.setPixelRatio(window.devicePixelRatio)
this.renderer.shadowMap.enabled = true // 启用阴影
this.viewNode.appendChild(this.renderer.domElement)
添加轨道控制器
// 添加轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
this.controls.enableDamping = true
this.controls.dampingFactor = 0.05
this.controls.maxPolarAngle = Math.PI * 0.45
this.controls.minPolarAngle = Math.PI * 0.1
this.controls.minDistance = 5
this.controls.maxDistance = 30
this.controls.enableRotate = false // 禁止相机旋转
this.controls.enableZoom = false // 禁止相机缩放
this.controls.enablePan = false // 禁止相机平移
this.initLights()
this.initWater()
this.initSky()
this.loadGLTFModel()
},
天空盒子
initSky() {
this.sky = new Sky()
this.sky.name='sky'
this.sky.scale.setScalar(999999) // 调整天空盒子的大小
this.scene.add(this.sky)
const skyUniforms = this.sky.material.uniforms
skyUniforms['turbidity'].value = 10
skyUniforms['rayleigh'].value = 2
skyUniforms['mieCoefficient'].value = 0.005
skyUniforms['mieDirectionalG'].value = 0.8
const parameters = {
elevation: 2,
azimuth: 180
}
const pmremGenerator = new THREE.PMREMGenerator(this.renderer)
const phi = THREE.MathUtils.degToRad(90 - parameters.elevation)
const theta = THREE.MathUtils.degToRad(parameters.azimuth)
const sun = new THREE.Vector3()
sun.setFromSphericalCoords(1, phi, theta)
this.sky.material.uniforms['sunPosition'].value.copy(sun)
},
添加模型的点击事件
onMouseClick(event) {
const mouse = new THREE.Vector2()
mouse.x = (event.clientX / this.viewNode.clientWidth) * 2 - 1
mouse.y = -(event.clientY / this.viewNode.clientHeight) * 2 + 1
const raycaster = new THREE.Raycaster()
raycaster.setFromCamera(mouse, this.camera)
console.log(';点击了模型上的某一个点点击了模型上的某一个点点击了模型上的某一个点');
const intersects = raycaster.intersectObjects(this.scene.children, true)
if (intersects.length > 0) {
console.log(';点击了模型上的某一个点点击了模型上的某一个点点击了模型上的某一个点');
const baifenDom = document.querySelector('.baifen')
const intersect = intersects[0]
console.log(intersect,'intersect-*-*-*--*');
// if (intersect.object.type === this.model) {
if (intersect.object.type === "Mesh" && intersect.object.parent.type === "Group") {
console.log(this.icondom,'icon-*-*-*--*');
console.log(this.divdom,'divdivdiv-*-*-*--*');
// // 清除上一次点击的所有操作
this.icondom&&this.scene.remove(this.icondom);
this.divdom&&baifenDom.removeChild(this.divdom)
// 创建icon图标
const iconGeometry = new THREE.SphereGeometry(0.5, 60, 60);
const iconMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
this.icondom = new THREE.Mesh(iconGeometry, iconMaterial);
this.icondom.position.set(intersect.point.x, intersect.point.y, intersect.point.z);
this.scene.add(this.icondom);
console.log( intersect,'baifenDombaifenDom');
this.divdom = document.createElement('div')
this.divdom.style.position = 'absolute'
this.divdom.style.width = '200px'
this.divdom.style.height = '200px'
this.divdom.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
this.divdom.style.color = 'white'
this.divdom.style.padding = '10px'
this.divdom.style.borderRadius = '5px'
this.divdom.style.zIndex = '1000'
this.divdom.style.top = `${event.clientY}px`
this.divdom.style.left = `${event.clientX}px`
this.divdom.innerHTML = `点击了模型上的某一个点,位置为: (${intersect.point.x}, ${intersect.point.y}, ${intersect.point.z})`
baifenDom.appendChild(this.divdom)
// 调整相机位置以拉近模型
// this.camera.position.set(intersect.point.x, intersect.point.y, intersect.point.z + 10); // 将相机位置设置为点击点的位置,并在z轴上偏移10单位以拉近模型
// this.camera.lookAt(intersect.point); // 让相机看向点击点
}
}
},
完整代码
<template>
<div class="contente">
<div class="baifen">
<div class="app-wrap" ref="view"></div>
</div>
</div>
</template>
<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { Water } from 'three/examples/jsm/objects/Water.js'
import { Sky } from 'three/examples/jsm/objects/Sky.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
// 导入glb模型
// const gltfModel = './models/dapeng2.glb' // 改为使用公共路径
export default {
name: 'MineTd',
data() {
return {
viewNode: null,
animationId: null,
scene: null,
camera: null,
renderer: null,
controls: null,
water: null,
sky: null,
model: null,
mixer: null, // 用于动画混合器
moveForward: false,
moveBackward: false,
moveLeft: false,
moveRight: false,
moveSpeed: 0.05, // 相机移动速度
icondom: null,divdom: null,
}
},
mounted() {
this.$nextTick(() => {
this.initScene()
this.animate()
})
window.addEventListener('resize', this.onWindowResize)
document.addEventListener('keydown', this.onKeyDown)
document.addEventListener('keyup', this.onKeyUp)
this.$refs.view.addEventListener('click', this.onMouseClick)
},
methods: {
initScene() {
this.viewNode = this.$refs.view
// 创建场景
this.scene = new THREE.Scene()
// 创建相机
this.camera = new THREE.PerspectiveCamera(
45,
this.viewNode.clientWidth / this.viewNode.clientHeight,
1,
9999999
)
// // 调整相机位置
this.camera.position.set(600, 800, 1000)
this.camera.lookAt(0, 0, 0)
// 创建渲染器
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
})
this.renderer.setSize(this.viewNode.clientWidth, this.viewNode.clientHeight)
this.renderer.setPixelRatio(window.devicePixelRatio)
this.renderer.shadowMap.enabled = true // 启用阴影
this.viewNode.appendChild(this.renderer.domElement)
// 添加轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
this.controls.enableDamping = true
this.controls.dampingFactor = 0.05
this.controls.maxPolarAngle = Math.PI * 0.45
this.controls.minPolarAngle = Math.PI * 0.1
this.controls.minDistance = 5
this.controls.maxDistance = 30
this.controls.enableRotate = false // 禁止相机旋转
this.controls.enableZoom = false // 禁止相机缩放
this.controls.enablePan = false // 禁止相机平移
this.initLights()
this.initWater()
this.initSky()
this.loadGLTFModel()
},
initLights() {
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6)
this.scene.add(ambientLight)
// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
directionalLight.position.set(10, 10, 10)
directionalLight.castShadow = true // 启用阴影投射
this.scene.add(directionalLight)
},
initWater() {
const waterGeometry = new THREE.PlaneGeometry(100, 100)
this.water = new Water(waterGeometry, {
textureWidth: 512,
textureHeight: 512,
waterNormals: new THREE.TextureLoader().load(
'https://threejs.org/examples/textures/waternormals.jpg',
texture => {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping
}
),
alpha: 1.0,
waterColor: 0x001e0f,
distortionScale: 3.7,
fog: this.scene.fog !== undefined,
depth: 10,
flowDirection: new THREE.Vector2(1, 0.1),
scale: 4.0
})
this.water.rotation.x = -Math.PI / 2
this.water.receiveShadow = true // 接收阴影
this.water.name = 'water' // 设置name
this.scene.add(this.water)
},
initSky() {
this.sky = new Sky()
this.sky.name='sky'
this.sky.scale.setScalar(999999) // 调整天空盒子的大小
this.scene.add(this.sky)
const skyUniforms = this.sky.material.uniforms
skyUniforms['turbidity'].value = 10
skyUniforms['rayleigh'].value = 2
skyUniforms['mieCoefficient'].value = 0.005
skyUniforms['mieDirectionalG'].value = 0.8
const parameters = {
elevation: 2,
azimuth: 180
}
const pmremGenerator = new THREE.PMREMGenerator(this.renderer)
const phi = THREE.MathUtils.degToRad(90 - parameters.elevation)
const theta = THREE.MathUtils.degToRad(parameters.azimuth)
const sun = new THREE.Vector3()
sun.setFromSphericalCoords(1, phi, theta)
this.sky.material.uniforms['sunPosition'].value.copy(sun)
},
loadGLTFModel() {
const loader = new GLTFLoader()
const loadingManager = new THREE.LoadingManager()
// 加载管理器
loadingManager.onProgress = (url, loaded, total) => {
const progress = (loaded / total * 100).toFixed(2)
console.log(`加载进度: ${progress}%`)
}
// 加载GLB模型
loader.load(
"/models/dapeng2.gltf", // 修改为.glb文件
(gltf) => {
this.model = gltf.scene
this.model.name = 'dapeng'
// 遍历模型网格启用阴影
this.model.traverse((node) => {
if (node.isMesh) {
node.castShadow = true
node.receiveShadow = true
}
})
// 调整模型位置和大小
this.model.position.set(0, 1, 0) // 将模型位置调整到相机的正下方
this.model.scale.set(0.2, 0.2, 0.2)
// 将模型添加到场景
this.scene.add(this.model)
// 处理动画
if (gltf.animations && gltf.animations.length) {
this.mixer = new THREE.AnimationMixer(this.model)
gltf.animations.forEach((clip) => {
const action = this.mixer.clipAction(clip)
action.play()
})
}
const bbox = new THREE.Box3().setFromObject(this.model);
const center = bbox.getCenter(new THREE.Vector3());
// this.camera.position.set(center.x+1000, center.y+1000, center.z+600);
// this.camera.lookAt(scene.position); // 让相机看向场景中心
},
(xhr) => {
console.log(`模型加载中: ${(xhr.loaded / xhr.total * 100)}%`)
},
(error) => {
console.error('模型加载失败:', error)
this.$message.error('模型加载失败,请检查模型文件是否存在')
}
)
},
animate() {
this.animationId = requestAnimationFrame(this.animate)
// 更新水面动画
if (this.water) {
this.water.material.uniforms['time'].value += 1.0 / 60.0
}
// 更新模型动画
if (this.mixer) {
this.mixer.update(0.016) // 假设60fps
}
this.controls.update()
this.renderer.render(this.scene, this.camera)
// 根据键盘输入移动相机
if (this.moveForward) {
this.camera.position.z -= this.moveSpeed
}
if (this.moveBackward) {
this.camera.position.z += this.moveSpeed
}
if (this.moveLeft) {
this.camera.position.x -= this.moveSpeed
}
if (this.moveRight) {
this.camera.position.x += this.moveSpeed
}
},
onMouseClick(event) {
const mouse = new THREE.Vector2()
mouse.x = (event.clientX / this.viewNode.clientWidth) * 2 - 1
mouse.y = -(event.clientY / this.viewNode.clientHeight) * 2 + 1
const raycaster = new THREE.Raycaster()
raycaster.setFromCamera(mouse, this.camera)
console.log(';点击了模型上的某一个点点击了模型上的某一个点点击了模型上的某一个点');
const intersects = raycaster.intersectObjects(this.scene.children, true)
if (intersects.length > 0) {
console.log(';点击了模型上的某一个点点击了模型上的某一个点点击了模型上的某一个点');
const baifenDom = document.querySelector('.baifen')
const intersect = intersects[0]
console.log(intersect,'intersect-*-*-*--*');
// if (intersect.object.type === this.model) {
if (intersect.object.type === "Mesh" && intersect.object.parent.type === "Group") {
console.log(this.icondom,'icon-*-*-*--*');
console.log(this.divdom,'divdivdiv-*-*-*--*');
// // 清除上一次点击的所有操作
this.icondom&&this.scene.remove(this.icondom);
this.divdom&&baifenDom.removeChild(this.divdom)
// 创建icon图标
const iconGeometry = new THREE.SphereGeometry(0.5, 60, 60);
const iconMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
this.icondom = new THREE.Mesh(iconGeometry, iconMaterial);
this.icondom.position.set(intersect.point.x, intersect.point.y, intersect.point.z);
this.scene.add(this.icondom);
console.log( intersect,'baifenDombaifenDom');
this.divdom = document.createElement('div')
this.divdom.style.position = 'absolute'
this.divdom.style.width = '200px'
this.divdom.style.height = '200px'
this.divdom.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
this.divdom.style.color = 'white'
this.divdom.style.padding = '10px'
this.divdom.style.borderRadius = '5px'
this.divdom.style.zIndex = '1000'
this.divdom.style.top = `${event.clientY}px`
this.divdom.style.left = `${event.clientX}px`
this.divdom.innerHTML = `点击了模型上的某一个点,位置为: (${intersect.point.x}, ${intersect.point.y}, ${intersect.point.z})`
baifenDom.appendChild(this.divdom)
// 调整相机位置以拉近模型
// this.camera.position.set(intersect.point.x, intersect.point.y, intersect.point.z + 10); // 将相机位置设置为点击点的位置,并在z轴上偏移10单位以拉近模型
// this.camera.lookAt(intersect.point); // 让相机看向点击点
}
}
},
onKeyDown(event) {
switch (event.key) {
case 'w':
this.moveForward = true
break
case 's':
this.moveBackward = true
break
case 'a':
this.moveLeft = true
break
case 'd':
this.moveRight = true
break
}
},
onKeyUp(event) {
switch (event.key) {
case 'w':
this.moveForward = false
break
case 's':
this.moveBackward = false
break
case 'a':
this.moveLeft = false
break
case 'd':
this.moveRight = false
break
}
},
onWindowResize() {
if (this.viewNode) {
this.camera.aspect = this.viewNode.clientWidth / this.viewNode.clientHeight
this.camera.updateProjectionMatrix()
this.renderer.setSize(this.viewNode.clientWidth, this.viewNode.clientHeight)
}
}
},
beforeDestroy() {
window.removeEventListener('resize', this.onWindowResize)
document.removeEventListener('keydown', this.onKeyDown)
document.removeEventListener('keyup', this.onKeyUp)
if (this.animationId) {
cancelAnimationFrame(this.animationId)
}
if (this.scene) {
this.scene.clear()
}
if (this.renderer) {
this.renderer.dispose()
this.renderer.forceContextLoss()
this.renderer.content = null
}
if (this.mixer) {
this.mixer.stopAllAction()
}
}
}
</script>
<style lang="scss" scoped>
.contente {
width: 100%;
height: 100%;
position: relative;
.baifen {
background: #fff;
height: 100%;
position: relative;
.app-wrap {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
}
}
</style>