简单的光线追踪教程(三)

简单的光线追踪教程(三)

6. 表面法线和多个物体

6.1. 表面法线阴影

首先,我们先设计一个表面法线,这样我们就可以将球面遮住。我们将法线设计为单位长度,则这样方便计算与移植。

球面法线:

double hit_sphere(const point3& 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;
    if (discriminant < 0) {
        return -1.0;
    } else {
        return (-b - sqrt(discriminant) ) / (2.0*a);
    }
}

color ray_color(const ray& r) {
    auto t = hit_sphere(point3(0,0,-1), 0.5, r);
    if (t > 0.0) {
        vec3 N = unit_vector(r.at(t) - vec3(0,0,-1));
        return 0.5*color(N.x()+1, N.y()+1, N.z()+1);
    }
    vec3 unit_direction = unit_vector(r.direction());
    t = 0.5*(unit_direction.y() + 1.0);
    return (1.0-t)*color(1.0, 1.0, 1.0) + t*color(0.5, 0.7, 1.0);
}

java实现:

double hitSphere(Vec3 center, double radius, Ray r){
   
    Vec3 oc = Vec3.minus(r.getOrigin(), center);
    double a = r.getDirection().lengthSquared();
    double halfB = Vec3.dot(oc, r.getDirection());
    double c = oc.lengthSquared() - radius * radius;
    double discriminant = halfB * halfB - a * c;
    if(discriminant < 0){
   
        return -1.0;
    }
    else{
   
        return (-halfB - Math.sqrt(discriminant)) / a;
    }
}

Vec3 rayColor(Ray r, Hittable world, int depth){
   
    HitRecord rec = new HitRecord();
    if(depth <= 0){
   
        return new Vec3(0, 0, 0);
    }

    if(world.hit(r, 0, Data.infinity, rec)){
   
        Vec3 target = Vec3.add(Vec3.add(rec.getP(), rec.getNormal()), Vec3.randomInUnitSphere());
        Vec3 v1 = rayColor(new Ray(rec.getP(), Vec3.minus(target, rec.getP())), world, depth - 1);
        return Vec3.mulConstant(v1, 0.5);
    }

    Vec3 unitDirection = Vec3.unitVector(r.getDirection());
    double t = 0.5 * (unitDirection.getY() + 1.0);
    Vec3 v1 = new Vec3(1.0, 1.0, 1.0);
    Vec3 v2 = new Vec3(0.5, 0.7, 1.0);
    return Vec3.add(Vec3.mulConstant(v1, (1.0 - t)), Vec3.mulConstant(v2, t));
}

我们就可以得到一个按法向量着色的球体

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rsPgFsc6-1625488104125)(https://raytracing.github.io/images/img-1.04-normals-sphere.png)]

6.2. 简化射线交球的代码

我们回顾一下我们的射线交球方程:

double hit_sphere(const point3& 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;

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

我们可得优化之后的代码为:

double hit_sphere(const point3& 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;
    }
}

java实现:

double hitSphere(Vec3 center, double radius, Ray r){
   
        Vec3 oc = Vec3.minus(r.getOrigin(), center);
        double a = r.getDirection().lengthSquared();
        double halfB = Vec3.dot(oc, r.getDirection());
        double c = oc.lengthSquared() - radius * radius;
        double discriminant = halfB * halfB - a * c;
        if(discriminant < 0){
   
            return -1.0;
        }
        else{
   
            return (-halfB - Math.sqrt(discriminant)) / a;
        }
    }
6.3. 抽象类:可命中对象Hittable

我们用一个抽象类Hittable来表示一个接收射线命中的函数类。

C++实现:

#ifndef HITTABLE_H
#define HITTABLE_H

#include "ray.h"

struct hit_record {
    point3 p;
    vec3 normal;
    double t;
};

class hittable {
    public:
        virtual bool hit(const ray& r, double t_min, double t_max, hit_record& rec) const = 0;
};

java实现:

public interface Hittable {
   
    boolean hit(Ray r, double tMin, double tMax, HitRecord rec);
}

对于球体需要实现可命中类的接口,来完成接收射线的击中,画出图形的功能

#ifndef SPHERE_H
#define SPHERE_H

#include "hittable.h"
#include "vec3.h"

class sphere : public hittable {
    public:
        sphere() {}
        sphere(point3 cen, double r) : center(cen), radius(r) {};

        virtual bool hit(
            const ray& r, double t_min, double t_max, hit_record& rec) const override;

    public:
        point3 center;
        double radius;
};

bool sphere::hit(const ray& r, double t_min, double t_max, hit_record& rec) const {
    vec3 oc = r.origin() - center;
    auto a = r.dire
内容介绍在成像领域,我们有很多手段。比如你可以通过照相机的光学元件,也可以通过像电脑游戏中的那样,通过GPU的一套渲染管线来实现成像。当然除此之外是不是就没有其他的成像方式了呢?当然答案是否定的。 在我们不去使用计算机图形学那套去成像的时候,最土鳖和最容易理解的成像方式就是光线追踪了。这里同学们应该理解到的有一个点,第一光线追踪不是唯一的成像方式,第二它与传统的GPU成像或者说 计算机图形学里说的那些光栅化之类的从思路上就有区别,第光线追踪是最简单的成像方式之一,大概你学完高中数学就可以实现光线追踪,写完两C++类足以做成非常优质的画面。所以同学们要对光线追踪有一个 清晰的认识,不要认为你学完这一套就无敌了,其实你学完了才会发现,这比OpenGL那些一套一套的规则简单多了。 大部分情况下,由于光线追踪不是按照图形学那边的那些管线来做的,所以它不讲究效率,而是遵循物理意义上的画质最佳。所以基本上你学会光线追踪,且不从事电影行业或者不学习引擎内核去研发高端引擎,那么这块知识估计你会带进坟墓。适合人群光线追踪适合于那些探究画质的同学,你可以轻松的把你的思维应用到你的算法中,但大概率无法转化成为实时算法,也就是无法转化成传统渲染管线这边的一套一套的东西。因为仿真从算法出发点上就是不考虑效率的。 你可以用光线追踪去渲染一些精致的画面,如果你是学习了游戏引擎了的话,你可以尝试自己写一个光线追踪的渲染器,来执行烘焙场景的操作。大部分情况下,通用引擎会使用AutoDesk的Beast SDK,比如Unity3D 里面就有beast.exe。如果你是游戏引擎的内核程序员,那么你有可能将你光线追踪和离线渲染学来的知识通过烘焙场景的方式来应用到你的实际工作中。光线追踪的地位在实时渲染领域中使用光线追踪的算法的探索当然也有人在做,这其中最厉害的当然就是Unreal,值得我们学习。如果你在你的引擎内核里使用了像vulkan这样的高级别渲染器,兼容性会差一点,但是你此时 就可以学习Unreal做光线追踪的思路,在实时渲染中,去或多或少加一点光线追踪。我们可以来思考这样的一个问题,实时渲染追求的是速度与性能,离线渲染追求的是极限画质。于是乎那些大神,或许未来你 就是这些大神中的某一个,你们做的操作莫过于把离线渲染算法中的某一部分比较烧性能的环节,比如通过IBL的方式事先通过离线渲染把所有渲染数据存储到一张图像里去,然后在实时渲染的时候把这张图片 中的数据取出来直接运算,就可以得到比实时渲染好,但是比离线渲染差那么一点点画质。这里之所以无法让实时渲染和离线渲染的画质完全一致是因为我们的3D世界就如同我们的眼球一样精度是很高的。如果你的 图片的分辨率不够大,离线渲染的时候存储的数据都是比较粗糙的采样数据,无法描绘出一个精致的世界。课程安排在我们的课程中,我们来通过最简单的方式,依然是最简单的方式来理解光线追踪是怎么玩出来的。画面或许很好看,但都是简单的高中几何数学,即便我们认为你没写过程序都能看懂意思。我们课程里面不涉及 物理渲染,我们使用的依然是经典的lambert这样的光照模型。物理渲染的方式既可以在实时渲染里实现,也可以在离线渲染里实现。大体的框架不会变,只是计算光的时候算法会变,那部分估计也不是美术可以听懂的了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.Youtiao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值