如何正确处理光线追踪中的折射
How to properly handle refraction in raytracing
我目前正在研究光线追踪器只是为了好玩,但我在折射处理方面遇到了麻烦。
可以找到整个raytracer的代码源on Github EDIT: The code migrated to Gitlab。
这是渲染图:
右侧球体的折射率设置为 1.5(玻璃)。
在折射之上,我想处理一个 "transparency" 系数,其定义如下:
- 0 --> 对象 100% 不透明
- 1 --> 对象是 100% 透明的(没有原始对象颜色的痕迹)
这个球体的透明度为 1。
这是处理折射部分的代码。可以查到on github here.
Color handleTransparency(const Scene& scene,
const Ray& ray,
const IntersectionData& data,
uint8 depth)
{
Ray refracted(RayType::Transparency, data.point, ray.getDirection());
Float_t eta = data.material->getRefraction();
if (eta != 1 && eta > Globals::Epsilon)
refracted.setDirection(Tools::Refract(ray.getDirection(), data.normal, eta));
refracted.setOrigin(data.point + Globals::Epsilon * refracted.getDirection());
return inter(scene, refracted, depth + 1);
}
// http://graphics.stanford.edu/courses/cs148-10-summer/docs/2006--degreve--reflection_refraction.pdf
Float_t getFresnelReflectance(const IntersectionData& data, const Ray& ray)
{
Float_t n = data.material->getRefraction();
Float_t cosI = -Tools::DotProduct(ray.getDirection(), data.normal);
Float_t sin2T = n * n * (Float_t(1.0) - cosI * cosI);
if (sin2T > 1.0)
return 1.0;
using std::sqrt;
Float_t cosT = sqrt(1.0 - sin2T);
Float_t rPer = (n * cosI - cosT) / (n * cosI + cosT);
Float_t rPar = (cosI - n * cosT) / (cosI + n * cosT);
return (rPer * rPer + rPar * rPar) / Float_t(2.0);
}
Color handleReflectionAndRefraction(const Scene& scene,
const Ray& ray,
const IntersectionData& data,
uint8 depth)
{
bool hasReflexion = data.material->getReflexion() > Globals::Epsilon;
bool hasTransparency = data.material->getTransparency() > Globals::Epsilon;
if (!(hasReflexion || hasTransparency) || depth >= MAX_DEPTH)
return 0;
Float_t reflectance = data.material->getReflexion();
Float_t transmittance = data.material->getTransparency();
Color reflexion;
Color transparency;
if (hasReflexion && hasTransparency)
{
reflectance = getFresnelReflectance(data, ray);
transmittance = 1.0 - reflectance;
}
if (hasReflexion)
reflexion = handleReflection(scene, ray, data, depth) * reflectance;
if (hasTransparency)
transparency = handleTransparency(scene, ray, data, depth) * transmittance;
return reflexion + transparency;
}
Tools::Refract
只是在内部调用 glm::refract
。 (以便我可以根据需要轻松更改)
我不处理 n1
和 n2
的概念:n2
被认为总是 1 表示空气。
我是不是漏掉了什么明显的东西?
编辑
在添加了一种方法来了解光线是否在物体内部(如果是,则取反法线)之后,我得到了这个:
在四处寻找帮助时,我偶然发现了 this post,但我认为答案没有任何答案。看完了,完全不明白自己应该做什么。
编辑 2
我已经尝试了很多东西,我目前处于这一点:
好多了,但我仍然不确定它是否正确。我正在使用这张图片作为灵感:
但是这个使用了两个折射率(为了更接近现实),而我想简化并始终将空气视为第二个(进或出)material。
我在代码中所做的实质性更改在这里:
inline Vec_t Refract(Vec_t v, const IntersectionData& data, Float_t eta)
{
Float_t n = eta;
if (data.isInside)
n = 1.0 / n;
double cosI = Tools::DotProduct(v, data.normal);
return v * n - data.normal * (-cosI + n * cosI);
}
这是相同球体的另一个视图:
我是作为物理学家而不是程序员来回答这个问题的,因为我没有时间阅读所有代码,所以不会给出代码来修复一般的想法。
从你上面所说的黑环是当 n_object 小于 n_air 时。这通常只有当你在一个物体内部时才正确,比如你在水中或类似的东西中,但是材料已经被构造成具有类似的奇怪特性并且应该得到支持。
在这种情况下,存在无法衍射的光线,因为衍射公式将折射光线放在材料之间界面的同一侧,这显然没有衍射意义。在这种情况下,表面会像反射表面一样起作用。这就是常说的全内反射的情况。
如果完全准确,那么几乎所有折射物体也会部分反射,并且反射或透射(因此折射)的光的比例由 Fresnel equations 给出。对于这种情况,如果角度太远,则仅将其视为反射,否则仍然是一个很好的近似值,否则视为透射(因此折射)。
还有一些情况下,如果无法反射(因为在那些方向上很暗)但可以透射光,则可以看到这种黑环效应。这可以通过拿一管紧密贴合物体边缘并直接指向外并且只在管内而不是外部发光的卡片来完成。
编辑:我发现之前的版本并不完全正确,所以我编辑了答案。
在阅读了所有评论、问题的新版本并自己做了一些实验后,我制作了以下版本的 refract
例程:
float3 refract(float3 i, float3 n, float eta)
{
eta = 2.0f - eta;
float cosi = dot(n, i);
float3 o = (i * eta - n * (-cosi + eta * cosi));
return o;
}
这次调用不需要额外的操作:
float3 refr = refract(rayDirection, normal, refrIdx);
我唯一不确定的是在进行内部光线相交时折射率的反转。在我的测试中,无论我是否反转索引,生成的图像都没有太大差异。
下面是一些不同索引的图片:
有关更多图片,请参阅 link,因为该站点不允许我在此处放置更多图片。
我目前正在研究光线追踪器只是为了好玩,但我在折射处理方面遇到了麻烦。
可以找到整个raytracer的代码源on Github EDIT: The code migrated to Gitlab。
这是渲染图:
右侧球体的折射率设置为 1.5(玻璃)。
在折射之上,我想处理一个 "transparency" 系数,其定义如下:
- 0 --> 对象 100% 不透明
- 1 --> 对象是 100% 透明的(没有原始对象颜色的痕迹)
这个球体的透明度为 1。
这是处理折射部分的代码。可以查到on github here.
Color handleTransparency(const Scene& scene,
const Ray& ray,
const IntersectionData& data,
uint8 depth)
{
Ray refracted(RayType::Transparency, data.point, ray.getDirection());
Float_t eta = data.material->getRefraction();
if (eta != 1 && eta > Globals::Epsilon)
refracted.setDirection(Tools::Refract(ray.getDirection(), data.normal, eta));
refracted.setOrigin(data.point + Globals::Epsilon * refracted.getDirection());
return inter(scene, refracted, depth + 1);
}
// http://graphics.stanford.edu/courses/cs148-10-summer/docs/2006--degreve--reflection_refraction.pdf
Float_t getFresnelReflectance(const IntersectionData& data, const Ray& ray)
{
Float_t n = data.material->getRefraction();
Float_t cosI = -Tools::DotProduct(ray.getDirection(), data.normal);
Float_t sin2T = n * n * (Float_t(1.0) - cosI * cosI);
if (sin2T > 1.0)
return 1.0;
using std::sqrt;
Float_t cosT = sqrt(1.0 - sin2T);
Float_t rPer = (n * cosI - cosT) / (n * cosI + cosT);
Float_t rPar = (cosI - n * cosT) / (cosI + n * cosT);
return (rPer * rPer + rPar * rPar) / Float_t(2.0);
}
Color handleReflectionAndRefraction(const Scene& scene,
const Ray& ray,
const IntersectionData& data,
uint8 depth)
{
bool hasReflexion = data.material->getReflexion() > Globals::Epsilon;
bool hasTransparency = data.material->getTransparency() > Globals::Epsilon;
if (!(hasReflexion || hasTransparency) || depth >= MAX_DEPTH)
return 0;
Float_t reflectance = data.material->getReflexion();
Float_t transmittance = data.material->getTransparency();
Color reflexion;
Color transparency;
if (hasReflexion && hasTransparency)
{
reflectance = getFresnelReflectance(data, ray);
transmittance = 1.0 - reflectance;
}
if (hasReflexion)
reflexion = handleReflection(scene, ray, data, depth) * reflectance;
if (hasTransparency)
transparency = handleTransparency(scene, ray, data, depth) * transmittance;
return reflexion + transparency;
}
Tools::Refract
只是在内部调用 glm::refract
。 (以便我可以根据需要轻松更改)
我不处理 n1
和 n2
的概念:n2
被认为总是 1 表示空气。
我是不是漏掉了什么明显的东西?
编辑
在添加了一种方法来了解光线是否在物体内部(如果是,则取反法线)之后,我得到了这个:
在四处寻找帮助时,我偶然发现了 this post,但我认为答案没有任何答案。看完了,完全不明白自己应该做什么。
编辑 2
我已经尝试了很多东西,我目前处于这一点:
好多了,但我仍然不确定它是否正确。我正在使用这张图片作为灵感:
但是这个使用了两个折射率(为了更接近现实),而我想简化并始终将空气视为第二个(进或出)material。
我在代码中所做的实质性更改在这里:
inline Vec_t Refract(Vec_t v, const IntersectionData& data, Float_t eta)
{
Float_t n = eta;
if (data.isInside)
n = 1.0 / n;
double cosI = Tools::DotProduct(v, data.normal);
return v * n - data.normal * (-cosI + n * cosI);
}
这是相同球体的另一个视图:
我是作为物理学家而不是程序员来回答这个问题的,因为我没有时间阅读所有代码,所以不会给出代码来修复一般的想法。
从你上面所说的黑环是当 n_object 小于 n_air 时。这通常只有当你在一个物体内部时才正确,比如你在水中或类似的东西中,但是材料已经被构造成具有类似的奇怪特性并且应该得到支持。
在这种情况下,存在无法衍射的光线,因为衍射公式将折射光线放在材料之间界面的同一侧,这显然没有衍射意义。在这种情况下,表面会像反射表面一样起作用。这就是常说的全内反射的情况。
如果完全准确,那么几乎所有折射物体也会部分反射,并且反射或透射(因此折射)的光的比例由 Fresnel equations 给出。对于这种情况,如果角度太远,则仅将其视为反射,否则仍然是一个很好的近似值,否则视为透射(因此折射)。
还有一些情况下,如果无法反射(因为在那些方向上很暗)但可以透射光,则可以看到这种黑环效应。这可以通过拿一管紧密贴合物体边缘并直接指向外并且只在管内而不是外部发光的卡片来完成。
编辑:我发现之前的版本并不完全正确,所以我编辑了答案。
在阅读了所有评论、问题的新版本并自己做了一些实验后,我制作了以下版本的 refract
例程:
float3 refract(float3 i, float3 n, float eta)
{
eta = 2.0f - eta;
float cosi = dot(n, i);
float3 o = (i * eta - n * (-cosi + eta * cosi));
return o;
}
这次调用不需要额外的操作:
float3 refr = refract(rayDirection, normal, refrIdx);
我唯一不确定的是在进行内部光线相交时折射率的反转。在我的测试中,无论我是否反转索引,生成的图像都没有太大差异。
下面是一些不同索引的图片:
有关更多图片,请参阅 link,因为该站点不允许我在此处放置更多图片。