使用three.js导入外部glb模型实现一个nft产品

nft数字藏品大火,各家公司都推出自己的app和小程序,那么炫酷的3D藏品到底如何实现的呢?我们来一探究竟!

需求背景

随着各家数字藏品项目上线,我们团队也紧随潮流打算在短期内开发一款数字藏品的产品。主要有哪些需求呢,当然就是别人有什么我们也做什么啦,相同的功能不同的UI。其他的功能都好说,但藏品把玩的3D效果之前未涉及过,但有了解过可以通过three.js来实现。所以,就开始看文档调研吧。

准备工作

首先在项目中安装three.js插件

npm i three
npm i three-orbitcontrols

然后在对应场景引入

import * as THREE from 'three';
import {
	OrbitControls
} from "three/examples/jsm/controls/OrbitControls"; // 鼠标控制器
import {
	GLTFLoader
} from "three/examples/jsm/loaders/GLTFLoader.js"; // glb模型加载器

使用

  1. 创建场景
createScene() {
	// 创建场景
	this.scene = new THREE.Scene();

	// 创建相机
	let width = this.$refs.container.$el.offsetWidth; // 窗口宽度
	let height = this.$refs.container.$el.offsetHeight; // 窗口高度
	this.width = width;
	this.height = height;
	let k = width / height; // 窗口宽高比
	// 初始化相机,参数通俗讲PerspectiveCamera(远近,宽高比,摄像机视锥体近端面(默认0.1),
	// 摄像机视锥体远端面(默认2000,无限大,表示可以看到最远))
	this.camera = new THREE.PerspectiveCamera(60, k, 0.1, 180); // 透视相机
	this.camera.position.set(0, 0, 4); // 设置相机位置
	this.camera.lookAt(this.scene.position); // 指向场景中心

	// 创建渲染器
	this.renderer = new THREE.WebGLRenderer({
		antialias: true, // 抗锯齿
		alpha: true,
	});
	// 设置渲染区域尺寸
	this.renderer.setSize(width, height);
	// 设置背景颜色
	this.renderer.setClearColor(0x1e1e1e, 1);
	// 渲染器开启阴影效果
	this.renderer.shadowMap.enabled = true;
	// 阴影贴图类型
	this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;

	// 添加坐标轴,用于调试,可以直观的展示模型的空间位置
	let axes = new THREE.AxesHelper(1000);
	this.scene.add(axes);

	// 将画布添加到容器中
	document.getElementById("container").appendChild(this.renderer.domElement);
	// 创建相机控件
	this.createOrbitControls();
},
  1. 创建背景
// 创建背景,根据业务需求完善
createUniverse() {
	const BgImg = ""; // 背景图片
	let texture = new THREE.TextureLoader().load(BgImg); // 加载背景贴图
	this.scene.background = texture; // 设置场景背景
},
  1. 创建相机控件
// 创建相机控件
createOrbitControls() {
	// 创建相机控件对象
	this.mouseControls = new OrbitControls(
		this.camera,
		this.renderer.domElement
	);
	// 右键平移拖拽
	this.mouseControls.enablePan = false; // 在移动端是双指拖拽
	// 鼠标缩放功能
	this.mouseControls.enableZoom = true;
	// 相机距离原点的距离范围
	this.mouseControls.minDistance = 0;
	this.mouseControls.maxDistance = 100;

	// 滑动阻尼,鼠标拖拽旋转的灵敏度,阻尼越小越灵敏
	this.mouseControls.enableDamping = true;
	// 阻尼系数(默认0.25)
	this.mouseControls.dampingFactor = 0.5;
	// 上下翻转角度范围
	this.mouseControls.maxPolarAngle = 3; // y旋转角度范围
	this.mouseControls.minPolarAngle = 0;

	// 是否自动旋转
	this.mouseControls.autoRotate = true;
	this.mouseControls.autoRotateSpeed = 5; // 自转速度
	// 加载模型
	this.loadGlbModel();
},
  1. 创建光源
// 创建光源
createLight() {
	// 配置环境光
	this.ambientLight = new THREE.AmbientLight(0x999999);
	// 将环境光添加到场景中
	this.scene.add(this.ambientLight);
	// 配置点光源
	this.pointLight = new THREE.PointLight(0xffffff, 1, 0);
	// 配置点光源的位置
	this.pointLight.position.set(500, 300, 400);
	// 将点光源添加至场景中
	this.scene.add(this.pointLight);
},
  1. 加载模型
// 加载glb、glbf模型
loadGlbModel() {
	let self = this;
	let loader = new GLTFLoader();
	// 本地静态模型调试要放在根目录public中,我们这边用的uniapp就放在static中,不然会出现加载问题
	loader.load(`${this.modelPath}`, (gltf) => {
		console.log('gltf ---->>', gltf);
		let loaderScene = gltf.scene;
		loaderScene.scale.set(1, 1, 1); // 设置模型大小缩放
		loaderScene.position.set(0, -1, 0);
		// 模型加入到场景中
		self.scene.add(loaderScene);
		// 渲染器启动
		this.repeatRender();
	}, (xhr) => {
		console.log(`${ (xhr.loaded / xhr.total) * 100 }%`)
	}, (err) => {
		console.log('error ---->>', err);
	})
},
  1. 渲染器
// 渲染器
repeatRender() {
	let animate = () => {
		// 请求动画帧,屏幕每刷新一次调用一次,绑定屏幕刷新频率
		this.clearAnim = requestAnimationFrame(animate);
		// 实时更新相机控件
		this.mouseControls.update();
		// 渲染场景和相机
		this.renderer.render(this.scene, this.camera);
	};
	animate();
},

完整代码

<template>
	<view class="enlarge">
		<view class="header">
			<view class="header-name">{{ goodsName }}</view>
			<view class="header-auth">
				<view class="auth">创作者:</view>
				<view class="name">{{ goodsAuth }}</view>
			</view>
		</view>
		<view class="container" id="container" ref="container"></view>
		<view class="bottom-box">
			<image class="bottom-box-img" src="@/static/img/share-bg.png" mode="widthFix"></image>
		</view>
	</view>
</template>

<script>
	import * as THREE from 'three';
	import {
		OrbitControls
	} from "three/examples/jsm/controls/OrbitControls"; // 鼠标控制器
	import {
		GLTFLoader
	} from "three/examples/jsm/loaders/GLTFLoader.js"; // 模型加载器
	import {
		FBXLoader
	} from 'three/examples/jsm/loaders/FBXLoader.js';
	export default {
		data() {
			return {
				goodsName: '做前端死路一条',
				goodsAuth: '鲁迅',
				scene: null, // 场景
				camera: null, // 相机
				mouseControls: null, // 相机控件
				renderer: null, // 渲染
				ambientLight: null, // 环境光
				clearAnim: null, // 动画帧
				autoRotate: true, // 是否自转
				autoRotateSpeed: 10, // 自转速度,数值越小速度越慢
				modelPath: "static/model/elvenKing.glb", // 模型路径
				width: null, // 画布宽度
				height: null, // 画布高度
				distance: 60, // 视野距
				near: 1, // 最小视野距
				far: 180, // 最大视野距
			};
		},
		onReady() {
			this.init();
		},
		onUnload() {
			// 组件卸载
			cancelAnimationFrame(this.clearAnim);
			// 场景
			this.scene = null;
			// 相机
			this.camera = null;
			// 相机控件
			this.mouseControls = null;
			// 渲染器
			this.renderer = null;
			// 环境光
			this.ambientLight = null;
			// 动画帧
			this.clearAnim = null;
		},
		methods: {
			init() {
				this.createScene(); // 创建场景
				this.createUniverse(); // 创建背景
				this.createLight(); // 创建光源
			},
			// 初始化步骤
			createScene() {
				// 创建场景
				this.scene = new THREE.Scene();

				// 创建相机
				let width = this.$refs.container.$el.offsetWidth; // 窗口宽度
				let height = this.$refs.container.$el.offsetHeight; // 窗口高度
				this.width = width;
				this.height = height;
				let k = width / height; // 窗口宽高比
				// 初始化相机,参数通俗讲PerspectiveCamera(远近,宽高比,摄像机视锥体近端面(默认0.1),
				// 摄像机视锥体远端面(默认2000,无限大,表示可以看到最远))
				this.camera = new THREE.PerspectiveCamera(this.distance, k, this.near, this.far); // 透视相机
				this.camera.position.set(0, 0, 4); // 设置相机位置
				this.camera.lookAt(this.scene.position); // 指向场景中心

				// 创建渲染器
				this.renderer = new THREE.WebGLRenderer({
					antialias: true, // 抗锯齿
					alpha: true,
				});
				// 设置渲染区域尺寸
				this.renderer.setSize(width, height);
				// 设置背景颜色
				this.renderer.setClearColor(0x1e1e1e, 1);
				// 渲染器开启阴影效果
				this.renderer.shadowMap.enabled = true;
				// 阴影贴图类型
				this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;

				// 添加坐标轴,辅助判断位置,可用于调试
				let axes = new THREE.AxesHelper(1000);
				this.scene.add(axes);

				// 将画布添加到容器中
				document.getElementById("container").appendChild(this.renderer.domElement);
				this.createOrbitControls();
			},
			// 创建背景,根据业务需求完善
			createUniverse() {
				const BgImg = "";
				let texture = new THREE.TextureLoader().load(BgImg); // 加载背景贴图
				this.scene.background = texture; // 设置场景背景
			},
			// 创建相机控件
			createOrbitControls() {
				// 创建相机控件对象
				this.mouseControls = new OrbitControls(
					this.camera,
					this.renderer.domElement
				);
				// 右键平移拖拽
				this.mouseControls.enablePan = false;
				// 鼠标缩放
				this.mouseControls.enableZoom = true;
				// 相机距离原点的距离范围
				this.mouseControls.minDistance = 0;
				this.mouseControls.maxDistance = 100;

				// 滑动阻尼,鼠标拖拽旋转的灵敏度,阻尼越小越灵敏
				this.mouseControls.enableDamping = true;
				// 阻尼系数(默认0.25)
				this.mouseControls.dampingFactor = 0.5;
				// 上下翻转角度范围
				this.mouseControls.maxPolarAngle = 3; // y旋转角度范围
				this.mouseControls.minPolarAngle = 0;

				// 是否自动旋转
				this.mouseControls.autoRotate = this.autoRotate;
				this.mouseControls.autoRotateSpeed = this.autoRotateSpeed; // 自转速度
				// 加载模型
				this.loadGlbModel();
			},
			// 创建光源
			createLight() {
				// 配置环境光
				this.ambientLight = new THREE.AmbientLight(0x999999);
				// 将环境光添加到场景中
				this.scene.add(this.ambientLight);
				// 配置点光源
				this.pointLight = new THREE.PointLight(0xffffff, 1, 0);
				// 配置点光源的位置
				this.pointLight.position.set(500, 300, 400);
				// 将点光源添加至场景中
				this.scene.add(this.pointLight);
			},
			// 加载glb、glbf模型
			loadGlbModel() {
				let self = this;
				let loader = new GLTFLoader();
				loader.load(`${this.modelPath}`, (gltf) => {
					console.log('gltf---->>', gltf);
					let loaderScene = gltf.scene;
					loaderScene.scale.set(1, 1, 1); // 设置模型大小缩放
					loaderScene.position.set(0, -1, 0);

					self.scene.add(loaderScene);
					// 渲染器启动
					this.repeatRender();
				}, (xhr) => {
					console.log(`${ (xhr.loaded / xhr.total) * 100 }%`)
				}, (err) => {
					console.log('error ---->>', err);
				})
			},

			// 渲染器
			repeatRender() {
				let animate = () => {
					// 请求动画帧,屏幕每刷新一次调用一次,绑定屏幕刷新频率
					this.clearAnim = requestAnimationFrame(animate);
					// 实时更新相机控件
					this.mouseControls.update();
					// 渲染场景和相机
					this.renderer.render(this.scene, this.camera);
				};
				animate();
			},
		}
	}
</script>

<style lang="less">
	.enlarge {
		padding: 32rpx;
		width: 100%;
		height: 100%;
		box-sizing: border-box;
		position: fixed;
		top: 0;
		right: 0;
		background-color: #1e1e1e;

		.header {
			position: absolute;
			top: 32rpx;
			left: 32rpx;
			color: #fff;
			background-color: rgba(0, 0, 0, 0);

			.header-name {
				font-size: 40rpx;
			}

			.header-auth {
				font-size: 28rpx;
				display: flex;
				color: #b1b1b1;
				margin-top: 16rpx;
			}
		}

		.container {
			width: 100%;
			height: 100%;
		}

		.bottom-box {
			width: 100%;
			padding: 0 32rpx;
			box-sizing: border-box;
			position: absolute;
			bottom: 160rpx;
			left: 0;
			z-index: -1;

			&-img {
				width: 100%;
			}
		}
	}
</style>

你可能感兴趣的:(javascript,开发语言)