Un obiettivo grandangolare non dovrebbe comportarsi in modo diverso rispetto ad altri modelli di obiettivi normali. Hanno solo un FOV più grande (nel D3DXMatrixPerspectiveFovLH
senso - presumo che tu usi DirectX), o valori più grandi a sinistra / destra e in basso / in alto (nel glFrustum
senso di OpenGL ).
Credo che la parte davvero interessante risieda nella modellazione dell'obiettivo fisheye. C'è Fisheye Quake che puoi studiare, arriva con la fonte.
La vera proiezione fisheye
La proiezione di un obiettivo fisheye, tuttavia, è altamente non lineare. Nel tipo più comune (a mia conoscenza, che è limitato alle telecamere di sorveglianza), un punto M
nello spazio viene proiettato sulla superficie di un emisfero unitario, quindi quella superficie subisce una proiezione parallela sul disco dell'unità:
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
Esistono altre mappature fisheye che possono dare effetti più interessanti. Tocca a voi.
Vedo due modi per implementare l'effetto fisheye in HLSL.
Metodo 1: eseguire la proiezione sullo shader di vertice
Vantaggio : quasi nulla deve essere modificato nel codice. Il framment shader è estremamente semplice. Piuttosto che:
...
float4 screenPoint = mul(worldPoint, worldViewProjMatrix);
...
Fai qualcosa del genere (probabilmente può essere semplificato molto):
...
// 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);
...
Svantaggi : poiché l'intera pipeline di rendering è stata pensata per le trasformazioni lineari, la proiezione risultante è esatta per i vertici, ma tutte le variazioni saranno errate, così come le coordinate della trama, e i triangoli appariranno comunque come triangoli anche se dovrebbero apparire distorti.
Soluzioni alternative : potrebbe essere accettabile ottenere una migliore approssimazione inviando una geometria raffinata alla GPU, con più suddivisioni triangolari. Questo potrebbe anche essere eseguito in uno shader di geometria, ma poiché questo passaggio si verifica dopo lo shader di vertice, lo shader di geometria sarebbe piuttosto complesso perché dovrebbe eseguire le proprie proiezioni aggiuntive.
Metodo 2: eseguire la proiezione sullo shader di frammento
Un altro metodo sarebbe quello di rendere la scena usando una proiezione grandangolare, quindi distorcere l'immagine per ottenere un effetto fisheye usando uno shader di frammento a schermo intero.
Se punto M
ha coordinate (x,y)
nella schermata fisheye, significa che aveva coordinate (x,y,z)
sulla superficie dell'emisfero, con z = sqrt(1-x*x-y*y)
. Il che significa che aveva coordinate (ax,ay)
nella nostra scena rese con un FOV di theta
tale a = 1/(z*tan(theta/2))
. (Non sono sicuro al 100% della mia matematica qui, controllerò di nuovo stasera).
Il framment shader sarebbe quindi qualcosa del genere:
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);
}
Vantaggio : ottieni una proiezione perfetta senza distorsioni oltre a quelle dovute alla precisione dei pixel.
Svantaggio : non è possibile visualizzare fisicamente l'intera scena, poiché il FOV non può raggiungere i 180 gradi. Inoltre, più grande è il FOV, peggiore è la precisione al centro dell'immagine ... che è esattamente dove vuoi la massima precisione.
Soluzioni alternative : la perdita di precisione può essere migliorata eseguendo diversi passaggi di rendering, ad esempio 5, ed eseguendo la proiezione come una mappa cubica. Un'altra soluzione molto semplice è semplicemente ritagliare l'immagine finale nel FOV desiderato - anche se l'obiettivo stesso ha un FOV a 180 gradi, potresti voler renderne solo una parte. Questo si chiama fisheye "full frame" (che è un po 'ironico, dato che dà l'impressione di ottenere qualcosa di "pieno" mentre in realtà ritaglia l'immagine).
(Nota: se lo hai trovato utile ma non abbastanza chiaro, per favore dimmelo, ho voglia di scrivere un articolo più dettagliato al riguardo).