Skip to content

Commit

Permalink
chore: 大量更新
Browse files Browse the repository at this point in the history
  • Loading branch information
stupidZhu committed Feb 26, 2024
1 parent 4cb68de commit 250dca3
Show file tree
Hide file tree
Showing 50 changed files with 1,747 additions and 1,185 deletions.
8 changes: 6 additions & 2 deletions apps/zxtool-site/.dumi/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ button {
.column {
flex-direction: column;
}
.info-img {
box-shadow: 1px 1px 4px #00000010;
.resume-img-wrapper {
> img {
width: 49%;
height: auto;
box-shadow: 2px 2px 4px #00000020;
}
}

.hr-with-content {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/zxtool-site/docs/blog/img/perspectiveMat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
153 changes: 153 additions & 0 deletions apps/zxtool-site/docs/blog/intersect.md
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))]
}
```

## 应用

用于射线拾取: 先检测射线是否与几何体的包围球相交, 如果相交则遍历几何体的三角面判断是否与几何体相交或者求出相交点
189 changes: 189 additions & 0 deletions apps/zxtool-site/docs/blog/pvMat.md
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)
}
```
Loading

0 comments on commit 250dca3

Please sign in to comment.