判断三个数是否能构成三角形_如何判断点在三角形内部

在计算机图形学中,渲染图片时,会利用图形的顶点把其表面划分成一个个的小三角形,然后再在三角形内部做线性插值,算出图形表面任意点的坐标、颜色等等。

本文主要研究:给定三角形三个顶点 A、B、C 的坐标,以及点 P 的坐标。如何根据它们判断点 P 是否在三角形内部?

24ec6e043a381147111655891d97eefd.png
四个点的坐标已知,如何判断 P 是否在三角形内部?

关键词:重心坐标,Barycentric coordinate system

参考资料:https://blackpawn.com/texts/pointinpoly

Same Side Technique

最容易想到的方法是:把点 P 和 A、B、C 连起来,然后分别计算

的夹角,如果它们的和恰好为
,那么点 P 就在三角形内部,反之则在其外部。但是这种方法很慢,而且涉及到求反三角函数,所以直接被淘汰。

还有一种方法,是判断点 P 在线的哪一边。如果同时满足:

  • 点 P 和 C 在直线 AB 的同侧
  • 点 P 和 A 在直线 BC 的同侧
  • 点 P 和 B 在直线 AC 的同侧

则点 P 显然就在

内部。

数学上如何判断两个点在直线同侧呢?以 P 、C 在 AB 同侧为例,只需

方向相同即可。例如上图中两个叉乘的方向都指向屏幕外。

转化成伪代码如下所示:

function SameSide(p1, p2, a, b)
    cp1 = CrossProduct(b-a, p1-a)
    cp2 = CrossProduct(b-a, p2-a) 
    if DotProduct(cp1, cp2)>=0 then return true
    else return false 

上面的伪代码用于判断 p1,p2 两个点,是否在 a 与 b 连线的同侧。

那么,相应的判断点是否在三角形内部的算法,也显而易见了:

function PointInTriangle(p, a, b, c)
    PC = SameSide(p, c, a, b) # p 和 c 在 ab 的同侧
    PA = SameSide(p, a, b, c) # p 和 a 在 bc 的同侧
    PB = SameSide(p, b, a, c) # p 和 b 在 ac 的同侧
    return PA and PB and PC

上述算法很简单且易于实现,但效率还不算太高,下面引入本文主角——重心坐标。

Barycentric Technique

3f242b2ecdcda7580825019dd111e37c.png

为了方便表达,令

这两个非零且不共线的向量可以作为一组基底,表达该三角形所在平面内的任意点。

因此我们可以令:

要想让 P 在

内部,必须要满足:
  • (反推法,取 BC 上一点 p'= v0+λ(v1-v0),那么 u=(1-λ) v=λ,u+v=1)

下一步是解出 u 和 v。

(1) 两侧同时点乘

(1) 两侧同时点乘

由于四个坐标都是已知的,因此

都是已知的。因此 (2) 和 (3) 构成了一个二元一次方程组。

解得(为了整洁,下面的公式去掉了

的向量符号):

发现这俩的分母相同,因此又可以节省很多计算。下面是伪代码:

function PointInTriangle(p, a, b, c)
    // 计算向量
    v0 = c-a
    v1 = b-a
    v2 = p-a
    
    // 计算点乘
    dot00 = dot(v0, v0)
    dot01 = dot(v0, v1)
    dot02 = dot(v0, v2)
    dot11 = dot(v1, v1)
    dot12 = dot(v1, v2)

    // 计算重心坐标系下的坐标
    invDenom = 1/(dot00 * dot11 - dot01 * dot01)
    u = (dot11 * dot02 - dot01 * dot12) * invDenom
    v = (dot00 * dot12 - dot01 * dot02) * invDenom
    
    return u>=0 and v>=0 and (u+v)<1 

我们把上述算法和第一节中的 Same Side Technique 对比一下。

对于三维向量:

点乘:3 个乘法 + 2 个加法

叉乘:6 个乘法 + 3 个加法

向量相加减:3个加法

因此,对于 SameSide Technique,判断一次 SameSide 需要做 15次乘法和 20 次加法(20次是伪代码的加法数,可以稍微优化一下,在函数外事先求好向量,但能省下的运算也很有限),三次 SameSide 完成判断,所以总共用了 45 个乘法,60 次加法。

对于 Barycentric Technique,数了一下共用了 23 次乘法,1次除法(似乎精度不高时乘法和除法效率差不多), 22 次加法,显然 Barycentric Technique 效率更高。

******************************************************************************************

根据大佬@TM Zhang 的评论,这里用星号分割线更新一下。上文中的 SameSide 算法重复计算了很多次叉乘,原始伪代码需要计算:

,

,

,

而三角形中,任意两条边的叉乘大小为三角形的面积的两倍,方向由右手螺旋定则判断。由此可以发现,

大小方向均相同,因此只需要计算第一个就好了。

即,只需要计算:

,
,

如果上面这四个叉乘方向相同,则 P 在三角形内部。

1a678a5bc972697eddbc0cd18e9ab92f.png

我们希望用前三个叉乘中的元素,表示最后一个叉乘中的元素:

因此只要前三个叉乘方向相同,它们之和的方向一定与它们相同,最终 SameSide Technique 化简为求三个叉乘:

,
,

改进过的 SameSide Technique 伪代码如下:

function PointInTriangle(p, a, b, c)
    AP = p-a
    AB = b-a
    BP = p-b
    BC = c-b
    CP = p-c
    CA = c-a

    APxAB = CrossProduct(AP, AB)
    BPxBC = CrossProduct(BP, BC)
    CPxCA = CrossProduct(CP, CA)
    
    dot1 = DotProduct(APxAB, BPxBC)
    dot2 = DotProduct(BPxBC, CPxCA)
    
    return dot1>=0 and dot2>=0

改进后的 SameSide Technique 需要31次加法,24次乘法,还是比不上 Barycentric Technique 的22次加法,24次乘法。虽然这俩看上去也没差多少,但是在计算机图形学中,一个物体表面可能有成千上万乃至几十万个小三角形,而每个小三角形中,又有几十上百个需要判断的点,即使是微弱的优势,也会被放大百万倍,不可轻视。但是评论区另有大佬指出,barycentric technique 需要计算除法,会对精度造成负面影响。

******************************************************************************************

为什么叫 Barycentric Technique

现在再回到 (1):

3f242b2ecdcda7580825019dd111e37c.png

将向量换回点的坐标相减的形式:

合并同类项得:

这种形式下,就立刻看出为啥这个方法叫 Barycentric Coordinate 了,即:可以把 P 看成 A、B、C 三个顶点的加权平均。P 离哪个点越近,该点的权重就越接近 1;如果三个顶点的权重都是 1/3,P 就是三角形的重心。

它也可以用于计算三角形内任何一点的颜色,例如在做渲染时,已知三角形三个顶点的颜色,那么它内部任何一点的颜色,就是三个顶点颜色的加权平均,权重如公式 (5) 所示。

在判断遮挡关系的 z-buffer 算法中,希望求出图片上每个像素点的深度,此时也需要用到 (5):

我们很容易知道图片上某个像素点的横、纵坐标。那么就可以把它所在的小三角形三个顶点的横、纵坐标,代入公式 (5) 联立求出 u、v 的值:虽然有三个坐标,但公式 (5) 只有两个未知数,因此知道横、纵坐标足以解出 u 和 v。求解的算法还是 Barycentric Technique 的伪代码,只不过此时传入的都是去掉 z 坐标的二维点。

三个顶点的 z 值是知道的,结合刚刚求出的 u、v 值,再代回 (5) 就求出了该像素点的 z 值。

后记

我是图形学的外行,本来这就是一篇学习笔记/备忘录,没想到在和评论区的很多大神的讨论中,学习到那么多新东西,感谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值