在网上看了一段时间threejs尝试加载gltf模型并为模型添加标签,点击高亮等效果。
文中不足之处请各位大佬斧正,感谢!!!
直接上代码
<template>
<div id="appPage">
<main>
<el-tree
ref="tree"
:props="props"
:data="treeArr"
node-key="id"
show-checkbox
@check="checked"
highlight-current
default-expand-all
>
</el-tree>
<div id="bimContainer" ref="bimContainer">
<el-progress
:text-inside="true"
:stroke-width="30"
:percentage="loadProgress"
color="#7fbaf1"
text-color="#333"
stroke-linecap="square"
v-if="isShowProgress"
:format="formatProgress"
></el-progress>
</div>
</main>
</div>
</template>
<script>
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import * as BufferGeometryUtils from "three/addons/utils/BufferGeometryUtils.js";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass";
import {
CSS3DObject,
CSS3DRenderer,
} from "three/examples/jsm/renderers/CSS3DRenderer";
const models = [
{
name: "#1号楼",
path: "/model/xpxm/xp-1.glb",
position: [0, 0, 0],
type: "glb",
},
];
export default {
data() {
return {
loadProgress: 0,
isShowProgress: true,
props: {
label: "name",
children: "children",
},
treeArr: [],
};
},
watch: {
loadProgress(value) {
if (value >= 100) {
setTimeout(() => {
this.isShowProgress = false;
}, 1500);
}
},
},
mounted() {
this.init();
this.loadModel();
window.onresize = () => {
this.renderer.setSize(
this.$refs.bimContainer.clientWidth,
this.$refs.bimContainer.clientHeight
);
this.camera.aspect =
this.$refs.bimContainer.clientWidth /
this.$refs.bimContainer.clientHeight;
this.camera.updateProjectionMatrix();
};
},
methods: {
themeChange(e) {
this.theme = e;
},
init() {
// 1. 创建场景
this.scene = new THREE.Scene();
// 加载天空盒
this.scene.background = new THREE.CubeTextureLoader().load([
// 右 左 上 下 前 后
"/model/skyTexture/1.jpg", //右
"/model/skyTexture/2.jpg", //左
"/model/skyTexture/3.jpg", //上
"/model/skyTexture/4.jpg", //下
"/model/skyTexture/5.jpg", //前
"/model/skyTexture/6.jpg", //后
]);
// 2. 添加透视投影相机
this.camera = new THREE.PerspectiveCamera(
50, // 视野角度
this.$refs.bimContainer.clientWidth /
this.$refs.bimContainer.clientHeight, // 画布宽高比
0.1, // 近裁截面距相机距离
10000 // 远裁截面距相机距离
);
this.camera.position.set(0, 20, 100);
this.camera.lookAt(this.scene.position);
// this.scene.add(new THREE.AxesHelper(100));
// 3. 添加渲染器
this.renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿
});
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(
this.$refs.bimContainer.clientWidth,
this.$refs.bimContainer.clientHeight
);
this.renderer.render(this.scene, this.camera);
this.$refs.bimContainer.appendChild(this.renderer.domElement);
this.renderer.setClearAlpha(1);
this.renderer.outputEncoding = THREE.sRGBEncoding;
// 添加光
const ambient = new THREE.AmbientLight(0xffffff, 100);
this.scene.add(ambient);
// 4. 添加相机控件
this.control = new OrbitControls(this.camera, this.renderer.domElement);
this.control.addEventListener("change", () => {
this.renderer.render(this.scene, this.camera);
this.labelRenderer.render(this.scene, this.camera);
});
},
// 模型居中
autoCenter(group) {
// 创建包围盒包裹需要居中的模型
let box3 = new THREE.Box3();
box3.expandByObject(group);
let vector3 = new THREE.Vector3();
box3.getCenter(vector3); // 返回包围盒的中心点并拷贝到vector上
group.position.x = group.position.x - vector3.x;
group.position.y = group.position.y - vector3.y;
group.position.z = group.position.z - vector3.z;
box3.makeEmpty();
},
// 加载模型
loadModel() {
const group = new THREE.Group();
group.name = '项目效果展示';
const manager = new THREE.LoadingManager();
let gltfLoader = new GLTFLoader(manager);
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/gltf/");
gltfLoader.setDRACOLoader(dracoLoader);
models.forEach((modelItem) => {
gltfLoader.load(modelItem.path, (loadedModel) => {
const mergedModel = this.merge(loadedModel.scene);
mergedModel.name = modelItem.name;
group.add(mergedModel);
this.autoCenter(group);
});
});
manager.onProgress = (url, itemsLoaded, itemsTotal) => {
this.loadProgress = parseInt((itemsLoaded / itemsTotal) * 100);
this.treeArr.push(group);
this.setChecked();
group.receiveShadow = true;
this.scene.add(group);
this.renderer.render(this.scene, this.camera);
group.children.forEach((item) => {
console.log(item);
this.scene.add(this.addLabel(item.name, item.position));
});
this.outline();
};
manager.onError = (url) => {
console.log("There was an error loading " + url);
};
},
merge(object) {
const geometries = [];
const matArr = [];
var meshs = new THREE.BufferGeometry();
object.children.forEach((child) => {
if (child.isMesh) {
const geo = child.geometry.clone();
// child.frustumCulled = false;
// child.material.side = THREE.DoubleSide;
// //模型阴影
// child.castShadow = true;
// child.receiveShadow = true;
//模型自发光
// child.material.emissive = child.material.color;
// child.material.emissiveMap = child.material.map;
child.material.alphaTest = 1;
child.renderOrder = 1; // 设置一个较高的值确保透明物体在不透明物体之后渲染
if (Array.isArray(child.material)) {
child.material = child.material[0];
child.material.transparent = false;
// 设置透明度为0.5 (例如)
child.material.opacity = 1;
} else {
child.material = new THREE.MeshPhongMaterial({
color: 0xffffff,
transparent: false,
opacity: 1,
});
}
matArr.push(child.material);
geo.index = null;
child.updateWorldMatrix(true, true);
geo.applyMatrix4(child.matrixWorld);
geometries.push(geo);
}
});
const mergeGeometries = BufferGeometryUtils.mergeGeometries(
geometries,
true
);
const singleMergeMesh = new THREE.Mesh(mergeGeometries, matArr);
return singleMergeMesh;
},
// 添加光源
addLight() {
this.Lights = [
//平行光(DirectionalLight)
// color 光的颜色。 缺省值为 0xffffff (白色)。intensity - (可选参数) 光照的强度。缺省值为1。
{ name: "AmbientLight", obj: new THREE.AmbientLight(0xffffff, 1.5) },
{
name: "PointLight",
obj: new THREE.PointLight(0xffffff, 1),
position: [6, 2, 8],
},
{
name: "DirectionalLight_top",
obj: new THREE.DirectionalLight(0xefefef, 1),
position: [-100, 100, 100],
},
{
name: "DirectionalLight_bottom",
obj: new THREE.DirectionalLight(0x1b1b1b, 1.2),
position: [100, -100, -100],
},
{
name: "DirectionalLight_right1",
obj: new THREE.DirectionalLight(0xffffff, 1),
position: [10, 80, 65],
},
{
name: "DirectionalLight_right2",
obj: new THREE.DirectionalLight(0xffffff, 1.2),
position: [10, 80, 65],
},
];
this.Lights.map((item) => {
item.obj.name = item.name;
item.position && item.obj.position.set(...item.position);
item.Helper = new THREE.PointLightHelper(item.obj);
item.castShadow = true;
this.scene.add(item.obj);
});
},
formatProgress(progress) {
return `模型加载中 ${progress}%`;
},
checked(checkedItem) {
let isChecked = this.$refs.tree.getNode(checkedItem).checked;
checkedItem.visible = isChecked;
this.renderer.render(this.scene, this.camera);
},
setChecked() {
let checkedArr = [];
this.treeArr.forEach((item) => {
if (item.visible) {
checkedArr.push(item.id);
if (item.children)
item.children.forEach((i) => {
if (i.visible) checkedArr.push(i.id);
});
}
});
this.$refs.tree.setCheckedKeys(checkedArr);
},
// 发光外边缘
outline() {
this.outlineComposer = new EffectComposer(this.renderer); // 轮廓渲染器
// 新建一个场景通道 为了覆盖到原理来的场景上
this.outlineComposer.addPass(new RenderPass(this.scene, this.camera)); //物体发光通道
this.outlinePass = new OutlinePass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
this.scene,
this.camera
);
this.outlinePass.edgeStrength = 2; // 边框的亮度
this.outlinePass.edgeGlow = 1; // 光晕[0,1]
this.outlinePass.edgeThickness = 2; // 边框宽度
this.outlinePass.pulsePeriod = 3; // 呼吸闪烁的速度
this.outlinePass.visibleEdgeColor.set(0x00ffff);
this.outlinePass.hiddenEdgeColor.set(0xdfff14);
this.outlineComposer.addPass(this.outlinePass);
this.raycaster = new THREE.Raycaster();
this.renderer.domElement.addEventListener("click", this.handlerOutline);
},
handlerOutline(event) {
this.mouse = {};
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 设置射线起点为鼠标位置,射线的方向为相机视角方向
this.raycaster.setFromCamera(this.mouse, this.camera);
// 计算射线相交
const intersects = this.raycaster.intersectObjects(this.scene.children);
console.log(intersects);
if (intersects.length > 0) {
// 选中物体
const selectedObject = intersects[0].object;
this.outlinePass.selectedObjects = [selectedObject];
this.outlineComposer.render();
}
},
// 给模型添加标签
addLabel(labelName, vector) {
const textDiv = document.createElement("div");
textDiv.className = "labelName";
textDiv.innerHTML = `<div class="modelTag">${labelName}</div>`;
const textLabel = new CSS3DObject(textDiv);
textLabel.position.set(vector.x, vector.y + 40, vector.z);
textLabel.scale.set(0.1, 0.1, 0.1);
this.scene.add(textLabel);
this.labelRenderer = new CSS3DRenderer();
this.labelRenderer.setSize(
this.$refs.bimContainer.offsetWidth,
this.$refs.bimContainer.offsetHeight
);
this.labelRenderer.domElement.style.position = "absolute";
this.labelRenderer.domElement.style.top = 0;
this.labelRenderer.domElement.style.left = 0;
this.labelRenderer.domElement.style.pointerEvents = "none";
this.$refs.bimContainer.appendChild(this.labelRenderer.domElement);
this.labelRenderer.render(this.scene, this.camera);
},
},
};
</script>
<style lang="less" scoped>
#appPage{
width: 100%;
height: 100%;
background: var(--app-page-bgc) repeat;
background-size: 100% 100%;
overflow: hidden;
}
#appPage #bimContainer {
width: 100%;
height: 100%;
position: relative;
}
#appPage .el-progress {
width: 350px;
height: 80px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
#appPage main {
position: relative;
}
#appPage.el-tree {
width: 320px;
background-color: rgba(0, 0, 0, 0.1);
color: #fff;
position: absolute;
left: 80px;
top: 20px;
z-index: 999;
border-radius: 8px;
}
.el-tree-node.is-current > .el-tree-node__content {
background-color: rgba(48, 61, 73, 0.2) !important;
}
#appPage .el-tree-node__content {
&:hover {
background-color: transparent !important;
}
}
.modelTag {
color: #fff !important;
font-size: 24px;
}
</style>
2184

被折叠的 条评论
为什么被折叠?



