Pembiasan tiga sisi dalam tiga langkah

Saat Anda merender objek 3D, Anda selalu perlu menambahkan beberapa materi agar terlihat dan terlihat seperti yang Anda inginkan; tidak masalah jika Anda melakukan ini dalam program khusus atau secara real time melalui WebGL.


Sebagian besar materi dapat disimulasikan menggunakan alat perpustakaan seperti Three.js, tetapi dalam tutorial ini saya akan menunjukkan kepada Anda bagaimana membuat objek terlihat seperti kaca dalam tiga langkah menggunakan - Anda dapat menebaknya - Three.js.


Langkah 1: Atur dan Refleksi Depan


Dalam contoh ini, saya akan menggunakan geometri berlian, tetapi Anda dapat menggunakan kubus sederhana atau bentuk lainnya.


Ayo siapkan proyek kami. Kita membutuhkan visualisator, adegan, kamera, dan geometri. Untuk memvisualisasikan permukaan kita, kita membutuhkan material. Penciptaan materi ini akan menjadi tujuan utama pelajaran. Jadi, mari kita buat objek SharedMaterial baru dengan shader vertex dan fragmen.


Bertentangan dengan harapan Anda, materi kami tidak akan transparan, bahkan kami akan mengubah apa yang ada di balik berlian. Untuk melakukan ini, kita perlu memvisualisasikan pemandangan (tanpa berlian) dalam tekstur. Saya hanya membuat pesawat ukuran seluruh lingkup menggunakan kamera ortogonal, tetapi Anda juga dapat membuat adegan dengan objek lain. Cara termudah untuk memisahkan permukaan latar belakang dari berlian di Three.js adalah dengan menggunakan Layers.


this.orthoCamera = new THREE.OrthographicCamera( width / - 2,width / 2, height / 2, height / - 2, 1, 1000 ); //    1  (0    ) this.orthoCamera.layers.set(1); const tex = await loadTexture('texture.jpg'); this.quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(), new THREE.MeshBasicMaterial({map: tex})); this.quad.scale.set(width, height, 1); //      1  this.quad.layers.set(1); this.scene.add(this.quad); 

Siklus visualisasi kami akan terlihat seperti ini:


 this.envFBO = new THREE.WebGLRenderTarget(width, height); this.renderer.autoClear = false; render() { requestAnimationFrame( this.render ); this.renderer.clear(); //    fbo this.renderer.setRenderTarget(this.envFbo); this.renderer.render( this.scene, this.orthoCamera ); //      this.renderer.setRenderTarget(null); this.renderer.render( this.scene, this.orthoCamera ); this.renderer.clearDepth(); //      this.renderer.render( this.scene, this.camera ); }; 

Hebat, waktu untuk sedikit bertamasya ke teori. Bahan transparan seperti kaca terlihat karena membiaskan cahaya. Ini karena cahaya melewati kaca lebih lambat daripada melalui udara, dan ketika berkas cahaya bertabrakan dengan objek pada sudut, perbedaan kecepatan menyebabkan cahaya berubah arah. Perubahan arah ini adalah apa yang dimaksud dengan pembiasan.



Untuk mengulang ini dalam kode, kita perlu mengetahui sudut antara vektor arah pandangan dan permukaan normal. Mari kita mengubah shader vertex untuk menghitung vektor-vektor ini.


 varying vec3 eyeVector; varying vec3 worldNormal; void main() { vec4 worldPosition = modelMatrix * vec4( position, 1.0); eyeVector = normalize(worldPos.xyz - cameraPosition); worldNormal = normalize( modelViewMatrix * vec4(normal, 0.0)).xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } 

Dalam fragmen shader, sekarang kita dapat menggunakan eyeVector dan worldNormal sebagai dua parameter pertama dalam fungsi pembiasan yang dibangun ke dalam refract . Parameter ketiga adalah rasio indeks bias, yaitu indeks bias (IOR) dari medium-glass padat kami. Dalam kasus kami, ini akan menjadi 1,0 / 1,5, tetapi Anda dapat mengubah nilai ini untuk mencapai hasil yang diinginkan. Sebagai contoh, indeks bias air adalah 1,33, dan berlian adalah 2,42.


 uniform sampler2D envMap; uniform vec2 resolution; varying vec3 worldNormal; varying vec3 viewDirection; void main() { // get screen coordinates vec2 uv = gl_FragCoord.xy / resolution; vec3 normal = worldNormal; // calculate refraction and add to the screen coordinates vec3 refracted = refract(eyeVector, normal, 1.0/ior); uv += refracted.xy; // sample the background texture vec4 tex = texture2D(envMap, uv); vec4 output = tex; gl_FragColor = vec4(output.rgb, 1.0); } 

https://codesandbox.io/embed/multi-side-refraction-step-13-pzxf9?fontsize=14&hidenavigation=1&theme=dark


Hebat! Kami telah berhasil menulis shader. Tetapi berlian itu hampir tidak terlihat ... Sebagian karena kami hanya memproses satu properti gelas. Tidak semua cahaya akan melewatinya dan dibiaskan, bahkan sebagian akan dipantulkan. Mari kita lihat bagaimana mencapainya!


Langkah 2: Refleksi dan persamaan Fresnel


Demi kesederhanaan, dalam pelajaran ini kita tidak akan menghitung refraksi nyata, tetapi hanya menggunakan warna putih untuk cahaya bias. Kita melangkah lebih jauh: bagaimana Anda tahu kapan harus merenung, dan kapan membiaskan? Secara teori, ini tergantung pada indeks bias material: ketika sudut antara vektor insiden dan normal permukaan lebih besar dari nilai ambang batas, cahaya akan dipantulkan.



Dalam fragmen shader, kita akan menggunakan persamaan Fresnel untuk menghitung proporsi antara sinar yang dipantulkan dan yang dibiaskan. Sayangnya, glsl tidak memiliki persamaan ini, Anda dapat menyalinnya dari sini:


 float Fresnel(vec3 eyeVector, vec3 worldNormal) { return pow( 1.0 + dot( eyeVector, worldNormal), 3.0 ); } 

Kita cukup mencampur warna tekstur dari sinar yang dibiaskan dengan warna putih yang dipantulkan menggunakan proporsi yang baru saja kita hitung.


 uniform sampler2D envMap; uniform vec2 resolution; varying vec3 worldNormal; varying vec3 viewDirection; float Fresnel(vec3 eyeVector, vec3 worldNormal) { return pow( 1.0 + dot( eyeVector, worldNormal), 3.0 ); } void main() { // get screen coordinates vec2 uv = gl_FragCoord.xy / resolution; vec3 normal = worldNormal; // calculate refraction and add to the screen coordinates vec3 refracted = refract(eyeVector, normal, 1.0/ior); uv += refracted.xy; // sample the background texture vec4 tex = texture2D(envMap, uv); vec4 output = tex; // calculate the Fresnel ratio float f = Fresnel(eyeVector, normal); // mix the refraction color and reflection color output.rgb = mix(output.rgb, vec3(1.0), f); gl_FragColor = vec4(output.rgb, 1.0); } 

https://codesandbox.io/embed/multi-side-refraction-step-23-3vdty?fontsize=14&hidenavigation=1&theme=dark


Itu sudah terlihat jauh lebih baik, tetapi ada sesuatu yang hilang ... Tepat, kita tidak melihat bagian belakang objek transparan. Mari kita perbaiki!


Langkah 3: Pembiasan Multilateral


Mempertimbangkan apa yang telah kita pelajari tentang refraksi dan refleksi, kita dapat memahami bahwa cahaya dapat berputar bolak-balik di dalam suatu objek beberapa kali sebelum meninggalkannya.
Untuk mencapai hasil yang benar, dari sudut pandang fisik, kita harus melacak setiap sinar, tetapi, sayangnya, perhitungan seperti itu terlalu berat untuk visualisasi waktu nyata. Jadi sebagai gantinya, saya akan menunjukkan kepada Anda bagaimana menggunakan perkiraan untuk setidaknya menunjukkan tepi tersembunyi dari berlian.
Kita akan membutuhkan peta normal dan wajah depan dan belakang dalam satu shader fragmen. Karena kita tidak dapat memvisualisasikan kedua sisi secara bersamaan, pertama-tama kita perlu mendapatkan tepi belakang sebagai tekstur.



Buat ShaderMaterial baru seperti pada langkah pertama, tapi sekarang kita akan membuat peta normal di gl_FragColor .


 varying vec3 worldNormal; void main() { gl_FragColor = vec4(worldNormal, 1.0); } 

Selanjutnya, kami memperbarui siklus visualisasi dan menambahkan pemrosesan wajah belakang.


 this.backfaceFbo = new THREE.WebGLRenderTarget(width, height); ... render() { requestAnimationFrame( this.render ); this.renderer.clear(); // render background to fbo this.renderer.setRenderTarget(this.envFbo); this.renderer.render( this.scene, this.orthoCamera ); // render diamond back faces to fbo this.mesh.material = this.backfaceMaterial; this.renderer.setRenderTarget(this.backfaceFbo); this.renderer.clearDepth(); this.renderer.render( this.scene, this.camera ); // render background to screen this.renderer.setRenderTarget(null); this.renderer.render( this.scene, this.orthoCamera ); this.renderer.clearDepth(); // render diamond with refraction material to screen this.mesh.material = this.refractionMaterial; this.renderer.render( this.scene, this.camera ); }; 

Sekarang kami menggunakan tekstur dengan bahan normal.


 vec3 backfaceNormal = texture2D(backfaceMap, uv).rgb; 

Dan akhirnya, normals wajah depan dan belakang kompatibel.


 float a = 0.33; vec3 normal = worldNormal * (1.0 - a) - backfaceNormal * a; 

Dalam persamaan ini, a hanyalah kuantitas skalar yang menunjukkan berapa banyak normals dari trailing edge untuk digunakan.


https://codesandbox.io/embed/multi-side-refraction-step-33-ljnqj?fontsize=14&hidenavigation=1&theme=dark


Ternyata! Semua sisi berlian hanya dapat dilihat dengan bantuan pantulan dan pembiasan, yang kami tambahkan pada bahan.


Keterbatasan


Seperti yang telah saya jelaskan, sangat tidak mungkin untuk mendapatkan materi transparan real-time yang secara fisik benar menggunakan metode ini. Masalah lainnya adalah memvisualisasikan beberapa objek kaca yang saling berhadapan. Karena kita hanya memvisualisasikan latar belakang, itu tidak akan berfungsi untuk melihatnya melalui serangkaian objek. Dan akhirnya, pantulan di bidang visibilitas yang saya perlihatkan di sini tidak akan bekerja secara normal di perbatasan layar, karena sinar dapat membiaskan dengan nilai-nilai yang melampaui batas-batas pesawat, dan kami tidak akan dapat menangkap kasus-kasus ini ketika membuat adegan menjadi tekstur.


Tentu saja, ada cara untuk mengatasi keterbatasan ini, tetapi tidak semuanya akan menjadi solusi WebGL yang bagus.


Saya harap Anda menikmati tutorial ini dan mempelajari sesuatu yang baru. Saya ingin tahu apa yang akan Anda lakukan dengan itu sekarang! Beri tahu saya di Twitter . Dan jangan ragu untuk bertanya tentang semuanya!

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


All Articles