在射线追踪器上实现phong着色时,我目前正在尝试不同的 Artifact 。
当我以我认为正确的方式实现镜面照明计算时,将发生第一种情况:累积添加光源的贡献:
specular_color += light_intensity * std::pow (std::max(0.f,reflected*camera_dir),mat.ns);
但是,如果我不积累捐款,可以通过
specular_color = light_intensity * std::pow (std::max(0.f,reflected*camera_dir),mat.ns);
我得到这个:
似乎更接近,但仍带有一些伪像。
打印specular_color变量假定的某些值(即3个float对象),我收到的值高达
对于x和y = 200的像素,添加了+号
没有它,我收到:
所有这些浮点值都用
a [ctr] = min (final_color.blue*255.0f,255.0f);
a [ctr+1] = min (final_color.green*255.0f,255.0f);
a [ctr+2] = min (final_color.red*255.0f,255.0f);
用于文件写入
而final_value只不过是:
final_color = diffuse_color * mat.ks + specular_color * mat.kd;
specular_color的组件(light_intensity,reflected,camera_dir)似乎是正确的,因为它们已在其他地方使用而没有问题。
因此,对于可能存在错误以及如何修复错误的任何建议,我将不胜感激。
最佳答案
如果看一下Phong反射模型的一般定义,它通常是所有光源的总和,其形式为:Σ kd * (L . N) * i + ks * (R . V)^a * i
在这种情况下,kd
和ks
是漫反射和镜面常数,L
,N
和R
是相关 vector ,a
是发光度,i
是当前入射光的强度。您的版本略有不同,因为您可以通过拆分总和并将常量移出的方式来重写它,但这并不是常见的实现方式:kd * Σ ((L . N) * i) + ks * Σ ((R . V)^a * i)
之所以没有那么做,是因为一般的渲染方程是如何工作的,通常是在一个点上半球上的积分形式:Lo(wo) = Le(wo) + ∫ f(wi, wo) * (wi . n) * Li(wi) dwi
在这种情况下Lo
是沿wo
方向的出射辐射率,Le
是该方向上的发射贡献,f
是BRDF,n
是表面的法线,Li
是在入射方向wi
(这是入射光的辐射贡献)正在整合)。实际上,这被实现为求和,再次表明方向上一点上的照明贡献是方向上每个单独照明计算的加权和类型。对于像您这样的具有点光源的简单渲染器,这只是每种光的贡献之和,因为假定光只能来自光源而不是环境本身。这并不是真正的问题,但是如果您计划实现更复杂的照明模型,则必须重新编写程序的结构。
但是,我怀疑的主要问题是,光线跟踪场景中的照明通常是在没有边界的线性空间中进行的,这意味着光不会像您观察到的那样始终保持在0-1的范围内。可以用大于1的值表示光,以区分例如太阳与简单的台灯,或者在您的情况下,许多小灯的组合可能导致组合时表面上的值远大于1。尽管这在渲染过程中不是问题(实际上必须采用这种方式才能获得正确的结果),但是当您决定最终呈现图像时,这是一个问题,因为监视器仅接受8位,或者在更现代的HDR显示的情况下设备,每个通道10位颜色,这意味着您必须以某种方式将场景中辐射的整个浮点范围表示为一个有限得多的整数范围。
从HDR到LDR的过程通常是通过色调映射完成的,色调映射实际上是一种将值的范围压缩成可以“智能”方式呈现的内容的操作,无论可能是什么。色调映射可以纳入各种因素,例如曝光,甚至可以从物理模拟的相机属性(如快门速度,光圈和ISO(因为我们习惯于相机如何捕捉电影和照片中所拍摄的世界))中得出曝光,或者可以粗略地近似于许多视频游戏,或者可以完全忽略。此外,色调映射操作的曲线和“样式”是完全主观的,通常是根据看起来适合所讨论内容的外观选择的,或者是在电影或视频游戏等情况下由艺术家特别选择的,这意味着您可以几乎没有什么选择,因为没有一个正确的答案(同样,它通常基于S形曲线胶卷展品,这是因为相机在媒体中的广泛使用),所以几乎只能选择最适合自己的东西。
即使将值的范围转换为更适合显示输出的值之后,色彩空间仍可能不正确,并且取决于您将其写入显示设备的方式,可能需要通过将值放入OETF(光电传递函数)对输出的电子信号进行编码以用于监视器。通常,您不必担心色彩空间,因为大多数人都在sRGB显示器上工作(Rec。709的细微变化),并直接在其应用程序中使用它,因此除非您不遗余力地创建色彩空间在您的光线追踪器中,除了这些,别无所求。另一方面, Gamma 校正通常必须作为OpenGL,Direct3D或Vulkan等API中的默认帧缓冲区来完成,而如果您正在输出,则通常已经在 Gamma 空间中了(而前面提到的照明数学是在线性空间中完成的)。到图像之类的东西,则根据格式可能不需要它。
不过,作为一个总结,您几乎只需要应用一个色调映射运算符,并可能对最终的色彩输出进行 Gamma 校正,以得到看起来合理的校正。如果您需要快速而肮脏的产品,则可以尝试x / (x + 1)
(也称为Reinhard色调映射),其中x是光线跟踪的输出。如果输出太暗,您也可以将该函数的输入乘以某个任意常数,以进行简单的“曝光”调整。最后,如果您的输出设备在 Gamma 空间中期望有某种东西,那么您可以获取色调映射的输出并将x^(1.0 / 2.2)
函数应用到它(请注意,这是对适当的sRGB OETF的略微简化,但是只要保留该选项就可以使用)请记住)以将其放入 Gamma 空间,不过如果您要输出到图像,通常也不需要这样做,但仍然需要牢记。要注意的另一件事是,色调映射通常会在0-1的范围内输出,因此如果您确实需要乘以255或例如输出图像格式可能期望的任何形式来转换为8位整数,则应该在所有操作之后与之前相比,将其乘以倍数几乎无济于事,除了使场景看起来更加明亮之外。
我还想提及一下,如果您打算将这种光线跟踪器进一步开发为更详细的内容(例如路径跟踪器),则使用Phong照明模型将是不够的,因为它违反了渲染预期的节能特性方程。存在许多BRDF,甚至是一个相对简单的基于Phong的BRDF(进行一些细微的修改即可使其正常运行),因此这种更改将不需要太多的额外代码,但会改善渲染器的视觉保真度并使其更富 future 性证明是否曾经实现过更复杂的行为。
关于c++ - 带phong阴影和光线追踪的伪像,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56469172/