后者可以实现这一点,但是有一个预处理阶段不适合我的CUDA路径跟踪器,我希望它尽可能自然。因此,您有没有想过要过时的论文,或者建议我解决完整的辐射传递方程?
#1 楼
正如评论中提到的那样,我强烈建议从全体积散射开始。这有两个方面:由于您正在执行路径跟踪,因此添加体积并不困难。估计。此外,它可以提供很好的“参考”,以查看您的估计是否运行良好/正确。
考虑到这一点,下面是如何在向后路径中实现全体积散射的基本介绍。跟踪器。
首先,让我们看一下仅反射但无透射/折射的向后路径跟踪器的代码:
void RenderPixel(uint x, uint y, UniformSampler *sampler) {
Ray ray = m_scene->Camera->CalculateRayFromPixel(x, y, sampler);
float3 color(0.0f);
float3 throughput(1.0f);
SurfaceInteraction interaction;
// Bounce the ray around the scene
const uint maxBounces = 15;
for (uint bounces = 0; bounces < maxBounces; ++bounces) {
m_scene->Intersect(ray);
// The ray missed. Return the background color
if (ray.GeomID == INVALID_GEOMETRY_ID) {
color += throughput * m_scene->BackgroundColor;
break;
}
// Fetch the material
Material *material = m_scene->GetMaterial(ray.GeomID);
// The object might be emissive. If so, it will have a corresponding light
// Otherwise, GetLight will return nullptr
Light *light = m_scene->GetLight(ray.GeomID);
// If this is the first bounce or if we just had a specular bounce,
// we need to add the emmisive light
if ((bounces == 0 || (interaction.SampledLobe & BSDFLobe::Specular) != 0) && light != nullptr) {
color += throughput * light->Le();
}
interaction.Position = ray.Origin + ray.Direction * ray.TFar;
interaction.Normal = normalize(m_scene->InterpolateNormal(ray.GeomID, ray.PrimID, ray.U, ray.V));
interaction.OutputDirection = normalize(-ray.Direction);
// Calculate the direct lighting
color += throughput * SampleLights(sampler, interaction, material->bsdf, light);
// Get the new ray direction
// Choose the direction based on the bsdf
material->bsdf->Sample(interaction, sampler);
float pdf = material->bsdf->Pdf(interaction);
// Accumulate the weight
throughput = throughput * material->bsdf->Eval(interaction) / pdf;
// Shoot a new ray
// Set the origin at the intersection point
ray.Origin = interaction.Position;
// Reset the other ray properties
ray.Direction = interaction.InputDirection;
ray.TNear = 0.001f;
ray.TFar = infinity;
// Russian Roulette
if (bounces > 3) {
float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
if (sampler->NextFloat() > p) {
break;
}
throughput *= 1 / p;
}
}
m_scene->Camera->FrameBufferData.SplatPixel(x, y, color);
}
英语:
在场景中射出射线。
检查是否击中任何物体。如果不是,我们将返回天空盒的颜色并断开。
如果这是第一条射线,或者我们刚从镜面反射回来,请检查是否击中了灯光。如果是这样,我们将发光添加到颜色累积中。
对直接照明进行采样。
为下一条光线选择新的方向。我们可以统一执行此操作,也可以根据BRDF进行重要性样本。
评估BRDF并进行累加。
根据我们选择的方向和我们的来源来创建新射线。
[可选]使用俄罗斯轮盘选择是否终止射线。
转到1.。
有关直接光采样的更多详细信息,请参见此答案。
例如,理想的镜面电介质(例如玻璃)的BSDF为:
class IdealSpecularDielectric : public BSDF {
public:
IdealSpecularDielectric(float3 albedo, float ior)
: BSDF(BSDFLobe::Specular, albedo),
m_ior(ior) {
}
private:
float m_ior;
public:
float3 Eval(SurfaceInteraction &interaction) const override {
return m_albedo;
}
void Sample(SurfaceInteraction &interaction, UniformSampler *sampler) const override {
float VdotN = dot(interaction.OutputDirection, interaction.Normal);
float IORo = m_ior;
if (VdotN < 0.0f) {
IORo = 1.0f;
interaction.Normal = -interaction.Normal;
VdotN = -VdotN;
}
float eta = interaction.IORi / IORo;
float sinSquaredThetaT = SinSquaredThetaT(VdotN, eta);
float fresnel = Fresnel(interaction.IORi, IORo, VdotN, sinSquaredThetaT);
float rand = sampler->NextFloat();
if (rand <= fresnel) {
// Reflect
interaction.InputDirection = reflect(interaction.OutputDirection, interaction.Normal);
interaction.SampledLobe = BSDFLobe::SpecularReflection;
interaction.IORo = interaction.IORi;
} else {
// Refract
interaction.InputDirection = refract(interaction.OutputDirection, interaction.Normal, VdotN, eta, sinSquaredThetaT);
interaction.SampledLobe = BSDFLobe::SpecularTransmission;
interaction.IORo = IORo;
}
if (AnyNan(interaction.InputDirection)) {
printf("nan");
}
}
float Pdf(SurfaceInteraction &interaction) const override {
return 1.0f;
}
};
代码中有趣的部分是
Sample()
。光线将在此处选择是反射还是折射。如答案所示,我们如何做到这一点实际上取决于我们。但是,一个明显的选择是根据菲涅耳方程进行采样。对于理想的镜面电介质,只有两个潜在的输出方向:完美折射或完美反射。因此,我们评估菲涅耳并随机选择以等于菲涅耳的比例折射/反射。
媒体
接下来,我们需要讨论媒体,以及媒体如何影响周围的光线反射。正如内森·里德(Nathan Reed)在此答案中所解释的:
我喜欢考虑体积散射的方式是,光子通过介质传播时,每单位长度的相互作用具有一定的概率(得到散射)或吸收)。只要它不相互作用,它就会一直畅通无阻地走下去,而且不会损失能量。距离越大,在该距离内某处进行交互的可能性就越大。每单位长度的相互作用概率是您在方程式中看到的系数$ \ sigma $。通常,我们对散射和吸收概率有不同的系数,因此$ \ sigma = \ sigma_s + \ sigma_a $
每单位长度的概率正是Beer-Lambert定律的由来。将射线段切成无穷小的间隔,将每个间隔作为可能的独立交互位置,然后沿射线进行积分;您将获得交互作用的概率随距离变化的指数分布(速率参数为σσ)。
您可以根据需要从技术上选择事件之间的距离,只要您正确地加权路径光子可以使其在两个相邻事件之间发生而不与介质相互作用的概率。换句话说,介质中的每个路径段的权重因子为$ e ^ {-\ sigma x} $,其中$ x $是段的长度。
在这种情况下,通常距离的一个不错的选择是从指数分布中对其进行重要性采样。换句话说,您设置$ x =-(\ ln \ xi)/ \ sigma $
总结:
穿过介质的射线有可能发生以下情况:
被吸收
被散射
由于这是蒙特卡洛积分,我们可以随意选择相互作用距离。但是,一个不错的选择是从类似于Beer-Lambert的指数分布的重要性样本中进行选择。
我选择使用散射系数来实现这一点,并让Beer-Lambert考虑吸收系数。 br />
class Medium {
public:
Medium(float3 absorptionColor, float absorptionAtDistance)
: m_absorptionCoefficient(-log(absorptionColor) / absorptionAtDistance) {
// This method for calculating the absorption coefficient is borrowed from Burley's 2015 Siggraph Course Notes "Extending the Disney BRDF to a BSDF with Integrated Subsurface Scattering"
// It's much more intutive to specify a color and a distance, then back-calculate the coefficient
}
virtual ~Medium() {
}
protected:
const float3a m_absorptionCoefficient;
public:
virtual float SampleDistance(UniformSampler *sampler, float tFar, float *weight, float *pdf) const = 0;
virtual float3a SampleScatterDirection(UniformSampler *sampler, float3a &wo, float *pdf) const = 0;
virtual float ScatterDirectionPdf(float3a &wi, float3a &wo) const = 0;
virtual float3 Transmission(float distance) const = 0;
};
非散射介质是光子直行的介质,但根据Beer-Lambert的说法,它会沿途衰减:
class NonScatteringMedium : public Medium {
public:
NonScatteringMedium(float3 color, float atDistance)
: Medium(color, atDistance) {
}
public:
float SampleDistance(UniformSampler *sampler, float tFar, float *weight, float *pdf) const override {
*pdf = 1.0f;
return tFar;
}
float3a SampleScatterDirection(UniformSampler *sampler, float3a &wo, float *pdf) const override {
return wo;
}
float ScatterDirectionPdf(float3a &wi, float3a &wo) const override {
return 1.0f;
}
float3 Transmission(float distance) const override {
return exp(-m_absorptionCoefficient * distance);
}
};
各向同性散射介质允许射线在穿过射线时发生散射(反射)事件。射线可以在相互作用点附近的球体的任何方向上反射(因此各向同性)。
反射之间的距离是随机的,基于指数“散布”概率。
class IsotropicScatteringMedium : public Medium {
public:
IsotropicScatteringMedium(float3 absorptionColor, float absorptionAtDistance, float scatteringDistance)
: Medium(absorptionColor, absorptionAtDistance),
m_scatteringCoefficient(1 / scatteringDistance) {
}
private:
float m_scatteringCoefficient;
public:
float SampleDistance(UniformSampler *sampler, float tFar, float *weight, float *pdf) const override {
float distance = -logf(sampler->NextFloat()) / m_scatteringCoefficient;
// If we sample a distance farther than the next intersecting surface, clamp to the surface distance
if (distance >= tFar) {
*pdf = 1.0f;
return tFar;
}
*pdf = std::exp(-m_scatteringCoefficient * distance);
return distance;
}
float3a SampleScatterDirection(UniformSampler *sampler, float3a &wo, float *pdf) const override {
*pdf = 0.25f * M_1_PI; // 1 / (4 * PI)
return UniformSampleSphere(sampler);
}
float ScatterDirectionPdf(float3a &wi, float3a &wo) const override {
return 0.25f * M_1_PI; // 1 / (4 * PI)
}
float3 Transmission(float distance) const override {
return exp(-m_absorptionCoefficient * distance);
}
};
在高散射距离下,该材料的行为几乎类似于非散射介质,因为它在穿过介质之前具有很小的散射概率。
,该材料的行为类似于蜡或玉。
随着散射距离越来越小,我们在介质中传播的机会越来越大。这带来了一些问题:
每射线反弹的数量将爆炸。
如果俄罗斯轮盘赌杀死了射线,或者如果您超过“ maxBounces”,您将“失去”介质内部的任何颜色贡献。
因此,您需要将maxBounces设置为一个较高的数字,并在通常情况下依靠Russian Roulette终止射线。此外,IsotropicScatteringMedium对于表示大多数不透明的材料非常无效。为了获得更好的性能,我们应该使用非各向同性的相位函数,例如Henyey-Greenstein或Schlick。这些使散射沿特定方向偏置。因此,我们可以将它们设置为具有较高的反向散射,从而减少“丢失”的射线的问题。
将它们放在一起
因此,借助这些新信息,如何我们是否修改集成器以了解BSDF和媒体?
首先,我们需要开始跟踪我们当前所用的介质及其折射率(IOR)。
SurfaceInteraction interaction;
interaction.IORi = 1.0f; // Vacuum
Medium *medium = nullptr; // nullptr == vacuum
然后,我们将积分器分为两部分:传输和物质相互作用。
void RenderPixel(uint x, uint y, UniformSampler *sampler) const {
Ray ray = m_scene->Camera->CalculateRayFromPixel(x, y, sampler);
float3 color(0.0f);
float3 throughput(1.0f);
SurfaceInteraction interaction;
interaction.IORi = 1.0f; // Air
Medium *medium = nullptr;
bool hitSurface = false;
// Bounce the ray around the scene
uint bounces = 0;
const uint maxBounces = 1500;
for (; bounces < maxBounces; ++bounces) {
m_scene->Intersect(ray);
// The ray missed. Return the background color
if (ray.GeomID == INVALID_GEOMETRY_ID) {
color += throughput * m_scene->BackgroundColor;
break;
}
// We hit an object
hitSurface = true;
// Calculate any transmission
if (medium != nullptr) {
float weight = 1.0f;
float pdf = 1.0f;
float distance = medium->SampleDistance(sampler, ray.TFar, &weight, &pdf);
float3 transmission = medium->Transmission(distance);
throughput = throughput * weight * transmission;
if (distance < ray.TFar) {
// Create a scatter event
hitSurface = false;
ray.Origin = ray.Origin + ray.Direction * distance;
// Reset the other ray properties
float directionPdf;
float3a wo = normalize(ray.Direction);
ray.Direction = medium->SampleScatterDirection(sampler, wo, &directionPdf);
ray.TNear = 0.001f;
ray.TFar = infinity;
ray.GeomID = INVALID_GEOMETRY_ID;
ray.PrimID = INVALID_PRIMATIVE_ID;
ray.InstID = INVALID_INSTANCE_ID;
ray.Mask = 0xFFFFFFFF;
ray.Time = 0.0f;
}
}
if (hitSurface) {
// Fetch the material
Material *material = m_scene->GetMaterial(ray.GeomID);
// The object might be emissive. If so, it will have a corresponding light
// Otherwise, GetLight will return nullptr
Light *light = m_scene->GetLight(ray.GeomID);
// If this is the first bounce or if we just had a specular bounce,
// we need to add the emmisive light
if ((bounces == 0 || (interaction.SampledLobe & BSDFLobe::Specular) != 0) && light != nullptr) {
color += throughput * light->Le();
}
interaction.Position = ray.Origin + ray.Direction * ray.TFar;
interaction.Normal = normalize(m_scene->InterpolateNormal(ray.GeomID, ray.PrimID, ray.U, ray.V));
interaction.OutputDirection = normalize(-ray.Direction);
interaction.IORo = 0.0f;
// Calculate the direct lighting
color += throughput * SampleOneLight(sampler, interaction, material->bsdf, light);
// Get the new ray direction
// Choose the direction based on the bsdf
material->bsdf->Sample(interaction, sampler);
float pdf = material->bsdf->Pdf(interaction);
// Accumulate the weight
throughput = throughput * material->bsdf->Eval(interaction) / pdf;
// Update the current IOR and medium if we refracted
if (interaction.SampledLobe == BSDFLobe::SpecularTransmission) {
interaction.IORi = interaction.IORo;
medium = material->medium;
}
// Shoot a new ray
// Set the origin at the intersection point
ray.Origin = interaction.Position;
// Reset the other ray properties
ray.Direction = interaction.InputDirection;
if (AnyNan(ray.Direction))
printf("bad");
ray.TNear = 0.001f;
ray.TFar = infinity;
ray.GeomID = INVALID_GEOMETRY_ID;
ray.PrimID = INVALID_PRIMATIVE_ID;
ray.InstID = INVALID_INSTANCE_ID;
ray.Mask = 0xFFFFFFFF;
ray.Time = 0.0f;
}
// Russian Roulette
if (bounces > 3) {
float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
if (sampler->NextFloat() > p) {
break;
}
throughput *= 1 / p;
}
}
if (bounces == maxBounces) {
printf("Over max bounces");
}
m_scene->Camera->FrameBufferData.SplatPixel(x, y, color);
}
用英语:
在场景中射出射线。
检查是否击中任何物体。如果不是,则返回天空盒的颜色并中断。
检查我们当前是否处于介质中(不是处于真空状态)。 />评估介质的传输并累积吞吐量。
如果散射距离小于从射线源到下一个曲面的距离,则进行散射事件。
对介质进行散射方向采样。
检查是否击中了表面(即没有发生散射事件)。
如果这是第一条射线,或者我们刚从镜面反射回来,请检查是否击中了灯光。如果是这样,我们将发光添加到颜色累积中。
对直接照明进行采样。
为下一条光线选择新的方向。我们可以统一执行此操作,也可以根据BRDF进行重要性样本。
评估BRDF并进行累加。
如果我们对材料进行折射,请为下一次反弹更新IOR和介质。
根据我们选择的方向以及我们的来源来创建新的射线。
评论
$ \ begingroup $
感谢您提供了如此惊人的答案!问题是,无论我尝试什么系数,我的输出看起来都像玻璃杯。这是否意味着我在散布零件上做错了?这是输出的顺序:imgur.com/a/gg2u1我只更改了散射系数,同时保持了相同的吸收率
$ \ endgroup $
–穆斯塔法·伊斯克(MustafaIşık)
17年6月12日在7:04
$ \ begingroup $
您确定不是偶然将系数设置为等于散射距离吗?它应该是散射系数=(1 /散射距离)。也是吸收系数= -log(absorptionColor)/吸收距离。最后,您如何得出系数并不重要。也就是说,较低的系数将意味着较少的吸收/散射。较高的系数意味着较小的系数。对于艺术家来说,从距离的角度考虑它会容易一些,因此我们根据距离来反算系数。
$ \ endgroup $
–RichieSams
17年6月12日13:25
$ \ begingroup $
不,参数设置部分相同。我无法解决问题,但如果解决了,我会在这里发表评论。
$ \ endgroup $
–穆斯塔法·伊斯克(MustafaIşık)
17年6月15日在7:19
#2 楼
对于首选使用扩散近似而不是完整体积路径跟踪的情况,Solid Angle发布的方法相当有效: >它是在Arnold渲染引擎,Blender's Cycles和pbrt中实现的,后者是开源的。在PBRT中实现该文件的文件位于:
https://github.com。 com / mmp / pbrt-v3 / blob / 7095acfd8331cdefb03fe0bcae32c2fc9bd95980 / src / core / bssrdf.cpp
评论
$ \ begingroup $
PBRT的实现就像光子束扩散一样。光子束扩散和什么立体角相同?我之所以问是因为,我认为PBD还会在几何图形上进行点采样作为预处理步骤,但是Solid Angle幻灯片却不这样做。
$ \ endgroup $
–穆斯塔法·伊斯克(MustafaIşık)
17年6月14日在12:17
$ \ begingroup $
这里有两个区别:集成功能(BSSRDF)和集成方法。第一个是偶极子,多极子,光子束扩散等功能。第二个是Jensen的层次八叉树或Solid Angle的重要采样射线跟踪方法。
$ \ endgroup $
– Stefan Werner
17年6月21日在8:36
评论
您是要在路径追踪器中估算地下散射,还是要使用全体积散射对其进行精确评估?我读到,模拟全体积散射确实很慢。如果不是那么慢,我想实现它。尽管扩散近似的工作速度更快,但是它们承担了很多事情,或者需要难看的预处理步骤。你以前做过吗?任何建议都会很棒!
它可能很慢,具体取决于实现方式。我在业余路径追踪器中实现了强力各向同性散射介质。范例图片:imgur.com/a/oy9F8。 (FPS /总帧时间在左上角)。使用像Henyey-Greenstein这样的反向散射相位函数可能会使其收敛于更不透明的材料,但是我不确定,因为我没有尝试过。如果没有其他问题,实现完全体积散射是了解近似值试图近似的好方法。
今晚晚些时候回家时,我会尝试写信。同时,这是指向我的路径跟踪器的链接。 github.com/RichieSams/lantern根本没有生产就绪。而是,这只是为了帮助我学习所有内容的数学知识。就是说,它在Apache 2.0下是开源的,因此可以随时对其进行复制和学习。
最后,关于这个主题,我问了两个问题:computergraphics.stackexchange.com/questions/2482/…computergraphics.stackexchange.com/questions/2563/…