版权:

​liujing7256​

前言

        当我们编写shader着色器程序的时候,经常看到shader内置变量,gl_Position,gl_FragCoord,感觉一头雾水。

详解

1. gl_Position:

#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0

out vec4 vertexColor; // 为片段着色器指定一个颜色输出

void main()
{
gl_Position = vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数
vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把输出变量设置为暗红色
}

 它并没有类型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 投影矩阵​

shader着色器变量gl_FragCoord 的含义_投影矩阵

眼坐标空间到规范化设备坐标系空间

shader着色器变量gl_FragCoord 的含义_投影矩阵_02

透视除法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 不会存在未定义输出的情况(因为不是用户定义的值就是系统给定的默认值)。

题外话, 给定一个矩阵,怎样判断投影矩阵 是 透视投影还是正交投影呢?

shader着色器变量gl_FragCoord 的含义_设备坐标系_03

正交投影矩阵公式

shader着色器变量gl_FragCoord 的含义_gl_Position_04

透视投影矩阵公式

答案: 判断 projectionMatrix[3][2] 是否不等于 零。

最后给两个函数,在基于延迟着色的fragment shader 中 反求片元在 眼坐标系 或 世界坐标系的位置:

vec3 decodeCameraSpacePositionFromDepthBuffer(in vec2 texCoord){ 
vec4 clipSpaceLocation;
clipSpaceLocation.xy = texCoord*2.0-1.0;
clipSpaceLocation.z = texture(depthTexture, texCoord).r * 2.0-1.0;
clipSpaceLocation.w = 1.0;
vec4 homogenousLocation = projectionMatrixInverse * clipSpaceLocation;
return homogenousLocation.xyz/homogenousLocation.w;
}
vec3 decodeWorldSpacePositionFromDepthBuffer(in vec2 texCoord){
vec4 clipSpaceLocation;
clipSpaceLocation.xy = texCoord*2.0-1.0;
clipSpaceLocation.z = texture(depthTexture, texCoord).r * 2.0-1.0;
clipSpaceLocation.w = 1.0;
vec4 homogenousLocation = viewProjectionMatrixInverse * clipSpaceLocation;
return homogenousLocation.xyz/homogenousLocation.w;
}