-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
50 changed files
with
1,747 additions
and
1,185 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
--- | ||
title: 相交相关的计算 | ||
toc: content | ||
group: WebGL | ||
--- | ||
|
||
# 相交相关的计算 | ||
|
||
## 判断点是否在三角形内部 | ||
|
||
### 通过面积判断 | ||
|
||
- 如果点和三角形的三个顶点构成的三个三角形的面积与大三角形的面积相等, 则点在三角形内部 | ||
- 这种方法可以不用事先判断点是否与三角形共面 | ||
|
||
```ts | ||
export const pointInTriangleV3_1 = (triangle: Vector3[], P: Vector3) => { | ||
const [A, B, C] = triangle | ||
|
||
const ab = B.clone().sub(A) | ||
const bc = C.clone().sub(B) | ||
const area2 = ab.clone().cross(bc).length() // 向量叉乘的模为构成三角形面积的两倍 | ||
let area2_1 = 0 | ||
|
||
for (let i = 0; i < 3; i++) { | ||
const P1 = triangle[i] | ||
const P2 = triangle[(i + 1) % 3] | ||
area2_1 += P1.clone().sub(P).cross(P2.clone().sub(P)).length() | ||
} | ||
|
||
return Math.abs(area2_1 - area2) <= 1e-5 | ||
} | ||
``` | ||
|
||
### 通过叉积判断 (二维) | ||
|
||
- 判断叉积值的正负 | ||
|
||
```ts | ||
export const cross = (v1: Num2, v2: Num2) => { | ||
return v1[0] * v2[1] - v1[1] * v2[0] | ||
} | ||
export const pointInTriangleV2 = (triangle: Num2[], P: Num2) => { | ||
for (let i = 0; i < 3; i++) { | ||
const P1 = triangle[i] | ||
const P2 = triangle[(i + 1) % 3] | ||
const v1: Num2 = [P1[0] - P[0], P1[1] - P[1]] | ||
const v2: Num2 = [P2[0] - P[0], P2[1] - P[1]] | ||
// 顶点逆时针的情况下用 < 0, 顺时针用 > 0 | ||
if (cross(v1, v2) < 0) return false | ||
} | ||
return true | ||
} | ||
``` | ||
|
||
### 通过叉积判断 (三维) | ||
|
||
- 判断叉积是否与三角形法向量同方向 (通过叉积与法向量的点积判断) | ||
- 这种方法需要事先判断点是否与三角形在同一平面内 | ||
|
||
```ts | ||
export const pointInTriangleV3_2 = (triangle: Vector3[], P: Vector3) => { | ||
const [A, B, C] = triangle | ||
|
||
const ab = B.clone().sub(A) | ||
const bc = C.clone().sub(B) | ||
const n = ab.clone().cross(bc) | ||
|
||
for (let i = 0; i < 3; i++) { | ||
const P1 = triangle[i] | ||
const P2 = triangle[(i + 1) % 3] | ||
// 判断叉积是否大于等于 0 | ||
// 三维向量叉积为法向量,需要判断 _n 和 n 是否同向, 同向则大于等于 0 | ||
const _n = P1.clone().sub(P).cross(P2.clone().sub(P)) | ||
const dot = _n.clone().dot(n) | ||
if (dot < 0) return false | ||
} | ||
|
||
return true | ||
} | ||
``` | ||
|
||
## 射线与平面的交点 | ||
|
||
- P = P1 + t\*v 射线表示为起点 + t倍的方向向量 | ||
- n·(P - P2) = 0 平面内的向量与平面法向量垂直(点乘为 0) | ||
|
||
```ts | ||
/** | ||
* | ||
* @param P1 射线起点 | ||
* @param v 射线方向 | ||
* @param P2 平面内一点 | ||
* @param n 平面法向量 | ||
*/ | ||
export const rayIntersectPlane = (P1: Vector3, v: Vector3, P2: Vector3, n: Vector3) => { | ||
// 1: P = P1 + t*v | ||
// 2: n·(P - P2) = 0 | ||
// => n·(P1 + t*v - P2) = 0 | ||
// => t = (n·P2 - n·P1)/(n·v) | ||
// 若 t >= 0, 则射线与平面相交,且交点为 P(P1 + t*v), 若t < 0, 则不相交 | ||
// 若 t = Infinity, 则射线与平面平行 | ||
|
||
const t = (n.clone().dot(P2) - n.clone().dot(P1)) / n.clone().dot(v) | ||
if (t < 0 || !Number.isFinite(t)) return null | ||
const P = P1.clone().add(v.clone().multiplyScalar(t)) | ||
return P | ||
} | ||
``` | ||
|
||
## 射线与球的交点 | ||
|
||
![射线与球的交点](./img/intersectSphere.jpg) | ||
|
||
```ts | ||
/** | ||
* 射线和球的交点 | ||
* @param P 射线起点 | ||
* @param v 射线方向 | ||
* @param O 圆心 | ||
* @param r 半径 | ||
*/ | ||
export const rayIntersectSphere = (P: Vector3, v: Vector3, O: Vector3, r: number) => { | ||
// cos(∠OPE) = len(PE)/len(PO) 求出 PE 长度 | ||
// sin(∠OPE) = len(OE)/len(PO) 求出 OE 长度 | ||
// 勾股定理 求出 P1E 长度 | ||
// t = len(PE)-len(P1E) | len(PE)+len(P1E) | ||
|
||
const po = O.clone().sub(P) | ||
const po_l = po.length() | ||
const po_n = po.clone().normalize() | ||
|
||
const sin = v.clone().cross(po_n).length() | ||
const oe_l = po_l * sin | ||
if (oe_l > r) return null | ||
|
||
const cos = po_n.clone().dot(v) | ||
if (cos <= 0) return null // 只可能在射线反方向相交 | ||
const pe_l = po_l * cos | ||
|
||
const p1e_l = Math.sqrt(r ** 2 - oe_l ** 2) | ||
|
||
if (p1e_l <= 1e-5) { | ||
return [P.clone().add(v.clone().multiplyScalar(pe_l))] | ||
} | ||
|
||
return [P.clone().add(v.clone().multiplyScalar(pe_l - p1e_l)), P.clone().add(v.clone().multiplyScalar(pe_l + p1e_l))] | ||
} | ||
``` | ||
|
||
## 应用 | ||
|
||
用于射线拾取: 先检测射线是否与几何体的包围球相交, 如果相交则遍历几何体的三角面判断是否与几何体相交或者求出相交点 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
--- | ||
title: 视图矩阵和投影矩阵的推导 | ||
toc: content | ||
group: WebGL | ||
--- | ||
|
||
# 视图矩阵和投影矩阵的推导 | ||
|
||
## 视图矩阵 (viewMatrix) | ||
|
||
- 视图矩阵就是 lookAt 的逆矩阵 | ||
- lookAt: 先旋转, 后位移 | ||
|
||
```ts | ||
const lookAt = (eye: Vector3, target: Vector3, up: Vector3) => { | ||
const zAxis = new Vector3().subVectors(eye, target).normalize() | ||
const xAxis = new Vector3().crossVectors(up, zAxis).normalize() | ||
//基向量 yAxis,修正上方向 | ||
const yAxis = new Vector3().crossVectors(zAxis, xAxis).normalize() | ||
// const mat4=new Matrix4().set( | ||
// xAxis.x,yAxis.x,zAxis.x,eye.x, | ||
// xAxis.y,yAxis.y,zAxis.y,eye.y, | ||
// xAxis.z,yAxis.z,zAxis.z,eye.z, | ||
// 0,0,0,1 | ||
// ) | ||
// prettier-ignore | ||
const moveMat = new Matrix4().set( | ||
1, 0, 0, eye.x, | ||
0, 1, 0, eye.y, | ||
0, 0, 1, eye.z, | ||
0, 0, 0, 1 | ||
) | ||
// prettier-ignore | ||
const rotateMat = new Matrix4().set( | ||
xAxis.x,yAxis.x,zAxis.x,0, | ||
xAxis.y,yAxis.y,zAxis.y,0, | ||
xAxis.z,yAxis.z,zAxis.z,0, | ||
0,0,0,1 | ||
) | ||
|
||
return new Matrix4().multiplyMatrices(moveMat, rotateMat) | ||
} | ||
``` | ||
|
||
## 正交投影矩阵 (projectionMatrix - OrthographicMatrix) | ||
|
||
- 先将绘制区域移动到坐标原点, 然后将绘制区域缩放到投影空间 | ||
- 经过视图矩阵变换, 相机位于坐标原点, 看向 z 轴负方向, n 和 f 实际上应该是负值 | ||
|
||
``` | ||
// 位移矩阵 * 缩放矩阵 | ||
[ | ||
2/(r-l),0,0,0 | ||
0,2/(t-b),0,0, | ||
0,0,2/(f-n),0, // n,f 取负得到 2/(n-f) | ||
0,0,0,1 | ||
] * | ||
[ | ||
1,0,0,(l+r)*-0.5 | ||
0,1,0,(b+t)*-0.5, | ||
0,0,1,(n+f)*-0.5, // n,f 取负得到 (n+f)*0.5 | ||
0,0,0,1 | ||
] | ||
``` | ||
|
||
```ts | ||
const getOrthographicMat = (l: number, r: number, t: number, b: number, n: number, f: number) => { | ||
return multiplyMats( | ||
new Matrix4().makeScale(2 / (r - l), 2 / (t - b), 2 / (n - f)), | ||
new Matrix4().makeTranslation((l + r) * -0.5, (b + t) * -0.5, (n + f) * 0.5), | ||
) | ||
} | ||
``` | ||
|
||
## 透视投影矩阵 (projectionMatrix - PerspectiveMatrix) | ||
|
||
将视锥体远裁截面压缩得和近裁截面一样大(用到齐次坐标, 先将这个矩阵称为 PressMatrix), 再乘以正交投影矩阵 | ||
|
||
0. 先将根据条件求出 l, r, t, b, n, f | ||
|
||
1. 缩小远裁截面 | ||
<br/> ![perspectiveMat](./img/perspectiveMat.png)<br/> | ||
可以得到 P1.x/P2.x=P1.y/P2.y=P1.z/P2.z; P2.z = n, 所以如果想要将P1变到P2, 需要除以 P2.z/n, 可以得到齐次坐标为 `P2.z/n` | ||
|
||
根据上面的条件可以算出 PressMatrix 的第四行 | ||
|
||
``` | ||
[ | ||
_,_,_,_, | ||
_,_,_,_, | ||
_,_,_,_, | ||
a,b,c,d, | ||
] * [x, y, z, w] | ||
=> ax+by+cz+dw = z/n | ||
=> c = 1/n; a=b=d=0 | ||
``` | ||
|
||
2. 应用齐次坐标后就能得到转换后的x,y值(不存在其他的旋转平移缩放),所以可以确定矩阵前两行 | ||
|
||
``` | ||
[ | ||
1,0,0,0, | ||
0,1,0,0, | ||
_,_,_,_, | ||
0,0,1/n,0 | ||
] | ||
``` | ||
|
||
3. 应用齐次坐标后会导致 z 的值也会缩小,而 z 方向的缩放应该在`正交投影矩阵`里面进行, 还需要对 z 做一些变换 | ||
|
||
``` | ||
[ | ||
_,_,_,_, | ||
_,_,_,_, | ||
a,b,c,d, | ||
_,_,_,_, | ||
] * [x, y, z, w] | ||
得到变换后的z坐标 => ax+by+cz+dw | ||
应用齐次坐标 => (ax+by+cz+1d)*(n/z) | ||
可以得到两个等式 => | ||
1. (ax+by+c*n+d)*(n/n) = n (z 等于 n 时, 计算出的 z 值为 n, 近裁截面不变) | ||
2. (ax+by+c*f+d)*(n/f) = f (z 等于 f 时, 计算出的 z 值为 f, 远裁截面不变) | ||
=> | ||
1. ax+by+d = n-cn | ||
2. ax+by+d = f^2/n - cf | ||
=> n-cn = f^2/n - cf | ||
=> cf-cn = (f^2-n^2)/n | ||
=> c = (f+n)/n | ||
将上式代入 ax+by+d = n-cn | ||
=> ax+by+d = -f | ||
=> d = -f | ||
得到矩阵 | ||
=> | ||
[ | ||
1,0,0,0, | ||
0,1,0,0, | ||
0,0,(n+f)/n,-f, | ||
0,0,1/n,0 | ||
] | ||
``` | ||
|
||
4. 经过视图矩阵变换, 相机位于坐标原点, 看向 z 轴负方向, n 和 f 实际上应该是负值 | ||
|
||
齐次坐标的特性可以给这个矩阵乘以一个数, 通常会乘以 n, 这样的话经过变换以后 gl_Position 的 w 分量就刚好等于 -z, 而此时相机在坐标原点, 顶点都在 z 轴负方向, w 就可以代表视点到该顶点的距离, 方便某些计算 | ||
|
||
``` | ||
[ | ||
1,0,0,0, | ||
0,1,0,0, | ||
0,0,(n+f)/n,f, | ||
0,0,1/-n,0 | ||
] | ||
=> | ||
[ | ||
n,0,0,0, | ||
0,n,0,0, | ||
0,0,n+f,n*f, | ||
0,0,-1,0 | ||
] | ||
``` | ||
|
||
5. 上面得到一个变换视锥体的矩阵 PressMatrix,左乘以`正交投影矩阵`得到透视投影矩阵 | ||
=> getOrthographicMat(l, r, t, b, n, f)\*PressMatrix | ||
|
||
```ts | ||
const getPerspectiveMat = (fov: number, aspect: number, near: number, far: number) => { | ||
const [n, f] = [near, far] | ||
const halfFov = MathUtils.degToRad(fov) * 0.5 | ||
const t = near * Math.tan(halfFov) | ||
const height = t * 2 | ||
const width = aspect * height | ||
|
||
// prettier-ignore | ||
const _mat = new Matrix4().set( | ||
n,0,0,0, | ||
0,n,0,0, | ||
0,0,n+f,n*f, | ||
0,0,-1,0 | ||
) | ||
|
||
const orthographicMat = getOrthographicMat(width * -0.5, width * 0.5, t, -t, n, f) | ||
return new Matrix4().multiplyMatrices(orthographicMat, _mat) | ||
} | ||
``` |
Oops, something went wrong.