Una lente gran angular no debe comportarse de manera diferente que otros modelos de lentes normales. Solo tienen un FOV más grande (en el D3DXMatrixPerspectiveFovLH
sentido, supongo que usa DirectX), o valores más grandes a la izquierda / derecha e inferior / superior (en el glFrustum
sentido de OpenGL ).
Creo que la parte realmente interesante radica en modelar la lente ojo de pez. Hay Fisheye Quake que puedes estudiar, viene con la fuente.
La verdadera proyección de ojo de pez
La proyección de una lente ojo de pez, sin embargo, es altamente no lineal. En el tipo de lente más común (que yo sepa, que se limita a las cámaras de vigilancia), M
se proyecta un punto en el espacio sobre la superficie del hemisferio de una unidad, luego esa superficie se proyecta paralelamente en el disco de la unidad:
M
x M: world position
\ M': projection of M on the unit hemisphere
\ ______ M": projection of M' on the unit disc (= the screen)
M'_x' `-.
,' |\ `.
/ | \ \
/ | \ \
| | \ |
__________|_____|____\_________|_________
M" O 1
Hay otras asignaciones de ojo de pez que pueden dar efectos más interesantes. Tu decides.
Puedo ver dos formas de implementar el efecto ojo de pez en HLSL.
Método 1: realice la proyección en el sombreador de vértices
Ventaja : casi nada necesita ser cambiado en el código. El sombreador de fragmentos es extremadamente simple. Más bien que:
...
float4 screenPoint = mul(worldPoint, worldViewProjMatrix);
...
Haces algo como esto (probablemente se puede simplificar mucho):
...
// This is optional, but it computes the z coordinate we will
// need later for Z sorting.
float4 out_Point = mul(in_Point, worldViewProjMatrix);
// This retrieves the world position so that we can project on
// the hemisphere. Normalise this vector and you get M'
float4 tmpPoint = mul(in_Point, worldViewMatrix);
// This computes the xy coordinates of M", which happen to
// be the same as M'.
out_Point.xy = tmpPoint.xy / length(tmpPoint.xyz);
...
Inconvenientes : dado que toda la tubería de renderizado se pensó para transformaciones lineales, la proyección resultante es exacta para vértices, pero todas las variaciones serán incorrectas, así como las coordenadas de textura, y los triángulos seguirán apareciendo como triángulos, aunque deberían aparecer distorsionados.
Soluciones alternativas : podría ser aceptable obtener una mejor aproximación enviando una geometría refinada a la GPU, con más subdivisiones de triángulos. Esto también se puede realizar en un sombreador de geometría, pero dado que este paso ocurre después del sombreador de vértices, el sombreador de geometría sería bastante complejo porque tendría que realizar sus propias proyecciones adicionales.
Método 2: realice la proyección en el sombreador de fragmentos
Otro método sería renderizar la escena usando una proyección de gran angular, luego distorsionar la imagen para lograr un efecto de ojo de pez usando un sombreador de fragmentos de pantalla completa.
Si el punto M
tiene coordenadas (x,y)
en la pantalla de ojo de pez, significa que tenía coordenadas (x,y,z)
en la superficie del hemisferio, con z = sqrt(1-x*x-y*y)
. Lo que significa que tenía coordenadas (ax,ay)
en nuestra escena renderizadas con un FOV de theta
tal a = 1/(z*tan(theta/2))
. (No estoy 100% seguro de mis cálculos aquí, lo revisaré nuevamente esta noche).
Por lo tanto, el sombreador de fragmentos sería algo así:
void main(in float4 in_Point : POSITION,
uniform float u_Theta,
uniform sampler2D u_RenderBuffer,
out float4 out_FragColor : COLOR)
{
z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y);
float a = 1.0 / (z * tan(u_Theta * 0.5));
out_FragColor = tex2D(u_RenderBuffer, (in_Point.xy - 0.5) * 2.0 * a);
}
Ventaja : obtienes una proyección perfecta sin distorsiones aparte de las debidas a la precisión de los píxeles.
Inconveniente : no puede ver físicamente toda la escena, ya que el FOV no puede alcanzar los 180 grados. Además, cuanto más grande sea el FOV, peor será la precisión en el centro de la imagen ... que es precisamente donde desea la máxima precisión.
Soluciones alternativas : la pérdida de precisión se puede mejorar realizando varias pasadas de representación, por ejemplo 5, y haciendo la proyección a la manera de un mapa de cubos. Otra solución muy simple es simplemente recortar la imagen final al FOV deseado; incluso si la lente en sí tiene un FOV de 180 grados, es posible que desee representar solo una parte de ella. Esto se llama ojo de pez "fotograma completo" (lo cual es un poco irónico, ya que da la impresión de que obtienes algo "completo" mientras en realidad recorta la imagen).
(Nota: si lo encuentra útil pero no lo suficientemente claro, dígame, tengo ganas de escribir un artículo más detallado sobre esto).