手写射线球相交检测可控制精度、避免开销并支持simd/gpu优化;需改写为无开方判别式b²≥c(单位方向)或b²≥a·c(非单位),且必须检查t>0以排除反向交点。

为什么不用现成的 glm::intersectRaySphere
因为手写能控制精度、避免临时对象开销,还能嵌入 SIMD 或 GPU shader。但直接套数学公式容易翻车——比如没处理射线原点在球内的情况,或用 sqrt 做判别式开方导致负数崩溃。
核心公式要改写成无开方判别形式
标准二次方程解法会算 t = (-b ± sqrt(b²−4ac)) / (2a),但这里 a 恒为 1(射线方向单位化后),关键其实是判断 b² − c 是否 ≥ 0。其中:
b = dot(ray_dir, ray_origin − sphere_center)c = dot(ray_origin − sphere_center, ray_origin − sphere_center) − sphere_radius²
只要 b*b >= c 就相交;若还要求最近交点 t,则用 t = -b − sqrt(b*b − c)(注意是减号,取更小的正根);但若只测是否相交,完全跳过 sqrt。
必须预处理射线方向是否单位化
公式推导默认 ray_dir 是单位向量。如果传入非单位向量(比如从相机生成的未归一化方向),会导致 b 和 c 计算全错。两种选择:
- 调用前确保
normalize(ray_dir),适合 CPU 端已知方向可复用的场景 - 不归一化,改用通用形式:令
rd = ray_dir,则a = dot(rd, rd),b = dot(rd, ray_origin − sphere_center),c = dot(ray_origin − sphere_center, ray_origin − sphere_center) − r*r,再判b*b >= a*c—— 这种更鲁棒,但多两次点积
多数实时渲染管线选后者,避免归一化误差和除零风险。
边界情况:射线起点在球内怎么处理
当 c (即起点到球心距离小于半径),说明起点已在球内。此时即使 <code>b*b 也可能相交(射线穿出球体)。正确做法是统一用判别式 <code>b*b − a*c:
- 若
b*b − a*c → 不相交 - 若
≥ 0→ 相交,且两个根中较小的t可能为负(起点在球内时),所以实际取交点需加t > 0判断
很多手写版本漏掉 t > 0 检查,导致把“射线反向穿过球”的伪交点也算进来。
最简健壮实现就三行核心计算,但 a、b、c 的定义方式和 t > 0 的时机,才是线上跑崩和稳如老狗的区别。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











