Kembang Api
Selamat datang di Sky Adventure, perusahaan futuristik yang menyediakan kembang api terbaik di galaksi! 🎇
Kita akan membuat situs web 3D untuk memamerkan kembang api kita menggunakan Three.js, React Three Fiber, dan mesin VFX kita.
Inilah yang akan kita bangun bersama!
Proyek Pemula
Proyek pemula kita sudah mencakup yang berikut:
- Pengaturan dasar React Three Fiber dengan pulau mengambang yang indah dari mana kita akan meluncurkan kembang api kita.
- Efek pasca-pemrosesan untuk membuat cahaya dari model dan nantinya kembang api.
- Antarmuka pengguna sederhana yang dibuat dengan Tailwind CSS dengan tiga tombol untuk nantinya meluncurkan kembang api.
Begini yang kita dapatkan saat kita menjalankan proyek pemula.
Kembang Api
Untuk membuat kembang api, kita akan menggunakan mesin VFX yang kita bangun pada pelajaran sebelumnya. Mesin ini memungkinkan kita untuk membuat dan mengelola beberapa sistem partikel dengan perilaku yang berbeda.
useFireworks
Untuk mengelola kembang api secara efisien, kita akan membuat custom hook yang disebut useFireworks
. Hook ini akan menangani pembuatan dan pengelolaan kembang api.
Mari tambahkan pustaka zustand ke proyek kita:
yarn add zustand
Sekarang, di dalam folder yang disebut hooks
, buat file baru yang disebut useFireworks.js
:
import { create } dari "zustand"; const useFireworks = create((set, get) => { return { fireworks: [], }; }); export { useFireworks };
Sebuah store sederhana dengan array kosong dari kembang api.
Mari kita tambahkan metode untuk membuat kembang api:
// ... import { randFloat, randInt } dari "three/src/math/MathUtils.js"; const useFireworks = create((set) => { return { fireworks: [], addFirework: () => { set((state) => { return { fireworks: [ ...state.fireworks, { id: `${Date.now()}-${randInt(0, 100)}-${state.fireworks.length}`, position: [0, 0, 0], velocity: [randFloat(-8, 8), randFloat(5, 10), randFloat(-8, 8)], delay: randFloat(0.8, 2), color: ["skyblue", "pink"], }, ], }; }); }, }; }); // ...
addFirework
akan menambahkan kembang api baru ke store dengan:
id
: pengenal unik untuk digunakan sebagai kunci di komponen React.position
: di mana kembang api akan dimulai.velocity
: arah dan kecepatan kembang api sebelum meledak.delay
: waktu sebelum kembang api meledak.color
: array warna yang digunakan untuk partikel ledakan kembang api.
Sekarang kita bisa menghubungkan hook ini ke UI kita untuk meluncurkan kembang api.
Buka UI.jsx
dan hubungkan metode addFirework
ke tombol:
import { useFireworks } dari "../hooks/useFireworks"; export const UI = () => { const addFirework = useFireworks((state) => state.addFirework); return ( <section className="fixed inset-0 z-10 flex items-center justify-center"> {/* ... */} <div // ... > {/* ... */} <div className="flex gap-4"> <button // .. onClick={addFirework} > 🎆 Classic </button> <button // .. onClick={addFirework} > 💖 Love </button> <button // .. onClick={addFirework} > 🌊 Sea </button> </div> {/* ... */} </div> </section> ); };
Untuk memeriksa apakah berfungsi, mari buat komponen Fireworks.jsx
. Kita akan mencatat kembang api di konsol untuk sementara waktu:
import { useFireworks } dari "../hooks/useFireworks"; export const Fireworks = () => { const fireworks = useFireworks((state) => state.fireworks); console.log(fireworks); };
Dan mari kita tambahkan ke komponen Experience
// ... import { Fireworks } dari "./Fireworks"; export const Experience = () => { // ... return ( <> {/* ... */} <Float speed={0.6} rotationIntensity={2} position-x={4} floatIntensity={2} > <Fireworks /> <Gltf src="/models/SkyIsland.glb" /> </Float> {/* ... */} </> ); };
Kita mengimpor komponen Fireworks
dan menambahkannya di sebelah SkyIsland dalam komponen <Float />
kita.
Saat menekan tombol, kita dapat melihat di konsol bahwa kembang api ditambahkan dengan benar ke store.
Sebelum merepresentasikannya di scene, kita perlu menangani siklus hidup kembang api. Saat ini, kembang api ditambahkan tetapi tidak pernah dihapus.
Di dalam addFirework
pada hook useFireworks
kita, kita dapat menambahkan setTimeout
untuk menghapus kembang api setelah waktu tertentu.
Pertama kita perlu tahu kapan kembang api muncul. Kita bisa menambahkan properti time
ke objek kembang api:
{ id: `${Date.now()}-${randInt(0, 100)}-${state.fireworks.length}`, // ... time: Date.now(), },
Kemudian panggil setTimeout
untuk menghapus kembang api yang telah meledak dan memudar:
addFirework: () => { set((state) => { // ... }); setTimeout(() => { set((state) => ({ fireworks: state.fireworks.filter( (firework) => Date.now() - firework.time < 4000 // Max delay of 2 seconds + Max lifetime of particles of 2 seconds ), })); }, 4000); },
Kita memfilter kembang api yang telah ditambahkan lebih dari 4 detik yang lalu. Dengan cara ini, kita mempertahankan kembang api yang masih aktif.
Sesuaikan waktu sesuai dengan pengaturan akhir yang akan Anda gunakan untuk kembang api.
Saat menekan tombol, kita dapat melihat di konsol bahwa kembang api dihapus dengan benar setelah waktu tertentu.
Sekarang kita bisa masuk ke bagian yang sudah Anda tunggu-tunggu: membuat kembang api di scene!
Mesin VFX (Wawa VFX)
Agar tidak menyalin/menempelkan komponen dari pelajaran sebelumnya, saya menerbitkan mesin VFX sebagai package di npm: Wawa VFX. Anda dapat menginstalnya dengan menjalankan:
yarn add wawa-vfx@^1.0.0
Dengan menggunakan
@^1.0.0
, kita memastikan bahwa kita selalu menggunakan versi major 1 dari paket tetapi termasuk fitur dan perbaikan terbaru dari versi minor dan patch. Dengan cara ini, kita dapat memanfaatkan fitur terbaru dan perbaikan bug tanpa perubahan yang merusak.
Sekarang kita dapat menggunakan <VFXParticles />
dan <VFXEmitter />
dalam proyek kita!
Dalam experience, mari kita tambahkan komponen VFXParticles
ke dalam scene:
// ... import { VFXParticles } from "wawa-vfx"; export const Experience = () => { const controls = useRef(); return ( <> {/* ... */} <VFXParticles name="firework-particles" settings={{ nbParticles: 100000, gravity: [0, -9.8, 0], renderMode: "billboard", intensity: 3, }} /> <EffectComposer> <Bloom intensity={1.2} luminanceThreshold={1} mipmapBlur /> </EffectComposer> </> ); };
Kami menambahkan komponen VFXParticles
dengan pengaturan berikut:
100000
particles. Jika kita mencapai batas, particles yang paling lama akan dihapus, jumlah ini seharusnya lebih dari cukup untuk banyak kembang api pada saat bersamaan.gravity
untuk mensimulasikan gravitasi pada particles.-9.8
adalah gravitasi di Bumi. (Tapi tunggu, kita ada di luar angkasa! 👀)renderMode
kebillboard
untuk selalu menghadap kamera.intensity
ke3
untuk membuat particles bersinar di langit malam.
Tempatkan komponen
<VFXParticles />
tidak terlalu penting. Pastikan saja di tingkat atas scene.
Dan tambahkan komponen VFXEmitter
di samping VFXParticles
untuk membentuk ledakan dalam mode debug
dan mendapatkan akses ke kontrol visual:
// ... import { VFXEmitter, VFXParticles } from "wawa-vfx"; export const Experience = () => { const controls = useRef(); return ( <> {/* ... */} <VFXParticles name="firework-particles" settings={{ nbParticles: 100000, gravity: [0, -9.8, 0], renderMode: "billboard", intensity: 3, }} /> <VFXEmitter emitter="firework-particles" debug /> {/* ... */} </> ); };
Pastikan untuk mengatur prop emitter
ke name
yang sama dengan komponen VFXParticles
dan mengatur debug
ke true
untuk melihat kontrol.
Rancangkan versi pertama dari ledakan kembang api. 💥
Setelah Anda puas dengan pengaturannya, Anda dapat menghapus prop debug
dari komponen VFXEmitter
, tekan tombol ekspor, dan tempel pengaturannya ke dalam komponen VFXEmitter
.
<VFXEmitter emitter="firework-particles" settings={{ nbParticles: 5000, delay: 0, spawnMode: "burst", colorStart: ["skyblue", "pink"], particlesLifetime: [0.1, 2], size: [0.01, 0.4], startPositionMin: [-0.1, -0.1, -0.1], startPositionMax: [0.1, 0.1, 0.1], directionMin: [-1, -1, -1], directionMax: [1, 1, 1], startRotationMin: [degToRad(-90), 0, 0], startRotationMax: [degToRad(90), 0, 0], rotationSpeedMin: [0, 0, 0], rotationSpeedMax: [3, 3, 3], speed: [1, 12], }} />
Kita sudah siap menghubungkan fireworks dengan mesin VFX!
Ledakan Kembang Api
Di dalam file Fireworks.jsx
, mari kita buat komponen Firework
yang akan mewakili satu kembang api:
// ... import { useRef } from "react"; import { VFXEmitter } from "wawa-vfx"; export const Fireworks = () => { // ... }; const Firework = ({ velocity, delay, position, color }) => { const ref = useRef(); return ( <> <group ref={ref} position={position}> <VFXEmitter emitter="firework-particles" settings={{ nbParticles: 5000, delay: 0, spawnMode: "burst", colorStart: ["skyblue", "pink"], particlesLifetime: [0.1, 2], size: [0.01, 0.4], startPositionMin: [-0.1, -0.1, -0.1], startPositionMax: [0.1, 0.1, 0.1], directionMin: [-1, -1, -1], directionMax: [1, 1, 1], startRotationMin: [degToRad(-90), 0, 0], startRotationMax: [degToRad(90), 0, 0], rotationSpeedMin: [0, 0, 0], rotationSpeedMax: [3, 3, 3], speed: [1, 12], }} /> </group> </> ); };
Cukup potong/tempel VFXEmitter
dari komponen Experience
ke komponen Firework
.
Kami membungkusnya dalam <group />
agar dapat memindahkan kembang api di dalam scene berdasarkan position
dan velocity
.
Sekarang kita bisa menampilkan komponen Firework
untuk setiap kembang api dalam penyimpanan useFireworks
:
// ... export const Fireworks = () => { const fireworks = useFireworks((state) => state.fireworks); return fireworks.map((firework) => ( <Firework key={firework.id} {...firework} /> )); }; // ...
Kami melakukan pemetaan pada array fireworks
dan menampilkan komponen Firework
untuk setiap kembang api. Kami menggunakan id
sebagai key
untuk mengidentifikasi setiap kembang api secara unik.
Luar biasa, ketika menekan tombol, kita dapat melihat ledakan dalam scene!
Mari hubungkan parameter untuk menunda ledakan dan menetapkan warna yang tepat untuk kembang api:
// ... const Firework = ({ velocity, delay, position, color }) => { const ref = useRef(); return ( <> <group ref={ref} position={position}> <VFXEmitter emitter="firework-particles" settings={{ // ... delay, // ... colorStart: color, // ... }} /> </group> </> ); };
Kami mengatur delay
dan colorStart
ke properti delay
dan color
yang diteruskan ke komponen Firework
.
Karena hanya ada kemungkinan warna skyblue
dan pink
, kita dapat menambahkan lebih banyak kemungkinan dalam hook useFireworks
:
// ... import { randInt } from "three/src/math/MathUtils.js"; const colors = [ ["skyblue", "pink"], ["orange", "yellow"], ["green", "teal"], ["mediumpurple", "indigo"], ["#ff9fed", "#e885ff", "#ff85b2", "#d69eff"], ]; const useFireworks = create((set) => { return { fireworks: [], addFirework: () => { set((state) => { return { fireworks: [ ...state.fireworks, { // ... color: colors[randInt(0, colors.length - 1)], // ... }, ], }; }); // ... }, }; }); export { useFireworks };
Kami membuat array dari array warna. Setiap array mewakili kombinasi warna kembang api yang mungkin. Kami memilih secara acak salah satu dari mereka saat membuat kembang api baru.
Ketika menekan tombol, kita dapat melihat berbagai warna dan penundaan kembang api.
Namun, kembang api hanya muncul di tengah scene. Kita perlu membuatnya terbang dari pulau sebelum meledak! 💥
Peluncuran Kembang Api
Untuk membuat kembang api diluncurkan dari pulau, kita perlu memperbarui position
berdasarkan velocity
dan waktu yang telah berlalu.
Kita dapat menggunakan hook useFrame
untuk memperbarui posisi kembang api:
import { useFrame } from "@react-three/fiber"; // ... const Firework = ({ velocity, delay, position, color }) => { const ref = useRef(); useFrame((_, delta) => { if (ref.current) { ref.current.position.x += velocity[0] * delta; ref.current.position.y += velocity[1] * delta; ref.current.position.z += velocity[2] * delta; } }); // ... };
Kita memperbarui posisi x
, y
, dan z
kembang api berdasarkan velocity
dan waktu yang telah berlalu sejak frame terakhir (delta
).
Saat menekan tombol, kita dapat melihat kembang api meledak di posisi baru!
Bagus, tetapi kembang api tanpa jejak tidaklah terlalu mengesankan. Mari kita tambahkan jejak ke kembang api!
Jejak Kembang Api
Untuk membuat jejak, kita dapat memunculkan partikel di belakang kembang api yang akan memudar seiring waktu.
Karena kita sudah memiliki grup bergerak yang merepresentasikan kembang api, kita dapat menambahkan VFXEmitter
dengan pengaturan berbeda untuk membuat jejak:
// ... const Firework = ({ velocity, delay, position, color }) => { // ... return ( <> <group ref={ref} position={position}> <VFXEmitter emitter="firework-particles" // ... /> <VFXEmitter emitter="firework-particles" settings={{ duration: delay, nbParticles: 100 * delay, delay: 0, loop: false, colorStart: ["white", "skyblue"], particlesLifetime: [0.1, 0.6], size: [0.01, 0.05], startPositionMin: [-0.02, 0, -0.02], startPositionMax: [0.02, 0, 0.02], startRotationMin: [0, 0, 0], startRotationMax: [0, 0, 0], rotationSpeedMin: [-12, -12, -12], rotationSpeedMax: [12, 12, 12], directionMin: [-1, -1, -1], directionMax: [1, 1, 1], speed: [0, 0.5], }} /> </group> </> ); };
Kita menambahkan VFXEmitter
baru dengan duration
yang sama dengan delay
kembang api. Dengan cara ini, jejak akan berlangsung sepanjang kembang api sebelum meledak.
Kami menghitung jumlah partikel berdasarkan delay
untuk memiliki jumlah partikel yang tepat di jejak.
Dan kami menetapkan spawnMode
ke time
untuk memunculkan partikel seiring waktu dan tidak semuanya sekaligus.
Karena kita masih menggunakan bidang sederhana untuk partikel, kita menggunakan komponen
<VFXParticles />
yang sama untuk jejak untuk performa maksimum.
Kita sekarang dapat melihat jejak kembang api sebelum meledak!
Ini lebih baik, tetapi pergerakan kembang api tidak terasa benar. Kita perlu menerapkan kurva pada gerakan untuk membuatnya lebih alami.
Kurva Kembang Api
Alih-alih menggerakkan kembang api dalam garis lurus, kita dapat menerapkan kurva pada gerakannya agar terlihat lebih alami.
Untuk melakukan perhitungan, kita akan membuat ref
bernama age
untuk melacak waktu yang telah berlalu sejak kembang api dibuat dan menyesuaikan posisi y
berdasarkan hal tersebut.
// ... const Firework = ({ velocity, delay, position, color }) => { const ref = useRef(); const age = useRef(0); useFrame((_, delta) => { if (ref.current) { // ... ref.current.position.y += velocity[1] * delta + age.current * age.current * -9.0 * delta; // ... age.current += delta; } }); // ... };
PS: Ini adalah rumus yang sama digunakan di komponen
VFXParticles
untuk mensimulasikan gravitasi.
Kembang api sekarang memiliki kurva yang indah sebelum meledak!
Peluncur
Saat ini kembang api kita muncul di tengah-tengah scene. Namun, kita ingin mereka muncul di peluncur yang telah ditentukan di pulau tersebut.
Mari kita buka model dengan versi online Gltfjsx:
Kita dapat melihat mesh model kita dengan nama dan posisinya.
Mari kita salin semua posisi peluncur ke dalam array spawns
di bagian atas file hook useFireworks
:
const spawns = [ [1.004, -0.001, 3.284], [-2.122, -0.001, 2.678], [-0.988, -0.001, 3.287], [2.888, -0.001, 1.875], [2.115, -0.001, 2.684], ];
Untuk membuat kembang api muncul di posisi yang tepat, kita perlu memperbarui position
dari kembang api saat membuatnya:
// ... const useFireworks = create((set) => { return { fireworks: [], addFirework: () => { set((state) => { return { fireworks: [ ...state.fireworks, { id: `${Date.now()}-${randInt(0, 100)}-${state.fireworks.length}`, position: spawns[randInt(0, spawns.length - 1)], // ... }, ], }; }); // ... }, }; }); // ...
Kembang api muncul di posisi peluncur tetapi di bagian bawah setiap peluncur.
Mari kita geser posisi y
dari spawns agar kembang api muncul di bagian atas peluncur:
const SPAWN_OFFSET = 0.2; const spawns = [ [1.004, -0.001 + SPAWN_OFFSET, 3.284], [-2.122, -0.001 + SPAWN_OFFSET, 2.678], [-0.988, -0.001 + SPAWN_OFFSET, 3.287], [2.888, -0.001 + SPAWN_OFFSET, 1.875], [2.115, -0.001 + SPAWN_OFFSET, 2.684], ];
Kita menambahkan konstanta SPAWN_OFFSET
untuk menggeser posisi y
dari spawns. Lebih mudah untuk menyesuaikan jika diperlukan.
Jauh lebih baik, kembang api sekarang muncul di bagian atas peluncur! Terasa lebih seperti pertunjukan kembang api yang nyata!
SFX
Untuk pengalaman yang lebih mendalam, kita dapat menambahkan efek suara pada kembang api. 🎶
Soundbank
Cara terbaik untuk mendapatkan efek suara adalah dengan merekamnya sendiri dan memprosesnya agar sesuai dengan kebutuhan Anda. Namun, karena kita tidak semua adalah desainer suara, kita bisa menggunakan efek suara yang kita temukan secara online!
Pastikan untuk menghormati lisensi efek suara yang Anda gunakan. Ketika mencari efek suara gratis, seperti model 3D, cari efek suara yang royalty-free
.
Pixabay dan Freesound adalah tempat yang baik untuk memulai mencari sumber daya gratis.
Epidemic Sound dan Artlist adalah opsi berbayar yang bagus jika Anda menginginkan pilihan yang lebih luas dan efek suara berkualitas tinggi.
Untuk kembang api kita, saya menemukan:
- Efek suara ini untuk peluncuran/trail.
- Efek suara ini untuk ledakan.
Unduh dan tambahkan ke proyek Anda dalam folder bernama sfxs
.
Positional audio
Daripada hanya membuat komponen Audio
standar yang memutarnya saat kembang api diluncurkan, kita akan menggunakan audio yang diposisikan secara spasial agar suaranya mengikuti kembang api.
Untuk melakukannya, kita akan menggunakan PositionalAudio dari Three.js.
Wrapper drei-nya dapat digunakan dengan komponen <PositionalAudio />
.
Apa yang dilakukannya adalah berdasarkan posisi komponen PositionalAudio
dan pendengarnya (kamera), ini akan menyesuaikan volume dan panning stereo dari suara agar terasa seperti berasal dari posisi komponen.
Mari mulai dengan menambahkan suara peluit ke dalam komponen Firework
:
// ... import { PositionalAudio } from "@react-three/drei"; const Firework = ({ velocity, delay, position, color }) => { // ... return ( <> <group ref={ref} position={position}> {/* Ledakan */} <VFXEmitter // ... /> {/* Jejak */} <PositionalAudio url="sfxs/firework-whistle-190306.mp3" distance={20} loop={false} autoplay /> <VFXEmitter // ... /> </group> </> ); };
Kita menambahkan komponen <PositionalAudio />
dengan url
dari suara peluit.
Kita set distance
ke 20
. Ini merepresentasikan jarak maksimum di mana suara bisa terdengar. Di luar jarak ini, suara akan memudar.
Sesuaikan dengan scene dan efek suara Anda.
Suara dimainkan ketika kembang api diluncurkan dan mengikuti kembang api dalam scene!
Sekarang mari kita tambahkan suara untuk ledakan:
import { useEffect } from "react"; const Firework = ({ velocity, delay, position, color }) => { const ref = useRef(); const age = useRef(0); const audioRef = useRef(); useEffect(() => { setTimeout(() => { audioRef.current?.play(); }, delay * 1000); }, []); // ... return ( <> <group ref={ref} position={position}> {/* Ledakan */} <PositionalAudio url="sfxs/firecracker-corsair-4-95046.mp3" ref={audioRef} distance={20} loop={false} autoplay={false} /> <VFXEmitter emitter="firework-particles" // /> {/* ... */} </group> </> ); };
Untuk yang satu ini, kita menggunakan useEffect
untuk memutar suara setelah delay
sebelum kembang api meledak. (Kita mengalikan delay
dengan 1000
untuk mengonversinya menjadi milidetik).
Jangan lupa untuk set autoplay
ke false
untuk mencegah suara diputar ketika komponen dimount.
Suara dimainkan ketika kembang api meledak dan kita bisa merasakan jika meledak di kiri atau kanan!
Memuat Awal Suara
Apakah Anda memperhatikan bahwa adegan menghilang ketika suara dimainkan? Itu karena seluruh adegan kita dibungkus dalam komponen <Suspense />
. Ketika suara dimuat, itu merender fallback dari komponen <Suspense />
(dalam kasus kita tidak ada).
Kita memiliki 2 solusi. Bungkus komponen <PositionalAudio />
dalam komponen <Suspense />
dengan fallback atau muat suara di awal aplikasi.
Untuk proyek kita, kita akan memuat suara di awal aplikasi.
Karena tidak ada hook berbasis useLoader
untuk suara dalam React Three Fiber, kita akan memasang komponen <positionAudio />
dummy di bagian atas App
untuk memuat suara:
// ... import { Loader, PositionalAudio } from "@react-three/drei"; function App() { return ( <> {/* ... */} <Canvas shadows camera={{ position: [12, 8, 26], fov: 30 }}> <color attach="background" args={["#110511"]} /> <Suspense> <Preloader /> {/* ... */} </Suspense> </Canvas> </> ); } const Preloader = () => { return ( <> <PositionalAudio url="sfxs/firework-whistle-190306.mp3" autoplay={false} /> <PositionalAudio url="sfxs/firecracker-corsair-4-95046.mp3" autoplay={false} /> </> ); }; export default App;
Dengan memasang komponen <PositionalAudio />
di bagian atas App
, adegan akan hanya dirender ketika suara sudah dimuat.
Kami menggunakan autoplay
diatur ke false
untuk mencegah suara bermain ketika dimuat.
Kita hanya memiliki 2 file audio untuk dimuat, tetapi jika Anda memiliki lebih banyak, Anda dapat membuat array
sounds
dan memetakan untuk memuat semua suara.
Anda dapat melihat dua mp3
dimuat di awal aplikasi di tab Network dari DevTools. Tidak ada pemuatan lainnya yang dilakukan ketika meluncurkan kembang api.
Tahap pro
Mari kita sisihkan kembang api untuk sesaat dan fokus pada pengaturan panggung dari tampilan.
Untuk benar-benar membuat kembang api bersinar, kita perlu mengatur suasana yang tepat.
Langit Berwarna
Ada banyak cara untuk menciptakan latar belakang dan langit di Three.js. Salah satu solusi favorit saya adalah membuat bola besar yang mengelilingi tampilan dan mengaplikasikan tekstur atau custom shader padanya.
Mari kita buat file GradientSky.jsx
:
import { shaderMaterial } from "@react-three/drei"; import { extend } from "@react-three/fiber"; import { useControls } from "leva"; import { BackSide, Color } from "three"; import { degToRad } from "three/src/math/MathUtils.js"; export const GradientSky = () => { const { colorBottom, colorTop, colorMiddle, blendMiddle, blendIntensity } = useControls("Sky 🌄", { colorTop: "#0e1c3e", colorMiddle: "#ffa200", colorBottom: "#160c2a", blendMiddle: { value: 0.2, min: 0, max: 1, step: 0.01, }, blendIntensity: { value: 0.06, min: 0, max: 1, step: 0.01, }, }); return ( <mesh rotation-x={degToRad(-5)} depthWrite={false} depthTest={false}> <sphereGeometry args={[40]} /> <gradientMaterial side={BackSide} colorBottom={colorBottom} colorTop={colorTop} colorMiddle={colorMiddle} blendMiddle={blendMiddle} blendIntensity={blendIntensity} toneMapped={false} depthWrite={false} /> </mesh> ); }; const GradientMaterial = shaderMaterial( { colorTop: new Color("white"), colorBottom: new Color("skyblue"), colorMiddle: new Color("pink"), blendMiddle: 0.2, blendIntensity: 1, }, /* glsl */ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, /* glsl */ ` uniform vec3 colorTop; uniform vec3 colorBottom; uniform vec3 colorMiddle; uniform float blendMiddle; uniform float blendIntensity; varying vec2 vUv; void main() { vec3 mixedTop = mix(colorMiddle, colorTop, smoothstep(0.498, 0.502, vUv.y)); vec3 mixedBottom = mix(colorMiddle, colorBottom, smoothstep(0.502, 0.498, vUv.y)); vec3 mixedColor = mix(colorBottom, colorTop, smoothstep(0.45, 0.55, vUv.y)); float blendMiddle = smoothstep(0.5-blendMiddle, 0.5, vUv.y) * smoothstep(0.5 + blendMiddle, 0.5, vUv.y) * blendIntensity; vec3 finalColor = mix(mixedColor, colorMiddle, blendMiddle); gl_FragColor = vec4(finalColor, 1.0); } ` ); extend({ GradientMaterial });
Karena tidak ada yang baru di file ini, saya akan langsung menjelaskannya dengan singkat:
- Kita membuat komponen
GradientSky
yang akan menciptakan bola raksasa di sekitar tampilan. - Kita menggunakan
shaderMaterial
untuk membuat material kustom untuk bola tersebut. - Kita menggunakan
useControls
untuk membuat antarmuka guna menyesuaikan warna langit. - Kita membuat gradasi dari
colorBottom
kecolorTop
dengancolorMiddle
di tengahnya. - Kita menggunakan
smoothstep
untuk menggabungkan warna-warna tersebut dan menciptakan gradasi yang indah. - Kita mengatur
side
keBackSide
karena kita berada di dalam bola dan ingin gradasi terlihat dari dalam. - Kita menggunakan
extend
untuk menambahkanGradientMaterial
ke dalam material yang tersedia di React Three Fiber.
Kita sekarang bisa menambahkan komponen GradientSky
ke dalam komponen Experience
:
// ... import { GradientSky } from "./GradientSky"; export const Experience = () => { const controls = useRef(); return ( <> {/* ... */} <directionalLight position={[1, 0.5, -10]} intensity={2} color="#ffe7ba" /> <GradientSky /> {/* ... */} </> ); };
Kini Anda bisa menyesuaikan warna langit sesuai selera Anda dan menciptakan suasana yang indah!
Untuk mendapatkan nuansa lebih luar-angkasa/langit-malaml, Anda bisa menambahkan bintang di langit. Silakan gunakan yang Anda buat di pelajaran sebelumnya atau yang dari drei.
Saya akan menambahkan yang dari drei:
// ... import { Stars } from "@react-three/drei"; export const Experience = () => { const controls = useRef(); return ( <> {/* ... */} <Stars fade speed={3} count={2000} /> <GradientSky /> {/* ... */} </> ); };
Langit kini dipenuhi dengan beberapa bintang untuk membuatnya semakin magis! ✨
Awan
Untuk menambahkan kedalaman dan suasana, kita dapat menambahkan awan ke dalam adegan.
Kita akan menggunakan komponen Cloud dari drei dalam komponen Experience
:
// ... import { MeshBasicMaterial } from "three"; import { useControls } from "leva"; import { Cloud, Clouds } from "@react-three/drei"; export const Experience = () => { const controls = useRef(); const { cloud1Color, cloud2Color, cloud3Color } = useControls("Clouds ☁️", { cloud1Color: "#54496c", cloud2Color: "orange", cloud3Color: "#9d7796", }); return ( <> {/* ... */} <Clouds material={MeshBasicMaterial}> <Cloud position-z={0} position-y={-5} seed={2} scale={2} volume={8} color={cloud1Color} fade={1000} /> <Cloud position-x={12} position-z={-10} seed={1} scale={2} volume={6} color={cloud2Color} fade={800} /> <Cloud position-x={-8} position-z={10} seed={5} scale={1} volume={12} color={cloud3Color} fade={100} /> </Clouds> {/* ... */} </> ); };
Bermainlah dengan position
, seed
, scale
, volume
, color
, dan fade
untuk menciptakan awan yang Anda suka.
Saya menambahkan kontrol untuk warna awan agar dapat menyesuaikannya dengan mudah saat menyesuaikan warna langit.
Lihat bagaimana sedikit demi sedikit adegan ini mulai terasa hidup! ☁️
Animasi Kamera
Untuk membuat adegan lebih menarik, dinamis, dan untuk melihat kembang api dengan lebih baik, kita dapat menganimasikan kamera.
Pertama, mari kita animasikan kamera saat adegan dimuat agar melihat ke arah pulau:
// ... import { useEffect } from "react"; export const Experience = () => { const controls = useRef(); useEffect(() => { controls.current.setLookAt(0, 15, 10, 0, 25, 0); controls.current.setLookAt(12, 8, 26, 4, 0, 0, true); }, []); // ... };
Kita mengatur kamera untuk melihat jauh secara instan ketika komponen dimuat, setelah itu kita memanggil metode setLookAt
untuk menganimasikan kamera agar melihat ke arah pulau. (Dengan parameter terakhir diset ke true
untuk menganimasikan kamera secara halus).
Sekarang kamera kita menganimasikan dengan halus untuk melihat ke arah pulau saat adegan dimuat. Pulau sekarang menjadi pusat perhatian!
Sekarang mari kita animasikan kamera untuk zoom-out saat kembang api diluncurkan:
// ... import { Fireworks } from "./Fireworks"; export const Experience = () => { // ... const fireworks = useFireworks((state) => state.fireworks); useEffect(() => { if (fireworks.length) { controls.current.setLookAt(0, 12, 42, 0, 0, 0, true); } else { controls.current.setLookAt(12, 8, 26, 4, 0, 0, true); } }, [fireworks]); // ... };
Kita menggunakan useEffect
dengan array fireworks
sebagai dependensi untuk memicu animasi kamera ketika jumlah kembang api berubah.
Ketika kembang api diluncurkan, kita menganimasikan kamera untuk zoom-out dan melihat kembang api. Ketika tidak ada kembang api, kita menganimasikan kamera untuk melihat lebih dekat ke pulau.
Sekarang kamera melakukan zoom-out saat kembang api diluncurkan untuk melihat ledakan dengan lebih baik!
Tema Warna
Waktunya menambahkan sentuhan akhir ke adegan kita: tema warna untuk kembang api!
Kita memiliki 3 tombol berbeda untuk meluncurkan kembang api:
- Classic: Warna acak saat ini
- Love: Warna merah muda dan merah
- Sea: Warna biru, putih, dan biru kehijauan
Dalam useFireworks.js
mari kita ubah array colors
sederhana kita menjadi objek themeColors
:
const themeColors = { classic: [ ["skyblue", "pink"], ["orange", "yellow"], ["green", "teal"], ["mediumpurple", "indigo"], ["#ff9fed", "#e885ff", "#ff85b2", "#d69eff"], ], love: [ ["red"], ["red", "fuchsia"], ["red", "pink"], ["pink"], ["fuchsia", "yellow"], ], sea: [ ["skyblue", "white"], ["deepskyblue", "skyblue"], ["aquamarine", "mediumaquamarine"], ["#368bff"], ], };
Dan dalam fungsi addFirework
, kita akan menambahkan parameter firework
untuk mendapatkan tema dari kembang api:
addFirework: (firework) => { set((state) => { const colors = themeColors[firework.theme]; // ... }); // ... },
Kita menggunakan firework.theme
untuk mendapatkan warna yang tepat dari objek themeColors
.
Sekarang dalam UI
kita perlu menentukan tema kembang api saat meluncurkannya:
<button onClick={() => addFirework({ theme: "classic" })} // ... > 🎆 Classic </button> <button onClick={() => addFirework({ theme: "love" })} // ... > 💖 Love </button> <button onClick={() => addFirework({ theme: "sea" })} // ... > 🌊 Sea </button>
Dengan mengirimkan objek, kita mempermudah penambahan lebih banyak parameter ke kembang api di masa depan.
Setiap tombol sekarang meluncurkan kembang api dengan tema warna yang berbeda!
Kesimpulan
Kita berhasil! 🎉 Tekan tombol untuk merayakannya! 🎇
Kita telah menciptakan pertunjukan kembang api yang indah di angkasa dengan efek suara, langit berwarna, awan, dan kamera dinamis.
Apa yang kita pelajari dalam pelajaran ini dapat diterapkan untuk menciptakan banyak adegan dan pengalaman berbeda. Kemungkinannya tidak terbatas!
Jenis efek yang paling mirip dengan kembang api adalah konfeti. Ini adalah latihan yang baik untuk mencoba membuat ledakan konfeti dengan mesin VFX. 🎊
Saya tidak sabar untuk melihat efek luar biasa apa yang akan Anda buat dengan mesin VFX! 🚀
React Three Fiber: The Ultimate Guide to 3D Web Development
✨ You have reached the end of the preview ✨
Go to the next level with Three.js and React Three Fiber!
Get full access to this lesson and the complete course when you enroll:
- 🔓 Full lesson videos with no limits
- 💻 Access to the final source code
- 🎓 Course progress tracking & completion
- 💬 Invite to our private Discord community
One-time payment. Lifetime updates included.