Ray_Tracing_In_One_Weekend上

目标:

使用vscodeIDE编写代码,这是我的配置

学习教程完成简易的光线追踪器开发

1·输出PPM图像

有一种PPM格式的图像,可以通过自定义数据生成简单的图像

PPM Viewer (rhodes.edu)加载网站

首先p3表明颜色是ASCII码格式,接着指定分辨率,和最大颜色值,下面的都是RGB颜色值 ,

我们可以通过代码设置这些数据,通过执行exe编译后的文件,将cont标准输出流的内容写入.ppm的文件里

首先指定p3和分辨率(200宽100高)和255最大颜色值,然后设置每个像素颜色值,首先在0---1之间,然后转为0---255的颜色

对于PPM来说从左往右,从上到下写入的,因此首先输出的数据是左上,最后输出的是右下

#include <iostream>

int main() {

    // Image

    int image_width = 256;
    int image_height = 100;

    // Render

    std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n";

    for (int j = 0; j < image_height; j++) {
        for (int i = 0; i < image_width; i++) {
            auto r = double(i) / (image_width-1);
            auto g = double(j) / (image_height-1);
            auto b = 0.0;

            int ir = int(255.999 * r);
            int ig = int(255.999 * g);
            int ib = int(255.999 * b);

            std::cout << ir << ' ' << ig << ' ' << ib << '\n';
        }
    }
}

我们已经有了所有数据,通过新建终端,编译后,执行命令写入命令

最终效果:

好了我们现在可以显示一个简单的图片,我想要计时器,以便更好的调试,为了输出到终端又不影响ppm文件,我们用std::clog << / std::erro<<,打印一层循环(还剩多少行image_height - j) ,目前速度很快

2·VEC3,color文件

让我们抽象出两个工具文件,用vec3`类3个double,using color = vec3;

vec3 color(double(i) / image_width, double(j) / image_height, 0.2);
color.write_color(std::cout);

3·ray类

作为光线追踪器,必不可少的就是光线,光线隐式公式:

光线是一个射线,a原点,t 时间,b方向 

让我们做一个蓝天背景吧,首先计算屏幕属性

// Image
auto aspect_ratio = 16.0 / 9.0;
int image_width = 400;
// Calculate the image height, and ensure that it's at least 1.
int image_height = int(image_width / aspect_ratio);
image_height = (image_height < 1) ? 1 : image_height;
// Camera
auto focal_length = 1.0;
auto viewport_height = 2.0;
auto viewport_width = viewport_height * (double(image_width)/image_height);
auto camera_center = point3(0, 0, 0);
// Calculate the vectors across the horizontal and down the vertical viewport edges.
auto viewport_u = vec3(viewport_width, 0, 0);
auto viewport_v = vec3(0, -viewport_height, 0);
// Calculate the horizontal and vertical delta vectors from pixel to pixel.
auto pixel_delta_u = viewport_u / image_width;
auto pixel_delta_v = viewport_v / image_height;
// Calculate the location of the upper left pixel.
auto viewport_upper_left = camera_center
                         - vec3(0, 0, focal_length) - viewport_u/2 - viewport_v/2;
auto pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);

然后向每个像素发射光线,通过光线的y值做个简单的线性插值

vec3 ray_color(const ray& r) {/* 根据ray的y值,返回渐变的蓝白色 */
    vec3 unit_direction = unit_vector(r.direction());/* 单位化光线 */
    auto t = 0.5*(unit_direction.y() + 1.0);/* 1---0 */
    return (1.0-t)*vec3(1.0, 1.0, 1.0) + t*vec3(0.5, 0.7, 1.0);/* 从上到下,从蓝到白 */
}

完成效果:

4·渲染球体

接下来我们渲染一个球体吧,使用球体的 隐式表示公式:

这是球心在0,0,0位置的方程,所有球面的点xyz都满足=r^2,如果<r^2则点在内部,否则在外部

如果球心在cx,cy,cz,则

其中r可以写为点p和球心c的向量形式(等价) ,最终的球面方程:

 

那么光线和球体相交,两个方程都满足,即光线为t时间时的坐标,作为点p满足在球体表面

唯一的未知数就是t,即关于t的一个一元二次方程

通过求根公式判断交点、如果存在返回指定颜色

bool hit_sphere(const vec3 &center, double radius, const ray &r)
{
    vec3 oc = r.origin() - center;
    auto a = dot(r.direction(), r.direction());
    auto b = 2.0 * dot(oc, r.direction());
    auto c = dot(oc, oc) - radius * radius;
    auto discriminant = b * b - 4 * a * c;/* 当 b2−4ac>=0 时,表示二次函数与x轴有至少一个交点 */
    return (discriminant > 0);
}

最终效果: 

5·着色

我们如何计算这个球体的法线?(交点 - 球心)

根据法线向量为球体着色

auto t = hit_sphere(vec3(0,0,-1), 0.5, r);
if (t > 0.0) {
    vec3 N = unit_vector(r.at(t) - vec3(0,0,-1));
    return 0.5*vec3(N.x()+1, N.y()+1, N.z()+1);
}

结果:

6·优化,重构代码

现在的main文件有太多杂项,我们把内部抽象出来

项1:求根公式

我们可以把求根公式中的b替换为2h,经过展开移项后

因此,修改后的代码 

double hit_sphere(const vec3 &center, double radius, const ray &r)
{
    vec3 oc = r.origin() - center;
    auto a = r.direction().length_squared();
    auto half_b = dot(oc, r.direction());
    auto c = oc.length_squared() - radius*radius;
    auto discriminant = half_b*half_b - a*c;

    if (discriminant < 0) {
        return -1.0;
    } else {
        return (-half_b - sqrt(discriminant) ) / a;
    }
}

项2:抽象物体继承体系

我们建立一个物体基类叫做hittable,其中STRUCT hit_record记录交点的信息,hit()虚函数用来判断是否有交点

建立一个sphere派生类继承hittable,

为了满足多物体绘制,不要忘记深度测试

项3:法线方向

我们想要物体在内外方向都有法线,因此如果光线和法线dot>0即同方向即光线从球体内部照射,则法线反转,否则不反转

front_face = dot(r.direction(), outward_normal) < 0;/* 光线和法线是否一致 */
normal = front_face ? outward_normal : -outward_normal;/* 不一致反转法线方向 */

项4:物体列表

保存所有物体的vector,每次求交会遍历内部所有物体,如果通过更新交点信息

项5:常用函数和工具

rtweekend.h常用数据和函数类,interval时间间隔类

项6:抽象出camera类

initialize()负责初始化相机,ray_color()计算光线颜色,render()开始渲染世界

现在我们的main文件的大部分内容都抽象到其他文件中了

7·两个球体

利用我们的框架API渲染两个球体吧

    hittable_list world;

    world.add(make_shared<sphere>(point3(0, 0, -1), 0.5));
    world.add(make_shared<sphere>(point3(0, -100.5, -1), 100));

    camera cam;

    cam.aspect_ratio = 16.0 / 9.0;
    cam.image_width = 400;

    cam.render(world);

结果:

8·反走样/抗锯齿

 MSAA:每个像素增加samples_per_pixel个采样点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值