《计算机图形学原理及实践》学习笔记之第七章

第七章 2D 和 3D 空间中的基础数学与几何

引言

  • 实现原则
    • 如果你对一个数学问题的理解足够透彻,则一定写一个程序实现它

记号

与传统的数学记号相同

集合

  • 集合 B 和 集合 C 的笛卡尔乘积 定义为:
    • B × C = { ( b , c ) : b ∈ B , c ∈ C } B × C = \{(b,c):b ∈ B, c ∈ C\} B×C={(b,c):bB,cC}
    • 读作 B 叉乘 C,但称为 笛卡尔乘积,保留向量叉乘的术语
  • 乘积 R×R 记作 R 2 R^2 R2,更高阶的是 R 3 R^3 R3 R 4 R^4 R4 等等…

函数

  • 函数的表示:

    • f : R → R : x → x 2 f:R → R : x → x^2 f:RR:xx2
    • f 是函数的名字
    • 第一个冒号后所示的两个集合,箭头左侧是 定义域 右侧是 陪域
    • 第二个冒号后所示的是 定义域中某元素 x 到 陪域中对应元素的 映射规则
  • g : R → R 0 + : x → x 2 g:R → R_0^+ : x → x^2 g:RR0+:xx2

    • 函数 f 与 函数 g 是有区别的。对于函数 g,函数值得集合 覆盖了整个陪域。而对于函数 f,函数值得集合只是陪域的一个真子集。
    • 函数 g 称为 满射,而函数 f 不是
  • h : R 0 + → R 0 + : x → x 2 h : R_0^+ → R_0^+ : x → x^2 h:R0+R0+:xx2

    • 函数 h 与 函数 g 又不同。函数 h 不仅仅满射,函数 h 定义域内任何两个元素都不会对应于陪域中同一个元素。换句话说有 h(a) = h(b) 则一定有 a = b
    • 我们成这样的属性为 单射。即 函数 h 不仅满射 而且 单射
  • 一个 既是 单射 又是 满射 的函数,具有 逆函数,以函数 h 举例

    • h − 1 : R 0 + → R 0 + : x → x h^{-1} : R_0^+ → R_0^+ : x → \sqrt{x} h1:R0+R0+:xx
    • 更一般的说,如果
    • f : C → D f : C → D f:CD
    • 既是 单射 又是 满射(或者说双射),那么它的逆函数:
    • f − 1 : D → C f^{-1} : D → C f1:DC
    • 是满足如下条件的唯一函数:
    • f − 1 ( f ( x ) ) = x f^{-1}(f(x)) = x f1(f(x))=x,对于所有 x ∈ D
    • f ( f − 1 ( y ) ) = y f(f^{-1}(y)) = y f(f1(y))=y,对于所有 y ∈ C
  • 分段函数(略)

  • 反正切函数

    • 定义 从 R 到 开区间 ( − 2 π / 2 , 2 / p i / 2 ) (-2\pi/2, 2/pi/2) (2π/2,2/pi/2) 的 arctan函数(反正切函数) 为 正切函数的 逆函数。
    • 反正切函数大多用在角度值的计算。
    • tan = cos / sin = y / x,arctan 同理
  • 程序如何计算三角函数呢?

    • 三角函数作为基本初等函数,我们似乎无法把它向下拆解,同时也无法很好的用程序表达。但是构造出其近似式,得出其近似值。
    • 泰勒公式/麦克劳林公式:
      • 泰勒公式是用多项式函数逼近一个给定函数的方法,也是机器学习中 梯度迭代的核心思想。
      • 泰勒公式的定义:
      • 如果函数 f ( x ) f(x) f(x) 在含 x 0 x_0 x0 的某个开区间 (a, b) 内具有 ( n + 1 ) (n + 1) (n+1)阶导数,则对任意 x ∈ (a, b) 有
        f ( x ) = f ( x 0 ) 0 ! + f ( x 0 ) ˙ 1 ! ∗ ( x − x 0 ) + f ( x 0 ) ¨ 2 ! ∗ ( x − x 0 ) 2 + f x 0 ¨ ˙ 3 ! ∗ ( x − x 0 ) 3 + . . . + f ( n ) ( x 0 ) n ! ∗ ( x − x 0 ) n + R n ( x ) f(x) = \frac{f(x_0)}{0!} + \frac{\dot{f(x_0)}}{1!}*(x-x_0) + \frac{\ddot{f(x_0)}}{2!}*(x-x_0)^2 + \frac{\dot{\ddot{f{x_0}}}}{3!}*(x-x_0)^3 + ... + \frac{f^{(n)}(x_0)}{n!}*(x-x_0)^n + R_n(x) f(x)=0!f(x0)+1!f(x0)˙(xx0)+2!f(x0)¨(xx0)2+3!fx0¨˙(xx0)3+...+n!f(n)(x0)(xx0)n+Rn(x)
  • 其中 R_n(x) 为余项,即误差,拉格朗日余项表示为 f ( n + 1 ) ( x 0 ) ( n + 1 ) ! ∗ ( x − x 0 ) ( n + 1 ) \frac{f^{(n+1)}(x_0)}{(n+1)!} * (x-x_0)^{(n+1)} (n+1)!f(n+1)(x0)(xx0)(n+1)

  • 麦克劳林公式,是泰勒公式的拓展,其将泰勒公式中的 x 0 x_0 x0 限定为 0
    f ( x ) = f ( 0 ) 0 ! + f ( 0 ) ˙ 1 ! ∗ x + f ( 0 ) ¨ 2 ! ∗ x 2 + . . . + f ( n ) ( 0 ) n ! ∗ x n + R n ( x ) f(x) = \frac{f(0)}{0!} + \frac{\dot{f(0)}}{1!}*x + \frac{\ddot{f(0)}}{2!}*x^2 + ... + \frac{f^{(n)}(0)}{n!}*x^n + R_n(x) f(x)=0!f(0)+1!f(0)˙x+2!f(0)¨x2+...+n!f(n)(0)xn+Rn(x)

  • 使用泰勒公式 或 麦克劳林公式可以对函数进行近似求解,但是它们是无穷项的呀?实际上,我们近似到第 n 项 和 近似到第 n + 1 项,它们的差 就是近似到第 n 项的余项,即误差,如果这个误差小于我们的 规定值,那么我们就可以输出近似结果了

实现代码:

public static double Sin(double x)
{
    double res = 0;
    double last = 0;
    int n = 1;
    double temp = x;
    do
    {
        last = res;
        res += temp;
        n+=2;
        temp = temp * x * x * (-1) / (n * (n - 1));
    }
    while (Math.Abs(res - last) > double.Epsilon);
    return res;
}
  • 这里使用的是麦克劳林的展开式,把 sin 函数代入即可得到 sin 的麦克劳林的展开式,比较简单,这里就不做推导了
  • 有了 sin,cos 和 tan 就很简单了,一个是相位的改变,另一个是二者的比值。
  • 那么我们是否可以用一个固定的多项式,来对其进行近似呢?
    • 实际上是可以的,就像机器学习梯度迭代最后得到一个固定的多项式一样。但实际上我们上面的代码更为合适,因为对于一些输入,其只需要迭代 十几次 就可以得到准确答案,而有的则需要迭代二十多次,如果我们把多项式固定下来,则对于一些输入来说就浪费了一些计算量。
  • 显而易见的,sin 的函数曲线与 幂函数曲线类似。我们可以通过构造 二次幂函数,然后将其进行分段构造成一个周期函数来近似 sin,这样做的话,我们的计算效率将会更高。至于这个幂函数该如何得到,这里就不作推导了,使用麦克劳林展开式计算三角函数是程序上更加常用的方法。

S i n x = x − x 3 3 ! + x 5 5 ! − x 7 7 ! + . . . + ( − 1 ) n + 1 ∗ x ( 2 n − 1 ) ( 2 n − 1 ) ! Sinx = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + ... + (-1)^{n + 1}*\frac{x^{(2n-1)}}{(2n - 1)!} Sinx=x3!x3+5!x57!x7+...+(1)n+1(2n1)!x(2n1)

坐标

坐标运算

  • 中点计算公式:
  • M = ( x 1 + x 2 2 , y 1 + y 2 2 ) M = (\frac{x_1+x_2}{2}, \frac{y_1+y_2}{2}) M=(2x1+x2,2y1+y2)
  • 还可以写成
  • M = 1 2 ( x 1 , y 1 ) + 1 2 ( x 2 , y 2 ) M = \frac{1}{2}(x_1,y_1) + \frac{1}{2}(x_2,y_2) M=21(x1,y1)+21(x2,y2)
  • 像中的计算公式这样的,各项系数之和为 1 (1/2 + 1/2 = 1),我们称之为 仿射。该类组合结果在不同的坐标系下具有不变性。
  • 我们设两点:P: ( x 1 , y 1 ) (x_1,y_1) (x1,y1) Q: ( x 2 , y 2 ) (x_2, y_2) (x2,y2)
  • 那么中点计算公式得到的就是 P 到 Q 连线的中点
  • 那么 1 3 P + 2 3 Q \frac{1}{3}P + \frac{2}{3}Q 31P+32Q 呢?
  • 就是 P 到 Q 连线的三分点处
  • 我们甚至可以取负数: − 1 3 P + 4 3 Q \frac{-1}{3}P + \frac{4}{3}Q 31P+34Q 这也仿射条件,且该点位于 P Q 连线所在直线上
  • 于是根据 仿射我们可以得到:
  • γ : R → R 2 : t → ( 1 − t ) P + t Q γ : R → R^2 : t → (1 - t)P + tQ γ:RR2:t(1t)P+tQ
  • 上式称为连接 P 和 Q的直线的参数化方程,变量 t 是参数。

向量、矩阵

  • n维向量可以看作是一个 n×1 的矩阵
  • m × n 的矩阵,表示为由 m行 n列 元素组成的矩阵
  • 矩阵的转置操作:
    • 转置可以把 m × n 的矩阵变为 n × m 的矩阵。只需要将矩阵 ij 位置上的元素置为 原来矩阵 ji 位置上的元素
    • 矩阵A 的 转置矩阵记作 A T A^T AT
  • 向量是矢量,即如果两个向量在坐标系下其位置不同,但是方向和长度相同,则这两个向量是同一个向量
  • 向量的长度 又称为 向量的模,记为 ∣ ∣ v ∣ ∣ ||v|| v,长度为 1 的向量称为单位向量,把一个向量变为单位向量,就是该向量除以自己的模,这一过程称为 归一化
  • 向量运算 略,主要提一点,向量的叉乘遵循右手定则
    • × [ v x v y ] = [ − v y v x ] ×\left[ \begin{matrix} v_x \\ v_y \end{matrix} \right] = \left[ \begin{matrix} -v_y \\ v_x \end{matrix} \right] ×[vxvy]=[vyvx] 为向量的"积",也记为 v ┴ v┴ v × v ×v ×v,其也是向量 v 的法向量
  • 点 和 点 之间的差为一个向量
  • 点 与 向量的和 为一个点
  • 矩阵乘法:
    • 如矩阵 A 是 m × k 的矩阵,矩阵 B 是 k × n 的矩阵,则二者可以进行乘法,得到的是 m × n 的矩阵

    • 在这里插入图片描述

    • 简单记为 矩阵 A 的 第 i 行,分别与矩阵 B 的 每列(即当前列为 第 j 列),对应元素相乘相加,即为 结果矩阵的 i,j 位置的元素

  • 隐式直线
    • 水平集:
      • 如果 F : R 2 → R F : R^2 → R F:R2R 是一个函数,那么对于任一 c,可定义一个集合:
      • L c = { ( x , y ) : F ( x , y ) = c } L_c=\{(x,y) : F(x,y) = c\} Lc={(x,y):F(x,y)=c}
      • 称 F 在 c 处的 水平集(类似于等高线图)
      • 那么显然,不同的水平集之间是没有交集的,即不同的等高线是不会相交的
      • 特别的,即 c = 0 的水平集为 函数 F 的 零水平集
    • 找到一个函数 F,使其在经过 P、Q 两点所在直线上为 0,即该直线上的点坐标 代入结果都为 0
      • 那么我们已经知道 P、Q 所在直线的法向量: n = × ( P − Q ) = ( P − Q ) ┴ n = ×(P - Q) = (P - Q)┴ n=×(PQ)=(PQ)
      • 而又:直线上的向量与该直线法向的点乘为 0,即设 X 是直线上一点有 ( X − P ) • n = 0 (X - P) • n = 0 (XP)n=0
      • 那么就有: F ( X ) = ( X − P ) • n F(X) = (X - P) • n F(X)=(XP)n
      • 上面就是直线的标准隐式形式
      • 标准隐式可以 转换为 直线方程
        • 设 P 为 ( c 1 , c 2 ) (c_1, c_2) (c1,c2),n 为 [ a b ] \left[ \begin{matrix} a \\ b \end{matrix} \right] [ab]
        • 那么标准隐式就是 ( ( x , y ) − ( c 1 , c 2 ) ) ⋅ [ a b ] ((x, y) - (c_1, c_2)) · \left[ \begin{matrix} a \\ b \end{matrix} \right] ((x,y)(c1,c2))[ab]
        • [ ( x − c 1 ) ( y − c 2 ) ] ⋅ [ a b ] \left[ \begin{matrix} (x - c_1) \\ (y - c_2) \end{matrix} \right] · \left[ \begin{matrix} a \\ b \end{matrix} \right] [(xc1)(yc2)][ab]
        • a ( x − c 1 ) + b ( y − c 2 ) = 0 a(x - c_1) + b(y - c_2) = 0 a(xc1)+b(yc2)=0
        • a x + b y − c 1 − c 2 = 0 ax + by - c_1 - c_2 = 0 ax+byc1c2=0
  • 图形学一般避免使用 y = mx + b 的形式描述直线,即 斜率-截距 形式。其中 m 为斜率,b 为截距。因为斜截式无法表示垂直的直线(直线垂直于 x 轴,该直线无斜率和截距,因为斜截式是一种函数表示,垂直于x轴的图像,也不满足函数条件)

矩阵乘法的代码实现:

public static int[,] MatrixX(int[,] a, int[,] b)
{
    if (a == null || b == null) return null;
    int ac = a.GetLength(0);
    int br = b.GetLength(1);
    if (a.GetLength(0) != b.GetLength(1)) return null;
    int[,] res = new int[ac, br];
    for(int col = 0; col < ac; ++col)
    {
        for(int row = 0; row < br; ++br)
        {
            int item = 0;
            for(int i = 0; i < ac; ++ac) item += a[col, i] * b[i, row];
        }
    }
    return res;
}

直线求交

参数化-参数化直线求交

  • 有两条直线的参数化表示:
  • γ : R → R 2 : t → t A + ( 1 − t ) B γ : R → R^2 : t → tA + (1 - t)B γ:RR2:ttA+(1t)B
  • η : R → R 2 : s → s C + ( 1 − s ) D η : R → R^2 : s → sC + (1 - s)D η:RR2:ssC+(1s)D
  • 如果两个直线有交点,则一定有 t 0 t_0 t0 s 0 s_0 s0 使得
  • t 0 A + ( 1 − t 0 ) B = s 0 C + ( 1 − s 0 ) D t_0A + (1-t_0)B = s_0C + (1-s_0)D t0A+(1t0)B=s0C+(1s0)D
  • v = A − D v = A - D v=AD u = C − D u = C - D u=CD
  • 得到 B − D = − t 0 v + s 0 u B - D = -t_0v + s_0u BD=t0v+s0u
  • 令两边与 × v ×v ×v 进行点积 得
  • ( B − D ) ⋅ ( × v ) = − t 0 v ⋅ ( × v ) + s 0 u ⋅ ( × v ) (B - D)·(×v) = -t_0v·(×v) + s_0u·(×v) (BD)(×v)=t0v(×v)+s0u(×v)
  • s 0 = ( B − D ) ⋅ ( × v ) u ⋅ ( × v ) s_0 = \frac{(B - D)·(×v)}{u·(×v)} s0=u(×v)(BD)(×v)
  • 如果要求 u 0 u_0 u0 那么两边都点乘 ×u 即可

参数化-隐式直线求交

  • γ ( t ) = ( 1 − t ) P + t Q γ(t) = (1 - t)P + tQ γ(t)=(1t)P+tQ
  • l = X : ( X − S ) • n = 0 l = {X : (X - S)•n = 0} l=X:(XS)n=0
  • 若两直线相交,则有 t 0 t_0 t0 使 γ ( t 0 ) γ(t_0) γ(t0) 在直线 l 上
  • 那么就可以得到:
  • ( γ ( t 0 ) − S ) ∗ n = 0 (γ(t_0) - S) * n = 0 (γ(t0)S)n=0
  • ( ( 1 − t 0 ) P + t 0 Q ) − S ) ∗ n = 0 ((1-t_0)P + t_0Q) - S) * n = 0 ((1t0)P+t0Q)S)n=0
  • ( t 0 ( Q − P ) + ( P − S ) ) ∗ n = 0 (t_0(Q - P) + (P - S)) * n = 0 (t0(QP)+(PS))n=0
  • u = ( Q − P ) u = (Q - P) u=(QP) v = ( P − S ) v = (P - S) v=(PS) 得:
  • t 0 u ∗ n + v ∗ n = 0 t_0u * n + v * n = 0 t0un+vn=0
  • t 0 = − v ∗ n u ∗ n t_0 = \frac{-v * n}{u * n} t0=unvn

直线求交代码实现:

public struct Vector2
{
    public float x;
    public float y;
    public Vector2(float x, float y)
    {
        this.x = x;
        this.y = y;
    }
    public static float operator * (Vector2 v1, Vector2 v2)
    {
        return v1.x * v2.x + v1.y * v2.y;
    }
    public static Vector2 operator *(Vector2 v1, float v2)
    {
        return new Vector2(v1.x * v2, v1.y * v2);
    }
    public static Vector2 operator *(float v1, Vector2 v2)
    {
        return new Vector2(v1 * v2.x, v1 * v2.y);
    }
    public static Vector2 operator + (Vector2 v1, Vector2 v2)
    {
        return new Vector2(v1.x + v2.x, v1.y + v2.y);
    }
    public static Vector2 operator -(Vector2 v1, Vector2 v2)
    {
        return new Vector2(v1.x - v2.x, v1.y - v2.y);
    }
    public static Vector2 operator -(Vector2 v)
    {
        return new Vector2(-v.x, -v.y);
    }
    public static Vector2 operator /(Vector2 v1, float v2)
    {
        return new Vector2(v1.x / v2, v1.y / v2);
    }
}
public static Vector2 PPIntersect(Vector2 A, Vector2 B, Vector2 C, Vector2 D)
{
    Vector2 v = A - B;
    Vector2 u = C - D;
    Vector2 xv = new Vector2(-v.y, v.x);
    float s0 = (B - D) * xv / (u * xv);
    return s0 * C + (1 - s0) * D;
}
public static Vector2 IPIntersect(Vector2 P, Vector2 Q, Vector2 S, Vector2 n)
{
    Vector2 v = P - S;
    Vector2 u = Q - P;
    float t0 = (-v * n) / (u * n);
    return (1 - t0) * P + t0 * Q;
}

光线-平面求交

  • 给定一条起点为 P、方向向量为 d(故光线上的点为 P + td, t >= 0),已经经过点 Q、法向量为 n 的平面,计算它们的交点。平面上任意一点 X 具有如下属性:
  • ( X − Q ) ⋅ n = 0 (X - Q) · n = 0 (XQ)n=0
  • 这是由直线的隐式方程得到的 平面的方程
  • 若 光线 与 平面相交,则有 t 0 t_0 t0 使得:
  • ( P + t 0 d − Q ) ⋅ n = 0 (P + t_0d - Q) · n = 0 (P+t0dQ)n=0
  • ( P − Q ) ⋅ n + t 0 d ⋅ n = 0 (P - Q) · n + t_0d · n = 0 (PQ)n+t0dn=0
  • t 0 = ( Q − P ) ⋅ n d ⋅ n t_0 = \frac{(Q - P) · n}{d · n} t0=dn(QP)n
  • 但在求解过程中 d·n 可能为 0,此时 光线方向d 与 平面法向 n 垂直,即 光线与平面没有交点,光线平行与平面。或光线就在平面上,与平面有无数个交点。此时无法求解处 t 0 t_0 t0
  • 平面也有其参数化方程,其由平面上一点 Q 和 两个线性无关的向量 u 和 v 定义:
  • Q + α u + β v Q + αu + βv Q+αu+βv
  • 若光线与平面有交点,则设 t 0 t_0 t0
  • P + t 0 d = Q + α u + β v P + t_0d = Q + αu + βv P+t0d=Q+αu+βv
  • P − Q = α u + β v − t 0 d P - Q = αu + βv - t_0d PQ=αu+βvt0d
  • 现在问题转换为了 u、v 和 d 的线性组合。令 M 是一个矩阵,它的列向量为这三个向量,则有:
  • P − Q = M [ α β − t ] P - Q = M\left[ \begin{matrix} α \\ β \\ -t \end{matrix} \right] PQ=Mαβt
  • 若 M 不可逆,则光线平行于平面,若 光线在平面内,则有无数的解。
  • 可以发现,对于参数化形式的平面求交,光线与隐式平面的求交计算要容易的多

代码:

public struct Ray
{
    public Vector3 orgion { get; private set; }
    public Vector3 dir { get; private set; }

    public Ray(Vector3 orgion, Vector3 dir)
    {
        this.orgion = orgion;
        this.dir = dir;
    }
}

public struct Plane
{
    public Vector3 Q { get; private set; }
    public Vector3 nor { get; private set; }
    public Vector3 u { get; private set; }
    public Vector3 v { get; private set; }
    public Plane(Vector3 Q, Vector3 nor)
    {
        this.Q = Q;
        this.nor = nor;
        this.u = Vector3.Zero;
        this.v = Vector3.Zero;
    }
    
    public Plane(Vector3 Q, Vector3 u, Vector3 v)
    {
        this.Q = Q;
        this.nor = Vector3.Cross(u, v);
        this.u = u;
        this.v = v;
    }

    public static bool IntersectWithRay(Ray ray, Plane plane, out Vector3 point)
    {
        point = Vector3.Zero;
        float dn = Vector3.Dot(ray.dir, plane.nor);
        if (dn == 0) return false;
        float t = Vector3.Dot(plane.Q - ray.orgion,  plane.nor) / dn;
        point = ray.orgion + t * ray.dir;
        return true;
    }
}

光线-球求交

球的隐式描述:
( X − Q ) ⋅ ( X − Q = r 2 (X - Q) · (X - Q = r^2 (XQ)(XQ=r2
将射线表示 P + t d P + td P+td 代入得到
( d ⋅ d ) t 2 + ( 2 d ⋅ v ) t + ( v ⋅ v − r 2 ) = 0 (d·d)t^2 + (2d·v)t + (v·v - r^2) = 0 (dd)t2+(2dv)t+(vvr2)=0
是一个一元二次方程,求解 t 即可,可能有 无解、 1个解、2个解

代码:

public struct Sphere
{
    public Vector3 origion;
    public float radius;
    public Sphere(Vector3 origion, float radius)
    {
        this.origion = origion;
        this.radius = radius;
    }
    public static int IntersectWithRay(Ray ray, Sphere sphere, out Vector3[] points)
    {
        points = new Vector3[2];
        Vector3 v = ray.orgion - sphere.origion;
        float a = Vector3.Dot(ray.dir, ray.dir);
        float b = 2 * Vector3.Dot(ray.dir, v);
        float c = Vector3.Dot(v, v) - (sphere.radius * sphere.radius);
        float t = b * b - 4 * a * c;
        if (t < 0) return 0;
        float aa = 2 * a;
        float b2a = -b / aa;
        if (t == 0)
        {
            points[0] = ray.orgion + ray.dir * b2a;
            return 1;
        }
        float gt2a = Mathf.Sqrt(t) / aa;
        points[0] = ray.orgion + ray.dir * (b2a + gt2a);
        points[1] = ray.orgion + ray.dir * (b2a - gt2a);
        return 2;
    }
}

三角形

在这里插入图片描述

如图,我们设 A、B、C 为三角形的三个顶点,则可以得到
Q = ( 1 − t ) A + t B Q = (1-t)A + tB Q=(1t)A+tB
R = ( 1 − s ) Q + s C R = (1-s)Q + sC R=(1s)Q+sC
联立两式可以得到:
R = ( 1 − s ) [ ( 1 − t ) A + t B ] + s C = ( 1 − s ) ( 1 − t ) A + ( 1 − s ) t B + s C R = (1-s)[(1-t)A + tB] + sC = (1-s)(1-t)A + (1-s)tB + sC R=(1s)[(1t)A+tB]+sC=(1s)(1t)A+(1s)tB+sC
这样,令 s、t 的取值范围都为 0~1,我们就可以计算出三角形 ABC 内任意一点 R
这即为 三角形的参数式表达

重心坐标

我们再来看参数表达:
R = ( 1 − s ) ( 1 − t ) A + ( 1 − s ) t B + s C R = (1-s)(1-t)A + (1-s)tB + sC R=(1s)(1t)A+(1s)tB+sC
该参数方程有三个系数,分别为:
( 1 − s ) ( 1 − t ) (1-s)(1-t) (1s)(1t) ( 1 − s ) t (1-s)t (1s)t s s s
当 s、t 都取 0~1 范围时,这三个系数都为正数,且三个系数之和为 1:
( 1 − s ) ( 1 − t ) + ( 1 − s ) t + s = ( 1 − s ) ( 1 − t + t ) + s = 1 − s + s = 1 (1-s)(1-t) + (1-s)t + s = (1-s)(1-t + t) + s = 1-s + s = 1 (1s)(1t)+(1s)t+s=(1s)(1t+t)+s=1s+s=1
因此,我们定义 三角形的重心坐标表示:
R = α A + β B + γ C R = αA + βB + γC R=αA+βB+γC
其中:
α + β + γ = 1 α + β + γ = 1 α+β+γ=1

练习:
在三角形 ABC 中,边 AB 的中点的重心坐标是什么?
P = 0.5 A + 0.5 B P = 0.5A + 0.5B P=0.5A+0.5B
三角形的中心的重心坐标:
P = 0.5 A + 0.5 B + 0.5 C P = 0.5A + 0.5B + 0.5C P=0.5A+0.5B+0.5C

A = ( 1 , 0 , 0 ) A = (1, 0, 0) A=(1,0,0) B = ( 0 , 1 , 0 ) B = (0, 1, 0) B=(0,1,0) C = ( 0 , 0 , 1 ) C = (0, 0, 1) C=(0,0,1),点 P 的重心坐标为 α、β、γ,那么点 P 的 3D 坐标是什么?
P = α ( 1 , 0 , 0 ) + β ( 0 , 1 , 0 ) + γ ( 0 , 0 , 1 ) = ( α , β , γ ) P = α(1, 0, 0) + β(0, 1, 0) + γ(0, 0, 1) = (α, β, γ) P=α(1,0,0)+β(0,1,0)+γ(0,0,1)=(α,β,γ)

重心坐标常用到的两种解释:

  • 在一个非退化三角形中,点 P 的 α 坐标等于 点P 到 边BC 的垂直距离 乘以 一个比例因子。该比例因子使得 点A 的 α 坐标正好为 1. 该结论可以由两种方法来进行验证:
    • 用顶点的坐标代入计算验证
    • 点 P 到边的 垂直距离,和 α坐标,都是 平面仿射函数(线性变换 + 位移),因此若在 三个顶点处,它们的取值都一样,则就可以保证在三角形内部任意一点它们是一样的
  • 根据上面的解释,我们可以得到 由点 P、B、C 构成的三角形,且 P 的 α坐标为:
    α = S P B C S A B C α = \frac{S_{PBC}}{S_{ABC}} α=SABCSPBC
    推导过程:
    设 比例因子为 θ,点 A 到 边BC 的垂直距离为 h,点 P 到 边BC 的垂直距离为 h’ 得:
    θ h = 1 θh = 1 θh=1
    θ h ′ = α θh' = α θh=α
    S A B C = h ∗ B C ∗ 0.5 S_{ABC} = h*BC*0.5 SABC=hBC0.5
    S P B C = h ′ ∗ B C ∗ 0.5 S_{PBC} = h'*BC*0.5 SPBC=hBC0.5
    S P B C S A B C = h ′ h = α / θ 1 / θ = α \frac{S_{PBC}}{S_{ABC}} = \frac{h'}{h} = \frac{α/θ}{1/θ} = α SABCSPBC=hh=1/θα/θ=α

空间三角形 和 射线 与 三角形求交

上面的三角形参数化 和 重心坐标 与维度无关,因此在 3D空间下 仍然适用
接下来我们思考,射线 与 三角形 求交的解法
三角形所在的平面是唯一的,且我们知道三角形三个顶点的位置,那么就可以得到三角形所在平面的表示
我们的策略为,先计算 射线 与 三角形所在平面的交点 Q,然后 计算 交点Q 的重心坐标,确定其是否在三角形内。
为了加速求交计算,我们会让三角形存储一些预计算数据包括:
n n n 三角形法向量
A B ┴ AB┴ AB 在三角形所在平面内,且 垂直于 AB,且 ( C − A ) ⋅ A B ┴ = 1 (C-A) · AB┴ = 1 (CA)AB=1
A C ┴ AC┴ AC 在三角形所在平面内,且 垂直于 AC,且 ( B − A ) ⋅ A C ┴ = 1 (B-A) · AC┴ = 1 (BA)AC=1
我们先看 γ 的计算:
首先证明 AB┴ 的模,为 C 到 AB 的垂直距离 的 倒数:
在这里插入图片描述

根据之前的重心坐标的两种解释,我们可以知道 设我们要求 X 的重心坐标 则:
γ = S X A B S A B C = h ′ h γ = \frac{S_{XAB}}{S_{ABC}} = \frac{h'}{h} γ=SABCSXAB=hh
那么接下来我们就要找 h’ 即,X 到 AB 的垂直距离:
在这里插入图片描述

这样我们就得到了:
γ = ( X − B ) ⋅ A B ⊥ γ = (X-B) \cdot AB^{\bot} γ=(XB)AB

γ = ( X − A ) ⋅ A B ⊥ γ = (X-A) \cdot AB^{\bot} γ=(XA)AB
同理得:
β = ( X − A ) ⋅ A C ⊥ β = (X-A) \cdot AC^{\bot} β=(XA)AC
α = 1 − γ − β α = 1 - γ - β α=1γβ
我们最后再看一下 A B ⊥ AB^{\bot} AB A C ⊥ AC^{\bot} AC 是怎么计算得到的
对于 A B ⊥ AB^{\bot} AB
我们先 A B ⊥ = n × ( B − A ) AB^{\bot} = n \times (B - A) AB=n×(BA) 得到正确方向的单位向量
然后 A B ⊥ / = ( C − A ) A ˙ B ⊥ AB^{\bot} /= (C-A) \dot AB^{\bot} AB/=(CA)A˙B 得到 正确的模长
同理,对于 A C ⊥ AC^{\bot} AC
我们先 A C ⊥ = n × ( C − A ) AC^{\bot} = n \times (C - A) AC=n×(CA) 得到正确方向的单位向量
然后 A B ⊥ / = ( B − A ) A ˙ C ⊥ AB^{\bot} /= (B-A) \dot AC^{\bot} AB/=(BA)A˙C 得到 正确的模长

半平面 和 三角形

函数 F(x, y) = Ax + By + C 可以映射为 R 3 R^3 R3 即 三维空间中的一个平面
它是怎么映射的呢?我们看 xy平面:
在这里插入图片描述

将 Ax + By + C = 0 这条直线,在 z方向展开,即可得到 F(x, y) = Ax + By + C 在 三维空间映射的 平面
同时我们可以看到 原点 到 点(A, B) 的向量,与 Ax + By + C = 0 这条直线垂直,而 Ax + By + C = 0 这条直线 实际上就是 F(x, y) = Ax + By + C 在三维映射平面 与 xy平面 的交线
所以 F(x, y) = Ax + By + C 映射的平面,与 xy平面的交线(称为 l) 一定与 原点 到 (A, B) 这一向量垂直(称为 AB)
那么 在 F(x, y) 平面上,位于 l 往 AB 正方向一侧,F(x, y) > 0
在 位于 l 往 AB 负方向一侧, F(x, y) < 0
在 位于 l 上, F(x, y) = 0
我们称 F(X) >= 0 的点构成的平面为 半平面
一个在 xy平面上的三角形,可以由 三个这样的平面 在 xy平面上的交线构成,那么对于 三角形内一点 X 可以得到:
F 1 ( X ) > = 0 a n d F 2 ( X ) > = 0 a n d F 3 ( X ) > = 0 F_1(X) >= 0 and F_2(X) >= 0 and F_3(X) >= 0 F1(X)>=0andF2(X)>=0andF3(X)>=0

关于三角形法向的方向,我们将三角形的三个顶点按一定的顺序排列,然后根据右手定则来确定法向。
一般取逆时针顺序

多边形

多边形通常表示为一个有序的顶点表。不自交的多边形称为简单多边形
凸多边形:取其边界上任意两点,其连线完全位于多边形内 称为 凸多边形

多边形的内部 与 外部:
( P 0 , P 1 , P 2 , . . . , P n ) (P_0, P_1, P_2, ... , P_n) (P0,P1,P2,...,Pn) 为一多边形,那么线段 P 0 P 1 , P 1 P 2 , . . . P_0P_1, P_1P_2, ... P0P1,P1P2,... 称为该多边形的 边
向量 v 0 = P 1 − P 0 v_0 = P_1 - P_0 v0=P1P0, v 1 = P 2 − P 1 v_1 = P_2 - P1 v1=P2P1 … 称为 边向量
对于每条边 P i P i + 1 P_iP_{i+1} PiPi+1 其朝内的边法向定义为 × v i ×v_i ×vi,朝外的边法向定义为 − ( × v i ) -(×v_i) (×vi)
还记得 × v i ×v_i ×vi 吗?它在本章开始时被定义,若 v I = ( x , y ) v_I = (x, y) vI=(x,y) 则有 × v I = ( − y , x ) ×v_I = (-y, x) ×vI=(y,x),可以发现 v i v_i vi × v i ×v_i ×vi 是垂直的
这样的定义使得我们可以按照一定的顶点排列顺序来表示 有孔的多边形的内部与外部:
在这里插入图片描述

在这里插入图片描述

如上图,若要判断 平面上一点 是否在 多边形内部,只需要从该点朝任一方向发射一条射线,我们将该射线 与 该多边形的相交情况做下面分类:

  • 如果在交点处射线与所交边的 朝内法向的内积(点积)为正,则称该点为 入点
  • 如果在交点处射线与所交边的 朝内法向的内积为负,则称该点为出点
  • 当然,如果一个交点都没有,该点一定位于该多边形外部

如果 出点数 大于 入点数,则说明该点位于该多边形内部

出点数与入点数之差称作多边形关于该点的绕数

绕数还有一种不同的定义:
对于由顶点 P 1 , P 2 , . . . , P n P_1, P_2, ... , P_n P1,P2,...,Pn 构成的多边形,其关于点 Q 的绕数 与 多边形所有边对点 Q 所张夹角之和有关。若所有夹角之和为 2pi,则绕数为 1;如果为 4pi, 则绕数为 2,依次类推我们有:
n = 1 2 π ∑ i = 1 n c o s − 1 ( ( P i + 1 − Q ) ⋅ ( P i − Q ) ∣ P i + 1 − Q ∣ ∣ P i − Q ∣ ) n = \frac{1}{2 \pi} \sum_{i = 1}^{n}cos^{-1}(\frac{(P_{i+1} - Q) \cdot (P_i - Q)}{|P_{i+1} - Q| |P_i - Q|}) n=2π1i=1ncos1(Pi+1QPiQ(Pi+1Q)(PiQ))

非简单多边形的内部

非简单多边形的内部有三种判断规则:

  1. 正绕数规则:判定绕数取正值的点位于多边形内部
  2. 奇绕数规则:判定绕数为奇数的点位于内部
  3. 非零绕数规则:判定绕数不为零的点位于内部

每种规则都有其用途,绘图程序应该允许这 3 种规则并存。

平面多边形的符号面积: 分而治之

P 1 = ( x 1 , y 1 ) P_1 = (x_1, y_1) P1=(x1,y1), P 2 = ( x 2 , y 2 ) P_2 = (x_2, y_2) P2=(x2,y2) 为 xy平面上的两点,Q = (0, 0) 为原点,那么三角形 Q P 1 P 2 QP_1P_2 QP1P2 的符号面积由下式给出:
1 2 ( x 1 y 2 − y 1 x 2 ) \frac{1}{2}(x_1y_2 - y_1x_2) 21(x1y2y1x2)
推导过程:
在这里插入图片描述

如果三角形顶点 Q、P1、P2 为逆时针次序,则三角形符号面积为正
如果按顺时针次序则符号面积为负
关于这一点我们可以这样验证:
首先旋转三角形并不会改变三角形符号面积的绝对值,那么我们可以通过旋转三角形,使得 点 P1 在 x 轴上
那么根据 顺、逆 时针排序,会有下面这种情况:
在这里插入图片描述
在这里插入图片描述

我们再来推导更一般的情况:
给出由顶点 P 0 = ( x 0 , y 0 ) P_0 = (x_0, y_0) P0=(x0,y0) P 1 = ( x 1 , y 1 ) P_1 = (x_1, y_1) P1=(x1,y1) P 2 = ( x 2 , y 2 ) P_2 = (x_2, y_2) P2=(x2,y2) 构成的三角形,求其符号面积
此时,我们可以设 P0 为坐标系原点,即将 P0 变为 (0, 0), 那么 P1 和 P2 的新坐标就为 (x1 - x0, y1 - y0)和 (x2 - x0, y2 - y0)
代入上面的公式,可得:
在这里插入图片描述

即:
S = 1 2 ∑ i = 0 2 ( x i y i + 1 − x i + 1 y i ) S = \frac{1}{2}\sum_{i=0}^{2}(x_{i}y_{i+1} - x_{i+1}y_{i}) S=21i=02(xiyi+1xi+1yi)
注意:下标要对 3 取余(模),即 x3 为 x0, y3 为 y0

若要确定一个多边形 P 0 P 1 . . . P n P_0P_1 ... P_n P0P1...Pn 的符号面积,可以采用相同的技术:
首先计算 Q P 0 P 1 QP_0P_1 QP0P1 Q P 1 P 2 QP_1P_2 QP1P2,… , Q P n P 0 QP_nP_0 QPnP0,然后去除位于 多边形外的那部分区域,雷焦位于内部的区域的面积,即可得到正确的总面积。最后形式为:
a r e a = 1 2 ∑ i = 0 n ( x i y i + 1 − y i x i + 1 ) area = \frac{1}{2}\sum_{i=0}{n}(x_iy_{i+1} - y_ix_{i+1}) area=21i=0n(xiyi+1yixi+1)
这里下标同样对 n+1 进行取余(模)

空间多边形的法向量

前面已经给出 对于三角形 P 0 P 1 P 2 P_0P_1P_2 P0P1P2 的法向量 n, n = ( P 1 − P 0 ) × ( P 2 − P 1 ) n = (P_1 - P_0) \times (P_2 - P_1) n=(P1P0)×(P2P1)
特别的,如果这些顶点都恰好共线,则 n = 0

法向量还有一种表示:
n = [ A y z A z x A x y ] n = \begin{bmatrix} A_{yz} \\ A_{zx} \\ A_{xy} \end{bmatrix} n=AyzAzxAxy
A y z A_{yz} Ayz 为三角形在 yz平面上的投影的面积
A z x A_{zx} Azx 为三角形在 zx平面上的投影的面积
A x y A_{xy} Axy 为三角形在 xy平面上的投影的面积
该法向量并不是一个单位向量,实际上其模长为该三角形的面积
这里可以通过 1 2 ( x 0 y 1 − y 1 x 0 ) \frac{1}{2}(x_0y_1 - y_1x_0) 21(x0y1y1x0) 来计算在 xy 平面上投影的面积,同理可以计算处 yz、zx 平面上投影的面积
可以发现,在对应分量上,其是 叉乘结果的二分之一

更一般多边形的符号面积

设有位于平面 S 上的一多边形 P 0 P 1 . . . P n P_0P_1 ... P_n P0P1...Pn,其法向量为单位向量 n,则可以在 S 上确定两个正交单位i向量(相互垂直的两个单位向量) x 和 y,使得 x、y 和 n 满足 n = x × y n = x \times y n=x×y
我们取平面上某点为原点,可以构建 xyn 坐标系,那么就可以在该坐标系下确定每个 P i P_i Pi 的坐标,显然 每个坐标的 z 分量 或者说 第三维度(3D坐标值) 为 0,因为我们的 z轴 就是该平面 法向 n
那么 P i P_i Pi 可以表示为 ( x i , y i , 0 ) (x_i, y_i, 0) (xi,yi,0),这样就可以根据这些坐标进行符号面积的计算。
如果该多边形是三角形,且 符号面积为正,则称为 正向三角形,如果 面积为负 则称为 负向三角形
那么,如果我们的 法向 n 取反向,即 法向取 -n,那么求得的面积 也会取反(因为 x 和 y 中有一轴会取反,这样才能满足 n = x × y n = x \times y n=x×y)。因此,符号面积 和 三角形的取向是依据 带法向的平面 定义的。
以 zx平面为例,如果三角形顶点按逆时针顺序排列,则该三角形为正向三角形

倾斜原理

n’ 为 A平面的法向,n 为 B平面的法向
反过来,B平面上的三角形 T,其沿其法向 n 向 A平面做投影得到的三角形 T’ ,T’ 与 T 的面积二者之间是一个 余弦关系:
T = T ′ ∗ n ′ ⋅ n T = T' * n' \cdot n T=Tnn
T’ 沿着 n 向 B平面做投影,同样会在 B平面 得到 T,公式不变

重心坐标的仿射变换不变的性质

仿射变换:线性变换 + 平移变换
线性变换:旋转、缩放、错切、退化/奇异
重心坐标的仿射变换不变的性质即:
对三角形的顶点 和 三角形内任一点 Q 进行 旋转、平移、比例缩放这些变换的组合后,Q 的重心坐标不变。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值