封面图
在上一节中,我们用threejs成功创建了一个蓝色的立方体。但是如果我们仔细观察一下,会发现几个非常有意思的现象,比如:
- 我们在创建立方体的时候并没有对它做旋转操作,但是它看起来却是像被旋转过一样。
- 这个立方体看起来不够立体,更像是一个六边形。
- 如果我们将网页放大一些,会看到这个立方体的边缘并不是直线,而是有很多的锯齿形状。
为什么会出现这种现象呢?下面我们来一个一个的弄明白其中的缘由。
似乎被旋转过
这个问题非常简单,让我们在之前的代码中稍作修改,将相机的位置稍微调整一下。
// 设置相机位置
camera.position.set(0, 0, 10)
// 设置镜头方向
camera.lookAt(0, 0, 0)
此时观察页面,发现我们只能看到一个正方形,难道立方体消失了吗?其实并不是,理解这个问题需要我们用到三维空间的坐标系以及三视图的概念。
3D笛卡尔坐标
3D笛卡尔坐标系由X,Y和Z轴组成,三轴交叉于点(0,0,0)(称为原点)。二维坐标系相似,但只有X和Y轴。这些概念在中学的数学中我们都学过,这里简单熟悉一下就好。
所有的3D图形系统都使用这样的坐标系,甚至我们在进行web开发使用的css也是使用的二维笛卡尔坐标系。
在高中时代,我们见过的空间直角坐标系如下:
它和上面彩色的坐标系这里暂时可以简单认为是同一个东西,因为我们把它沿x轴旋转90度,然后再沿y轴旋转90度,即可得到上面的坐标系。
三视图
在工业零件的加工过程中,我们通常会用到三视图。
三视图指的是:主视图、俯视图和左视图。左视图通常又称为侧视图。
通过三视图的测量数据,工人就可以用车床车削出正确的零件。
比如:
与之类似,当我们将相机的位置改为:
// 设置相机位置
camera.position.set(0, 0, 10)
// 设置镜头方向
camera.lookAt(0, 0, 0)
其实是在坐标系的(0,0,10)这个位置对立方体进行观察,相当于一个主视图、或者侧视图。我们只看到立方体的一个面,所以立方体看起来就只是一个正方形。
当我们将相机的位置改为:
// 设置相机位置
camera.position.set(0, 10, 10)
// 设置镜头方向
camera.lookAt(0, 0, 0)
我们就可以看到立方体的两个面。
就像是一个六边形。
当我们将相机的位置改为:
// 设置相机位置
camera.position.set(10, 10, 10)
// 设置镜头方向
camera.lookAt(0, 0, 0)
相当于我们可以看到立方体的一个角,此时看起来就像是一个正六边形。
但此时这个立方体看起来还是个六边形,还是不太像一个立方体,这是为什么呢?是不是和我们用的材质有关系?
我们一起来验证一下。
材质和灯光
我们知道在现实生活中我们看到的物体都是各种各样材料,玻璃、木头、塑料、钢铁等等。由于他们能够将光线反射到我们的眼睛中,所以我们能够看到它们。
所以我们能看到物体的一个条件是:这个物体首先得对光有反应,起码能反射光线。而体现在threejs 中则是这种材质需要能够对光照有反应。既然需要光,那么我们先在场景中添加一个光源。
// 创建灯光
const light = new THREE.DirectionalLight('white', 8)
scene.add(light)
DirectionalLight 是平行光,通常用来模仿太阳的光线,它的光线不会随着距离而消失。此时我们查看页面,似乎没什么变化。
这是因为我们虽然在场景中添加了灯光,但是我们创建立方体时,采用材质是(MeshBasicMaterial)基础材质,这种材质不受光照的影响,换句话说,这种材质会忽略场景中任何灯光。
因为 MeshBasicMaterial是 three.js 中提供的最基本的材料。它不会对灯光做出反应,并且网格的整个表面都用单一颜色着色。不执行基于视角或距离的着色,因此对象看起来甚至不是三维的。我们所能看到的只是一个二维轮廓。
我们将材质换成(MeshStandardMaterial) 标准材质,
const mater = new THREE.MeshStandardMaterial({
color: new THREE.Color('blue')
// roughness: 0.3
})
(MeshStandardMaterial) 标准材质,是一种高质量、通用、物理精确的材料,可以使用真实世界的物理方程对光做出反应。此时我们观察页面,发现我们已经可以看到有黑色的阴影出现了,有了一定的立体效果。
就像给姑娘拍照一样,好看的照片需要优雅的姿势,和适合的灯光来配合。诚然,这张照片并不是很好看,我们还需要让这位姑娘换个姿势,另外再调整一下灯光。
我们先将灯光的默认位置打印出来看下:
// light.position _Vector3 {x: 0, y: 1, z: 0}
原来它在坐标轴(0,1,0)这个位置,我们调整一下,设置为(40, 30, 60)
// 创建灯光
const light = new THREE.DirectionalLight('white', 8)
light.position.set(40, 30, 60)
console.log('light.position', light.position)
scene.add(light)
然后我们需要给姑娘换个好点的姿势,好么,旋转一下它:
cube.rotation.set(-0.3, 0.7, -0.1)
此时我们观察页面,这个立方体就很有立体感了。
辅助对象
在旋转立方体的过程中,我们发现旋转的程度不是特别容易控制。这个也容易解决,我们可以添加一个辅助对象 AxesHelper 轴辅助对象,它可以简单模拟3个坐标轴的对象。 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴。
我们将它添加到场景中:
const axesHelper = new THREE.AxesHelper( 5 );
scene.add( axesHelper );
参数 5 表示轴线段的长度,此时我们就很容易去调整在哪根轴上旋转多少度比较合适,以达到合适的效果。
抗锯齿
解决了不像立方体的问题,我们还有一个问题要处理,就是处理立方体边缘的锯齿,处理方法很简单,我们只需要启用渲染器的抗锯齿参数 antialias 即可,我们将渲染器的antialias 参数 设置为true。
// 创建渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true // 启用抗锯齿
})
观察页面发现锯齿消失了。
抗锯齿是一项非常复杂的技术,暂时我们只需要知道启用antialias这个属性即可。
小结
我们发现了上节创建的立方体的三个小问题,并通过设置灯光、调整灯光位置、调整立方体的姿态,以及启用渲染器的抗锯齿属性消除了立方体边缘的锯齿。
接下来我们会继续关注其中的一些容易被忽略的问题,比如:
- 我们创建几何体时所设置的(2,3,2)它们的单位是什么,米、厘米、分米?
- 我们是不是可以给立方体加个纹理,让它看起来像一块石头、木头或者铁块儿?
在下一节中,我们将对这些问题做些比较详细的介绍。