Matriz de proyección Derivación inversa-Z

Original: https://www.jianshu.com/p/cda012fb96df

Derivación general de la matriz de perspectiva de DirectX

  • Ángulo de campo de visión vertical α
  • β Ángulo FOV horizontal
  • w, h ancho, alto
  • relación de aspecto r\frac{w}{h}
  • d Cuando la altura media es 1, la distancia entre el plano y la cámara

Tabla de símbolos:

¿Por qué la altura media debe ser 1?
Debido a que queremos que el resultado final del cálculo esté en el espacio NDC, el rango es [-1, 1] en xy, donde establecemos la mitad de la altura de y en 1, y de acuerdo con la relación de aspecto, la mitad del ancho de x es r, y luego lo devolveremos x/r para lograr que xy esté en el rango [-1, 1].

\tan{\frac{\alpha}{2}} = \frac{1}{d}\quad\quad\stackrel{}\Rightarrow\quad\quad d = \frac{1}{\tan{\frac{ \alfa}{2}}}
\frac{w_1}{1}=\frac{w}{h}=r\quad\quad\stackrel{}\Rightarrow\quad\quad w_1=r tan{\frac{\beta}{2}}=\ frac{w_1}{d}=\frac{r}{d}=r\tan{\frac{ \alpha }{2}}

  Dado un punto, encuentra el punto de su perspectiva en el plano cuya altura media es 1, y se puede obtener la siguiente relación:

\frac{y}{z}=\frac{y'}{d} \quad\quad\stackrel{}\Rightarrow\quad\quad y'=\frac{y}{z\tan{\frac{\alpha {2}}}

x'=\frac{x}{z\tan{\frac{\alpha}{2}}}de la misma   manera
  En este plano, el rango de valores de y es [-1, 1], y el rango de valores de x es [-r, r] Para asegurar 1:1, divida x entre r .
  La matriz actual es:

\left[ \begin{matriz} \frac{1}{r\tan{\frac{\alpha}{2}}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan{\frac {\alpha}{2}}}&0&0\\0&0&A&1\\0&0&B&0\\\end{matriz}\right]

  Cuando el espacio de visualización se multiplica por esta matriz,
  la matriz actual es:

\left[x, y, z, 1\right] \left[\begin{matriz}\frac{1}{r\tan{\frac{\alpha}{2}}} & 0 & 0 & 0 \\ 0&\frac{1}{\tan{\frac{\alpha}{2}}}&0&0\\0&0&A&(1)\\0&0&B&0\\\end {matriz}\right] =[\frac{x}{r \tan{\frac{\alpha}{2}}}, \frac{y}{\tan{\frac{\alpha}{2}}} , Az+B, z]
\quad\quad\stackrel{divide\quad w}\Rightarrow\quad\quad [\frac{x}{rz\tan{\frac{\alpha}{2}}}, \frac{y}{z\tan {\frac{\alfa}{2}}}, A+\frac{B}{z}, 1]

  A partir de esto, se obtiene la función de conversión de view->ndc z:g(z)=A+\frac{B}{z}

  Se espera que cuando se convierta a NDC, el plano cercano sea 0 y el plano lejano sea 1, entonces:

g(f)=A+\frac{B}{f}=1\quad y\quad g(n)=A+\frac{B}{n}= 0
B=\frac{nf}{nf}\quad A=\frac{-f}{nf}

  Finalmente obtener la matriz:

\left[ \begin{matriz} \frac{1}{r\tan{\frac{\alpha}{2}}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan{\frac {\alpha}{2}}}&0&0\\0&0&\frac{-f}{nf}&1\\0&0&\frac{nf}{nf}&0\\\end {matriz}\right]

Reversa-Z

  La diferencia se refleja principalmente en la diferencia entre los cálculos A y B. Esperamos que el plano cercano de NDC sea 1 y el plano lejano sea 0, así que reescriba la fórmula:

g(f)=A+\frac{B}{f}=0\quad y\quad g(n)=A+\frac{B}{n}= 1
B=\frac{-nf}{nf}\quad A=\frac{n}{nf}

  Finalmente obtenga la matriz Reverse-Z:

\left[ \begin{matriz} \frac{1}{r\tan{\frac{\alpha}{2}}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan{\frac {\alpha}{2}}}&0&0\\0&0&\frac{n}{nf}&1\\0&0&\frac{-nf}{nf}&0\\\end {matriz}\right]

Matriz DirectX en Unity

  La API de la plataforma para PC de Unity es DX, que es diferente de la matriz que derivamos para las diferencias de plataforma.

  • La matriz pasada al renderizar a RT necesita invertir y
  • Para invertir la z de la matriz de vista, de modo que la matriz DX pase cuando Unity renderiza es esta fórmula:

\left[ \begin{matriz} \frac{1}{r\tan{\frac{\alpha}{2}}} & 0 & 0 & 0 \\ 0 & -\frac{1}{\tan{\ frac{\alfa}{2}}}&0&0\\0&0&\frac{-n}{nf}&-1\\0&0&\frac{-nf}{nf}&0\ \\end{matriz}\right]

  Podemos obtener el espacio de recorte en función de z y w:C(z)=\frac{-n(z+f)}{nf}\quad y \quad C(w)=-z

  Y después de la verificación por división homogénea NDC(z)=\frac{C(z)}{C(w)}=\frac{-n(z+f)}{(nf)(-z)}
  , si hay un punto en nuestra escena [0, 0, cerca], debido a la inversión de la matriz Vista Unitaria, se convierte [0, 0, -cerca](bajo el espacio de vista), y este punto se introduce: C(-n)=n, de manera similar C(-f)=0, NDC(-n)=1, CDN(-f)=0.
  Hay una función bajo URP:

//当Reverse-z(API为DX时)
// { (f-n)/n, 1, (f-n)/(n*f), 1/f }
float4 _ZBufferParams;
float LinearEyeDepth(float depth, float4 zBufferParam)
{
    return 1.0 / (zBufferParam.z * depth + zBufferParam.w);
}

  El método de uso es pasar SV_Position o el valor muestreado del mapa de profundidad en el sombreador de fragmentos. Tenga en cuenta que SV_Position en el sombreador de fragmentos ha sufrido una división de perspectiva e incluso una transformación de ventana gráfica. El valor z es equivalente a la fórmula NDC anterior.
  Lo escribimos de nuevo según la fórmula:

ProfundidadOjoLineal(NDC(z))=\frac{1}{\frac{-n(z+f)}{(nf)(-z)}*\frac{fn}{nf}+\frac{1}{ f} }=-z

  Esta z está en el espacio de vista, porque la matriz de vista en sí misma niega z, y esta operación -z simplemente nos hace ignorar la operación de negación de la matriz de vista.
  También existe el método Linear01Depth:

float Linear01Depth(float depth, float4 zBufferParam)
{
    return 1.0 / (zBufferParam.x * depth + zBufferParam.y);
}

Fórmula derivada:

Profundidad01Lineal(NDC(z))=\frac{1}{\frac{-n(z+f)}{(nf)(-z)}*\frac{fn}{n}+1}=-\frac {z}{f}

Plataforma móvil Unity, derivación de matriz de perspectiva OpenGL

  En comparación con DirectX del lado de la PC, la diferencia se refleja principalmente en tres aspectos:

  • Dos filas y dos columnas no necesitan invertir el eje y
  • El término de multiplicación de w es -1
  • El rango de ndc es [-1, 1], n se asigna a -1, f se asigna a 1
    Tenga en cuenta que en Opengl, el espacio de la cámara mira en la dirección del semieje negativo del eje z, estrictamente hablando , -n se asigna a -1, -f se asigna a 1

  También escriba la matriz fundamental:

\left[ \begin{matriz} \frac{1}{r\tan{\frac{\alpha}{2}}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan{\frac {\alpha}{2}}}&0&0\\0&0&A&(-1)\\0&0&B&0\\\end{matriz}\right]

NDC(z)=-A- \frac{B}{z}
NDC(-n)=-A-\frac{B}{-n}=-1 y NDC(-f)=-A-\frac{B}{-f}=1
B=\frac{2nf}{nf}\quad A=\frac{n+f}{nf}

  La matriz final resultante es:

\left[ \begin{matriz} \frac{1}{r\tan{\frac{\alpha}{2}}} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan{\frac {\alpha}{2}}} & 0 & 0 \\ 0 & 0 & \frac{n+f}{nf} & (-1) \\ 0 & 0 & \frac{2nf}{nf} & 0 \\ \end{matriz}\right]

NDC(z)=\frac{(n+f)z+2nf}{(fn)z}

Sin embargo, debido a que la profundidad se almacena en el rango de 0-1, el valor SV_Position obtenido en el sombreador de fragmentos en realidad se reasigna:

FragSVPos(z)=NDC(Z)*0.5+0.5

//当API为OPENGL时
// { (n-f)/n, f/n, (n-f)/(n*f), 1/n }
float4 _ZBufferParams;

Profundidad de ojo lineal(NDC(Z)*0.5+0.5)=\frac{1}{\left(\frac{(n+f)z+2nf}{-(nf)z}*\frac{1}{2}+ \frac{1}{2}\right)*\frac{nf}{nf}+\frac{1}{ n }}= -z
Profundidad lineal01(NDC(Z)*0.5+0.5)=\frac{1}{\left(\frac{(n+f)z+2nf}{-(nf)z}*\frac{1}{2}+ \frac{1}{2}\right)*\frac{nf}{n}+\frac{f}{ n }}= -\frac{z}{f}

Adquisición de matriz de proyección en Unity

  La matriz se puede obtener llamando a la API de Camera:

Camera camera = Camera.main;
Matrix4x4 oglProj = camera.projectionMatrix

  Esta matriz en realidad está "muerta". Es la matriz de OpenGL (plataforma móvil) que derivamos anteriormente. Unity tiene una API que puede obtener la matriz de la API actual de acuerdo con la plataforma actual:

Matrix4x4 proj = GL.GetGPUProjectionMatrix(camera.projectionMatrix, true);

  El primer parámetro es la matriz obtenida de Camera, y el segundo parámetro es: si renderizar a RT. Este RT incluye un búfer de color normal, y el unity_MatrixVP pasado al Shader se calcula así:

Matrix4x4 unity_MatrixVP = proj * camera.worldToCameraMatrix;

Construye dos matrices en Unity con python

  Para las pruebas:

import glm
import numpy as np

near = n = 0.3
far = f = 1000

fov = glm.radians(60)
aspect = 2

dxproj_reverse_z = np.matrix([[1 / (aspect * np.tan(fov/2)), 0, 0, 0], 
    [0, -1 / np.tan(fov/2), 0, 0],
    [0, 0, n / (f - n), -1],
    [0, 0, n * f / (f - n), 0]])
print(glm.transpose(dxproj_reverse_z))

#print(glm.transpose(glm.perspective(fov, aspect, near, far)))
oglproj = np.matrix([
    [1 / (aspect * np.tan(fov/2)), 0, 0, 0],
    [0, 1 / np.tan(fov/2), 0, 0],
    [0, 0, -(f + n) / (f - n), -1],
    [0, 0, -2*n*f/(f-n), 0],])

print(glm.transpose(oglproj))

Supongo que te gusta

Origin blog.csdn.net/tangyin025/article/details/126564270
Recomendado
Clasificación