Transparent/noisy 使用简单漫反射计算时的球体

Transparent/noisy spheres when using simple diffuse calculations

我一直在尝试编写一个光线追踪器,但在尝试实现简单的漫反射计算时遇到了一个问题(试图在一个周末光线追踪中复制第一个但没有向导)

相关代码如下:

Intersection/diffuse 计算:

#pragma once

#include "Camera.h"
#include <cmath>
#include "Defs.h"

template<typename O>
class Sphere{
  O Radius;
  Point3<O> Center;
  Color<O> Col;

public:
  Sphere(O radius, Point3<O> center, Color<O> color);

    O quadratic(Ray<O> ray_in, O &disc, O t_Min, O t_Max);
    bool intersect(Ray<O> ray_in, rayInfo<O> &info, O t_Max);
};

template<typename O>
Sphere<O>::Sphere(O radius, Point3<O> center, Color<O> color) : Radius(radius), Center(center), Col(color) {}

template<typename O>
O Sphere<O>::quadratic(Ray<O> ray_in, O &disc, O t_Min, O t_Max){
  Point3<O> origin = ray_in.Origin;
  Vec3<O> direction = ray_in.Direction;

  Vec3<O> o = origin-Center;
  O a = direction.dot(direction);
  O b = 2 * direction.dot(o);
  O c = o.dot(o) - (Radius * Radius);
  O discriminant = b * b - 4 * (a * c);

  if (discriminant < 0){
      return false;
  }

  disc = ((-b - sqrt(discriminant)) / (2 * a));

  if (disc > t_Max || t_Min > disc){
      disc = ((-b + sqrt(discriminant)) / (2 * a));
      if (disc > t_Max || t_Min > disc){
          return false;
      }
  }

  return true;
}

template<typename O>
bool Sphere<O>::intersect(Ray<O> ray_in, rayInfo<O> &info, O t_Max){

    O disc;

    if (quadratic(ray_in, disc, info.Min, t_Max)){

        Point3<O> p = ray_in.at(disc);
        Vec3<O> normal = (p - Center) / Radius;

        info.Point = p;
        info.Normal = normal;
        info.front_face();
        info.Min = disc;

        return true;
    }
    else{
        return false;
    }
}

示踪剂class:

#pragma once

#include <iostream>
#include "Shapes.h"
#include "Defs.h"
#include "Image.h"

template<typename O>
class Tracer{
  std::vector<Sphere<O>> Shapes;

public:
  Tracer(std::vector<Sphere<O>> shapes);
  void iterator(Ray<O> &ray, O &depth, O t_Max, O t_Min);

};

template<typename O>
Tracer<O>::Tracer(std::vector<Sphere<O>> shapes) : Shapes(shapes) {}

template<typename O>
void Tracer<O>::iterator(Ray<O> &ray, O &depth, O t_Max, O t_Min){

  O conc = 1;
  Color<O> col(0.4f, 0.8f, 0.9f);
  bool hit = false;

  rayInfo<O> info;
  info.Min = t_Min;

  if (depth <= 0)
      conc = 0;

  while (depth > 0){

    for (auto i = Shapes.begin(); i != Shapes.end(); i++){

      if (i->intersect(ray, info, t_Max)){
        conc *= 0.28;
        hit = true;
      }
    }

    if (!hit){
      break;
    }

    Vec3<O> circ = Vec3<O>::random_in_unit_sphere();
    Point3<O> target = info.Point + info.Normal + circ;
    ray = Ray<O>(info.Point, target - info.Point);
    info.Min = t_Min;
    hit = false;
    depth--;
  }

  col = col * conc;
  Image<O>::ColorPixel(std::cout, col);
}

主要以防万一:

#include <iostream>
#include <cmath>
#include "../Matrix.h"
#include "Camera.h"
#include <vector>
#include "Image.h"
#include "Shapes.h"
#include "Tracer.h"
#include "../Defs.h"

template<typename O>
using Point3 = Vec3<O>;

template<typename O>
using Color = Vec3<O>;

int main(){
  const int img_width = 640;
  const int img_height = 480;
  const float img_ratio = img_width/img_height;
  float t_Max = infinity; float t_Min = 0.01; float depth = 50.0f;

  float inv_width = 1 / float(img_width);
  float inv_height = 1 / float(img_height);

  std::vector<Sphere<float>> shapes;

  Camera<float> cam1(40.0f, img_ratio, Point3<float>(0.0f, 0.0f, 0.0f), Point3<float>(0.0f, 0.0f, -1.0f), Vec3<float>(0.0f, 1.0f, 0.0f));

  Sphere<float> cir1(0.4f, Point3<float>(0.0f, 0.0f, -1.0f), Color<float>(0.7f, 0.3f, 0.2f));
  Sphere<float> cir2(3.0f, Point3<float>(0.0f, -3.0f, -1.0f), Color<float>(0.2f, 0.7f, 0.8f));
  Sphere<float> cir3(0.5f, Point3<float>(1.0f, 0.0f, -1.0f), Color<float>(0.2f, 0.3f, 0.7f));
  shapes.push_back(cir1);
  shapes.push_back(cir2);
  shapes.push_back(cir3);

  Tracer<float> tracer(shapes);

  std::cout << "P3\n" << img_width << ' ' << img_height << "\n255" << std::endl;

  Ray<float> ray(Point3<float>(0.0f), Vec3<float>(0.0f));

  for (int j = 0; j < img_height; j++)
  {
    std::cerr << "\rScanlines remaining: " << j << ' ' << std::flush;
    for (int i = 0; i < img_width; i++){
        depth = 50.0f;
        float x = i;
        float y = j;

        cam1.screenCords(x, y, img_width, img_height);

        ray = cam1.get_raydir(x, y);
        //ray = Ray<float>(Vec3<float>(x1, y1, 1), Point3<float>(0.0f, 0.0f, 0.0f));
        tracer.iterator(ray, depth, t_Max, t_Min);
    }
  }
  std::cerr << "\n done " << std::endl;
}

这是现在的样子:

编辑:当我在形状循环外衰减时(通过将 `conc *= 0.28 移动到它之外),我的图像最终看起来像这样:

我可以看到一些看起来像阴影的东西,但这显然不是预期的行为。

编辑 2:

正如 Yavok 指出的那样,将 info.Min 设置为每个交点命中的顶点是反向逻辑。相反,我应该减少 info.Max,这样光线就不会一直到达比当前最近的物体更远的物体。

我添加了 3(立方根)的抗锯齿和伽马校正,现在图像看起来好多了。还是有点奇怪,但已经进步了:

编辑 3:

终于成功了!结果我的 random_in_unit_sphere() 函数出错了。它应该看起来像这样:

static Vec3<T> random_in_unit_sphere(){
        bool flag = true;
        Vec3<T> p;
        while (flag){
            p = randomm(-1, 1);
            auto l = p.length();
            if (l * l < 1) { flag = false; }
        }

        return p;

    }

感谢 Yakov 和 Spektre!非常感谢。

懒得调试您的代码,但是屏幕截图和快速查看源代码提示存在准确性问题。所以尝试使用 64 位 doubles 而不是 32 位 floats...

光线和 ellipsoid/sphere 之间的交点往往在浮点数上有噪声...一旦在其上添加折射和反射,噪声就会成倍增加...

有时也有助于使用相对坐标而不是绝对坐标(即使在浮点数上也会产生巨大影响)。有关详细信息,请参阅:

  • ray and ellipsoid intersection accuracy improvement
  • raytrace through 3D mesh

噪声存在是因为您随机化了漫射光线:

Vec3<O> circ = Vec3<O>::random_in_unit_sphere();

每次你的光线击中某物时,你都会衰减颜色:

    conc *= 0.28;

显然,有些光线会比其他光线反射得更多,因此会变暗。

此噪声是任何蒙特卡洛积分器的预期伪影。要减少噪声,您需要增加每个像素的样本数并在非常结束。

“透明度”之所以存在,是因为您在相交循环中应用了该衰减:

for (auto i = Shapes.begin(); i != Shapes.end(); i++){
  if (i->intersect(ray, info, t_Max)){
    conc *= 0.28; // <-- here
    hit = true;
  }
}

与多个球体相交的光线将被多次衰减,即使是应该被遮挡的球体也是如此。相反,当你发现你的光线反弹时,你必须在循环外衰减:

if (!hit){
  break;
}
conc *= 0.28; // <-- apply attenuation once per bounce