实现效果:
- 导入高精度导弹车 GLTF 三维模型。
- 鼠标点击模型任意部位,单独高亮变红、半透明。
- 自动识别当前点击部件,右上角展示中文名称(导弹体/主车体/底盘架/轮胎/发射架)。
- 再次点击新部位,自动取消上一次高亮,切换选中效果。
演示动态图效果如下:
核心原理讲解:
1. 射线拾取 Raycaster
ThreeJS 模型点击选中唯一通用方案:射线投射。
简单理解:鼠标点击屏幕 → 生成一条从相机射出的射线 → 穿透场景 → 检测碰到了哪个模型Mesh → 实现选中。
2. GLTF模型结构特点
复杂模型(导弹车、设备、机械模型)都是由多个Mesh子部件组成:
主车体、轮胎、发射架、导弹体、底盘架都是独立Mesh,自带独立 name 属性,我们可以通过 name 映射中文部件名。
3. 高亮逻辑
- 点击新部件:旧部件材质恢复原色、不透明。
- 当前点击部件:改为红色、半透明高亮。
完整代码
1. template 结构:
<template>
<div class="three-container" ref="content">
<!-- three渲染画布挂载容器 -->
<div class="three" id="three"></div>
<!-- 右上角显示选中部件名称面板 -->
<div class="show-block">
<div class="show-title">选中部分:</div>
<div class="show-content">{{ chooseText }}</div>
</div>
</div>
</template>
2. script 代码:
<script>
import * as THREE from 'three'
// gltf模型加载器,用来导入导弹车gltf模型
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
// 轨道控制器:鼠标拖拽旋转、缩放、平移模型
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
export default {
beforeDestroy() {
// 组件销毁生命周期,执行资源释放
this.leaveDestory()
},
mounted() {
this.$nextTick(() => {
// dom渲染完成后初始化threejs场景
this.init()
// 浏览器窗口大小变化监听,适配画布
window.onresize = () => {
this.resize()
}
})
},
data () {
return {
// 右上角展示选中部件的文字
chooseText: '未选中任何部分'
}
},
methods: {
/**
* 组件销毁,释放threejs全部资源,防止内存泄漏
* 清除定时器、销毁几何体材质、销毁渲染器、控制器、场景
*/
leaveDestory() {
document.onclick = null
window.missileMarsLoad = null
// 停止动画帧循环
if (window.myMar) {
cancelAnimationFrame(window.myMar)
window.myMar = null
}
if (window.scene) {
// 遍历场景,释放所有网格的几何体、材质显存
window.scene.traverse(function (v) {
if (v.type === 'Mesh') {
if (v.geometry && v.geometry.dispose) {
v.geometry.dispose()
}
if (v.material && v.material.dispose) {
v.material.dispose()
}
}
})
// 清空场景子元素
if (window.scene.children.length > 0) {
window.scene.remove(window.scene.children[0])
}
// 清空全局缓存变量
window.model = null
window.selectObj = null
// 销毁webgl渲染上下文
window.renderer.dispose()
window.renderer.forceContextLoss()
window.renderer.domElement = null
window.renderer = null
window.camera = null
// 销毁轨道控制器
window.controls.dispose()
window.controls = null
window.onresize = null
window.scene.clear()
window.scene = null
}
},
/**
* 窗口大小改变,更新相机宽高比和画布尺寸,画面自适应
*/
resize() {
let width = this.$refs.content.offsetWidth
let height = this.$refs.content.offsetHeight
window.camera.aspect = width / height
window.camera.updateProjectionMatrix()
window.renderer.setSize(width, height)
},
/**
* threejs初始化入口
* 创建场景、相机、渲染器、灯光、控制器、加载导弹车模型
*/
init() {
let width = this.$refs.content.offsetWidth
let height = this.$refs.content.offsetHeight
// 1.创建3D场景容器
window.scene = new THREE.Scene()
// 2.创建透视相机,45度视场角,近远裁剪面0.1~1000
window.camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
// 3.创建webgl渲染器,开启抗锯齿
window.renderer = new THREE.WebGLRenderer({ antialias: true })
// 适配设备像素比,高分屏画面不模糊
window.renderer.setPixelRatio(window.devicePixelRatio)
// 设置画布背景色,和截图深灰色背景一致
window.renderer.setClearColor(0x2B2F30, 1.0)
window.renderer.setSize(width, height)
// 开启阴影
window.renderer.shadowMapEnabled = true
// 设置相机初始位置
window.camera.position.x = -0.649176390603234
window.camera.position.y = -0.013178514581831093
window.camera.position.z = 1.3688883296014047
window.camera.lookAt(window.scene.position)
// 4.初始化鼠标轨道控制器
window.controls = new OrbitControls(window.camera, window.renderer.domElement)
window.controls.target.set(0.5, 0, 0)
window.controls.update()
window.controls.enablePan = true // 允许平移
window.controls.enableDamping = true // 开启阻尼拖拽,滑动更顺滑
// 5.添加环境半球光,柔和环境照明
let hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.3)
hemiLight.position.set(0, 500, 0)
window.scene.add(hemiLight)
// 聚光灯,产生阴影效果
let spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(-40, 60, -10)
spotLight.castShadow = true
window.scene.add(spotLight)
// 6.实例化gltf加载器,加载导弹发射车模型
let loader = new GLTFLoader()
window.missileMarsLoad = true
loader.load('static/models/missileLauncher.gltf', (gltf) => {
if (window.missileMarsLoad) {
// 把解析完成的模型加入3D场景
window.model = gltf.scene
window.scene.add(window.model)
window.renderer.render(window.scene, window.camera)
// 给画布绑定鼠标点击事件,触发部件选中逻辑
document.onclick = (e) => {
this.choose(e)
}
}
})
// 启动渲染动画循环
this.animate()
// 将渲染生成的canvas dom挂载到页面div
document.getElementById('three').appendChild(window.renderer.domElement)
},
/**
* 鼠标点击拾取模型部件核心函数(射线检测)
* 功能:点击模型,选中mesh变红半透明高亮,右上角显示部件中文名称
* event 鼠标点击事件对象
*/
choose(event) {
event.preventDefault()
// 获取鼠标屏幕坐标
var Sx = event.clientX;
var Sy = event.clientY;
// 将浏览器屏幕坐标 转为 threejs标准化设备坐标(-1 ~ +1)
var x = (Sx / window.innerWidth) * 2 - 1;
var y = -(Sy / window.innerHeight) * 2 + 1;
// 创建射线投射器:从相机位置,穿过鼠标点击的屏幕点发射射线
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(x, y), window.camera);
// 射线与导弹车模型做碰撞检测,返回相交到的物体数组
var intersects = raycaster.intersectObjects([window.model]);
// 判断是否点击到模型物体
if (intersects.length > 0) {
// 如果上一次已经选中物体,把旧物体材质恢复原始状态
if (window.selectObj) {
window.selectObj.object.material.transparent = true;
window.selectObj.object.material.opacity = 1;
window.selectObj.object.material.color = new THREE.Color('rgb(255, 255, 255)');
window.selectObj = null
}
// 保存当前点击相交的物体
window.selectObj = intersects[0]
// 修改当前选中部件材质:半透明+红色高亮
window.selectObj.object.material.transparent = true;
window.selectObj.object.material.opacity = 0.7;
window.selectObj.object.material.color = new THREE.Color('rgb(255, 0, 0)');
// 获取模型mesh的name属性,做部件名称映射
let name = window.selectObj.object.name
console.log(name)
// 根据模型内部mesh名字,映射为业务中文名称,赋值给页面显示
if (name === 'defaultMaterial_9') {
this.chooseText = '导弹体'
} else if (name === 'defaultMaterial') {
this.chooseText = '主车体'
} else if (name === 'defaultMaterial_6') {
this.chooseText = '底盘架'
} else if (name === 'defaultMaterial_4' || name === 'defaultMaterial_5') {
this.chooseText = '轮胎'
} else if (name === 'defaultMaterial_7') {
this.chooseText = '发射架'
}
}
},
/**
* requestAnimationFrame动画循环
* 持续更新控制器状态、执行场景渲染
*/
animate() {
window.myMar = requestAnimationFrame(this.animate)
window.controls.update()
window.renderer.render(window.scene, window.camera)
}
}
}
</script>
3. css样式代码:
.three-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.three {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
}
.show-block {
width: 300px;
box-sizing: border-box;
padding: 20px;
position: absolute;
right: 20px;
top: 20px;
z-index: 2;
background-color: rgba(31, 31, 31, 0.8);
color: #FFF;
}
.show-title {
font-size: 22px;
line-height: 22px;
font-weight: bold;
}
.show-content {
font-size: 20px;
line-height: 20px;
margin-top: 20px;
}
功能详细拆解:
1. 模型加载
使用官方 GLTFLoader 加载外部模型,支持 gltf / glb 通用格式,工业模型、设备模型、场景模型全部通用。
2. 射线拾取核心逻辑
将鼠标屏幕坐标转为 WebGL 标准设备坐标,通过 Raycaster 发射射线,与模型所有子 Mesh 做碰撞检测,精准拾取最小部件。
3. 高亮切换机制
- 每次新点击,优先恢复上一次选中部件的颜色、透明度。
- 当前部件改为 红色半透明高亮,视觉层级非常明显。
4. 部件名称映射
复杂模型每个子Mesh都有独立name,通过判断name,前端自定义中文释义,实现“点击即识别部件”的仿真效果。
总结:
本文实现了 ThreeJS 三维模型精细化部件拾取核心能力: 模型加载 → 射线拾取 → 动态高亮变色 → 部件名称识别展示 → 内存优化销毁。
模型说明: 本文演示所用的 3D 模型来源于网络,版权归原作者所有。本文仅做 Three.js 前端技术学习演示,并未对模型本身做任何修改。