【技术求助】C++光线追踪器多球体渲染:最近距离计算异常
光线追踪器多球体渲染异常问题排查
我正在用C++实现光线追踪器,目前无法正确渲染多个球体。核心逻辑是对每个像素投射光线,遍历场景内所有物体判断相交,但输出结果存在异常。尝试过归一化光线方向向量、调整判别式比较逻辑、对比交点根的距离等方法,问题仍未解决。
核心渲染循环代码
for (unsigned int y = 0; y < m_resY; y++) { for (unsigned int x = 0; x < m_resX; x++) { if (x % 2 == 0 && y % 2 == 0) { m_buffer[y * m_resX + x].color = Color(-1, -1, -1); continue; } double u = (double(x) * 2 / (m_resX - 1)) - 1; double v = (double(y) * 2 / (m_resY - 1)) - 1; RayTracer::Ray r = m_camera.ray(u, v); double closestDist = std::numeric_limits<double>::max(); for (auto &obj : m_objects) { if (obj->hits(r)) { if (std::abs(r.getHit().dist) < std::abs(closestDist)) { closestDist = r.getHit().dist; r.setClosestObj(obj.get()); r.getClosestHit() = r.getHit(); } } else if (m_buffer[y * m_resX + x].computed == false) { m_buffer[y * m_resX + x].color = Color(50, 50, 50); } } if (r.getClosestObj() != nullptr) { m_light->computeLight(r, *r.getClosestObj(), m_objects); m_buffer[y * m_resX + x].color = r.m_finalColor; m_buffer[y * m_resX + x].computed = true; } } }
Sphere类的相交判断方法
bool Sphere::hits(Ray &ray) const { Math::Vector3<double> ocp = ray.m_origin - m_center; Math::Vector3<double> oc = Math::Vector3<double>(ocp.x, ocp.y, ocp.z); double a = ray.m_direction.dot(ray.m_direction); double b = 2.0f * oc.dot(ray.m_direction); double c = oc.dot(oc) - m_radius * m_radius; double discriminant = b * b - 4.0f * a * c; if (discriminant < 0) return false; ray.getHit().dist = (-b - sqrt(discriminant)) / (2.0f * a); ray.getHit().hitPosition = ray.m_origin + ray.m_direction * ray.getHit().dist; ray.getHit().normal = ray.getHit().hitPosition - m_center; return true; }
问题分析与修复建议
1. 未过滤相机后方的交点
当前hits方法直接取了第一个根,但这个根可能为负数(表示交点在光线起点的后方,也就是相机背面),会导致错误地将相机后方的球体判定为相交,干扰最近距离计算。
修复:计算两个根后,筛选出最小的正根(加入小epsilon避免精度误差),没有有效正根则返回false:
bool Sphere::hits(Ray &ray) const { Math::Vector3<double> oc = ray.m_origin - m_center; double a = ray.m_direction.dot(ray.m_direction); double b = 2.0 * oc.dot(ray.m_direction); double c = oc.dot(oc) - m_radius * m_radius; double discriminant = b * b - 4.0 * a * c; if (discriminant < 0) return false; double sqrtDiscrim = sqrt(discriminant); double t1 = (-b - sqrtDiscrim) / (2.0 * a); double t2 = (-b + sqrtDiscrim) / (2.0 * a); // 筛选有效正根,排除相机后方或极近的交点 double t = std::numeric_limits<double>::max(); const double epsilon = 1e-6; if (t1 > epsilon) { t = t1; } if (t2 > epsilon && t2 < t) { t = t2; } if (t == std::numeric_limits<double>::max()) { return false; } ray.getHit().dist = t; ray.getHit().hitPosition = ray.m_origin + ray.m_direction * t; ray.getHit().normal = (ray.getHit().hitPosition - m_center).normalized(); // 归一化法线 return true; }
2. 光线状态被重复修改导致逻辑混乱
遍历物体时,每次调用hits都会直接修改传入的ray对象的hit数据,导致后续物体的相交判断基于前一个物体的hit结果,而非光线的原始状态。
修复:修改hits方法,不直接修改ray,而是返回临时的交点结果;或者在遍历每个物体前重置ray的hit状态。更稳妥的方式是重构为返回可选的交点结构体:
// 假设定义了HitResult结构体 struct HitResult { double dist; Math::Vector3<double> hitPosition; Math::Vector3<double> normal; }; bool Sphere::hits(const Ray &ray, HitResult &outHit) const { Math::Vector3<double> oc = ray.m_origin - m_center; double a = ray.m_direction.dot(ray.m_direction); double b = 2.0 * oc.dot(ray.m_direction); double c = oc.dot(oc) - m_radius * m_radius; double discriminant = b * b - 4.0 * a * c; if (discriminant < 0) return false; double sqrtDiscrim = sqrt(discriminant); double t1 = (-b - sqrtDiscrim) / (2.0 * a); double t2 = (-b + sqrtDiscrim) / (2.0 * a); const double epsilon = 1e-6; double t = std::numeric_limits<double>::max(); if (t1 > epsilon) { t = t1; } if (t2 > epsilon && t2 < t) { t = t2; } if (t == std::numeric_limits<double>::max()) { return false; } outHit.dist = t; outHit.hitPosition = ray.m_origin + ray.m_direction * t; outHit.normal = (outHit.hitPosition - m_center).normalized(); return true; }
然后更新主循环,用临时变量存储每个物体的交点,避免污染光线对象:
for (unsigned int y = 0; y < m_resY; y++) { for (unsigned int x = 0; x < m_resX; x++) { if (x % 2 == 0 && y % 2 == 0) { m_buffer[y * m_resX + x].color = Color(-1, -1, -1); continue; } double u = (double(x) * 2 / (m_resX - 1)) - 1; double v = (double(y) * 2 / (m_resY - 1)) - 1; RayTracer::Ray r = m_camera.ray(u, v); double closestDist = std::numeric_limits<double>::max(); const Object* closestObj = nullptr; HitResult closestHit; for (auto &obj : m_objects) { HitResult tempHit; if (obj->hits(r, tempHit)) { if (tempHit.dist < closestDist) { closestDist = tempHit.dist; closestObj = obj.get(); closestHit = tempHit; } } } if (closestObj != nullptr) { r.setClosestObj(closestObj); r.setClosestHit(closestHit); m_light->computeLight(r, *closestObj, m_objects); m_buffer[y * m_resX + x].color = r.m_finalColor; m_buffer[y * m_resX + x].computed = true; } else { // 所有物体都不相交时统一设置背景色 m_buffer[y * m_resX + x].color = Color(50, 50, 50); m_buffer[y * m_resX + x].computed = true; } } }
3. 背景色设置逻辑错误
原代码中只要有一个物体不相交就设置背景色,若后续有物体相交,虽然最终会覆盖颜色,但逻辑冗余且容易出错。应改为遍历完所有物体后,若没有找到任何相交物体,再设置背景色。
4. 浮点数精度与类型混用问题
代码中混用了double和float(如2.0f),可能导致精度损失。建议统一使用double,将所有2.0f改为2.0。
5. 法线未归一化
原hits方法中计算的法线未归一化,会导致后续光照计算(如漫反射、镜面反射)结果异常,必须归一化法线向量。
内容的提问来源于stack exchange,提问作者Xscreade
相关产品推荐
相关产品推荐

