今天來(lái)說(shuō)說(shuō)射線和球的相交檢測(cè)。
從圖形來(lái)說(shuō)
![射線和圓相交, origin是射線起點(diǎn)奶甘, dir是射線的方向向量篷店。p0,p1是兩個(gè)交點(diǎn)臭家,center為圓心疲陕,半徑為R,d為圓心到射線的距離][ray-sphere]
我們先以2D切面圖來(lái)說(shuō)明钉赁,當(dāng)射線和圓相交的時(shí)候蹄殃,可以看到,球心 center 到射線 ray 的距離 d <= R你踩,這個(gè)即為相交的條件诅岩。那么射線與球相切就轉(zhuǎn)化為了球心到射線的距離d的判斷。先求出d:
- 設(shè)圓心在射線上的投影為c'带膜,則 origin吩谦,center, c' 形成了一個(gè)直角三角形。
- 獲得射線起點(diǎn)到圓心的向量
Voc = Vcenter - Vorigin
- 在射線方向上的投影為:
Poc= Voc·(Voc·dir)
- 勾股定理:
d·d = Voc·Voc - Poc·Poc
可以求出d的數(shù)值膝藕,
- d < R式廷,射線穿過(guò)圓,與圓有兩個(gè)交點(diǎn)芭挽。
- d = R滑废,射線與圓相切,有一個(gè)交點(diǎn)為切點(diǎn)览绿。
- d > R,射線在圓外穗慕,沒(méi)有交點(diǎn)饿敲。
接下來(lái)求P0,P1:
- c'逛绵,center怀各,P0 or P1點(diǎn)構(gòu)成直角三角形倔韭。
- P0 or P1到c'的距離 tca·tca = R·R - d·d;
- 有如下式子
P0 = dir·( |Poc| - tca );
P1 = dir·( |Poc| + tca );
要注意,沒(méi)有交點(diǎn)的時(shí)候瓢对, tca·tca < 0 是沒(méi)辦法開(kāi)平方的
推導(dǎo)三維情況可以照上面的去做寿酌,dot能保證投影點(diǎn)在同一個(gè)平面上的。
附代碼
bool Intersect(const Ray& ray, const Sphere& sphere, float& t0, float& t1)
{
Vector3 oc = sphere.GetCenter() - ray.GetOrigin();
float projoc = dot(ray.GetDirection(), oc);
if (projoc < 0)
return false;
float oc2 = dot(oc, oc);
float distance2 = oc2 - projoc * projoc;
if (distance2 > sphere.GetRadiusSquare())
return false;
float discriminant = sphere.GetRadiusSquare() - distance2;
if(discriminant < FLOAT_EPSILON)
t0 = t1 = projoc;
else
{
discriminant = sqrt(discriminant);
t0 = projoc - discriminant;
t1 = projoc + discriminant;
if (t0 < 0)
t0 = t1;
}
return true;
}
從方程角度來(lái)看
射線方程:ray : P(t) = O + D·t ( t >= 0 )
球的方程:sphere : sqr( P-C ) = R·R
(sqr(x) = x^2 = x·x)
O=origin, D=direction, C=center, R=radius
射線方程表明的是如下一個(gè)點(diǎn)的集合P硕蛹,當(dāng)t從零增大時(shí), D·t會(huì)沿著D向量的方向從零逐步變長(zhǎng)醇疼,t 取值無(wú)限表示了射線單方向。從O點(diǎn)開(kāi)始在D方向上無(wú)限個(gè)點(diǎn)構(gòu)成了一條射線法焰。
球的方程表明了任何點(diǎn)P秧荆,只要到C點(diǎn)的距離等于半徑R,則表明點(diǎn)在球面上埃仪,這么一個(gè)球面上的點(diǎn)的集合乙濒。
因此當(dāng)射線與球相交的時(shí)候,這個(gè)點(diǎn)既在射線上卵蛉,又在球面上颁股。等式射線的P(t) = 球的P成立。
聯(lián)立兩個(gè)方程傻丝,試著求解 t 有:
sqr( O + D·t - C ) = R·R
設(shè) O-C=OC甘有,有:
sqr( OC+D·t ) - R·R = 0
//展開(kāi)得到如下式子
=> D·D·t·t + 2·OC·D·t + OC·OC - R·R = 0
=> (D·D)·t·t + 2·(OC·D)·t + OC·OC - R·R = 0
因?yàn)?D 是單位向量有D·D = dot(D, D) = 1
最后方程為:
t·t + 2·(OC·D)·t + OC·OC - R·R = 0;
這是一個(gè)關(guān)于 t 的二次方程at^2 + bt + c = 0
那么解就已經(jīng)出來(lái)了:
- t0 = -(b + √Δ) / 2a
- t1 = -(b - √Δ) / 2a
- a = D·D = dot(D, D) = 1;
- b = 2·OC·D = 2·dot(OC, D);
- c = OC·OC - R·R = dot(OC, OC) - R·R;
- 判別式 Δ = sqr(b) - 4ac
= 4·sqr( OC·D ) - 4·( OC·OC - R·R )
= 4·( sqr( OC·D ) - OC·OC + R·R );
如果判別式 Δ > 0,則表明球與射線相交桑滩。
根據(jù)以上方程梧疲,我們其中試著展開(kāi) t 的式子
t0 = -(b + √Δ) / 2a = -(b + √Δ) / 2·1
= -b/2 - √(Δ/4)
= -dot(OC, D) - √( sqr( dot(OC, D) ) - dot(OC, OC) + R·R )
求出 t 后可以根據(jù) P(t) = O + D * t
得到交點(diǎn)。
附代碼
bool Intersect(const Ray& ray, const Sphere& sphere, float& t0, float& t1)
{
Vector3 oc = ray.GetOrigin() - sphere.GetCenter();
float dotOCD = dot(ray.GetDirection(), oc);
if (dotOCD > 0)
return false;
float dotOC = dot(oc, oc);
float discriminant = dotOCD*dotOCD - dotOC + sphere.GetRadiusSquare();
if (discriminant < 0)
return false;
else if (discriminant < FLOAT_EPISLON)
t0 = t1 = -dotOCD;
else
{
discriminant = sqrt(discriminant);
t0 = -dotOCD - discriminant;
t1 = -dotOCD + discriminant;
if (t0 < 0)
t0 = t1;
}
return true;
}
補(bǔ)充一些內(nèi)容:
交點(diǎn)的法線
因?yàn)榻稽c(diǎn)在球面上运准,球面法線反向是從球心指向球面的點(diǎn)幌氮。
設(shè)交點(diǎn)為 IntersecionP,只需要簡(jiǎn)單的計(jì)算:
normal = ( IntersectionP - sphere.GetCenter() ).Normalize();