three.js渲染全景的基础理论浅谈

1,560 阅读4分钟

前言

这是我的第一篇技术文章,也是在工作学习中体会到了文章博客给我带来的帮助,从而想自己开始写一些。我计划会分享一些前端技术相关的文章,也会有webgl方向,如果喜欢这片文章的话可以关注我,也欢迎在留言区与我讨论,你们的支持将是我持续更新的动力。

进入正题,网上有很多three.js全景的介绍,大多都是pia代码,对其为什么要这样实现的深度解读少之又少。就像我在学习全景的时候看到如下代码:

geometry.scale(-1, 1, 1);

这句代码是怎么让几何体的正面朝内的呢?
会不会有什么问题呢?
对自己代码中任何api的不了解最终都会引起令人迷惑的问题。
所以我决定来搞清楚。让我们开始吧!

pia代码

一样的,我们构建全景的步骤大多都是,创建一个面朝内的几何体,然后把相机放到几何体中心。

//新建一个球体
const geometry = new THREE.SphereGeometry( 500, 64, 32 );
//沿x轴进行-1的scale,让球体的面朝内(因为我们将从球内进行观看)。
geometry.scale( - 1, 1, 1 );
//载入一张全景图生成threejs中可以使用的材质
const material = new THREE.MeshBasicMaterial( {
    map: new THREE.TextureLoader().load( 'panoPic.jpg' )
} );
//将几何体和材质进行结合。
const mesh = new THREE.Mesh( geometry, material );
const scene = new THREE.Scene();
const width = 800, height = 600;
const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
scene.add( camera );
scene.add( mesh );

原理解读

想要讨论几何体的面朝哪,必须先知道webgl中对正面的定义是什么:

经过一系列的变换矩阵处理之后,标准空间中,一个面(三角形)三个顶点逆时针顺序为正面,顺时针为反面

可以让webgl,不绘制反面,默认是会进行绘制的:

gl.enable(gl.CULL_FACE);
...
gl.disable(gl.CULL_FACE);

three.js可以通过如下代码来设置双面渲染,默认不绘制反面:

const material = new THREE.MeshPhongMaterial({
  side: THREE.DoubleSide,
});

ok,搞清楚什么是正面之后,就可以看看是怎么翻面的了。
众所周知,scale方法可以缩放几何体。

	scale: function ( x, y, z ) {

		// scale geometry

		_m1.makeScale( x, y, z );

		this.applyMatrix4( _m1 );

		return this;

	},

如上Geometry.prototype.scale代码所示,就是乘上一个变换矩阵。矩阵乘法不了解的可以看引用。

www.mathwarehouse.com/algebra/mat…

经过变换之后,源顶点的x,会变成-x,yz不变。随后更新面的法线向量。向量的变换不可以直接使用scale的变换矩阵,需要用这个矩阵的逆矩阵的转至矩阵,因为变换矩阵,有一些非正交的情况,缩放和平移是非正交的,旋转是正交的,而这些非正交的案例会让法向量计算错误。平移变换直接作用与向量,会让向量改变方向。缩放如果三个坐标轴方向的缩放不一致,也会让法向量不再垂直变换后的面,那么为什么要用变换矩阵的逆转至矩阵呢?证明如下:

有顶点1指向顶点2的向量s,原法向量n,变换后有顶点1指向顶点2的向量s',变换后法向量n',变换矩阵M,法向量变换矩阵M', 则有

ns=0n \cdot s = 0

n=M×nn' = M' \times n

s=M×ss' = M \times s

ns=0n' \cdot s' = 0

(M×n)(M×s)=0(M' \times n) \cdot (M \times s) = 0

AB=AT×B\because A \cdot B = A^T \times B

(M×n)T×(M×s)=0\therefore (M' \times n)^T \times (M \times s) = 0

(A×B)T=BT×AT\because (A \times B)^T = B^T \times A^T

nT×MT×M×s=0\therefore n^T \times M'^T \times M \times s = 0

AB=AT×Bns=0\because A \cdot B = A^T \times B \quad n \cdot s = 0

nT×s=0\therefore n^T \times s = 0

MT×M=I\therefore M'^T \times M = I

MT=M1\therefore M'^T = M^{-1}

M=(M1)T\therefore M' = (M^{-1})^T

ok, 上述就是法向量变换矩阵的证明过程。
下面我们来看下程序运行的效果:

可见,这种算法可以处理各个轴上缩放倍数不一致的情况是可行的, 负值也是可以正确垂直与平面,但是方向却不是正面(转换后)向外,是反面向外。所以在不使用法向量的情况如上操作没有问题,如果需要面朝里,也需要法向量来计算光照效果怎么办呢(全景业务里不需要法向量)?我们可以重新生成法向量!

	computeFaceNormals: function () {

		const cb = new Vector3(), ab = new Vector3();

		for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {

			const face = this.faces[ f ];

			const vA = this.vertices[ face.a ];
			const vB = this.vertices[ face.b ];
			const vC = this.vertices[ face.c ];

			cb.subVectors( vC, vB );
			ab.subVectors( vA, vB );
			cb.cross( ab );

			cb.normalize();

			face.normal.copy( cb );

		}

	},

如上Geometry.prototype.computeFaceNormals代码所示,用的是中间顶点到两边顶点的向量的向量积(cross product)来求的法线向量。对向量的向量积不了解的可以看引用。

www.mathsisfun.com/algebra/vec…

三个顶点的顺序从逆时针变成顺时针之后,上述的向量积也会变成原来法线的反方向(右手定则,引用内有说明)。

所以现在我们知道了用scale来让几何体的面朝内,以及带来的问题,以及如何解决。

总结

本文简单讲解了下全景业务场景,让几何体正面朝内的原理,由于我个人也在持续学习webgl/three.js,所以文章如有错误请在评论区指出,也欢迎与我讨论。