答:Three.js 基于 OpenGL,那我们从 OpenGL 文档看到这么一句话:
"The preceding paragraph mentions inches and millimeters - do these really have anything to do with OpenGL? The answer is, in a word, no. The projection and other transformations are inherently unitless. If you want to think of the near and far clipping planes as located at 1.0 and 20.0 meters, inches, kilometers, or leagues, it's up to you. The only rule is that you have to use a consistent unit of measurement. Then the resulting image is drawn to scale." ——[《OpenGL Programming Guide》][11]
中文:前面段落提及的英寸和毫米真的和 OpenGL 有关系吗?没有。投影和其它变换在本质上都是无单位的。如果你想把近距离和远距离的裁剪平面分别放置在 1.0 和 20.0 米/英寸/千米/里格,这取决于你。这里唯一的要求是你必须使用统一的测量单位,然后按比例绘制最终图像。
对于 Three.js 提供的几何体,我们不需要自己定义这些几何体的顶点和面,只需提供 API 指定的参数即可(如长方体的长宽高)。当然,你仍然可以通过定义顶点和面来创建自定义的几何体。如:
var vertices = [
new THREE.Vector3(1, 3, 1),
new THREE.Vector3(1, 3, -1),
new THREE.Vector3(1, -1, 1),
new THREE.Vector3(1, -1, -1),
new THREE.Vector3(-1, 3, -1),
new THREE.Vector3(-1, 3, 1),
new THREE.Vector3(-1, -1, -1),
new THREE.Vector3(-1, -1, 1)
]
var faces = [
new THREE.Face3(0, 2, 1),
new THREE.Face3(2, 3, 1),
new THREE.Face3(4, 6, 5),
new THREE.Face3(6, 7, 5),
new THREE.Face3(4, 5, 1),
new THREE.Face3(5, 0, 1),
new THREE.Face3(7, 6, 2),
new THREE.Face3(6, 3, 2),
new THREE.Face3(5, 7, 0),
new THREE.Face3(7, 2, 0),
new THREE.Face3(1, 3, 4),
new THREE.Face3(3, 6, 4)
]
var geometry = new THREE.Geometry()
geometry.vertices = vertices
geometry.faces = faces
geomtry.computeFaceNormals()
引言
随着 WebGL 标准的快速推进,越来越多团队尝试在浏览器上推出可交互的 3D 作品。相较于二维场景,它更能为用户带来真实和沉浸的体验。
然而 OpenGL 和 WebGL(基于 OpenGL ES) 都比较复杂,Three.js 则更适合初学者。本文将分享一些 Three.js 的基础知识,希望能让你能有所收获。
当然,分享的知识点也不会面面俱到,想更深入的学习,还得靠大家多看多实践。另外,为了控制篇幅,本文更倾向于通过案例中的代码和注释进行阐述一些细节。
若想系统学习,笔者认为看书是一个不错的选择:
![Three.js开发指南(原书第2版)][2]
Three.js开发指南(原书第2版) [购买链接>>][3]
尽管由于 Three.js 的不断迭代,书本上的某些 API 已改变(或弃用),甚至难免还有一些错误,但这些并不影响整体的阅读。
Canvas 2D
如引言中说道,3D 图像在计算机中最终以 2D 图像呈现。因此,渲染模式只是作为一个载体。下面我们用 JavaScript(无依赖) 在 Canvas 2D 渲染一个在正视图/透视图中的立方体。
正视图中的立方体:
See the Pen 3D Orthographic View by SitePoint (@SitePoint) on CodePen.
透视图中的立方体:
See the Pen 3D Perspective View by SitePoint (@SitePoint) on CodePen.
若要将三维图形渲染在二维屏幕上,需要将三维坐标以某种方式转为二维坐标。但对于更复杂的场景,大量坐标的转换和阴影等耗性能操作无疑需要 Web 提供更高效的渲染模式。
另外,想了解上述两个案例的实现原理,可查看译文:[《用 JavaScript 构建一个3D引擎》][4]。
WebGL
[WebGL][5](Web Graphics Library)在 GPU 中运行。因此需要使用能够在 GPU 上运行的代码。这样的代码需要提供成对的方法(其中一个叫顶点着色器, 另一个叫片段着色器),并且使用一种类 C/C++ 的强类型语言 GLSL(OpenGL Shading Language)。 每一对方法组合起来称为一个 program(着色程序)。
顶点着色器的作用是计算顶点的位置。根据计算出的一系列顶点位置,WebGL 可以对点、线和三角形在内的一些图元进行光栅化处理。当对这些图元进行光栅化处理时需要使用片段着色器方法。片段着色器的作用是计算出当前绘制图元中每个像素的颜色值。
用 WebGL 绘制一个三角形:
See the Pen WebGL - Fundamentals by Jc (@JChehe) on CodePen.
查看上述案例的代码实现后,我们发现绘制一个看似简单的三角形其实并不简单,它需要我们学习更多额外的知识。
因此,对于刚入门的开发者来说,直接使用 WebGL 来绘制并拼装出几何体是不现实的。但我们可以在了解 WebGL 的基础知识后,再通过 Three.js 这类封装后的库来现实我们的需求。
Three.js
打开 [Three.js 官方文档][6] 并阅览左侧的目录,发现该文档对初学者并不友好。但相对于其他资料,它提供了最新的 API 说明,尽管有些描述并不详细(甚至需要在懂 WebGL 等其他知识的前提下,才能了解某个术语的意思)。下面提供两个 Three.js 的相关图片资料,希望它们能让你对 Three.js 有个整体的认识:
![Three.js 文档的结构][7]
Three.js 文档结构:[图片来自>>][8]
![Three.js 核心对象结构和基本的渲染流程][9]
Three.js 核心对象结构和基本的渲染流程:[图片来自>>][10]
Three.js 的基本要素
我们先通过一个简单但完整的案例来了解 Three.js 的基本使用:
在线案例:
See the Pen threejs-blog-01-hello-world by Jc (@JChehe) on CodePen.
看完上述案例代码后,你可能会产生以下几个疑问:
下面我们逐一回答:
1. Three.js 的单位是什么?
答:Three.js 基于 OpenGL,那我们从 OpenGL 文档看到这么一句话: "The preceding paragraph mentions inches and millimeters - do these really have anything to do with OpenGL? The answer is, in a word, no. The projection and other transformations are inherently unitless. If you want to think of the near and far clipping planes as located at 1.0 and 20.0 meters, inches, kilometers, or leagues, it's up to you. The only rule is that you have to use a consistent unit of measurement. Then the resulting image is drawn to scale." ——[《OpenGL Programming Guide》][11] 中文:前面段落提及的英寸和毫米真的和 OpenGL 有关系吗?没有。投影和其它变换在本质上都是无单位的。如果你想把近距离和远距离的裁剪平面分别放置在 1.0 和 20.0 米/英寸/千米/里格,这取决于你。这里唯一的要求是你必须使用统一的测量单位,然后按比例绘制最终图像。
2. 坐标系的位置和指向是?
答:Three.js 的坐标系是遵循右手坐标系,如下图:
![右手坐标系][12]
右手坐标系
坐标系的原点在画布中心(
canvas.width / 2
,canvas.height / 2
)。我们可以通过 Three.js 提供的THREE.AxisHelper()
辅助方法将坐标系可视化。RGB颜色分别代表 XYZ 轴:
See the Pen threejs-blog-02-axis by Jc (@JChehe) on CodePen.
另外,补充一点:对于旋转
cube.rotation
正值是逆时针旋转,负值是顺时针旋转。3. 具有透视效果的摄像机的参数含义是?
答:
THREE.PerspectiveCamera(fov, aspect, near, far)
具有 4 个参数,具体解释如下:![PerspectiverCamera][13]
透视图中,灰色的部分是视景体,是可能被渲染的物体所在的区域。
Three.js 还提供了其他 3 种摄像机:[CubeCamera][14]、[OrthographicCamera][15]、[StereoCamera][16]。
其中 OrthographicCamera 是正交投影摄像机,他不具有透视效果,即物体的大小不受远近距离的影响。
切换正交投影摄像机和透视摄像机:
See the Pen switchCamera by Jc (@JChehe) on CodePen.
4. Mesh 的作用是?
答:Mesh 好比一个包装工,它将『可视化的材质』粘合在一个『数学世界里的几何体』上,形成一个『可添加到场景的对象』。
当然,创建的材质和几何体可以多次使用(若需要)。而且,包装工不止一种,还有
Points
(点集)、Line
(线/虚线) 等。同一个几何体的多种表现形式:
See the Pen multi-appearance by Jc (@JChehe) on CodePen.
Three.js 提供的几何体
从 Three.js 文档目录的
Geometries
可看到,Three.js 已为我们提供了很多现成的几何体,但如果对几何知识不常接触,可能就很难从它的英文名字联想到其实际的形状。下面我们将它们一次性罗列出来:Three.js 提供的 18 个几何体:
See the Pen all-the-geometry by Jc (@JChehe) on CodePen.
目前 Three.js 一共提供了 22 个 Geometry,除了
EdgesGeometry
、ExtrudeGeometry
、TextGeometry
、WireframeGeometry
,上面涵盖 18 个,它们分别是底层的planeGeometry
和以下 17 种(顺序与上述案例一一对应,下同):剩余的 TextGeometry、EdgesGeometry、WireframeGeometry、ExtrudeGeometry 我们单独拿出来解释:
See the Pen the-remaining-geomtry by Jc (@JChehe) on CodePen.
如案例所示,EdgesGeometry 和 WireframeGeometry 更多地可能作为辅助功能去查看几何体的边和线框(三角形图元)。
ExtrudeGeometry 则是按照指定参数将一个二维图形沿 z 轴拉伸出一个三维图形。
TextGeometry 则需要从外部加载特定格式的字体文件(可在 [typeface.js][17] 网站上进行转换)进行渲染,其内部依然使用 ExtrudeGeometry 对字体进行拉伸,从而形成三维字体。另外,该类字体的本质是一系列类似 SVG 的指令。所以,字体越简单(如直线越多),就越容易被正确渲染。
以上就是目前 Three.js 提供的几何体,当然,这些几何体的形状也不仅于此,通过改变参数即能生成更多种类的形状,如
THREE.CircleGeometry
可生成扇形。另外,通过
console.log
查看任意一个geometry
对象可发现,在 Three.js 中的几何体基本上是三维空间中的点集(即顶点)和这些顶点连接起来的面组成的。以立方体为例(widthSegments、heightSegments、depthSegments 均为 1 时):对于 Three.js 提供的几何体,我们不需要自己定义这些几何体的顶点和面,只需提供 API 指定的参数即可(如长方体的长宽高)。当然,你仍然可以通过定义顶点和面来创建自定义的几何体。如:
上述代码需要注意的点有:
geometry.verticesNeedUpdate = true
。更多关于需要主动设置变量来开启更新的事项,可查看官方文档的 [How to update things][18]。声音
我们从文档目录中竟然发现有
Audio
音频对象,为什么 Three.js 不是游戏引擎,却带个音频组件呢?原来这个音频也是 3D 的,它会受到摄像机的距离影响:我们可以到 [官方案例][19] 亲自体验一下
Audio
的效果。常见的插件
在 Three.js 的官方案例中,你几乎都能看到左右上角的两个常驻控件,它们分别是:JavaScript 性能监测器 [stats.js][20] 和可视化调参插件 [dat.GUI][21]。
stats.js
stats.js 为开发者提供了易用的性能监测功能,它目前支持四种模式:
![FPS][22] ![每帧渲染时间][23] ![内存占用量][24] ![用户自定义][25]
dat.GUI
[dat.GUI][26] 为开发者提供了可视化调参的面板,对参数调整的操作提供了极大的便利。 ![dat.gui][27]
关于这两个插件的使用,请查看他们的官方文档或 Three.js 官方案例中的代码。
其他一些东西
自适应屏幕(窗口)大小
阴影
阴影是增强三维场景效果的重要因素,但 Three.js 出于性能考虑,默认关闭阴影。下面我们来看看如何开启阴影的。
// 球体的阴影可以投射到平面和球体上 sphere.castShadow = true
// 更改渲染器的投影类型,默认值是 THREE.PCFShadowMap renderer.shadowMap.type = THREE.PCFSoftShadowMap
// 更改光源的阴影质量,默认值是 512 spotLight.shadow.mapSize.width = 1024 spotLight.shadow.mapSize.height = 1024
// Fog( hex, near, far ),线性雾化。 // near 表示哪里开始应用雾化效果(摄像机为 0) // far 表示哪里的雾化浓度为 1。若某物体在该距离后,则其表现为雾的颜色。当雾的颜色和渲染器的背景色相同时,则表现为消失(实为颜色相同)。 scene.fog = new THREE.Fog( 0xffffff, 0.015, 100 )
// FogExp2( hex, density ),指数雾化 // density 是雾化强度 scene.fog = new THREE.FogExp2( 0xffffff, 0.01 )
// 雾化效果默认是全局影响的,若某个材质不受雾化效果影响,则可为材质的 fog 属性设置为 false(默认值 true) var material = new THREE.Material({ fog: false })
var material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide // 其他值:THREE.FrontSide(默认值)、THREE.BackSide })
function onDocumentMouseDown(event) { var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) 2 - 1, -( event.clientY / window.innerHeight ) 2 + 1, 0.5); vector = vector.unproject(camera);
}
distance: 49.90470 face: THREE.Face3 faceIndex: 4 object: THREE.Mesh point: THREE.Vector3