WebGPU / TSL
WebGPU là một tiêu chuẩn web mới cung cấp một API cấp thấp để vẽ đồ họa và thực hiện tính toán trên GPU. Nó được thiết kế để kế thừa WebGL, với hiệu suất tốt hơn và các tính năng nâng cao hơn.
Tin vui là bây giờ bạn có thể sử dụng nó với Three.js mà chỉ cần thay đổi nhỏ trong mã nguồn.
Trong bài học này, chúng ta sẽ khám phá cách sử dụng WebGPU với Three.js và React Three Fiber, và cách viết shaders bằng ngôn ngữ đổ bóng mới Three Shading Language (TSL).
Nếu bạn mới học về shaders, tôi khuyên bạn nên hoàn thành chương Shaders trước khi tiếp tục với chương này.
WebGPU Renderer
Để sử dụng WebGPU API thay vì WebGL, chúng ta cần sử dụng WebGPURenderer
(Không có mục riêng trong tài liệu Three.js) thay vì WebGLRenderer.
Với React Three Fiber, khi tạo một thành phần <Canvas>
, việc cài đặt renderer được thực hiện tự động. Tuy nhiên, chúng ta có thể ghi đè renderer mặc định bằng cách truyền một hàm vào thuộc tính gl
của thành phần <Canvas>
.
Trong App.jsx
, chúng ta có một thành phần <Canvas>
sử dụng WebGLRenderer
mặc định. Hãy sửa đổi nó để sử dụng WebGPURenderer
.
Đầu tiên, chúng ta cần dừng frameloop
cho đến khi WebGPURenderer
sẵn sàng. Chúng ta có thể thực hiện điều này bằng cách đặt thuộc tính frameloop
thành never
.
// ... import { useState } from "react"; function App() { const [frameloop, setFrameloop] = useState("never"); return ( <> {/* ... */} <Canvas // ... frameloop={frameloop} > {/* ... */} </Canvas> </> ); } export default App;
Tiếp theo, chúng ta cần import phiên bản WebGPU của Three.js:
import * as THREE from "three/webgpu";
Khi sử dụng WebGPU, chúng ta cần sử dụng module
three/webgpu
thay vì module mặc địnhthree
. Điều này là vìWebGPURenderer
không được bao gồm trong bản xây dựng mặc định của Three.js.
Sau đó, chúng ta có thể sử dụng thuộc tính gl
để tạo một đối tượng WebGPURenderer
mới:
// ... 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> </> ); } // ...
Chúng ta tạo một đối tượng WebGPURenderer
mới và truyền phần tử canvas
vào đó. Chúng ta cũng đặt một số tùy chọn cho renderer, như powerPreference
, antialias
, alpha
, stencil
và shadowMap
. Các tùy chọn này tương tự như những tùy chọn được sử dụng trong WebGLRenderer
.
Cuối cùng, chúng ta gọi phương thức init()
của renderer để khởi tạo nó. Khi quá trình khởi tạo hoàn tất, chúng ta đặt trạng thái frameloop
thành "always"
để bắt đầu render.
Hãy xem kết quả trong trình duyệt:
Khối lập phương của chúng ta bây giờ đã được render bằng WebGPURenderer
thay vì WebGLRenderer
.
Thật đơn giản phải không! Chúng ta đã thiết lập thành công WebGPURenderer
trong ứng dụng React Three Fiber của mình. Bạn có thể sử dụng cùng một API của Three.js để tạo và thao tác các đối tượng 3D, giống như bạn đã làm với WebGLRenderer
.
Những thay đổi lớn nhất là khi viết shaders. API WebGPU sử dụng ngôn ngữ đổ bóng khác với WebGL, điều này có nghĩa là chúng ta cần viết shaders của mình theo cách khác. Trong WGSL thay vì GLSL.
Đây là lúc Three Shading Language (TSL) xuất hiện.
Ngôn Ngữ Tô Bóng Three
TSL là một ngôn ngữ tô bóng mới được thiết kế để sử dụng với Three.js để viết shader theo cách thân thiện với người dùng hơn thông qua cách tiếp cận dựa trên node.
Một lợi thế lớn của TSL là nó không phụ thuộc vào renderer, nghĩa là bạn có thể sử dụng cùng các shader với các renderer khác nhau, như WebGL và WebGPU.
Điều này giúp việc viết và duy trì shader dễ dàng hơn, vì bạn không cần lo lắng về sự khác biệt giữa hai ngôn ngữ tô bóng.
Nó cũng bảo đảm cho tương lai, vì nếu một renderer mới được công bố, chúng ta có thể sử dụng cùng shader mà không cần thay đổi gì miễn là TSL hỗ trợ nó.
Ngôn Ngữ Tô Bóng Three vẫn đang trong quá trình phát triển, nhưng đã có sẵn trong các phiên bản mới nhất của Three.js. Cách tốt nhất để học và theo dõi các thay đổi là xem Trang wiki Ngôn Ngữ Tô Bóng Three. Tôi đã sử dụng nó nhiều để học cách sử dụng.
Vật liệu dựa trên node
Để hiểu cách tạo shader với TSL, chúng ta cần hiểu ý nghĩa của việc dựa trên node.
Trong cách tiếp cận dựa trên node, chúng ta tạo shader bằng cách kết nối các node khác nhau để tạo một biểu đồ. Mỗi node đại diện cho một hoạt động hoặc chức năng cụ thể, và các kết nối giữa các node đại diện cho luồng dữ liệu.
Cách tiếp cận này có nhiều lợi ích, như:
- Biểu diễn trực quan: Dễ hiểu và hình dung luồng dữ liệu và hoạt động trong một shader.
- Tái sử dụng: Có thể tạo ra các node tái sử dụng có thể dùng trong các shader khác nhau.
- Linh hoạt: Có thể dễ dàng sửa đổi và thay đổi hành vi của một shader bằng cách thêm hoặc xóa các node.
- Mở rộng: Thêm/Tùy chỉnh các tính năng từ các vật liệu có sẵn trở nên dễ dàng.
- Agnostic: TSL sẽ tạo mã phù hợp cho renderer mục tiêu, dù là WebGL (GLSL) hay WebGPU (WGSL).
Trước khi bắt đầu mã hóa vật liệu dựa trên node đầu tiên, chúng ta có thể sử dụng playground của Three.js trực tuyến để thử nghiệm với hệ thống node một cách trực quan.
Mở playground của Three.js và phía trên, nhấp vào nút Examples, và chọn basic > fresnel
.
Bạn sẽ thấy một trình chỉnh sửa vật liệu dựa trên node với hai color
node và một float
node kết nối với một fresnel
node. (‘Color A’, ‘Color B’, và “Fresnel Factor”)
Node fresnel
được kết nối với màu sắc của Basic Material
, tạo ra hiệu ứng fresnel cho ấm trà.
Sử dụng nút Splitscreen
để xem trước kết quả bên phải.
Giả sử chúng ta muốn ảnh hưởng đến độ mờ của Basic Material
dựa trên thời gian. Chúng ta có thể thêm một Timer
node và kết nối nó với một Fract
node để đặt lại thời gian về 0 khi đạt đến 1. Sau đó, kết nối đến đầu vào opacity
của Basic Material
.
Ấm trà của chúng ta giờ đây mờ dần trước khi biến mất và lại hiện lên.
Dành thời gian để chơi đùa với các node khác nhau và xem cách chúng ảnh hưởng đến vật liệu.
Bây giờ chúng ta đã có một hiểu biết cơ bản về cách vật liệu dựa trên node hoạt động, hãy xem cách sử dụng vật liệu dựa trên node mới từ Three.js trong React Three Fiber.
Triển khai React Three Fiber
Cho đến nay, với WebGL, chúng ta đã sử dụng MeshBasicMaterial, MeshStandardMaterial, hoặc thậm chí là ShaderMaterial tùy chỉnh để tạo material của chúng ta.
Khi sử dụng WebGPU, chúng ta cần sử dụng material mới tương thích với TSL. Tên của chúng giống như những cái chúng ta đã sử dụng trước đây với Node
trước Material
:
MeshBasicMaterial
->MeshBasicNodeMaterial
MeshStandardMaterial
->MeshStandardNodeMaterial
MeshPhysicalMaterial
->MeshPhysicalNodeMaterial
- ...
Để sử dụng chúng theo cách khai báo với React Three Fiber, chúng ta cần extend
chúng. Trong App.jsx
:
// ... import { extend } from "@react-three/fiber"; extend({ MeshBasicNodeMaterial: THREE.MeshBasicNodeMaterial, MeshStandardNodeMaterial: THREE.MeshStandardNodeMaterial, }); // ...
Trong các phiên bản tương lai của React Three Fiber, điều này có thể được thực hiện tự động.
Bây giờ chúng ta có thể sử dụng MeshBasicNodeMaterial
và MeshStandardNodeMaterial
mới trong các component của mình.
Hãy thay thế MeshStandardMaterial
từ khối lập phương trong component Experience
của chúng ta bằng MeshStandardNodeMaterial
:
<mesh> <boxGeometry args={[1, 1, 1]} /> <meshStandardNodeMaterial color="pink" /> </mesh>
Chúng ta có thể sử dụng MeshStandardNodeMaterial
giống như cách chúng ta sử dụng MeshStandardMaterial
.
Khối lập phương của chúng ta hiện đang dựa vào MeshStandardNodeMaterial
thay vì MeshStandardMaterial
. Giờ đây chúng ta có thể sử dụng nodes để tùy chỉnh material.
Color Node
Hãy học cách tạo các node tùy chỉnh để cá nhân hóa materials của chúng ta với TSL.
Trước tiên, hãy tạo một component mới có tên PracticeNodeMaterial.jsx
trong thư mục src/components
.
export const PracticeNodeMaterial = ({ colorA = "white", colorB = "orange", }) => { return <meshStandardNodeMaterial color={colorA} />; };
Và trong Experience.jsx
, thay thế khối cube của chúng ta bằng một mặt phẳng sử dụng PracticeNodeMaterial
:
// ... import { PracticeNodeMaterial } from "./PracticeNodeMaterial"; export const Experience = () => { return ( <> {/* ... */} <mesh rotation-x={-Math.PI / 2}> <planeGeometry args={[2, 2, 200, 200]} /> <PracticeNodeMaterial /> </mesh> </> ); };
Chúng ta có một mặt phẳng với PracticeNodeMaterial
.
Để tùy chỉnh material của mình, chúng ta giờ có thể thay đổi các node khác nhau có sẵn bằng cách sử dụng các node khác nhau. Danh sách các node có sẵn có thể được tìm thấy ở trang wiki.
Hãy bắt đầu một cách đơn giản với node colorNode
để thay đổi màu sắc của material. Trong PracticeNodeMaterial.jsx
:
import { color } from "three/tsl"; export const PracticeNodeMaterial = ({ colorA = "white", colorB = "orange", }) => { return <meshStandardNodeMaterial colorNode={color(colorA)} />; };
Chúng ta thiết lập thuộc tính colorNode
sử dụng node color
từ module three/tsl
. Node color
nhận vào một màu làm tham số và trả về một node màu có thể sử dụng trong material.
Điều này cho kết quả tương tự như trước, nhưng giờ chúng ta có thể thêm nhiều node hơn để tùy chỉnh material của mình.
Hãy import các node mix
và uv
từ module three/tsl
và sử dụng chúng để trộn hai màu dựa trên tọa độ UV của mặt phẳng.
import { color, mix, uv } from "three/tsl"; export const PracticeNodeMaterial = ({ colorA = "white", colorB = "orange", }) => { return ( <meshStandardNodeMaterial colorNode={mix(color(colorA), color(colorB), uv())} /> ); };
Nó sẽ thực thi các node khác nhau để lấy màu cuối cùng của material. Node mix
nhận vào hai màu và một yếu tố (trong trường hợp này là tọa độ UV) và trả về một màu là sự trộn lẫn của hai màu dựa trên yếu tố.
Nó hoàn toàn giống như sử dụng hàm mix
trong GLSL, nhưng giờ đây chúng ta có thể sử dụng nó dưới dạng node-based. (Đọc dễ hiểu hơn nhiều!)
Chúng ta giờ có thể thấy hai màu được trộn lẫn dựa trên tọa độ UV của mặt phẳng.
Điều tuyệt vời là chúng ta không bắt đầu từ con số không. Chúng ta đang sử dụng MeshStandardNodeMaterial
hiện có và chỉ thêm các node tùy chỉnh của chúng ta vào. Điều này có nghĩa là các bóng, ánh sáng và tất cả các tính năng khác của MeshStandardNodeMaterial
vẫn có sẵn.
Khai báo inline các node là ổn cho logic node rất đơn giản, nhưng đối với logic phức tạp hơn, tôi khuyên bạn nên khai báo các node (và sau đó các uniforms, và nhiều hơn thế) trong 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} />; };
Điều này thực hiện chính xác những gì trước đó, nhưng giờ chúng ta có thể thêm nhiều node hơn vào đối tượng nodes
và truyền chúng tới meshStandardNodeMaterial
theo cách tổ chức/chung hơn.
Bằng cách thay đổi các thuộc tính colorA
và colorB
, nó sẽ không gây ra việc biên dịch lại shader nhờ vào hook useMemo
.
Hãy thêm controls để thay đổi màu sắc của material. Trong 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> </> ); };
Màu sắc mặc định hoạt động chính xác, nhưng cập nhật màu không có hiệu ứng.
Đó là bởi vì chúng ta cần truyền màu sắc dưới dạng uniforms
cho meshStandardNodeMaterial
.
Uniforms
Để khai báo uniforms trong TSL, chúng ta có thể sử dụng uniform
node từ module three/tsl
. Node uniform
nhận một giá trị làm tham số (nó có thể là các kiểu khác nhau như float
, vec3
, vec4
, v.v.) và trả về một uniform node có thể được sử dụng trong các nodes khác nhau trong khi được cập nhật từ mã thành phần của chúng ta.
Hãy chuyển các màu được mã hóa cứng sang uniforms trong 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} />; };
Chúng ta khai báo một đối tượng uniforms
để tổ chức mã tốt hơn và chúng ta sử dụng các giá trị uniform thay vì giá trị mặc định chúng ta có khi tạo các nodes của mình.
Bằng cách trả về chúng trong useMemo
, chúng ta bây giờ có quyền truy cập vào uniforms trong thành phần của mình.
Trong một useFrame
, chúng ta có thể cập nhật các 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} />; };
Sử dụng phương thức
value.set
khi bạn cập nhật một object uniform. Ví dụ, uniformscolor
hoặcvec3
. Đối với uniformsfloat
, bạn cần đặt giá trị trực tiếp:uniforms.opacity.value = opacity;
Màu sắc giờ đây đang được cập nhật chính xác theo thời gian thực.
Trước khi làm thêm với màu sắc, hãy xem cách chúng ta có thể ảnh hưởng đến vị trí của các điểm vertex của mặt phẳng của chúng ta sử dụng positionNode
.
Điều Chỉnh Node Vị Trí
Node positionNode
cho phép chúng ta thay đổi vị trí của các đỉnh trong hình học của mình.
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.