[光线追踪] 08 -- 更多相机
在之前已经实现了最基本的针孔相机, 现在就来进行一个其他相机的实现.

平行相机
这里为了名称统一, 就把使用平行投影的相机称作平行相机了. 平行投影可以看作视点位于无限远处的立体投影, 但是直接把无穷引入计算里会导致不少问题, 所以需要给平行相机单独做一个类型.
在立体相机 (也就是针孔相机) 里, d 解释为视平面中心对于视点的相对位置, 所以视方向也等于 d. 但在平行相机里, 因为视点位置不参与计算, 所以视平面中心需要世界坐标表示, 而视方向也需要另外给出. 那么平行相机可以实现为

正如针孔相机里说过那样, 要手动给出纹理坐标系 u, v 是非常不直观的做法, 所以平行相机也应该给出一个比较直观的构造方法. 在针孔相机里存在 fov (视野范围) 这一项, 但因为平行相机的理论视点在无限远处, 所有非零的 fov 都会使视平面变为无限大. 所以在平行相机里使用视平面宽度代替 fov 项, 那么比较直观的构造方法为:

然后构建一个场景进行渲染, 这个场景也是下面所有相机的渲染场景:



在这里需要注意到右下角的黑色圆形: 尽管理论上平行相机的视点是在无限远, 但为了避免无穷, 实际上光线的起点是在视平面上的, 所以在视平面背后的物体是不可见的. 在这幅图里, 视平面刚好有一部分在球体内部, 并且球体是不透明的, 所以造成了渲染图里的一个黑色圆形.

透镜相机 (景深)
景深就是当物体不在相机的焦点 (应该说焦平面) 上时会产生失焦, 从而造成物体的轮廓变得模糊. 有关景深的更多知识可以去 wiki 看看 [https://zh.wikipedia.org/zh-cn/%E6%99%AF%E6%B7%B1].
在光栅化渲染里可以实现伪景深, 但因为这个过程是在已有的渲染输出上进行的, 所以光栅化渲染的景深在细节上是有错误的. 光追可以对相机里的透镜进行建模从而模拟出准确的景深, 下图是一个现实相机的简单模型:

传感器平面与透镜在外部组成了一个虚构的平面, 焦平面, 在焦平面上的所有点都可以完美对焦, 而不在焦平面上的点则会失焦, 如下图所示, 并且距离焦平面越远失焦越严重

可以看到离开焦平面的点在传感器上会形成一片区域, 而不是一个点, 并且这个区域的形状取决于透镜的形状.
与针孔相机类似, 在实现时可以对透镜相机的模型进行化简. 可以看到上面的图里, 以透镜为中心, 左右两边的模型可以看作是相似的, 并且近似地忽略薄透镜的折射, 化简的模型为:

在这个模型里, 传感器即是焦平面, 透镜变为一个圆面, 并且从下面的图例可以看到不在焦平面上的点在传感器上仍然会扩散为一片区域. 那么可以对针孔相机进行修改: 视点位置作为透镜的圆心位置, 那么为了模拟景深, 相机射出的光线起点不再是视点, 而是透镜上的任意点, 剩余部分跟针孔相机相同. 另外, 景深强度正比于透镜的半径, 大概是就是所谓的光圈? 对摄影学一概不通就是了, 所以我直接用 lens_radius 了.

透镜相机大部分参数与针孔相机相同, 这里就不多说了. 下面是透镜相机对应的 get_ray 方法:

里面需要注意的是如何获得在透镜上的采样点, 这里我是直接使用视平面的纹理坐标系 u, v 获得透镜采样点相对于视点的位置. 但如同之前说的那样, 在移轴相机里 u, v 不是与视方向 d 垂直的, 所以这样也会造成透镜不与 d 垂直, 但是我也不能真的搞到一台移轴相机来实验究竟是什么情况就是了. 另外, 如果在对透镜进行采样时不对 u, v 进行归一化的话, 透镜将会是一个与画面长宽比相同的椭圆.
下面简单进行展示:


在这里, 对焦距离在绿球附近, 所以只有绿球是清晰的, 而红球和蓝球是模糊的. 更改对焦距离 (focus distance) 和 透镜半径 可以改变渲染结果:


题外话: 可以留意到红球的边界与绿球之间产生了明显的黑边, 这就是 RGB 空间不准确的地方了, 为了得到正确的色彩, 颜色混合应该在 XYZ 空间内进行. 但这个就是另外的话题了.

鱼眼相机
鱼眼相机是一个非线性投影的相机, 在鱼眼相机里, 视平面不再是一个长方形, 而是球体的一部分, 关于鱼眼相机可以去看看 wiki [https://en.wikipedia.org/wiki/Fisheye_lens].
对于鱼眼相机, 在视点处的局部坐标系 {p; u, v, d} 里, 天顶角 θ 与像素点到图像中心的距离 r 成一定关系, 并且这个关系确定了鱼眼相机的类型: (选自 wiki)

在这里仅实现保角鱼眼相机 (即上图里的 Equidistant) 作为展示, 因为其他类型的鱼眼相机都涉及反三角函数的分类讨论, 这里就不搞这些了. 那么鱼眼相机可以实现为:


get_ray 里的过程就是从像素位置得到到图像中心的距离 r, 然后计算立体角对应的局部坐标 tex, 最后把局部坐标转为全局坐标即是光线的方向 d.
然后进行展示:


可以看到, 鱼眼相机的 fov 可以达到 200°, 从而使在场景里的另外两个球体也出现在了画面中. 实际上, 与针孔相机不同的是, 在这里实现的鱼眼相机是没有视野限制的, 但超过 360° 会非常扭曲, 如下图所示


球极全景相机
全景相机就是可以看到所有方向的相机, fov 为 360° 的鱼眼相机就是一个典型的全景相机. 全景相机的模型多种多样, 这里要实现的是基于球极坐标的简单全景相机.
在球极相机里, 天顶角 θ 和方位角 φ 与 figure 上的纹理坐标 tex_u, tex_v 成一次关系. 在所有相机里, 纹理坐标的值都是在 [-1, 1] 里 (opengl标准 dx 是 [0,1]), 那么则有映射关系: , 剩下的东西就和鱼眼相机里差不多了. 但需要注意的是, 鱼眼相机里的局部坐标为 {u, v, d}, 而在球极相机里为 {d, u, v}, 这是为了保证视方向在图像中心. 那么球极相机实现为:

展示:


因为这里的 fov 已经固定 (360°), 所以当改变长宽比时会造成物体的扭曲:

只有长宽比为2:1时物体扭曲为最小.

项目仓库: https://github.com/nyasyamorina/nyasRT
群: 274767696