一般来说,如果图像的一个轴是N像素长,并且覆盖从x0到x1的切线空间视场,则像素n(其中n介于0和N-1之间)覆盖的角度范围是α1-α0,其中α0=tan-1(n⋅(x1-x0)/N+x0) 和α1=tan-1((n+1)⋅(x1-x0)/N+x0),得出的分辨率为1/(α1-α0)。记住这一点,用微分进行绘图将更加容易。
根据前面的段落,将像素索引与角度相关的函数为α(n)=tan-1((n+0.5)⋅(x1-x0)/N+x0),其中0.5与n相加以计算像素中心角。对于α(n)=((x1-x0)/N)/(1+((n+0.5)⋅(x1-x0)/N+x0)2)的总导数,tan-1(x)的导数可以合宜地设为1/(1+x2)。反相这一点并且从弧度转换为角度,我们可以得出(π/180)/(d/dn α(n))像素/度的像素位置n的分辨率。插入从我的Vive接收到的值,我们可以绘制图4中的函数结果(因为没有透镜,因此没有色差,三原色的分辨率曲线合并为一):
图4
我们可以从图表中得出大量的信息。首先,平面中间图像不适合VR渲染,因为分辨率在中心和边缘之间增加了2.5到3倍,这意味着不成比例的大量渲染像素被分配到外围,而它们在那里不是十分有用。尽管平面上投影为3D图形所固有,但没有人规定3D仿射空间(affine space)上的平面必须是平的。在3D投影空间中使用平坦平面会产生很好的渲染技巧,但这不是本文的话题。
不过,图4中的曲线是否令你想起什么呢?我们设想一下将图4垂直缩放3.5倍,然后取出剪刀,将其切成31个相同大小的垂直条状。接下来,我们通过增加从中心往四面的量来向下移位这些条状。现在将这幅画面与图1进行比较。是否看到任何相似之处呢?前方剧透:这正是下一个渲染步骤中会发生的事情。
第2步:透镜失真和色差的非线性校正
成功校正透镜失真的秘诀是:在校准步骤中测量失真情况。理想情况下是在工厂中对每个头显进行相关处理。确切的校准步可能有所不同,但一个选项是利用校准的相机来捕捉显示在头显显示器上的已知校准图像将如何呈现给用户。
这里的要点是:创建一种可信的虚拟现实幻觉。如果虚拟对象位于虚拟用户的特定方向,则虚拟对象需要呈现在与真实用户相同的方向。换句话说:对于头显显示器的每一个像素,我们需要知道像素呈现给头显用户的确切方向。最容易表达这一方向的方法是利用水平和垂直切线空间参数。
现在,如果校准为我们提供了每个显示像素的切线空间方向映射,我们在渲染过程中又该如何为任意像素指定颜色呢?幸运的是,我们已经拥有了一个用于表示切线空间中的虚拟3D环境的图像:在步骤1生成的中间图像。这为透镜失真校正提供了一个简单的步骤:对于每个显示像素,在校准映射中查找切线空间坐标,然后在中间图像复制具有相同切线空间坐标的像素。由于中间图像的直线式结构,最后一部分的查找其实相当简单。
更幸运的是:相同的步骤可以校正色差。我们无需为每个显示像素储存一个切线空间坐标,我们只需储存三个:为红,绿,蓝颜色组件各储存一个。因为三种颜色在相同透镜下的衍射程度不同,因此它们对于同一像素的切线空间坐标将有所不同。
这非常好,但剩下的问题是如何表示校准映射。原则上,校准映射是一个1080×1200像素的图像(对Vive来说),每个像素有六个组件:三种颜色各一个切线空间(x,y)。由于技术原因,表示这样的映射在渲染性能方面效率不高。更好的方法是(这也是Vive和所有其他OpenVR头显所使用的方法),将映射简化为NxM相同大小(在显示空间中)矩形的“畸变网格”,每个颜色的切线空间坐标仅存储在那些矩形的边角,并在每个矩形的边角值之间进行插值。这样做更好,因为它非常紧凑(N和M可以很小),而且性能很好(因为现代显卡非常擅于绘图,以及在矩形中插值)。图5显示了我的Vive的右屏幕绿色通道失真网格,包括显示空间和切线空间。对于后一种情况,失真网格都叠加在中间图像的矩形边界上。
图5
图5告诉了我们一些有趣的事情。最重要的是,这表明Vive的透镜会导致相当多的枕形失真。靠近透镜中心的网格被压缩,而周围的网格被拉伸。这实现了把更多实际显示像素分配给重要的中央注视点区域,并且把更少显示像素分配给周边视觉的预期效果。换句话说,透镜远远消除了图4所示的糟糕分辨率分布。证据是中心位置的最终显示分辨率高于周边,如图1和图2所示。
通过在切线空间中拉伸网格单元可以平衡分辨率。一个小单元会为实际显示的固定尺寸区域分配少量中间图像像素,从而增加局部分辨率;而拉伸单元会把更多像素填充至相同的固定尺寸区域,从而降低局部分辨率。这解释了图1和图2中倒置分辨率分布,但尚未出现锯齿的现象。
结果表明,问题的解释十分简单:利用线性插值,显卡使用来自矩形边角的值进行插值,这意味着生成的失真映射是分段线性函数。分段线性函数的导数是片段间不连续跳变的分段常数函数。正是那些恒定分段(由每个网格单元内逐渐变化的局部分辨率调制)导致了图1和图2中的现象。导数中的不连续性不是问题,因为观察者无法知觉导数,只能知觉函数,而函数在什么地方都是连续的。
对于图5,另一个有趣的观察结果是,切线空间中的畸变网格和中间图像之间的重叠并不完美。中间图像的一部分未被覆盖(沿着左边界的透镜形状区域),而且畸变网格的一部分落在中间图像之外。第一部分意味着用户无法看到中间图像的一部分,以及它的视场;第二部分意味着实际显示的部分不接收有效的图像数据,因此这变得无法使用。
Vive的设计师在这里作出了一个有趣的决定:他们将右边中间图像的左边界延伸到右边显示器的左边缘之外(左边显示器也同样如此),从而获得与另一只眼睛的部分立体重叠。在直接向左看时视场不会延伸,因为那里没有更多的显示像素,但在向左或向上或向下看时视场将会延伸。“缺点”是Vive渲染的中间图像会出现奇怪的“偏食”形状,在内边缘缺失了两大块。
最后要提的一点是:1512×1680像素的推荐中间图像大小来自于哪里呢?选择它的原因是:为了令中间图像的分辨率在通过畸变网格馈送之后能够大致与透镜中心区域中的真实屏幕分辨率相匹配,从而让重新采样过程中的混叠最小化(在把一个光栅图像翘曲到另一个光栅图像之上时的固有现象)。具体来说,中间图像在透镜中心的分辨率略低于实际显示的分辨率(10.01像素/度 vs 11.42像素/度),但往外来说中间图像能够快速超过实际显示的分辨率,请参见图6。
总之:量化VR头显的真实物理分辨率是一个复杂的问题,因为这取决于透镜的光学属性,校准映射的表现形式和分辨率(网格 vs 图像 vs 分析函数),而且当然还包括真实显示的像素数目及其真实视场。但事实证明,通过在透镜中心引用绿色通道分辨率,存在一种合理的方法来将其整合为具体的数字并进行粗略比较。