The Book of Shaders by Patricio Gonzalez Vivo & Jen Lowe

Bahasa Indonesia - Tiếng Việt - 日本語 - 中文版 - 한국어 - Español - Portugues - Français - Italiano - Deutsch - Русский - Polski - English


Uniform

Tới giờ ta đã biết cách mà GPU xử lý 1 số lượng lớn các thread song song, mỗi cái chịu trách nhiệm đổi màu cho 1 vùng trong cả màn hình. Dù mỗi thread không biết các thread khác đang làm gì, ta vẫn phải gửi dữ liệu từ CPU tới cho từng thread. Kiến trúc của GPU yêu cầu dữ liệu gửi cho các thread phải giống nhau (uniform) và không được thay đổi (read only).

Các dữ liệu đầu vào này được gọi là uniform và hỗ trợ hầu hết các kiểu dữ liệu cơ bản như: float, vec2, vec3, vec4, mat2, mat3, mat4, sampler2DsamplerCube. Uniform được định nghĩa cùng với kiểu dữ liệu tương ứng, ở phần trên cùng của code shader, ngay sau khi quy định độ chính xác của các số thực.

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;  // Kích thước canvas (Rộng, cao)
uniform vec2 u_mouse;       // Vị trí con trỏ chuột trong canvas
uniform float u_time;       // Thời gian hiện tại tính từ lúc load xong shader

Bạn có thể hình dung uniform giống như các cầu nối giữa CPU và GPU. Tên của các biến có thể thay đổi theo từng chương trình nhưng trong quyển sách này tôi sẽ luôn dùng: u_time (thời gian hiện tại tính từ lúc load xong shader), u_resolution (kích thước vùng mà shader sẽ vẽ) và u_mouse (vị trí con trỏ chuột trong vùng được vẽ). Tôi dùng quy tắc thêm tiền tố u_ vào trước các tên biến để đánh dấu các uniform. Ví dụ code trên ShaderToy.com cũng sử dụng quy tắc riêng:

uniform vec3 iResolution;   // Kích thước canvas
uniform vec4 iMouse;        // Vị trí con trỏ chuột trong canvas (xy=vị trí hiện tại, zw=vị trí click)
uniform float iTime;        // Thời gian hiện tại tính từ lúc load xong shader

Hãy xem thực tế uniform làm việc như thế nào. Ở đoạn code dưới đây tôi dùng u_time - thời gian hiện tại tính bằng giây, kể từ lúc load xong shader - với hàm sine để kiểm soát sắc đỏ trong canvas theo thời gian.

GLSL có nhiều điều thú vị. Phần cứng của GPU giúp tăng tốc các hàm lượng giác và luỹ thừa: sin(), cos(), tan(), asin(), acos(), atan(), pow(), exp(), log(), sqrt(), abs(), sign(), floor(), ceil(), fract(), mod(), min(), max()clamp(). Chúng rất nhanh.

Cùng sửa đoạn code trên nào.

gl_FragCoord

Tương tự như biến lưu trữ giá trị output trong GLSL, vec4 gl_FragColor, ta cũng có biến lưu trữ giá trị input, vec4 gl_FragCoord, là toạ độ của điểm ảnh (pixel) hoặc một vùng điểm ảnh (screen fragment) mà thread này đang xử lý. Ta biết rằng giá trị của vec4 gl_FragCoord khác nhau giữa từng thread, nên nó không phải là uniform.

Ở đoạn code trên ta chuẩn hoá (normalize) toạ độ của từng fragment bằng cách chia nó cho kích thước của canvas. Bằng cách này, giá trị nhận được sẽ luôn nằm trong khoảng từ 0.0 tới 1.0, và sẽ khiến việc ánh xạ sang sắc độ RED và GREEN dễ hơn.

Trong shader ta không có nhiều cách để debug lắm bên cạnh việc thử dùng một màu rất chói để kiểm tra. Code shader thi thoảng cũng giống như dựng một chiếc thuyền bên trong một cái chai, rất khó nhưng đẹp và khiến ta thoả mãn.

Giờ là lúc để thử xem ta hiểu code tới đâu.

Sau khi làm các bài tập này, bạn có thể sẽ thắc mắc mình có thể sử dụng năng-lực-shader mới này của mình ở đâu nữa. Ở chương tới ta sẽ xem làm thế nào để tạo shader bằng three.js, Processing, và openFrameworks.