请问如何在GLSL中从窗口坐标(帧缓冲中的像素)和像素深度值计算出眼空间坐标(可以理解为相机坐标)?类似于GLU库中的gluUnproject函数。
看起来是GLSL将gl_FragCoord.z转换为眼空间z的重复问题。
编辑(完整答案):
// input: x_coord, y_coord, samplerDepth
vec2 xy = vec2(x_coord,y_coord); //in [0,1] range
vec4 v_screen = vec4(xy, texture(samplerDepth,xy), 1.0 );
vec4 v_homo = inverse(gl_ProjectionMatrix) * 2.0*(v_screen-vec4(0.5));
vec3 v_eye = v_homo.xyz / v_homo.w; //transfer from homogeneous coordinates
vec4 unProjectedPosition = invertedModelViewProjection * vec4(
2.0 * (gl_FragCoord.x - view[0]) / view[2] - 1.0,
2.0 * (gl_FragCoord.y - view[1]) / view[3] - 1.0,
2.0 * gl_FragCoord.z - 1.0,
1.0);
嗯,opengl.org上的一个人指出,投影产生的剪裁空间坐标被clipPos.w除以来计算归一化设备坐标。当从片段到NDC再到剪裁空间坐标反转步骤时,需要重构w(这恰好是与之对应的视图空间(相机)坐标的-z),并将ndc坐标乘以该值来计算正确的剪裁空间坐标(通过将其乘以逆投影矩阵可以将其转换为视图空间坐标)。
以下代码假定您正在后处理中处理帧缓冲区。在呈现几何体时进行处理时,您可以使用gl_FragCoord.z代替texture2D(sceneDepth, ndcPos.xy).r。
以下是代码:
uniform sampler2D sceneDepth;
uniform mat4 projectionInverse;
uniform vec2 clipPlanes; // zNear, zFar
uniform vec2 windowSize; // window width, height
#define ZNEAR clipPlanes.x
#define ZFAR clipPlanes.y
#define A (ZNEAR + ZFAR)
#define B (ZNEAR - ZFAR)
#define C (2.0 * ZNEAR * ZFAR)
#define D (ndcPos.z * B)
#define ZEYE -(C / (A + D))
void main()
{
vec3 ndcPos;
ndcPos.xy = gl_FragCoord.xy / windowSize;
ndcPos.z = texture2D (sceneDepth, ndcPos.xy).r; // or gl_FragCoord.z
ndcPos -= 0.5;
ndcPos *= 2.0;
vec4 clipPos;
clipPos.w = -ZEYE;
clipPos.xyz = ndcPos * clipPos.w;
vec4 eyePos = projectionInverse * clipPos;
}
基本上这是 gluUnproject 的 GLSL 版本。
我刚刚意识到,在片段着色器中进行这些计算是不必要的。你可以通过在CPU上执行这些操作并将其与MVP逆矩阵相乘来节省一些运算(假设glDepthRange(0, 1)
,请随意编辑):
glm::vec4 vp(left, right, width, height);
glm::mat4 viewportMat = glm::translate(
vec3(-2.0 * vp.x / vp.z - 1.0, -2.0 * vp.y / vp.w - 1.0, -1.0))
* glm::scale(glm::vec3(2.0 / vp.z, 2.0 / vp.w, 2.0));
glm::mat4 mvpInv = inverse(mvp);
glm::mat4 vmvpInv = mvpInv * viewportMat;
shader->uniform("vmvpInv", vmvpInv);
在着色器中:
vec4 eyePos = vmvpInv * vec4(gl_FragCoord.xyz, 1);
vec3 pos = eyePos.xyz / eyePos.w;
khronos.org
有一个 Wiki 页面,列出了几种不同的情况,并用着色器代码进行了解释,因此值得在这里发布。
从窗口空间计算眼睛空间。