Three.js 是什么?
一个封装了 WebGL 的库,简化 WebGL 的使用
WebGL vs OpenGL
OpenGL
主要被认为是一种 API(应用程序编程接口),它为我们提供了大量可用于操作图形和图像的函数,主要用 C语言编写的。
然而,OpenGL 本身并不是一个 API,而是一个规范,规定了每个函数如何执行和输出,至于具体实现,是由各个厂商的开发者根据自己的硬件特性开发出相应的 API。市场上,OpenGL 大都是显卡、GPU 、浏览器厂商来实现的,例如 Google。
WebGL
是一种用于在Web浏览器中展示 3D 图形的技术,是基于 OpenGL ES 2.0 的 Javascript API。也就是,通过浏览器提供的接口,我们可以使用底层的 OpenGL 库。
OpenGL ES 是 OpenGL 为了满足嵌入式设备需求而开发的一个特殊版本,是它的子集。但和 OpenGL 还是有差别,并不是只取其中一部分。
基础的 Three.js 应用结构
要写一个 Three.js 应用常用用到的四个核心概念,包括渲染器(Renderer)、场景(Scene)、相机(Camera)、网格(Mesh)。
渲染器 Renderer
将在指定相机角度下看到的 scene 画面绘制到浏览器上,也就是将摄像机的三维场景渲染成一个二维场景。
如果涉及到动态的场景,例如旋转,需要创建循环渲染 requestAnimationFrame,它会以每秒 60 次的频率来绘制场景
var renderer = new THREE.WebGLRenderer({ antialias: true });
// 将渲染器的像素比设置为设备像素比
renderer.setPixelRatio(window.devicePixelRatio);
// 将渲染器的大小设置为窗口的内部宽度(window.innerWidth)和高度(window.innerHeight)
// 如果为 1/2 ,视图将缩小一半
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置动画循环
document.body.appendChild(renderer.domElement);function renderFunc() {requestAnimationFrame(renderFunc); // 循环渲染renderer.render(scene, camera);
}renderFunc()
场景 Scene
场景是一个容器,可以将所有我们想渲染的物体都装在里面
var scene = new THREE.Scene();scene.add(cube) // 把cube装入场景里,初始位置为 (0,0,0)
相机 Camera
在浏览器里渲染的景象就是从相机视角拍出来的。
相机的位置、视角不同,拍到的风景也不同,只能拍到相机投影范围内的景物
相机的类型有两种,透视摄像机(PerspectiveCamera)、正投影相机(OrthographicCamera)
透视摄像机 PerspectiveCamera
近大远小,距离相机近的地方大,距离相机远的地方小
var camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
PerspectiveCamera 的参数说明,如下图所示:
- fov:视场。可以理解为睁眼时,下眼皮到上眼皮睁开的角度。假如是 90 度,那么两个眼皮距离中间的角度分别是 45 度。假如是 180 度,那么物体会变小,因为在整个可视区的占比变小了。
- aspect:长宽比。整个窗口的长宽比,一般是 window.innerWidth / window.innerHeight
- near:近面。距离相机多近的距离开始渲染场景。
- far:远面。定义相机可以看多远。
近面到远面的范围,为相机可以看到的范围,出了这个范围,则不可见。
正投影相机 OrthographicCamera
正投影相机的所有对象渲染的尺寸都一样。因此它不关心使用什么样的长宽比和视角。
var camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far)
OrthographicCamera 的参数说明,如下图所示:
- left:左边界。可视范围的左平面,超出这个范围的不会被渲染。
- right:右边界。可被渲染的最右面。
- top:上边界。可被渲染的最上面。
- bottom:下边界。可被渲染的最下面。
- near:近面。距离相机的位置,从该位置开始渲染
- far:远面。基于相机的位置,一直渲染到场景中的这一点
只有在范围 left、right、top、bottom、near、far 范围内的可见。超出该范围不可见。
网格 Mesh
可以理解为用一种特定的材质(Material)来绘制的一个特定的几何体(Geometry)
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4); // 绘制立方体
var cubeMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true}); // 绘制材质
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial); // 用该材质绘制后的立方体
scene.add(cube); // 添加到场景中
坐标系转换
坐标如下所示
可通过创建坐标系辅助对象,来展示坐标系。红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
var coneAxesHelper = new THREE.AxesHelper(size); // size 为轴的线段长度
右手坐标系
Three.js 使用的是右手坐标系,x 轴朝右,y 轴朝上,z 轴朝向自己。
围绕轴旋转
拇指指向轴正方向,四指弯曲的方向为旋转正方向。
如下图所示,将 rotation 的 y 值,从 0 度改为 50 度,物体沿着 y 轴逆时针旋转
物体旋转 50 度:
editor 编辑器
使用 Three.js 源码 的 editor编辑器,可以让我们更直观的理解 Three.js 中的物体的坐标系位置。
将项目跑起来,点击到对应的目录即可。
由此可看出,相机被添加到场景中时的初始位置是 (0,0,0)
相机朝向 Z 轴的负方向
一个简单的 Three.js 应用
HTML 代码:
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><title>My first three.js app</title><style>body { margin: 0; }</style></head><body><script type="module" src="/main.js"></script></body>
</html>
Javascript 代码:
import * as THREE from 'three';const scene = new THREE.Scene(); // 场景
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); // 透视摄像机const renderer = new THREE.WebGLRenderer(); // 渲染器
renderer.setSize( window.innerWidth, window.innerHeight ); // 设置渲染器的大小
document.body.appendChild( renderer.domElement ); // 将渲染器元素添加到 HTML 文档中const geometry = new THREE.BoxGeometry( 1, 1, 1 ); // 创建一个立方体几何
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); // 创建材质
const cube = new THREE.Mesh( geometry, material ); // 用材质绘制后的立方体
scene.add( cube ); // 添加到场景中camera.position.z = 5; // 设置相机的位置,因为相机朝向 Z 轴负方向。往后推,这样才能看得到全局function animate() {requestAnimationFrame( animate ); // 循环渲染cube.rotation.x += 0.01; // 立方体沿 x 轴旋转cube.rotation.y += 0.01; // 立方体沿 y 轴旋转renderer.render( scene, camera ); // 渲染
}animate();
换成优雅的类封装
把所有的代码放在同一个页面里,会越堆越多,阅读体验不好。因此可按照功能拆分成函数。进一步可放到类里调用。
以下代码仅提供思路。
// 调用game = new Game("canvas.webgl");game.start();// 封装
export class Game {width!: number; // 视图宽度height!: number; // 视图高度aspect!: number; // 视图宽高比...constructor(selector: string) {if (Game.instance) {return Game.instance;}Game.instance = this;this.selector = selector;this.initConfig();this.initTime();this.initResource();this.initContainer();this.initScene();this.initCamera();this.initRenderer();...}initConfig() {this.width = window.innerWidth;this.height = window.innerHeight;this.aspect = this.width / this.height;}initScene() {this.gameScene = new GameScene();}initCamera() {this.gameCamera = new GameCamera();}...
}
参考资料,也为不错的学习资料。
《Three.js 开发指南》
Three.js 专栏
Three.js-docs
Three.js-manual