Bagaimana cara membuat lensa wide-angle / fisheye dengan HLSL?


29

Apa konsep yang perlu diimplementasikan untuk mencapai efek lensa sudut lebar dari berbagai ekstremitas?

Pseudocode dan penjelasan khusus yang merujuk pada berbagai tahapan dari pipa konten, serta informasi apa yang perlu diteruskan dari kode sumber ke HLSL akan sangat berguna.

Juga, apa perbedaan antara menerapkan lensa sudut lebar, dan mata ikan?

Jawaban:


37

Lensa sudut lebar tidak boleh berperilaku berbeda dari model lensa biasa lainnya. Mereka hanya memiliki FOV yang lebih besar (dalam D3DXMatrixPerspectiveFovLHarti - saya asumsikan Anda menggunakan DirectX), atau nilai kiri / kanan dan bawah / atas yang lebih besar (dalam glFrustumarti OpenGL ).

Saya percaya bagian yang sangat menarik terletak pada pemodelan lensa fisheye. Ada Fisheye Quake yang bisa Anda pelajari, ia datang dengan sumbernya.

Proyeksi mata ikan yang benar

Proyeksi lensa mata ikan, bagaimanapun, sangat non-linear. Dalam jenis lensa yang lebih umum (sepengetahuan saya, yang terbatas pada kamera pengintai), sebuah titik Mdi ruang diproyeksikan ke permukaan belahan unit, kemudian permukaan itu mengalami proyeksi paralel ke cakram 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

Ada pemetaan fisheye lain yang mungkin memberikan efek lebih menarik. Terserah kamu.

Saya bisa melihat dua cara untuk menerapkan efek fisheye di HLSL.

Metode 1: melakukan proyeksi pada vertex shader

Keuntungan : hampir tidak ada yang perlu diubah dalam kode. Shader fragmen sangat sederhana. Daripada:

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

Anda melakukan sesuatu seperti ini (mungkin banyak disederhanakan):

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

Kelemahan : karena seluruh pipa render dipikirkan untuk transformasi linear, proyeksi yang dihasilkan tepat untuk simpul, tetapi semua variasi akan salah, serta koordinat tekstur, dan segitiga akan tetap tampak sebagai segitiga meskipun harus tampak terdistorsi.

Penanganan masalah : dapat diterima untuk mendapatkan perkiraan yang lebih baik dengan mengirimkan geometri yang disempurnakan ke GPU, dengan lebih banyak subdivisi segitiga. Ini mungkin juga dilakukan dalam geometri shader, tetapi karena langkah ini terjadi setelah vertex shader, geometri shader akan sangat kompleks karena harus melakukan proyeksi tambahan sendiri.

Metode 2: melakukan proyeksi pada shader fragmen

Metode lain adalah membuat adegan menggunakan proyeksi sudut lebar, kemudian mendistorsi gambar untuk mencapai efek mata ikan menggunakan shader fragmen layar penuh.

Jika titik Mmemiliki koordinat (x,y)pada layar mata ikan, itu berarti titik tersebut memiliki koordinat (x,y,z)pada permukaan belahan bumi, dengan z = sqrt(1-x*x-y*y). Yang berarti ada koordinat (ax,ay)dalam adegan kami yang diberikan dengan FOV thetaseperti itu a = 1/(z*tan(theta/2)). (Tidak 100% yakin tentang matematika saya di sini, saya akan periksa lagi malam ini).

Oleh karena itu shader fragmen akan menjadi seperti ini:

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

Keuntungan : Anda mendapatkan proyeksi sempurna tanpa distorsi selain dari yang disebabkan oleh akurasi piksel.

Kekurangan : Anda tidak dapat secara fisik melihat seluruh pemandangan, karena FOV tidak dapat mencapai 180 derajat. Juga, semakin besar FOV, semakin buruk presisi di tengah gambar ... yang tepat di mana Anda ingin presisi maksimum.

Penanganan masalah : kehilangan presisi dapat ditingkatkan dengan melakukan beberapa render pass, misalnya 5, dan melakukan proyeksi dengan cara peta kubus. Solusi lain yang sangat sederhana adalah hanya memotong gambar akhir ke FOV yang diinginkan - bahkan jika lensa itu sendiri memiliki 180 derajat FOV, Anda mungkin ingin membuat hanya sebagian darinya. Ini disebut mata ikan "full-frame" (yang agak ironis, karena memberi kesan bahwa Anda mendapatkan "penuh" sesuatu saat benar-benar memotong gambar).

(Catatan: jika Anda menemukan ini berguna tetapi tidak cukup jelas, tolong beritahu saya, saya merasa ingin menulis artikel yang lebih rinci tentang ini).


sangat berguna, dan saya akan menyambut artikel yang lebih rinci yang ingin Anda tulis dengan sepenuh hati!
SirYakalot

Apakah mungkin untuk menggabungkan kedua pendekatan untuk mendapatkan hasil yang lebih baik? Pertama, lakukan proyeksi dalam VS untuk melihat semuanya dan kemudian tidak memproyeksikan dalam PS dan memproyeksikan lagi untuk mendapatkan UV yang benar dan segalanya? Mungkin perlu mengirim beberapa parameter lagi ke PS untuk tidak memproyeksikan dengan benar ke aslinya.
Ondrej Petrzilka

3

Seharusnya begitu z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y), bukan?

Implementasi GLSL saya adalah:

#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, bagaimana fovTheta dihitung?
tom

1
Saya hanya mengedit jawaban ini untuk menyesuaikan pemformatan, saya yakin Anda ingin langsung ke @manman.
Josh
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.