简介:因为最近工作需求涉及到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