Intro Newton Protocol: apa yang bisa muat dalam 4 kilobyte

gambar

Saya baru-baru ini berkompetisi dalam adegan demo Revisi 2019 dalam kategori intro PC 4k, dan intro saya memenangkan tempat pertama. Saya melakukan coding dan grafik, dan dixan menggubah musik. Aturan dasar dari kompetisi ini adalah untuk membuat file atau situs web yang dapat dieksekusi yang hanya berukuran 4.096 byte. Ini berarti bahwa segala sesuatu harus dihasilkan menggunakan matematika dan algoritma; tidak ada cara lain yang bisa saya peras gambar, video dan audio ke dalam sejumlah kecil memori. Pada artikel ini, saya akan berbicara tentang pipa render intro intro Newton saya. Di bawah ini Anda dapat melihat hasil yang sudah selesai, atau klik di sini untuk melihat tampilannya langsung di Revisi, atau buka pouet untuk berkomentar dan mengunduh intro yang berpartisipasi dalam kompetisi. Anda dapat membaca tentang pekerjaan dan koreksi pesaing di sini .


Teknik bidang jarak pari Ray sangat populer dalam disiplin intro 4k karena memungkinkan Anda menentukan bentuk kompleks hanya dalam beberapa baris kode. Namun, kelemahan dari pendekatan ini adalah kecepatan eksekusi. Untuk membuat adegan, Anda perlu menemukan titik persimpangan sinar dengan adegan, pertama-tama tentukan apa yang Anda lihat, misalnya, sinar dari kamera, dan kemudian sinar berikutnya dari objek ke sumber cahaya untuk menghitung pencahayaan. Saat bekerja dengan ray marching, persimpangan ini tidak dapat ditemukan dalam satu langkah, Anda perlu mengambil banyak langkah kecil di sepanjang balok dan mengevaluasi semua objek di setiap titik. Di sisi lain, ketika menggunakan ray tracing, Anda dapat menemukan persimpangan yang tepat dengan memeriksa setiap objek hanya sekali, tetapi himpunan bentuk yang dapat digunakan sangat terbatas: Anda perlu memiliki rumus untuk setiap jenis untuk menghitung persimpangan dengan sinar.

Dalam pengantar ini, saya ingin mensimulasikan pencahayaan yang sangat akurat. Karena itu perlu untuk merefleksikan jutaan sinar di tempat kejadian, ray tracing tampaknya merupakan pilihan logis untuk mencapai efek ini. Saya membatasi diri pada satu figur - bola, karena persimpangan sinar dan bola dihitung cukup sederhana. Bahkan dinding di intro sebenarnya adalah bola yang sangat besar. Selain itu, ini menyederhanakan simulasi fisika; itu sudah cukup untuk memperhitungkan hanya konflik antar bidang.

Untuk menggambarkan jumlah kode yang sesuai dengan 4096 byte, di bawah ini saya sajikan kode sumber lengkap dari intro yang sudah selesai. Semua bagian kecuali HTML di bagian akhir dikodekan sebagai gambar PNG untuk mengkompresnya ke ukuran yang lebih kecil. Tanpa kompresi ini, kode akan mengambil hampir 8900 byte. Bagian yang disebut Synth adalah versi SoundBox . Untuk mengemas kode dalam format minimal ini, saya menggunakan Google Closure Compiler dan Shader Minifier . Pada akhirnya, hampir semuanya dikompres ke PNG menggunakan JsExe . Pipa kompilasi lengkap dapat dilihat pada kode sumber Core Kritis 4k intro saya sebelumnya, karena sepenuhnya cocok dengan yang disajikan di sini.


Musik dan synthesizer sepenuhnya diimplementasikan dalam Javascript. Bagian pada WebGL dibagi menjadi dua bagian (disorot dengan warna hijau dalam kode); dia membuat pipa render. Fisika dan elemen pelacak sinar adalah pembagi GLSL. Sisa kode dikodekan dalam gambar PNG, dan HTML ditambahkan ke akhir gambar yang dihasilkan tidak berubah. Browser mengabaikan data gambar dan hanya mengeksekusi kode HTML, yang pada gilirannya menerjemahkan PNG kembali ke javascript dan mengeksekusinya.

Rendering pipeline


Gambar di bawah ini menunjukkan saluran rendering. Ini terdiri dari dua bagian. Bagian pertama dari pipa adalah simulator fisika. Adegan intro berisi 50 bola bertabrakan satu sama lain di dalam ruangan. Ruangan itu sendiri terdiri dari enam bola, beberapa di antaranya lebih kecil dari yang lain untuk membuat dinding lebih melengkung. Dua sumber iluminasi vertikal di sudut-sudut juga merupakan bola, yaitu total 58 bola di TKP. Bagian kedua dari pipa adalah pelacak sinar, yang membuat adegan. Diagram di bawah ini menunjukkan rendering satu frame pada waktu t. Simulasi fisika mengambil bingkai sebelumnya (t-1) dan mensimulasikan keadaan saat ini. Pelacak ray mengambil posisi saat ini dan posisi frame sebelumnya (untuk saluran kecepatan) dan membuat adegan. Kemudian post-processing menggabungkan 5 frame sebelumnya dan frame saat ini untuk mengurangi distorsi dan kebisingan, dan kemudian menciptakan hasil akhir.


Merender bingkai pada waktu t.

Bagian fisiknya cukup sederhana, di Internet Anda bisa menemukan banyak tutorial tentang cara membuat simulasi primitif untuk bola. Posisi, jari-jari, kecepatan, dan massa disimpan dalam dua tekstur dengan resolusi 1 x 58. Saya menggunakan fungsionalitas Webgl 2, yang memungkinkan rendering ke beberapa target render, sehingga data dua tekstur direkam secara bersamaan. Fungsionalitas yang sama digunakan oleh pelacak ray untuk membuat tiga tekstur. Webgl tidak menyediakan akses apa pun ke tracing API pelacakan sinar NVidia RTX atau DirectX Raytracing (DXR), jadi semuanya dilakukan dari awal.

Ray tracer


Ray tracing sendiri adalah teknik yang cukup primitif. Kami melepaskan sinar ke dalam pemandangan, itu dipantulkan 4 kali, dan jika masuk ke sumber cahaya, warna pantulan menumpuk; kalau tidak, kita jadi hitam. Pada 4096 byte (yang meliputi musik, synthesizer, fisika, dan rendering) tidak ada ruang untuk membuat struktur pelacak ray akselerasi yang kompleks. Oleh karena itu, kami menggunakan metode brute-force, yaitu, kami memeriksa semua 57 bola (dinding depan tidak termasuk) untuk setiap sinar, tanpa membuat optimasi untuk mengecualikan bagian dari bola. Ini berarti bahwa untuk memberikan 60 frame per detik dalam resolusi 1080p, Anda hanya dapat memancarkan 2-6 sinar, atau sampel per piksel. Ini cukup dekat untuk membuat pencahayaan yang halus.


1 sampel per piksel.


6 sampel per piksel.

Bagaimana cara mengatasinya? Pada awalnya saya menyelidiki algoritma ray tracing, tetapi sudah disederhanakan ke titik. Saya berhasil sedikit meningkatkan kinerja dengan menghilangkan kasus ketika sinar mulai di dalam bola, karena kasus seperti itu hanya berlaku di hadapan efek transparansi, dan hanya objek buram hadir dalam adegan kami. Setelah itu, saya menggabungkan masing-masing kondisi jika menjadi pernyataan terpisah untuk menghindari percabangan yang tidak perlu: meskipun perhitungan "berlebihan", pendekatan ini masih lebih cepat daripada sekelompok pernyataan kondisional. Anda juga dapat meningkatkan pola pengambilan sampel: alih-alih memancarkan sinar secara acak, kami dapat mendistribusikannya di seluruh tempat dengan pola yang lebih rata. Sayangnya, ini tidak membantu dan menyebabkan artefak bergelombang di setiap algoritma yang saya coba. Namun, pendekatan ini menciptakan hasil yang bagus untuk gambar foto. Akibatnya, saya kembali menggunakan distribusi yang sepenuhnya acak.

Piksel tetangga harus memiliki pencahayaan yang sangat mirip, jadi mengapa tidak menggunakannya saat menghitung pencahayaan satu piksel? Kami tidak ingin mengaburkan tekstur, hanya pencahayaan, jadi kami perlu merendernya di saluran yang terpisah. Kami juga tidak ingin mengaburkan objek, jadi kami harus memperhitungkan pengidentifikasi objek untuk mengetahui piksel mana yang dapat dengan mudah dikaburkan. Karena kita memiliki objek yang memantulkan cahaya dan kita membutuhkan pantulan yang jelas, tidak cukup hanya dengan mengetahui ID dari objek pertama yang bertabrakan dengan balok. Saya menggunakan kasus khusus untuk bahan reflektif murni untuk juga memasukkan ID dari objek pertama dan kedua yang terlihat dalam refleksi di saluran pengidentifikasi objek. Dalam hal ini, kabur dapat menghaluskan pencahayaan pada objek dalam pantulan, sementara pada saat yang sama mempertahankan batas-batas objek.


Saluran tekstur, kami tidak perlu mengaburkannya.


Di sini, di saluran merah berisi ID dari objek pertama, hijau - kedua, dan biru - ketiga. Dalam praktiknya, mereka semua dikodekan ke dalam nilai float tunggal, di mana bagian integer menyimpan pengidentifikasi objek, dan yang fraksional menunjukkan kekasaran: 332211.RR.

Karena ada objek dengan kekasaran yang berbeda dalam adegan (beberapa area kasar, cahaya tersebar pada yang lain, di ketiga ada pantulan cermin), saya menyimpan kekasaran untuk mengontrol jari-jari blur. Tidak ada detail kecil dalam adegan, jadi saya menggunakan inti 50 x 50 besar dengan bobot dalam bentuk kuadrat terbalik untuk kabur. Ini tidak memperhitungkan ruang dunia (ini bisa direalisasikan untuk mendapatkan hasil yang lebih akurat), karena pada permukaan yang terletak pada sudut di beberapa arah, itu mengikis area yang lebih besar. Pengaburan seperti itu menciptakan gambar yang cukup halus, tetapi artefak terlihat jelas, terutama dalam gerakan.


Saluran pencahayaan dengan artefak yang kabur dan masih terlihat. Dalam gambar ini, titik-titik buram di dinding belakang terlihat, yang disebabkan oleh bug kecil dengan pengidentifikasi objek yang dipantulkan kedua (sinar meninggalkan tempat kejadian). Pada gambar yang sudah jadi, ini tidak terlalu terlihat, karena pantulan yang jelas diambil dari saluran tekstur. Sumber pencahayaan juga menjadi buram, tetapi saya menyukai efek ini dan saya meninggalkannya. Jika diinginkan, ini dapat dicegah dengan mengubah pengidentifikasi objek tergantung pada materi.

Saat objek berada dalam adegan dan kamera yang memotret adegan bergerak perlahan, pencahayaan di setiap frame harus tetap konstan. Oleh karena itu, kita dapat melakukan blur tidak hanya pada koordinat XY layar; kita bisa kabur dalam waktu. Jika kita berasumsi bahwa pencahayaan tidak berubah terlalu banyak dalam 100 ms, kita dapat rata-rata untuk 6 frame. Tetapi selama jendela waktu ini, objek dan kamera akan tetap agak jauh, sehingga perhitungan sederhana dari rata-rata untuk 6 frame akan menghasilkan gambar yang sangat buram. Namun, kami tahu di mana semua objek dan kamera berada di peta sebelumnya, sehingga kami dapat menghitung vektor kecepatan di ruang layar. Ini disebut proyeksi ulang sementara. Jika saya memiliki piksel pada waktu t, maka saya dapat mengambil kecepatan piksel itu dan menghitung di mana itu pada waktu t-1, dan kemudian menghitung di mana piksel pada waktu t-1 pada waktu t-2, dan seterusnya. 5 frame kembali. Tidak seperti ruang layar yang buram, saya menggunakan bobot yang sama untuk setiap bingkai, mis. hanya rata-rata warna antara semua frame untuk "blur" sementara.


Saluran kecepatan piksel yang melaporkan di mana piksel berada di bingkai terakhir berdasarkan pergerakan objek dan kamera.


Untuk menghindari keburaman bersama dari objek, kita akan kembali menggunakan saluran pengidentifikasi objek. Dalam hal ini, kami hanya mempertimbangkan objek pertama yang bertabrakan dengan balok. Ini memberikan anti-aliasing di dalam objek, mis. dalam refleksi.

Tentu saja, pikselnya mungkin tidak terlihat di bingkai sebelumnya; itu bisa disembunyikan oleh objek lain atau berada di luar bidang pandang kamera. Dalam kasus seperti itu, kami tidak dapat menggunakan informasi sebelumnya. Pemeriksaan ini dilakukan secara terpisah untuk setiap frame, jadi kami dapatkan dari 1 hingga 6 sampel atau frame per piksel, dan gunakan yang mungkin. Gambar di bawah ini menunjukkan bahwa untuk benda lambat ini bukan masalah yang sangat serius.


Ketika objek bergerak dan membuka bagian baru dari pemandangan, kami tidak memiliki 6 bingkai informasi untuk rata-rata untuk bagian ini. Gambar ini menunjukkan area yang memiliki 6 bingkai (putih), serta yang tidak memilikinya (gradasi yang gelap secara bertahap). Munculnya kontur disebabkan oleh pengacakan lokasi pengambilan sampel untuk piksel di setiap bingkai dan fakta bahwa kami mengambil pengidentifikasi objek dari sampel pertama.


Pencahayaan kabur rata-rata lebih dari enam frame. Artefak hampir tidak terlihat dan hasilnya stabil dari waktu ke waktu, karena di setiap frame hanya satu frame dari enam perubahan di mana pencahayaan diperhitungkan.

Menggabungkan semua ini, kita mendapatkan gambar yang sudah jadi. Pencahayaan buram ke piksel tetangga, sementara tekstur dan pantulan tetap jelas. Kemudian semua ini dirata-rata antara enam frame untuk membuat gambar yang lebih halus dan lebih stabil dari waktu ke waktu.


Gambar yang sudah jadi.

Artefak redaman masih terlihat, karena saya rata-rata beberapa sampel per piksel, meskipun saya mengambil saluran pengidentifikasi objek dan kecepatan untuk persimpangan pertama. Anda dapat mencoba memperbaikinya dan merapikan pantulan dengan membuang sampel jika tidak sesuai dengan yang pertama, atau setidaknya jika tabrakan pertama tidak bersamaan. Dalam prakteknya, jejaknya hampir tidak terlihat, jadi saya tidak repot-repot menghilangkannya. Batas-batas objek juga terdistorsi, karena saluran kecepatan dan pengidentifikasi objek tidak dapat dihaluskan. Saya sedang mempertimbangkan kemungkinan rendering seluruh gambar pada 2160p dengan pengurangan skala lebih lanjut menjadi 1080p, tetapi NVidia GTX 980ti saya tidak mampu memproses resolusi seperti itu pada 60fps, jadi saya memutuskan untuk meninggalkan ide ini.

Secara umum, saya sangat senang dengan bagaimana intro itu ternyata. Saya berhasil memeras segala yang ada dalam pikiran saya, dan meskipun ada sedikit bug, hasil akhirnya adalah kualitas yang sangat tinggi. Di masa mendatang, Anda dapat mencoba memperbaiki bug dan meningkatkan anti-aliasing. Layak juga bereksperimen dengan fitur-fitur seperti transparansi, blur gerakan, berbagai bentuk dan transformasi objek.

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


All Articles