Skip to content

基础概念

threejs-plan-1

1. 场景 Scene

相当于现实中的话剧舞台, 可以包含人物, 沙发, 电视, 灯光, 水果, 金属, 玻璃等等

https://threejs-docs.netlify.app/docs/index.html#manual/zh/introduction/Creating-a-scene

Scene 对象就是3D世界的容器, 所有的对象, 包括灯光, 网格, 物体, 流体等都要放到这个容器里. 还包含背景, 烟雾等属性

创建场景

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset='utf-8'>
		<title>three.js app</title>
	</head>
	<body>
		<script src='/src/js/three.js'></script>
		<script>
			// Our Javascript will go here.
		</script>
	</body>
</html>
js
  import { Scene } from 'three';

  const scene = new Scene();

  // 或: const scene = new THREE.Scene();
  // 伪代码
  scene.add('物体', '灯光', '环境', '...');

2. 几何体 Geometry

简单理解: 物体的形状

几何体是3D图形的基本元素。表示3D物体的形状和大小,如立方体、球体、圆柱体等

常用内置几何体:

2-1. 计算机三维世界的组成

Three.Vector3 (x, y, z)

  • 线

Three.Line

三个不在1条直线上的点可以组成1个三角形面, n个三角形面通过组合可以拼接成任何形状的物体

Mesh网格模型

3. 基础材质 MeshBasicMaterial

简单理解: 皮肤

是threejs中的基本材质, 这种材质只会显示几何体的颜色,不会受到光照影响

常用的内置材质

4. 网格对象 Mesh

网格用来将几何体和材质结合起来形成可视化的3D物体, 是 Three.js 中最基础的可视化对象

由几何体和材质两部分组成,可以被添加到Three.js的场景中,并且可以被控制,如移动、旋转、缩放等

Mesh网格模型

Mesh网格模型 Mesh网格模型

为什么是三角形
  • 三角形是最简单的多边形,少于3个顶点就不能成为一个表面;
  • 三角形必然是平坦的,含4个或以上的顶点的多边形,不一定平坦,三个点确定一个平面,多余的点可能在这个面之上或者之下;
  • 三角形经多种转换之后,仍然是三角形,这对于仿射转换和透视转换也成立。最坏的情况下,从三角形的边去看,三角形会退化为线段。在其它角度观察,仍能维持是三角形;
  • 几乎所有商用图形加速硬件都是为三角形光栅化而设计的。

5. 相机 camera

相机是指场景中的观察者,它决定了你看到的是场景中的哪些内容. 3D 场景中可以放置多个镜头, 而且可以在相机之间进行切换

https://threejs-docs.netlify.app/docs/index.html#api/zh/cameras/Camera

5-1. 3D场景中用到的坐标系

  • 世界坐标系

公共坐标, 比如公共场景对象

相机默认在世界坐标原点

坐标系

  • 本地坐标系

每个元素都有自己的坐标系, 也就是自身的本地坐标系

  • 右手坐标系

右手坐标系 手指方向均为正方向

5-2. 透视相机 PerspectiveCamera

模拟人眼看到的投影效果(近大远小)

透视相机 透视相机

透视投影透视投影 视锥

视锥宽高比 aspect

默认长宽比为1,即表示看到的是正方形, 那么如何适配不同的屏幕宽高比?

透视投影 从上往下效果 俯视角度

5-3. 正交相机 OrthographicCamera

设置left, right top, bottom, near, 和far指定长方体, 使得视野是平行的而不是透视的

正交相机 正交相机视野

正交相机 使用正交相机展示三视图

5-4. 其他相机

摄像机阵列(arraycamera), 立方相机(cubecamera)等

6. 渲染器: WebGLRenderer

  • WebGLRenderer 将场景绘制到画布上
  • WebGLRenderTarget 缓冲, 后台渲染
  • WebGLMultipleRenderTargets 高级渲染器, 常用于后期处理等

6-1. threejs 渲染结构图

threejs 渲染结构图

threejs 渲染结构图
  • 所有的物体, 灯光, 纹理都要经过render才能看到, 也就是说 render 用来呈现结果, render 通过算法把 3D 场景投影成2D画面绘制到画布上

  • Mesh, Light 是单个对象, 而 Object3D, Group 可以添加n个子对象, 也就是可以对对象进行分组, 创建集合.

  • 所有的物体, 灯光, 纹理等3D对象都要放到场景中才能被查看, 也就是说 Scene 是最底层的容器, 而且Scene 是树结构的对象. 也就是说可以通过遍历拿到所有的子集

  • 相机在场景内, 也在场景外, 说明 Camera 比较特殊, 可以看向场景, 但看不到自己, 也可以不看向场景, 此时就是'黑屏', 可以对物体样式产生影响, 光线照到物体上与物体的材质产生反应, 比如变亮变暗, 变色等

  • Mesh(网格)继承自Object3D, 其自身包含两个部分, Geometry(几何体)和Material(材质)

  • Geometry(几何体)相当于HTML中的布局, 用来描述物体的形状, 具有物体顶点的相关信息, 比如顶点坐标, 索引等

  • Material(材质)相当于css, 用来描述物体的样式, 对应了webgl 中着色功能, 存储了物体的颜色, 阴影, 光泽度, 漫反射等, 这些效果可以通过 Texture 贴图来实现.

  • Texture(纹理)是种特殊的图像, 可以是png, img等格式, 也可以是canvas画布, 视频等形式

  • 隐含信息:

    相机也可以包含在Group中, 跟随物体移动

    对 Group 应用缩放移动等方法, 内部的3D对象会同时被操作, 不用单独给每个3D对象应用变换

    每个对象的变换都是相对于父级做相对运动, 如 #_5-3-正交相机 俯视视角

    场景发生了移动, 观察者本地坐标不变, 但世界坐标发生了变化

    Geometry Material Texture 均可以复用, 用于性能优化

    threejs 内置了丰富的 Geometry/Material, 也可以通过建模创建自定义几何体/材质

7. 在React中使用

Demo1-0 使用 react 渲染
js
import * as THREE from 'three';

console.log(THREE);
const canvas = document.getElementById('c');
// 创建3D场景
const scene = new THREE.Scene();
// 创建立方体
const cube = new THREE.BoxGeometry(1, 1, 1);
// 设置立方体表面颜色
const material = new THREE.MeshBasicMaterial({
  color: '#1890ff',
});
// 生成物体对象
const mesh = new THREE.Mesh(cube, material);

scene.add(mesh);

// 创建观察场景的相机
const camera = new THREE.PerspectiveCamera(75, width / height); // 透视相机

// 设置相机位置
camera.position.set(2, 2, 3); // 相机默认的坐标是在(0,0,0);
// 设置相机方向
camera.lookAt(scene.position); // 将相机朝向场景
// 将相机添加到场景中
scene.add(camera);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({
  canvas,
});

// 设置渲染器大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 执行渲染
renderer.render(scene, camera);

threejs 中没有单位, 都是向量

position.set 也可以写成: position.x = 2, position.y = 2, position.z = 3;

因为都是向量, 所以向量长度: mesh.position.length()

mesh.position.distanceTo(camera.position) 可以得到两个向量坐标的距离

.lookAt(new THREE.Vector3(2, 2, 3)) 可以让3D物体自动旋转朝向某个坐标

8. 遗留的问题

  • 边缘有锯齿
  • 没有3D效果
  • 每个面颜色相同

9. 去除锯齿

创建渲染器时增加 antialias 属性
js
const renderer = new WebGLRenderer({
  canvas,
  antialias: true, // 抗锯齿
});

// 根据屏幕像素比进行渲染, 避免HiDPI设备上绘图模糊
renderer.setPixelRatio(window.devicePixelRatio);

设置像素比效果更加明显

  • renderer 更多属性和方法, 详见官网:

    .autoClear

    ...

    .getClearColor()

    .getContext()

    .getCurrentViewport()

10. 3D效果

Demo1-1
js
// const material = new THREE.MeshBasicMaterial({
const material = new THREE.MeshLambertMaterial({ 
  color: '#1890ff',
});

MeshLambertMaterial 表面粗糙的材质,可以起镜面反射作用. 不适用于金属、玻璃等物体

11. 为什么换了材质却什么都看不见

Demo1-2
js
// 添加全局光照
const ambientLight = new THREE.AmbientLight(0xffffff);

scene.add(ambientLight);

AmbientLight 是全局/环境光照

12. 添加方向光

Demo1-3
js
// 添加方向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);

scene.add(directionalLight);

DirectionalLight 是方向光

13. 给每个面添加不同的颜色

Demo1-4
js
const geometry = new THREE.BoxGeometry(1, 1, 1);
let mats = [];

console.log(geometry);
for (var i = 0; i < geometry.groups.length; i++) {
  // 重新生成新的材质
  const material = new THREE.MeshBasicMaterial({
    color: new THREE.Color(Math.random() * 0xffffff),
  });

  mats.push(material);
}
const mesh = new THREE.Mesh(geometry, mats);
// ...

THREE.Color 支持多种颜色格式

geometry.groups 中包含几何模型的元素

14. 坐标轴辅助工具

Demo1-5
js
const width = window.innerWidth;
const height = window.innerHeight;
// 创建3D场景
const scene = new THREE.Scene();
// 创建立方体
const geometry = new THREE.BoxGeometry(2, 2, 2);
// 设置立方体表面颜色
const material = new THREE.MeshBasicMaterial({
  color: '#1890ff',
});
// 生成物体对象
const mesh = new THREE.Mesh(geometry, material);

scene.add(mesh);

// 辅助坐标系
const axesHelper = new THREE.AxesHelper(4);

scene.add(axesHelper);

// 创建观察场景的相机
const camera = new THREE.PerspectiveCamera(75, width / height); // 透视相机

// 设置相机位置
camera.position.set(2, 2, 3); // 相机默认的坐标是在(0,0,0);
// 设置相机方向
camera.lookAt(scene.position); // 将相机朝向场景
// 将相机添加到场景中
scene.add(camera);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({
  canvas,
  antialias: true, // 抗锯齿
});

renderer.setPixelRatio(window.devicePixelRatio);
// 设置渲染器大小
renderer.setSize(width, height);
// 执行渲染
renderer.render(scene, camera);

axesHelper 内置的坐标轴辅助工具

红色是x轴正方向, 绿色是y轴正方向, 蓝色是z轴正方向, 垂直于屏幕

15. 添加控制器

Demo1-6
js
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

const controls = new OrbitControls(camera, canvas);

// controls.enabled = false; // 禁用拖拽控制
controls.enableDamping = true; // 启用拖拽惯性效果

轨道控制器是以物体为中心,对物体做环绕运动

为什么拖拽惯性没有生效?
js
// ...
let timer;
const tick = () => {
  clearTimeout(timer);
  timer = setTimeout(() => {
    // update objects
    controls.update();
    // 重新渲染整个场景
    renderer.render(scene, canvas);
    // 调用下次更新函数
    tick();
  });
};

tick();

16. 改写成 requestAnimationFrame

Demo1-7
js
const tick = () => {
  // update objects
  controls.update();
  renderer.render(scene, canvas);
  window.requestAnimationFrame(tick);
};

tick();

17. 匀速运动

Demo1-8
js
mesh.rotation.set(Math.PI / 4, 0, 0, 'XYZ'); // 'XYZ' 表示旋转顺序的字符串 默认为 xyz
mesh.scale.set(3, 1, 1); // 物体缩放

let timestamp = Date.now();
const tick = () => {
  const currentTime = Date.now();
  const deltaTime = currentTime - timestamp; // 两次渲染时间间隔

  timestamp = currentTime;
  mesh.rotation.y += 0.001 * deltaTime; // 可以保持匀速
  renderer.render(scene, camera);
  window.requestAnimationFrame(tick);
}

所有3D对象都有4个用于变换的属性: position, scale, rotation, quaternion (四元数)

mesh.rotation.reorder('yxz') 可以设置旋转轴应用顺序, 使用 quaternion 方式也可以进行旋转, 但不用担心旋转轴应用顺序的问题

使用 rotation 或 quaternion, 两个属性都会同步更新

18. 加速运动

Demo1-9
js
const clock = new THREE.Clock();
const tick = () => {
  const elapsedTime = clock.getElapsedTime(); // 获取时钟运行的总时长
  // clock.getDelta(); 两次渲染时间间隔

  // update objects
  mesh.rotation.y += 0.005 * elapsedTime; // 加速
  // 重新渲染整个场景
  renderer.render(scene, camera);
  // 调用下次更新函数
  window.requestAnimationFrame(tick);
}

19. 物体做圆周运动

Demo1-10
js
// 添加网格辅助线
const gridHelper = new THREE.GridHelper(100, 50, 0xcd37aa, 0x4a4a4a);

scene.add(gridHelper);

const tick = () => {
  cube.position.y = Math.sin(elapsedTime); 
  cube.position.x = Math.cos(elapsedTime); 
}

tick();

物体运动的方法 1

19-1. 相机做圆周运动

Demo1-10
js
const tick = () => {
  camera.position.y = Math.sin(elapsedTime); 
  camera.position.x = Math.cos(elapsedTime); 
  camera.lookAt(mesh.position); 
}

tick();

物体运动的方法 2

20. 监听窗口大小变化

Demo1-11
js
window.addEventListener( 'resize', function () {

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);

}, false );

camera.aspect 相机视锥体长宽比

在大多数属性发生改变之后,需要调用 .updateProjectionMatrix 来使得这些改变生效。

21. 画布性能监控

Demo1-12
js
import Stats from 'three/examples/jsm/libs/stats.module';

// 创建性能监视器
const stats = new Stats();

// 设置监视器面板,传入面板id(0: fps, 1: ms, 2: mb)
stats.setMode(0);

// 将监视器添加到页面中
document.body.appendChild(stats.domElement);

const tick = () => {
  // ...
  // 更新帧数
  stats.update();
  // 调用下次更新函数
  window.requestAnimationFrame(tick);
}

Released under the CC BY-SA License.