ELSE
首发于ELSE

浏览器上的3D—WebGL和Three.js

WebGL是什么

WebGL(全称Web Graphics Library)是一种3D绘图协议,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0(OpenGL是最常用的跨平台图形库)结合在一起,通过增加OpenGL ES 2.0的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染,这样Web开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型了,还能创建复杂的导航和数据视觉化。显然,WebGL技术标准免去了开发网页专用渲染插件的麻烦,可被用于创建具有复杂3D结构的网站页面,甚至可以用来设计3D网页游戏等等。

WebGL的工作原理

  1. WebGL API

WebGL基本绘图元素只有点、线、三角形,如下图中的这种复杂图形,它也是由三角形构成的:


2. WebGL绘制流程

2.1 获取顶点坐标

简单图形的顶点坐标可以通过自己写或者库(如three.js)提供的api来获得,

但是复杂图形的顶点坐标数目庞大,一般是通过三维软件导出的(如3dmax等软件导出的obj文件

由于顶点数据往往成千上万,在获取到顶点坐标后,我们通常会将它存储在显存,即缓存区内,方便GPU更快读取。

2.2 图元装配

图元装配就是由顶点生成一个个图元(即三角形)。

顶点着色器--它由opengl es编写,由javascript以字符串的形式定义并传递给GPU生成。

如下是一段顶点着色器代码:

attribute vec4 position;
void main() {
  gl_Position = position; 
}

attribute修饰符用于声明由浏览器(javascript)传输给顶点着色器的变量值;

position即我们定义的顶点坐标;

gl_Position是一个内建的传出变量。

这段代码什么也没做,如果是绘制2d图形,没有什么问题,但如果是绘制3d图形,即传入的顶点坐标是一个三维坐标,我们则需要将三维坐标转换成屏幕坐标。

比如:v(-0.5, 0.0, 1.0)转换为p(0.2, -0.4),这个过程类似我们用相机拍照。

2.2.1 定点着色器处理流程


如上图,顶点着色器会先将坐标转换完毕,然后由GPU进行图元装配,有多少顶点,这段顶点着色器程序就运行了多少次。

这时候顶点着色器变为:

attribute vec4 position;
uniform mat4 matrix;
void main() {
  gl_Position = position * matrix; 
}

这就是应用了矩阵matrix,将三维世界坐标转换成屏幕坐标,这个矩阵叫投影矩阵,由javascript传入。

2.3 光栅化

在图元生成完毕之后,我们需要给模型“上色”,而完成这部分工作的,则是运行在GPU中的“片元着色器”来完成。

它同样是一段opengl es程序,模型看起来是什么质地(颜色、漫反射贴图等)、灯光等由片元着色器来计算。

如下是一段简单的片元着色器代码:

precision mediump float; 
void main(void) {
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}

gl_FragColor即输出的颜色值。

2.3.1 片元着色器处理流程

片元着色器生成多少片元(像素),运行多少次。


3. WebGL的完整工作流程

1、准备数据阶段

在这个阶段,我们需要提供顶点坐标、索引(控制三角形绘制顺序)、uv(决定贴图坐标)、法线(决定光照效果),以及各种矩阵(比如投影矩阵等)。

其中顶点数据存储在缓存区(因为数量巨大),以修饰符attribute传递给顶点着色器;

矩阵则以修饰符uniform传递给顶点着色器。

2、生成顶点着色器

根据我们需要,由Javascript定义一段顶点着色器(opengl es)程序的字符串,生成并且编译成一段着色器程序传递给GPU。

3、图元装配

GPU根据顶点数量,逐个执行顶点着色器程序,生成顶点最终的坐标,完成坐标转换。

4、生成片元着色器

模型的颜色,质地,光照效果,阴影等,都在这个阶段处理。

5、光栅化

通过片元着色器,我们确定好了每个片元的颜色,以及根据深度缓存区判断哪些片元被挡住了,不需要渲染,最终将片元信息存储到颜色缓存区,最终完成整个渲染。

three.js是什么

three.js是以WebGL为基础的库,封装了一些3D渲染需求中重要的工具方法与渲染循环。WebGL门槛相对较高,Three.js对WebGL提供的接口进行了非常好的封装,简化了很多细节,大大降低了学习成本。

  • three.js在WebGL基础上都为我们做了什么?

上图中黄色和绿色部分,都是three.js参与的部分,其中黄色是javascript部分,绿色是opengl es部分。
我们发现,能做的,three.js基本上都帮我们做了。

  • 辅助我们导出了模型数据;
  • 自动生成了各种矩阵;
  • 生成了顶点着色器;
  • 辅助我们生成材质,配置灯光;
  • 根据我们设置的材质生成了片元着色器。

而且将webGL基于光栅化的2D API,封装成了我们人类能看懂的 3D API。

  • three.js完整运行流程:

以下是three.js的一个简单例子

var camera, scene, renderer;
var geometry, material, esh;
init();
animate();

function init() {
    //THREE是一个three.js对象,可以狭义的理解为three.js库的抽象

    // 创建一个场景
    scene = new THREE.Scene();
    // 创建一个具有透视效果的摄像机
    camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
    camera.position.z = 1;
    //创建一个几何体
	geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
    //创建一个材质
	material = new THREE.MeshNormalMaterial();
    // 创建一个网格:将材质包裹在几何体上
	mesh = new THREE.Mesh( geometry, material );
    //将mesh添加到场景中
	scene.add( mesh );
    // 创建一个 WebGL 渲染器,Three.js 还提供 <canvas>, <svg>, CSS3D 渲染器
	renderer = new THREE.WebGLRenderer( { antialias: true } );
    // 设置渲染器的尺寸
	renderer.setSize( window.innerWidth, window.innerHeight );
    // 将渲染器的输出(此处是 canvas 元素)插入到 body 中
	document.body.appendChild( renderer.domElement );
    // 渲染,即摄像机拍下此刻的场景
    // renderer.render(scene, camera)
}

function animate() {
    /**window.requestAnimationFrame() 方法告诉浏览器您希望执行动画并请求浏览器在
    下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函   
    数会在浏览器重绘之前调用。**/
    requestAnimationFrame( animate );
    //定义每次重绘时物体变化的旋转角度    
    mesh.rotation.x += 0.01;
    mesh.rotation.y += 0.02;

    // 渲染,即摄像机拍下此刻的场景
    renderer.render( scene, camera );

}

推荐资料:

three.js - Javascript 3D library(官方网站)

three.js / documentation(官方文档)

Three.js 中文文档(内容可能不全或过时,仅可作为参考,请以官方英文文档为准)

three.js / examples(官方示例)

github.com/mrdoob/three (three.js项目地址)

专栏:THREE.JS源码注释 - CSDN博客(十分详细的源码解读)

【官方双语/合集】线性代数的本质(基础知识,关于矩阵)

参考文章:

图解WebGL&Three.js工作原理 - cnwander - 博客园

Three.js 现学现卖

发布于 2018-07-31

文章被以下专栏收录

    坐标上海,专注前端,基于Node全栈开发,欢迎加入我们!【https://else.vip】