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
- 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].
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:
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:
Cuando el espacio de visualización se multiplica por esta matriz,
la matriz actual es:
A partir de esto, se obtiene la función de conversión de view->ndc z:
Se espera que cuando se convierta a NDC, el plano cercano sea 0 y el plano lejano sea 1, entonces:
Finalmente obtener la matriz:
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:
Finalmente obtenga la matriz Reverse-Z:
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:
Podemos obtener el espacio de recorte en función de z y w:
Y después de la verificación por división homogénea
, si hay un punto en nuestra escena , debido a la inversión de la matriz Vista Unitaria, se convierte (bajo el espacio de vista), y este punto se introduce: , de manera similar , , .
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:
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:
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:
La matriz final resultante es:
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:
//当API为OPENGL时
// { (n-f)/n, f/n, (n-f)/(n*f), 1/n }
float4 _ZBufferParams;
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))