Come posso creare un obiettivo grandangolare / fisheye con HLSL?


29

Quali sono i concetti che devono essere implementati per ottenere l'effetto di un obiettivo grandangolare di estremità diverse?

Sarebbe molto utile lo pseudocodice e la spiegazione specifica riferita alle varie fasi della pipeline di contenuti, nonché quali informazioni debbano essere trasmesse dal codice sorgente a HLSL.

Inoltre, quali sono le differenze tra l'implementazione di un obiettivo grandangolare e un fisheye?

Risposte:


37

Un obiettivo grandangolare non dovrebbe comportarsi in modo diverso rispetto ad altri modelli di obiettivi normali. Hanno solo un FOV più grande (nel D3DXMatrixPerspectiveFovLHsenso - presumo che tu usi DirectX), o valori più grandi a sinistra / destra e in basso / in alto (nel glFrustumsenso 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 Mnello 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 Mha 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 thetatale 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).


molto utile, e accolgo con favore l'articolo più dettagliato che vuoi scrivere con tutto il cuore!
SirYakalot,

Sarebbe possibile combinare entrambi gli approcci per ottenere risultati migliori? Prima fai la proiezione in VS per vedere tutto e poi proietta in PS e proietta nuovamente per ottenere UV e tutto corretti? Può essere necessario inviare alcuni altri parametri a PS per non proiettare correttamente l'originale.
Ondrej Petrzilka,

3

Dovrebbe essere z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y)vero?

La mia implementazione GLSL è:

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float fovTheta; // FOV's theta

// fisheye
void main (void)
{   
    vec2 uv = v_texCoord - 0.5;
    float z = sqrt(1.0 - uv.x * uv.x - uv.y * uv.y);
    float a = 1.0 / (z * tan(fovTheta * 0.5));
//  float a = (z * tan(fovTheta * 0.5)) / 1.0; // reverse lens
    gl_FragColor = texture2D(u_texture, (uv* a) + 0.5);
}

hey @Josh, come è stato calcolato fovTheta?
Tom,

1
Ho modificato solo questa risposta per regolare la formattazione, credo che tu voglia indirizzare direttamente @bman.
Josh,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.