Doom of SceneKit. Pengalaman Yandex dengan grafik 3D di iOS

- Aku terlalu muda untuk mati.


SceneKit adalah kerangka kerja grafik 3D tingkat tinggi di iOS yang membantu membuat adegan dan efek animasi. Ini termasuk mesin fisik, generator partikel dan serangkaian tindakan sederhana untuk objek 3D yang memungkinkan Anda untuk menggambarkan adegan dalam hal konten - geometri, bahan, pencahayaan, kamera - dan menganimasinya melalui deskripsi perubahan untuk objek-objek ini.



Hari ini kita akan melihat SceneKit dengan tampilan penuh perhatian, sedikit keras, tetapi pertama-tama, mari kita membahas dasar-dasarnya dan melihat seperti apa adegan 3D itu dan apa yang perlu dilakukan untuk membuatnya.


Adegan paling sederhana dari tiga simpul dengan geometri di dalamnya.
Adegan paling sederhana dari tiga node dengan geometri di dalamnya


Pertama, Anda perlu membuat struktur dasar adegan, yang terdiri dari node atau node adegan. Setiap node dapat berisi geometri dan node lainnya. Geometri bisa sederhana, seperti bola, kubus, atau piramida, atau lebih kompleks, dibuat di editor eksternal.


Hamparan bahan
Bahan overlay


Kemudian, untuk geometri ini, Anda perlu menentukan bahan yang akan menentukan representasi dasar dari objek. Setiap bahan itu sendiri mengatur model pencahayaannya sendiri dan, tergantung padanya, menggunakan serangkaian properti yang berbeda. Setiap properti seperti itu biasanya berwarna atau tekstur, tetapi selain opsi yang biasa digunakan ini, ada juga opsi untuk menggunakan CALayer , AVPlayer , dan SKScene .


Tambahkan sumber pencahayaan
Tambahkan sumber pencahayaan


Setelah itu, perlu menambahkan sumber cahaya yang menentukan seberapa baik objek terlihat di satu atau beberapa bagian adegan. Mereka, dengan analogi dengan geometri, harus berbaring di dalam sebuah simpul. SceneKit mendukung berbagai jenis pencahayaan , serta beberapa jenis bayangan .


Efek Out of the Box Boke
Efek Out of the Box Boke


Maka Anda perlu membuat kamera (dan meletakkannya di node terpisah) dan mengatur parameter dasar untuk itu. Ada banyak dari mereka, tetapi dengan bantuan mereka, Anda dapat membuat efek keren. Didukung di luar kotak, bokeh (atau blur), HDR dengan adaptasi, cahaya, SSAO, dan rona / saturasi didukung.


Animasi sederhana di SceneKit
Animasi sederhana di SceneKit


Akhirnya, SceneKit mencakup serangkaian tindakan sederhana untuk objek 3D yang memungkinkan Anda mengatur perubahan adegan seiring waktu. SceneKit juga mendukung tindakan yang dijelaskan dalam JavaScript , tetapi ini adalah topik untuk artikel terpisah.


Interaksi generator partikel dengan mesin fisik dapat menyebabkan tornado!
Interaksi generator partikel dengan mesin fisik dapat menyebabkan tornado!


Selain grafis, fitur utama SceneKit adalah generator partikel dan mesin fisik canggih yang memungkinkan Anda untuk mengatur properti fisik nyata untuk objek dan partikel biasa dari generator.


Sejumlah besar tutorial terperinci telah ditulis tentang semua chip ini. Namun dalam proses pengembangan, kami praktis tidak menggunakan peluang ini ...


Hei, tidak terlalu kasar


Suatu kali saya menulis model pencahayaan untuk game 3D yang lebih baik daripada sinar matahari nyata, memberikan FPS yang dapat diterima pada Nvidia 8800, tetapi saya memutuskan untuk tidak merilis mesinnya, karena Tuhan baik kepada saya dan saya tidak ingin menunjukkan ketidakmampuannya dalam hal ini.
- John Carmack

Kami akan memulai studi terperinci dengan tugas yang agak sederhana yang muncul untuk hampir semua orang yang bekerja cukup serius dengan SceneKit: bagaimana cara memuat model dengan geometri kompleks dan bahan yang terhubung, pencahayaan, dan bahkan animasi?


Ada beberapa cara, dan mereka semua memiliki pro dan kontra:


  1. SCNScene (bernama :) - mendapat heboh dari sebuah bundel,


  2. SCNScene (url: options :) - memuat adegan dengan URL,


  3. SCNScene (mdlAsset :) - mengonversi adegan dari berbagai format,


  4. SCNReferenceNode (url :) - malas memuat adegan.



Dapatkan adegan dari bundel


Anda dapat menggunakan metode standar : letakkan model kami dalam format dae atau scn di bundel scnasset dan muat dari sana dengan analogi dengan UIImage (bernama :).


Tetapi bagaimana jika Anda ingin mengontrol sendiri pembaruan model tanpa merilis pembaruan di App Store setiap kali Anda perlu mengubah beberapa tekstur? Atau misalkan Anda perlu mendukung peta dan model yang dibuat pengguna. Atau - Anda tidak ingin menambah ukuran aplikasi, karena grafik 3D di dalamnya bukan fungsi utama.


Memuat adegan dengan URL


Anda dapat menggunakan konstruktor adegan dari URL file scn. Metode ini mendukung pengunduhan tidak hanya dari sistem file, tetapi juga dari jaringan, tetapi dalam kasus terakhir, Anda dapat melupakan kompresi. Plus, Anda perlu mengonversi model ke format scn terlebih dahulu. Anda bisa, tentu saja, menggunakan dae, tetapi dengan itu muncul serangkaian pembatasan. Misalnya, kurangnya rendering berbasis fisik.


Keuntungan utama metode ini adalah memungkinkan Anda mengkonfigurasi pengaturan impor secara fleksibel. Anda dapat, misalnya, memodifikasi siklus hidup animasi dan membuatnya berulang tanpa henti. Anda dapat secara eksplisit menentukan sumber untuk memuat sumber daya eksternal seperti tekstur, Anda dapat mengubah orientasi dan skala adegan, membuat normals yang hilang untuk geometri, menggabungkan seluruh geometri adegan menjadi satu simpul besar atau membuang semua elemen adegan yang tidak sesuai dengan standar format.


Konversi pemandangan dari berbagai format


Opsi ketiga adalah menggunakan konstruktor dengan MDLAsset . Artinya, pertama-tama kita membuat set MDLAs , tersedia dalam kerangka ModelIO, dan kemudian meneruskannya ke konstruktor untuk adegan tersebut.


Opsi ini bagus karena memungkinkan Anda mengunduh banyak format berbeda. Secara resmi, MDLAsset dapat memuat format obj, ply, stl, dan usd, tetapi setelah kehabisan daftar semua format yang mungkin, setidaknya terkait dengan grafik komputer, saya menemukan empat lagi: abc, bsp, vox dan md3, tetapi mereka mungkin tidak sepenuhnya didukung atau tidak di semua sistem, dan bagi mereka Anda perlu memeriksa kebenaran impor.


Penting juga untuk mempertimbangkan bahwa metode ini memiliki overhead untuk konversi, dan menggunakannya dengan sangat hati-hati.


Metode-metode ini memiliki satu perangkap umum: mereka mengembalikan SCNScene, bukan SCNNode. Satu-satunya cara untuk menambahkan konten ke adegan yang ada adalah dengan menyalin semua node anak, dan - Anda dapat dengan mudah melewati langkah ini - animasi dari node root (misalnya, mereka dapat muncul di sana ketika bekerja dengan dae). Selain itu, Anda perlu mempertimbangkan bahwa dalam adegan hanya ada satu lingkungan tekstur (jika Anda tidak menggunakan kustom shaders untuk refleksi).


Lazily memuat adegan


Opsi keempat adalah menggunakan SCNReferenceNode . Ini mengembalikan bukan adegan, tetapi sebuah simpul, yang dengan sendirinya dapat malas (atau atas permintaan) memuat ke dalam dirinya sendiri seluruh hirarki adegan. Dengan demikian, metode ini mirip dengan yang pertama, tetapi di dalam dirinya menyembunyikan semua masalah dengan penyalinan.


Dia memiliki satu hal tetapi: parameter global dari adegan itu hilang.


Ternyata ini adalah cara termudah dan tercepat untuk mengunduh model Anda, tetapi jika Anda memerlukan penyetelan file, metode pertama akan lebih baik.


Sebagai hasilnya, kami memilih opsi pertama, karena paling nyaman bagi kami untuk bekerja dalam format scn, dan bagi para desainer - untuk mengonversi dari format dae ke format itu. Selain itu, kami membutuhkan animasi penyetelan file saat boot.


Sama sekali tidak optimasi prematur


Setelah bermain-main dengan proses ini untuk waktu yang lama, saya dapat memberikan Anda beberapa saran.


Tip yang paling penting adalah untuk mengkonversi file ke scn terlebih dahulu. Kemudian Anda bisa, dengan membuka file di editor adegan bawaan di Xcode, melihat bagaimana objek Anda akan terlihat di SceneKit.


Selain itu, pada kenyataannya, file scn hanyalah representasi biner dari adegan tersebut, jadi memuat darinya akan memakan waktu paling sedikit. Untuk dae yang sama, Anda harus mengurai xml terlebih dahulu, kemudian mengonversi semua jerat, animasi dan bahan. Selain itu, konversi animasi dan materi merupakan sumber masalah potensial. Kami ingat kurangnya dukungan PBR di dae: ternyata jika Anda ingin menggunakannya, Anda harus mengubah jenis semua materi setelah konversi dan secara manual meletakkan tekstur yang sesuai.


Dengan operasi ini, Anda bisa mendapatkan efek samping yang sangat berguna: kompresi tekstur yang signifikan. Cukup dengan membukanya di "Lihat" dan ekspor, ubah format menjadi heic. Rata-rata, operasi sederhana ini menghemat 5 megabyte per model.


Juga, jika Anda mengunduh adegan dari Internet, saya dapat menyarankan Anda untuk mengunduhnya dalam arsip, membongkar dan mentransfer URL dari file scn yang belum dibongkar. Ini akan menghemat Anda dan pengguna megabita tambahan - yang, pada gilirannya, akan mempercepat unduhan, dan juga mengurangi jumlah titik kegagalan. Setuju: membuat permintaan terpisah untuk setiap sumber daya eksternal, dan bahkan di Internet seluler bukanlah cara terbaik untuk meningkatkan keandalan.


Sakit saya banyak


Ketika saya mengendarai mobil, saya sering mendengar hard drive alam semesta berderak, memuat jalan berikutnya.
- John Carmack

Jadi, ketika pekerjaan memuat dan mengimpor model dijalankan, tugas baru muncul: menambahkan berbagai efek dan fitur ke tempat kejadian. Dan percayalah, ada sesuatu untuk diceritakan. Kita mulai dengan menelusuri berbagai konstanta di SceneKit.


Batasan dalam SceneKit segera dipertimbangkan setelah fisika.  Dan sebelum merender bingkai
Batasan dalam SceneKit dianggap segera setelah fisika. Dan sebelum merender bingkai


Batasan, katamu? Apa konstanta? Hanya sedikit orang yang tahu, dan bahkan lebih banyak membicarakannya, tetapi SceneKit memiliki konstanta sendiri. Dan meskipun mereka tidak sefleksibel konstanta di UIkit, Anda masih bisa melakukan banyak hal menarik dengannya.


SCNReplicatorConstraint
SCNReplicatorConstraint


Mari kita mulai dengan konstanta sederhana - SCNReplicatorConstraint . Semua yang ia lakukan adalah menduplikasi posisi, rotasi, dan ukuran objek lain dengan offset tambahan. Seperti halnya semua konstanta lain, ia dapat mengubah kekuatan dan mengatur bendera inkrementalitas. Kedua parameter terbaik dapat ditampilkan pada konstanta ini.


Mengurangi Kekuatan 10 Kali
Mengurangi Kekuatan 10 Kali


Kekuatan memengaruhi seberapa banyak transformasi yang diterapkan pada objek. Dan karena posisi objek target berubah setiap frame - objek bayangan mendekati sepersepuluh dari perbedaan jarak. Karena itu, efek penundaan muncul.


Peningkatan meningkat dan meningkat 10 kali lipat
Peningkatan meningkat dan kekuatan berkurang 10 kali lipat


Inkrementalitas , pada gilirannya, mempengaruhi apakah konstanta dibatalkan setelah rendering. Misalkan kita mematikannya. Kemudian kita melihat bahwa pada setiap frame, konstanta diterapkan sebelum rendering, dan setelah rendering dibatalkan, sehingga setiap frame diulang. Hasilnya, dengan menggabungkan kedua parameter ini, Anda bisa mendapatkan efek jarum jam yang agak menarik.


Pesawat selalu menghadap kamera.
Pesawat selalu menghadap kamera.


Mari kita beralih ke konstanta yang lebih menarik: papan reklame.


Misalkan, perlu bahwa beberapa objek selalu "menghadap" kita. Untuk melakukan ini, cukup gunakan SCNBillboardConstraint , tunjukkan sumbu mana yang dapat diputar objek. Selanjutnya, sebelum menghitung setiap frame (setelah langkah dengan fisika), posisi dan orientasi semua objek akan diperbarui untuk memenuhi semua konstanta.


Di sini Anda dapat menyebutkan Lihat At Constraint : ini mirip dengan papan reklame, hanya objek yang dapat diatur menghadap objek lain dalam adegan, bukan kamera saat ini.


Apa yang bisa dilakukan dengan bantuan mereka? Tentu saja, paling sering konstanta ini digunakan untuk menggambar pohon atau benda kecil. Mereka juga menciptakan efek khusus seperti api atau ledakan. Selain itu, dengan bantuan mereka, Anda dapat membuat kamera mengikuti objek di atas panggung.


Menjaga jarak antar objek
Menjaga jarak antar objek


SCNDistanceConstraint memungkinkan Anda untuk mengatur jarak minimum dan / atau maksimum ke posisi objek lain. Dan ya, Anda bisa menggunakannya untuk membuat ular. :) Batasan ini juga dapat digunakan untuk mengikat kamera ke karakter, meskipun posisi kamera biasanya lebih rumit, dan menggambarkannya dengan constrates saja bukanlah tugas yang mudah. Efek yang sama dapat dicapai dengan menambahkan pegas di mesin fisik, tetapi pegas ini dapat ditambah dengan ketegangan jika Anda perlu menghindari masalah dengan peregangan yang berlebihan atau kompresi pegas.


Banyak yang telah melihat di Hitman, Fallout atau Skyrim: Anda menyeret tubuh dengan Anda, menyentuh rintangan - dan mulai berperilaku seolah-olah iblis telah memasukinya. Konstanta ini akan membantu menghindari bug semacam itu.


SCNSliderConstraint
SCNSliderConstraint


SCNSliderConstraint memungkinkan Anda untuk mengatur jarak minimum antara objek yang diberikan dan tubuh fisik dengan topeng tabrakan yang sesuai. Konstanta cukup lucu, tetapi sekali lagi, mereka mencoba mensimulasikannya menggunakan interaksi fisik. Gagasan utamanya adalah mengatur jari-jari zona mati dengan tubuh fisik untuk objek yang tidak memiliki tubuh fisik.


Kinematika terbalik sedang bekerja
Kinematika terbalik sedang bekerja


SCNIKConstraint adalah konstanta yang paling menarik, tetapi juga yang paling kompleks, yang menggunakan apa yang disebut kinematika terbalik. Menggunakan rantai node induk, kinematika terbalik secara berulang mencoba membawa posisi simpul yang Anda terapkan konstan ini ke titik yang diperlukan. Bahkan, ini memungkinkan Anda untuk tidak berpikir dalam posisi apa bahu dan lengan seharusnya, tetapi hanya untuk mengatur posisi tangan dan kemungkinan sudut rotasi dari simpul penghubung. Sisanya akan dihitung untuk Anda. Kelemahan utama dari batasan ini adalah memungkinkan Anda untuk hanya mengatur posisi tangan, tetapi bukan orientasinya, dan batasan sudut dapat dibuat mendunia, tanpa menghancurkan sumbu.


Jadi, kami bertemu secara detail dengan konstanta dan dengan apa yang mereka tahu bagaimana melakukannya. Mari kita lanjutkan mengeksplorasi efek yang menarik. Kami akan berurusan dengan efek bayangan.


Ada pesawat, tetapi tidak
Ada pesawat, tetapi tidak


Tampaknya akan lebih mudah di mesin yang mendukung bayangan daripada membuat bayangan? Namun terkadang bayangan harus dilemparkan ke bidang yang benar-benar transparan. Ini sangat berguna di ARKit, karena gambar kamera ditampilkan di belakang pesawat, dan bayangan harus dilemparkan ke suatu tempat. Triknya ternyata cukup sederhana: pertama Anda harus mengaktifkan bayangan yang ditangguhkan dan mematikan perekaman di semua komponen pesawat pada tab material, dan bayangan akan terus menumpuknya. Satu-satunya masalah adalah bahwa pesawat ini akan tumpang tindih dengan objek di belakangnya.


Tapi bayangan bukan satu-satunya efek yang dipelajari dengan buruk di SceneKit. Mari kita berurusan dengan cermin sekarang.


Cermin SCNFloor - apa yang bisa lebih sederhana
Cermin SCNFloor - apa yang bisa lebih sederhana


Setiap orang yang bermain dengan SceneKit mungkin tahu tentang scnfloor, yang menambahkan pantulan cermin ke lantai. Tetapi untuk beberapa alasan, sangat sedikit yang menggunakannya untuk refleksi cermin yang jujur, karena Anda dapat menempatkan model Anda pada geometri lantai, memiringkannya sedikit dan mengubahnya ... menjadi cermin biasa.



Teteskan pada kaca dan cermin melengkung
Teteskan pada kaca dan cermin melengkung


Tapi, apa yang bahkan kurang diketahui, peta normal dapat diatur untuk jenis kelamin ini. Karena itu, pada gilirannya, Anda dapat membuat banyak efek menarik yang berbeda, seperti efek goresan atau cermin melengkung.


Sangat ungu


Suatu kali saya mencium seorang gadis dengan mata terbuka. Gadis itu memotong wajahnya dengan bidang guntingan dekat. Sejak itu saya hanya mencium dengan mata tertutup.
- John Carmack

Bayangan, cermin - efek menarik. Tetapi ada satu efek yang, ketika digunakan dengan terampil, bisa berubah menjadi lebih menarik - tekstur video.



Video biasa dan tinggi peta
Video biasa dan tinggi peta


Anda mungkin membutuhkannya hanya untuk menampilkan video di dalam gim. Tetapi jauh lebih menarik bahwa dengan bantuan tekstur video Anda dapat memodifikasi geometri. Untuk melakukan ini, Anda perlu menempatkan tekstur video dengan peta ketinggian di properti perpindahan material Anda dan menggunakan material di pesawat dengan jumlah segmen yang cukup besar. Masih memahami bagaimana menaruhnya di sana.


Saya sebutkan dalam deskripsi proses pembuatan adegan yang dapat Anda gunakan SKScene sebagai properti material, dan ini adalah adegan SpriteKit. SpriteKit seperti SceneKit, tetapi untuk grafis 2D. Ini memiliki dukungan untuk menampilkan video menggunakan SKVideoNode . Anda hanya perlu memasukkan SKVideoNode di SKScene, dan SKScene di SCNMaterialProperty, dan Anda selesai.


Tapi setelah mengekspor adegan 3D yang dihasilkan dan membukanya di tempat lain, kita akan melihat kotak hitam. Mengaduk-aduk file scn, saya menemukan alasannya. Ternyata saat menyimpan kode video, itu tidak menyimpan URL video. Tampaknya Anda mengambil dan memerintah. Tapi tidak semuanya begitu sederhana: file scn adalah yang disebut binary plist, yang berisi hasil NSKeyedArchiver. Dan materi, yang merupakan adegan SpriteKit, adalah plist biner yang sama, yang, ternyata, sudah ada di dalam plist biner lain! Bagus bahwa hanya ada dua tingkat sarang.


Nah, sekarang kita bahkan akan beralih ke efek, tetapi ke alat yang memungkinkan Anda untuk membuat segala jenis efek. Ini adalah pengubah shader.


Sebelum Anda memodifikasi sesuatu, Anda perlu memahami apa yang kami modifikasi. Shader, menurut definisi, adalah program untuk GPU yang berjalan untuk setiap titik dan untuk setiap piksel. Jadi, shader adalah program yang menentukan bagaimana suatu objek terlihat di layar.


Nah, pengubah shader memungkinkan Anda untuk mengubah hasil shader standar menjadi GLSL atau Bahasa Shading Metal. Mereka juga tersedia di editor visual, yang memungkinkan Anda untuk melihat perubahan dalam pengubah secara real time.



Pemetaan bulu dan paralaks
Pemetaan Bulu dan Paralaks


Dengan bantuan pengubah shader, Anda dapat membuat efek visual yang kompleks. Misalnya, beberapa efek paling terkenal: Fur dan Parallax Mapping .


#pragma arguments texture2d bg; texture2d height; float depth; float layers; #pragma transparent #pragma body constexpr sampler sm = sampler(filter::linear, s_address::repeat, t_address::repeat); float3 bitangent = cross(_surface.tangent, _surface.normal); float2 direction = float2(-dot(_surface.view.rgb, _surface.tangent), dot(_surface.view.rgb, _surface.bitangent)); _output.color.rgba = float4(0); for(int i = 0; i < int(floor(layers)); i++) { float coeff = float(i) / floor(layers); float2 defaultCoords = _surface.diffuseTexcoord + direction * (1 - coeff) * depth; float2 adjustment = float2(scn_frame.sinTime + defaultCoords.x, scn_frame.cosTime) * depth * coeff * 0.1; float2 coords = defaultCoords + adjustment; _output.color.rgb += bg.sample(sm, coords).rgb * coeff * (height.sample(sm, coords).r + 0.1) * (1.0 - coeff); _output.color.a += (height.sample(sm, coords).r + 0.1) * (1.0 - coeff); } return _output; 

Ray Casting dengan kaustik real-time.
Ray Casting dengan Real-Time Caustics


Lebih menarik lagi, tidak ada yang mengganggu untuk sepenuhnya membuang hasil pekerjaan mereka dan menulis renderer mereka sendiri. Misalnya, Anda dapat mencoba mengimplementasikan Ray Casting di shader. Dan semua ini bekerja cukup cepat untuk memberikan 30 FPS bahkan pada perhitungan yang rumit. Tetapi ini adalah topik untuk laporan terpisah. Ayo Mobius !


Mimpi buruk!


Saya tidak suka berkedip, karena kelopak mata tertutup dengan tajam memuat GPU untuk BDPT karena kurangnya pencahayaan.
- John Carmack

Jadi, kami memiliki banyak objek dengan efek keren. Sekarang tinggal belajar bagaimana merekamnya. Untuk melakukan ini, mari kita beralih ke topik yang lebih kompleks: bagaimana kita belajar cara merekam video langsung dari SceneKit tanpa UI eksternal dan bagaimana kita mengoptimalkan rekaman ini puluhan kali.


Mari kita beralih ke solusi paling sederhana: ReplayKit . Cari tahu mengapa itu tidak cocok. Secara umum, solusi ini memungkinkan Anda untuk membuat entri layar dalam beberapa baris kode dan menyimpannya melalui pratinjau sistem. Tapi Ini memiliki minus besar - ia merekam semuanya, seluruh UI, termasuk semua tombol di layar. Ini adalah keputusan pertama kami, tetapi karena alasan yang jelas tidak mungkin untuk membuatnya menjadi produksi: pengguna harus membagikan video, dan membagikannya bukan dari pratinjau sistem.


Kami menemukan diri kami dalam situasi di mana solusi perlu ditulis dari awal. Sepenuhnya dari awal. Jadi, mari kita lihat bagaimana di iOS Anda dapat membuat video Anda sendiri dan merekam frame Anda di sana. Semuanya cukup sederhana:


Proses perekaman
Proses perekaman


Kita perlu membuat entitas yang akan merekam file - AVAssetWriter , tambahkan stream video - AVAssetWriterInput , dan buat adaptor untuk stream ini yang akan mengubah buffer piksel kami ke dalam format yang diperlukan oleh stream - AVAssetWriterPixelBufferAdaptor .


Untuk jaga-jaga, saya mengingatkan Anda bahwa penyangga piksel adalah suatu entitas, yang merupakan bagian dari memori di mana data untuk piksel entah bagaimana ditulis. Ini pada dasarnya adalah representasi gambar tingkat rendah.


Tapi bagaimana cara mendapatkan buffer pixel ini? Solusinya sederhana. SCNView memiliki fungsi .snapshot () yang bagus yang mengembalikan UIImage. Kami hanya perlu membuat penyangga piksel dari UIImage ini.


 var unsafePixelBuffer: CVPixelBuffer? CVPixelBufferPoolCreatePixelBuffer(NULL, self.pixelBufferPool, &unsafePixelBuffer) guard let pixelBuffer = maybePixelBuffer else { return } CVPixelBufferLockBaseAddress(pixelBuffer, 0) let data = CVPixelBufferGetBaseAddress(pixelBuffer) let rgbColorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) let rowBytes = NSUInteger(CVPixelBufferGetBytesPerRow(pixelBuffer)) let context = CGContext( data: data, width: image.width, height: image.height, bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), space: rgbColorSpace, bitmapInfo: bitmapInfo.rawValue ) context?.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height)) CVPixelBufferUnlockBaseAddress(pixelBuffer, 0) self.appendPixelBuffer(pixelBuffer, withPresentationTime: presentationTime) 

Kami hanya mengalokasikan tempat di memori, menjelaskan format apa yang dimiliki piksel ini, memblokir buffer untuk mengubah, mendapatkan alamat memori, membuat konteks di alamat yang diterima, di mana kami menggambarkan bagaimana piksel dikemas, berapa banyak garis dalam gambar dan ruang warna apa yang kami gunakan. Kemudian kami menyalin piksel dari UIImage di sana, mengetahui format final, dan membuka kunci perubahan.



Sekarang Anda perlu melakukan ini setiap frame. Untuk melakukan ini, kami membuat tautan tampilan yang akan memanggil panggilan balik untuk setiap frame, di mana kami, pada gilirannya, akan memanggil metode snapshot dan membuat buffer piksel dari gambar. Semuanya sederhana!



Tapi tidak. Solusi semacam itu, bahkan pada telepon yang kuat, menyebabkan kelambatan yang mengerikan dan penarikan FPS. Ayo lakukan optimasi.



Katakanlah kita tidak membutuhkan 60 FPS. Kami bahkan akan senang dengan tanggal 25. Tetapi apa cara termudah untuk mencapai hasil ini? Tentu saja, Anda hanya perlu meletakkan semua ini di utas latar belakang. Apalagi menurut pengembang, fungsi ini aman untuk thread.



Hmm, lag sudah kurang, tapi videonya sudah berhenti merekam ...


Semuanya sederhana. Seperti yang mereka katakan, jika Anda memiliki masalah, dan Anda akan menyelesaikannya dengan bantuan beberapa utas, Anda akan memiliki 2 masalah.


Jika Anda mencoba merekam penyangga piksel dengan stempel waktu lebih rendah dari yang direkam terakhir, maka seluruh video akan tidak valid.



Maka jangan tulis buffer baru sampai tulisan sebelumnya berakhir.



Hmm, ini jauh lebih baik. Tapi semua sama, mengapa kelambatan muncul awalnya?



Ternyata fungsi .snapshot () , yang dengannya kita mendapatkan gambar dari layar, membuat penyaji baru untuk setiap panggilan, menggambar bingkai dari awal dan mengembalikannya, bukan gambar yang ada di layar. Ini mengarah pada efek yang menyenangkan. Misalnya, simulasi fisik dua kali lebih cepat.


Tapi tunggu - mengapa kita mencoba membuat bingkai baru setiap saat? Tentunya di suatu tempat Anda dapat menemukan buffer yang ditampilkan di layar. Memang, ada akses ke buffer seperti itu, tetapi sangat tidak trivial. Kita perlu mendapatkan CAMetalDrawable dari Metal.


Sayangnya, mendapatkan ke Logam langsung dari SCNView tidak begitu mudah untuk alasan yang cukup dapat dimengerti - di SceneKit Anda dapat memilih jenis API sendiri, tetapi jika Anda melihat di bawah tenda dan melihat lapisannya , Anda dapat melihat bahwa itu bertindak seperti itu, dalam kasus Metal, CAMetalLayer .


Tapi di sini juga, kegagalan menunggu kita: di CAMetalLayer, satu-satunya cara untuk berinteraksi dengan tampilan adalah fungsi nextDrawable, yang mengembalikan CAMetalDrawable yang tidak digunakan. Dipahami bahwa Anda akan menulis data ke dalamnya dan memanggil fungsi sekarang di atasnya, yang akan menampilkannya di layar.


Solusinya sebenarnya ada. Faktanya adalah bahwa setelah menghilang dari layar, penyangga tidak dialokasikan, tetapi hanya ditempatkan kembali ke kolam. Memang, mengapa mengalokasikan memori setiap kali jika dua atau tiga buffer sudah cukup: satu ditampilkan di layar, yang kedua untuk rendering dan yang ketiga, misalnya, untuk postprocessing, jika Anda memilikinya.


Ternyata setelah menampilkan buffer, data darinya tidak hilang di mana pun dan Anda dapat mengaksesnya dengan aman dan aman.


Dan jika kita sebagai penerus mulai merespons setiap panggilan ke nextDrawable () untuk menyimpannya, kita mendapatkan hampir apa yang kita butuhkan. Masalahnya adalah bahwa CAMetalDrawable yang disimpan adalah di mana gambar sedang diambil sekarang.


Lompatan ke solusi nyata sangat sederhana - kami menyimpan Drawable saat ini dan yang sebelumnya.


Dan ini dia, akses langsung ke memori melalui CAMetalDrawable.


 var unsafePixelBuffer: CVPixelBuffer? CVPixelBufferPoolCreatePixelBuffer(NULL, self.pixelBufferPool, &unsafePixelBuffer) guard let pixelBuffer = maybePixelBuffer else { return } CVPixelBufferLockBaseAddress(pixelBuffer, 0) let data = CVPixelBufferGetBaseAddress(pixelBuffer) let width: NSUInteger = lastDrawable.texture.width let height: NSUInteger = lastDrawable.texture.height let rowBytes: NSUInteger = NSUInteger(CVPixelBufferGetBytesPerRow(pixelBuffer) lastDrawable.texture.getBytes( data, bytesPerRow: rowBytes, fromRegion: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0 ) CVPixelBufferUnlockBaseAddress(pixelBuffer, 0) self.appendPixelBuffer(pixelBuffer, withPresentationTime: presentationTime) 

Jadi, sekarang kita tidak membuat konteks dan menggambar UIImage di dalamnya, tetapi menyalin satu keping memori ke yang lain. Muncul pertanyaan: bagaimana dengan format pixel? ..


Itu tidak cocok dengan deviceColorSpace ... Dan itu tidak cocok dengan ruang warna yang umum digunakan ...


Inilah titik di mana penulis dari salah satu perapian umum, yang melakukan tugas yang sama, mogok . Semua orang bahkan tidak sampai di sini.



Nah, semua trik ini - demi filter yang menyeramkan?


Ya tidak! Dalam artikel tentang ARKit, Anda dapat menemukan penyebutan bahwa gambar dari kamera tidak menggunakan ruang warna standar, tetapi diperluas. Dan bahkan matriks transformasi ruang warna disajikan. Tetapi mengapa terlibat dalam transformasi, jika Anda dapat mencoba merekam langsung dalam format ini? Masih mencari tahu format apa dari 60 yang tersedia ...



Dan kemudian saya mulai bangkrut. Saya merekam tiga video dalam aliran yang berbeda dengan format yang berbeda, menggantikannya dengan setiap rekaman.


Sebagai hasilnya, dalam tentang format keempat puluh, kita mendapatkan namanya. Ternyata itu tidak lain adalah kCVPixelFormatType_30RGBLEPackedWideGamut . Bagaimana saya tidak menebak?



Tapi kegembiraan saya bertahan sampai penguji pertama. Saya tidak punya kata-kata. Bagaimana? Saya hanya menghabiskan banyak waktu mencari format yang tepat. Ada baiknya masalah dilokalisasi dengan cepat - bug bereproduksi secara stabil dan hanya pada 6s dan 6s Plus. Hampir segera setelah itu, saya ingat bahwa tampilan dengan dukungan gamut luas mulai dipasang hanya di iPhone ketujuh.


Mengubah gamut lebar menjadi 32RGBA lama yang baik, saya mendapatkan catatan kerja! Masih memahami bagaimana menentukan bahwa perangkat mendukung wide-gamut. Ada iPad dengan berbagai jenis tampilan, dan saya pikir pasti Anda bisa mendapatkan jenis tampilan ENUM dari sistem. Mengaduk-aduk dokumentasi, saya menemukannya - ini adalah displayGamut di UITraitCollection .


Setelah memberikan perakitan kepada penguji, saya menerima berita menyenangkan dari mereka - semuanya bekerja tanpa penundaan, bahkan pada perangkat lama!


Kesimpulannya, saya ingin memberi tahu Anda - lakukan 3D-grafis! Dalam aplikasi kami, yang augmented reality bukan kasus penggunaan utama, selama akhir pekan, orang-orang bepergian lebih dari 2.000 kilometer, menonton lebih dari 3.000 objek dan merekam lebih dari 1.000 video dengan mereka! Bayangkan apa yang dapat Anda lakukan jika Anda melakukannya sendiri.

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


All Articles