5.2 投影相机模型 - Physically Based Rendering From Theory To Implementation(PBRT)

Physically Based Rendering From Theory To Implementation

翻译:https://pbr-book.org/4ed/Cameras_and_Film/Projective_Camera_Models

总目录: Physically Based Rendering From Theory To Implementation - 基于物理的渲染从理论到实践第四版原书翻译

5.2 Projective Camera Models 投影相机模型

如何将一个三维场景投影到二维图像上进行显示,是计算机三维图形学中的一个基本问题。大多数经典的方法都可以用4x4投影变换矩阵来表示。因此,我们将介绍一个投影矩阵相机类——ProjectiveCamera,然后基于它定义两个相机模型。第一个实现了正投影,另一个实现了透视投影,这是两种常用的经典投影。

<<ProjectiveCamera Definition>>= 
class ProjectiveCamera : public CameraBase {
  public:
    <<ProjectiveCamera Public Methods>> 
  protected:
    <<ProjectiveCamera Protected Members>> 
};

正投影和透视投影都需要指定两个垂直于视点方向的平面:近平面和远平面。当使用光栅化进行渲染时,不在这两个平面之间的对象将被剔除,不包含在最终图像中。(剔除近平面前的对象特别重要,以避免深度0处的奇异,否则投影矩阵映射到摄像机后的点就会显得在摄像机前。)在射线跟踪器中,投影矩阵纯粹用于确定离开相机的射线,这些问题不适用;因此,在这种情况下,不太需要担心这些平面的深度设置。

在定义和讨论投影相机时,还有另外三个坐标系统(见图5 - 2)非常有用。

  • 屏幕空间:屏幕空间定义在电影平面上。摄像机将摄像机空间中的物体投射到胶片平面上;在生成的图像中可以看到屏幕窗口内的部件。近平面的点映射到深度z值0,远平面的点映射到深度z值1。请注意,尽管这被称为“屏幕”空间,但它仍然是一个3D坐标系统,因为值是有意义的。
  • 规范化设备坐标(NDC)空间:这是正在渲染的实际图像的坐标系统。在x和y中,这个空间范围从 ( 0 , 0 ) (0,0) (0,0) ( 1 , 1 ) (1,1) (1,1) ( 0 , 0 ) (0,0) (0,0)是图像的左上角。深度值与屏幕空间相同,并且有一个线性变换将屏幕转换到NDC空间。
  • 栅格空间:这几乎与NDC空间相同,除了x和y坐标范围从 ( 0 , 0 ) (0,0) (0,0)到图像的x和y像素分辨率。

投影相机使用4x4矩阵在所有这些空间之间进行变换。
在这里插入图片描述
图5 - 2: 几个相机相关的坐标空间通常用于简化Camera的实现。camera类保存了它们之间的转换。渲染空间中的场景对象是由摄像机观察的,该摄像机位于摄像机空间的原点并沿+z轴线方向点。在相机空间中,远近平面之间的物体被投影到 z = n e a r z=near z=near 电影平面上。胶片平面在栅格空间中 z = 0 z = 0 z=0 处,那里x和y的范围从 ( 0 , 0 ) (0,0) (0,0) 到以像素为单位的图像分辨率。归一化设备坐标(NDC)空间归一化栅格空间,使x和y范围从 ( 0 , 0 ) (0,0) (0,0) ( 1 , 1 ) (1,1) (1,1)

除了CameraBase类需要的参数外,ProjectiveCamera还需要投影变换矩阵、图像的屏幕空间范围,以及其他与相机聚焦的距离和镜头光圈大小有关的参数。如果镜头光圈不是无穷小的针孔,那么部分图像可能会模糊,就像使用真实镜头系统的失焦物体一样。本节后面将讨论这种效果的模拟。

<<ProjectiveCamera Public Methods>>= 
ProjectiveCamera(CameraBaseParameters baseParameters,
        const Transform &screenFromCamera, Bounds2f screenWindow,
        Float lensRadius, Float focalDistance)
    : CameraBase(baseParameters), screenFromCamera(screenFromCamera),
      lensRadius(lensRadius), focalDistance(focalDistance) {
    <<Compute projective camera transformations>> 
}

ProjectiveCamera的实现将投影变换传递给基类的构造函数。这个变换给出了从摄像机到屏幕的投影;由此,构造函数可以轻松地计算从栅格空间到相机空间的其他变换。

ProjectiveCamera(CameraBaseParameters baseParameters,
        const Transform &screenFromCamera, Bounds2f screenWindow,
        Float lensRadius, Float focalDistance)
    : CameraBase(baseParameters), screenFromCamera(screenFromCamera),
      lensRadius(lensRadius), focalDistance(focalDistance) {
    //<<Compute projective camera transformations>> 
    <<Compute projective camera screen transformations>> 
	cameraFromRaster = Inverse(screenFromCamera) * screenFromRaster;
}
class ProjectiveCamera : public CameraBase {
  public:
    // <<ProjectiveCamera Public Methods>> 
    ...
  protected:
    // <<ProjectiveCamera Protected Members>> 
    Transform screenFromCamera, cameraFromRaster;
};

在构造函数中唯一需要计算的重要变换是raster-from-screen投影。它分两步计算,通过组合从raster-from-NDC 和从NDC-from-screen变换。这里一个重要的细节是坐标y的变换最终被反向;这是必要的,因为增加y值会使图像在屏幕坐标中向上移动,但在栅格坐标中向下移动。

ProjectiveCamera(CameraBaseParameters baseParameters,
        const Transform &screenFromCamera, Bounds2f screenWindow,
        Float lensRadius, Float focalDistance)
    : CameraBase(baseParameters), screenFromCamera(screenFromCamera),
      lensRadius(lensRadius), focalDistance(focalDistance) {
    //<<Compute projective camera transformations>> 
    Transform NDCFromScreen =
	    Scale(1 / (screenWindow.pMax.x - screenWindow.pMin.x),
	          1 / (screenWindow.pMax.y - screenWindow.pMin.y), 1) *
	    Translate(Vector3f(-screenWindow.pMin.x, -screenWindow.pMax.y, 0));
	Transform rasterFromNDC =
	    Scale(film.FullResolution().x, -film.FullResolution().y, 1);
	rasterFromScreen = rasterFromNDC * NDCFromScreen;
	screenFromRaster = Inverse(rasterFromScreen);
	cameraFromRaster = Inverse(screenFromCamera) * screenFromRaster;
}
class ProjectiveCamera : public CameraBase {
  public:
    // <<ProjectiveCamera Public Methods>> 
    ...
  protected:
    // <<ProjectiveCamera Protected Members>> 
    Transform screenFromCamera, cameraFromRaster;
    Transform rasterFromScreen, screenFromRaster;
};

5.2.1 Orthographic Camera 正交相机

正交相机是基于正交变换的相机。正交变换取场景的一个矩形区域,并将其投影到定义该区域的盒子的正面。它不会产生前缩短的效果(物体越远,在图像平面上越小),但它会使平行线保持平行,并保持物体之间的相对距离。图5 - 3展示了这个矩形体如何定义场景的可见区域。

<<OrthographicCamera Definition>>= 
class OrthographicCamera : public ProjectiveCamera {
  public:
    <<OrthographicCamera Public Methods>> 
  private:
    <<OrthographicCamera Private Members>> 
};

在这里插入图片描述
图5.3: 正射视点体是相机空间中一个轴线对齐的盒子,其定义是为了使区域内的物体投射到盒子的 z = n e a r z = near z=near 表面上。

正交摄像机构造函数使用orthographic()函数生成正交变换矩阵,稍后将对其进行定义。

<<OrthographicCamera Public Methods>>= 
OrthographicCamera(CameraBaseParameters baseParameters,
                   Bounds2f screenWindow, Float lensRadius, Float focalDist)
    : ProjectiveCamera(baseParameters, Orthographic(0, 1), screenWindow,
                       lensRadius, focalDist) {
    <<Compute differential changes in origin for orthographic camera rays>> 
    <<Compute minimum differentials for orthographic camera>> 
}

正交视图变换保持x和y坐标不变,但将近平面的z值映射为0,将远平面的z值映射为1。为了做到这一点,场景首先沿着z轴线平移,以便近平面与 z = 0 z=0 z=0 对齐。然后,场景z轴被缩放,以便远平面映射到 z = 1 z=1 z=1。这两个变换的组合给出了整个变换。对于像pbrt这样的射线跟踪器,我们希望近平面位于0,以便光线从穿过相机位置的平面开始;远处飞机的位置并不特别重要。

<<Transform Function Definitions>>+=  
Transform Orthographic(Float zNear, Float zFar) {
    return Scale(1, 1, 1 / (zFar - zNear)) *
           Translate(Vector3f(0, 0, -zNear));
}

由于正投影的简单性,可以很容易地在GenerateRayDifferential()方法中直接计算在x和y方向上的微分射线。差分射线的方向将与主射线相同(就像它们对于由正射影机产生的所有射线一样),并且所有射线的起源差异将是相同的。因此,由于在胶片平面上的x,y两个方向上有单个像素的位移,这里的构造函数预先计算了光线原点在相机空间坐标中的位移。

<<OrthographicCamera Definition>>= 
class OrthographicCamera : public ProjectiveCamera {
	...
private:
    Vector3f dxCamera, dyCamera;
};
OrthographicCamera(CameraBaseParameters baseParameters,
                   Bounds2f screenWindow, Float lensRadius, Float focalDist)
    : ProjectiveCamera(baseParameters, Orthographic(0, 1), screenWindow,
                       lensRadius, focalDist) {
    // <<Compute differential changes in origin for orthographic camera rays>> 
    dxCamera = cameraFromRaster(Vector3f(1, 0, 0));
	dyCamera = cameraFromRaster(Vector3f(0, 1, 0));
    <<Compute minimum differentials for orthographic camera>> 
}

我们现在可以通过代码在栅格空间中获取一个采样点并将其转换为相机光线。图5.5总结了这个过程。首先,将栅格空间样本位置变换为相机空间中的一个点,得到一个位于近平面上的点,即相机光线的原点。由于相机空间观测方向指向z轴,因此相机空间射线方向为(0,0,1)。
在这里插入图片描述
图5.5: 用正射影相机创建一条光线,将胶片平面上的栅格空间位置转换为相机空间,给出光线在近平面上的原点。射线在相机空间中的方向为(0,0,1)。

如果透镜孔径不是针孔,则修改光线的来源和方向,以模拟散焦模糊。最后,光线在返回之前被转换到渲染空间。

<<OrthographicCamera Method Definitions>>= 
pstd::optional<CameraRay> OrthographicCamera::GenerateRay(
        CameraSample sample, SampledWavelengths &lambda) const {
    <<Compute raster and camera sample positions>> 
    Ray ray(pCamera, Vector3f(0, 0, 1), SampleTime(sample.time), medium);
    <<Modify ray for depth of field>> 
    return CameraRay{RenderFromCamera(ray)};
}

一旦所有的变换矩阵都设置好了,就很容易将栅格空间的样本点转换到相机空间。

<<OrthographicCamera Method Definitions>>= 
pstd::optional<CameraRay> OrthographicCamera::GenerateRay(
        CameraSample sample, SampledWavelengths &lambda) const {
    // <<Compute raster and camera sample positions>> 
    Point3f pFilm = Point3f(sample.pFilm.x, sample.pFilm.y, 0);
	Point3f pCamera = cameraFromRaster(pFilm);
	
    Ray ray(pCamera, Vector3f(0, 0, 1), SampleTime(sample.time), medium);
    
    <<Modify ray for depth of field>> 
    return CameraRay{RenderFromCamera(ray)};
}

GenerateRayDifferential()的实现执行了相同的计算去生成主摄像机光线。利用在OrthographicCamera构造函数中计算的偏移量找到差分光线源,然后将全光线差分变换到渲染空间。

<<OrthographicCamera Method Definitions>>+= 
pstd::optional<CameraRayDifferential>
OrthographicCamera::GenerateRayDifferential(CameraSample sample,
        SampledWavelengths &lambda) const {
    <<Compute main orthographic viewing ray>> 
    //<<Compute ray differentials for OrthographicCamera>> 
    if (lensRadius > 0) {
	    <<Compute OrthographicCamera ray differentials accounting for lens>> 
	} 
	else {
	    ray.rxOrigin = ray.o + dxCamera;
	    ray.ryOrigin = ray.o + dyCamera;
	    ray.rxDirection = ray.ryDirection = ray.d;
	}
    ray.hasDifferentials = true;
    return CameraRayDifferential{RenderFromCamera(ray)};
}

5.2.2 Perspective Camera 透视相机

透视投影类似于正投影,因为它将一个空间的体积投射到一个2D电影平面上。然而,它包括了透视的效果:距离较远的物体被投影为比距离较近的相同大小的物体更小。与正投影不同,透视投影不能保持距离和角度,平行线不再保持平行。透视投影与眼睛或相机镜头生成3D世界图像的方式相当接近。

<<PerspectiveCamera Definition>>= 
class PerspectiveCamera : public ProjectiveCamera {
  public:
    // <<PerspectiveCamera Public Methods>> 
    PerspectiveCamera(CameraBaseParameters baseParameters, Float fov,
	                  Bounds2f screenWindow, Float lensRadius, Float focalDist)
	    : ProjectiveCamera(baseParameters, Perspective(fov, 1e-2f, 1000.f),
	                       screenWindow, lensRadius, focalDist) {
	    <<Compute differential changes in origin for perspective camera rays>> 
	    <<Compute cosTotalWidth for perspective camera>> 
	    <<Compute image plane area at  for PerspectiveCamera>> 
	    <<Compute minimum differentials for PerspectiveCamera>> 
	}
  private:
    <<PerspectiveCamera Private Members>> 
};

透视投影描述了对场景的透视观看。场景中的点被投影到垂直于z轴的观察平面上。Perspective()函数计算这个转换;它需要一个fov中的视场角度和近z平面和远z平面的距离(图5.6)。
在这里插入图片描述
图5.6: 透视变换矩阵将摄像机空间中的点投影到近平面上。投影点的x′和y′坐标等于未投影的x′和y′坐标除以z′坐标。这里描述了该操作,其中投影的效果由箭头表示。然后计算投影的z’坐标,以便近平面上的点映射到z’=0,远平面上的点映射到z’= 1。

<<Transform Function Definitions>>+= 
Transform Perspective(Float fov, Float n, Float f) {
    <<Perform projective divide for perspective projection>> 
    <<Scale canonical perspective view to specified field of view>> 
}

最容易理解的转换分为两个步骤:

  1. 相机空间中的点p被投影到观察平面上。一点代数运算表明,可以通过将x和y除以点的z坐标值来计算观察平面上投影的x’和y’坐标。投影的z深度被重新映射,以便近平面的z值为0,远平面的z值为1。我们要做的计算是
    x ′ = x / z y ′ = y / z z ′ = f ( z − n ) z ( f − n ) x' = x/z\\y' = y/z\\z' = \frac{f(z-n)}{z(f-n)} x=x/zy=y/zz=z(fn)f(zn)
    所有这些计算都可以编码成一个4x4矩阵,然后可以应用于齐次坐标:
    在这里插入图片描述
<<Perform projective divide for perspective projection>>= 
SquareMatrix<4> persp(1, 0,           0,              0,
                      0, 1,           0,              0,
                      0, 0, f / (f - n), -f*n / (f - n),
                      0, 0,           1,              0);
  1. 用户指定的角度视场(fov)是通过缩放投影平面上的(x, y)值来计算的,以便视场内的点投影到视图平面上的坐标[-1,1]内。对于正方形图像,x和y都位于屏幕空间的[- 1,1]之间。否则,图像较窄的方向映射到[- 1,1],较宽的方向映射到屏幕空间比例更大的范围。回想一下,正切等于直角三角形对边与邻边之比。这里邻边的长度是1,所以对边的长度是tan(fov/2)。按此长度的倒数缩放将视场映射到范围[- 1,1]。
<<Scale canonical perspective view to specified field of view>>= 
Float invTanAng = 1 / std::tan(Radians(fov) / 2);
return Scale(invTanAng, invTanAng, 1) * Transform(persp);

与OrthographicCamera一样,PerspectiveCamera的构造函数计算生成的光线如何随着像素的移动而变化的信息。在这种情况下,射线源是不变的,射线差只是在它们的方向上不同。在这里,我们计算相机空间中关于像素位置偏移的近透视平面上的位置变化。

<<Compute differential changes in origin for perspective camera rays>>= 
dxCamera = cameraFromRaster(Point3f(1, 0, 0)) -
           cameraFromRaster(Point3f(0, 0, 0));
dyCamera = cameraFromRaster(Point3f(0, 1, 0)) -
           cameraFromRaster(Point3f(0, 0, 0));
<<PerspectiveCamera Private Members>>= 
Vector3f dxCamera, dyCamera;

透视相机的视场的最大角度的余弦偶尔会有用。特别是,通过与视场方向的点积并与该值进行比较,可以快速剔除视场外的点。余弦值可以通过计算摄像机的视点向量与图像某个角的向量之间的夹角得到(参见图5 - 7)。这个角需要稍微调整一下,以考虑以每个像素为中心的滤波函数的宽度,该函数用于根据图像样本的位置对其加权(这个主题将在8.8节讨论)。
在这里插入图片描述
图5 - 7 计算PerspectiveCamera最大视角的余弦,通过使用相机的观看方向作为中心轴,并计算该轴与到图片的一个角落的向量之间的夹角余弦 θ \theta θ,可以找到限制透视相机观看方向的圆锥。在摄像机空间中,这简化为归一化后的向量的分量。

<<Compute cosTotalWidth for perspective camera>>= 
Point2f radius = Point2f(film.GetFilter().Radius());
Point3f pCorner(-radius.x, -radius.y, 0.f);
Vector3f wCornerCamera = Normalize(Vector3f(cameraFromRaster(pCorner)));
cosTotalWidth = wCornerCamera.z;
<<PerspectiveCamera Private Members>>+= 
Float cosTotalWidth;

在透视投影中,相机空间的光线都来源于原点, ( 0 , 0 , 0 ) (0,0,0) (0,0,0)。射线的方向由从原点到近平面上的点pCamera的向量给出,该向量对应于提供的CameraSample的pFilm位置。换句话说,射线向量的方向在分量上等于这个点的位置,因此,与其做无用的减法来计算方向,不如直接从pCamera点初始化方向。

<<PerspectiveCamera Method Definitions>>= 
pstd::optional<CameraRay> PerspectiveCamera::GenerateRay(
        CameraSample sample, SampledWavelengths &lambda) const {
    <<Compute raster and camera sample positions>> 
    Ray ray(Point3f(0, 0, 0), Normalize(Vector3f(pCamera)),
            SampleTime(sample.time), medium);
    <<Modify ray for depth of field>> 
    return CameraRay{RenderFromCamera(ray)};
}

GenerateRayDifferential()方法同样按照GenerateRay()的实现,除了这个计算微分射线的附加片段。

<<Compute offset rays for PerspectiveCamera ray differentials>>= 
if (lensRadius > 0) {
    <<Compute PerspectiveCamera ray differentials accounting for lens>> 
} else {
    ray.rxOrigin = ray.ryOrigin = ray.o;
    ray.rxDirection = Normalize(Vector3f(pCamera) + dxCamera);
    ray.ryDirection = Normalize(Vector3f(pCamera) + dyCamera);
}

5.2.3 The Thin Lens Model and Depth of Field 薄透镜模型和景深

理想的针孔相机只允许光线通过一个点到达胶片,这在物理上是不可实现的;虽然可以使光圈极小的相机达到这种效果,但光圈极小的相机只允许相对较少的光线到达胶片传感器。在小光圈的情况下,需要长时间的曝光才能捕捉到足够多的光子来准确捕捉图像,这反过来会导致在相机快门打开的情况下,场景中移动的物体变得模糊。

真正的相机有镜头系统,通过有限大小的光圈将光线聚焦到胶片平面上。相机设计师(以及使用可调光圈相机的摄影师)面临一个权衡:光圈越大,到达胶片的光线越多,所需的曝光时间越短。然而,镜头只能聚焦在一个平面上(焦平面),场景中的物体离这个平面越远,它们就越模糊。光圈越大,这种效果越明显。

RealisticCamera(仅包含在本书的在线版中)实现了真实世界相机中镜头系统的相当精确的模拟。对于目前介绍的简单相机模型,我们可以应用光学中的经典近似,薄透镜近似,用传统的计算机图形投影模型来模拟有限光圈的效果。薄透镜近似将光学系统建模为具有球面轮廓的单透镜,其中透镜的厚度相对于透镜的曲率半径较小。

在薄透镜近似下,平行于光轴并穿过透镜的入射光线聚焦在透镜后面的一点,称为焦点。焦点在透镜后面的距离, f f f,就是透镜的焦距。如果将胶片平面放置在与镜头后面的焦距相等的距离上,那么无限远的物体将被聚焦,因为它们成像到胶片上的一个点。

图5 - 8展示了基本的设置。在这里,我们遵循了典型的镜头坐标系约定,将镜头垂直于坐标 z z z 轴,镜头在 z = 0 z=0 z=0,场景沿着坐标轴 − z -z z。(注意,这里的坐标系与我们在相机空间中使用的坐标系不同,这里的视角方向是 + z +z +z。)镜头场景侧的距离用未定焦的变量z表示,镜头胶片侧(正z)的距离为定焦, z ′ z' z
在这里插入图片描述
图5 - 8: 一个薄透镜,沿z轴位于 z = 0 z=0 z=0 处。平行于光轴并穿过薄透镜(虚线)的入射光线都要经过一个点p,即焦点。透镜和焦点之间的距离 f f f是透镜的焦距。

对于景深为 z z z 具有焦距 f f f 的薄透镜中的点,高斯透镜方程将物体到透镜的距离以及点的图像到透镜的距离联系起来:
1 z ′ − 1 z = 1 f \frac{1}{z'}-\frac{1}{z} = \frac{1}{f} z1z1=f1
注意:当 z = − ∞ z=-\infty z=,我们由 z ′ = f z'=f z=f

我们可以用高斯透镜方程来求出透镜与将聚焦平面设置在某一处z的胶片之间的距离,即焦距(图5.9):

z ′ = f z f + z z' = \frac{fz}{f+z} z=f+zfz

在这里插入图片描述
图5 - 9: 为了将一个薄透镜聚焦在场景中的深度z处,可以使用式(5.2)来计算z点在镜头胶片侧聚焦的距离 z ′ z' z。聚焦是通过调整镜头和胶片平面之间的距离来实现的。

不位于焦点平面上的点成像到胶片平面上的圆盘上,而不是单个点。这个圆盘的边界被称为混淆圈。混淆圈的大小受光线通过的光圈直径、焦距以及物体与透镜之间的距离的影响。虽然混淆圈在单一深度下半径为0,但附近深度范围内的混淆圈足够小,它们看起来仍然是焦点。(只要它的混淆圈小于像素之间的间距,一个点就会有效地看起来是聚焦的。)聚焦时出现的深度范围称为景深。

图5 - 10展示了水彩画场景中的这种效果。随着镜头光圈的增大,离焦点平面越远的点,模糊度越高。注意,中心的铅笔杯在所有图像中都保持焦点,因为焦点平面被放置在它的深度。图5 - 11展示了用于渲染风景场景的景深。请注意这个效果是如何将观众的眼睛吸引到图像中心的聚焦中的草地上的。

在这里插入图片描述
图5.10 (a) 无景深
在这里插入图片描述
图5.10 (b) 小透镜光圈
在这里插入图片描述
图5.10 © 大透镜光圈

图5 - 10:(a)没有散焦模糊的场景;(b)由于镜头光圈相对较小,所以景深很大,只有离焦区域有少量的模糊;©光圈很大,离焦区域有更大的混乱圈,从而在胶片平面上有更多的模糊。(由Angelo Ferretti拍摄)

在这里插入图片描述
图5.11:景深使这部分景观的景深感和比例感更强。(场景由Laubwerk提供)

高斯透镜方程还可以让我们计算混乱圈的大小;给定焦距 f f f 的透镜聚焦在距离 z f z_f zf 处,则薄膜平面在 z f ′ z'_f zf 处。给定深度 z z z 处的另一个点,高斯透镜方程给出了透镜聚焦该点的距离 z ′ z' z。这个点不是在薄膜平面的前面就是在薄膜平面的后面;图5.12(a)显示了它在后面的情况。
在这里插入图片描述
Figure 5.12: (a) If a thin lens with focal length fis focused at some depth zf, then the distance from the lens to the focus plane is z, given by the Gaussian lens equation.
图5.12: (a)焦距为 f f f 的薄透镜聚焦在某深度 z f z_f zf处,则,由高斯透镜方程给出,透镜到聚焦平面的距离为 z f ′ z'_f zf。场景中深度为 z ≠ z f z \ne z_f z=zf 的点将在胶片平面上成像为一个圆;这里 z z z聚焦于 z ′ z' z,在薄膜平面的后面。(b)为了计算混乱圈的直径,我们可以应用类似三角形: d l d_l dl(透镜直径)与 z ′ z' z 的比值必须与 d c d_c dc(混乱圈直径)与 z ′ − z f ′ z'-z'_f zzf的比值相同。

混淆圆的直径由在 z ′ z' z到胶片平面的镜头之间的圆锥体的交点给出。如果我们知道透镜的直径 d l d_l dl,那么就可以使用相似三角形来求解混乱圆的直径 d c d_c dc(参见图5 - 12(b)):

d l z ′ = d c ∣ z ′ − z f ′ ∣ \frac{d_l}{z'} = \frac{d_c}{|z'-z'_f|} zdl=zzfdc

求解 d c d_c dc,有:

d c = ∣ d l ( z ′ − z f ′ ) z ′ ∣ d_c = \left | \frac{d_l(z'-z'_f)}{z'} \right | dc= zdl(zzf)

应用高斯镜头方程将结果表示为场景深度,我们可以发现

d c = ∣ d l ∗ f ∗ ( z − z f ) z ∗ ( f + z f ) ∣ d_c = \left | \frac{d_l * f * (z-z_f)}{z * (f+z_f)} \right | dc= z(f+zf)dlf(zzf)

请注意,混乱的圆的直径是成比例的直径的镜头。镜头直径通常用镜头的焦距比数n表示,n表示直径与焦距的比值, d l = f n d_l = \frac{f}{n} dl=nf

图5.13展示了一个50mm焦距、25mm光圈、聚焦在 z f = 1 m z_f = 1m zf=1m的一个函数图像。注意,模糊与焦平面周围的深度是不对称的,在焦平面前面的物体比在焦平面后面的物体增长得快得多。
在这里插入图片描述
图5.13:焦距为50mm,光圈为25mm,聚焦在1米处的混乱圈直径随深度的函数。

在光线跟踪器中为薄透镜建模非常简单:所需要做的就是在透镜上选择一个点,然后找到从该点出发的合适光线,使得聚焦平面上的物体在胶片上得到聚焦(参见图5 - 14)。因此,投影相机需要两个额外的景深参数:一个设置镜头光圈的大小,另一个设置焦距。

<<ProjectiveCamera Protected Members>>+= 
Float lensRadius, focalDistance;

在这里插入图片描述
在这里插入图片描述
图5 - 14: (a)对于针孔相机模型,一条相机光线与胶片平面上的每个点(实心圆)相关联,该点由穿过针孔透镜单点的光线(空圆)给出(b)对于有限光圈的相机模型,对每条射线在圆盘形透镜上采样一个点(实心圆)。然后,我们计算通过透镜中心(对应于针孔模型)的光线以及它与焦点平面的交点(实线)。我们知道,无论镜头样本的位置如何,焦点平面中的所有物体都必须处于焦点中。因此,对应于透镜位置样本(虚线)的光线是由从透镜样本点开始并通过计算出的焦点平面交点的光线给出的。

通常有必要为每个图像像素跟踪许多光线,以便充分采样镜头以获得平滑的散焦模糊。图5.15显示了图5.11中每像素只有4个样本的风景场景(图5.11每像素有2048个样本)。
在这里插入图片描述
图5.15:景深的景观场景,每像素只有4个采样:景深采样不足,图像颗粒化。(场景由Laubwerk提供)

<<Modify ray for depth of field>>= 
if (lensRadius > 0) {
    <<Sample point on lens>> 
    <<Compute point on plane of focus>> 
    <<Update ray for effect of lens>> 
}

SampleUniformDiskConcentric()函数,在章节a .5.1中定义,在 [ 0 , 1 ) 2 [0,1)^2 [0,1)2上取一个 ( u , v ) (u,v) (u,v)样本位置,并将其映射到以原点 ( 0 , 0 ) (0,0) (0,0)为中心的二维单位圆盘。为了将其转换为透镜上的一个点,这些坐标由透镜半径缩放。CameraSample类在pLens成员变量中提供镜头采样参数 ( u , v ) (u,v) (u,v)

<<Sample point on lens>>= 
Point2f pLens = lensRadius * SampleUniformDiskConcentric(sample.pLens);

光线的原点是透镜上的这个点。现在有必要确定新射线的正确方向。我们知道,从给定的图像样本经过透镜的所有光线必须汇聚在焦点平面上的同一点上。此外,我们知道射线通过透镜的中心时方向不变,因此找到适当的收敛点就是将来自针孔模型的未扰动的射线与焦点平面相交,然后将新射线的方向设置为从透镜上的点到交点的向量。

对于这个简单的模型,焦点平面垂直于坐标z轴,射线从原点开始,所以穿过透镜中心的射线与焦点平面相交很简单。交点的t值由

t = f o c a l D i s t a n c e d z t = \frac{focalDistance}{d_z} t=dzfocalDistance

<<Compute point on plane of focus>>= 
Float ft = focalDistance / ray.d.z;
Point3f pFocus = ray(ft);

现在可以初始化射线了。将原点设置为镜头上的采样点,并设置方向,使光线通过焦点平面上的点pFocus。

<<Update ray for effect of lens>>= 
ray.o = Point3f(pLens.x, pLens.y, 0);
ray.d = Normalize(pFocus - ray.o);

为了计算薄透镜的光线微分,将片段<<Update ray for effect of lens>>中使用的方法应用于在胶片平面上x,y两个方向偏移一个像素的光线。实现这一点的片段,<<Compute OrthographicCamera ray differentials accounting for lens>>和<<ompute PerspectiveCamera ray differentials accounting for lens>>,不包括在这里。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值