如何使用带着色器的透视视图设置特定眼点

时间:2013-06-18 08:20:22

标签: opengl view shader perspective

在这些日子里,我正在阅读Jason L. McKesson撰写的Learning Modern 3D Graphics Programming书。基本上它是一本关于OpenGL 3.3的书,我现在在chapter 4,这是关于正交和透视的。

在本章的最后,在"Further Study"部分下,他建议尝试一些诸如实现可变眼点(他在相机空间的开头(0,0,0)使用以获得简洁性)和任意透视平面位置。 他说我需要分别用E_x和E_y来偏移顶点的X,Y相机空间位置。

我无法理解这段话,我怎么能使用可变眼点仅修改X,Y偏移?

编辑:可能是这样的吗?

#version 330

layout(location = 0) in vec4 position;
layout(location = 1) in vec4 color;

smooth out vec4 theColor;

uniform vec2 offset;
uniform vec2 E;
uniform float zNear;
uniform float zFar;
uniform float frustumScale;


void main()
{
    vec4 cameraPos = position + vec4(offset.x, offset.y, 0.0, 0.0);
    vec4 clipPos;

    clipPos.xy = cameraPos.xy * frustumScale + vec4(E.x, E.y, 0.0, 0.0);

    clipPos.z = cameraPos.z * (zNear + zFar) / (zNear - zFar);
    clipPos.z += 2 * zNear * zFar / (zNear - zFar);

    clipPos.w = cameraPos.z / (-E.z);

    gl_Position = clipPos;
    theColor = color;
}

Edit2:谢谢鲍里斯,你的照片帮了很多:)特别是因为:

  • 它清楚地说明了您之前所说的关于将E视为投影位置而非眼点位置
  • 的内容
  • 它强调项目平面的大小必须始终为[-1,1],我在书中读到的内容并未完全理解其含义

只是好奇心,为什么你在减去之后提到乘法?这本书是否出于同样的原因,那就是宽高比?因为逻辑上所有东西都让我完全相反,那就是第一次翻译(-2)然后乘法(/ 5)..或者也许用“缩放”这个术语,本书指的是重塑函数?

1 个答案:

答案 0 :(得分:5)

在这里,我们感兴趣的是计算从相机坐标(CC)到标准化设备坐标(NDC)的转换。

E视为相机坐标中投影平面的位置,而不是根据投影平面的眼点位置。在相机坐标系中,根据定义,眼点位于原点,至少在我对“摄像机坐标”的解释中是什么?表示:以您查看场景的位置为中心的坐标框架。 (你可以在数学上定义一个以任何地方为中心的透视变换,但这意味着你的输入空间不是摄像机空间,imho。这就是World-> Camera变换的用途,正如你将在chapter 6中看到的那样)

要点:

  • 你在相机空间,因此你的视点位于(0,0,0)
  • 你正在看负Z轴
  • 您的投影平面与xOy平面平行,两个方向的尺寸均为[-1,1]

这是这里的图片(每个刻度是0.5单位):

enter image description here

在这张图片中,您可以看到投影平面(灰色梯形的底边)以(0,0,-1)为中心,X和Y方向的尺寸均为[-1,1]

现在,问的是,不是选择(0,0,-1)这个平面的中心,而是选择任意(E.x,E.y,E.z)位置(假设E.z为负)。但是这个平面仍然要与xOy轴平行且尺寸相同。

你可以看到,尺寸E.xy与E.z的作用非常不同,因为E.xy将参与减法,而E.z将参与分裂。通过示例很容易看到:

  1. 假设zNear = -E.z(不一定是这种情况,但事实上你总是可以改变frustumScale以获得满足这个要求的等效观点)
  2. 考虑点E(投影平面的中心)。
  3. 它在NDC空间的坐标是什么?根据定义,它是(0,0,-1)。你所做的是减去E.xy,但除以-E_z。

    你的代码有了这个想法,但仍有一些问题:

    1. 首先,您定义了uniform vec2 E;而不是uniform vec3 E;(只是一个错字,不是什么大问题)
    2. clipPos.xy = ... ;行约为vec2算术。因此,您只能乘以标量值(即浮点数)或加/减vec2值。因此,vec4(E.x, E.y, 0.0, 0.0)的类型不正确,您应该改为使用E.xy,其类型vec2正确。
    3. 您实际应该减去E.xy而不是添加它。这在上面的示例中很容易看到。
    4. 最后,事情更加微妙; - )
    5. 我拍了一张照片来说明修改:

      enter image description here

      此图片中的每个刻度为1个单位。左上角是您的相机坐标空间,显示zNear,zFar和两个可能的投影平面。蓝色是解释和着色器here中使用的蓝色,红色是您现在要使用的那个。彩色区域对应于最终屏幕中应该可见的内容,例如什么应该在NDC空间的立方体[-1,1] ^ 3中。因此,如果使用蓝色投影平面,则需要获取右上角的空间,如果使用红色投影平面,则需要在底部获取空间。为此,您可以观察到需要在NDC空间中执行缩放和转换,例如:透视师之后! (我认为书中所写的内容不正确,或以不同方式解释问题)。

      因此你想要用欧几里德坐标(即不是齐次坐标,例如没有W坐标):

      clipPosEuclideanRed.xy = clipPosEuclideanBlue.xy * (-E.z) - E.xy;
      clipPosEuclideanRed.z = clipPosEuclideanBlue.z;
      

      但是,因为你是齐次坐标,所以这个值实际上是:

      clipPosEuclidean.xyz = clipPos.xyz / clipPos.w; // with clipPos.w = -cameraPos.z;
      

      因此,你必须写作:

      clipPosRed.xy = clipPosBlue.xy * (-E.z) - E.xy * (-cameraPos.z);
      clipPosRed.z = clipPosBlue.z;
      

      所以我对这个问题的解决办法就是只添加一行:

      void main()
      {
          vec4 cameraPos = position + vec4(offset.x, offset.y, 0.0, 0.0);
          vec4 clipPos;
      
          clipPos.xy = cameraPos.xy * frustumScale;
      
          // only add this line
          clipPos.xy = - clipPos.xy * E.z + E.xy * cameraPos.z;
      
          clipPos.z = cameraPos.z * (zNear + zFar) / (zNear - zFar);
          clipPos.z += 2 * zNear * zFar / (zNear - zFar);
      
          clipPos.w = -cameraPos.z;
      
          gl_Position = clipPos;
          theColor = color;
      }