您已经意识到您需要返回剪辑空间坐标(技术上未标准化的设备坐标 https://stackoverflow.com/questions/21841598/when-does-the-transition-from-clip-space-to-screen-coordinates-happen)来自你的顶点着色器。问题是如何以及在哪里从 UIKit 坐标到 Metal 的剪辑空间坐标。
让我们首先定义这些不同的空间。请注意,下面,我实际上am为了简单起见,使用 NDC 坐标,因为在这种特殊情况下,我们不会通过返回顶点位置来引入透视w != 1
。 (这里我指的是w
剪辑空间位置的坐标;在下面的讨论中,w
始终指视图宽度)。
我们将顶点传递到任何方便的空间中的顶点着色器(这通常称为模型空间)。由于我们在 2D 环境下工作,因此不需要对世界空间和眼睛空间进行通常的一系列变换。本质上,UIKit 视图的坐标是将我们的模型空间、世界空间和眼睛空间合而为一。
我们需要某种正交投影矩阵来从这个空间移动到剪辑空间。如果我们去掉与 z 轴相关的不必要部分并假设视图边界的原点是 (0, 0),我们会得到以下转换:
我们可以将该矩阵传递到顶点着色器中,或者可以在将顶点发送到 GPU 之前进行转换。考虑到涉及的数据很少,目前这并不重要。事实上,使用矩阵有点浪费,因为我们只需通过几次乘法和一次加法来转换每个坐标。这是 Metal 顶点函数中的样子:
float2 inverseViewSize(1.0f / width, 1.0f / height); // passed in a buffer
float clipX = (2.0f * in.position.x * inverseViewSize.x) - 1.0f;
float clipY = (2.0f * -in.position.y * inverseViewSize.y) + 1.0f;
float4 clipPosition(clipX, clipY, 0.0f, 1.0f);
为了验证我们是否从这个转换中得到了正确的结果,让我们插入视图的左上角和右下角点,以确保它们最终出现在剪辑空间的末端(通过线性,如果这些点转换正确,那么所有其他人都会):
这些点看起来是正确的,所以我们就完成了。如果您担心此转换带来的明显失真,请注意它完全被视口变换 https://www.songho.ca/opengl/gl_transform.html#wincoord这发生在光栅化之前。