最近在项目中遇到一些需要threejs的知识,特此撰写一篇相关文档记录一下自己的threejs学习。
threejs官方文档地址官方文档
创建一个threejs首先最基本的需要场景scene
、相机camera
、渲染器render
和容器DOM
元素。
const scene = new THREE.Scene();
场景能够让你在什么地方、摆放什么东西来交给three.js来渲染,这是你放置物体、灯光和摄像机的地方。
他的一些基本属性都可以在threejs的官方文档库中查阅到。
相机一共分为两类:正投影相机和透视相机
正投影相机是一种平行投影形式的相机,它将场景投影到一个平面上。这种相机通常用于2D场景或需要物体尺寸相对固定的3D场景。正投影相机没有远近裁剪面,并且不会根据距离来改变物体的大小,因此在使用正投影相机拍摄3D场景时需要注意物体大小的调整。
透视相机则是一种模拟人眼视角的相机,其投影效果更贴近实际情况。透视相机可以通过设置视角(Field of View)、近裁剪面(Near Clipping Plane)和远裁剪面(Far Clipping Plane)来控制场景的视野和深度感。透视相机投影出来的图像可以根据距离的远近来改变物体的大小,这也符合我们在现实世界中观察物体的规律。
官方两个相机视角的example
const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
其中4个参数的含义分别是
- fov — 摄像机视锥体垂直视野角度(越大边缘畸变越严重)
- aspect — 摄像机视锥体长宽比
- near — 摄像机视锥体近端面
- far — 摄像机视锥体远端面
这些参数一起定义了摄像机的viewing frustum(视锥体)。
const camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 );
OrthographicCamera( left : Number, right : Number, top : Number, bottom : Number, near : Number, far : Number )
其中6个参数的含义分别是
left — 摄像机视锥体左侧面。
right — 摄像机视锥体右侧面。
top — 摄像机视锥体上侧面。
bottom — 摄像机视锥体下侧面。
near — 摄像机视锥体近端面。
far — 摄像机视锥体远端面。这些参数一起定义了摄像机的viewing frustum(视锥体)。
渲染器是一个用于将场景和相机输出成图像的组件。它实现了WebGL的接口,并提供了简单易用的API来创建3D图形。它负责将场景、相机和材质等元素渲染到屏幕上。
在Three.js中,有几种不同类型的渲染器可供选择,例如WebGLRenderer、WebGLRenderTarget等。其中最常用的是WebGLRenderer,它使用WebGL技术进行渲染,并且可以自动适应不同的设备和浏览器。使用WebGLRenderer时,可以设置一些参数,如清空颜色、渲染目标的大小、是否启用阴影等。同时还可以设置多个渲染目标,以支持后期处理等高级功能。
首先创建一个Vue3项目
Vue create [项目名称]
引入threejs包
npm install three
在template
中创建div
作为DOM
元素放置threejs
对象
在script
中引入threejs
并且创建场景、相机、渲染器三要素
实例化场景scene
,相机camera
scene = new THREE.Scene();
//使用透视相机,具体参数上面有介绍
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 650, 0);//设置相机位置
添加立方纹理背景,这里的png图片要注意格式,必须是正方形图片
不想找图片的话,图片资源在官方example的github中有,可以自行提取。
const cubeLoader = new THREE.CubeTextureLoader();
cubeLoader.setPath('/img/');
const textureCube = cubeLoader.load([
'px.png', 'nx.png',
'py.png', 'ny.png',
'pz.png', 'nz.png'
]);
scene.background = textureCube;
增加光源
//增加环境光源
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
//增加点光源
pointLight = new THREE.PointLight(0xffffff, 5);
pointLight.position.set(-500, 1000, 100);
scene.add(pointLight);
增加一个平台底座,盛放等等加载的模型
//添加三位平台底座
scene.add(new THREE.GridHelper(800, 20));
接着把所有的东西都交给渲染器渲染出来
// 渲染
renderer = new THREE.WebGLRenderer({ antialias: true });//antialias - 是否执行抗锯齿。默认为false.
renderer.setPixelRatio(window.devicePixelRatio);//设置设备像素比。通常用于避免HiDPI设备上绘图模糊
renderer.setSize((window.innerWidth - 292), (window.innerHeight - 54));
const container: any = canvasRef.value
container.appendChild(renderer.domElement);
这个时候你是能加载出背景了,但是你无法转动方向,这时候就要介绍轨道控制器OrbitControls
OrbitControls 是一个附加组件,必须显式导入。
在 THREE.js 中,有两种不同版本的 OrbitControls 控制器。第一种是较早版本的 OrbitControl,它通常存储在addons
文件夹中,文件路径为three/addons/controls/OrbitControls.js
。而第二种是更新的 OrbitControls,它位于examples
文件夹中,文件路径为three/examples/jsm/controls/OrbitControls.js
。
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';//推荐使用新版本的
const controls = new OrbitControls(camera, renderer.domElement);
controls.enabled = false; // 关闭用户控制相机视角权限
controls.minDistance = 300;
controls.maxDistance = 700;
window.addEventListener('resize', onWindowResize);
/**
* 画面大小
*/
function onWindowResize() {
camera.aspect = (window.innerWidth / window.innerHeight);
camera.updateProjectionMatrix();//更新摄像机投影矩阵。在任何参数被改变以后必须被调用。
renderer.setSize(window.innerWidth, window.innerHeight);
}
//注意,在大多数属性发生改变之后,你将需要调用.updateProjectionMatrix来使得这些改变生效。
至此,你就可以看到一个带有平面网格平台的可控制画面角度的三维画面了!
现在我们可以在里面整点活,加载一些有趣的模型
我的模型资源是在这个gltf模型网站上下载的,下载好模型后,就是要添加到场景中了!
//额外引入gltf包
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
// 引入机器人
const planeLoader = new GLTFLoader();
planeLoader.load('/models/plane.gltf', (gltf: any) => {
const plane = gltf.scene;
const mixer = new THREE.AnimationMixer(plane) //配合动画使用,稍作解释
plane.scale.set(20, 20, 20); // 调整大小
plane.position.set(0, 0, 0); // 调整位置
scene.add(plane) // 添加
});
一架b52就被我偷回了家哈哈哈
光有一个飞机好像还少点什么,加一点树木点缀一下。
const treeLoader = new GLTFLoader();
let treeGroup;//放置多个树木可以一次性加入到组中
treeLoader.load('/models/tree.gltf', (gltf: any) => {
const tree = gltf.scene;
// 复制树木模型
for (let i = 0; i < 50; i++) {
tree.scale.set(10, 10, 10)
const clonedTree = tree.clone(); // 复制树木模型
clonedTree.position.set(-200 + Math.random() * 400, 0, -200 + Math.random() * 400); // 随机生成位置
treeGroup.add(clonedTree); // 添加到组中
}
// 加载模型
treeGroup = new THREE.Group();
scene.add(treeGroup);
});
这样树也有了,不过b52好像被树遮住了,那么干脆就让它起飞️,让它动起来!
这里就用到了刚刚在plane中定义的动画混合器AnimationMixer
,首先我们了解一下它。
动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。
首先我们创建一些点,然后将点使用CatmullRomCurve3
连接成线,用于作为飞机的飞行路线。
// 创建曲线
const points = [
new THREE.Vector3(300, 110, -200),
new THREE.Vector3(300, 500, 200),
new THREE.Vector3(-300, 10, 200),
new THREE.Vector3(-300, 250, -200),
];
// 绘制曲线
const curve = new THREE.CatmullRomCurve3(points, true, 'catmullrom', 0.5);
const path = new THREE.Path(curve.getPoints(200)); // 将曲线转换成路径
介绍一下这里的点位坐标,在threejs中点位坐标是下面这种形式,而(0,0,0)坐标中心就是网格平台的中心点。
接着介绍一下CatmullRomCurve3
使用Catmull-Rom算法, 从一系列的点创建一条平滑的三维样条曲线。
它提供的几个参数可以了解一下
– Vector3点数组
closed – 该曲线是否闭合,默认值为false。
curveType – 曲线的类型,默认值为centripetal。
tension – 曲线的张力,默认为0.5。
接着我们下面将创建好的曲线显现出来,便于我们对飞行路线做调试,以免撞到地面
// 曲线轨迹 测试使用
const geometry = new THREE.BufferGeometry().setFromPoints(curve.getPoints(50));
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
const curveObject = new THREE.Line(geometry, material);
scene.add(curveObject)
接着你就可以看到,绘制的飞行曲线了!我已经迫不及待想看到她飞起来的样子!
接下来就该使用AnimationMixer
将b52起飞!我们创建一个Object3D
基类,将飞机作为子元素添加进去。并且设置他的位置等。
在上方绘制曲线的时候,获取曲线全场curveLength
,然后通过定制一个速度,得到完成飞行全距离的总时间。在开始时定义一个startTime
,然后将定义的moveTime
取余,占全长的多少即位该移动的距离
function palyAnimate() {
if (plane && mixer) {
// 创建Follower
let follower = new THREE.Object3D();
scene.add(follower);
const path = new THREE.Path(curve.getPoints(200)); // 将曲线转换成路径
follower.position.copy(path.getPointAt(0));// 将follower的位置设置在初始位置
follower.lookAt(path.getPointAt(0.1)); // 设定为朝向曲线路径的第二个点的方向
scene.remove(plane);// 删除原来的模型
follower.add(plane);// 将模型作为子元素添加到follower中
const totalTime = curveLength / 1 // 这里的1是速度
startTime += 1;
const moveTime = startTime % totalTime;
const distance = (moveTime * 10) % curveLength; //应该移动的距离
const planePosition = curve.getPointAt(distance / curveLength)//获取follower应该在曲线上的位置
follower.position.copy(planePosition); // 更新follower的位置
const lookAhead = 0.1; // 指定follower朝向曲线路径前方的距离(可根据需要调整)
const target = curve.getPointAt((distance + lookAhead) / curveLength); // 获取follower朝向的目标点
follower.lookAt(target); // 更新follower的朝向
}
requestAnimationFrame(palyAnimate);
renderer.render(scene, camera);
}
最后调整一下曲线和模型大小就可以看到b52在空中翱翔!
最后在加上一些相机的运镜视角
//调整相机、光线角度
function rotateCamera() {
theta += 0.1;
camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
camera.position.y = Math.abs(radius * Math.sin(THREE.MathUtils.degToRad(theta))) + 100;
camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
pointLight.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta))
pointLight.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
pointLight.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
camera.lookAt(scene.position);
controls.update(); // 更新OrbitControls
}
最终效果还是可以看看的,也顺利的让飞机起飞了!
初识threejs可能还有很多不对的地方,希望大家多多包涵