# 入门及调试
# 轨道控制器查看物体
除了渲染几何体,但是无法全方位的查看该三维几何体。这时候通过轨道控制器,让相机围绕几何体运动,如同太阳围绕地球一般,去观察几何体。实现旋转缩放预览
TIP
- 旋转:拖动鼠标左键
- 缩放:滚动鼠标中键
- 平移:拖动鼠标右键
# 创建轨道控制器
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
// 创建轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 创建控制器阻尼,让控制器看起来更真实。必须在动画循环里面调用update
controls.enableDamping = true
// 必须传入2个参数:
// 相机,让哪一个相机围绕目标运动。默认目标是原点。立方体在原点处。
// 渲染的画布dom对象,用于监听鼠标事件控制相机的围绕运动。
OrbitControls本质
OrbitControls
本质上就是改变相机的参数,比如相机的位置属性,改变相机位置也可以改变相机拍照场景中模型的角度,实现模型的360度旋转预览效果,改变透视投影相机距离模型的距离,就可以改变相机能看到的视野范围。
controls.addEventListener('change', function () {
// 浏览器控制台查看相机位置变化
console.log('camera.position',camera.position);
});
# 每一帧根据控制器更新画面
因为控制器监听鼠标事件之后,要根据鼠标的拖动,来控制相机围绕目标运动,并根据运动之后的效果,显示出画面来。 为了保证画面流畅渲染,选择使用请求动画帧requestAnimationFrame,在屏幕渲染下一帧画面时触发回调函数来执行画面的渲染。
function render() {
// 如果后期需要控制器带有阻尼效果,或者自动旋转等效果,就需要加入controls.update()
controls.update()
renderer.render(scene, camera);
// 渲染下一帧的时候就会调用render函数
requestAnimationFrame(render);
}
render();
# 综合代码
<template>
<div id="canvas-container" ref="container"></div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import {
Scene,
Color,
PerspectiveCamera,
WebGLRenderer,
Mesh,
BoxBufferGeometry,
MeshBasicMaterial,
AxesHelper,
} from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
const container = ref();
onMounted(() => {
// 1、创建场景
const scene = new Scene();
// 设置场景的属性
scene.background = new Color("black");
// 2、创建相机
const fov = 35; // AKA Field of View
const aspect = container.value.clientWidth / container.value.clientHeight;
const near = 0.1; // the near clipping plane
const far = 100; // the far clipping plane
const camera = new PerspectiveCamera(fov, aspect, near, far);
// 设置相机位置
camera.position.set(0, 0, 10);
// 3、创建一个几何体
const geometry = new BoxBufferGeometry(1, 1, 1);
// 创建一个默认白色材质
const material = new MeshBasicMaterial();
// 根据几何体和材质创建物体
const cube = new Mesh(geometry, material);
// 添加几何体到场景中
scene.add(cube);
// 添加坐标辅助器
const axesHelper = new AxesHelper(3);
scene.add(axesHelper);
// 初始化渲染器
const renderer = new WebGLRenderer();
// 接下来,将渲染器设置为与我们的容器元素相同的大小
renderer.setSize(container.value.clientWidth, container.value.clientHeight);
// 最后,设置像素比例,以便我们的场景在HiDPI显示器上看起来不错
renderer.setPixelRatio(window.devicePixelRatio);
// 将自动创建的元素添加到<canvas>页面
container.value.append(renderer.domElement);
// // 渲染或“创建静止图像”场景
// renderer.render(scene, camera);
// 添加控制器,鼠标可以交互旋转,看起来是一个真正的立方体。
const controls = new OrbitControls(camera, renderer.domElement);
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
});
</script>
<style lang="scss" scoped>
#canvas-container {
width: 500px;
height: 500px;
}
</style>
# requestAnimationFrame
是HTML5的新特性,区别于setTimeout和setInterval。requestAnimationFrame比后两者精确,采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。因此屏幕每一帧都刷新一次画面,就需要执行
# 坐标轴辅助器
开发阶段便于调试查看,添加坐标辅助器。红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴。
import { AxesHelper } from 'three'
const axesHelper = new AxesHelper( 5 );
scene.add( axesHelper ); // 添加到创建的场景中
# 控制物体移动
为了让物体移动起来。我们可以设置它的position属性进行位置的设置。
相机和几何体都是物体。每个物体都是1个对象。
- 在官方文档里,我们可以看到相机camera和物体mesh都继承Object3D类。所以camera、mesh都属于3d对象。
- 从3d对象的官方文档里,我们可以找到position属性,并且该属性一个vector3对象
// 方式1:设置该向量的x、y 和 z 分量。
cube.position.set(x,y,z); // set继承至vector3类
// 方式2:直接设置position的x,y,z属性
cube.position.x = x;
cube.position.y = y;
cube.position.z = z;
# 每一帧修改一点位置形成动画
例如,每一帧让立方体向右移动0.01,并且当位置大于5时,从0开始。
function render() {
cube.position.x += 0.01;
if (cube.position.x > 5) {
cube.position.x = 0;
}
renderer.render(scene, camera);
// 渲染下一帧的时候就会调用render函数
requestAnimationFrame(render);
}
render();
# 控制物体缩放
因为物体的scale
属性是vector3
对象,因此按照vector
的属性和方法,设置x/y/z轴方向的缩放大小。
//例如设置x轴放大3倍、y轴方向放大2倍、z轴方向不变
cube.scale.set(3, 2, 1);
//单独设置某个轴的缩放
cube.scale.x = 3
cube.scale.y = 2
cube.scale.z = 1
# 控制物体旋转
因为的旋转通过设置rotation
属性,该属性是Euler类
的实例[Euler:欧拉]
,因此可以通过Euler类
的方法进行设置旋转角度。
//直接设置旋转属性,例如围绕x轴旋转90度
cube.rotation.x = -Math.PI/2
//围绕x轴旋转45度
cube.rotation.set(-Math.PI / 4, 0, 0, "XZY");
// 继承的方法: .set ( x : Float, y : Float, z : Float, order : String ) : this
// 参数说明: x - 用弧度表示x轴旋转量。 y - 用弧度表示y轴旋转量。 z - 用弧度表示z轴旋转量。 order - (optional) 表示旋转顺序的字符串。
# 正确处理动画的运行
为了最好的利用性能和渲染效果,那么我们只需要在绘制每一帧画面的时候,计算需要渲染的画面即可。 这个时候就可以使用window.requestAnimationFrame方法。
确保总是使用第一个参数 (或其它获得当前时间的方法) 计算每次调用之间的时间间隔,否则动画在高刷新率的屏幕中会运行得更快。
let preTime;
function render(time) {
//第一次调用render函数,没有上一帧的时间
if (preTime === undefined) {
preTime = time;
}
//计算每帧画面的间隔时间,单位毫秒
const deltaTime = time - preTime;
//保留当前时间作为上一帧时间,用于下一帧计算2帧间隔
preTime = time;
//renderer.render(scene, camera);
// 渲染下一帧的时候就会调用render函数
requestAnimationFrame(render);
}
render();
TIP
为了确保不同时间间隔,运动的速度一致,那么应该按照
移动距离 = 速度 * 时间
那么如果想要1m/s的速度远离原点的匀速运动
let preTime;
function render(time) {
//第一次调用render函数,没有上一帧的时间
if (preTime === undefined) {
preTime = time;
}
//计算每帧画面的间隔时间,单位毫秒,当前时间减去上一帧的时间,即为2帧直接的间隔时间
const deltaTime = time - preTime;
preTime = time;
// cube物体允许运动
// elapsedTime/1000是将毫秒改为秒
// 1m/s的速度* 时间(秒)= 移动的距离
// 将当前位置+=移动的距离,即为最后的距离
cube.position.x += 1 * (deltaTime/1000)
// renderer.render(scene, camera);
// 渲染下一帧的时候就会调用render函数
requestAnimationFrame(render);
}
render();
# 尺寸自适应
window.addEventListener("resize", () => {
// 更新摄像头
camera.aspect = container.value.clientWidth / container.value.clientHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(container.value.clientWidth, container.value.clientHeight);
// 最后,设置像素比例
renderer.setPixelRatio(window.devicePixelRatio);
});
# 控制场景全屏
window.addEventListener("dblclick", () => {
const fullScreenElement = document.fullscreenElement;
if (!fullScreenElement) {
// 双击控制屏幕进入全屏,退出全屏
// 让画布对象全屏
renderer.domElement.requestFullscreen();
} else {
// 退出全屏,使用document对象
document.exitFullscreen();
}
});
# gui 可视化改变三维场景
- 安装dat.gui
npm install dat.gui --save
- 引入
// 导入dat.gui
import * as dat from "dat.gui";
示例代码
// 控制变量图形界面
const gui = new dat.GUI();
gui
.add(cube.position, "x")
.min(0)
.max(5)
.step(0.01)
.name("移动x轴坐标")
// 改变事件
.onChange((value) => {
console.log(value);
})
// 改变完成后事件回调
.onFinishChange((value) => {
console.log("value", value);
});
// 修改物体的颜色
gui.addColor({ color: "#fff" }, "color").onChange((color) => {
cube.material.color.set(color);
});
// .onFinishChange((color) => {
// cube.material.color.set(color);
// });
// 设置选项框
gui.add(cube, "visible").name("是否显示");
// 点击触发某个事件
const fun = () => {
console.log("事件已触发");
// do something
};
gui.add({ fun: fun }, "fun").name("点击触发事件");
// 设置文件夹
var folder = gui.addFolder("设置立方体属性");
folder.add(cube.material, "wireframe"); // 只显示线框
folder.add(cube.material, "visible"); // 是否显示
# .domElement
通过.domElement属性可以获取gui界面的HTML元素,那就意味着你可以改变默认的style样式,比如位置、宽度等
//改变交互界面style属性
gui.domElement.style.right = '0px';
gui.domElement.style.width = '300px';
# .name()
.add()
创建的交互界面,会默认显示所改变属性的名字,为了通过交互界面更好理解你改变的某个对象属性,你可以通过.name()
方法改变gui生成交互界面显示的内容。示例如上。
# .step()
步长.step()
方法可以设置交互界面每次改变属性值间隔是多少。示例如上。
# .onChange()
当gui界面某个值的时候,.onChange()
方法就会执行,这时候你可以根据需要通过.onChange()
执行某些代码。示例如上。
# addColor()
.addColor()
生成颜色值改变的交互界面。
# gsap动画库
# 安装
npm install gsap
# 导入使用
import gsap from 'gsap'
var moveXanimation = gsap.to(cube.position, {
x: 3,
duration: 5,
ease: "", // 具体查看官网
repeat: -1, // -1 无线循环
yoyo: true, // 往返运动
delay: 2, // 动画开始延迟时间
onStart: () => {
console.log("动画开始");
},
onComplete: () => {
console.log("动画完成");
},
});
gsap.to(cube.rotation, {
x: 2 * Math.PI,
duration: 5,
repeat: -1,
yoyo: true,
});
// 控制动画开始与暂停
window.addEventListener("dblclick", () => {
if (moveXanimation.isActive()) {
moveXanimation.pause();
} else {
moveXanimation.resume();
}
});