WebGPU / TSL

Starter pack

WebGPU adalah standar web baru yang menyediakan API tingkat rendah untuk merender grafik dan melakukan komputasi pada GPU. Ini dirancang untuk menjadi penerus WebGL, menawarkan performa yang lebih baik dan fitur yang lebih canggih.

Kabar baiknya, sekarang kita dapat menggunakannya dengan Three.js dengan sedikit perubahan pada basis kode.

Dalam pelajaran ini, kita akan menjelajahi cara menggunakan WebGPU dengan Three.js dan React Three Fiber, serta cara menulis shaders menggunakan Three Shading Language (TSL) yang baru.

Jika Anda baru dalam bidang shaders, saya sarankan untuk menyelesaikan bab Shaders terlebih dahulu sebelum melanjutkan yang ini.

Renderer WebGPU

Untuk menggunakan WebGPU API alih-alih yang WebGL, kita perlu menggunakan WebGPURenderer (Belum ada bagian khusus pada dokumentasi Three.js) sebagai pengganti WebGLRenderer.

Dengan React Three Fiber, saat membuat komponen <Canvas>, penyiapan renderer dilakukan secara otomatis. Namun, kita dapat menimpa renderer default dengan melewatkan fungsi ke prop gl dari komponen <Canvas>.

Dalam App.jsx, kita memiliki komponen <Canvas> yang menggunakan WebGLRenderer default. Mari kita modifikasi untuk menggunakan WebGPURenderer.

Pertama, kita perlu menghentikan frameloop hingga WebGPURenderer siap. Kita dapat melakukannya dengan mengatur prop frameloop ke never.

// ...
import { useState } from "react";

function App() {
  const [frameloop, setFrameloop] = useState("never");

  return (
    <>
      {/* ... */}
      <Canvas
        // ...
        frameloop={frameloop}
      >
        {/* ... */}
      </Canvas>
    </>
  );
}

export default App;

Selanjutnya, kita perlu mengimpor versi WebGPU dari Three.js:

import * as THREE from "three/webgpu";

Saat menggunakan WebGPU, kita harus menggunakan modul three/webgpu alih-alih modul three default. Ini karena WebGPURenderer tidak termasuk dalam build default Three.js.

Kemudian, kita dapat menggunakan prop gl untuk membuat instance WebGPURenderer baru:

// ...

function App() {
  const [frameloop, setFrameloop] = useState("never");

  return (
    <>
      {/* ... */}
      <Canvas
        // ...
        gl={(canvas) => {
          const renderer = new THREE.WebGPURenderer({
            canvas,
            powerPreference: "high-performance",
            antialias: true,
            alpha: false,
            stencil: false,
            shadowMap: true,
          });
          renderer.init().then(() => {
            setFrameloop("always");
          });
          return renderer;
        }}
      >
        {/* ... */}
      </Canvas>
    </>
  );
}
// ...

Kita membuat instance WebGPURenderer baru dan melewatkan elemen canvas ke dalamnya. Kita juga mengatur beberapa opsi untuk renderer, seperti powerPreference, antialias, alpha, stencil, dan shadowMap. Opsi-opsi ini mirip dengan yang digunakan dalam WebGLRenderer.

Terakhir, kita memanggil metode init() dari renderer untuk menginisialisasinya. Setelah inisialisasi selesai, kita mengatur status frameloop ke "always" untuk memulai rendering.

Mari kita lihat hasilnya di browser:

Kubus kita sekarang dirender menggunakan WebGPURenderer alih-alih WebGLRenderer.

Semudah itu! Kita telah berhasil mengatur WebGPURenderer dalam aplikasi React Three Fiber kita. Anda sekarang dapat menggunakan API Three.js yang sama untuk membuat dan memanipulasi objek 3D, seperti yang Anda lakukan dengan WebGLRenderer.

Perubahan terbesar adalah ketika menulis shaders. API WebGPU menggunakan bahasa bayangan yang berbeda dari WebGL, yang berarti kita harus menulis shaders kita dengan cara yang berbeda. Dalam WGSL alih-alih GLSL.

Ini adalah tempat di mana Three Shading Language (TSL) masuk.

Three Shading Language

TSL adalah bahasa shading baru yang dirancang untuk digunakan dengan Three.js untuk menulis shaders dengan cara yang lebih ramah pengguna menggunakan pendekatan berbasis node.

Keuntungan besar dari TSL adalah tidak terikat pada renderer tertentu, yang berarti Anda dapat menggunakan shader yang sama dengan renderer yang berbeda, seperti WebGL dan WebGPU.

Ini membuatnya lebih mudah untuk menulis dan memelihara shader, karena Anda tidak perlu memikirkan perbedaan antara dua bahasa shading.

Ini juga tahan masa depan, karena jika ada renderer baru yang dirilis, kita bisa menggunakan shader yang sama tanpa perubahan selama TSL mendukungnya.

Three Shading Language masih dalam pengembangan, tetapi sudah tersedia dalam versi terbaru dari Three.js. Cara terbaik untuk mempelajarinya, dan untuk melacak perubahan, adalah dengan memeriksa halaman wiki Three Shading Language. Saya menggunakannya secara ekstensif untuk belajar bagaimana menggunakannya.

Node based materials

Untuk memahami cara membuat shader dengan TSL, kita perlu memahami apa yang dimaksud dengan berbasis node.

Dalam pendekatan berbasis node, kita membuat shader dengan menghubungkan node-node berbeda untuk membuat grafik. Setiap node mewakili operasi atau fungsi tertentu, dan koneksi antara node-node tersebut mewakili aliran data.

Pendekatan ini memiliki banyak keuntungan, seperti:

  • Representasi visual: Lebih mudah untuk memahami dan memvisualisasikan aliran data dan operasi dalam shader.
  • Reusability: Kita dapat membuat node yang dapat digunakan kembali dalam shader yang berbeda.
  • Fleksibilitas: Kita dapat dengan mudah memodifikasi dan mengubah perilaku shader dengan menambah atau menghapus node.
  • Ekstensibilitas: Menambah/Mengkustomisasi fitur dari material yang ada sekarang menjadi lebih mudah.
  • Agnostic: TSL akan menghasilkan kode yang sesuai untuk renderer target, apakah itu WebGL (GLSL) atau WebGPU (WGSL).

Sebelum kita mulai membuat kode material berbasis node pertama kita, kita dapat menggunakan Three.js playground online untuk bereksperimen dengan node-system secara visual.

Buka Three.js playground dan di bagian atas, klik tombol Examples, dan pilih basic > fresnel.

Three.js playground

Anda seharusnya melihat editor material berbasis node dengan dua node color dan sebuah node float yang terhubung ke node fresnel. (Color A, Color B, dan Fresnel Factor)

Node fresnel terhubung ke warna dari Basic Material yang menghasilkan pewarnaan Teapot dengan efek fresnel.

Three.js playground

Gunakan tombol Splitscreen untuk melihat hasilnya di sebelah kanan.

Misalkan kita ingin mempengaruhi opasitas dari Basic Material berdasarkan waktu. Kita dapat menambahkan node Timer dan menghubungkannya ke node Fract untuk mengatur ulang waktu ke 0 setelah mencapai 1. Kemudian kita menghubungkannya ke input opacity dari Basic Material.

Teapot kita sekarang memudar lalu menghilang dan muncul kembali.

Luangkan waktu untuk bermain dengan node-node yang berbeda dan lihat bagaimana mereka mempengaruhi material.

Sekarang kita memiliki pemahaman dasar tentang bagaimana material berbasis node bekerja, mari kita lihat bagaimana menggunakan material berbasis node baru dari Three.js dalam React Three Fiber.

Implementasi React Three Fiber

Sejauh ini, dengan WebGL, kita telah menggunakan MeshBasicMaterial, MeshStandardMaterial, atau bahkan ShaderMaterial kustom untuk membuat material kita.

Saat menggunakan WebGPU, kita perlu menggunakan material baru yang kompatibel dengan TSL. Nama-nama mereka sama dengan yang kita gunakan sebelumnya dengan Node sebelum Material:

  • MeshBasicMaterial -> MeshBasicNodeMaterial
  • MeshStandardMaterial -> MeshStandardNodeMaterial
  • MeshPhysicalMaterial -> MeshPhysicalNodeMaterial
  • ...

Untuk menggunakannya secara deklaratif dengan React Three Fiber, kita perlu extend mereka. Dalam App.jsx:

// ...
import { extend } from "@react-three/fiber";

extend({
  MeshBasicNodeMaterial: THREE.MeshBasicNodeMaterial,
  MeshStandardNodeMaterial: THREE.MeshStandardNodeMaterial,
});
// ...

Di versi masa depan dari React Three Fiber, ini mungkin dilakukan secara otomatis.

Sekarang kita bisa menggunakan MeshBasicNodeMaterial dan MeshStandardNodeMaterial yang baru di dalam komponen kita.

Mari kita ganti MeshStandardMaterial dari kubus dalam komponen Experience kita dengan MeshStandardNodeMaterial:

<mesh>
  <boxGeometry args={[1, 1, 1]} />
  <meshStandardNodeMaterial color="pink" />
</mesh>

WebGPU Pink Cube

Kita dapat menggunakan MeshStandardNodeMaterial seperti kita menggunakan MeshStandardMaterial.

Kubus kita sekarang bergantung pada MeshStandardNodeMaterial alih-alih MeshStandardMaterial. Kita sekarang dapat menggunakan nodes untuk menyesuaikan material tersebut.

Node Warna

Mari kita pelajari cara membuat node kustom untuk mempersonalisasi material kita dengan TSL.

Pertama, mari kita buat komponen baru bernama PracticeNodeMaterial.jsx di dalam folder src/components.

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  return <meshStandardNodeMaterial color={colorA} />;
};

Dan di dalam Experience.jsx, ganti kubus kita dengan plane menggunakan PracticeNodeMaterial:

// ...
import { PracticeNodeMaterial } from "./PracticeNodeMaterial";

export const Experience = () => {
  return (
    <>
      {/* ... */}

      <mesh rotation-x={-Math.PI / 2}>
        <planeGeometry args={[2, 2, 200, 200]} />
        <PracticeNodeMaterial />
      </mesh>
    </>
  );
};

WebGPU Plane

Kita memiliki plane dengan PracticeNodeMaterial.

Untuk mempersonalisasi material kita, kita sekarang dapat mengubah berbagai node yang tersedia menggunakan node yang berbeda. Daftar yang tersedia dapat ditemukan di halaman wiki.

Mari kita mulai dengan node colorNode untuk mengubah warna material kita. Dalam PracticeNodeMaterial.jsx:

import { color } from "three/tsl";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  return <meshStandardNodeMaterial colorNode={color(colorA)} />;
};

Kita mengatur prop colorNode menggunakan node color dari modul three/tsl. Node color mengambil warna sebagai argumen dan mengembalikan node warna yang dapat digunakan dalam material.

Ini memberikan hasil yang sama seperti sebelumnya, tetapi sekarang kita dapat menambahkan lebih banyak node untuk mempersonalisasi material kita.

Mari kita impor node mix dan uv dari modul three/tsl dan gunakan untuk mencampur dua warna berdasarkan koordinat UV dari plane.

import { color, mix, uv } from "three/tsl";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  return (
    <meshStandardNodeMaterial
      colorNode={mix(color(colorA), color(colorB), uv())}
    />
  );
};

Ini akan menjalankan node yang berbeda untuk mendapatkan warna akhir dari material. Node mix mengambil dua warna dan faktor (dalam hal ini, koordinat UV) dan mengembalikan warna yang merupakan campuran dari kedua warna berdasarkan faktor tersebut.

Ini persis seperti menggunakan fungsi mix di GLSL, tetapi sekarang kita dapat menggunakannya dengan pendekatan berbasis node. (Jauh lebih mudah dibaca!)

WebGPU Plane with Mix

Kita sekarang dapat melihat dua warna bercampur berdasarkan koordinat UV dari plane.

Yang luar biasa, adalah kita tidak memulai dari awal. Kita menggunakan MeshStandardNodeMaterial yang ada dan hanya menambahkan node kustom kita ke dalamnya. Yang berarti bayangan, cahaya, dan semua fitur lain dari MeshStandardNodeMaterial masih tersedia.

Menyatakan node secara inline adalah baik untuk logika node yang sangat sederhana, tetapi untuk logika yang lebih kompleks, saya merekomendasikan untuk mendeklarasikan node (dan nanti uniform, dan lebih banyak lagi) dalam hook useMemo:

// ...
import { useMemo } from "react";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  const { nodes } = useMemo(() => {
    return {
      nodes: {
        colorNode: mix(color(colorA), color(colorB), uv()),
      },
    };
  }, []);

  return <meshStandardNodeMaterial {...nodes} />;
};

Ini melakukan hal yang persis sama seperti sebelumnya, tetapi sekarang kita dapat menambahkan lebih banyak node ke objek nodes dan meneruskannya ke meshStandardNodeMaterial dengan cara yang lebih terorganisir/generic.

Dengan mengubah prop colorA dan colorB, itu tidak akan menyebabkan kompilasi ulang shader berkat hook useMemo.

Mari kita tambahkan controls untuk mengubah warna material. Dalam Experience.jsx:

// ...
import { useControls } from "leva";

export const Experience = () => {
  const { colorA, colorB } = useControls({
    colorA: { value: "skyblue" },
    colorB: { value: "blueviolet" },
  });
  return (
    <>
      {/* ... */}

      <mesh rotation-x={-Math.PI / 2}>
        <planeGeometry args={[2, 2, 200, 200]} />
        <PracticeNodeMaterial colorA={colorA} colorB={colorB} />
      </mesh>
    </>
  );
};

Warna default berfungsi dengan benar, tetapi memperbarui warna tidak memiliki efek.

Ini karena kita perlu meneruskan warna sebagai uniforms ke meshStandardNodeMaterial.

Uniforms

Untuk mendeklarasikan uniforms dalam TSL, kita dapat menggunakan node uniform dari modul three/tsl. Node uniform mengambil nilai sebagai argumen (dapat berupa berbagai tipe seperti float, vec3, vec4, dll.) dan mengembalikan uniform node yang dapat digunakan dalam berbagai node sambil diperbarui dari kode komponen kita.

Mari ubah warna yang di-hardcode menjadi uniforms dalam PracticeNodeMaterial.jsx:

// ...
import { uniform } from "three/tsl";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  const { nodes, uniforms } = useMemo(() => {
    const uniforms = {
      colorA: uniform(color(colorA)),
      colorB: uniform(color(colorB)),
    };

    return {
      nodes: {
        colorNode: mix(uniforms.colorA, uniforms.colorB, uv()),
      },
      uniforms,
    };
  }, []);

  return <meshStandardNodeMaterial {...nodes} />;
};

Kami mendeklarasikan objek uniforms untuk organisasi kode yang lebih baik, dan kami menggunakan nilai uniform daripada nilai default yang kami dapatkan saat pembuatan node kami.

Dengan mengembalikannya dalam useMemo, sekarang kita memiliki akses ke uniforms dalam komponen kita.

Dalam useFrame kita dapat memperbarui uniforms:

// ...
import { useFrame } from "@react-three/fiber";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  // ...

  useFrame(() => {
    uniforms.colorA.value.set(colorA);
    uniforms.colorB.value.set(colorB);
  });

  return <meshStandardNodeMaterial {...nodes} />;
};

Gunakan metode value.set ketika Anda memperbarui uniform objek. Misalnya, color atau vec3 uniforms. Untuk float uniforms, Anda perlu mengatur nilai secara langsung: uniforms.opacity.value = opacity;

Warna sekarang diperbarui dengan benar secara real-time.

Sebelum melakukan lebih banyak hal pada warna, mari kita lihat bagaimana kita dapat mempengaruhi posisi vertices dari plane kita menggunakan positionNode.

Mengatur Node Posisi

Node positionNode memungkinkan kita untuk mempengaruhi posisi dari vertices geometri kita.

Three.js logoReact logo

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
Unlock the Full Course – Just $85

One-time payment. Lifetime updates included.