Hàm tạo hình
Logic của shader rất đặc biệt, nó khác so với những gì chúng ta quen thuộc trong JavaScript hoặc các ngôn ngữ lập trình khác. Mặc dù cú pháp khá giống nhau, nhưng cách ta triển khai logic thì khác.
Nó giống một hộp đen, bạn đưa vào một số dữ liệu và nhận được một số màu sắc cho fragment shader, hoặc một số vị trí cho vertex shader.
Nhưng làm thế nào để bạn kiểm soát đầu ra? Làm thế nào để bạn làm cho nó trông như bạn muốn? Đây là lúc các hàm tạo hình ra đời.
Chúng ta sẽ tập trung vào fragment shader cho bài học này, nhưng các nguyên tắc tương tự cũng áp dụng cho vertex shader.
Khu vực thực hành
Để thành thạo nghệ thuật tạo hình, bạn cần phải thực hành và thử nghiệm. Sẽ mất thời gian để quen với cách hoạt động của shader, nhưng khi bạn nắm vững, bạn sẽ có thể tạo ra những hiệu ứng tuyệt vời.
Vì tôi muốn bạn có được vị trí tốt nhất để thành công, tôi đã chuẩn bị cảnh Phòng Trưng Bày Nghệ Thuật 3D này để bạn có thể nhìn thấy shader của mình trong thời gian thực trong một môi trường dễ chịu.
Bạn có cảm nhận được sự sáng tạo đang tuôn trào không? Tất cả những khung trống đó đang chờ đợi kiệt tác của bạn!
Mô hình 3D được sử dụng là VR Gallery của Maxim Mavrichev và được cấp phép theo Creative Commons Attribution.
Tôi đã sử dụng <CameraControls />
để tạo ra camera góc nhìn thứ nhất để điều hướng bên trong phòng trưng bày và Squoosh để giảm kích thước của các texture.
SimpleShaderMaterial
Tất cả các khung từ phòng trưng bày đều là bản sao của SimpleShaderMaterial
, đó là một shader tùy chỉnh cơ bản mà tôi đã chuẩn bị cho bạn với:
uColor
: một màu sắcuTime
: thời gian đã trôi qua kể từ khi bắt đầu ứng dụngvUv
: tọa độ UV của đoạn (từ 0 đến 1 trên cả hai trục)
Tất cả đều được mở rộng trong <App.jsx />
để có thể sử dụng chúng theo cách khai báo trong cảnh của chúng ta.
Chúng được đặt tên dựa trên vị trí của chúng trong phòng trưng bày, nhưng hãy thoải mái đổi tên chúng thành một cái gì đó có ý nghĩa hơn một khi bạn đã tạo ra những kiệt tác với chúng!
Hàm
Đối với mỗi đoạn nhỏ của shader của chúng ta, cùng một đoạn mã sẽ được thực thi với các đầu vào khác nhau.
Nếu chúng ta muốn vẽ một đường thẳng, chúng ta có thể sử dụng các câu lệnh if để kiểm tra xem pixel có nằm trong đường thẳng hay không. Nhưng việc làm các hình dạng phức tạp hơn sẽ rất khó và kém hiệu quả.
Thay vào đó, chúng ta sử dụng hàm để định hình đầu ra của chúng ta. Đừng lo lắng, bạn không cần phải là một chuyên gia toán học để sử dụng chúng. Bạn chỉ cần hiểu các hàm nào có sẵn và chúng thực hiện những gì.
Hãy nghĩ về một hàm như một cỗ máy nhận một số đầu vào và cung cấp cho bạn một số đầu ra. Ví dụ, hàm
f(x) = x * 2
nhận một sốx
và cho bạn kết quả làx * 2
.
Để hình dung hiệu quả của các hàm khác nhau, chúng ta sử dụng Graphtoy, đây là công cụ đơn giản để nhập các hàm và xem đầu ra của chúng. Nó giúp xác nhận sự hiểu biết của chúng ta về hàm khi thử nghiệm trong shader.
Hãy hình dung hàm f(x) = x * 2
của chúng ta trong Graphtoy:
Graphtoy sẽ nhanh chóng trở thành người bạn tốt nhất của bạn khi bạn thử nghiệm với shader.
Đã đến lúc thử nghiệm với các hàm khác nhau mà chúng ta có.
Hàm Step
Hàm step là một hàm đơn giản trả về 0
nếu đầu vào nhỏ hơn một ngưỡng và 1
nếu đầu vào lớn hơn ngưỡng.
Nó nhận hai tham số:
edge
: ngưỡngx
: giá trị đầu vào
Hãy thử nó trên đoạn shader ArtFront02Material.jsx
:
void main() { float pct = 1.0; pct = step(0.5, vUv.x); vec3 finalColor = pct * uColor; gl_FragColor = vec4(finalColor, 1.0); }
Chúng ta khai báo một biến pct
để xác định tỷ lệ phần trăm của màu sắc mà chúng ta muốn hiển thị. Chúng ta thiết lập nó mặc định là 1.0
, sau đó sử dụng hàm step
để đặt pct
thành 0.0
nếu vUv.x
nhỏ hơn 0.5
.
Chúng ta có thể thấy nửa bên trái của frame là màu đen, và nửa bên phải là màu được đặt trong uColor
uniform.
Hãy áp dụng nó cho trục dọc với một ngưỡng khác:
void main() { float pct = 1.0; pct = step(0.2, vUv.y); vec3 finalColor = pct * uColor; gl_FragColor = vec4(finalColor, 1.0); }
Chúng ta có thể thấy 20% phía dưới của frame là màu đen, và phần còn lại là màu tím.
Tọa độ UV có nguồn gốc từ góc dưới bên trái của frame, vì vậy
[0, 0]
là góc dưới bên trái, và[1, 1]
là góc trên bên phải.
Nếu bạn muốn đảo ngược hiệu ứng, bạn có thể đơn giản hoán đổi các tham số của hàm step
:
void main() { float pct = 1.0; pct = step(vUv.y, 0.2); vec3 finalColor = pct * uColor; gl_FragColor = vec4(finalColor, 1.0); }
Chúng ta bây giờ đã có hiệu ứng ngược lại.
Mix
Hàm mix function là một hàm đơn giản trả về một nội suy tuyến tính giữa hai giá trị.
Nó nhận ba tham số:
x
: giá trị thứ nhấty
: giá trị thứ haia
: giá trị để nội suy giữax
vày
Hãy thử nó:
void main() { float pct = 1.0; pct = mix(0.0, 1.0, vUv.x); vec3 finalColor = pct * uColor; gl_FragColor = vec4(finalColor, 1.0); }
Chúng ta có thể thấy một gradient từ đen đến tím.
Giống như nhiều hàm khác, bạn có thể sử dụng nó trên các loại dữ liệu khác, chẳng hạn như các thành phần vector. Hãy sử dụng nó để tạo một gradient từ trắng đến tím trên trục dọc:
void main() { vec3 whiteColor = vec3(1.0); vec3 finalColor = mix(whiteColor, uColor, vUv.y); gl_FragColor = vec4(finalColor, 1.0); }
Chúng ta có thể thấy một gradient từ trắng đến tím.
Hãy thay đổi giá trị nội suy bằng cách gọi hàm mix
lần đầu để lấy giá trị pct
, và sau đó sử dụng nó để nội suy giữa whiteColor
và uColor
:
void main() { vec3 whiteColor = vec3(1.0); float pct = mix(0.0, 0.3, vUv.y); vec3 finalColor = mix(whiteColor, uColor, pct); gl_FragColor = vec4(finalColor, 1.0); }
Màu tím rất nhạt vì giá trị lớn nhất của pct
là 0.3
.
Sử dụng Graphtoy để thử nghiệm với hàm mix
và hiểu cách nó hoạt động.
3 nội suy khác nhau với hàm mix
cho ra kết quả hoàn toàn khác nhau.
Smoothstep
Hàm smoothstep là một hàm đơn giản trả về một phép nội suy mượt mà giữa hai giá trị.
Nó nhận ba tham số:
edge0
: ranh giới dướiedge1
: ranh giới trênx
: giá trị để nội suy giữaedge0
vàedge1
Nó cho ba kết quả khác nhau:
0.0
nếux
nhỏ hơnedge0
1.0
nếux
lớn hơnedge1
- một phép nội suy mượt mà giữa
0.0
và1.0
nếux
nằm giữaedge0
vàedge1
Hãy sử dụng nó để thay đổi giá trị pct
của chúng ta:
void main() { vec3 whiteColor = vec3(1.0); float pct = smoothstep(0.4, 0.6, vUv.y); vec3 finalColor = mix(whiteColor, uColor, pct); gl_FragColor = vec4(finalColor, 1.0); }
Chuyển tiếp giờ chỉ diễn ra giữa 0.4
và 0.6
. Mọi thứ bên dưới là màu trắng, và mọi thứ bên trên là màu tím.
Min & Max
min
và max
là những hàm đơn giản trả về giá trị nhỏ nhất hoặc lớn nhất giữa hai đầu vào. Chúng hoạt động giống hệt như các hàm Math.min
và Math.max
trong JavaScript.
Hãy sử dụng chúng để tinh chỉnh giá trị pct
của chúng ta. Trước tiên, hãy đảm bảo giá trị này không bao giờ dưới 0.4
(nghĩa là không bao giờ hoàn toàn trắng):
void main() { vec3 whiteColor = vec3(1.0); float pct = smoothstep(0.4, 0.6, vUv.y); pct = max(pct, 0.4); vec3 finalColor = mix(whiteColor, uColor, pct); gl_FragColor = vec4(finalColor, 1.0); }
Và không bao giờ vượt quá 0.6
(nghĩa là không bao giờ hoàn toàn tím):
void main() { vec3 whiteColor = vec3(1.0); float pct = smoothstep(0.4, 0.6, vUv.y); pct = max(pct, 0.4); pct = min(pct, 0.6); vec3 finalColor = mix(whiteColor, uColor, pct); gl_FragColor = vec4(finalColor, 1.0); }
Màu sắc bị nhạt đi, và chuyển đổi rất mượt mà.
Hãy hình dung nó trên Graphtoy:
Đại diện max(x, 0.4)
Đại diện min(x, 0.6)
Và nếu chúng ta kết hợp chúng:
Bạn có thể thấy các giá trị của chúng ta không bao giờ xuống dưới 0.4
và không bao giờ vượt quá 0.6
.
Sự kết hợp giữa các hàm
min
vàmax
của chúng ta có thể được thay thế bằng hàm clamp, là cách viết tắt chomin(max(x, min), max)
.
Các phép toán & mẫu
Trước khi chúng ta khám phá nhiều hàm hữu dụng khác, hãy xem cách chúng ta có thể sử dụng các phép toán để tạo ra các mẫu.
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.