Obiektyw szerokokątny nie powinien zachowywać się inaczej niż inne zwykłe modele obiektywów. Mają po prostu większy FOV (w tym D3DXMatrixPerspectiveFovLH
sensie - 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 M
w 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 M
ma 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 theta
typu 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).
Powinno być
z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y)
, prawda?Moja implementacja GLSL to:
źródło