视口(viewport)是渲染结果最终落到的屏幕矩形区域。

在本博客中,我们约定视口矩形由左下角位置 (x0,y0)(x_0, y_0) 和宽高 (wvp,hvp)(w_{vp}, h_{vp}) 描述。我们会将 NDC坐标 映射到视口矩形,这是 屏幕映射 的最后一步。

映射就是一次普通的缩放加平移:xx、yy 从 [−1,1][-1, 1] 拉到视口的宽高,zz 单独映射成深度值写进深度缓冲。

xscreen=wvp2(xndc+1)+x0,yscreen=hvp2(yndc+1)+y0x_{screen} = \frac{w_{vp}}{2}(x_{ndc} + 1) + x_0, \qquad y_{screen} = \frac{h_{vp}}{2}(y_{ndc} + 1) + y_0

zdepth=zndc+12(fvp−nvp)+nvpz_{depth} = \frac{z_{ndc} + 1}{2}(f_{vp} - n_{vp}) + n_{vp}

其中 [nvp,fvp][n_{vp}, f_{vp}] 是深度范围,OpenGL 的 glDepthRange 默认取 [0,1][0, 1]。所以"zz 保持在 [−1,1][-1, 1] 不变"只在深度范围取 [−1,1][-1, 1] 时才成立,而 zndc∈[−1,1]z_{ndc} \in [-1,1] 经默认深度范围映射后同样是线性的(不会影响深度测试的相对顺序)。

视口和窗口是完全不同的概念:窗口是操作系统中窗口管理器给应用的像素画布与事件接受的总和,视口只是画布上的一个矩形。两者的关系决定了它的三种典型用法:

  • 一个窗口可以有多个视口:分屏双人、小地图、编辑器里同时开好几个 3D 预览
  • 视口可以小于窗口,也可以超出窗口之外(当然多余的部分会被裁剪)
  • 视口矩形之外的片元不会被生成,所以视口隐式地充当了一次矩形裁剪