【光线追踪系列七】折射与电介质材质

本次光线追踪系列从基础重新开始,主要参照 Ray Tracing in One Weekend ,具体实现代码框架见 https://github.com/RayTracing/raytracing.github.io/。本文只是主要精炼光追相关理论,具体实现可参照原文。

电介质是水,玻璃和钻石等透明材料。当光线射到它们上时,它分裂为反射射线和折射(透射)射线。我们将通过在反射或折射之间随机选择,并且每次交互仅生成一条散射射线来解决这一问题。
在这里插入图片描述

本节将会实现折射向量和电介质材质的光线追踪,例如玻璃材质。

一、折射

对于折射,我们使用:
在这里插入图片描述
具体公式为:
在这里插入图片描述

其中,θ1,θ2 分别是入射角和折射角,η1,η2分别是两种介质的折射率。

已知入射光线向量I和法向量N,折射光线向量T的计算公式如下:
在这里插入图片描述
其中:
在这里插入图片描述
具体推导过程可参见原文。

写成代码如下:

bool refract(const vec3& v, const vec3& n, float ni_over_nt, vec3& refracted) {
    vec3 uv = unit_vector(v);
    float dt = dot(uv, n);
    float discriminant = 1.0 - ni_over_nt*ni_over_nt*(1-dt*dt);
    if (discriminant > 0) {
        refracted = ni_over_nt*(uv - n*dt) - n*sqrt(discriminant);
        return true;
    }
    else
        return false;
}

二、电介质材质

玻璃、水、钻石都是电介质,当光线通过它们内部,会发生折射(refraction),同时也会发生反射(reflection)。
在这里插入图片描述
例如当我们远处看玻璃窗户的时候,基本就是透明的,但当我们贴着窗户看(视线和玻璃平面夹角很小)的时候,窗户能够像镜子一样反射一部分光。
如何计算反射和折射光强的比重呢?菲涅耳方程(Fresnel Formula)可以用来计算描述光在不同介质之间的反射比和透射比,但菲涅尔方程相当复杂,我们可以使用Fresnel-Schlick近似法求反射比的近似解(此处可参见之前PBR及之间的涅菲尔专题相关实现原理):
在这里插入图片描述
代码实现如下:

float schlick(float cosine, float n1_over_n2) {
    float r0 = (1-n1_over_n2) / (1+n1_over_n2);
    r0 = r0*r0;
    return r0 + (1-r0)*pow((1 - cosine),5);
}

2.1 电介质类实现

如果是从里到外入射,即入射向量在法向量另外一侧的情况:
在这里插入图片描述

则要对法向量取反,并颠倒里外的折射率之比:

class dielectric : public material
{
public:
    dielectric(float ri) : ref_idx(ri) {} //n2/n1
    virtual bool scatter(const ray &r_in, const hit_record &rec, vec3 &attenuation, ray &scattered) const
    {
        vec3 outward_normal;
        vec3 reflected = reflect(r_in.direction(), rec.normal);
        float ni_over_nt;
        attenuation = vec3(1.0, 1.0, 1.0);
        vec3 refracted;

        float reflect_prob; //反射概率
        float cosine;

        if (dot(r_in.direction(), rec.normal) > 0) //从里到外,即入射向量在法向量另外一侧的情况
        {
            outward_normal = -rec.normal;   //对法向量取反
            ni_over_nt = ref_idx;           
            cosine = ref_idx * dot(r_in.direction(), rec.normal) / r_in.direction().length();
            //不知道为什么这里要乘一个ref_idx
        }
        else    //从外到里,即入射向量在法向量同一侧
        {
            outward_normal = rec.normal;    //法向量不变
            ni_over_nt = 1.0 / ref_idx;     
            cosine = -dot(r_in.direction(), rec.normal) / r_in.direction().length();
        }

        if (refract(r_in.direction(), outward_normal, ni_over_nt, refracted))
        {
            reflect_prob = schlick(cosine, ref_idx);
        }
        else    //若无折射,则全反射
        {
            reflect_prob = 1.0;
        }

        if (random_double() < reflect_prob)
        {
            scattered = ray(rec.p, reflected);
        }
        else
        {
            scattered = ray(rec.p, refracted);
        }

        return true;
    }

    float ref_idx;
};

场景使用三个电介质模型:

	list[0] = new sphere(vec3(0, 0, -1), 0.4, new dielectric(1.5));
	list[1] = new sphere(vec3(0, -100.5, -1), 100, new lambertian(vec3(0.0, 1.0, 1.0)));//new
	list[2] = new sphere(vec3(1, 0, -1), 0.3, new dielectric(0.5));    //new
	list[3] = new sphere(vec3(-1, 0, -1), 0.3, new dielectric(5.0));    //new

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值