一、前言
ThreeJs 封装了 WebGL 进行渲染时所涉及到的相关概念,如光照,材质,纹理以及相机等。除此之外,其还抽象了场景(Scene)以及用于渲染的渲染器(WebGLRenderer)。这些相关概念都被封装成了一个对象,那么它们是如何协作的呢,关系又是如何呢?这篇文章主要就是来分析一下 ThreeJs 中核心中的核心,即场景,物体,光照,材质,纹理以及相机这些对象是如何渲染的。
下面截取了一个渲染效果图,看起来还不错是不是。这是 ThreeJs 的官方 demo lights / spotlights 的渲染效果图。这个 demo 中就基本涉及到了上面所提的核心对象,下面我将基于此 demo 来分析这些核心对象是如何被组织在一起进行渲染的。
二、demo 解析
Demo 有一点点长,对于不熟悉 ThreeJs 的人来说会有一点点难度,因此这里主要分析了构建、初始化以及渲染 3 个部分来分别说明。
1.构建
// 构建渲染器 WebGLRenderer
var renderer = new THREE.WebGLRenderer();
// 设置显示比例
renderer.setPixelRatio( window.devicePixelRatio );
// 构建一个透视投影的相机
var camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 2000 );
// 构建一个轨道控制器,主要就是通过鼠标来控制相机沿目标物体旋转,从而达到像在旋转场景一样,可以从各个不同角度观察物体
var controls = new THREE.OrbitControls( camera, renderer.domElement );
// 构建场景
var scene = new THREE.Scene();
// 构建Phong网格材质MeshPhongMaterial,该材质可以模拟具有镜面高光的光泽表面,一个用于接收阴影的平面,一个用于场景中的物体 Box
var matFloor = new THREE.MeshPhongMaterial();
var matBox = new THREE.MeshPhongMaterial( { color: 0xaaaaaa } );
// 构建几何体,同样分别用于 平面 和 Box
var geoFloor = new THREE.PlaneBufferGeometry( 2000, 2000 );
var geoBox = new THREE.BoxBufferGeometry( 3, 1, 2 );
// 构建平面网格 mesh
var mshFloor = new THREE.Mesh( geoFloor, matFloor );
mshFloor.rotation.x = - Math.PI * 0.5;
// 构建 box 网格 mesh
var mshBox = new THREE.Mesh( geoBox, matBox );
// 构建环境光
var ambient = new THREE.AmbientLight( 0x111111 );
// 构建 3 个不同颜色的 聚光灯(SpotLight)
var spotLight1 = createSpotlight( 0xFF7F00 );
var spotLight2 = createSpotlight( 0x00FF7F );
var spotLight3 = createSpotlight( 0x7F00FF );
// 声明用于描述聚光灯的 3 个不同光束帮助器
var lightHelper1, lightHelper2, lightHelper3;
复制代码
上面代码中,基本上每一行都添加了详细的注释,其中有调用了一个内部的函数 createSpotlight() ,如下。
function createSpotlight( color ) {
var newObj = new THREE.SpotLight( color, 2 );
newObj.castShadow = true;
newObj.angle = 0.3;
newObj.penumbra = 0.2;
newObj.decay = 2;
newObj.distance = 50;
newObj.shadow.mapSize.width = 1024;
newObj.shadow.mapSize.height = 1024;
return newObj;
}
复制代码
这个方法,主要就是根据指定的颜色构建一个聚光灯并设置好相应的参数。这里不管是相机、光照、材质还是物体,其详细的参数并不打算在这里一一讲述,有需要的话再进一步说明。
2.初始化
function init() {
......
// 将平面,box,环境光以及光源辅助器等全部添加到 scene 中
scene.add( mshFloor );
scene.add( mshBox );
scene.add( ambient );
scene.add( spotLight1, spotLight2, spotLight3 );
scene.add( lightHelper1, lightHelper2, lightHelper3 );
document.body.appendChild( renderer.domElement );
onResize();
window.addEventListener( 'resize', onResize, false );
controls.target.set( 0, 7, 0 );
controls.maxPolarAngle = Math.PI / 2;
controls.update();
}
复制代码
初始化主要就是将平面,box ,光照这些都添加进场景中,但是要注意,相机并没有被添加进来。
3.渲染
function render() {
TWEEN.update();
if ( lightHelper1 ) lightHelper1.update();
if ( lightHelper2 ) lightHelper2.update();
if ( lightHelper3 ) lightHelper3.update();
renderer.render( scene, camera );
requestAnimationFrame( render );
}
复制代码
渲染函数 render() 中最关键的调用渲染器的 WebGLRenderer#render() 方法同时去渲染场景和相机。
根据上面的分析,以及对 ThreeJs 源码的分析,梳理出如下 2 个类图关系。
图中,渲染器负责同时渲染场景以及相机。而光照和网格都被添加到场景中。几何体以及材质都是网格的 2 个基本属性,也决定一个网格的形状和表面纹理。
该图是对上图的补充,说明光照,相机以及网格都属于 Object3D 对象。在 ThreeJs 中还有许多的类都是继承自 Object3D 的。
三、渲染分析
1.关于 WebGL 需要知道的基本知识
1.1 WebGL 的渲染管线
先来看一下 WebGL 的流水线渲染管线图,如下所示。这个是必须要了解的,我们可以不必完全理解渲染管线的每个步骤,但我们必须要知道渲染管线的这个流程。
渲染管线指的是WebGL程序的执行过程,如上图所示,主要分为 4 个步骤:
-
顶点着色器的处理,主要是一组矩阵变换操作,用来把3D模型(顶点和原型)投影到viewport上,输出是一个个的多边形,比如三角形。
-
光栅化,也就是把三角形连接区域按一定的粒度逐行转化成片元(fragement),类似于2D空间中,可以把这些片元看做是3D空间的一个像素点。
-
片元着色器的处理,为每个片元添加颜色或者纹理。只要给出纹理或者颜色,以及纹理坐标(uv),管线就会根据纹理坐标进行插值运算,将纹理或者图片着色在相应的片元上。
-
把3D空间的片元合并输出为2D像素数组并显示在屏幕上。