threejs封装加载 .glb 格式模型,修改贴图

组件

<template>
	<div id="modelCreation"><slot></slot></div>
</template>
<script setup lang="ts">
/**
 * 组件使用
 * 1、安装依赖:npm install three -D
 * 2、使用的页面引用组件
 * 3、引入加载的模型,浏览器会打印通道数据
 * 4、通过定义好的类、函数修改贴图
 */
/**
 * class类 Init 参数说明
 * @param el dom节点
 * @param list 模型渲染贴纸
 * @param modelUrl 模型地址
 * @param speed 旋转速度
 * @param size 模型大小
 * @param vector 模型 x,y,z分量
 * @param position 模型 x,y,z位置
 * @param autoRotate  true 自定旋转 false 禁止旋转
 */
import * as THREE from "three";
import {
    
     GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import {
    
     OrbitControls } from "three/examples/jsm/controls/OrbitControls";

const emit = defineEmits(["load"]);

let array: any = [];
let speed: number = 1;
let scene: any = null;
let camera: any = null;
let controls: any = null;
let renderer: any = null;
let stopAnimationType: boolean = false;
let oldId: number | null = null, id: number | null = null;

// 定义接收参数的类型
interface InitType {
    
    
	el: string;
	list: any[];
	size: number;
	speed: number;
	modelUrl: string;
	vector: object[];
	position: object[];
	autoRotate: boolean;
}

// 初始化模型
class Init {
    
    
	list: object[];
	gltfLoader: any;
	model: InitType;
	autoRotate: boolean = true;
	canvas: HTMLElement | null = null;
	constructor(data: InitType) {
    
    
		this.clear();
		// 参数存储
		this.model = data;
		// 开启动画
		stopAnimationType = true;
		// 设置是否自动动画
        this.model.autoRotate === undefined || (() => this.autoRotate = this.model.autoRotate)();
		// 速度判断
		if (this.model.speed) speed = this.model.speed;
		// 获取模型数据
		this.model.list ? this.list = this.model.list : this.list = [];
		// 获取当前渲染的dom元素
		if (!this.model.el) throw new Error("请填写渲染的dom元素");
		// 获取dom元素
		this.canvas = document.querySelector(this.model.el) as HTMLElement;
		// 相机设置
		camera = new THREE.PerspectiveCamera(
			this.model.size ? this.model.size : 28,
			this.canvas.clientWidth / this.canvas.clientHeight,
			0.01,
			100
		);
		// 相机初始偏移角度
		camera.position.set(
			this.model.position && this.model.position.length && this.model.position[0] ? this.model.position[0] : -4,
			this.model.position && this.model.position.length && this.model.position[1] ? this.model.position[1] : 9,
			this.model.position && this.model.position.length && this.model.position[2] ? this.model.position[2] : 9
		);

		// 设置Vector3 x,y,z 三个分量,进行缩放
		camera.lookAt(new THREE.Vector3(
			this.model.vector && this.model.vector.length && this.model.vector[0] ? this.model.vector[0] : 1,
			this.model.vector && this.model.vector.length && this.model.vector[0] ? this.model.vector[0] : 45,
			this.model.vector && this.model.vector.length && this.model.vector[0] ? this.model.vector[0] : 20
		));

		//  创建场景对象Scene
		scene = new THREE.Scene();

		// 创建模型容器
		this.gltfLoader = new GLTFLoader();
		if (!this.model.modelUrl) {
    
    
			throw new Error("请添加模型地址");
		};
		// 调用模型,加载模型
		this.modeLoad();
		animate();
	};

	public modeLoad() {
    
    
		// 加载模型
		this.gltfLoader.load(
			this.model.modelUrl,
			(gltf: any) => {
    
    
				console.log(
					`%c 模型通道 ↓↓↓,通过 name 判断`,
					`color: white;
					 height: 60px; 
					 display: block;
					 font-size: 20px;
					 line-height: 60px;
					 padding-left: 50px;
					 padding-right: 80px;
					 background:#ff720df2;
					 border: 2px solid white;
					`
				);
				console.table(gltf.scene.children);
				array = [];
				this.list.forEach((item: any) => {
    
    
					item.loadBear = gltf.scene.children.find((child: any) => {
    
    
						return item.name == child.name;
					});
					array.push(item);
					updateSticker(item.label, item.url);
				});
				nextTick(() => {
    
    
					scene.add(gltf.scene);
				});
			},
			(xhr: any) => {
    
    
				if (xhr.loaded / xhr.total > 0) {
    
    
					emit("load", Number((Number(xhr.loaded / xhr.total) * 100).toFixed(2)));
				};
				// console.log(xhr.loaded / xhr.total, "当前进度:" + (xhr.loaded / xhr.total) * 100 + "%");
			},
			(error: any) => {
    
    
				console.log(error);
			}
		);

		renderer = new THREE.WebGLRenderer({
    
    
			canvas: this.canvas,
			antialias: true,
		});
		renderer.setClearColor(0xfeffff, 0);
		renderer.setPixelRatio(window.devicePixelRatio);
		renderer.setSize(this.canvas?.clientWidth, this.canvas?.clientHeight);
		renderer.outputEncoding = THREE.sRGBEncoding;


		controls = new OrbitControls(camera, renderer.domElement);
		controls.maxPolarAngle = 1;
		controls.minAzimuthAngle = 0;
		controls.enablePan = true;
		controls.autoRotate = this.autoRotate;

		let obj: any = {
    
    
			list: this.list,
			model: this.model,
			canvas: this.canvas,
		}, that = this;
		for (let key in obj) {
    
    
			Object.defineProperty(that, key, {
    
    
				get: function () {
    
    
					return obj[key];
				},
				set: function (value) {
    
    
					throw new Error("当前属性:'" + key + "'的值, 无法进行修改");
				}
			});
		};
	};

	// 清空threejs中实例
	public clear() {
    
    
		if (scene) {
    
    
			scene.traverse((child: any) => {
    
    
				if (child.material) {
    
    
					child.material.dispose();
				}
				if (child.geometry) {
    
    
					child.geometry.dispose();
				}
				child = null;
			});
		};
		if (renderer) {
    
    
			renderer.forceContextLoss();
			renderer.dispose();
			renderer.domElement = null;
			renderer = null;
		};
		if (scene) {
    
    
			scene.clear();
			scene = null;
		};
		camera = null;
		stopAnimationType = false;
		controls = null;
	};
};

// 动画加载
const animate = () => {
    
    
	if (stopAnimationType) {
    
    
		controls.autoRotateSpeed = speed;
		controls.update();
		renderer.render(scene, camera);
		id = requestAnimationFrame(animate);
		clear(oldId as number);
		oldId = id;
	};

};

// 清除每次requestAnimationFrame产生的内存
const clear = (oldId: number) => {
    
    
	if (oldId) {
    
    
		cancelAnimationFrame(oldId);
	};
};

// 更换材质
const updateSticker = (name: string, url: string = "") => {
    
    
	array.forEach(async (item: any) => {
    
    
		if (item.label == name) {
    
    
			let getsheet = new THREE.TextureLoader().load(url);
			getsheet.flipY = false;
			getsheet.encoding = THREE.sRGBEncoding;
			item.loadBear.material = await new THREE.MeshBasicMaterial({
    
    
				map: getsheet,
			});
		};
	});
};

// 更换颜色, 修改某一通道的对应的模型颜色
const updateColor = (color: string, value: string) => {
    
    
	array.forEach(async (item: any) => {
    
    
		if (item.label == value) {
    
    
			item.loadBear.material.color.set(color);
		};
	});
};

defineExpose({
    
     Init, updateSticker, updateColor });
</script>

组件调用

<template>
    <div id="tddis">
        <modelCreation ref="Three" @load="load">
            <div>
                <canvas id="canvas"></canvas>
            </div>
        </modelCreation>
        <view class="maskLoading" v-if="data.maskLoading">
            <van-loading color="#0094ff" size="60px" text-size="30px" vertical>模型已加载{
   
   {data.time}}···</van-loading>
        </view>
    </div>
</template>
<script setup lang="ts">
// 初始化类
let threeExample: any = null;
// 初始化组件实例
const Three: any = ref(null);
// 加载数据
const data: any = reactive({
    
    
    time:null,
    maskLoading: false,
});

onMounted(() => {
    
    
    threeExample = new Three.value.Init({
    
    
		el: "#canvas",
		modelUrl: "./sijiantao3.glb",  // 模型地址
		list: [
			{
    
     label: "床板", name: "dizuo", url: "./chuangban.png" },
			{
    
     label: "床头垫", name: "kaobeiR", url: "./chuangtoudian.png" },
			{
    
     label: "被套", name: "Group22783", url: "" },
			{
    
     label: "床单", name: "chuangli", url: "" },
			{
    
     label: "枕头1", name: "Plane003", url: "" },
			{
    
     label: "枕头2", name: "Plane003001", url: "" },
		],
	});
});

// 模型加载中,返回加载进度
const load = (val: number) => {
    
    
    data.maskLoading = true;
    data.time = val;
    if(val == 100) {
    
    
        data.maskLoading = false;
    }
};

// 更换材质
const replaceTheMaterial = () => {
    
    
	// updateSticker 方法两个参数
	// 第一个参数,为对应 上面绑定的模型通道名称,比如: 床板的名称 dizuo
	// 第二个参数没有可以不传,有的传材质 url 和 本地图片都可以
	Three.value.updateSticker(row.classifyName);
};

// 更换颜色
const changeColor = () => {
    
    
	// updateColor 方法两个参数
	// 参数一:参数传入颜色值
	// 参数二:传入对应通道的 label
	Three.value.updateColor("#FF0000", "床板");
};

onBeforeUnmount(() => {
    
    
	// 清除three
	threeExample.clear();
});
</script>
<style lang="scss" scoped>
#tddis {
    
    
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    box-sizing: border-box;
}
#canvas {
    
    
    width: 100vw;
	height: 100vh;
	box-sizing: border-box;
	background-color: rgba(0, 0, 0, 0.1) !important;
}

.maskLoading {
    
    
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: grid;
    position: fixed;
    place-items: center;
    background: rgba(0,0,0,0.6);
}
</style>

有问题请反馈下,进行修改

猜你喜欢

转载自blog.csdn.net/weixin_44244230/article/details/127809838
今日推荐