If you're seeing this message, it means we're having trouble loading external resources on our website.

Nếu bạn đang sử dụng bộ lọc web, vui lòng kiểm tra lại xem bộ lọc có chặn hai tên miền *.kastatic.org*.kasandbox.org hay không.

Nội dung chính

Thuật toán sinh nhiễu Perlin

Một trình tạo số ngẫu nhiên hiệu quả sẽ tạo ra các số không có mối tương quan hoặc quy luật rõ ràng. Như bạn có thể đã thấy, ứng dụng tính ngẫu nhiên trong lập trình có thể giúp ta tạo ra kết quả tự nhiên như ngoài đời thực. Tuy nhiên, nếu tính ngẫu nhiên đóng vai trò là nguyên tắc định hướng duy nhất thì kết quả có thể sẽ không "tự nhiên" như ta mong đợi.
Một thuật toán có thể cho ra kết quả tự nhiên hơn là thuật toán sinh nhiễu Perlin. Ken Perlin đã phát triển hàm sinh nhiễu khi thực hiện bộ phim Tron vào đầu thập niên 1980 và sử dụng hàm sinh nhiễu để tạo kết cấu theo quy trình cho các hiệu ứng do máy tính tạo ra. Năm 1997, Perlin đã được trao giải Oscar về thành tựu kỹ thuật với công trình này. Thuật toán sinh nhiễu Perlin có thể được ứng dụng để tạo ra nhiều hiệu ứng khác nhau với khả năng mô phỏng tự nhiên tốt như đám mây, phong cảnh và họa tiết có hoa văn như trên đá cẩm thạch.
Thuật toán sinh nhiễu Perlin đem đến hiệu ứng tự nhiên hơn vì thuật toán này tạo ra một chuỗi các số giả ngẫu nhiên có trình tự tự nhiên. Biểu đồ dưới đây biểu diễn kết quả của thuật toán sinh nhiễu Perlin theo thời gian, với trục x biểu diễn thời gian. Hãy lưu ý đến độ mịn của đường cong:
Hình I.5: Nhiễu
Ngược lại, biểu đồ tiếp theo bên dưới biểu diễn phân phối các số ngẫu nhiên thuần túy theo thời gian:
Hình I.6: Ngẫu nhiên
ProcessingJS có tích hợp sẵn thuật toán sinh nhiễu Perlin thông qua hàm noise(). Hàm noise() nhận một, hai hoặc ba đối số vì độ nhiễu được tính toán theo một, hai hoặc ba chiều. Hãy cùng bắt đầu với hiệu ứng nhiễu một chiều.

Hiệu ứng nhiễu

Hàm tạo nhiễu tính toán hiệu ứng nhiễu trên một vài "môi trường tính toán tự do". Bên cạnh đó, hàm noiseDetail() được sử dụng để thay đổi cả số môi trường tính toán tự do và tầm quan trọng của chúng với nhau, từ đó lần lượt thay đổi cách hoạt động của hàm nhiễu.
Ta sẽ cùng xem xét việc vẽ hình tròn trong khung nhập lệnh ProcessingJS với hoành độ x ngẫu nhiên:
var x = random(0, width);
ellipse(x, 180, 16, 16);
Bây giờ, thay vì lấy hoành độ x một cách ngẫu nhiên, chúng ta cần hoành độ x được tạo bởi thuật toán nhiễu Perlin. Có thể bạn nghĩ rằng chúng ta chỉ cần thay hàm random() bằng hàm noise():
var x = noise(0, width);
ellipse(x, 180, 16, 16);
Về mặt lý thuyết, chúng ta cần thuật toán nhiễu Perlin tính giá trị x trong khoảng từ 0 đến chiều rộng khung kết quả (width), tuy nhiên đây không phải là cách triển khai đúng. Các đối số của hàm random() xác định giá trị nhỏ nhất và giá trị lớn nhất của khoảng chọn giá trị ngẫu nhiên nhưng hàm noise() không hoạt động như vậy. Thay vào đó, hàm noise() yêu cầu truyền vào một đối số xác định "thời điểm" và luôn trả về một giá trị trong khoảng từ 0 đến 1. Chúng ta có thể coi kết quả của thuật toán nhiễu Perlin một chiều như một chuỗi tuyến tính bao gồm các giá trị tương ứng theo thời gian. Ví dụ, sau đây là các giá trị đầu vào ví dụ và giá trị trả về:
Thời gianGiá trị nhiễu
00,469
0,010,480
0,020,492
0,030,505
0,040,517
Trong ProcessingJS, để truy cập một trong những giá trị nhiễu, ta phải truyền thời điểm cụ thể vào hàm noise(). Ví dụ:
var n = noise(0.03);
Theo bảng trên, hàm noise() sẽ trả về 0,505 tại thời điểm 0,03. Chúng ta có thể viết một chương trình có biến lưu trữ thời gian và yêu cầu giá trị nhiễu liên tục bằng hàm draw():
Đoạn mã trên cho ra cùng một giá trị lặp đi lặp lại. Lí do là bởi chúng ta đang yêu cầu lặp đi lặp lại kết quả của hàm noise() tại cùng một thời điểm.
Tuy nhiên, nếu chúng ta tăng biến thời gian t, chúng ta sẽ nhận được kết quả khác:
Tốc độ tăng biến t cũng ảnh hướng đến độ mịn của kết quả thuật toán sinh nhiễu. Nếu chúng ta thực hiện những bước nhảy lớn ở thời điểm phù hợp, các giá trị sẽ giống như được tạo một cách ngẫu nhiên hơn.
Hình I.7
Hãy thử chạy đoạn mã trên nhiều lần, tăng t lên 0,01; 0,02; 0,05; 0,1; 0,0001 và bạn sẽ thấy các kết quả khác nhau.

Ánh xạ giá trị nhiễu

Vấn đề tiếp theo của chúng ta là phải làm gì với giá trị nhiễu. Sau khi có được giá trị trong khoảng từ 0 đến 1, chúng ta có thể ánh xạ giá trị đó tới bất kỳ đâu chúng ta muốn.
Chúng ta có thể chỉ ánh xạ đơn giản bằng cách nhân với số lớn nhất trên khoảng, hoặc phức tạp hơn bằng cách sử dụng hàm map() của ProcessingJS. Hàm map() nhận năm đối số. Đối số đầu tiên là giá trị mà chúng ta muốn ánh xạ, trong trường hợp này là giá trị n. Các đối số tiếp theo lần lượt là giá trị lớn nhất và giá trị nhỏ nhất của khoảng hiện tại cũng như khoảng sẽ ánh xạ đến.
Hình I.8
Trong chương trình dưới đây, giá trị nhiễu nằm trong khoảng từ 0 đến 1 còn giá trị ánh xạ đến nằm trong khoảng từ 0 đến chiều rộng khung kết quả.
Chúng ta có thể áp dụng phương pháp tương tự để xây dựng lộ trình ngẫu nhiên bằng cách sử dụng giá trị tạo ra bởi thuật toán sinh nhiễu Perlin để làm tọa độ x và y của các bước trong lộ trình:
Ta có thể thấy chương trình trên có thêm hai biến: txty. Đó là bởi vì chúng ta cần hai biến theo dõi thời gian, một biến cho hoành độ x của đối tượng Walker và một biến cho tung độ y.
Bạn có thể tự hỏi, tại sao tx lại bắt đầu từ 0 và ty bắt đầu từ 10.000? Mặc dù những con số này là ngẫu nhiên nhưng vẫn được chọn sao cho đáp ứng điều kiện là hai giá trị khác nhau. Lí do là bởi hàm sinh nhiễu có tính xác định, tức là luôn cho cùng một kết quả khi thời điểm là t. Nếu ta yêu cầu giá trị nhiễu cho cả xy tại cùng một thời điểm t thì xy sẽ luôn bằng nhau, khi đó đối tượng Walker sẽ chỉ di chuyển theo đường chéo. Thay vào đó, ta sẽ sử dụng hai phần khác nhau của không gian nhiễu, x bắt đầu tại 0 và y bắt đầu tại 10.000 để xy có thể độc lập với nhau.
Hình I.9
Trên thực tế, đây không phải là khái niệm thời gian theo nghĩa đen mà là một phép ẩn dụ giúp ta hiểu được cách thức hoạt động của hàm sinh nhiễu. Những gì ta có là không gian chứ không phải thời gian. Biểu đồ trên biểu diễn một chuỗi giá trị nhiễu tuyến tính trong không gian một chiều và ta có thể yêu cầu giá trị có hoành độ x cụ thể. Trong các ví dụ, ta sẽ thường thấy biến được đặt tên là xoff để biểu thị độ lệch của giá trị x dọc theo đồ thị nhiễu thay vì t biểu thị thời gian.
Trong thử thách tiếp theo, chúng ta sẽ luyện tập cách ứng dụng thuật toán sinh nhiễu với đối tượng Walker theo một cách khác.
Khóa học "Mô phỏng tự nhiên" này được biên soạn dựa trên cuốn "The Nature of Code" (tạm dịch: Bản chất của lập trình) của tác giả Daniel Shiffman, được sử dụng theo giấy phép Creative Commons Attribution-NonCommercial 3.0 Unported License.

Tham gia cuộc thảo luận?

Chưa có bài đăng nào.
Bạn có hiểu Tiếng Anh không? Bấm vào đây để thấy thêm các thảo luận trên trang Khan Academy Tiếng Anh.