1、标准化设备坐标(Normalized Device Coordinates, NDC)
顶点坐标已经在顶点着色器中处理过,它们就应该是标准化设备坐标了,标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。
2、UV坐标系
UV坐标系一般以左下角为原点(0,0),经常用于贴图上面。范围为0-1.
OpenGL 一个完整的空间变换流水线如下:
model coordinate system(模型坐标系)->world coordinate system(世界坐标系) --> eye coordinate system(相机坐标系) --> clip coordinate system(裁减平面坐标系) --> normalized device coordinate system(标准设备坐标系NDC) --> window device coordinate system(屏幕坐标系)。
- 模型变换和观察变换,使将物体的坐标从模型空间,通过旋转移动等复合变换,转换到了以摄像机为原点的观察空间坐标系下。在观察空间下,z轴上的数据表示了物体距离摄像机的远近,即深度,此时的深度值还是线性的。(观察空间使用的是右手坐标系,即 -z轴才是摄像机的正前方)
- 观察空间中,只有位于视锥体内的物体才会被摄像机渲染并且可见,所以需要将范围外的顶点剔除。但是视锥体是一个金字塔形状,由六个锥平面构成。使用六个锥平面直接进行裁剪判断会很麻烦; 因此,有一种更加方便的方法是:通过一个透视投影矩阵把顶点从观察空间转换到一个裁剪空间下,转换的过程实际是对x,y,z分量都进行了不同程度的缩放和平移。使x,y,z值满足:直接用w分量作为裁剪的范围值,如果变换后的x,y,z分量都位于[-w,w]这个范围内,就说明该顶点位于裁剪空间内,反之会被剔除。 以下是推导出的透视投影矩阵和顶点相乘后的坐标点(这里不做推导过程):
Near:近裁剪平面距离
Far:远裁剪平面距离
FOV:锥体竖直方向的张开角度
Aspect:摄像机的横纵比 - 在上一步操作中,我们可以得到裁剪空间下的 z 和 w 分量:
- 经过透视投影变换,坐标系转换到了裁剪空间下。紧接着,我们可以通过判断当前的 x,y,z 分量是否处于 [-w,w] 范围内,来判断坐标点是否处于视锥体内,并将视锥体外的坐标点剔除。在观察空间,世界空间,模型空间下,坐标点的w分量一直都是1,所以说,经过剔除后的裁剪空间下的坐标点的范围值为 [-1,1]。
- 当完成所有的裁剪工作后,就要进行真正的投影了,即把视锥体投影到屏幕空间中,从而得到真正的二维像素坐标。 期间需要进行两步操作,齐次除法和映射输出。
- 齐次除法:用齐次坐标系的 w 分量去除以x,y,z分量。以z轴分量为例:
经过齐次除法后,坐标系由裁剪空间转换到了NDC(归一化的设备坐标,Normalized Device Coordinates)下,此时的x,y,z分量的范围值为[-1,1]。 - 映射输出:NDC下的x和y的坐标范围是[-1,1],而屏幕空间左下角像素坐标是(0,0),右上角的像素坐标是(pixelWidth,pixelHeight),因此x和y会先除以2再加1/2,映射到[0,1],然后再分别乘以pixelWidth和pixelHeight:
上面的式子对x,y值做了处理,而z分量会直接被用于深度缓冲(Zbuffer),当然这个也不一定,会根据硬件选取合适的存储格式。 除此之外,为了能将z值存储到深度图中,需要将其范围映射到 [0,1]:
此时的 d 值就是 Unity 中深度图存储的数据。因为d是关于z_ndc的函数,z_ndc是关于1/z_visw的函数,因此 d 自然不是线性的了。 (z_ndc:ndc空间下的z值;z_visw:观察空间下的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;
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;
}