透视中 n 等分点和倍增线段的直接画法

之前学习了任意 n 等分点的画法,该方法虽能绘制任意等分点,但对程序作图不太适合,最近研究发现,其实有一些更直接的方式来找 n 等分点,其不需要做任何辅助线,只需要做测量即可

n 等分点,倍增线段

直入主题,有这样的结论:

任意指向某消失点的线段,它的 n 等分点的位置仅和线段在画布上的长度,以及线段和消失点在画布上的距离相关。

假设该线段离消失点较远的点和消失点的距离为 a,离消失点较近的点和消失点的距离为 b,则该线段的第 i 个 n 等分点和消失点的距离是:

d(n, i, a, b) = nab / (ia + (n - i)b)

比如,当a = 6b = 2时,它的 2 等分点距离消失点的距离是 2 * 6 * 2 / (6 + 2) = 3,如下图:

二等分

比如,当a = 6b = 2时,它的三等分点距离消失点的距离分别为 3 * 6 * 2 / (6 + 2 * 2) = 3.6, 3 * 6 * 2 / (2 * 6 + 2) = 2.57,如下图:

三等分

倍增线段,假设该线段离消失点较远的点和消失点的距离为 a,离消失点较近的点和消失点的距离为 b,则它的倍增点距离该线段离消失点较远的点的距离为:

1
d(a, b) = (2a^2 - 2ab) / (2a - b)

比如,当a = 5, b = 3时,它的倍增线段距离离消失点较远的点的距离是 (2 * 5 * 5 - 2 * 5 * 3) / (2 * 5 - 3) = 2.85,如下图:

倍增线段

n 等分点代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 根据一条指向消失点的直线的近处和远处到消失点的长度,获取它的 n 等分点距直线近处的位置
* @param n 要求几等分点
* @param longToDim 在画布上直线近处到消失点的大小
* @param shortToDim 在画布上直线远处到消失点的大小
* @returns 画布上各等分点到直线近处的长度,数组大小为 n - 1
*/
function nSplit(n: number, longToDim: number, shortToDim: number): number[] {
if (n <= 1 || longToDim <= 0 || shortToDim <= 0) return []
if (longToDim < shortToDim) [shortToDim, longToDim] = [longToDim, shortToDim] // swap
const res: number[] = []
for (let i = 1; a <= n - 1; i++) {
const toDim = n * longToDim * shortToDim / (i * longToDim + (n - i) * shortToDim)
res.push(longToDim - toDim)
}
return res
}

一个交互式示例

下面是一个利用 p5.js 来展示该算法的交互式示例,可以拖动 A,C 点和两个消失点,代码实现就在 html 文件中:

CLICK ME!

100 等分

example

证明

因为画图麻烦所以这里不画图了 w

n 等分点

考虑一个左手坐标系中的三个点 P0,P1,P2,其中P0 = (x, y, z, P1 = (x, y, z + l), P2 = (x, y, z + 2),相机在原点,viewport 为z=d

显然,P0,P1,P2 是同一个线段上 3 个点,P1 为 P0P2 的中点。这三个点向相机方向投影,在 viewport 上分别为P0' = (x * d / z, y * d / z, d), P1' = (x * d / (z + l), y * d / (z + l), d), P2' = (x * d / (z + 2l), y * d / (z + 2l), d)

现在只看 viewport 这个平面,我们要知道二等分点和线段、消失点的关系,就是要看 |P0’O|,|P1’O|,和 |P2’O| 的关系,其中 |P0’O| 和 |P2’O| 对我们是可知的。这三个点显然都在一条斜率等于 y / x 的线性函数上,其中 P0’距原点最远,P2’最近。

我们能得到|P0'O| = (d / z) * sqrt(x^2 + y^2)|P1'O| = (d / (z + l)) * sqrt(x^2 + y^2)|P2'O| = (d / (z + 2l)) * sqrt(x^2 + y^2),不太容易得到的是,|P1'O| = 2 * |P0'O| * |P2'O| / (|P0'O| + |P2'O|)

再研究三等分点,增加一个点 P3,使用上面相同的方式,研究|P0'O|, |P1'O|, |P3'O||P0'O|, |P2'O|, |P3'O|的关系,其中|P0'O||P3'O| 对我们是可知的,后面的以此类推。

倍增线段

仍旧是像二等分点一样,先三个点 P0,P1,P2,但这次可知的是|P0'O||P1'O|,需要求得 |P2'O|,这里只需要对上面得到的关系 |P1'O| = 2 * |P0'O| * |P2'O| / (|P0'O| + |P2'O|) 化简一下就能得到结果。