nft数字藏品大火,各家公司都推出自己的app和小程序,那么炫酷的3D藏品到底如何实现的呢?我们来一探究竟!
随着各家数字藏品项目上线,我们团队也紧随潮流打算在短期内开发一款数字藏品的产品。主要有哪些需求呢,当然就是别人有什么我们也做什么啦,相同的功能不同的UI。其他的功能都好说,但藏品把玩的3D效果之前未涉及过,但有了解过可以通过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模型加载器
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();
},
// 创建背景,根据业务需求完善
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 = true;
this.mouseControls.autoRotateSpeed = 5; // 自转速度
// 加载模型
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();
// 本地静态模型调试要放在根目录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);
})
},
// 渲染器
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>