《Ray Tracing From The Ground Up》Chapter3

Bare-Bones Ray Tracing

这一章内容是写一个最简单的光线追踪器,我们trace plane 和 sphere 就可以了。

本篇文章主要是整理代码结构,具体图形学知识一定要看书,书上讲的很清楚。

本章的光线为平行光,起点都在viewplane,方向为z轴负方向。我们把要trace的物体放在viewplane后面。显示被光线击中并且离viewplane最近的物体的颜色。



本书作者给的代码实现中包含了很多类,所以理解起来有点困难。并且因为作者给的代码不能直接运行(编译会报错),我重新按照作者的结构实现了一下,不过去掉了Point3D这个类,所有三维的点都用Vector3D表示。输出的图像为PPM格式,代码运行环境为Archlinux,没在windows环境下测试过。在此整理一下我的代码结构:

首先看下有main的主代码:

#include "World.h"

#include <fstream>
#include <iostream>
using namespace std;
ofstream out;
int main()
{
    out.open("fileppm.ppm", ios::out);
    out << "P3\n"
        << 400 << " " << 400 << "\n255\n";
    World w;
    w.build();
    w.render_scene();

    out.close();
    return 0;
}

World就是所有类的一个主体,我们在World的成员函数里实现build函数(初始化所有东西),然后调用render_scene来绘制图像。

class World {
  public:
    ViewPlane vp;
    RGBColor background_color;
    Sphere sphere;
    Tracer *tracer_ptr;
    std::vector<GeometricObject *> objects;

    World();
    void build();
    void add_object(GeometricObject *object_ptr);
    ShadeRec hit_bare_bones_objects(const Ray &ray) const;
    void render_scene() const;
    void display_pixel(const RGBColor &pixel_color) const;
    ~World();
};



接下来看看build都初始化了一些什么:
void World::build()
{
    vp.set_hres(400);
    vp.set_vres(400);
    vp.set_pixel_size(1);
    vp.set_gamma(1.0);
    background_color = blue;

    //指针指向的对象是一个球体,trace_ray函数即为singlesphere中的函数
    //tracer_ptr = new SingleSphere(this);
    //sphere.set_center(0.0);
    //sphere.set_radius(200);

    tracer_ptr = new MultipleObjects(this);
    Sphere *sphere_ptr = new Sphere;
    sphere_ptr->set_center(-10, -40, 0);
    sphere_ptr->set_radius(100.0);
    sphere_ptr->set_color(1.0, 0.0, 0.0);
    add_object(sphere_ptr);

    sphere_ptr = new Sphere(Vector3D(0, 60, 0), 80.0);
    sphere_ptr->set_color(1.0, 1.0, 0.0);
    add_object(sphere_ptr);

    Plane *plane_ptr = new Plane;
    plane_ptr->a = Vector3D(0.0);
    plane_ptr->nor = Vector3D(0.6, 0.3, 0.7);
    plane_ptr->set_color(0.0, 0.30, 0.0);
    add_object(plane_ptr);
}

注意被注释掉的一部分是只画一个Sphere的代码,这里我们实现多个物体渲染(MultipleObjects)。可以大致明白先初始化了Viewplane,然后通过tracer_ptr新建一个MultipleObjects对象,然后通过add_object加入三个物体的指针,初始化完成。

一、添加Objects

这里非常让人迷惑,Viewplane还是比较好理解的,我们透过这个plane去看这个世界,因此不多讲。

再看tracer_ptr,它的类型为Tracer*,可以把它看作我们追踪的所有物体的一个父类。我们的MultipleObjects就是Trace的子类。

那么Tracer到底干了些什么?看看目前的Tracer类:

class World;
class Tracer {
  public:
    Tracer(void);

    Tracer(World *world_ptr);

    virtual ~Tracer(void);

    virtual RGBColor trace_ray(const Ray &ray) const;

  public:
    World *world_ptr;
};

注意:

  public:
    World *world_ptr;
这代表这我们初始化一个Tracer,需要一个World型的指针。这也就代表了,一个Tracer,与一个World关联起来了。我们新建了一个World以后,把它的指针传递给Tracer对象,我们就可以利用tracer_ptr对这个World里的物体进行渲染。

最开始的是一个前置声明,因为Tracer类是比World类先定义的,但是我们需要用到World,所以:

class World;

Tracer里的虚函数得在继承它的类里重新实现,这里的tracer_ray就是核心功能实现的函数。

我们看MultipleObjects:

class MultipleObjects : public Tracer {
  public:
    MultipleObjects(void);

    MultipleObjects(World *_worldPtr);

    virtual ~MultipleObjects(void);

    virtual RGBColor trace_ray(const Ray &ray) const;
};

与此对应的有SingleSphere:

class SingleSphere : public Tracer {
  public:
    SingleSphere(void);

    SingleSphere(World *_worldPtr);

    virtual ~SingleSphere(void);

    virtual RGBColor trace_ray(const Ray &ray) const;
};

接下来我们先回到build函数里写的

tracer_ptr = new MultipleObjects(this);

也就是说我们现在用的是MultipleObjects的trace方法。

然后我们看看如何实现多个物体的追踪。首先想到的肯定是vector容器,每条光线都要按顺序遍历一下这个vector,如果有物体被hit,那么选离viewplane最近的那个物体,显示它的颜色。

于是我们有了:

std::vector<GeometricObject *> objects;

GeometricObject就是所有被追踪物体的父类,Plane,Sphere都继承自它。

class GeometricObject {
  public:
    RGBColor color;

    GeometricObject();
    GeometricObject(const GeometricObject &obj);
    GeometricObject &operator=(const GeometricObject &rhs);

    virtual bool hit(const Ray &ray, double &t, ShadeRec &sr) const = 0;
    virtual ~GeometricObject();

    void set_color(const RGBColor &c);
    void set_color(const float r, const float g, const float b);
    RGBColor get_color(void);
};

我们调用vector的push_back函数把GeometricOject的对象指针加进去。这里我们加入了一个plane和两个Sphere,代码比较好理解。

二、光线追踪

现在我们已经添加好了三个物体,接下来就是设置ray然后检测是否hit。看看render_scene函数

void World::render_scene() const
{
    RGBColor pixel_color;
    Ray ray;
    double zw = 200.0;
    double x, y;

    ray.d = Vector3D(0, 0, -1);
    for (int r = 0; r < vp.vres; r++) {
        for (int c = 0; c < vp.hres; c++) {
            pixel_color = background_color;
            x = vp.s * (c - 0.5 * (vp.hres - 1.0));
            y = vp.s * (r - 0.5 * (vp.vres - 1.0));
            ray.o = Vector3D(x, y, zw);
            pixel_color = tracer_ptr->trace_ray(ray);
            display_pixel(pixel_color);
        }
    }
}

两层循环里的内容需要靠一张图来理解:


如果你对这个图没什么感觉,先看

https://en.wikipedia.org/wiki/Pixel

x = vp.s * (c - 0.5 * (vp.hres - 1.0));
y = vp.s * (r - 0.5 * (vp.vres - 1.0));
每个pixel对应一条光线,光线中心为pixel的中心。

 pixel_color = tracer_ptr->trace_ray(ray);

这句话就是trace的核心。此时tracer_ptr是一个指向MultipleObjects的指针,因此我们得看MultipleObjects的trace_ray函数

RGBColor MultipleObjects::trace_ray(const Ray &ray) const
{
    ShadeRec sr(world_ptr->hit_bare_bones_objects(ray)); // sr is copy constructed

    if (sr.hit_an_object)
        return (sr.color);
    else
        return (world_ptr->background_color);
}

这里又牵扯到一个新的类ShadeRec,意为Shade Record。即着色记录,记录了是否击中,击中的物体颜色,击中点对应光线的参数t等等。hit_bare_bones_objects是World的成员函数,实现如下:

ShadeRec World::hit_bare_bones_objects(const Ray &ray) const
{
    ShadeRec sr(*this);
    double t, tmin = kHugeValue;
    for (size_t j = 0; j < objects.size(); j++) {
        if (objects[j]->hit(ray, t, sr)) {
            if (t < tmin) {
                sr.hit_an_object = true;
                tmin = t;
                sr.color = objects[j]->get_color();
            }
        }
    }
    return sr;
}

这里就非常明了了,即遍历该World里的objects容器里的所有物体,判断.......最后返回该着色记录。

到此只需要在屏幕上显示该点颜色即可。

结果如下(因为我设置的plane并非垂直z轴,是一个倾斜的,因此它会对两个球体的显示有影响,这是一个透视问题,如果你把plane的参数改成与z轴垂直,那么你不会看到紫色的background,只有绿色的plane,以及两个部分重叠的标准圆形,我们还没有开始shading,因此看起来是平面图):



三、源代码及资源

Ray Trace From Ground up pdf

https://github.com/vladotrocol/Graphics/blob/master/Ray%20Tracing%20From%20The%20Ground%20Up.pdf

关于我修改过的代码在此下载(本来是不想要积分的,,为啥自动给我加了):

https://download.csdn.net/download/moon_cy/10483443

输出的图像为PPM格式,代码运行环境为Archlinux,没在windows环境下测试过。鉴于本菜鸡还不会写Makefile,如果需要看结果的,请在命令行输入:

g++ -g Build_World.cpp Vector3D.cpp RGBColor.cpp Ray.cpp GeometryObject.cpp Plane.cpp ViewPlane.cpp ShadeRec.cpp Sphere.cpp Tracer.cpp One_Sphere_to_trace.cpp World.cpp  Multiple_Objects_to_trace.cpp -o q

./q
查看file.ppm即可。

.



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值