You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

【技术求助】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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 04:52:11