Rendering stereo anggaran rendah dalam beberapa baris kode (stereogram, anaglyph, stereoscope)

Akhir pekan yang lain telah tiba, yang berarti saya sedang menulis beberapa lusin baris kode lagi dan membuat satu atau dua ilustrasi. Pada artikel sebelumnya saya sudah menjelaskan cara melakukan ray tracing dan bahkan meledakkan barang . Ini mungkin mengejutkan Anda, tetapi grafik komputer cukup mudah: bahkan beberapa ratus baris telanjang C ++ dapat menghasilkan beberapa citra yang sangat menarik.

Topik hari ini adalah visi binokular, dan kami bahkan tidak akan melanggar 100 garis penghalang melakukannya. Karena kita dapat menggambar adegan 3D, bodoh jika mengabaikan pasangan stereo, jadi hari ini kita akan membuat sesuatu seperti ini:




Kegilaan murni pencipta Karpet Ajaib masih menghantam pikiranku. Bagi mereka yang tidak sadar, game ini memungkinkan Anda untuk melakukan render 3D dalam mode anaglyph dan stereogram dari menu pengaturan utama ! Itu liar bagi saya.

Paralaks


Jadi, mari kita mulai. Sebagai permulaan, apa yang menyebabkan alat penglihatan kita merasakan kedalaman pada objek? Ada istilah pintar “paralaks”. Mari fokus pada layar. Segala sesuatu yang ada di dalam bidang layar didaftarkan oleh otak kita sebagai satu objek. Tetapi jika seekor lalat terbang di antara mata dan layar kita, maka otak melihatnya sebagai dua objek. Laba-laba di belakang layar juga akan berlipat ganda.



Otak kita sangat efisien dalam menganalisis gambar yang sedikit berbeda. Ia menggunakan perbedaan binokular untuk mendapatkan informasi tentang kedalaman dari gambar 2D yang berasal dari retina menggunakan stereopsis . Nah, abaikan kata-kata besar dan mari kita gambar saja!

Mari kita bayangkan bahwa monitor kita adalah jendela ke dunia virtual :)



Tugas kita adalah menggambar dua gambar dari apa yang kita lihat melalui "jendela" itu, satu untuk setiap mata. Pada gambar di atasnya "sandwich" merah-biru. Mari kita lupakan sekarang bagaimana mengirimkan gambar-gambar ini ke otak kita, pada tahap ini kita hanya perlu menyimpan dua file terpisah. Secara khusus, saya ingin tahu cara mendapatkan gambar-gambar ini menggunakan raytracer kecil saya .

Mari kita asumsikan sudut tidak berubah dan itu adalah vektor (0,0, -1). Mari kita asumsikan kita bisa memindahkan kamera ke area di antara mata, tapi lalu apa? Satu detail kecil: pandangan frustrasi melalui "jendela" kita asimetris. Tetapi pelacak sinar kami hanya dapat membuat frustum simetris:



Dan apa yang kita lakukan sekarang? Cheat :)

Kita dapat membuat gambar yang sedikit lebih lebar dari yang kita butuhkan dan kemudian cukup memotong bagian tambahan:



Anaglyph


Saya pikir kami telah membahas mekanisme rendering dasar, dan sekarang kami menangani masalah pengiriman gambar ke otak kita. Cara paling sederhana adalah kacamata jenis ini:



Kami membuat dua render skala abu-abu dan menetapkan gambar kiri dan kanan untuk masing-masing saluran merah dan biru. Inilah yang kami dapatkan:



Kaca merah memotong satu saluran, sedangkan kaca biru memotong yang lain. Dikombinasikan, mata mendapatkan gambar yang berbeda dan kami melihatnya dalam 3D. Berikut ini modifikasi pada komit utama dari tinyraytracer. Perubahan tersebut termasuk posisi kamera untuk perakitan mata dan saluran.

Anaglyph renders adalah salah satu cara tertua untuk menonton gambar stereo (yang dihasilkan komputer). Ini memiliki banyak kelemahan, misalnya, transmisi warna yang buruk. Tetapi di sisi lain, ini sangat mudah dibuat di rumah.

Jika Anda tidak memiliki kompiler di komputer Anda, itu bukan masalah. Jika Anda memiliki akun guithub, Anda dapat melihat, mengedit, dan menjalankan kode (sic!) Dalam satu klik di browser Anda.

Buka di gitpod

Saat Anda membuka tautan ini, gitpod menciptakan mesin virtual untuk Anda, meluncurkan VS Code, dan membuka terminal pada mesin jarak jauh. Dalam histori perintah (klik pada konsol dan tekan tombol atas) ada satu set lengkap perintah yang memungkinkan Anda untuk mengkompilasi kode, meluncurkannya dan membuka gambar yang dihasilkan.

Stereoskop


Dengan smartphone yang menjadi arus utama, kami ingat tentang penemuan abad ke-19 yang disebut stereoscope. Beberapa tahun yang lalu Google menyarankan untuk menggunakan dua lensa (yang, sayangnya, sulit dibuat di rumah, Anda harus membelinya), sedikit kardus (tersedia di mana saja) dan smartphone (di saku Anda) untuk membuatnya agak dapat dipercaya Kacamata VR.



Mereka banyak di AliExpress dan biaya seperti $ 3 sepasang. Dibandingkan dengan rendering anaglyph, kami bahkan tidak punya banyak hal untuk dilakukan: cukup ambil dua foto dan letakkan berdampingan. Inilah komitnya .



Sebenarnya, tergantung pada lensa kita mungkin perlu memperbaiki distorsi lensa , tapi aku tidak peduli dengan itu, karena itu terlihat baik-baik saja. Tetapi jika kita benar-benar perlu menerapkan pra-distorsi barel yang mengkompensasi distorsi alami dari lensa, begitulah tampilannya untuk ponsel cerdas dan kacamata saya:



Inilah tautan gitpod:

Buka di gitpod

Autostereogram


Dan apa yang kita lakukan jika kita tidak ingin menggunakan peralatan tambahan? Maka hanya ada satu opsi - menyipitkan mata. Gambar sebelumnya, sejujurnya, cukup untuk menonton stereo, cukup julingkan mata Anda (baik menyilangkan mata atau menempelkannya). Berikut adalah skema yang memberi tahu kami cara menonton ilustrasi sebelumnya. Dua garis merah menunjukkan gambar yang dipersepsikan oleh retina kiri, dua garis biru - retina kanan.



Jika kita fokus pada layar, maka empat gambar bergabung menjadi dua. Jika kita menyilangkan mata, atau fokus pada objek yang jauh, adalah mungkin untuk memberi makan otak "tiga" gambar. Gambar pusat tumpang tindih, yang menciptakan efek stereo.

Orang-orang yang berbeda menggunakan metode yang berbeda: misalnya, saya tidak bisa menyilangkan mata saya, tetapi membatasinya dengan mudah. Sangat penting bahwa autostereogram yang dibangun untuk metode tertentu hanya boleh dilihat dengan metode itu, atau kita mendapatkan peta kedalaman terbalik (ingat paralaks positif dan negatif?). Masalahnya adalah sulit untuk menyilangkan mata, sehingga hanya bekerja pada gambar kecil. Tetapi bagaimana jika kita menginginkan yang lebih besar? Mari kita mengorbankan warna sepenuhnya dan hanya fokus pada bagian persepsi kedalaman. Inilah gambar yang kami harapkan pada akhir artikel:



Ini adalah autostereogram bermata tembok. Bagi mereka yang lebih suka metode lain, inilah gambar untuk itu . Jika Anda tidak terbiasa dengan autostereogram, cobalah kondisi yang berbeda: layar penuh, gambar lebih kecil, kecerahan, kegelapan. Tujuan kami adalah untuk membingkai mata kami sehingga dua strip vertikal di dekatnya tumpang tindih. Cara termudah adalah fokus pada bagian kiri atas gambar, karena jelas. Secara pribadi, saya membuka gambar di layar penuh. Jangan lupa untuk menghapus kursor mouse juga!

Jangan berhenti pada efek 3D yang tidak lengkap. Jika Anda samar-samar melihat bentuk bulat, dan efek 3D lemah, ilusi itu tidak lengkap. Bola seharusnya "melompat" keluar dari layar ke arah penonton, efeknya harus stabil dan berkelanjutan. Stereopsis memiliki gisteresis: sekali Anda mendapatkan gambar yang stabil, itu akan semakin rinci semakin lama Anda mengamatinya. Semakin jauh mata dari layar, semakin besar efek persepsi kedalaman.

Stereogram ini dibuat menggunakan metode yang disarankan 25 tahun yang lalu dalam artikel ini: " Menampilkan Gambar 3D: Algoritma untuk Gambar Tunggal Random Dot Stereogram ."

Mari kita mulai


Titik awal untuk membuat autostereogram adalah peta kedalaman (karena kita mengabaikan warna). Komit ini menarik gambar berikut:



Pesawat yang lebih dekat dan lebih jauh mengatur kedalaman kita: titik terjauh di peta saya memiliki kedalaman 0, sedangkan yang terdekat memiliki kedalaman 1.

Prinsip-prinsip inti


Katakanlah mata kita berada pada jarak dari layar. Kami menempatkan pesawat jauh (imajiner) kami (z = 0) pada jarak yang sama "di belakang" layar. Kami memilih variabel μ, yang menentukan lokasi pesawat dekat (z = 1), yang akan berada pada jarak μd dari pesawat jauh. Untuk kode saya, saya memilih μ = ⅓. Secara keseluruhan, seluruh "dunia" kita hidup pada jarak dari d-μd ke d di belakang layar. Katakanlah kita tahu jarak antara mata (dalam piksel, saya memilih 400 piksel):



Jika kita melihat titik merah, maka dua piksel yang ditandai dengan warna hijau harus memiliki warna yang sama dalam stereogram. Bagaimana cara menghitung jarak di antara mereka? Mudah Jika titik yang diproyeksikan saat ini memiliki kedalaman z, maka paralaks dibagi dengan jarak antara mata sama dengan fraksi antara kedalaman yang sesuai: p / e = (d-dμz) / (2d-dμz). Omong-omong, perhatikan bahwa d disederhanakan dan tidak muncul di tempat lain! Kemudian p / e = (1-μz) / (2-μz), artinya parallax sama dengan p = e * (1-μz) / (2-μz) piksel.

Gagasan utama di balik autostereogram adalah: kita pergi melalui seluruh peta kedalaman, untuk setiap nilai kedalaman kita menentukan piksel mana yang akan memiliki warna yang sama dan meletakkannya ke dalam sistem kendala kami. Kemudian kita mulai dari gambar acak dan mencoba memenuhi semua kendala yang telah kita tentukan sebelumnya.

Mempersiapkan gambar sumber


Di sini kita menyiapkan gambar yang nantinya akan dibatasi oleh kendala paralaks. Inilah komit , dan itu menarik ini:



Perhatikan bahwa sebagian besar warna acak, terlepas dari saluran merah tempat saya meletakkan rand () * sin untuk membuat pola periodik. Garis-garis adalah 200 piksel terpisah, yang (diberi μ = 1/3 dan e = 400) nilai paralaks maksimum di dunia kita (pesawat jauh). Pola ini secara teknis tidak diperlukan, tetapi akan membantu memfokuskan mata.

Render autostereogram


Sebenarnya, kode lengkap yang menggambar autostereogram terlihat seperti ini:

int parallax(const float z) { const float eye_separation = 400.; // interpupillary distance in pixels const float mu = .33; // if the far plane is a distance D behind the screen, then the near plane is a distance mu*D in front of the far plane return static_cast<int>(eye_separation*((1.-z*mu)/(2.-z*mu))+.5); } size_t uf_find(std::vector<size_t> &same, size_t x) { return same[x]==x ? x : uf_find(same, same[x]); } void uf_union(std::vector<size_t> &same, size_t x, size_t y) { if ((x=uf_find(same, x)) != (y=uf_find(same, y))) same[x] = y; } int main() { [...] for (size_t j=0; j<height; j++) { // autostereogram rendering loop std::vector<size_t> same(width); std::iota(same.begin(), same.end(), 0); // initialize the union-find data structure (same[i]=i) for (size_t i=0; i<width; i++) { // put the constraints int par = parallax(zbuffer[i+j*width]); int left = i - par/2; int right = left + par; // works better than i+par/2 for odd values of par if (left>=0 && right<(int)width) uf_union(same, left, right); // left and right pixels will have the same color } for (size_t i=0; i<width; i++) { // resolve the constraints size_t root = uf_find(same, i); for (size_t c=0; c<3; c++) framebuffer[(i+j*width)*3+c] = framebuffer[(root+j*width)*3+c]; } } [...] 


Inilah komit , fungsi parallax (const float z) memberi kita jarak antara piksel dengan warna yang sama untuk nilai kedalaman saat ini. Kami merender stereogram baris demi baris, karena baris tidak tergantung satu sama lain (kami tidak memiliki paralaks vertikal). Loop utama hanya berulang melalui setiap baris; setiap kali dimulai dengan satu set piksel yang tidak terbatas, dan kemudian untuk setiap piksel itu menambah satu kendala kesetaraan. Pada akhirnya, ini memberi kita sejumlah cluster dengan piksel berwarna yang sama. Misalnya, piksel dengan indeks kiri dan kanan harus berakhir identik.

Bagaimana cara menyimpan set kendala ini? Cara paling sederhana adalah struktur data union-find . Saya tidak akan merinci, cukup buka Wikipedia, ini benar-benar tiga baris kode. Poin utamanya adalah bahwa untuk setiap kluster ada piksel "root" tertentu yang bertanggung jawab atas kluster. Pixel root mempertahankan warna awalnya, dan semua piksel lain dalam cluster harus diperbarui:

  for (size_t i=0; i<width; i++) { // resolve the constraints size_t root = uf_find(same, i); for (size_t c=0; c<3; c++) framebuffer[(i+j*width)*3+c] = framebuffer[(root+j*width)*3+c]; } 


Buka di gitpod

Kesimpulan


Itu dia, sungguh. Dua puluh baris kode dan autostereogram kami siap untuk Anda lihat. Ngomong-ngomong, jika kita berusaha cukup keras, dimungkinkan untuk mengirimkan informasi warna.

Saya tidak membahas sistem stereoskopis lainnya seperti sistem 3D terpolarisasi , karena harganya jauh lebih mahal. Jika saya melewatkan sesuatu, silakan koreksi saya!

Source: https://habr.com/ru/post/id441014/


All Articles