Rendering volumetrik di WebGL


Gambar 1. Contoh rendering volumetrik yang dilakukan oleh renderer WebGL yang dijelaskan dalam posting. Kiri: simulasi distribusi probabilitas spasial elektron dalam molekul protein potensial tinggi. Kanan: tomogram pohon bonsai. Kedua dataset diambil dari repositori Open SciVis Datasets .

Dalam visualisasi ilmiah, rendering volumetrik banyak digunakan untuk memvisualisasikan bidang skalar tiga dimensi. Bidang skalar ini sering berupa garis nilai homogen yang mewakili, misalnya, kerapatan muatan di sekitar molekul, MRI atau CT scan, aliran udara yang menyelimuti pesawat, dll. Render volumetrik adalah metode sederhana yang secara konseptual mengubah data tersebut menjadi gambar: dengan mengambil sampel data sepanjang sinar dari mata dan menetapkan warna dan transparansi untuk setiap sampel, kita dapat membuat gambar yang berguna dan indah dari bidang skalar tersebut (lihat Gambar 1). Dalam renderer GPU, bidang skalar tiga dimensi seperti itu disimpan sebagai tekstur 3D; namun, WebGL1 tidak mendukung tekstur 3D, jadi peretasan tambahan diperlukan untuk meniru mereka dalam rendering volume. WebGL2 baru-baru ini menambahkan dukungan untuk tekstur 3D, memungkinkan browser untuk menerapkan rendering volume yang elegan dan cepat. Dalam posting ini kita akan membahas dasar-dasar matematika rendering volumetrik dan berbicara tentang bagaimana menerapkannya di WebGL2 untuk membuat penyaji volumetrik interaktif yang bekerja sepenuhnya di browser! Sebelum Anda mulai, Anda dapat menguji renderer online volumetrik yang dijelaskan dalam pos ini.

1. Pendahuluan



Gambar 2: rendering volumetrik fisik, dengan mempertimbangkan penyerapan dan emisi cahaya berdasarkan volume, serta efek hamburan.

Untuk membuat gambar yang secara fisik realistis dari data volumetrik, kita perlu mensimulasikan bagaimana sinar cahaya diserap, dipancarkan dan tersebar oleh medium (Gambar 2). Meskipun memodelkan penyebaran cahaya melalui media pada level ini menciptakan hasil yang indah dan benar secara fisik, itu terlalu mahal untuk rendering interaktif, yang merupakan tujuan dari perangkat lunak visualisasi. Dalam visualisasi ilmiah, tujuan utamanya adalah memungkinkan para ilmuwan untuk secara interaktif meneliti data mereka, serta mengajukan pertanyaan tentang tugas penelitian mereka dan menjawabnya. Karena model hamburan sepenuhnya fisik akan terlalu mahal untuk rendering interaktif, aplikasi visualisasi menggunakan model penyerapan-emisi yang disederhanakan, baik mengabaikan efek hamburan yang mahal, atau entah bagaimana mendekati mereka. Dalam artikel ini, kami hanya mempertimbangkan model penyerapan-emisi.

Dalam model penyerapan-emisi, kami menghitung efek pencahayaan yang muncul pada Gambar 2 hanya di sepanjang sinar hitam, dan mengabaikan efek yang muncul dari sinar abu-abu putus-putus. Sinar yang melewati volume dan mencapai mata menumpuk warna yang dipancarkan oleh volume dan secara bertahap memudar sampai mereka sepenuhnya diserap oleh volume. Jika kita melacak sinar dari mata melalui volume, kita dapat menghitung cahaya yang masuk mata dengan mengintegrasikan balok di atas volume untuk mengakumulasi emisi dan penyerapan sepanjang balok. Ambil sinar jatuh ke volume pada suatu titik s=0dan keluar volume pada suatu titik s=L. Kita dapat menghitung cahaya yang masuk ke mata menggunakan integral berikut:

C(r)= int0LC(s) mu(s)e int0s mu(t)dtds


Ketika balok melewati volume, kami mengintegrasikan warna yang dipancarkan C(s)dan penyerapan  mu(s)di setiap titik sdi sepanjang balok. Cahaya yang dipancarkan di setiap titik memudar dan kembali ke mata penyerapan volume hingga titik ini, yang dihitung dengan istilah e int0s mu(t)dt.

Dalam kasus umum, integral ini tidak dapat dihitung secara analitis, oleh karena itu, pendekatan numerik harus digunakan. Kami melakukan pendekatan integral dengan mengambil banyak sampel Nsepanjang sinar dalam interval s=[0,L]masing-masing terletak di kejauhan  Deltasterpisah satu sama lain (Gambar 3), dan merangkum semua sampel ini. Istilah redaman pada setiap titik pengambilan sampel menjadi produk dari seri akumulasi penyerapan dalam sampel sebelumnya.

C(r)= sumi=0NC(i Deltas) mu(i Deltas) Deltas prodj=0i1e mu(j Deltas) Deltas


Untuk menyederhanakan jumlah ini lebih jauh, kami memperkirakan jangka waktu redaman ( e mu(j Deltas) Deltas) dia dekat Taylor. Juga untuk kenyamanan kami memperkenalkan alpha  alpha(i Deltas)= mu(i Deltas) Deltas. Ini memberi kita persamaan pengomposisian alfa yang dilakukan dari depan ke belakang:

C(r)= sumi=0NC(i Deltas) alpha(i Deltas) prodj=0i1(1 alpha(j Deltas))



Gambar 3: Perhitungan integral dari rendering emisi-penyerapan volume.

Persamaan di atas direduksi menjadi a for loop, di mana kita melangkah melalui balok langkah demi langkah melalui volume dan secara berulang menumpuk warna dan opacity. Siklus ini berlanjut sampai balok meninggalkan volume atau warna yang terakumulasi menjadi buram (  alpha=1) Perhitungan berulang dari jumlah di atas dilakukan dengan menggunakan persamaan komposisi depan-ke-belakang yang sudah dikenal:

 hatCi= hatCi1+(1 alphai1) hatC(i Deltas)


 alphai= alphai1+(1 alphai1) alpha(i Deltas)


Persamaan akhir ini mengandung opacity yang sebelumnya dikalikan untuk pencampuran yang tepat,  hatC(i Deltas)=C(i Deltas) alpha(i Deltas).

Untuk merender gambar volume, cukup lacak sinar dari mata ke setiap piksel, lalu lakukan iterasi yang ditunjukkan di atas untuk setiap sinar yang melintasi volume. Setiap ray yang diproses (atau piksel) adalah independen, jadi jika kita ingin membuat gambar dengan cepat, kita perlu cara untuk memproses sejumlah besar piksel secara paralel. Di sinilah GPU berguna. Dengan menerapkan proses raymarching dalam fragmen shader, kita dapat menggunakan kekuatan komputasi GPU paralel untuk menerapkan penyaji volume yang sangat cepat!


Gambar 4: Raymarching di atas kisi volume.

2. Implementasi GPU di WebGL2


Untuk melakukan raymarching dilakukan dalam fragmen shader, perlu untuk memaksa GPU untuk mengeksekusi fragmen shader pada piksel di mana kita ingin melacak ray. Namun, pipa OpenGL bekerja dengan primitif geometris (Gambar 5), dan tidak memiliki cara langsung untuk mengeksekusi fragmen shader di area spesifik layar. Untuk mengatasi masalah ini, kita dapat membuat semacam geometri menengah untuk mengeksekusi fragmen shader pada piksel yang perlu kita render. Pendekatan kami untuk rendering volume akan serupa dengan Shader Toy dan penyaji adegan demo , yang membuat dua segitiga layar penuh untuk melakukan shader fragmen, dan kemudian melakukan pekerjaan rendering yang sebenarnya.


Gambar 5: Pipeline OpenGL di WebGL terdiri dari dua tahap shader yang dapat diprogram: sebuah shader vertex, yang bertanggung jawab untuk mengubah simpul input menjadi ruang klip, dan shader fragmen, yang bertanggung jawab untuk menaungi piksel yang ditutupi oleh segitiga.

Meskipun rendering dua segitiga layar penuh dengan cara ShaderToy akan berfungsi, itu akan melakukan pemrosesan fragmen yang tidak perlu ketika volume tidak mencakup seluruh layar. Kasing ini cukup umum: pengguna menjauhkan kamera dari volume untuk melihat banyak data secara umum atau mempelajari bagian karakteristik besar. Untuk membatasi pemrosesan fragmen menjadi hanya piksel yang dipengaruhi oleh volume, kita dapat merasterisasi jajaran genjang dari kisi-kisi volume, dan kemudian melakukan langkah raymarching dalam shader fragmen. Selain itu, kita tidak perlu membuat wajah depan dan belakang dari jajaran genjang, karena dengan urutan render segitiga tertentu, shader fragmen dalam kasus ini dapat dilakukan dua kali. Selain itu, jika kami membuat hanya wajah depan, maka kami mungkin mengalami masalah ketika pengguna memperbesar karena wajah depan akan diproyeksikan di belakang kamera, yang berarti mereka akan terpotong, mis. Piksel ini tidak akan ditampilkan. Untuk memungkinkan pengguna mendekatkan kamera sepenuhnya ke volume, kami hanya akan membuat wajah terbalik dari jajaran genjang. Pipa rendering yang dihasilkan ditunjukkan pada Gambar 6.


Gambar 6: Pipa WebGL untuk volume raymarching. Kami akan meraster wajah terbalik dari jajaran genjang volume sehingga shader fragmen dieksekusi untuk piksel yang menyentuh volume ini. Di dalam shader fragmen, kami render sinar melalui volume langkah demi langkah untuk rendering.

Dalam pipa ini, sebagian besar render nyata dilakukan dalam fragmen shader; Namun, kita masih bisa menggunakan shader vertex dan peralatan untuk interpolasi tetap fungsi untuk melakukan perhitungan yang bermanfaat. Vertex shader akan mengonversi volume berdasarkan posisi kamera pengguna, menghitung arah sinar dan posisi mata dalam ruang volume, dan kemudian mentransfernya ke fragmen shader. Arah sinar yang dihitung pada setiap vertex kemudian diinterpolasi segitiga oleh peralatan interpolasi fungsi-tetap dalam GPU, memungkinkan kita untuk menghitung arah ray untuk setiap fragmen sedikit lebih murah, namun, ketika ditransfer ke shader fragmen, arah ini mungkin tidak dinormalisasi, jadi masih harus menormalkan mereka.

Kami akan membuat jajaran genjang pembatas sebagai kubus tunggal [0, 1] dan menskalakannya dengan nilai sumbu volume untuk memberikan dukungan untuk volume volume yang tidak sama. Posisi mata diubah menjadi kubus tunggal, dan dalam ruang ini arah balok dihitung. Raymarching dalam ruang kubus tunggal akan memungkinkan kita untuk menyederhanakan operasi pengambilan sampel tekstur selama raymarching dalam shader fragmen. karena mereka sudah berada di ruang koordinat tekstur [0, 1] dari volume tiga dimensi.

Vertex shader yang digunakan oleh kami ditunjukkan di atas, permukaan belakang yang dirasterisasi yang dilukis dengan arah sinar visibilitas ditunjukkan pada Gambar 7.

#version 300 es layout(location=0) in vec3 pos; uniform mat4 proj_view; uniform vec3 eye_pos; uniform vec3 volume_scale; out vec3 vray_dir; flat out vec3 transformed_eye; void main(void) { // Translate the cube to center it at the origin. vec3 volume_translation = vec3(0.5) - volume_scale * 0.5; gl_Position = proj_view * vec4(pos * volume_scale + volume_translation, 1); // Compute eye position and ray directions in the unit cube space transformed_eye = (eye_pos - volume_translation) / volume_scale; vray_dir = pos - transformed_eye; }; 


Gambar 7: Wajah terbalik dari jajaran genjang volume yang dilukis pada arah balok.

Sekarang shader fragmen memproses piksel yang kami perlukan untuk merender volume, kami dapat melihat volume dan menghitung warna untuk setiap piksel. Selain arah balok dan posisi mata, dihitung dalam vertex shader, untuk rendering volume, kita perlu mentransfer data input lainnya ke fragmen shader. Tentu saja, sebagai permulaan, kita perlu sampler tekstur 3D untuk sampel volume. Namun, volume hanyalah blok nilai skalar, dan jika kita akan menggunakannya secara langsung sebagai nilai warna ( C(s)) dan opacity (  alpha(s)), maka gambar yang diberikan dalam skala abu-abu tidak akan sangat berguna bagi pengguna. Misalnya, tidak mungkin untuk menyorot area menarik dengan warna berbeda, menambahkan noise dan membuat area latar belakang transparan untuk menyembunyikannya.

Untuk memberikan kontrol pengguna atas warna dan opacity yang ditetapkan untuk setiap nilai sampel, peta warna tambahan yang disebut fungsi transfer digunakan dalam renderers visualisasi ilmiah. Fungsi transfer mengatur warna dan opacity untuk ditetapkan ke nilai tertentu yang diambil dari volume. Meskipun ada fungsi transfer yang lebih kompleks, biasanya fungsi tersebut menggunakan tabel pencarian warna sederhana, yang dapat direpresentasikan sebagai tekstur satu dimensi warna dan opacity (dalam format RGBA). Untuk menerapkan fungsi transfer saat melakukan raymarching volume, kita dapat mencicipi tekstur fungsi transfer berdasarkan nilai skalar yang diambil dari tekstur volume. Nilai warna kembali dan opacity kemudian digunakan sebagai C(s)dan  alpha(s)sampel.

Data input terakhir untuk fragmen shader adalah dimensi volume yang kami gunakan untuk menghitung ukuran langkah balok (  Deltas) untuk mencicipi setiap voxel di sepanjang balok setidaknya satu kali. Karena persamaan balok tradisional memiliki bentuk r(t)= veco+t vecd, untuk kepatuhan, kami akan mengubah terminologi dalam kode, dan menunjukkan  Deltasbagaimana  textttdt. Begitu pula dengan intervalnya s=[0,L]sepanjang balok, ditutupi oleh volume, kami menyatakan sebagai [ texttttmin, texttttmax].

Untuk melakukan volume raymarching dalam shader fragmen, kami akan melakukan hal berikut:

  1. Kami menormalkan arah sinar visibilitas yang diterima sebagai input dari vertex shader;
  2. Silang garis pandang dengan batas volume untuk menentukan interval [ texttttmin, texttttmax]untuk melakukan raymarching dengan tujuan rendering volume;
  3. Kami menghitung panjang langkah seperti itu  textttdtsehingga setiap voxel disampel setidaknya satu kali;
  4. Mulai dari titik masuk ke r( texttttmin), mari kita pergi melalui berkas melalui volume sampai kita mencapai titik akhir di r( texttttmax)
    1. Di setiap titik kami mencicipi volume dan menggunakan fungsi transfer untuk menetapkan warna dan opacity;
    2. Kami akan mengakumulasikan warna dan opacity di sepanjang balok menggunakan persamaan pengomposisian dari depan ke belakang.

Sebagai optimasi tambahan, Anda dapat menambahkan kondisi untuk keluar secara prematur dari siklus raymarching untuk menyelesaikannya ketika warna yang terakumulasi menjadi hampir buram. Ketika warnanya menjadi hampir buram, sampel apa pun setelah itu akan hampir tidak berpengaruh pada piksel, karena warnanya akan sepenuhnya diserap oleh lingkungan dan tidak akan mencapai mata.

Shader fragmen penuh untuk perender volume kami ditunjukkan di bawah ini. Komentar telah ditambahkan ke dalamnya, menandai setiap tahap proses.


Gambar 8: Hasil visualisasi bonsai siap jadi dari sudut pandang yang sama seperti pada Gambar 7.

Itu saja!

Penyaji yang dijelaskan dalam artikel ini akan dapat membuat gambar yang mirip dengan yang ditunjukkan pada Gambar 8 dan Gambar 1. Anda juga dapat mengujinya secara online . Demi singkatnya, saya menghilangkan kode Javascript yang diperlukan untuk menyiapkan konteks WebGL, memuat tekstur volume dan fungsi transfer, mengatur shader dan merender kubus untuk merender volume; Kode penyaji lengkap tersedia untuk referensi di Github .

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


All Articles