Babylon加载本地gltf模型并完成一些基础的点击操作
加载模型
本文使用的是vue3+vite搭建的环境,在搭建好环境后安装babylon
npm install --save @babylonjs/core
然后按需引入babylon模块
import {
Engine,
Scene,
Vector3,
Vector2,
HemisphericLight,
ArcRotateCamera,
SceneLoader,
HighlightLayer,
Color3,
Mesh,
StandardMaterial,
PBRMaterial
} from '@babylonjs/core'
当然也可以全部导入
npm install --save babylonjs
import * as BABYLON from "babylonjs";
要使用babylon加载模型到web页面上,首先要先创建一个画布
<div id="renderCanvasContainer">
<canvas id="renderCanvas" ref="renderCanvas"></canvas>
</div>
然后获取这个dom元素(原生JS)
var canvas = document.getElementById('renderCanvas');
vue3也可以这么写(基于创建画布时的ref标签获取)
const renderCanvas = ref(null)
const canvas = renderCanvas.value
而后实例化一个渲染引擎,并创建场景
var engine = new BABYLON.Engine(canvas, true);
const scene = new Scene(engine)
特别的你可以让页面多一个调试面板,只需在场景创建后调用debug,但是要安装@babylonjs/inspector包,如果你是分模块导入的话,如果是全部导入就不需要了
npm install --save @babylonjs/inspector
然后在刚刚实例化的scene下加入下面这句代码就行了
scene.debugLayer.show();
而后我们需要创建一个相机
const camera = new ArcRotateCamera(
'camera', // 相机的名称
Math.PI / 6, // 水平旋转角度
Math.PI / 4, // 垂直旋转角度
2000, // 相机到目标点的距离
new Vector3(0, 0, 0), // 目标点
scene // 相机所在的场景(刚刚创建的场景)
)
下一步将相机绑定到画布上,相当于把刚才创建的相机安装到指定位置,使得用户能够通过鼠标、键盘来控制相机。
camera.attachControl(canvas, true);//第一个参数是html元素,最开始我们创建的画布,第二个是布尔值
好了有了场景和相机后下面请需要开灯,这样场景就亮起来了
const light = new HemisphericLight(
'light', // 光源的名称,用于标识这个光源
new Vector3(1, 1, 0), // 光的方向,这是一个向量,表示光从 (1, 1, 0) 方向投射到场景中
scene // 光源所在的场景
);
light.intensity = 0.7; // 设置光照强度为 0.7
这样一切准备就绪就可以导入我们的本地模型了,把模型放入public文件夹,最好在新建一个model,把gltf所有文件放进去
SceneLoader.ImportMesh(
'', // 要导入的特定网格的名称,空字符串表示导入所有网格
'/model/', // 模型文件的路径
'yy.gltf', // 模型文件的名称
scene, // 要将模型导入到的目标场景
function (meshes) { // 回调函数,处理加载完成后的操作
// meshs是模型中的所有网格,是模型的基本组成后续要实现各种交互需要了解。
// 一会的鼠标监听事件就写在这里
console.log('模型已加载', meshes);
}
)
然后启动一个渲染循环,确保你在更新场景后重新渲染场景
engine.runRenderLoop(function () {
scene.render();
});
然后加一个窗口大小改变事件,是的浏览器窗口改变后引擎和画布大小也相应调整
window.addEventListener('resize', function () {
engine.resize();
});
以上就完成了本地模型的加载显示
控制模型某个部分的显示与隐藏
想要对模型进行操作,比如对某一部分进行显示与隐藏,高亮或改变颜色,你首先要了解gltf的基本构成,这个大家可以从刚才打开的debug的左侧面板观察
就比如我这个模型是个医院场景,我想控制楼层的显示与隐藏,那么我就要先获取医院节点,然后获取医院节点下的所有子节点,你自己看下楼层(或者你想控制的mesh的name)设计一个逻辑,控制楼层的显示与隐藏,下面是我的例子
import { Scene } from '@babylonjs/core';
export default class FloorController {
scene:Scene; // 声明scene是个Babylon场景类型变量
constructor(scene) { // 这个类接受一个scene并传递给实例属性this.scene
this.scene = scene;
}
chooseFloor(num) {
this.changeFloorEnabled(num);
this.changeOtherEnabled(!Boolean(num));
}
changeFloorEnabled(num) {
const name = 'F' + num;
const hosp = this.scene.getTransformNodeByName('hosp');
const children = hosp?.getChildren();
if (!num && children) {
this.showFacade(children);
return;
}
if (children) {
for (const iterator of children) {
iterator.setEnabled(iterator.name === name);
}
}
}
showFacade(list) {
if (list) {
for (const iterator of list) {
iterator.setEnabled(true);
}
}
}
changeOtherEnabled(bool) {
const circumstances = this.scene.getTransformNodeByName('circumstances');
if (circumstances) {
circumstances.setEnabled(bool);// 控制节点的显示与交互
}
}
}
changeOtherEnabled(bool) {
const circumstances = scene.getTransformNodeByName('circumstances');
if (circumstances) {
circumstances.setEnabled(bool);// 控制节点的显示与交互
}
}
然后你可以设计一个点击事件来执行这个操作,比如设计一个按钮列表控制楼层的显示和隐藏
<div id="buttonContainer">
<el-button
v-for="floor in Array.from({ length: 15 }, (v, k) => k + 1).reverse()"
:key="floor"
type="primary"
@click="handleFloorClick(floor)"
class="floor-button"
>
{{ floor }}楼
</el-button>
</div>
然后是这个点击函数,不过之前我们把显示和隐藏的逻辑写到了另一个ts文件中,这里使用需要先引入
import FloorController from '../scenes/hospitalController' // 根据实际路径调整
const handleFloorClick = (floor) => {
console.log(`Clicked ${floor}楼`)
if (floorController.value) {
floorController.value.chooseFloor(floor) // 分别调用changeFloorEnabled和changeOtherEnabled设置选中的楼层可见和其他场景不可见
}
}
添加模型的点击事件
假如我们想获取我们点击模型那部分网格的属性信息,并且改变他的颜色,我们可以在引入模型时添加监听器,还记得刚刚导入模型时的回调函数吗,具体的实现逻辑就写在这里面
SceneLoader.ImportMesh(
'', // 要导入的特定网格的名称,空字符串表示导入所有网格
'/model/', // 模型文件的路径
'yy.gltf', // 模型文件的名称
scene, // 要将模型导入到的目标场景
(meshes) => { // 导入完成后的回调函数
// 添加点击事件监听器
canvas.addEventListener('pointerdown', (evt) => {
// 使用 scene.pick 获取鼠标点击的网格
const pickResult = scene.pick(scene.pointerX, scene.pointerY);
// 检查是否拾取到网格
if (pickResult.hit) {
const pickedMesh = pickResult.pickedMesh;
console.log('Picked mesh:', pickedMesh);
// 打印网格的所有属性
console.log('Mesh name:', pickedMesh.name);
console.log('Mesh position:', pickedMesh.position);
console.log('Mesh rotation:', pickedMesh.rotation);
console.log('Mesh scaling:', pickedMesh.scaling);
console.log('Mesh material:', pickedMesh.material);
// 如果之前有选中的网格,恢复其原始颜色
if (selectedMesh && selectedMesh !== pickedMesh) {
if (selectedMesh.material) {
if (selectedMesh.material instanceof PBRMaterial) {
selectedMesh.material.albedoColor = originalColors.get(selectedMesh).clone();
} else if (selectedMesh.material instanceof StandardMaterial) {
selectedMesh.material.diffuseColor = originalColors.get(selectedMesh).clone();
}
}
}
// 记录当前选中的网格及其原始颜色
selectedMesh = pickedMesh;
if (!originalColors.has(pickedMesh)) {
if (pickedMesh.material) {
if (pickedMesh.material instanceof PBRMaterial) {
originalColors.set(pickedMesh, pickedMesh.material.albedoColor.clone());
} else if (pickedMesh.material instanceof StandardMaterial) {
originalColors.set(pickedMesh, pickedMesh.material.diffuseColor.clone());
}
}
}
// 修改选中网格的颜色
if (pickedMesh.material) {
if (pickedMesh.material instanceof PBRMaterial) {
pickedMesh.material.albedoColor = new Color3(0, 1, 0); // 绿色
} else if (pickedMesh.material instanceof StandardMaterial) {
pickedMesh.material.diffuseColor = new Color3(0, 1, 0); // 绿色
}
} else {
// 如果没有材质,创建一个新的 StandardMaterial 并应用
const material = new StandardMaterial('newMaterial', scene);
material.diffuseColor = new Color3(0, 1, 0); // 绿色
pickedMesh.material = material;
originalColors.set(pickedMesh, material.diffuseColor.clone());
}
}
});
}
);
当然你需要定义一些变量
const originalColors = new Map(); // 存储训中mesh的颜色
let selectedMesh = null; // 存储选中的mesh
这样应该就没问题了