web-dev-qa-db-fra.com

Obtention de la vraie valeur z à partir du tampon de profondeur

L'échantillonnage à partir d'un tampon de profondeur dans un shader renvoie des valeurs comprises entre 0 et 1, comme prévu. Compte tenu des plans de plan rapproché et éloigné de la caméra, comment puis-je calculer la vraie valeur z à ce point, c'est-à-dire la distance de la caméra?

47
Hannesh

De http://web.archive.org/web/20130416194336/http://olivers.posterous.com/linear-depth-in-glsl-for-real

// == Post-process frag shader ===========================================
uniform sampler2D depthBuffTex;
uniform float zNear;
uniform float zFar;
varying vec2 vTexCoord;
void main(void)
{
    float z_b = texture2D(depthBuffTex, vTexCoord).x;
    float z_n = 2.0 * z_b - 1.0;
    float z_e = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear));
}

[modifier] Voici donc l'explication (avec 2 erreurs, voir le commentaire de Christian ci-dessous):

Une matrice de perspective OpenGL ressemble à ceci: from songho.ca

Lorsque vous multipliez cette matrice par un point homogène [x, y, z, 1], cela vous donne: [s'en fiche, s'en fiche, Az + B, -z] (avec A et B les 2 grandes composantes dans la matrice).

OpenGl fait ensuite la division en perspective: il divise ce vecteur par sa composante w. Cette opération ne se fait pas dans les shaders (sauf cas particuliers comme le shadowmapping) mais dans le matériel; vous ne pouvez pas le contrôler. w = -z, donc la valeur Z devient -A/z -B.

Nous sommes maintenant en coordonnées de périphérique normalisées. La valeur Z est comprise entre 0 et 1. Pour une raison stupide, OpenGL nécessite qu'il soit déplacé vers la plage [-1,1] (tout comme x et y). Une mise à l'échelle et un décalage sont appliqués.

Cette valeur finale est ensuite stockée dans le tampon.

Le code ci-dessus fait exactement le contraire:

  • z_b est la valeur brute stockée dans le tampon
  • z_n transforme linéairement z_b de [-1,1] à [0,1]
  • z_e est la même formule que z_n = -A/z_e -B, mais résolu pour z_e à la place. C'est équivalent à z_e = -A/(z_n + B). A et B doivent être calculés sur le CPU et envoyés sous forme d'uniformes, btw.

La fonction opposée est:

varying float depth; // Linear depth, in world units
void main(void)
{
    float A = gl_ProjectionMatrix[2].z;
    float B = gl_ProjectionMatrix[3].z;
    gl_FragDepth  = 0.5*(-A*depth + B) / depth + 0.5;
}
54
Calvin1602

Je sais que c'est une vieille, vieille question, mais je me suis retrouvé ici plus d'une fois à plusieurs reprises, alors j'ai pensé partager mon code qui effectue les conversions avant et arrière.

Ceci est basé sur la réponse de @ Calvin1602. Ceux-ci fonctionnent en GLSL ou en ancien code C simple.

uniform float zNear = 0.1;
uniform float zFar = 500.0;

// depthSample from depthTexture.r, for instance
float linearDepth(float depthSample)
{
    depthSample = 2.0 * depthSample - 1.0;
    float zLinear = 2.0 * zNear * zFar / (zFar + zNear - depthSample * (zFar - zNear));
    return zLinear;
}

// result suitable for assigning to gl_FragDepth
float depthSample(float linearDepth)
{
    float nonLinearDepth = (zFar + zNear - 2.0 * zNear * zFar / linearDepth) / (zFar - zNear);
    nonLinearDepth = (nonLinearDepth + 1.0) / 2.0;
    return nonLinearDepth;
}
10
david van brink