Як створити ширококутний / риб'ячий лінз за допомогою HLSL?


29

Які поняття потрібно реалізувати для досягнення ефекту ширококутної лінзи різних кінцівок?

Псевдокод та конкретне пояснення стосовно різних стадій контентного конвеєра, а також того, яку інформацію потрібно передати від вихідного коду до HLSL, було б дуже корисним.

Крім того, в чому полягають відмінності між ширококутною лінзою та риб’ячим оком?

Відповіді:


37

Ширококутний об'єктив не повинен вести себе інакше, ніж інші звичайні моделі лінз. Вони просто мають більший FOV (в D3DXMatrixPerspectiveFovLHсенсі - я припускаю, що ви використовуєте DirectX) або більший лівий / правий і нижній / верхній значення (у glFrustumсенсі OpenGL ).

Я вважаю, що дійсно цікава частина полягає в моделюванні лінзи риб'ячого ока. Існує Fisheye Quake, який можна вивчити, він постачається з джерелом.

Справжня проекція риб'ячого ока

Проте проекція лінзи з риб'ячим оком вкрай нелінійна. У більш поширеному (наскільки мені відомо, який обмежується камерами спостереження) об'єктиві, точка Mпростору проектується на поверхню одиничної півкулі, то ця поверхня проходить паралельну проекцію на одиничний диск:

           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

Існують і інші відображення риб'ячого ока, які можуть дати більш цікаві ефекти. Тобі вирішувати.

Я бачу два способи реалізації ефекту «риб'яче око» при ЛПНЩ.

Спосіб 1: виконати проекцію на вершинній шейдері

Перевага : у коді майже нічого не потрібно змінювати. Фрагментний шейдер надзвичайно простий. Замість:

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

Ви робите щось подібне (можливо, це може бути багато спрощено):

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

Недоліки : оскільки весь конвеєр рендерингу вважався лінійним перетворенням, отримана проекція є точною для вершин, але всі зміни будуть помилковими, як і текстурні координати, і трикутники все одно будуть виглядати як трикутники, хоча вони повинні виглядати спотвореними.

Обхідні шляхи : може бути прийнятним отримати краще наближення, надіславши вишукану геометрію до GPU, що має більше трикутних підрозділів. Це також може бути виконано в геометричній шейдері, але оскільки цей крок відбувається після вершинного шейдера, шейдер геометрії був би досить складним, оскільки він повинен був би виконати власні додаткові прогнози.

Спосіб 2: виконати проекцію на фрагменті шейдера

Іншим методом було б візуалізацію сцени за допомогою проекції широкого кута, а потім спотворення зображення для досягнення ефекту риб'ячого ока за допомогою шейдера на весь екран.

Якщо точка Mмає координати (x,y)на екрані риб'ячого ока, це означає, що вона мала координати (x,y,z)на поверхні півкулі, з z = sqrt(1-x*x-y*y). А це означає, що (ax,ay)в нашій сцені були координати, представлені FOV thetaтакого a = 1/(z*tan(theta/2)). (Не на 100% впевнений у моїй математиці тут, я перевіряю ще сьогодні сьогодні).

Таким чином, шейдер фрагмента буде приблизно таким:

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

Перевага : ви отримуєте ідеальну проекцію, без перекосів, крім тих, завдяки точності пікселів.

Недолік : фізично неможливо переглянути всю сцену, оскільки FOV не може досягти 180 градусів. Крім того, чим більше FOV, тим гірша точність в центрі зображення ... саме там, де ви хочете максимальної точності.

Обхідні шляхи : втрату точності можна поліпшити, виконавши кілька пропусків візуалізації, наприклад 5, та виконати проекцію за допомогою кубової карти. Ще одне дуже просте вирішення полягає в тому, щоб просто обрізати кінцеве зображення до потрібного FOV - навіть якщо в самому об’єктиві є 180-градусний FOV, ви можете відтворити лише його частину. Це називається "повним кадром" риб'ячим оком (що начебто іронічно, оскільки створюється враження, що ви отримуєте щось "повне", тоді як воно насправді обробляє зображення).

(Примітка. Якщо ви вважаєте це корисним, але недостатньо зрозумілим, скажіть, будь ласка, мені здається, що я напишу більш детальну статтю про це).


дуже корисно, і я б вітаю більш детальну статтю, яку ви хочете написати від усієї душі!
SirYakalot

Чи можна було б поєднати обидва підходи для отримання кращих результатів? Спочатку зробіть проекцію у VS, щоб все зрозуміти, а потім зніміть проект у PS та знову спроектуйте, щоб отримати правильні УФ та все? Може бути необхідним надіслати ще кілька параметрів на PS, щоб відмовитись правильно від оригіналу.
Ondrej Petrzilka

3

Це має бути z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y), правда?

Моя реалізація 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);
}

Ей @ Джош, як обчислили fovTheta?
Tom

1
Цю відповідь я змінив лише для коригування форматування, я вважаю, що ви хочете звернутися до @bman безпосередньо.
Джош
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.