前言
大家好,我叫虚竹。最近一直在学习threejs
,感觉很有意思就梳理了一下自己学习笔记分享出来。以备日后项目需要,后续还会不断完善丰富我的threejs
案例库,希望能在学习的路上帮到大家。
为什么要学它,市场有需求。近年来web得到了快速的发展,随着HTML5的普及,网页的表现能力越来越强大。尽管网页已经实现了很多精美的展示效果与优雅的交互效果,但人总是贪婪的,web开发已经不满足于2D效果的实现,更加炫酷的3D效果更能吸引人的目光。Three.js
是用于实现web3D
效果的JS库,它的出现让3D应用开发更简单。本文将通过Three.js
的介绍及示例带我们走进3D的奇妙世界。
Web3研习社:54web3.cc/
效果截图如下所示:
优势
- TS支持
- 面向对象编程(继承)
- 文档相对完善(入门简单)
- 丰富的开源案例
- 基于浏览器显示,用户访问相对方便
- 生态全,行业认可度高,WebGL封装完善
劣势
- 学习曲线比较高,特别在一些比较复杂的动画时,需要三维空间想象能力和较好的数学基础
- 性能优化存在瓶颈,由于threejs是基于浏览器的3D,浏览的内存较小,导致在复杂模型和动画时内存溢出
- 对设备要求较高,低设备会导致3D模型渲染较慢,动画卡顿
- 没有提供一些基础建模软件的插件
初识 threejs
官网介绍:Javascript 3D library(JavaScript 3D 库)。
Three.js
是一款基于webGL(Web Graphics Library
)的封装,简单易用且轻量级的3D库。Three.js对WebGL提供的接口进行了非常好的封装,简化了很多细节,大大降低了学习成本,极大地提高了性能,功能也非常强大。用户不需要详细地学习WebGL,就能轻松创作出三维图形,是前端开发者研发3D绘图的主要工具。
Three.js
是纯渲染引擎,而且代码易读,容易作为学习WebGL、3D图形、3D数学应用的平台,也可以做中小型的重表现的Web项目。
用最简单的一句话概括:WebGL 和 Three.js 的关系,相当于 JavaScript 和 jQuery 的关系。
技术栈
- vite v2.9
- react v18
- threejs v140
- less
- hooks
目录结构
准备工作
- 下载安装 nodejs v12+
- 代码编辑器工具 VS Code
- 推荐谷歌浏览器
基础知识点
- 场景(Scene):是物体、光源等元素的容器。
- 相机(Camera):场景中的相机,可以通过操作相机的方式来改变观察者的位置和朝向,场景中只能添加一个,决定哪些物体将在屏幕上渲染。
- 渲染器(Renderer):场景的渲染方式,如 WebGL/canvas2D/CSS3D。
- 物体对象(Mesh):包括二维物体(点、线、面)、三维物体,模型等等。
- 光源(light):场景中的光照,如果不添加光照场景将会是一片漆黑,包括全局光、平行光、点光源等。
- 材质(material):材质就像是物体的皮肤,决定物体外表的样子,例如物体的颜色,看起来是否光滑,是否有贴图等等。
- 加载器(Loader):用于加载纹理、图片、模型、音频等资源。
- 控制器(Control):可通过键盘、鼠标控制相机的移动。
大体实现思路
- 构建一个场景,也就是一个三维空间
- 创建一个相机,也就是一个观察点,并且定义观察的位置和角度
- 定义形状和材质,把他们合起来之后放到场景中
- 使用指定的渲染器将整体渲染到屏幕上
代码实现
构建本地服务
Vite 是一种新型前端构建工具,能够显著提升前端开发体验。Vite 需要 Node.js 版本 >= v12.0.0,然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。
npm create vite@latest three-demo
cd three-demo
npm install
npm run dev
# 或者 yarn 安装启动项目
yarn create vite@latest three-demo
cd three-demo
yarn
yarn start
安装threejs库
npm i three
搭建3D场景步骤
导入threejs核心库
import * as THREE from "three";
创建场景
// 创建场景对象Scene
const scene = new THREE.Scene();
创建渲染器
// 参数:antialias 是否执行抗锯齿。默认为false
const renderer = new THREE.WebGLRenderer({ antialias: true });
创建相机
// 创建透视相机,带四个参数
const camera = new THREE.PerspectiveCamera(for, aspect, near, far);
初始化相机
const initCamera = useCallback(() => {
camera.aspect = window.innerWidth / window.innerHeight; // 设置场景的宽高比
camera.for = 45; // 相机的视角
camera.near = 1; // 相机的近端面
camera.far = 1000; // 相机的远端面
camera.position.set(0, 10, 20); // 设置相机位置
camera.lookAt(0, 0, 0); // 设置相机面向(0, 0, 0)xyz坐标观察
camera.updateProjectionMatrix(); // 更新相机
}, [camera]);
初始化渲染场景
const domRef = useRef(); // 创建 domRef 对象,并通过 domRef.current 访问对应的 DOM 对象
const initRenderer = useCallback(() => {
renderer.setPixelRatio(window.devicePixelRatio); // 设置分辨率为当前设备的分辨率,解决场景模糊,抗锯齿的一种很好的方法
renderer.setSize(window.innerWidth, window.innerHeight); // 设置画布大小
renderer.shadowMap.enabled = true; // 开启渲染阴影效果
domRef.current.appendChild(renderer.domElement); // 挂载 DOM
}, [renderer, domRef]);
初始化灯光
const lights = useRef([]).current; // 创建 lights 空数组
const createLight = useCallback(() => {
// 太阳光
// const dirLight = new THREE.DirectionalLight("#fff", 0.5);
// dirLight.position.set(100, 200, 200);
// 环境光
const ambientLight = new THREE.AmbientLight("#fff", 0.3);
// 点光源
const pointLight = new THREE.PointLight("#fff", 1, 8);
pointLight.position.set(0, 5, 0); // 设置灯光xyz坐标位置
scene.add(pointLight, ambientLight); // 将灯光添加到场景中
lights.push(pointLight, ambientLight);
}, []);
渲染函数
// 渲染函数
const renders = useCallback(() => {
renderer.clear();
renderer.render(scene, camera);
}, [renderer, scene, camera]);
初始化用户交互
const initControls = useCallback(() => {
controls.enableDamping = true; // 是否有惯性
controls.enableZoom = true; // 是否可以缩放
// controls.autoRotate = true; // 是否自动旋转
controls.autoRotateSpeed = 2; // 设置自动旋转速度
controls.enablePan = true; // 是否开启右键拖拽
}, []);
创建基础立方体
const meshs = useRef([]).current; // 创建 meshs 空数组
const createRect = useCallback(() => {
// 设置正方体宽、高、深分别为2、2、2
const rectGeometry = new THREE.BoxGeometry(2, 2, 2); // 创建几何体对象
const rectMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 }); // 创建材质 - MeshBasicMaterial基础网格材质
const rect = new THREE.Mesh(rectGeometry, rectMaterial); // 网格物体由几何形状和材质组成不同的物体
rect.position.set(4, 0, 0); // 设置物体xyz坐标位置
scene.add(rect); // 将物体添加到场景中
meshs.push(rect);
}, []);
创建彩色立方体
创建彩色立方体只需将基础网格材质MeshBasicMaterial
改成法线网格材质MeshNormalMaterial
,代码如下:
const createRect2 = useCallback(() => {
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2);
const cubeMaterial = new THREE.MeshNormalMaterial(); // 创建法线网格材质
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(0, 0, 0);
scene.add(cube);
meshs.push(cube);
}, []);
创建线条几何体
const points = [];
const colors = [];
const createLine = useCallback(() => {
const lineGeometry = new THREE.BufferGeometry(); // 是点、线、面几何体的有效表述
const lineMaterial = new THREE.LineBasicMaterial({ vertexColors: true }); // 基础线条材质
for (let i = 0; i < 10000; i++) {
const x = Math.random() * 2 - 1;
const y = Math.random() * 2 - 1;
const z = Math.random() * 2 - 1;
points.push(x, y, z);
colors.push(Math.random());
colors.push(Math.random());
colors.push(0xff0000);
}
lineGeometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(points, 3)
);
lineGeometry.setAttribute(
"color",
new THREE.Float32BufferAttribute(colors, 3)
);
const line = new THREE.Line(lineGeometry, lineMaterial); // 连续的线组成的物体
line.position.set(8, 0, 0);
scene.add(line);
meshs.push(line);
}, []);
让立方体动起来
const id = useRef(null);
// 循环动画
const animate = useCallback(() => {
meshs.forEach((item) => {
item.rotation.x += (0.5 / 180) * Math.PI;
item.rotation.y += (0.5 / 180) * Math.PI;
});
......
id.current = window.requestAnimationFrame(animate);
}, []);
useEffect(() => {
......
animate();
return () => {
window.cancelAnimationFrame(id.current); // 取消动画
meshs.forEach((item) => {
scene.remove(item); // 删除场景
item.geometry.dispose(); // 释放内存
item.material.dispose();
});
scene.remove();
renderer.dispose(); // 释放内存
};
}, [renderer, scene, camera]);
引入控制器
可以使用鼠标对场景进行操作,比如旋转场景,移动场景,缩放场景
。定义一个轨道控制器,要想使轨道控制器生效,必须循环渲染场景requestAnimationFrame
,也就是在动画循环里调用controls.update()
方法。
// 导入轨道控制器JS库
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 更新动画
const animate = useCallback(() => {
......
controls.update(); // 更新控制器
renders();
id.current = window.requestAnimationFrame(animate);
}, []);
添加3D字体
导入FontLoader
JS库用于加载JSON格式的字体类,可以将.ttf
格式字体转换为json类型,可以使用facetype.js
来在线转换字体。
import { FontLoader } from "three/examples/jsm/loaders/FontLoader";
// 创建加载字体对象
const loader = new FontLoader();
导入TextGeometry
JS库将显示的文本生成单一几何体类。
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry";
创建字体函数
const fonts = useRef([]).current;
const createFont = useCallback(() => {
loader.load("fonts/STKaiti_Regular.json", (font) => {
const textGeometry = new TextGeometry("@ 懒 人 码 农", {
font: font, // 字体实例
size: 0.5, // 字体大小
height: 0.5, // 字体厚度
curveSegments: 0.05, // 文本的曲线上点的数量
bevelThickness: 0.05, // 文本上斜角的深度
bevelSize: 0.05, // 斜角与原始文本轮廓之间的延伸距离
// bevelSegments: 20, // 斜角的分段数
bevelEnabled: true, // 是否开启斜角
});
const textMaterial = new THREE.MeshNormalMaterial({
flatShading: true, // 是否使用平面着色进行渲染
});
const text = new THREE.Mesh(textGeometry, textMaterial);
// text.position.set(-6, 6, 0);
scene.add(text); // 将中文字体添加到场景中
fonts.push(text);
});
}, []);
让文字动起来
const clock = new THREE.Clock();
const animate = useCallback(() => {
const elapsed = clock.getElapsedTime();
......
fonts.forEach((item) => {
item.position.set(Math.sin(elapsed) * -6, 6, Math.cos(elapsed) * -6);
});
controls.update();
renders();
id.current = window.requestAnimationFrame(animate);
}, []);
浏览器窗口变动自适应
const onWindowResize = useCallback(() => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
renders();
}, []);
useEffect(() => {
......
window.addEventListener("resize", onWindowResize, false);
return () => {
window.removeEventListener("resize", onWindowResize, false);
}
}, [])
参考资料
- threejs官网:https://threejs.org/
- vite 官方文档: https://cn.vitejs.dev/
- github源码地址:https://github.com/mrdoob/three.js/
- threejs基础教程: http://www.yanhuangxueyuan.com/Three.js/
- 在线字体转换工具:https://gero3.github.io/facetype.js/
- 在线提取字体工具:https://www.fontke.com/tool/subfont/
结语
本文就写到这吧,以上内容是Three.js
中提供的基础功能并结合示例阐述。希望大家看完后能对Three.js
有个初步的了解,并能上手使用Three.js
绘制出基础的3D
图形动画。如果觉得有用,赶紧点赞收藏起来吧,说不定哪天就用上啦~