¿Ortho y Persp están invirtiendo el signo de profundidad Z?
Las coordenadas NDC para OpenGL forman un cubo, cuyo -Z
lado presiona contra la pantalla mientras que su +Z
lado está más alejado.
Cuando uso...
// ortho arguments are: left, right, bottom, top, near, far
pos = pos * glm::ortho<float>(-1, 1, -1, 1, -1, 1);
...el z
componente de pos
se refleja; -1 se convierte en 1, 10 se convierte en -10, etc.
glm::persp hace algo similar y es un poco extraño. Si una posición es z
igual a near
, esperaría que descansara en el plano del cubo NDC que mira hacia la pantalla, pero en cambio su signo se invierte arbitrariamente; Ni siquiera aterriza en el lado más alejado.
¿Por qué es esto?
Las coordenadas NDC para OpenGL forman un cubo, cuyo lado -Z presiona contra la pantalla mientras que su lado +Z está más alejado.
Eché un vistazo al tutorial de Song Ho Ahn sobre transformaciones OpenGL para asegurarme de no decir ninguna tontería.
Proyección en perspectiva
En la proyección en perspectiva, un punto 3D en una pirámide truncada (coordenadas del ojo) se asigna a un cubo (NDC); el rango de la coordenada x de [l, r] a [-1, 1], la coordenada y de [b, t] a [-1, 1] y la coordenada z de [-n, -f] a [-1, 1].
Tenga en cuenta que las coordenadas del ojo están definidas en el sistema de coordenadas para diestros, pero NDC usa el sistema de coordenadas para zurdos . Es decir, la cámara en el origen mira a lo largo del eje -Z en el espacio ocular, pero mira a lo largo del eje +Z en NDC.
(El énfasis es mío).
Proporciona el siguiente bonito ejemplo de esto:
Entonces, llegué a la conclusión de que
glm::ortho<float>(-1, 1, -1, 1, -1, 1);
no debería producir una matriz de identidad sino una donde el eje z se refleja, por ejemplo, algo como
| 1 0 0 0 |
| 0 1 0 0 |
| 0 0 -1 0 |
| 0 0 0 1 |
Como no lo tengo glm
a mano, tomé las líneas de código relevantes del código fuente en github ( glm ). Indagando un poco en el código fuente, finalmente encontré la implementación de glm::ortho()
en orthoLH_ZO()
:
template<typename T>
GLM_FUNC_QUALIFIER mat<4, 4, T, defaultp> orthoLH_ZO(T left, T right, T bottom, T top, T zNear, T zFar)
{
mat<4, 4, T, defaultp> Result(1);
Result[0][0] = static_cast<T>(2) / (right - left);
Result[1][1] = static_cast<T>(2) / (top - bottom);
Result[2][2] = static_cast<T>(1) / (zFar - zNear);
Result[3][0] = - (right + left) / (right - left);
Result[3][1] = - (top + bottom) / (top - bottom);
Result[3][2] = - zNear / (zFar - zNear);
return Result;
}
Transformé un poco este código para hacer el siguiente ejemplo:
#include <iomanip>
#include <iostream>
struct Mat4x4 {
double values[4][4];
Mat4x4() { }
Mat4x4(double val)
{
values[0][0] = val; values[0][1] = 0.0; values[0][2] = 0.0; values[0][3] = 0.0;
values[1][0] = 0.0; values[1][1] = val; values[1][2] = 0.0; values[1][3] = 0.0;
values[2][0] = 0.0; values[2][1] = 0.0; values[2][2] = val; values[2][3] = 0.0;
values[3][0] = 0.0; values[3][1] = 0.0; values[3][2] = 0.0; values[3][3] = val;
}
double* operator[](unsigned i) { return values[i]; }
const double* operator[](unsigned i) const { return values[i]; }
};
Mat4x4 ortho(
double left, double right, double bottom, double top, double zNear, double zFar)
{
Mat4x4 result(1.0);
result[0][0] = 2.0 / (right - left);
result[1][1] = 2.0 / (top - bottom);
result[2][2] = - 1;
result[3][0] = - (right + left) / (right - left);
result[3][1] = - (top + bottom) / (top - bottom);
return result;
}
std::ostream& operator<<(std::ostream &out, const Mat4x4 &mat)
{
for (unsigned i = 0; i < 4; ++i) {
for (unsigned j = 0; j < 4; ++j) {
out << std::fixed << std::setprecision(3) << std::setw(8) << mat[i][j];
}
out << '\n';
}
return out;
}
int main()
{
Mat4x4 matO = ortho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
std::cout << matO;
return 0;
}
Compilado e iniciado, proporciona el siguiente resultado:
1.000 0.000 0.000 0.000
0.000 1.000 0.000 0.000
0.000 0.000 -1.000 0.000
-0.000 -0.000 0.000 1.000
Demostración en vivo en coliru
¡Eh! z se escala con -1, es decir, los valores de z se reflejan en el plano xy (como se esperaba).
Por tanto, la observación de OP es totalmente correcta y razonable:
...se refleja el componente z de pos; -1 se convierte en 1, 10 se convierte en -10, etc.
La parte más dificil:
¿Por qué es esto?
Mi suposición personal: uno de los gurús de la SGI que inventó todo este material de GL hizo esto con su sabiduría.
Otra suposición: en el espacio ocular, el eje x apunta hacia la derecha y el eje y apunta hacia arriba. Traduciendo esto a coordenadas de pantalla, el eje y debe apuntar hacia abajo (ya que los píxeles generalmente/técnicamente se dirigen comenzando en la esquina superior izquierda). Entonces, esto introduce otro eje reflejado que cambia la lateralidad del sistema de coordenadas (nuevamente).
Es un poco insatisfactorio y por eso busqué en Google y encontré esto (¿duplicado?):
SO: ¿Por qué el sistema de coordenadas de dispositivo normalizado es para zurdos?