Three.js 动态网格渲染和动画教程
在这篇教程中,我们将使用 Three.js 来创建一个动态的网格渲染和动画展示。看下效果
初始设置
首先,我们需要导入必要的 Three.js 模块和其他依赖,如下所示:
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { gsap } from "gsap";
import "./style.css";
THREE
是主库,提供了创建和显示 3D 图形的核心功能。OrbitControls
允许用户通过鼠标操作来旋转、缩放和平移相机。gsap
是一个强大的动画库,用于创建平滑的动画效果。
创建渲染器、场景和相机
创建并配置渲染器、场景和相机:
const renderer = new THREE.WebGLRenderer({ alpha: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 100);
const controls = new OrbitControls(camera, renderer.domElement);
WebGLRenderer
用于在网页上渲染 3D 图形。- 设置
alpha: true
允许背景透明。 PerspectiveCamera
提供了一种透视投影的相机视角。- 相机参数解释:视角 75 度,纵横比 2,最近裁剪面 0.1,最远裁剪面 100。
窗口调整和动画循环
确保渲染器的尺寸随窗口大小改变而调整,并设置动画循环:
window.addEventListener("resize", () => {
const { clientWidth, clientHeight } = renderer.domElement;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(clientWidth, clientHeight, false);
camera.aspect = clientWidth / clientHeight;
camera.updateProjectionMatrix();
});
- 这段代码监听窗口尺寸变化,并相应地调整渲染器和相机的配置。
定义尺寸和图片 URLs
const W = 10,
H = 10,
SW = W * 20,
SH = H * 20;
const IMG_URLS = ["https://picsum.photos/600/600", "https://picsum.photos/800/800"];
W
和H
分别代表网格的宽度和高度,这里设置为 10 个单位。SW
和SH
是整个场景的网格的宽度和高度的扩展,通过乘以 20 来创建更细分的网格,从而可以创建更复杂的几何形状和细节。IMG_URLS
数组包含了用于纹理的图片 URL,这些图片会被加载并映射到几何体的表面,提供视觉上的丰富性。
设置相机位置
camera.position.set(0, 0, 8);
- 这行代码设置相机的位置,使其在 Z 轴上位于原点之前 8 个单位处,为观察者提供了一个正面的视角来查看整个 3D 场景。
添加聚光灯
for (const { color, intensity, x, y, z } of [
{ color: "white", intensity: 1, x: -W, y: 0, z: 0 },
{ color: "white", intensity: 1, x: W, y: 0, z: 0 },
]) {
const L = new THREE.SpotLight(color, intensity, W, Math.PI / 2, 0, 0);
L.position.set(x, y, z);
scene.add(L);
}
循环遍历一个包含聚光灯配置的数组,设置两个聚光灯来照亮场景。每个灯的参数如下:
color
指定了灯光的颜色,这里为白色,提供了中性的照明,适合大多数场景。intensity
是灯光的强度,设置为 1,代表标准的光照强度。x
,y
,z
定义了灯光的位置。灯光被放置在网格的左右两侧(-W
和W
),这样可以从两侧对场景进行均匀的照明,减少阴影区域,提高视觉效果的立体感。W
是聚光灯的距离参数,控制光源的范围。Math.PI / 2
设置聚光灯的角度,指的是聚光灯光线发散的宽度。- 最后两个参数(
0, 0
)代表聚光灯的衰减和阴影。
创建顶点数组
const vs = [];
for (let i = 0; i < SH; ++i) {
vs[i] = [];
for (let j = 0; j < SW; ++j) {
vs[i][j] = {
// 顶点数据将在这里定义
};
}
}
vs
是一个二维数组,用于存储顶点数据。每个顶点包含网格中的位置、纹理坐标等信息。SH
和SW
是之前定义的网格的高度和宽度,这里通过双层循环为每一个网格单元生成一个顶点对象。- 这些顶点数据随后会用来构建 3D 模型的几何体。
构建几何体
const geoms = [];
for (let k = 0; k <= 1; ++k) {
const geom = new THREE.BufferGeometry();
// 设置属性和计算法线
geoms.push(geom);
}
geoms
数组用于存储几何体。- 使用
THREE.BufferGeometry()
构建高效的几何体对象,该对象适合复杂的 3D 模型。 - 在这个循环中,可以针对每种不同的顶点数组配置和使用不同的绘制方式,例如使用三角剖分方法来生成网格。
- 几何体的属性(如顶点位置、纹理坐标)和法线会在这里设置,法线是重要的渲染属性,影响材质对光线的反应。
加载纹理并创建材料和网格
const g = new THREE.Group();
for (const [i, geom] of geoms.entries()) {
const map = new THREE.TextureLoader().load(IMG_URLS[i]);
const mat = new THREE.MeshLambertMaterial({ map, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geom, mat);
g.add(mesh);
}
scene.add(g);
- 使用
THREE.Group()
创建一个组,这使得可以一次性对多个对象进行变换、控制和动画处理。 THREE.TextureLoader().load()
方法用于加载纹理图像,这些图像从IMG_URLS
数组中获取,将为每个几何体的表面提供视觉纹理。THREE.MeshLambertMaterial
是一种对光照反应良好的材料,适合创建现实感的效果。side: THREE.DoubleSide
参数指明材料应该在几何体的两侧都可见。THREE.Mesh
对象将几何体和材料结合起来,创建最终的网格对象。- 将每个网格添加到组中,然后将整个组添加到场景中,这样做的好处是可以统一管理、变换或动画化这些网格。
动画循环
最后,添加动画循环:
renderer.setAnimationLoop((t) => {
g.rotation.y += rotationSpeed;
renderer.render(scene, camera);
controls.update();
});
代码
github
https://github.com/calmound/threejs-demo/tree/main/photo (opens in a new tab)
gitee
https://gitee.com/calmound/threejs-demo/tree/main/photo (opens in a new tab)