版权:
前言
当我们编写shader着色器程序的时候,经常看到shader内置变量,gl_Position,gl_FragCoord,感觉一头雾水。
详解
1. gl_Position:
它并没有类型in、out或是uniform的声明,而是直接使用,且在后面的程序中也未被引用。原来它是默认是归一化的裁剪空间坐标,xyz各个维度的范围为-1到1,仅能在顶点着色器中使用,既是输入也是输出。gl_Position赋值范围就是float的取值范围(32位),只不过只有[-1,1]区间的片元被绘制。它是vec4类型的,不能重声明为dvec4等类型。
gl_Position可以通过视角划分转换为标准化设备空间中的笛卡尔坐标:
2. gl_FragCoord
gl_FragCoord 表示当前片元着色器处理的候选片元窗口相对坐标信息,是一个 vec4 类型的变量 (x, y, z, 1/w), 其中 x, y 是当前片元的窗口坐标,OpenGL 默认以窗口左下角为原点, 在 着色器中通过布局限定符可以重新设定原点,比如窗口左上角为原点 origin_upper_left
,窗口大小由 glViewport() 函数指定。x, y 默认是像素中心 而非 整数, 原点 的窗口坐标值为 (0.5, 0.5), 小数部分恒为 0.5, 当viewport 范围 为(0,0,800,600)时, x, y 的取值范围为(0.5, 0.5, 799.5, 599.5), 当在着色器中布局限定符设置为 pixel_center_integer 时, x, y 取值为整数。
第三个分量 z 表示的是当前片元的深度信息,由 vertex shader 处理过后系统插值得到, gl_FragCoord.z 的产生过程:
假设 OpenGL 变换的各坐标系统定义如下: world.xyzw 表示 世界坐标系的坐标, eye.xyzw 表示眼坐标系(也叫观察坐标系,还有叫相机坐标系,呃。。。) 的坐标, clip.xyzw 表示 裁剪坐标系的坐标 , ndc.xyzw 表示 规范化设备坐标系坐标, win.xyzw 表示 窗口坐标系坐标, OpenGL 一个完整的空间变换流水线如下:
world coordinate system --> eye coordinate system --> clip coordinate system --> normalized device coordinate system --> window device coordinate system。
gl_FragCoord.z 生成过程:
(1)世界坐标系内的坐标乘以观察矩阵变换到眼坐标空间 eye.xyzw = viewMatrix * world.xyzw;
(2)眼坐标系内的坐标通过乘上投影矩阵变换到裁剪空间 clip.xyzw = projectMatrix * eye.xyzw;
(3)裁剪坐标系内的坐标通过透视除法(也就是 w 为 1 化) 到 规范化设备坐标系 ndc.xyz = clip.xyz / clip.w;
(4)设备规范化坐标系到窗口坐标系 win.z = (dfar - dnear)/2 * ndc.z + (dfar+dnear)/2;
可以看出gl_FragCoord.z 是 win.z 。dnear ,dfar 是由 glDepthRange(dnear, dfar) 给定的,按openGL 默认值 (0,1) , win.z = ndc.z/2 + 0.5。
有时候我们需要在 shader 内反算 眼坐标系 或 世界坐标系 内的坐标, 这在后处理或延迟着色中很有用,不需要另外使用颜色缓存保留物体位置信息,减少带宽占用。
由窗口空间坐标反算规范化设备空间坐标:
ndc.xyzw = ( gl_FragCoord.xy/viewport.wh * 2.0 - 1.0, gl_FragCoord.z * 2.0 - 1.0, 1.0 );
这样我们只需向shader 中传入矩阵信息就可以获得该片元在指定空间内的坐标 ,例如
- eye.xyzw = projectionMatrixInverse * ndc.xyzw;
- world.xyzw = modelViewProjectionMatrixInverse * ndc.xyzw
注意最终结果要除以 w 分量, eye.xyz = eye.xyz/eye.w;
第四个分量 gl_FragCoord.w
先看看透视投影矩阵的推导:
原文地址:OpenGL 投影矩阵
眼坐标空间到规范化设备坐标系空间
透视除法w的来源
gl_FragCoord.w 是裁剪空间 clip.w 的倒数即 1/clip.w , 由上面的透视投影矩阵的推导过程可以看出,为了凑透视除法, clip.w 值就是 眼坐标系 z 值的负数,也就是距离相机的距离。 取负数 是因为 眼坐标系 与 规范化设备坐标系 手向性不同, 眼坐标系是右手系, 规范化设备坐标系是左手系。这里暗示我们,对于透视投影, 由 gl_FragCoord.w 可以很方便的知道当前片元在眼坐标系中 距离相机的距离 : gl_FragCoord.w = - 1/Ze ----------> Ze = - 1/gl_FragCoord.w;
那么正交投影呢? 正交投影矩阵的 w 值 恒为 1, 还是乖乖的传矩阵做逆运算吧。当然 用逆矩阵反求 各坐标系的坐标 具有通用性。
总结:
gl_FragCoord 比较有用的是反求各坐标系的空间位置,减少带宽占用。 也可以方便的获取当前片元的窗口坐标和片元深度值。当深度测试开启时, 如果在片元着色器中没有定义 gl_FragDepth 的值, gl_FragCoord.z 即成为默认输出的深度值,并且gl_FragDepth 不会存在未定义输出的情况(因为不是用户定义的值就是系统给定的默认值)。
题外话, 给定一个矩阵,怎样判断投影矩阵 是 透视投影还是正交投影呢?
正交投影矩阵公式
透视投影矩阵公式
答案: 判断 projectionMatrix[3][2] 是否不等于 零。
最后给两个函数,在基于延迟着色的fragment shader 中 反求片元在 眼坐标系 或 世界坐标系的位置: