Jak utworzyć obiektyw szerokokątny / rybie oko za pomocą HLSL?


29

Jakie koncepcje należy wdrożyć, aby uzyskać efekt soczewki szerokokątnej o różnych kończynach?

Bardzo przydatny byłby pseudokod i szczegółowe objaśnienie odnoszące się do różnych etapów potoku treści, a także informacje, które należy przekazać z kodu źródłowego do HLSL.

Jakie są różnice między obiektywem szerokokątnym a rybie oko?

Odpowiedzi:


37

Obiektyw szerokokątny nie powinien zachowywać się inaczej niż inne zwykłe modele obiektywów. Mają po prostu większy FOV (w tym D3DXMatrixPerspectiveFovLHsensie - zakładam, że używasz DirectX) lub większe lewe / prawe i dolne / górne wartości (w OpenGLglFrustum sensie ).

Uważam, że naprawdę interesująca część polega na modelowaniu obiektywu typu rybie oko. Istnieje Quake rybie oko , którego możesz się uczyć, pochodzi ze źródła.

Prawdziwa projekcja rybiego oka

Jednak rzut obiektywu typu rybie oko jest wysoce nieliniowy. W bardziej powszechnym (o ile mi wiadomo, ograniczonym do kamer monitorujących) rodzaju soczewce punkt Mw przestrzeni jest rzutowany na powierzchnię półkuli jednostki, a następnie powierzchnia ta przechodzi równolegle na dysk jednostki:

           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

Istnieją inne odwzorowania typu rybie oko które mogą dać bardziej interesujące efekty. To zależy od Ciebie.

Widzę dwa sposoby na wdrożenie efektu rybiego oka w HLSL.

Metoda 1: Wykonaj projekcję na module cieniującym wierzchołki

Zaleta : prawie nic nie trzeba zmieniać w kodzie. Moduł cieniujący fragmenty jest niezwykle prosty. Zamiast:

...
float4 screenPoint = mul(worldPoint, worldViewProjMatrix);
...

Robisz coś takiego (prawdopodobnie można to znacznie uprościć):

...
// 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);
...

Wady : ponieważ cały potok renderowania był przemyślany dla przekształceń liniowych, wynikowy rzut jest dokładny dla wierzchołków, ale wszystkie zmiany będą niepoprawne, a także współrzędne tekstury, a trójkąty będą nadal pojawiać się jako trójkąty, nawet jeśli powinny być zniekształcone.

Obejścia : można uzyskać lepsze przybliżenie, wysyłając ulepszoną geometrię do GPU, z większą liczbą podziałów trójkątów. Można to również wykonać w module cieniującym geometrię, ale ponieważ ten krok ma miejsce po module cieniującym wierzchołek, moduł cieniujący geometria byłby dość złożony, ponieważ musiałby wykonać własne dodatkowe rzuty.

Metoda 2: wykonaj projekcję na module cieniującym fragmenty

Inną metodą byłoby renderowanie sceny przy użyciu projekcji szerokokątnej, a następnie zniekształcenie obrazu w celu uzyskania efektu rybiego oka za pomocą pełnoekranowego modułu cieniującego fragmenty.

Jeśli punkt Mma współrzędne (x,y)na ekranie rybiego oka, oznacza to, że miał współrzędne (x,y,z)na powierzchni półkuli, z z = sqrt(1-x*x-y*y). Co oznacza, że ​​miał współrzędne (ax,ay)w naszej scenie renderowane z FOV tego thetatypu a = 1/(z*tan(theta/2)). (Nie jestem w 100% pewien co do mojej matematyki tutaj, sprawdzę ponownie dziś wieczorem).

Moduł cieniujący fragmenty byłby więc mniej więcej taki:

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);
}

Zaleta : otrzymujesz doskonałą projekcję bez zniekształceń oprócz tych spowodowanych dokładnością pikseli.

Wada : nie można fizycznie zobaczyć całej sceny, ponieważ pole widzenia nie może osiągnąć 180 stopni. Ponadto, im większy jest FOV, tym gorsza jest precyzja w środku obrazu ... dokładnie tam, gdzie chcesz maksymalnej precyzji.

Obejścia : utratę precyzji można poprawić, wykonując kilka przejść renderowania, na przykład 5, i wykonując projekcję w formie mapy sześcianu. Innym bardzo prostym obejściem jest po prostu przycięcie końcowego obrazu do żądanego pola widzenia - nawet jeśli sam obiektyw ma pole widzenia o kącie 180 stopni, możesz chcieć renderować tylko jego część. Nazywa się to „pełnoklatkowym” rybie oko (co jest dość ironiczne, ponieważ daje wrażenie, że dostajesz „pełne” coś, podczas gdy faktycznie przycina obraz).

(Uwaga: jeśli uznasz to za przydatne, ale niewystarczająco jasne, powiedz mi, mam ochotę napisać bardziej szczegółowy artykuł na ten temat).


bardzo przydatne i chętnie przyjmuję bardziej szczegółowy artykuł, który chcesz napisać z całego serca!
SirYakalot,

Czy można połączyć oba podejścia, aby uzyskać lepsze wyniki? Najpierw wykonaj projekcję w VS, aby wyświetlić wszystko, a następnie odrzuć projekt w PS i ponownie rzutuj, aby uzyskać prawidłowe UV i wszystko? Konieczne może być przesłanie kilku dodatkowych parametrów do PS, aby cofnąć projekt poprawnie do oryginału.
Ondrej Petrzilka

3

Powinno być z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y), prawda?

Moja implementacja GLSL to:

#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);
}

hej @Josh, jak obliczono fovTheta?
tom

1
Zredagowałem tę odpowiedź tylko w celu dostosowania formatowania. Myślę, że chcesz bezpośrednio zwrócić się do @bman.
Josh
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.