在学习了一些理论知识后,要做一下实战演练了,做一个简单的车辆展览来看看吧。
通过调整相机的位置,将导入的车辆模型分成三个视角展示。
- 车辆外部:可以观察车辆的整体外观以及轮廓结构
- 车辆内部:相机在汽车内部,可以改变相机焦点位置观察车辆内部情况
首先根据需求,先要有一辆汽车。我的素材是从爱给网上找到的,3D模型的类型是gltf
格式,并且携带了3种动画效果。
创建场景和导入素材
场景的创建比较简单,具体可以参考上一篇文章 Three.js杂记(十三)—— 包围盒。里面对render
渲染器、scene
场景、camera
相机等元素创建都有写过,并且对GLTFLoader
3D模型导入也有过介绍,所以具体代码和流程就不再复述了。
为了界面更好区分,我将scene.background
的颜色设置为了#ccc
。
scene.background = new THREE.Color("#ccc");
导入模型后:
设置光源和地面
当前模型是黑色的,因为我并没有设置环境贴图或者光线。
接下来设置光源,我使用了点光源PointLight
,从5个不同方向对车辆进行照射。因为5个点光源,所以使用Group
组的形式放到了一起。
// 添加灯光组
const lightGroup = new THREE.Group();
let lightDir = [[3, 0.5, 0],[-3, 0.5, 0],[0, 0.5, 3],[0, 0.5, -3],[0, 3, 0],
]
for(let i = 0; i < lightDir.length; i++) {let light1 = new THREE.PointLight(0xffffff, 10);light1.position.set(...lightDir[i]);lightGroup.add(light1);
}
scene.add(lightGroup);
当前拥有光源后效果:
为了更好的呈现,我在车辆下方添加了一块平面几何体PlaneGeometry
,使用的材质是MeshStandardMaterial
。对几何体进行绕x轴旋转。
// 制作地面
const floorGeometry = new THREE.PlaneGeometry(10, 10);
const floorMaterial = new THREE.MeshStandardMaterial({ color: '#fff' });
let floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.material.opacity = 0.5;
floor.rotateX(-Math.PI / 2);
floor.position.y = 0;
scene.add(floor);
当前车辆展示就看起来很有科技感了
内外切换面板
接下来准备一块面板,面板上添加外部和内部三种选项。
面板使用div
元素制作,通过绝对定位放在界面右上角。
然后设置点击方法,对相机位置进行调整
这里我使用了
Tween
补间动画,然后在animtion
中需要添加补间动画的update
方法
import { update as TweenUpdate } from 'three/examples/jsm/libs/tween.module';
// ...
// 面板上数据
const cGUI = ref({cameraPos: [{ name: '车辆外部', pos: [-3, 3, 3] },{ name: '车辆内部', pos: [0, 0.8, 0] },]
})
// 切换相机视角方法
const changeCameraPos = (pos: number[]) => {// 创建补间对象const tween = new Tween(camera.position);tween.to({ x: pos[0],y: pos[1],z: pos[2]}, 1000).start();
}
// ...
function animate() {TweenUpdate(); //更新动画controls.update(); //鼠标控制render.render(scene, camera);window.requestAnimationFrame(animate);
}
这样一来,点击切换相机位置改变时,就可以比较流畅的看见过程。
以下是效果:
相机位置和视角
现在场景内已经使用了OrbitControls
去用鼠标拖动改变相机位置。并且为了后续效果,我禁用了缩放功能
const controls = new OrbitControls(camera, render.domElement);
controls.enableZoom = false;
但是,此功能在车辆内部就不适用了,改变相机位置会导致穿模。在车辆内部需要的是改变相机焦点lookAt
,保持相机的位置不动。
这里我是根据点击方法中相机位置,设置inOut
属性判断内外。
// 切换相机视角方法
const changeCameraPos = (pos: number[]) => {if (Math.abs(pos[0]) < 1){inOut = true; // 在车辆内部controls.enableRotate =false; // 禁止旋转} else {inOut = false; // 在车辆外部controls.enableRotate = true; // 允许旋转}// ......
}
此时,在animation
动画中,也需要进行判断处理:
function animate() {// ...if (!inOut) {controls.update(); //鼠标控制}// ...
}
将这些设置完成后,车辆外部能正常观察汽车,但是内部移动不动,此时就需要绑定鼠标按下移动释放等事件了。
通过这些事件中鼠标的位置,去设置camera
相机的焦点位置。此处我参考了:three.js笔记5–添加鼠标移动视角 | Here. There. (godbasin.github.io)
原理也很明了:相当于以相机固定不动位置作为顶点,然后用焦点画一个圆环
代码:
// 定义角度
var theta = 0;
// 初始化鼠标X方向移动值
var mouseX = 0;
var r = 1000 / (2* Math.PI); // 用于角度计算: 鼠标移动1000px时,角度改变2PI
var far = 100; // 用于照相机焦点设置(焦点距离,越大越精确)
var move = 0.1; // 用于步长(照相机移动距离)
var mousedownFlag = false; // 鼠标是否按下
var inOut = false;
// 添加鼠标移动时事件
document.addEventListener('mousemove', handleMousemove, false);
// 添加鼠标页面点击释放
document.addEventListener('mousedown', initMousePosition, false);
document.addEventListener('mouseup', ()=>{ mousedownFlag = false; }, false);
// 初始化鼠标移动值
function initMousePosition(e:any) {if (!inOut) return ;mousedownFlag = inOut;mouseX = getMousePos(e || window.event).x;
}
// 获取鼠标坐标,传入事件event
function getMousePos(event: any) {var e = event || window.event;var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;var scrollY = document.documentElement.scrollTop || document.body.scrollTop;var x = e.pageX || e.clientX + scrollX;var y = e.pageY || e.clientY + scrollY;return { 'x': x, 'y': y };
}// 处理鼠标移动
function handleMousemove(e: any) {if (!mousedownFlag) return ;var e = e || window.event;// 获取鼠标x坐标var newMouseX = getMousePos(e).x;// 若值无效,更新坐标然后返回if (Number.isNaN((newMouseX - mouseX) / r)) { mouseX = newMouseX; return; }// 更新视角以及坐标位置theta += (newMouseX - mouseX) / r;mouseX = newMouseX;// 更新照相机焦点renderCameraLookat();
}
// 更新照相机焦点
function renderCameraLookat() {camera.lookAt(new THREE.Vector3(camera.position.x + far * Math.sin(theta), 1, camera.position.z + far * Math.cos(theta)));
}
因为上述代码已经在mousemove
的过程中修改了相机的焦点位置,所以不需要在animation
方法中再去添加。
当前切换到汽车内部,可以拖动旋转相机,对车内情况进行观察了
后续
PS: 这里对于ThreeJs的性能消耗还是要提一下的,主要是使用
GPU
进行处理图形,但是这次突然是我的CPU爆炸,排查原因可能是浏览器设置导致,然后我通过Edge的edge://flags/
设置GPU。
![[t10.png]]
并且我的笔记本有GPU0和GPU1,是双显。去Nvidia中进行了设置,具体可参考:Win10笔记本双显卡怎么切换 在哪里设置独立显卡-百度经验 (baidu.com)
后续此汽车模型还有3种动画效果,之后再进行播放和切换了。
未完待续…