Membangun Shader Dengan Babylon.js
Diterbitkan: 2022-03-10Shader adalah konsep kunci jika Anda ingin melepaskan kekuatan mentah GPU Anda. Saya akan membantu Anda memahami cara kerjanya dan bahkan bereksperimen dengan kekuatan batin mereka dengan cara yang mudah, terima kasih kepada Babylon.js.
Bagaimana cara kerjanya?
Sebelum bereksperimen, kita harus melihat bagaimana segala sesuatunya bekerja secara internal.
Saat berhadapan dengan 3D yang dipercepat perangkat keras, Anda harus berurusan dengan dua CPU: CPU utama dan GPU. GPU adalah sejenis CPU yang sangat khusus.
Bacaan Lebih Lanjut tentang SmashingMag:
- Membangun Game WebGL Lintas-Platform Dengan Babylon.js
- Menggunakan API Gamepad Di Game Web
- Pengantar Pemodelan Poligonal Dan Three.js
- Cara Membuat Mesin Drum 8-Bit yang Responsif
GPU adalah mesin negara yang Anda atur menggunakan CPU. Misalnya, CPU akan mengonfigurasi GPU untuk membuat garis, bukan segitiga; itu akan menentukan apakah transparansi aktif; dan seterusnya.
Setelah semua status disetel, CPU dapat menentukan apa yang akan dirender: geometri.
Geometri terdiri dari:
- daftar titik yang disebut simpul dan disimpan dalam array yang disebut buffer simpul,
- daftar indeks yang mendefinisikan wajah (atau segitiga) yang disimpan dalam array bernama buffer indeks.
Langkah terakhir untuk CPU adalah menentukan cara membuat geometri; untuk tugas ini, CPU akan menentukan shader di GPU. Shader adalah potongan kode yang akan dieksekusi oleh GPU untuk setiap simpul dan piksel yang harus dirender. (Sebuah simpul — atau simpul ketika ada beberapa di antaranya — adalah "titik" dalam 3D).
Ada dua jenis shader: vertex shader dan pixel (atau fragmen) shader.
Pipa Grafik
Sebelum menggali ke dalam shader, mari kita mundur. Untuk merender piksel, GPU akan mengambil geometri yang ditentukan oleh CPU dan akan melakukan hal berikut:
- Menggunakan buffer indeks, tiga simpul dikumpulkan untuk mendefinisikan sebuah segitiga.
- Buffer indeks berisi daftar indeks simpul. Ini berarti bahwa setiap entri dalam buffer indeks adalah jumlah simpul dalam buffer vertex.
- Ini sangat berguna untuk menghindari duplikasi simpul.
Misalnya, buffer indeks berikut adalah daftar dua wajah: [1 2 3 1 3 4]. Wajah pertama berisi simpul 1, simpul 2 dan simpul 3. Wajah kedua berisi simpul 1, simpul 3 dan simpul 4. Jadi, ada empat simpul dalam geometri ini:
Shader titik diterapkan ke setiap titik segitiga. Tujuan utama dari vertex shader adalah untuk menghasilkan piksel untuk setiap vertex (proyeksi pada layar 2D dari vertex 3D):
Menggunakan tiga piksel ini (yang menentukan segitiga 2D di layar), GPU akan menginterpolasi semua nilai yang melekat pada piksel (setidaknya posisinya), dan shader piksel akan diterapkan ke setiap piksel yang termasuk dalam segitiga 2D untuk menghasilkan warna untuk setiap piksel:
Proses ini dilakukan untuk setiap wajah yang ditentukan oleh buffer indeks.
Jelas, karena sifat paralelnya, GPU mampu memproses langkah ini untuk banyak wajah secara bersamaan dan mencapai kinerja yang sangat baik.
GLSL
Kami baru saja melihat bahwa untuk membuat segitiga, GPU membutuhkan dua shader: vertex shader dan pixel shader. Shader ini ditulis dalam bahasa bernama Graphics Library Shader Language (GLSL). Sepertinya C
Berikut adalah contoh shader vertex yang umum:
precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
Struktur Shader Vertex
Sebuah vertex shader berisi berikut ini:
- Atribut . Atribut mendefinisikan bagian dari vertex. Secara default, sebuah simpul setidaknya harus berisi posisi (a
vector3:x, y, z
). Namun, sebagai pengembang, Anda dapat memutuskan untuk menambahkan lebih banyak informasi. Misalnya, di shader sebelumnya, adavector2
bernamauv
(yaitu koordinat tekstur yang memungkinkan Anda menerapkan tekstur 2D ke objek 3D). - Seragam . Seragam adalah variabel yang digunakan oleh shader dan ditentukan oleh CPU. Satu-satunya seragam yang kita miliki di sini adalah matriks yang digunakan untuk memproyeksikan posisi simpul (x, y, z) ke layar (x, y).
- Bervariasi . Variabel yang bervariasi adalah nilai yang dibuat oleh vertex shader dan ditransmisikan ke pixel shader. Di sini, vertex shader akan mengirimkan nilai
vUV
(salinan sederhanauv
) ke pixel shader. Ini berarti bahwa sebuah piksel didefinisikan di sini dengan koordinat posisi dan tekstur. Nilai-nilai ini akan diinterpolasi oleh GPU dan digunakan oleh pixel shader. - utama . Fungsi bernama
main
adalah kode yang dieksekusi oleh GPU untuk setiap simpul dan setidaknya harus menghasilkan nilai untukgl_position
(posisi simpul saat ini di layar).
Kita dapat melihat dalam sampel kita bahwa vertex shader cukup sederhana. Ini menghasilkan variabel sistem (dimulai dengan gl_
) bernama gl_position
untuk menentukan posisi piksel terkait, dan menetapkan variabel bervariasi yang disebut vUV
.
Voodoo Dibalik Matriks
Hal tentang shader kami adalah bahwa kami memiliki matriks bernama worldViewProjection
, dan kami menggunakan matriks ini untuk memproyeksikan posisi vertex ke variabel gl_position
. Itu keren, tapi bagaimana kita mendapatkan nilai dari matriks ini? Ini adalah seragam, jadi kita harus mendefinisikannya di sisi CPU (menggunakan JavaScript).
Ini adalah salah satu bagian kompleks dalam melakukan 3D. Anda harus memahami matematika yang rumit (atau Anda harus menggunakan mesin 3D seperti Babylon.js, yang akan kita lihat nanti).
Matriks worldViewProjection
adalah kombinasi dari tiga matriks yang berbeda:
Menggunakan matriks yang dihasilkan memungkinkan kita untuk mengubah simpul 3D menjadi piksel 2D, sambil memperhitungkan sudut pandang dan segala sesuatu yang terkait dengan posisi, skala, dan rotasi objek saat ini.
Ini adalah tanggung jawab Anda sebagai pengembang 3D: untuk membuat dan menjaga agar matriks ini tetap mutakhir.
Kembali ke Shader
Setelah shader vertex dieksekusi pada setiap vertex (tiga kali, maka), kita akan memiliki tiga piksel dengan gl_position
dan nilai vUV
yang benar. GPU akan menginterpolasi nilai-nilai ini pada setiap piksel yang terkandung dalam segitiga yang dihasilkan dengan piksel-piksel ini.
Kemudian, untuk setiap piksel, ia akan mengeksekusi pixel shader:
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }
Struktur Shader Piksel (atau Fragmen)
Struktur pixel shader mirip dengan vertex shader:
- Bervariasi . Variabel yang bervariasi adalah nilai yang dibuat oleh vertex shader dan ditransmisikan ke pixel shader. Di sini, pixel shader akan menerima nilai
vUV
dari vertex shader. - Seragam . Seragam adalah variabel yang digunakan oleh shader dan ditentukan oleh CPU. Satu-satunya seragam yang kami miliki di sini adalah sampler, yang merupakan alat yang digunakan untuk membaca warna tekstur.
- utama . Fungsi bernama
main
adalah kode yang dijalankan oleh GPU untuk setiap piksel dan yang setidaknya harus menghasilkan nilai untukgl_FragColor
(yaitu warna piksel saat ini).
Pixel shader ini cukup sederhana: Ia membaca warna dari tekstur menggunakan koordinat tekstur dari vertex shader (yang, pada gilirannya, mendapatkannya dari vertex).
Masalahnya adalah ketika shader dikembangkan, Anda hanya setengah jalan, karena Anda kemudian harus berurusan dengan banyak kode WebGL. Memang, WebGL sangat kuat tetapi juga sangat rendah, dan Anda harus melakukan semuanya sendiri, mulai dari membuat buffer hingga mendefinisikan struktur vertex. Anda juga harus melakukan semua perhitungan, mengatur semua status, menangani pemuatan tekstur, dan seterusnya.
Terlalu keras? BABYLON.Bahan Pelindung Untuk Penyelamatan
Saya tahu apa yang Anda pikirkan: "Shaders benar-benar keren, tapi saya tidak ingin repot dengan pipa internal WebGL atau bahkan dengan matematika."
Dan Anda benar! Ini adalah pertanyaan yang sah, dan itulah alasan saya membuat Babylon.js!
Untuk menggunakan Babylon.js, pertama-tama Anda memerlukan halaman web sederhana:
<!DOCTYPE html> <html> <head> <title>Babylon.js</title> <script src="Babylon.js"></script> <script type="application/vertexShader"> precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Normal varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } </script> <script type="application/fragmentShader"> precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); } </script> <script src="index.js"></script> <style> html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; margin: 0px; overflow: hidden; } #renderCanvas { width: 100%; height: 100%; touch-action: none; -ms-touch-action: none; } </style> </head> <body> <canvas></canvas> </body> </html>
Anda akan melihat bahwa shader ditentukan oleh <script>
. Dengan Babylon.js, Anda juga dapat mendefinisikannya dalam file terpisah (file .fx
).
- sumber Babylon.js
- Repositori GitHub
Akhirnya, kode JavaScript utama adalah ini:
"use strict"; document.addEventListener("DOMContentLoaded", startGame, false); function startGame() { if (BABYLON.Engine.isSupported()) { var canvas = document.getElementById("renderCanvas"); var engine = new BABYLON.Engine(canvas, false); var scene = new BABYLON.Scene(engine); var camera = new BABYLON.ArcRotateCamera("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene); camera.attachControl(canvas); // Creating sphere var sphere = BABYLON.Mesh.CreateSphere("Sphere", 16, 5, scene); var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] }); amigaMaterial.setTexture("textureSampler", new BABYLON.Texture("amiga.jpg", scene)); sphere.material = amigaMaterial; engine.runRenderLoop(function () { sphere.rotation.y += 0.05; scene.render(); }); } };
Anda dapat melihat bahwa saya menggunakan BABYLON.ShaderMaterial
untuk menghilangkan beban kompilasi, penautan, dan penanganan shader.
Saat Anda membuat BABYLON.ShaderMaterial
, Anda harus menentukan elemen DOM yang digunakan untuk menyimpan shader atau nama dasar file tempat shader berada. Jika Anda memilih untuk menggunakan file, Anda harus membuat file untuk setiap shader dan menggunakan pola berikut: basename.vertex.fx
dan basename.fragment.fx
. Kemudian, Anda harus membuat materi seperti ini:
var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader", { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] });
Anda juga harus menentukan nama atribut dan uniform yang Anda gunakan.
Kemudian, Anda dapat langsung mengatur nilai uniform dan sampler Anda menggunakan setTexture
, setFloat
, setFloats
, setColor3
, setColor4
, setVector2
, setVector3
, setVector4
, setMatrix
.
Cukup sederhana, bukan?
Dan apakah Anda ingat matriks worldViewProjection
sebelumnya, menggunakan Babylon.js dan BABYLON.ShaderMaterial
. Anda hanya tidak perlu khawatir tentang hal itu! BABYLON.ShaderMaterial
akan secara otomatis menghitungnya untuk Anda karena Anda akan mendeklarasikannya dalam daftar seragam.
BABYLON.ShaderMaterial
juga dapat menangani matriks berikut untuk Anda:
-
world
, -
view
, -
projection
, -
worldView
, -
worldViewProjection
.
Tidak perlu matematika lagi. Misalnya, setiap kali Anda menjalankan sphere.rotation.y += 0.05
, matriks world
bola akan dibuat untuk Anda dan ditransmisikan ke GPU.
Lihat sendiri hasil langsungnya.
Buat Shader Anda Sendiri (CYOS)
Sekarang, mari kita menjadi lebih besar dan membuat halaman di mana Anda dapat secara dinamis membuat shader Anda sendiri dan melihat hasilnya dengan segera. Halaman ini akan menggunakan kode yang sama yang telah kita bahas sebelumnya dan akan menggunakan objek BABYLON.ShaderMaterial
untuk mengkompilasi dan mengeksekusi shader yang akan Anda buat.
Saya menggunakan editor kode ACE untuk Create Your Own Shader (CYOS). Ini adalah editor kode yang luar biasa, dengan penyorotan sintaks. Jangan ragu untuk melihatnya.
Menggunakan kotak kombo pertama, Anda akan dapat memilih shader yang telah ditentukan sebelumnya. Kita akan melihat masing-masing dari mereka setelahnya.
Anda juga dapat mengubah mesh (yaitu objek 3D) yang digunakan untuk melihat pratinjau shader Anda menggunakan kotak kombo kedua.
Tombol kompilasi digunakan untuk membuat BABYLON.ShaderMaterial
baru dari shader Anda. Kode yang digunakan oleh tombol ini adalah sebagai berikut:
// Compile shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "normal", "uv"], uniforms: ["world", "worldView", "worldViewProjection"] }); var refTexture = new BABYLON.Texture("ref.jpg", scene); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = new BABYLON.Texture("amiga.jpg", scene); shaderMaterial.setTexture("textureSampler", amigaTexture); shaderMaterial.setTexture("refSampler", refTexture); shaderMaterial.setFloat("time", 0); shaderMaterial.setVector3("cameraPosition", BABYLON.Vector3.Zero()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial;
Sangat sederhana, bukan? Materi siap mengirimi Anda tiga matriks yang telah dihitung sebelumnya ( world
, worldView
dan worldViewProjection
). Simpul akan datang dengan koordinat posisi, normal dan tekstur. Dua tekstur juga sudah dimuat untuk Anda:
Akhirnya, renderLoop
adalah tempat saya memperbarui dua seragam yang nyaman:
- Satu disebut
time
dan mendapat beberapa animasi lucu. - Yang lainnya disebut
cameraPosition
, yang memasukkan posisi kamera ke dalam shader Anda (berguna untuk persamaan pencahayaan).
engine.runRenderLoop(function () { mesh.rotation.y += 0.001; if (shaderMaterial) { shaderMaterial.setFloat("time", time); time += 0.02; shaderMaterial.setVector3("cameraPosition", camera.position); } scene.render(); });
Shader Dasar
Mari kita mulai dengan shader pertama yang didefinisikan di CYOS: shader dasar.
Kita sudah tahu shader ini. Ini menghitung gl_position
dan menggunakan koordinat tekstur untuk mengambil warna untuk setiap piksel.
Untuk menghitung posisi piksel, kita hanya perlu matriks worldViewProjection
dan posisi vertex:
precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
Koordinat tekstur ( uv
) ditransmisikan tanpa dimodifikasi ke pixel shader.
Harap dicatat bahwa kita perlu menambahkan precision mediump float
pada baris pertama untuk vertex dan pixel shader karena Chrome membutuhkannya. Ini menentukan bahwa, untuk kinerja yang lebih baik, kami tidak menggunakan nilai mengambang presisi penuh.
Pixel shader bahkan lebih sederhana, karena kita hanya perlu menggunakan koordinat tekstur dan mengambil warna tekstur:
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }
Kami sebelumnya melihat bahwa seragam textureSampler
diisi dengan tekstur amiga
. Jadi, hasilnya adalah sebagai berikut:
Shader Hitam dan Putih
Mari kita lanjutkan dengan shader baru: shader hitam dan putih. Tujuan dari shader ini adalah untuk menggunakan yang sebelumnya tetapi dengan mode rendering hanya hitam dan putih.
Untuk melakukannya, kita dapat menyimpan shader vertex yang sama. Pixel shader akan sedikit dimodifikasi.
Opsi pertama yang kita miliki adalah mengambil hanya satu komponen, seperti yang hijau:
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0); }
Seperti yang Anda lihat, alih-alih menggunakan .rgb
(operasi ini disebut swizzle), kami menggunakan .ggg
.
Tetapi jika kita menginginkan efek hitam putih yang benar-benar akurat, maka menghitung luminance (yang memperhitungkan semua komponen) akan lebih baik:
precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { float luminance = dot(texture2D(textureSampler, vUV).rgb, vec3(0.3, 0.59, 0.11)); gl_FragColor = vec4(luminance, luminance, luminance, 1.0); }
Operasi dot
(atau produk dot
) dihitung seperti ini: result = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z
.
Jadi, dalam kasus kami, luminance = r * 0.3 + g * 0.59 + b * 0.11
. (Nilai-nilai ini didasarkan pada fakta bahwa mata manusia lebih sensitif terhadap warna hijau.)
Kedengarannya keren, bukan?
Shader Bayangan Sel
Mari beralih ke shader yang lebih kompleks: shader cell-shading.
Yang ini akan meminta kita untuk mendapatkan titik normal dan posisi titik ke dalam pixel shader. Jadi, vertex shader akan terlihat seperti ini:
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 world; uniform mat4 worldViewProjection; // Varying varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vPositionW = vec3(world * vec4(position, 1.0)); vNormalW = normalize(vec3(world * vec4(normal, 0.0))); vUV = uv; }
Harap dicatat bahwa kami juga menggunakan matriks dunia karena posisi dan normal disimpan tanpa transformasi apa pun, dan kami harus menerapkan matriks dunia untuk memperhitungkan rotasi objek.
Pixel shader adalah sebagai berikut:
precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { float ToonThresholds[4]; ToonThresholds[0] = 0.95; ToonThresholds[1] = 0.5; ToonThresholds[2] = 0.2; ToonThresholds[3] = 0.03; float ToonBrightnessLevels[5]; ToonBrightnessLevels[0] = 1.0; ToonBrightnessLevels[1] = 0.8; ToonBrightnessLevels[2] = 0.6; ToonBrightnessLevels[3] = 0.35; ToonBrightnessLevels[4] = 0.2; vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); vec3 color = texture2D(textureSampler, vUV).rgb; if (ndl > ToonThresholds[0]) { color *= ToonBrightnessLevels[0]; } else if (ndl > ToonThresholds[1]) { color *= ToonBrightnessLevels[1]; } else if (ndl > ToonThresholds[2]) { color *= ToonBrightnessLevels[2]; } else if (ndl > ToonThresholds[3]) { color *= ToonBrightnessLevels[3]; } else { color *= ToonBrightnessLevels[4]; } gl_FragColor = vec4(color, 1.); }
Tujuan shader ini adalah untuk mensimulasikan cahaya, dan alih-alih menghitung bayangan halus, kami akan menerapkan cahaya sesuai dengan ambang kecerahan tertentu. Misalnya, jika intensitas cahaya antara 1 (maksimum) dan 0,95, warna objek (diambil dari tekstur) akan diterapkan secara langsung. Jika intensitasnya antara 0,95 dan 0,5, warna akan dilemahkan dengan faktor 0,8. Dan seterusnya.
Ada empat langkah utama dalam shader ini.
Pertama, kami mendeklarasikan ambang batas dan konstanta level.
Kemudian, kami menghitung pencahayaan menggunakan persamaan Phong (kami akan menganggap bahwa cahaya tidak bergerak):
vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW));
Intensitas cahaya per piksel tergantung pada sudut antara arah normal dan cahaya.
Kemudian, kita mendapatkan warna tekstur untuk pikselnya.
Akhirnya, kami memeriksa ambang batas dan menerapkan level ke warna.
Hasilnya terlihat seperti objek kartun:
Phong Shader
Kami menggunakan sebagian dari persamaan Phong di shader sebelumnya. Mari kita gunakan sepenuhnya sekarang.
Shader vertex jelas sederhana di sini karena semuanya akan dilakukan di shader piksel:
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vUV = uv; vPosition = position; vNormal = normal; }
Menurut persamaan, kita harus menghitung bagian "difusi" dan "spekular" menggunakan arah cahaya dan normal simpul:
precision highp float; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; // Uniforms uniform mat4 world; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 vLightPosition = vec3(0, 20, 10); // World values vec3 vPositionW = vec3(world * vec4(vPosition, 1.0)); vec3 vNormalW = normalize(vec3(world * vec4(vNormal, 0.0))); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); vec3 color = texture2D(textureSampler, vUV).rgb; // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); // Specular vec3 angleW = normalize(viewDirectionW + lightVectorW); float specComp = max(0., dot(vNormalW, angleW)); specComp = pow(specComp, max(1., 64.)) * 2.; gl_FragColor = vec4(color * ndl + vec3(specComp), 1.); }
Kita sudah menggunakan bagian difus di shader sebelumnya, jadi di sini kita hanya perlu menambahkan bagian specular. Anda dapat menemukan informasi lebih lanjut tentang Phong shading di Wikipedia.
Hasil dari bola kita:
Buang Shader
Untuk shader buang, saya ingin memperkenalkan konsep baru: kata kunci discard
.
Shader ini membuang setiap piksel non-merah dan menciptakan ilusi objek yang digali.
Shader vertex sama dengan yang digunakan oleh shader dasar:
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }
Pixel shader di sisinya harus menguji warna dan menggunakan membuang ketika, misalnya, komponen hijau terlalu tinggi:
precision highp float; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { vec3 color = texture2D(textureSampler, vUV).rgb; if (color.g > 0.5) { discard; } gl_FragColor = vec4(color, 1.); }
Hasilnya sedikit lucu:
Pembatas Gelombang
Kami telah banyak bermain dengan pixel shader, tetapi saya juga ingin memberi tahu Anda bahwa kami dapat melakukan banyak hal dengan vertex shader.
Untuk shader gelombang, kami akan menggunakan kembali shader piksel Phong.
Shader vertex akan menggunakan time
bernama seragam untuk mendapatkan beberapa nilai animasi. Menggunakan seragam ini, shader akan menghasilkan gelombang dengan posisi simpul:
precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; uniform float time; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec3 v = position; vx += sin(2.0 * position.y + (time)) * 0.5; gl_Position = worldViewProjection * vec4(v, 1.0); vPosition = position; vNormal = normal; vUV = uv; }
Sebuah sinus diterapkan ke position.y
, dan hasilnya adalah sebagai berikut:
Pemetaan Lingkungan Bulat
Yang ini sebagian besar terinspirasi oleh artikel “Membuat Bayangan Pemetaan Refleksi Bulat/Lingkungan.” Saya akan membiarkan Anda membaca artikel bagus itu dan bermain dengan shader terkait.
Fresnel Shader
Saya ingin mengakhiri artikel ini dengan favorit saya: shader Fresnel.
Shader ini digunakan untuk menerapkan intensitas yang berbeda sesuai dengan sudut antara arah pandang dan normal vertex.
Vertex shader sama dengan yang digunakan oleh cell-shading shader, dan kita dapat dengan mudah menghitung istilah Fresnel di pixel shader kita (karena kita memiliki posisi normal dan kamera, yang dapat digunakan untuk mengevaluasi arah tampilan):
precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 color = vec3(1., 1., 1.); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Fresnel float fresnelTerm = dot(viewDirectionW, vNormalW); fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.); gl_FragColor = vec4(color * fresnelTerm, 1.); }
Shader Anda?
Anda sekarang lebih siap untuk membuat shader Anda sendiri. Jangan ragu untuk memposting ke forum Babylon.js untuk membagikan eksperimen Anda!
Jika Anda ingin melangkah lebih jauh, berikut adalah beberapa tautan yang berguna:
- Babylon.js, situs web resmi
- Babylon.js, repositori GitHub
- Forum Babylon.js, Pengembang Game HTML5
- Buat Shader Anda Sendiri (CYOS), Babylon.js
- OpenGL Shading Language,” Wikipedia
- Bahasa Shading OpenGL, dokumentasi