0. 向量复习
因为本节涉及基本的向量芝士,如果大家对向量不是很熟悉,请见:向量复习(一)。已经很熟悉的童鞋,可以跳过这部分内容。
1.背景介绍
生成一个单位向量非常的简单,但是如果我们要求这个单位向量是在单位球或单位圆锥里面呢(单位圆内的均匀分布的单位向量,请见randomDirection)。而且随机生成均匀分布的单位向量,应该怎们做呢?这点非常重要哦,因为我们在粒子系统中,需要这个技术来模拟流星效果,或者爆炸效果,也就是说粒子的方向向量是在单位圆锥里面的均匀分布的随机单位向量,比如下面的流星效果:Blibli演示视频链接。
粒子系统(Particle System)演示
2. 生成圆锥内的均匀分布的单位向量
计算思路先要用到角度坐标系统,如果对它不是很熟悉的童鞋,可以看看下面角度坐标系统和笛卡尔坐标系统的转化关系12:
也就是说,我们先用角度坐标计算在圆锥里面的单位方向向量,然后用上面的公式转化到一般坐标系里面。所以接下来就是计算在角度坐标系中,球上的坐标3:
没有太多数学基础的童鞋不用着急额,这里的公式和概念先混个眼熟,后面我们直接用到计算,不太明白也没有关系哦。接下来,我们现在有一个圆锥(Cone),圆心(Origin)为P,其方向向量(Direction)为D,边角(Side angle)为的ψ。我们以P = [ 0, 0, 0 ],D = [ 0, 0, 1 ], ψ = Π / 4为例,就能得到下图中的圆锥3:
现在我们只考虑三维的情况,且P = [ 0, 0 ,0 ](如果P不在原点,我们只需要把整个圆锥平移到P就行,即:[ 0, 0, 0 ] + P),我们有如下的Cone( D, ψ ),计算在圆锥内的均匀分布的方向向量的公式为3:
其中,θ为[ 0, 2Π ]中均匀分布取值,同样z为[ cosΦ, 1 ]中均匀分布取值,而Φ就是我们给定圆锥的Side Angle(ψ),这样就可以计算这个圆锥里面的随机向量。注意,教材上面计算Φ可通过圆锥方向向量D中Z值来求得3:
但笔者实在无法理解这样的做法,这里给大家提一下,有兴趣的童鞋可以研究一下,如果有结果麻烦告知一下啦~
到这里,我们得到了圆锥里面的向量,但是注意这里的向量都是以Z轴(圆锥的本地坐标)为中心轴的向量(上图圆锥中的向量),所以我们想要根据圆锥的方向向量D进行旋转对齐
接下来,我们将进入到代码解析当中,看看如何用Three.js了进行实现。
3. 代码解析
在理解了计算原理之后,那就该来实现它啦哒,这里我们用Javascript来进行代码讲解。之前我们计算圆锥内的向量是需要用到三个变量:P(圆点),D(方向)和ψ(边角角度),但是我们现在需要生成在单位圆锥内均匀分布的随机方向向量,那么P永远就是原点(因为向量可以随意平移),我们只需要指定其他两个变量就能进行计算圆锥内的单位方向向量:
/**
* get random Unit Vector3 bounded by the Cone.
* This is defined by the direction of the cone and its side angle, a.k.a., emission angle
*
* Reference resource:
* Generating uniform unit random vectors in Rn, UMONS, Belgium, Andersen Ang
*
* @param {Vector3} center center of the cone
* @param {Vector3} direction direction of the cone
* @param {Number} sideAngle in degrees
*/
static randomUnitVector3InCone( center, direction, sideAngle ) {
let theta = Math.random() * 360;
// side angle of the cone is sideAngle degrees
let cosPhi = Math.cos( MyMath.radians( sideAngle ) );
let z = MyMath.randomInRange( cosPhi, 1 );
console.assert( MyMath.doubleCompare( 1 - z * z, 0 ) >= 0, 1 - z * z );
let sinPhi = Math.sqrt( 1 - z * z );
let x = sinPhi * Math.cos( MyMath.radians( theta ) );
let y = sinPhi * Math.sin( MyMath.radians( theta ) );
// hit it with a rotation matrix to get it aligned with the direction of the cone
let rotation = new THREE.Matrix4().lookAt( center, direction.clone(), Dynamics.zAxis.clone() );
let directionV = new THREE.Vector3( x, y, z ).clone().transformDirection( rotation );
// console.log( MyMath.degrees( direction.angleTo( directionV ) ), direction.length() );
return directionV;
}
代码基本就是上面计算思路的直接实现,因为我们需要用到cosΦ,所以直接计算cosΦ,不用计算Φ。
MyMath.randomInRange( cosPhi, 1 );
这一步我们就计算在这个给定Cone里面的均匀分布的随机方向向量,边角角度范围为[ 0, Φ ],Φ即为圆锥的Side Angle。
接下来,我们就一步步套公式进行计算,最后得到一个随机单位向量,这个向量在以Z轴为方向轴,SideAngle为Φ的单位圆锥内:
let sinPhi = Math.sqrt( 1 - z * z );
let x = sinPhi * Math.cos( MyMath.radians( theta ) );
let y = sinPhi * Math.sin( MyMath.radians( theta ) );
之后,我们需要用原来的圆锥方向轴对生成的方向向量进行对齐,这里我们用到Three.js提供的两个方法:lookAt和transformDirection
.lookAt ( eye : Vector3, center : Vector3, up : Vector3, ) : this
Constructs a rotation matrix, looking from eye towards center oriented by the up vector.
创建一个旋转矩阵,这个矩阵从eye的位置看向center,并且由up控制
这里的eye是圆锥的圆心P,center是圆锥的方向向量D,up是Z轴(注意在WebGL中Z轴正对屏幕,并非垂直向上)
.transformDirection ( m : Matrix4 ) : this
Transforms the direction of this vector by a matrix (the upper left 3 x 3 subset of a m) and then normalizes the result.
用一个矩阵(用到的部分是左上的3x3的矩阵进行旋转)旋转当前向量,并且对其进行单位化(模为1)
写出来的代码为:
// hit it with a rotation matrix to get it aligned with the direction of the cone
let rotation = new THREE.Matrix4().lookAt( center, direction.clone(), Dynamics.zAxis.clone() );
let directionV = new THREE.Vector3( x, y, z ).clone().transformDirection( rotation );
4. 附录:代码
5.参考资料
- Generating uniform unit random vectors in R_n, Andersen Ang
- Vector3 in three.js
- Spherical Coordinates
- Polar coordinate system
6. 免责声明
※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;