简介:因为最近工作需求涉及到threejs的开发,并且自己也一直想要学习和攻克这方面的知识,就通过自学之后记录下了这篇文章。这篇文章主要包含threejs的几个方面模型预览、环境贴图、模型的缩放、平移、打光、清晰度调整等方面。非常详细,有兴趣的同学可以收藏慢慢看!!!
一、threejs安装
通过npm或者yarn安装threejs库
npm install three
or yarn add three
导入整个 three.js核心库
import * as THREE from 'three';
二、场景创建
html:
<template>
<div ref="container"></div>
</template>
script:
const container = ref(null)
// 创建场景
const scene = new THREE.Scene();
// 创建相机
camera = new THREE.OrthographicCamera(
window.innerWidth / -10.5,
window.innerWidth / 10.5,
window.innerHeight / 10.5,
window.innerHeight / - 10.5,
0.1,
1000
);// 这边设置的是OrthographicCamera解决模型近大远小的问题
// 相机位置
camera.position.z = 200; // 根据模型大小调整
camera.position.y = 0;
camera.position.x = 0;
camera.lookAt(0, 0, 0)
// 创建渲染器
renderer = new THREE.WebGLRenderer({ alpha: true }); // 设置背景为透明
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000, 0);
container.value.appendChild(renderer.domElement);
// 如果需要添加环境贴图(添加背景环境)可以加上以下代码
// import { RGBELoader} from "three/examples/jsm/loaders/RGBELoader"
// let rgbeLoader = new RGBELoader();
// const rgbUrl = "xxxxx"; // 环境贴图hdr文件路径
// rgbeLoader.load(rgbUrl, envMap => {
// // 设置球形贴图
// envMap.mapping = THREE.EquirectangularReflectionMapping;
// // 设置环境贴图
// scene.background = envMap;
// scene.environment = envMap;
// })
// 渲染场景
const animate = () => {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
// 监听窗口缩放
window.addEventListener("resize", () => {
renderer.setSize( window.innerWidth, window.innerHeight );
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
三、模型引入
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
const modelContainer = new THREE.Object3D();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./draco/')
// 加载 glb 格式的 3D 模型
loader.setDRACOLoader(dracoLoader)
const modelPath = url // url是你模型的路径
loader.load(
modelPath,
(gltf) => {
// 加载成功后的回调函数
model = gltf.scene;
const box = new THREE.Box3().setFromObject(model);
// 获取包围盒中心点
const center = box.getCenter(new THREE.Vector3());
model.position.sub(center); // 将模型位置移到原点处
// 将模型添加到父对象中
modelContainer.add(model);
scene.add(modelContainer);
},
(xhr) => {
// 加载过程中的回调函数
// emit("handleProgress",Math.floor((xhr.loaded / xhr.total) * 100))
// 如果有写模型加载进度条的要求,可以在这里判断
},
(error) => {
// 加载失败的回调函数
console.error("Failed to load model", error);
}
);
四、交互事件
变量:
const STATE = {
NONE: -1,
MOVE: 0,
ZOOM_OR_PAN: 1,
POSITION_MOVE: 1,
}
const mouseInfo = ref({startX: 0, startY: 0, isDown: false, startPointerDistance: 0, state: STATE.NONE}) // 触控参数存储
const rotateSpeed = 0.010; // 拖动速度 可自行调整
const twoFirst = ref(true)
const lastPoint = ref({
touchStartX1: 0,
touchStartY1: 0,
touchStartX2: 0,
touchStartY2: 0,
})
旋转、缩放、平移方法
// 开始触碰
renderer.domElement.addEventListener('touchstart', (event) => {
handleTouchStart(event)
});
const handleTouchStart = (event) => {
mouseInfo.value.isDown = true
// 双指
const touch0 = event.touches[0]
const touch1 = event.touches[1]
// 单点触控
if (event.touches.length === 1) {
mouseInfo.value.startX = touch0.pageX
mouseInfo.value.startY = touch0.pageY
mouseInfo.value.state = STATE.MOVE
} else if (event.touches.length === 2) {
// 双指,计算两指距离,一般是平移或者缩放
const dx = (touch0.pageX - touch1.pageX)
const dy = (touch0.pageY - touch1.pageY)
mouseInfo.value.startPointerDistance = Math.sqrt(dx * dx + dy * dy)
mouseInfo.value.startX = (touch0.pageX + touch1.pageX) / 2
mouseInfo.value.startY = (touch0.pageY + touch1.pageY) / 2
mouseInfo.value.state = STATE.ZOOM_OR_PAN
}
}
// 监听缩放\旋转操作
renderer.domElement.addEventListener('touchmove', (event) => {
handleTouchMove(event)
});
const handleTouchMove = (event) => {
if (!mouseInfo.value.isDown) {
return
}
switch (mouseInfo.value.state) {
case STATE.MOVE:
if (event.touches.length === 1) {
handleRotate(event)
} else if (event.touches.length === 2) {
// 兼容处理,如果是移动过程中出现双指时,需要取消单指,然后再重新计算
renderer.domElement.removeEventListener("touchmove",handleTouchMove, false)
renderer.domElement.removeEventListener("touchend",handleTouchEnd, false)
handleTouchStart(event)
}
break
case STATE.ZOOM_OR_PAN:
if (event.touches.length === 1) {
} else if (event.touches.length === 2) {
handleZoomOrPan(event)
}
break
default:
break
}
}
// 模型旋转
const handleRotate = (event) => {
const x = event.touches[0].pageX
const y = event.touches[0].pageY
const {startX, startY} = mouseInfo.value
const deltaX = x - startX;
const deltaY = y - startY;
modelContainer.rotation.y += deltaX * rotateSpeed;
modelContainer.rotation.x += deltaY * rotateSpeed;
mouseInfo.value.startX = x
mouseInfo.value.startY = y
}
// 模型缩放
const handleZoomOrPan = (event) => {
let {touchStartX1,touchStartY1, touchStartX2, touchStartY2} = lastPoint.value;
let initialScale = modelContainer.scale.x;
const touch0 = event.touches[0]
const touch1 = event.touches[1]
const dx = (touch0.pageX - touch1.pageX)
const dy = (touch0.pageY - touch1.pageY)
const distance = Math.sqrt(dx * dx + dy * dy)
let obj = {
touchStartX1: touch0.pageX,
touchStartY1: touch0.pageY,
touchStartX2: touch1.pageX,
touchStartY2: touch1.pageY,
}
if (twoFirst.value) {
twoFirst.value = false;
lastPoint.value = {...obj}
} else {
let deltaScale = initialScale * (distance / mouseInfo.value.startPointerDistance);
// 限制缩放距离
if (deltaScale < -2) {
deltaScale = -2
} else if (deltaScale > 2) {
deltaScale = 2
}
mouseInfo.value.startPointerDistance = distance;
modelContainer.scale.set(deltaScale, deltaScale, deltaScale);
const avgX = (touch0.pageX + touch1.pageX) / 2;
const avgY = (touch0.pageY + touch1.pageY) / 2;
const deltaX = avgX - (touchStartX1 + touchStartX2) / 2;
const deltaY = avgY - (touchStartY1 + touchStartY2) / 2;
modelContainer.position.x += deltaX * 0.3;
modelContainer.position.y -= deltaY * 0.3;
lastPoint.value = {
touchStartX1: touch0.pageX,
touchStartY1: touch0.pageY,
touchStartX2: touch1.pageX,
touchStartY2: touch1.pageY,
}
}
}
// 监听触控结束
renderer.domElement.addEventListener('touchend', (event) => {
handleTouchEnd()
});
const handleTouchEnd = () => {
mouseInfo.value.isDown = false
mouseInfo.value.state = STATE.NONE
twoFirst.value = true;
lastPoint.value.touchStartX1 = 0;
lastPoint.value.touchStartY1 = 0;
lastPoint.value.touchStartX2 = 0;
lastPoint.value.touchStartY2 = 0;
// 取消移动事件监听
renderer.domElement.removeEventListener("touchmove",handleTouchMove, false)
renderer.domElement.removeEventListener("touchstart",handleTouchStart, false)
}
自动旋转、重置位置方法
// 开启自动旋转
const rotateTimer = ref(null)
const setTimer = () => {
rotateTimer.value = setInterval(() => {
modelContainer.rotation.y -= 0.1
}, 100)
}
// 重置模型位置
const ResetPosition = () => {
modelContainer.position.z = 0;
modelContainer.position.y = 0;
modelContainer.position.x = 0;
modelContainer.scale.z = 1;
modelContainer.scale.y = 1;
modelContainer.scale.x = 1;
modelContainer.rotation.z = 0;
modelContainer.rotation.y = 0;
modelContainer.rotation.x = 0;
}
五、优化调整
打光
// 环境光 (这是一定要的)
const ambientLight = new THREE.AmbientLight(0xffffff, 2);
scene.add(ambientLight);
// 白色平行光(模型更明亮)
const directionalLight = new THREE.DirectionalLight( 0xffffff, 2 ); // 参数自行调整
directionalLight.position.x = 1;
directionalLight.position.y = 1;
directionalLight.position.z = 80;
directionalLight.target = modelContainer; // target指向模型
scene.add( directionalLight );
// *创建点光源(这个看情况给)
var pointLight = new THREE.PointLight(0xffffff, 500); // 设置点光源的颜色和强度
pointLight.position.set(0, 0, 100); // 设置点光源的位置
scene.add(pointLight);
清晰度
// 在创建渲染器时,添加antiallias:true抗锯齿,让模型看起来更加平滑
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
// 设置画布分辨率 提高画质
renderer.setPixelRatio(window.devicePixelRatio);
ios上safari网页缩放问题
在safari上打开缩放模型会和网页缩放冲突,添加以下方法可以解决。
// 在main.ts中添加
window.onload = function() {
var lastTouchEnd = 0;
document.addEventListener('touchstart', function(event) {
if (event.touches.length > 1) {
event.preventDefault();
}
});
document.addEventListener('touchend', function(event) {
var now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
document.addEventListener('gesturestart', function(event) {
event.preventDefault();
});
document.addEventListener('dblclick', function (event) {
event.preventDefault();
})
}
六、参考
模型下载:https://sketchfab.com/ or https://market.pmnd.rs/
环境贴图hdr下载:https://hdrmaps.com/
threejs文档参考:http://www.yanhuangxueyuan.com/threejs/docs/index.html#manual/zh/introduction/Creating-a-scene