Translate chapters

pull/303/head
Hoang.Vu 4 years ago
parent bd498a53d9
commit b06bb02c34

@ -0,0 +1,50 @@
# Giới thiệu
<canvas id="custom" class="canvas" data-fragment-url="cmyk-halftone.frag" data-textures="vangogh.jpg" width="700px" height="320px"></canvas>
Hai bức ảnh trên đây được tạo ra bằng những cách khác nhau. Bức đầu tiên được vẽ bởi Van Gogh bằng nhiều lớp sơn chồng lên nhau. Nó đã ngốn của ông ấy hàng giờ đồng hồ. Bức thứ hai được tạo ra trong chớp mắt từ 4 ma trận điểm ảnh: một cho màu xanh (Cyan), một cho màu hồng (Magenta), một cho màu vàng (Yellow) và một cho màu đen (Black). Điểm khác biệt chính là: các lớp của bức tranh thứ hai được tạo ra đồng thời chứ không phải tuần tự.
Quyển sách này nói về một kỹ thuật mang tính cách mạng trong Đồ họa Máy tính, *fragment shader*, giúp nâng tầm đồ hoạ kỹ thuật số lên một tầm cao mới. Bạn có thể nghĩ sức ảnh hưởng của nó tương đương với cỗ máy in của Gutenberg khi xưa vậy.
![Cỗ máy in của Gutenberg](gutenpress.jpg)
Fragment shader cho bạn toàn quyền kiểm soát các điểm ảnh được render trên màn hình cực kỳ nhanh chóng. Đó là lí do tại sao nó được dùng trong mọi lĩnh vực của Đồ hoạ máy tính, từ các bộ lọc video trên điện thoại di động tới các trò chơi điện tử 3D đáng kinh ngạc.
![Game Journey của That Game Company](journey.jpg)
Ở các chương tiếp theo, bạn sẽ biết kỹ thuật này nhanh và mạnh kinh khủng tới mức nào, và làm thế nào để áp dụng nó vào công việc.
## Quyển sách này dành cho những ai ?
Quyển sách này được viết cho các kỹ sư và lập trình viên sáng tạo, những người đã có kinh nghiệm lập trình và kiến thức cơ bản về Giải tích và Lượng giác, và cho những ai muốn nâng tầm chất lượng đồ hoạ trong tác phẩm của mình. (Nếu bạn muốn học lập trình, tôi khuyên bạn nên bắt đầu từ trang web [Processing](https://processing.org/) rồi quay lại đây khi đã cảm thấy đủ kiến thức.
Quyển sách này sẽ chỉ cho bạn cách dùng các shader và cách áp dụng shader vào các dự án để cải thiện hiệu năng cũng như chất lượng đồ hoạ. Vì các shader viết bằng GLSL (viết tắt của OpenGL Shading Language) sẽ được biên dịch và chạy trên rất nhiều nền tảng khác nhau, bạn có thể ứng dụng những gì học được ở đây cho bất kỳ môi trường nào sử dụng OpenGL, OpenGL ES hoặc WebGL. Cụ thể, bạn có thể ứng dụng những kiến thức này cho các bản vẽ [Processing](https://processing.org/), các ứng dụng [openFrameworks](http://openframeworks.cc/), các cỗ máy [Cinder](http://libcinder.org/) tương tác được, các website sử dụng [Three.js](http://threejs.org/) hay các trò chơi trên iOS/Android.
## Nội dung của quyển sách này là gì ?
Quyển sách này sẽ tập trung vào việc sử dụng các pixel shader viết bằng GLSL. Đầu tiên ta sẽ định nghĩa thế nào là shader, rồi mới học cách tạo các hình, mẫu, ảnh bằng Toán học và làm chúng chuyển động bằng shader. Bạn sẽ được học các kiến thức nền tảng của shader và áp dụng chúng vào các chuyên ngành khác như: Xử lý ảnh
(Các phép toán xử lý ảnh, ma trận chập, làm mờ, bộ lọc, bảng quy chiếu và các hiệu ứng khác), mô phỏng (
Mô phỏng tiến trình sống theo phương pháp Conway, mô hình Phản ứng - Khuếch tán của Gray-Scott, các gợn sóng trên mặt nước, hiệu ứng màu nước, nhiễu Voronoi, vân vân ...). Tới cuối quyển sách, ta sẽ nói về các kỹ thuật cao cấp hơn dựa trên thuật toán Dò tia - Ray Marching.
*Tất cả các chương đều có các ví dụ tương tác được để bạn khám phá.* Khi bạn sửa code, bạn sẽ thấy thay đổi ngay lập tức. Các khái niệm có thể hơi trừu tượng và dễ gây nhầm lẫn, nên các ví dụ tương tác được sẽ rất cần thiết để hỗ trợ bạn học. Bạn nắm vững các khái niệm càng nhanh thì bạn học càng dễ.
Những nội dung nằm ngoài phạm vi của quyển sách:
* Đây *không phải* sách nói về OpenGL hay WebGL. OpenGL/WebGL là chủ đề rộng lớn hơn cả GLSL và fragment shader. Để tìm hiểu thêm về OpenGL/WebGL, tôi khuyên bạn nên đọc [Giới thiệu OpenGL](https://open.gl/introduction), [Hướng dẫn lập trình OpenGL - xuất bản lần 8](http://www.amazon.com/OpenGL-Programming-Guide-Official-Learning/dp/0321773039/ref=sr_1_1?s=books&ie=UTF8&qid=1424007417&sr=1-1&keywords=open+gl+programming+guide) (còn được biết tới với tên gọi là The Red Book) hoặc [WebGL: Up and Running](http://www.amazon.com/WebGL-Up-Running-Tony-Parisi/dp/144932357X/ref=sr_1_4?s=books&ie=UTF8&qid=1425147254&sr=1-4&keywords=webgl)
* Đây *không phải* sách Toán. Mặc dù ta có đề cập tới một vài thuật toán và kỹ thuật liên quan tới Giải tích và Lượng giác, nhưng chúng tôi sẽ không giải thích chúng một cách chi tiết. Để tiện giải đáp các thắc mắc về Toán, tôi khuyên bạn nên có một trong số các quyển sách sau: [Ứng dụng Toán trong lập trình và đồ hoạ máy tính - xuất bản lần 3](http://www.amazon.com/Mathematics-Programming-Computer-Graphics-Third/dp/1435458869/ref=sr_1_1?ie=UTF8&qid=1424007839&sr=8-1&keywords=mathematics+for+games) hoặc [Kiến thức Toán cần thiết cho lập trình Game và Ứng dụng real-time](http://www.amazon.com/Essential-Mathematics-Games-Interactive-Applications/dp/0123742978/ref=sr_1_1?ie=UTF8&qid=1424007889&sr=8-1&keywords=essentials+mathematics+for+developers).
## Bạn cần gì để bắt đầu ?
Chỉ vài thứ thôi! Nếu bạn có trình duyệt có thể xử lý WebGL (như Chrome, Firefox hoặc Safari) và kết nối Internet, click nút _"Next >>>"_ ở cuối trang này để bắt đầu ngay.
Hoặc, tuỳ theo bạn có gì và cần gì từ quyển sách, bạn có thể:
- [Tạo quyển sách phiên bản không cần Internet](https://thebookofshaders.com/appendix/00/?lan=vi)
- [Chạy các ví dụ trên Raspberry Pi mà không cần trình duyệt](https://thebookofshaders.com/appendix/01/?lan=vi)
- [Tạo phiên bản PDF của sách để in](https://thebookofshaders.com/appendix/02/?lan=vi)
- Xem [repository trên GitHub](https://github.com/patriciogonzalezvivo/thebookofshaders) của quyền sách này để giúp sửa và chia sẻ code.

@ -0,0 +1,49 @@
# Mở đầu
## Fragment shader là gì
Ở chương trước, tôi đã mô tả shader tương đương với cỗ máy in của Gutenberg nhưng cho ngành đồ hoạ. Tại sao ? Và quan trọng hơn: shader là gì ?
![Từ việc chép-từng-từ-một, Ảnh bên trái của William Blades (1891). Cho tới in-từng-trang-một, Ảnh bên phải của Rolt-Wheeler (1920).](print.png)
Nếu bạn đã từng vẽ trên máy tính, bạn biết rằng để có được hình mình muốn, bạn phải vẽ hình tròn, hình chữ nhật rồi vài đường thẳng, vài hình tam giác. Quá trình đó không khác gì việc viết từng chữ một - đó là một loạt các chỉ dẫn để máy tính thực hiện lần lượt.
Shader cũng là một tập hợp các chỉ dẫn, nhưng các chỉ dẫn đó sẽ được thực thi cùng lúc cho từng điểm ảnh trên màn hình. Điều đó có nghĩa là code bạn viết phải xử lý khác nhau tuỳ theo vị trí của điểm ảnh trên màn hình. Giống như máy rập chữ, chương trình của bạn sẽ hoạt động như một hàm nhận vị trí của điểm ảnh rồi trả về màu của điểm ảnh đó. Chương trình đó chạy rất rất nhanh.
![Các khuôn chữ Trung Quốc](typepress.jpg)
## Làm cách nào shader lại chạy nhanh tới vậy ?
Để trả lời câu hỏi này, tôi xin giới thiệu với bạn sự kỳ diệu của việc *xử lý song song*.
Hãy tưởng tượng CPU là một dây chuyền công nghiệp lớn, và mọi tác vụ đi qua dây chuyền là một khâu. Có vài khâu đồ sộ hơn các khâu khác, tức là chúng cần nhiều thời gian và năng lượng hơn để xử lý. Ta nói chúng cần nhiều năng lực xử lý hơn. Kiến trúc của máy tính khiến mỗi khâu phải thực hiện tuần tự; khâu này kết thúc rồi mới đến khâu tiếp theo. Máy tính hiện đại thường có tới 4 bộ xử lý tương tự như 4 dây chuyền sản xuất này, lần lượt thực thi từng tác vụ nhỏ. Mỗi dây chuyền nhỏ trong đó gọi là một *thread*.
![CPU](00.jpeg)
Các trò chơi điện tử và các ứng dụng đồ hoạ cần nhiều năng lực xử lý hơn hẳn các phần mềm khác. Vì các nội dung đồ hoạ của chúng yêu cầu thực hiện rất nhiều phép toán, cho từng điểm ảnh một. Mỗi điểm ảnh trên màn hình đều phải được tính đến, còn trong các trò chơi 3 chiều thì cả các vật thể lẫn các góc camera cũng phải được tính luôn.
Quay trở lại phép so sánh về dây chuyền và tác vụ. Mỗi điểm ảnh trên màn hình đại diện cho 1 tác vụ nhỏ. Bản thân từng tác vụ không phải là vấn đề lớn với CPU, nhưng (vấn đề ở đây là) các tác vụ bé xíu này lại phải được thực thi cho từng điểm ảnh trên cả màn hình. Có nghĩa là trên màn hình cũ có độ phân giải 800x600, có tới 480.000 điểm ảnh cần phải được xử lý mỗi khung hình, tương đương với 14.400.000 phép tính mỗi giây! Đúng thế! Đó chính là điều khiến bộ vi xử lý bị quá tải. Còn ở màn hình retina thời hiện đại có độ phân giải 2880x1800 hiển thị 60 khung hình một giây, thì số phép tính mỗi giây lên tới 311.040.000. Bằng cách nào mà các kỹ sư đồ hoạ giải quyết được vấn đề này ?
![](03.jpeg)
Đây là lúc mà xử lý song song trở thành một giải pháp tốt. Thay vì phải trang bị vài bộ vi xử lý to lớn và mạnh mẽ, hoặc *các dây chuyền*, sẽ là thông minh hơn nếu để cho hàng loạt các bộ vi xử lý tí hon chạy song song. Và đó chính là việc mà các bộ xử lý đồ hoạ (GPU) làm.
![GPU](04.jpeg)
Hình dung hàng loạt các bộ vi xử lý tí hon như các dây chuyền được xếp thành hàng hình chữ nhật, còn dữ liệu của mỗi điểm ảnh là một quả bóng bàn. Nhét 14.400.000 quả bóng bàn trong một giây vào một ống đơn lẻ sẽ rất khó. Nhưng một nhóm nhiều ống xếp thành hàng 800x600 thì có thể đưa 480.000 quả bóng bàn chui qua tới 30 lần một giây một cách dễ dàng. Độ phân giải cao hơn cũng tương tự - phần cứng càng có năng lực xử lý song song thì khối lượng công việc mà nó có thể giải quyết lại lớn hơn.
Một "siêu năng lực" khác của GPU là các hàm Toán học được tối ưu bằng phần cứng, nên các phép toán phức tạp sẽ được xử lý trực tiếp trên phần cứng thay vì phần mềm. Điều đó có nghĩa là các phép tính lượng giác và ma trận sẽ được tính cực kỳ nhanh - như điện luôn.
## GLSL là gì?
GLSL là viết tắt của OpenGL Shading Language, là một quy chuẩn để viết các shader mà ta sẽ dùng ở các chương tới. Có nhiều loại shader phụ thuộc vào phần cứng và hệ điều hành. Ở đây chúng ta sẽ sử dụng quy chuẩn của
[Khronos Group](https://www.khronos.org/opengl/). Hiểu về lịch sử hình thành của OpenGL sẽ giúp ích trong việc vượt qua được một số rào cản kỳ lạ của nó, vì thế tôi giới thiệu quyển sách này: [OpenGL là ](http://openglbook.com/chapter-0-preface-what-is-opengl.html)
## Shader nổi tiếng khó nhằn, sao lại thế ?
Như chú Ben nói "Quyền lực càng cao, trách nhiệm càng lớn", và việc tính toán song song cũng tuân thủ quy tắc này; thiết kế kiến trúc rất mạnh mẽ của GPU cũng đi kèm với các ràng buộc và giới hạn.
Để các dây chuyền, tức các thread, có thể chạy song song, thì chúng phải độc lập. Có thể nói rằng các thread bị **mù** khi không thể biết được các thread khác đang làm gì. Giới hạn này dẫn tới việc toàn bộ dữ liệu phải đi theo 1 chiều. Nên thread này không thể biết kết quả của thread kia hay thay đổi dữ liệu đầu vào hoặc lấy dữ liệu đầu ra của một thread nọ để chuyển cho một thread khác nữa.
Và GPU cũng luôn khiến cho các bộ vi xử lý (các dây chuyền) của mình phải bận rộn; cứ dây chuyền nào xong việc thì sẽ nhận được thông tin mới để xử lý tiếp. Nên mỗi thread còn chẳng biết nó vừa hoàn thành xong việc gì. Nó có thể vừa mới vẽ xong 1 nút bấm trên giao diện của hệ điều hành, rồi vẽ một phần bầu trời trong 1 trò chơi nào đó, sau đó lại phải hiển thị nội dung của 1 cái email. Mỗi thread không chỉ bị **mù** mà còn **mất trí nhớ** nữa. Bên cạnh việc viết code shader khá trừu tượng do phải viết một hàm dùng chung cho mọi điểm ảnh nhưng kết quả thì phụ thuộc vào vị trí của điểm ảnh đó, thì các ràng buộc về việc bị mù và mất trí nhớ ở trên cũng là nguyên do khiến cho shader không được biết đền nhiều bởi các lập trình viên mới vào nghề.
Nhưng đừng lo! Ở các chương tới, ta sẽ học từng bước một, từ đơn giản tới phức tạp. Nếu bạn đang đọc trên một trình duyệt đời mới, bạn có thể sẽ thích tương tác với các ví dụ. Đừng trì hoãn sự sung sướng nữa mà hãy click nút *Next >>* để nhảy thẳng vào code nào!

@ -0,0 +1,53 @@
## Hello World
Thông thường thì ví dụ "Hello World!" sẽ là bước đầu tiên khi học một ngôn ngữ lập trình mới. Đó là một chương trình đơn giản, in ra 1 dòng chữ chào đón.
Ở địa-bàn-của-GPU thì việc vẽ các ký tự chữ số là bước khởi đầu có phần quá phức tạp, thay vào đó chúng tôi chọn sẽ tô một màu tươi sáng lên màn hình để chào đón các bạn nồng nhiệt nhất!
<div class="codeAndCanvas" data="hello_world.frag"></div>
Nếu bạn đang đọc trên trình duyệt thì đoạn code phía trên có thể sửa được đấy. Điều đó có nghĩa là bạn có thể sửa bất kỳ phần nào bạn muốn. Thay đổi sẽ được cập nhật ngay lập tức nhờ vào kiến trúc GPU có khả năng biên dịch và cập nhật shader *thời gian thực*. Hãy thử thay đổi các giá trị ở dòng 8.
Dù chỉ có vài dòng code đơn giản thôi, nhưng ta có thể thu được nhiều thứ từ chúng lắm đấy:
1. Ngôn ngữ Shader có 1 hàm `main` trả về mã màu. Tương tự như ngôn ngữ C.
2. Màu cuối cùng của mỗi điểm ảnh được lưu vào biến toàn cục là `gl_FragColor`.
3. Ngôn ngữ nhìn-như-C này có sẵn vài *biến* (như `gl_FragColor`), *hàm**kiểu dữ liệu*. Trong trường hợp này, ta đã được giới thiệu về kiểu `vec4` lưu dữ liệu của một vector 4 chiều với độ chính xác của số thực. Sau này ta sẽ thấy các kiểu tương tự như `vec3``vec2` cũng như các kiểu dữ liệu phổ biến khác như: `float`, `int``bool`.
4. Nếu tinh ý, bạn sẽ nhận ra `vec4` lưu giá trị của 4 kênh đỏ (RED), xanh lá (GREEN), xanh dương (BLUE) và độ trong suốt (ALPHA). Và bạn cũng sẽ nhận thấy các giá trị này đã được chuẩn hoá **(normalized)**, có nghĩa là giá trị chỉ nằm trong khoảng từ `0.0` tới `1.0`. Sau này ta sẽ thấy các giá trị được chuẩn hoá sẽ giúp việc ánh xạ từ biến này sang biến kia trở nên dễ dàng như thế nào.
5. Một tính năng quan trọng khác (học từ ngôn ngữ C) ở trong ví dụ này là sự xuất hiện của các macro, chúng sẽ được xử lý ngay trước khi code được biên dịch. Bằng cách sử dụng macro, ta có thể `#define` các biến toàn cục và một vài thao tác điều kiện cơ bản (với `#ifdef``#endif`). Tất cả các lệnh macro đều được bắt đầu bằng ký hiệu hashtag (`#`). Ngay trước khi biên dịch, tất cả các lệnh điều kiện như `#ifdef` (nếu có macro) và `#ifndef` (nếu không có macro) sẽ được kiểm tra. Ở ví dụ "Hello World!" này, ta chỉ kiểm tra xem macro `GL_ES` có tồn tại không mà thôi. Macro này có tồn tại trên hầu hết các thiết bị điện thoại di động và trình duyệt, tức là dòng lệnh số 2 sẽ chỉ có tác dụng trên các nền tảng này.
6. Các kiểu dữ liệu số thực là yếu tố sống còn trong shader, nên việc quy định mức độ chính xác của chúng là tối quan trọng. Độ chính xác thấp đồng nghĩa với việc dựng hình nhanh hơn và đánh đổi lấy chất lượng thấp hơn. Bạn có thể tỉ mỉ điều chỉnh độ chính xác cho từng biến nếu muốn. Ở dòng 2 (`precision mediump float;`) ta đã quy định mức độ chính xác của toàn bộ các số thực ở mức trung bình. Nhưng cũng có thể giảm xuống mức thấp (`precision lowp float;`) hoặc tăng lên mức cao (`precision highp float;`).
7. Điều cuối cùng, có thể là quan trọng nhất, GLSL không đảm bảo các biến sẽ được chuyển đổi sang kiểu phù hợp. Điều đó nghĩa là gì ? Các nhà sản xuất có các cách tiếp cận khác nhau để tăng tốc card xử lý đồ hoạ của riêng họ nhưng đều phải tuân thủ các yêu cầu tối thiểu. Mà tự chuyển đổi kiểu dữ liệu thì không nằm trong số các yêu cầu bắt buộc đó. Trong ví dụ "Hello World!" của ta, `vec4` có độ chính xác của số thực nên giá trị mà nó lưu giữ nên có kiểu là `float`. Nếu bạn muốn viết code có độ ổn định cao mà không phải tốn hàng giờ ngồi debug trước màn hình trắng tinh, thì hãy làm quen với việc viết thêm dấu chấm (`.`) sau các số thực, nó giúp GPU biết đang phải xử lý một số thực. Đoạn code dưới đây không phải lúc nào cũng chạy đúng:
```glsl
void main() {
gl_FragColor = vec4(1,0,0,1); // LỖI
}
```
Ta vừa mới mô tả những yếu tố cơ bản nhất trong chương trình "Hello World!", giờ là lúc để tự sửa code và bắt đầu áp dụng những gì mới được học. Bạn sẽ nhận ra nếu có lỗi thì chương trình không thể biên dịch được còn màn hình sẽ hiển thị màu trắng. Hãy thử vài thứ thú vị sau xem sao:
* Thử thay số thực bằng số nguyên, có thể card đồ hoạ của bạn vẫn chấp nhận đó
* Thử comment dòng số 8 và không lưu lại bất kỳ giá trị điểm ảnh nào
* Thử tạo một hàm mới chỉ trả về duy nhất một màu cố định và gọi hàm đó trong hàm `main()`. Gợi ý, đây là ví dụ một hàm trả về màu đỏ:
```glsl
vec4 red(){
return vec4(1.0,0.0,0.0,1.0);
}
```
* Có nhiều cách khởi tạo kiểu `vec4`, sau đây là 1 trong số chúng:
```glsl
vec4 color = vec4(vec3(1.0,0.0,1.0),1.0);
```
Dù ví dụ này không hấp dẫn lắm, nhưng nó là ví dụ cơ bản nhất - ta đã đổi màu của toàn bộ canvas sang một màu cố định. Ở chương tới ta sẽ tìm cách đổi màu điểm ảnh dựa theo vị trí của nó và thời điểm mà trang web được load nữa.

@ -0,0 +1,61 @@
## 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`, `sampler2D``samplerCube`. 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.
```glsl
#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](https://www.shadertoy.com/) cũng sử dụng quy tắc riêng:
```glsl
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.
<div class="codeAndCanvas" data="time.frag"></div>
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()`](../glossary/?lan=vi&search=sin), [`cos()`](../glossary/?lan=vi&search=cos), [`tan()`](../glossary/?lan=vi&search=tan), [`asin()`](../glossary/?lan=vi&search=asin), [`acos()`](../glossary/?lan=vi&search=acos), [`atan()`](../glossary/?lan=vi&search=atan), [`pow()`](../glossary/?lan=vi&search=pow), [`exp()`](../glossary/?lan=vi&search=exp), [`log()`](../glossary/?lan=vi&search=log), [`sqrt()`](../glossary/?lan=vi&search=sqrt), [`abs()`](../glossary/?lan=vi&search=abs), [`sign()`](../glossary/?lan=vi&search=sign), [`floor()`](../glossary/?lan=vi&search=floor), [`ceil()`](../glossary/?lan=vi&search=ceil), [`fract()`](../glossary/?lan=vi&search=fract), [`mod()`](../glossary/?lan=vi&search=mod), [`min()`](../glossary/?lan=vi&search=min), [`max()`](../glossary/?lan=vi&search=max) và [`clamp()`](../glossary/?lan=vi&search=clamp). Chúng rất nhanh.
Cùng sửa đoạn code trên nào.
* Hãy giảm tần suất đổi màu xuống sao cho việc đổi màu khó có thể nhận ra được
* Hãy tăng tần suất đổi màu tới khi bạn chỉ nhìn thấy 1 màu duy nhất nhấp nháy liên tục
* Hãy thay đổi mỗi kênh RGB với một tần suất khác nhau, bạn sẽ thấy các kết quả rất bất ngờ
## 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.
<div class="codeAndCanvas" data="space.frag"></div>
Ở đ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.
![](08.png)
Giờ là lúc để thử xem ta hiểu code tới đâu.
* Bạn có biết toạ độ `(0.0, 0.0)` nằm ở đâu trên canvas không ?
* Vậy còn `(1.0, 0.0)`, `(0.0, 1.0)`, `(0.5, 0.5)``(1.0, 1.0)`?
* Hãy tìm cách lấy màu tại vị trí con trỏ chuột khi được click và di chuyển nó đi bất kỳ chỗ nào con trỏ chuột đang ở
* Bạn có tưởng tượng được cách nào để tạo ra các mảng màu hay ho bằng cách sử dụng `u_time``u_mouse` không ?
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.

@ -0,0 +1,232 @@
## Chạy thử shader
Trong quá trình hình thành nên quyển sách này tôi đã tạo ra hàng loạt các công cụ có thể viết, hiển thị, chia sẻ và tìm duyệt các shader. Các công cụ này hoạt động giống nhau trên cả Linux, MacOS, Windows, [Raspberry Pi](https://www.raspberrypi.org/) và cả trình duyệt nữa, mà không cần phải chỉnh sửa gì thêm.
## Chạy thử shader trên trình duyệt
**Hiển thị**: Toàn bộ ví dụ tương tác được trong quyển sách này đều được hiển thị bằng [glslCanvas](https://github.com/patriciogonzalezvivo/glslCanvas) để preview shader theo cách vô cùng đơn giản.
```html
<canvas class="glslCanvas" data-fragment-url=“yourShader.frag" data-textures=“yourInputImage.png” width="500" height="500"></canvas>
```
Như bạn thấy, chỉ cần một thẻ `canvas` với `class="glslCanvas"` và URL tới shader ở thuộc tính `data-fragment-url` là đủ. Tìm hiểu thêm tại [đây](https://github.com/patriciogonzalezvivo/glslCanvas).
Nếu bạn giống tôi, bạn có thể sẽ muốn preview shader ngay trong console, và trong trường hợp thì bạn nên ngó qua [glslViewer](https://github.com/patriciogonzalezvivo/glslViewer). Ứng dụng này cho phép bạn kết hợp shader với script `bash` hay bất kỳ công cụ dòng lệnh nào của Unix giống như cách mà [ImageMagick](http://www.imagemagick.org/script/index.php) hoạt động. Ngoài ra [glslViewer](https://github.com/patriciogonzalezvivo/glslViewer) cũng là một cách hay để biên dịch shader trên [Raspberry Pi](https://www.raspberrypi.org/), cũng chính là lí do mà [openFrame.io](http://openframe.io/) dùng nó để preview shader. Tìm hiểu thêm về công cụ này tại [đây](https://github.com/patriciogonzalezvivo/glslViewer).
```bash
glslViewer yourShader.frag yourInputImage.png —w 500 -h 500 -s 1 -o yourOutputImage.png
```
**Viết shader**: để chia sẻ kinh nghiệm code shader, tôi đã tạo một editor online gọi là [glslEditor](https://github.com/patriciogonzalezvivo/glslEditor). Editor này được nhúng vào các ví dụ trong quyển sách này, nó bổ sung các widget rất tiện cho việc viết code GLSL. Bạn cũng có thể chạy riêng nó như một ứng dụng web ở [editor.thebookofshaders.com/](http://editor.thebookofshaders.com/). Tìm hiểu thêm tại [đây](https://github.com/patriciogonzalezvivo/glslEditor).
![](glslEditor-01.gif)
Nếu bạn thích làm việc offline bằng [SublimeText](https://www.sublimetext.com/) bạn có thể cài đặt [package glslViewer](https://packagecontrol.io/packages/glslViewer). Tìm hiểu thêm tại [đây](https://github.com/patriciogonzalezvivo/sublime-glslViewer).
![](glslViewer.gif)
**Chia sẻ**: Bạn có thể chia sẻ shader bằng editor online ([editor.thebookofshaders.com/](http://editor.thebookofshaders.com/)) Cả phiên bản nhúng và chạy độc lập đều có nút bấm export cho bạn URL tới shader của mình. Nó còn có khả năng export trực tiếp tới [openFrame.io](http://openframe.io/).
![](glslEditor-00.gif)
**Thư viện**: Bên cạnh việc export sang [openFrame.io](http://openframe.io/) Tôi còn tạo ra công cụ để bạn có thể khoe shader của mình trên bất kỳ site nào, tên nó là [glslGallery](https://github.com/patriciogonzalezvivo/glslGallery). Tìm hiểu thêm tại [đây](https://github.com/patriciogonzalezvivo/glslGallery).
![](glslGallery.gif)
## Chạy shader trên framework yêu thích
Trong trường hợp bạn đã có kinh nghiệm lập trình với các framework như: [Processing](https://processing.org/), [Three.js](http://threejs.org/) hay [OpenFrameworks](http://openframeworks.cc/), you có thể sẽ muốn thử shader trên các nền tảng đó. Dưới đây là hướng dẫn sử dụng shader trên các framework phổ biến với các uniform giống như shader được viết trong quyển sách này. (Trong [repository GitHub cho chương này](https://github.com/patriciogonzalezvivo/thebookofshaders/tree/master/04), bạn có thể tìm thấy mã nguồn hoàn chỉnh của cả 3 framework này).
### Với **Three.js**
Ricardo Cabello (hay còn gọi là [MrDoob](https://twitter.com/mrdoob) ), một anh chàng thông minh và khiêm tốn đã phát triển một trong những framework nổi tiếng nhất cho WebGL cùng với các [cộng sự](https://github.com/mrdoob/three.js/graphs/contributors), đó là [Three.js](http://threejs.org/). Bạn sẽ tìm thấy rất nhiều ví dụ, hướng dẫn và sách dạy bạn cách dùng thư viện Javascript này để tạo nên các tác phẩm đồ hoạ 3D rất ngầu.
Dưới đây là một ví dụ về code HTML và JS mà bạn cần để bắt đầu viết shader với three.js. Hãy chú ý tới đoạn `id="fragmentShader"`, đây là nơi mà bạn có thể copy-paste shader từ quyển sách này thay vào đó để thử.
```html
<body>
<div id="container"></div>
<script src="js/three.min.js"></script>
<script id="vertexShader" type="x-shader/x-vertex">
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
gl_FragColor=vec4(st.x,st.y,0.0,1.0);
}
</script>
<script>
var container;
var camera, scene, renderer;
var uniforms;
init();
animate();
function init() {
container = document.getElementById( 'container' );
camera = new THREE.Camera();
camera.position.z = 1;
scene = new THREE.Scene();
var geometry = new THREE.PlaneBufferGeometry( 2, 2 );
uniforms = {
u_time: { type: "f", value: 1.0 },
u_resolution: { type: "v2", value: new THREE.Vector2() },
u_mouse: { type: "v2", value: new THREE.Vector2() }
};
var material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
container.appendChild( renderer.domElement );
onWindowResize();
window.addEventListener( 'resize', onWindowResize, false );
document.onmousemove = function(e){
uniforms.u_mouse.value.x = e.pageX
uniforms.u_mouse.value.y = e.pageY
}
}
function onWindowResize( event ) {
renderer.setSize( window.innerWidth, window.innerHeight );
uniforms.u_resolution.value.x = renderer.domElement.width;
uniforms.u_resolution.value.y = renderer.domElement.height;
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
uniforms.u_time.value += 0.05;
renderer.render( scene, camera );
}
</script>
</body>
```
### Với **Processing**
Bắt đầu bởi [Ben Fry](http://benfry.com/) và [Casey Reas](http://reas.com/) năm 2001, [Processing](https://processing.org/) là môi trường siêu đơn giản mà mạnh mẽ để bắt đầu code shader (ít nhất là với tôi). [Andres Colubri](https://codeanticode.wordpress.com/) đã cải tiến OpenGL và Video trong Processing để khiến cho việc sử dụng GLSL trở nên dễ hơn bao giờ hết. Processing sẽ thực thi file shader có tên `"shader.frag"` trong thư mục `data`. Nếu bạn copy ví dụ ở đây, hãy nhớ đổi tên file cho khớp.
```cpp
PShader shader;
void setup() {
size(640, 360, P2D);
noStroke();
shader = loadShader("shader.frag");
}
void draw() {
shader.set("u_resolution", float(width), float(height));
shader.set("u_mouse", float(mouseX), float(mouseY));
shader.set("u_time", millis() / 1000.0);
shader(shader);
rect(0,0,width,height);
}
```
Để shader hoạt động với các phiên bản trước 2.1, bạn cần thêm dòng sau vào phần đầu của shader: `#define PROCESSING_COLOR_SHADER`. Nó sẽ trông như thế này:
```glsl
#ifdef GL_ES
precision mediump float;
#endif
#define PROCESSING_COLOR_SHADER
uniform vec2 u_resolution;
uniform vec3 u_mouse;
uniform float u_time;
void main() {
vec2 st = gl_FragCoord.st/u_resolution;
gl_FragColor = vec4(st.x,st.y,0.0,1.0);
}
```
Để biết thêm về shader trong Processing, hãy xem [hướng dẫn](https://processing.org/tutorials/pshader/).
### Với **openFrameworks**
Ai cũng có công cụ mà họ cảm thấy thoải mái nhất, trong trường hợp của tôi thì đó là [openFrameworks](http://openframeworks.cc/). Framework viết bằng C++ này dựa trên OpenGL và một vài thư viện mã nguồn mở khác. Nó rất giống Processing ở nhiều điểm, chỉ khác ở phần biên dịch C++ phức tạp. Giống như Processing, openFrameworks sẽ tìm file shader của bạn để thực thi, nên nếu copy file `.frag` nào ở đây thì đừng quên đổi tên file trong code cho khớp.
```cpp
void ofApp::draw(){
ofShader shader;
shader.load("","shader.frag");
shader.begin();
shader.setUniform1f("u_time", ofGetElapsedTimef());
shader.setUniform2f("u_resolution", ofGetWidth(), ofGetHeight());
ofRect(0,0,ofGetWidth(), ofGetHeight());
shader.end();
}
```
Nếu bạn muốn sử dụng bộ đầy đủ các uniform trong GlslViewer và GlslCanvas trong openFrameworks một cách đơn giản, tôi xin giới thiệu addon [ofxShader](https://github.com/patriciogonzalezvivo/ofxshader) để hỗ trợ nhiều buffer, vật liệu, hot-reload và tự động chuyển đổi sang OpenGL ES trên Raspberry Pi. Code đơn giản như sau:
```cpp
//--------------------------------------------------------------
void ofApp::setup(){
ofDisableArbTex();
sandbox.allocate(ofGetWidth(), ofGetHeight());
sandbox.load("grayscott.frag");
}
//--------------------------------------------------------------
void ofApp::draw(){
sandbox.render();
sandbox.draw(0, 0);
}
```
Để tìm hiểu thêm về shader trong openFrameworks hãy đọc [bài hướng dẫn tuyệt vời này](http://openframeworks.cc/ofBook/chapters/shaders.html) của [Joshua Noble](http://thefactoryfactory.com/).
### Với **Blender**
[GlslTexture](https://github.com/patriciogonzalezvivo/glslTexture) là một addon giúp bạn tạo ra các texture theo công thức của GLSL và hoàn toàn tương thích với các sandbox khác trong chương này. Cách mà nó hoạt động:
1. Operator Search: `F3` (hoặc gõ phím `Space` tuỳ theo chỉnh sửa của bạn). Gõ `GlslTexture`
![](blender/00.png)
2. Đổi kích thước `width``height` và tên file shader ở `Source` (có thể là đường dẫn tới 1 file khác)
![](blender/01.png)
3. Sử dụng ảnh trong vật liệu. Tên của ảnh sẽ dựa trên tên của file shader.
![](blender/02.png)
4. Mở phần Text Editor và viết shader (hoặc sửa từ bên ngoài). Nó sẽ được cập nhật ngay lập tức.
![](blender/03.png)

@ -0,0 +1,140 @@
# Các thuật toán hình học
## Các hàm số cơ bản (Hàm hình dạng - Shape function)
Chương này có thể đặt tên là "bài học sơn hàng rào của ngài Miyagi". Trước đó, ta đã ánh xạ toạ độ *x**y* sang các kênh *RED**GREEN*. Ta cũng đã tạo ra một hàm nhận một vector 2 chiều (x, y) làm tham số đầu vào và trả ra một vector 4 chiều (r, g, b, a). Nhưng trước khi đi sâu vào việc biến đổi dữ liệu giữa các kiểu thì ta cần bắt đầu từ những thứ đơn giản hơn ... rất nhiều. Đó là việc tạo ra các hàm chỉ xử lý vector 1 chiều. Càng bỏ thời gian và công sức học cho thuẩn thục kỹ năng này, võ karate-shader của bạn sẽ càng cao siêu.
![The Karate Kid (1984)](mr_miyagi.jpg)
Cấu trúc code dưới đây sẽ là hàng rào của chúng ta. Trong đó, ta chuẩn hoá giá trị *x* (`st.x`) bằng 2 cách: một là với cường độ sáng (quan sát gradient từ đen sang trắng) và hai là vẽ đồ thị một đường màu xanh lá đè lên trên (trường hợp này giá trị của *x* được gán trực tiếp cho *y*). Đừng tập trung quá nhiều vào hàm vẽ đồ thị, ta sẽ đi vào chi tiết sau.
<div class="codeAndCanvas" data="linear.frag"></div>
**Chú thích**: Hàm khởi tạo `vec3` sẽ tự động "nhận ra" bạn muốn gán màu của cả 3 kênh RGB giống nhau ở dòng 19. Còn hàm khởi tạo `vec4` thì "nhận ra`" bạn muốn tạo một vector 4 chiều từ một vector 3 chiều kết hợp một số thực nữa dành cho chiều thứ tư, ở dòng 25.
Đoạn code này là hàng rào của bạn; nên hãy tập trung quan sát kỹ. Bạn sẽ còn sử dụng khoảng `0.0``1.0` này nhiều lần nữa. Bạn sẽ thuần thục kỹ năng tạo nên đồ thị này.
Ánh xạ một-một giữa *x**y* (phần gradient đen trắng) được gọi là *nội suy tuyến tính (linear interpolation)*. Ta có thể thay các hàm toán học khác để thay đổi hình dáng của đồ thị. Ví dụ ta có thể vẽ đồ thị luỹ thừa 5 của *x* để có được đường cong như hình dưới.
<div class="codeAndCanvas" data="expo.frag"></div>
Thú vị phải không ? Ở dòng 22, hãy thử các số mũ khác nhau như: 20.0, 2.0, 1.0, 0.0, 0.2 và 0.02 chẳng hạn. Hiểu được mối quan hệ giữa giá trị và số mũ sẽ rất hữu ích. Bằng cách sử dụng các hàm toán học ở đây bạn sẽ nắm rõ hơn về cách điều khiển các đường cong.
[`pow()`](..glossary/?lan=vi&search=pow) là hàm có sẵn trong số rất nhiều hàm của GLSL. Hầu hết đều được tăng tốc tính toán bởi phần cứng, có nghĩa là nếu được dùng đúng cách nó sẽ giúp code chạy nhanh hơn.
Hãy thay hàm luỹ thừa ở dòng 22 bằng các hàm khác như: [`exp()`](..glossary/?lan=vi&search=exp), [`log()`](..glossary/?lan=vi&search=log) và [`sqrt()`](..glossary/?lan=vi&search=sqrt). Một số hàm sẽ cho kết quả thú vị nếu bạn sử dụng hằng số PI. Ở dòng 8 tôi có định nghĩa macro sẽ thay thế tất cả những chỗ `PI` xuất hiện bằng hằng số `3.14159265359`.
### Step và Smoothstep
GLSL cũng có vài hàm số riêng biệt được tăng tốc bởi phần cứng.
Hàm [`step()`](..glossary/?lan=vi&search=step) nhận 2 tham số đầu vào. Tham số đầu tiên là một ngưỡng giới hạn nào đó, còn tham số thứ 2 là giá trị mà ta muốn biết có vượt qua ngưỡng giới hạn kia không. Bất kỳ giá trị nào nhỏ hơn ngưỡng sẽ cho kết quả `0.0` và ngược lại, tất cả các giá trị lớn hơn ngưỡng sẽ cho kết quả `1.0`.
Hãy thử thay giá trị ở dòng 20 của đoạn code dưới đây.
<div class="codeAndCanvas" data="step.frag"></div>
Một hàm tương tự là [`smoothstep()`](..glossary/?lan=vi&search=smoothstep). Tham số đầu vào là 1 khoảng min-max kèm thêm 1 giá trị. Hàm này sẽ nội suy giá trị đó trong khoảng min-max, các giá trị nằm ngoài khoảng này sẽ trở min hoặc max tuỳ theo nó nằm ở phía nào của khoảng đã cho.
<div class="codeAndCanvas" data="smoothstep.frag"></div>
Ở ví dụ trước, dòng 12, chú ý rằng ta đã dùng hàm `smoothstep` để vẽ đồ thị trong hàm `plot()`. Nếu muốn đồ thị trồi lên ở một đoạn nào đó thì làm thế nào ? Bằng cách ghép hai hàm [`smoothstep()`](..glossary/?lan=vi&search=smoothstep) lại. Hãy thay dòng code dưới đây vào dòng 20. Trông như ta đã chẻ đôi canvas ra phải không ?
```glsl
float y = smoothstep(0.2,0.5,st.x) - smoothstep(0.5,0.8,st.x);
```
### Sin và Cos
Khi bạn muốn dùng Toán để tạo chuyển động, tạo hình hay pha trộn các giá trị, không có gì tốt hơn việc làm quen với sin và cos.
Hai hàm lượng giác cơ bản này kết hợp với nhau để tạo nên những vòng tròn đa năng như dao gấp quân đội Thuỵ Sỹ của MacGyver vậy. Việc tìm hiểu cách chúng hoạt động và kết hợp với nhau ra sao rất quan trọng. Về cơ bản, cho một góc bất kỳ (đơn vị radian), hai hàm này sẽ cho kết quả là tọa độ *x* ([cos](..glossary/?lan=vi&search=cos)) và *y* ([sin](..glossary/?lan=vi&search=sin)) của 1 điểm trên đường tròn có bán kính bằng 1. Và chính việc kết quả thu được từ 2 hàm này vừa biến thiên một cách mềm mại lại còn luôn được chuẩn hoá sẵn theo cặp và cả đơn lẻ (trong khoảng -1 tới 1) khiến cho 2 hàm này trở thành các công cụ siêu hữu ích.
![](sincos.gif)
Mặc dù rất khó để mô tả mối liên hệ giữa các hàm lượng giác với đường tròn, nhưng chuyển động đẹp tuyệt trên đây đã làm rất tốt nhiệm vụ mô tả tóm tắt mối liên hệ này.
<div class="simpleFunction" data="y = sin(x);"></div>
Hãy nhìn thật kỹ đồ thị hình sine. Chú ý cách mà các giá trị *y* biến thiên rất mượt giữa +1 và -1. Như ta đã thấy ở ví dụ có sử dụng thời gian ở chương trước, bạn có thể sử dụng tính chất tuần hoàn này của hàm [`sin()`](..glossary/?lan=vi&search=sin) để áp dụng cho các thuộc tính. Nếu bạn đang đọc trên trình duyệt, bạn có thể sửa đoạn code phía trên để xem các sóng đồ thị thay đổi như thế nào. (Chú ý: Đừng quên dấu chấm phẩy ở cuối dòng.)
Hãy thử các thay đổi sau và xem điều gì xảy ra:
* Cộng `u_time` với *x* trước khi gọi hàm `sin`. Bạn sẽ thấy sóng đồ thị sẽ dịch chuyển dọc theo trục hoành.
* Nhân *x* với `PI` trước khi gọi hàm `sin`. Bạn sẽ thấy 2 chu kỳ bị **co lại** và lặp lại mỗi 2 đơn vị số nguyên.
* Nhân `u_time`với *x* trước khi gọi hàm `sin`. Bạn sẽ thấy **tần số (frequency)** giữa các chu kỳ ngày càng ngắn lại. Chú ý rằng u_time càng lớn thì càng khó nhìn rõ đồ thị do các chu kỳ bị co lại rất nhiều.
* Cộng 1.0 vào [`sin(x)`](..glossary/?lan=vi&search=sin). Và bạn sẽ thấy toàn bộ sóng được **nâng lên (displaced)** khiến cho giá trị nằm trong khoảng 0.0 và 2.0.
* Nhân [`sin(x)`](..glossary/?lan=vi&search=sin) với 2.0. Và bạn sẽ thấy **biên độ (amplitude)** rộng gấp đôi.
* Tính giá trị tuyệt đối ([`abs()`](..glossary/?lan=vi&search=abs)) của hàm `sin(x)`. Trông đồ thị sẽ giống như đường đi của quả bóng nảy trên mặt đất.
* Tách riêng phần thập phân bằng hàm [`fract()`](..glossary/?lan=vi&search=fract)từ kết quả của [`sin(x)`](..glossary/?lan=vi&search=sin).
* Làm tròn lên bằng hàm [`ceil()`](..glossary/?lan=vi&search=ceil) và làm tròn xuống bằng hàm [`floor()`](..glossary/?lan=vi&search=floor) từ kết quả của [`sin(x)`](..glossary/?lan=vi&search=sin) để có được sóng điện tử của các giá trị 1 và -1.
### Các hàm hữu ích khác
Chúng tôi vừa mới giới thiệu cho các bạn 1 vài hàm mới. Giờ là lúc thử nghiệm từng hàm một bằng cách uncomment từng dòng dưới đây một. Hãy làm quen với các hàm này. Tôi biết bạn đang thắc mắc ... tại sao ? Google nhanh với từ khoá "generative art" sẽ cho bạn câu trả lời. Hãy nhớ rằng các hàm này là hàng rào của chúng ta. Chúng ta đang dần thuần thục với các chuyển động 1 chiều, chỉ có lên và xuống. Sớm thôi, ta sẽ đụng tới các chiều thứ hai, ba và bốn!
![Anthony Mattox (2009)](anthony-mattox-ribbon.jpg)
<div class="simpleFunction" data="y = mod(x,0.5); // tính phn dư ca phép tính x / 0.5
//y = fract(x); // tách phần thập phân của x
//y = ceil(x); // làm tròn lên
//y = floor(x); // làm tròn xuống
//y = sign(x); // lấy dấu âm dương của x
//y = abs(x); // tính giá trị tuyệt đối của x
//y = clamp(x,0.0,1.0); // kẹp x trong khoảng 0.0 và 1.0
//y = min(0.0,x); // tìm số nhỏ nhất giữa 2 số 0.0 và x
//y = max(0.0,x); // tìm số lớn nhất giữa 2 số 0.0 và x "></div>
### Các hàm nâng cao
[Golan Levin](http://www.flong.com/) có tài liệu mô tả rất chi tiết về các hàm phức tạp khác vô cùng hữu ích. Ứng dụng chúng vào GLSL sẽ là một bước đi thông minh để bắt đầu dựng nên thư viện code của chính bạn.
* Các hàm đa thức: [www.flong.com/texts/code/shapers_poly](http://www.flong.com/texts/code/shapers_poly/)
* Các hàm luỹ thừa: [www.flong.com/texts/code/shapers_exp](http://www.flong.com/texts/code/shapers_exp/)
* Các hàm mô phỏng đường tròn và elip: [www.flong.com/texts/code/shapers_circ](http://www.flong.com/texts/code/shapers_circ/)
* Đường cong Bezier và các hàm tương tự: [www.flong.com/texts/code/shapers_bez](http://www.flong.com/texts/code/shapers_bez/)
<div class="glslGallery" data="160414041542,160414041933,160414041756" data-properties="clickRun:editor,hoverPreview:false"></div>
Như một đầu bếp đi thu thập các kỳ hoa dị thảo, nghệ sỹ kỹ thuật số và các lập trình viên đồ hoạ cũng sẽ có niềm yêu thích riêng với các hàm nội suy của riêng họ.
[Iñigo Quiles](http://www.iquilezles.org/) có 1 bộ sưu tầm các [hàm](http://www.iquilezles.org/www/articles/functions/functions.htm) rất hữu ích. Sau khi đọc [bài báo này](http://www.iquilezles.org/www/articles/functions/functions.htm) hãy xem cách thực thi các hàm đó trong GLSL. Hãy chú ý tới các tiểu tiết như thêm dấu chấm "." vào sau các số thực và sử dụng cách đặt tên hàm của ngôn ngữ C vào GLSL; ví dụ thay vì `powf()` hãy dùng `pow()`:
<div class="glslGallery" data="05/impulse,05/cubicpulse,05/expo,05/expstep,05/parabola,05/pcurve" data-properties="clickRun:editor,hoverPreview:false"></div>
Để tạo động lực cho bạn, đây là 1 ví dụ hoàn hảo (tạo nên bởi [Danguafer](https://www.shadertoy.com/user/Danguafer)) về việc thuần thục môn võ karate-hàm-số.
<iframe width="800" height="450" frameborder="0" src="https://www.shadertoy.com/embed/XsXXDn?gui=true&t=10&paused=true" allowfullscreen></iframe>
Ở các chương tiếp theo *(Next >>)* chúng ta sẽ học các chiêu mới. Đầu tiên là trộn màu rồi sau đó là vẽ hình.
#### Bài tập
Hãy nhìn vào bảng các phương trình dưới đây, được tạo bởi [Kynd](http://www.kynd.info/log/). Hãy xem cách mà anh ấy kết hợp các hàm lại với nhau để kiểm soát các giá trị nằm trong khoảng 0.0 và 1.0. Giờ là lúc bạn tập luyện bằng cách dựng lại các hàm này. Hãy nhớ rằng càng luyện tập chăm chỉ bạn sẽ càng giỏi võ.
![Kynd - www.flickr.com/photos/kynd/9546075099/ (2013)](kynd.png)
#### Một vài công cụ cho bạn
Đây là một vài công cụu sẽ giúp bạn vẽ đồ thị các hàm 1 cách trực quan nhất.
* Grapher: Nếu bạn dùng máy Mac, gõ `grapher` trong Spotlight và bạn có thể dùng ngay công cụ siêu tiện ích này.
![OS X Grapher (2004)](grapher.png)
* [GraphToy](http://www.iquilezles.org/apps/graphtoy/): Thêm một sản phẩm nữa của [Iñigo Quilez](http://www.iquilezles.org) để minh hoạ các hàm GLSL trên WebGL.
![Iñigo Quilez - GraphToy (2010)](graphtoy.png)
* [Shadershop](http://tobyschachman.com/Shadershop/): Công cụ tuyệt vời này của [Toby Schachman](http://tobyschachman.com/) sẽ dạy bạn cách để tạo nên các hàm số phức tạp theo cách đơn giản nhất.
![Toby Schachman - Shadershop (2014)](shadershop.png)

@ -0,0 +1,145 @@
![Biểu đồ màu của Paul Klee (1931)](klee.jpg)
## Màu sắc
Ta chưa đề cập nhiều lắm về các loại vector trong GLSL. Trước khi đi tiếp thì việc hiểu các kiểu dữ liệu này rất quan trọng và màu sắc là chủ đề phù hợp tuyệt vời để giải thích cho các khái niệm đó.
Nếu bạn đã quen với mô hình lập trình hướng đối tượng thì có thể bạn đã chú ý tới việc ta có thể truy cập dữ liệu bên trong các vector như cách mà `struct` của ngôn ngữ C làm việc.
```glsl
vec3 red = vec3(1.0,0.0,0.0);
red.x = 1.0;
red.y = 0.0;
red.z = 0.0;
```
Định nghĩa một màu sử dụng các ký hiệu *x*, *y**z* có thể hơi khó hiểu nhỉ ? Đó cũng chính là lý do mà ta sẽ có nhiều cách khác để truy cập dữ liệu trong vector. Các ký hiệu `x`, `y``z` có thể thay thế bằng `.r`, `.g`, `.b`, hoặc `.s`, `.t`, `.p`. (`.s`, `.t``.p` sẽ được sử dụng ở các chương sau để truy cập các toạ độ trong không gian texture). Ngoài ra bạn cũng có thể truy cập các giá trị trong vector bằng vị trí trong array (`[0]`, `[1]``[2]`).
Mỗi dòng code dưới đây đều truy cập một giá trị giống nhau trong vector:
```glsl
vec4 vector;
vector[0] = vector.r = vector.x = vector.s;
vector[1] = vector.g = vector.y = vector.t;
vector[2] = vector.b = vector.z = vector.p;
vector[3] = vector.a = vector.w = vector.q;
```
Những cách truy cập khác nhau vào cùng 1 giá trị trong vector này sẽ giúp bạn viết code rõ ràng và dễ dàng hơn. Và sự linh hoạt này cũng sẽ khiến bạn dần dần xoá nhoà khoảng cách giữa không gian màu sắc và không gian vật lý, do chúng có cùng một cách lưu trữ dữ liệu.
Một tính năng khác cũng tuyệt vời không kém của vector trong GLSL là các giá trị bên trong có thể được tráo đổi *(swizzle)* vị trí theo bất kỳ trật tự nào bạn muốn, khiến cho việc xử lý chúng dễ dàng hơn bao giờ hết.
```glsl
vec3 yellow, magenta, green;
// Tạo màu vàng (1., 1., 0.)
yellow.rg = vec2(1.0); // Gán giá trị 1. cho kênh R và G
yellow[2] = 0.0; // Gán giá trị 0. cho kênh B
// Tạo màu hồng (1., 0., 1.)
magenta = yellow.rbg; // Đảo vị trí của 2 kênh G và B
// Tạo màu xanh lá (0., 1., 0.)
green.rgb = yellow.bgb; // Lấy giá trị ở kênh B của màu vàng để gán đồng thời cho cả kênh R và B của màu xanh lá
```
#### Tiện ích
Bạn có thể hiếm khi chọn màu bằng các con số vì nó không mấy trực quan, nhưng bắt buộc phải làm vậy trong GLSL. May thay, có rất nhiều tiện ích hỗ trợ thao tác này. Bạn có thể tuỳ ý lựa chọn tiện ích phù hợp nhất, miễn là kết quả trả về được lưu dưới dạng `vec3` hoặc `vec4`. Ví dụ, tôi sử dụng các template sau trên trang [Spectrum](http://www.eigenlogik.com/spectrum/mac):
```
vec3({{rn}},{{gn}},{{bn}})
vec4({{rn}},{{gn}},{{bn}},1.0)
```
### Trộn màu
Bạn đã biết cách định nghĩa các màu sắc rồi, giờ thì kết hợp nó với kiến thức đã có từ các chương trước nào. Trong GLSL có 1 hàm rất hữu ích, đó là [`mix()`](../glossary/?lan=vi&search=mix), giúp bạn trộn 2 màu với nhau theo 1 tỉ lệ nhất định. Và tỉ lệ đó cũng nằm trong khoảng [0.0, 1.0]. Hoàn hảo, đó chính là những gì mà ta đã học và luyện tập ở chương trước với việc sơn hàng rào, giờ thì lôi ra áp dụng thôi!
![](mix-f.jpg)
Hãy xem đoạn code dưới đây và chú ý vào dòng 18 vì tôi sẽ sử dụng giá trị tuyệt đối của đồ thị sóng hình sin làm tỉ lệ trộn 2 màu `colorA``colorB`.
<div class="codeAndCanvas" data="mix.frag"></div>
Thử xem bạn thuần thục môn võ karate-shader đến đâu rồi nào:
* Hãy tạo một vùng chuyển tiếp mượt mà giữa 2 màu xem sao. Hãy sử dụng nó để diễn tả một cảm giác nào đó nhé. Màu gì thì diễn tả cảm giác đó tốt nhất ? Nó xuất hiện rồi biến mất như thế nào ? Rồi lại thử với một cảm giác khác. Sửa code để đổi 2 màu được chọn để trộn phía trên xem sao.
* Thay vì dùng hàm sin, hãy thử các hàm khác mà ta đã học ở chương trước xem sao
* Robert Penner đã phát triển một series các hàm số dùng trong animation rất nổi tiếng, chúng được gọi là các [easing functions](http://easings.net/), bạn có thể sử dụng [ví dụ này](../edit.php#06/easing.frag) để tham khảo và lấy cảm hứng nhưng tốt nhất là bạn tự tạo ra dải màu gradient của riêng mình.
### Gradient
Hàm [`mix()`](../glossary/?lan=vi&search=mix) còn nhiều vũ khí bí mật khác nữa. Thay vì truyền vào 1 số thực `float` để chỉ định tỉ lệ trộn 2 màu, bạn có thay nó bằng một `vec3` (hoặc `vec4` tuỳ vào định dạng của 2 màu gốc) để chỉ định tỉ lệ trộn màu cho từng kênh `r`, `g`, `b` (và cả `a`) riêng biệt.
![](mix-vec.jpg)
Hãy xem đoạn code ví dụ dưới đây. Cũng tương tự như ở chương trước, tôi sẽ vẽ đồ thị của hàm số được dùng để chỉ định tỉ lệ trộn màu. Hiện tại thì tất cả các kênh đều dùng chung 1 hàm số nên sẽ có đồ thị giống nhau.
Bạn hãy thử uncomment dòng số 25 xem điều gì xảy ra. Rồi lần lượt cả dòng 26 và 27 nữa. Bạn sẽ thấy mỗi kênh của 2 màu `colorA``colorB` lại được trộn với nhau theo một tỉ lệ riêng.
<div class="codeAndCanvas" data="gradient.frag"></div>
Chắc hẳn bạn chưa quên 3 hàm số tôi dùng ở các dòng từ 25 tới 27 đâu nhỉ. Hãy thí nghiệm thoải mái với chúng đi. Kiến thức từ chương trước sẽ giúp bạn tạo nên rất nhiều dải màu gradient thú vị đó. Hãy thử:
![Bức 'The Fighting Temeraire' của William Turner (1838)](turner.jpg)
* Tạo dải màu gradient mô phỏng cảnh hoàng hôn của William Turner
* Chuyển qua lại giữa màu bình minh và hoàng hôn bằng cách dùng biến `u_time`.
* Tạo dải màu 7 sắc cầu vồng
* Sử dụng hàm `step()` để tạo nên những lá cờ sặc sỡ
### HSB
Ta không thể học về màu sắc mà không đề cập tới không gian màu được. Có thể bạn đã biết, chúng ta có nhiều cách khác nhau để lưu trữ màu ngoài cách dùng 3 kênh đỏ, xanh lá, xanh dương.
[HSB](http://en.wikipedia.org/wiki/HSL_and_HSV) là viết tắt của Hue (sắc độ), Saturation (độ bão hoà màu) và Brightness (hoặc Value, độ sáng), là một cách định dạng màu khác, vốn có tổ chức và dễ hiểu hơn nhiều. Hãy dành vài phút để đọc hiểu 2 hàm `rgb2hsv()``hsv2rgb()` trong đoạn code dưới đây.
Bằng cách ánh xạ vị trí trên trục X với Hue, vị trí trên trục Y với Brightness, ta có được dải phổ của những màu sắc có thể quan sát được bằng mắt thường. Cách phân bố màu trên không gian kiểu này của HSB giúp cho việc chọn màu dễ hơn cách dùng RGB nhiều.
<div class="codeAndCanvas" data="hsb.frag"></div>
### HSB trong hệ toạ độ cực
HSB ban đầu được thiết kế để biểu diễn bằng hệ toạ độ cực (vị trí mỗi điểm được xác định bằng khoảng cách tới gốc toạ độ và góc phương vị - góc so với 1 trục toạ độ duy nhất), thay vì hệ toạ độ Đề-các. Giả sử toàn bộ canvas sẽ được dùng để biểu diễn hệ toạ độ cực. Để chuyển đổi màu ở định dạng HSB từ hệ toạ độ Đề-các sang hệ toạ độ cực thì ta cần xác định được khoảng cách từ 1 điểm ảnh trên canvas tới điểm chính giữa của canvas (chính là gốc toạ độ cực), rồi tính góc nghiêng của vector từ tâm tới điểm ảnh đó với trục hoành. Để làm được điều này thì ta cần dùng tới các hàm [`length()`](../glossary/?lan=vi&search=length) và [`atan(y,x)`](../glossary/?lan=vi&search=atan) (ở các ngôn ngữ shader khác thì người ta hay dùng hàm `atan2(y,x)` còn trong GLSL thì đó chính là hàm atan này, nhờ có **overload**).
Khi sử dụng các hàm vector và lượng giác, `vec2`, `vec3``vec4` sẽ chẳng khác gì vector thông thường, kể cả bạn có dùng nó để biểu diễn màu đi chăng nữa. Vì vậy ta sẽ coi như màu sắc và vector tương đương nhau, thực tế cho thấy lối suy nghĩ linh hoạt này sẽ rất hữu dụng.
**Chú ý:** Các hàm hình học, ngoài [`length`](../glossary/?lan=vi&search=length) ra thì còn rất nhiều: [`distance()`](../glossary/?lan=vi&search=distance), [`dot()`](../glossary/?lan=vi&search=dot), [`cross`](../glossary/?lan=vi&search=cross), [`normalize()`](../glossary/?lan=vi&search=normalize), [`faceforward()`](../glossary/?lan=vi&search=faceforward), [`reflect()`](../glossary/?lan=vi&search=reflect) và [`refract()`](../glossary/?lan=vi&search=refract). GLSL cũng có nhiều hàm đặc biệt dành riêng cho vector như: [`lessThan()`](../glossary/?lan=vi&search=lessThan), [`lessThanEqual()`](../glossary/?lan=vi&search=lessThanEqual), [`greaterThan()`](../glossary/?lan=vi&search=greaterThan), [`greaterThanEqual()`](../glossary/?lan=vi&search=greaterThanEqual), [`equal()`](../glossary/?lan=vi&search=equal) và [`notEqual()`](../glossary/?lan=vi&search=notEqual).
Khi ta đã có góc và khoảng cách trong hệ toạ độ cực rồi, ta cần "chuẩn hoá" (normalize) các giá trị đó sao cho chúng nằm trong khoảng [0.0, 1.0]. Ở dòng 27, hàm [`atan(y,x)`](../glossary/?lan=vi&search=atan) sẽ cho kết quả là góc tính bằng đơn vị radian, nằm trong khoảng [-PI, PI] tương đương với [-3.14, 3.14]. Vậy để chuẩn hoá về khoảng [0.0, 1.0], ta sẽ "remap" góc đó bằng cách chia cho 2 PI (dùng hằng số `TWO_PI` được định nghĩa trên đầu) rồi cộng kết quả thu được với 0.5. Còn với khoảng cách thì dễ rồi, giá trị lớn nhất mà nó có thể đạt được trong canvas là 0.5, cũng chính là bán kính đường tròn ngoại tiếp của canvas. Vậy nên để "remap" khoảng cách về khoảng [0.0, 1.0] thì ta chỉ cần x2.
Và đó cũng chính là phần chủ đạo trong đoạn code dưới đây.
<div class="codeAndCanvas" data="hsb-colorwheel.frag"></div>
Hãy thử sửa code sao cho:
* Dải màu HSB quay vòng vòng như bánh xe màu.
* Sử dụng các hàm số cơ bản kết hợp với các hàm chuyển đổi định dạng HSB sang RGB để co giãn 1 dải Hue nhất định nào đó.
![Quang phổ tổng hợp và tách riêng tần số đỏ, vàng, xanh - William Home Lizars (1834)](spectrums.jpg)
* Nếu quan sát kỹ bánh xe màu được dùng để chọn màu (như hình dưới), bạn sẽ thấy các dải phổ của nó hơi khác một chút vì nó dùng không gian màu RYB. Ví dụ, màu ở vị trí đối diện với đỏ trên bánh xe là màu xanh lá, còn ở ví dụ trên thì ta lại thấy đó là màu xanh da trời (cyan). Bạn có thể sửa code để tạo ra bánh xe màu giống như hình này không ? (Gợi ý: hàm số)
![](colorwheel.png)
* Hãy đọc [quyển 'Interaction of Color' của Josef Albers](http://www.goodreads.com/book/show/111113.Interaction_of_Color) và dùng các shader dưới đây để thực hành.
<div class="glslGallery" data="160505191155,160505193939,160505200330,160509131554,160509131509,160509131420,160509131240" data-properties="clickRun:editor,openFrameIcon:false,showAuthor:false"></div>
#### Chú ý về hàm và tham số
Trước khi sang chương tiếp theo hãy cùng nhìn lại một chút bằng cách quan sát các hàm số đã dùng. Bạn sẽ thấy có từ khoá `in` được đặt trước kiểu dữ liệu của mỗi tham số. Từ khoá này là một trong những [*qualifier*](http://www.shaderific.com/glsl-qualifiers/#inputqualifier) của GLSL và trong trường hợp này thì nó cho ta / GPU biết rằng mọi thay đổi trong hàm này sẽ không làm thay đổi giá trị của biến bên ngoài được truyền vào làm tham số. Ta sẽ còn thấy thêm các qualifier khác ở các chương tiếp theo như `out``inout`. Qualifier `inout`, đại khái là sẽ khiến cho biến đó được truyền vào hàm với dạng tham chiếu (reference), nên mọi thay đổi trong hàm này đối với biến đó sẽ được giữ nguyên khi ra khỏi hàm.
```glsl
int newFunction(in vec4 aVec4, // read-only
out vec3 aVec3, // write-only
inout int aInt); // read-write
```
Ở chương tiếp theo ta sẽ dùng tất cả kiến thức đã học được trước đó để tạo nên các hình khối bằng cách *pha trộn (blend)* các không gian lại với nhau. Bạn không đọc nhầm đâu, ... *pha trộn* các không gian đó.

@ -0,0 +1,232 @@
![Alice Hubbard, Providence, United States, ca. 1892. Photo: Zindman/Freemont.](froebel.jpg)
## Hình dáng
Cuối cùng thì cũng tới phần này! Ta đã trang bị biết bao kiến thức chỉ để chờ giây phút này thôi đấy! Hầu hết những kiến thức nền tảng của GLSL đều đã được giới thiệu ở các chương trước. Giờ là lúc để kết hợp tất cả những kiến thức đó lại. Ở chương này ta sẽ học cách vẽ những hình khối cơ bản nhất, bằng code, chạy song song trên các bộ vi xử lý của GPU.
### Hình chữ nhật
Cứ tưởng tượng rằng ta có một tờ giấy kẻ ô sẵn vẫn hay dùng trong môn Toán và ta phải làm bài tập về nhà là vẽ một hình vuông lên đó. Kích thước của tờ giấy là 10x10 còn hình vuông sẽ cỡ 8x8. Làm thế nào nhỉ ?
![](grid_paper.jpg)
Bạn sẽ tô màu tất cả các ô của tờ giấy trừ những ô ở dòng trên cùng và dưới cùng, và cả cột đầu tiên lẫn cột cuối cùng nữa, phải không ?
Điều này thì có liên quan gì tới shader ? Mỗi ô vuông nhỏ trên tờ giấy đó có thể coi như một điểm ảnh hay một thread. Ta biết vị trí của từng ô vuông đó, giống như bàn cờ vua ấy mà. Ở các chương trước ta đã ánh xạ toạ độ *x**y* vào các kênh màu *đỏ**xanh lá*, và ta cũng đã biết cách để đưa các toạ độ đó về trong khoảng [0.0, 1.0] mà vẫn giữ nguyên tỉ lệ. Áp dụng những điều đó như thế nào để vẽ được hình vuông ở chính giữa canvas bây giờ ?
Hãy bắt đầu bằng đoạn code giả dưới đây có sử dụng lệnh `if` để kiểm tra từng ô một. Cách làm này cũng giống hệt như cách mà ta chọn các ô sẽ tô màu trên giấy.
```glsl
if ( (X > 1) AND (Y > 1) )
tô màu trắng
else
tô màu đen
```
Nhưng ta biết có một cách khác, tốt hơn, đó là dùng hàm [`step()`](../glossary/?lan=vi&search=step), và thay vì dùng kích thước 10x10 thì ta sẽ dùng các toạ độ đã được chuẩn hoá trong khoảng [0.0, 1.0]
```glsl
uniform vec2 u_resolution;
void main(){
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(0.0);
// Mỗi dòng dưới đây sẽ cho kết quả 1.0 hoặc 0.0
float left = step(0.1,st.x); // Tương đương với ( X > 0.1 )
float bottom = step(0.1,st.y); // Tương đương với ( Y > 0.1 )
// Phép nhân left*bottom tương đương với điều kiện logic AND
color = vec3( left * bottom );
gl_FragColor = vec4(color,1.0);
}
```
Hàm [`step()`](../glossary/?lan=vi&search=step) sẽ chọn màu đen (`vec3(0.0)`) cho tất cả các điểm ảnh có toạ độ dưới 0.1, và màu trắng (`vec3(1.0)`) cho tất cả các điểm ảnh còn lại. Phép nhân giữa `left``bottom` cũng tương đương với lệnh điều kiện logic `AND` tức là cả 2 giá trị phải bằng 1.0 thì kết quả của phép nhân mới bằng 1.0 được, các trường hợp khác đều cho kết quả 0.0. Đoạn code phía trên sẽ vẽ 2 đường thẳng màu đen, một đường ở phía dưới cùng của canvas, và đường còn lại nằm ở mép trái của canvas.
![](rect-01.jpg)
Trong đoạn code phía trên, ta phải sử dụng hàm [`step()`](../glossary/?lan=vi&search=step) hai lần, mỗi lần cho một trục. Để rút gọn hơn, ta có thể truyền cả 2 toạ độ vào hàm đó cùng lúc như sau:
```glsl
vec2 borders = step(vec2(0.1),st);
float pct = borders.x * borders.y;
```
Cho tới lúc này, ta mới chỉ vẽ được 2 đường viền ở dưới cùng và bên trái của hình vuông. Hãy vẽ nốt 2 đường viền còn lại ở phía trên và bên phải nào:
<div class="codeAndCanvas" data="rect-making.frag"></div>
Hãy uncomment *dòng 21-22*, bạn sẽ thấy tôi đã đảo ngược giá trị của toạ độ `st` rồi áp dụng y nguyên hàm [`step()`](../glossary/?lan=vi&search=step) như code cũ. Bằng phép đảo ngược này thì điểm `vec2(0.0,0.0)` sẽ đại diện cho góc trên cùng bên phải. Hay nói cách khác, tôi đã sử dụng phép chiếu đối xứng.
![](rect-02.jpg)
Chú ý: Ở *dòng 18 và 22* tất cả kết quả thu được từ 2 hàm `step()` được nhân với nhau. Đoạn code dưới đây cũng sẽ có tác dụng tương tự:
```glsl
vec2 bl = step(vec2(0.1),st); // Tô màu các ô ở đường viền dưới cùng và bên trái
vec2 tr = step(vec2(0.1),1.0-st); // Tô màu các ô ở đường viền trên cùng và bên phải
color = vec3(bl.x * bl.y * tr.x * tr.y);
```
Ô hay nhỉ ? Toàn bộ bài này chỉ dùng mỗi hàm [`step()`](../glossary/?lan=vi&search=step) rồi nhân kết quả lại với nhau, kèm theo phép chiếu đối xứng qua tâm.
Trước khi sang phần tiếp theo, hãy thử:
* Thay đổi kích thước của hình vuông / chữ nhật màu trắng
* Thử dùng hàm [`smoothstep()`](../glossary/?lan=vi&search=smoothstep) thay vì [`step()`](../glossary/?lan=vi&search=step). Chú ý là khi thay đổi giá trị, ta sẽ thấy các cạnh của hình chữ nhật sẽ thay đổi từ rõ nét sang mờ dần.
* Thử dùng cách khác để vẽ, ví dụ như hàm [`floor()`](../glossary/?lan=vi&search=floor).
* Chọn một cách vẽ bạn thích nhất và biến nó thành 1 hàm mà bạn có thể sử dụng lại trong tương lai. Cố gắng giữ cho hàm đó thật linh hoạt và hiệu quả nhé.
* Tạo một hàm khác chỉ vẽ mỗi đường viền thôi
* Nghĩ xem làm cách nào để đặt nhiều hình chữ nhật có màu sắc và kích thước khác nhau cùng vào trong một canvas nhé ? Nếu bạn nghĩ được cách rồi thì thử dựng lại bức tranh nổi tiếng của [Piet Mondrian](http://en.wikipedia.org/wiki/Piet_Mondrian) nhé.
![Bức 'Tableau ' của Piet Mondrian (1921)](mondrian.jpg)
### Hình tròn
Vẽ hình vuông trên tờ giấy kẻ ô sẵn hay vẽ hình chữ nhật trong hệ toạ độ Đề-các rất dễ, nhưng để vẽ hình tròn thì ta phải tìm cách khác, đặc biệt là phải chú ý tới từng điểm ảnh một. Một trong những giải pháp được đưa ra là *remap* toạ độ không gian về một hệ quy chiếu nào đó dựa trên khoảng cách tới tâm rồi dùng hàm [`step()`](../glossary/?lan=vi&search=step) để loại bỏ các điểm ảnh nằm quá xa tâm.
Quay lại với môn Toán nào, ta sẽ cần 1 tờ giấy có kẻ ô sẵn và 1 chiếc compa. Chỉ cần xoay 1 vòng compa là ta có ngay 1 đường tròn, giờ thì chỉ việc tô màu bên trong đường tròn là được.
![](compass.jpg)
Nếu coi mỗi ô vuông kẻ sẵn trên giấy là 1 điểm ảnh trên canvas, thì để áp dụng cách vẽ tương tự như trên vào trong shader, thực tế ta sẽ phải kiểm tra từng ô một xem nó có nằm bên trong chu vi đường tròn không. Ta có thể biết được điều này bằng cách tính khoảng cách của điểm ảnh đó tới tâm đường tròn.
![](circle.jpg)
Có vài cách để tính khoảng cách và cách dễ nhất là dùng hàm [`distance()`](../glossary/?lan=vi&search=distance), mà bên trong nó sẽ dùng hàm [`length()`](../glossary/?lan=vi&search=length) để tính độ dài đoạn thẳng nối giữa mỗi điểm ảnh với tâm hình tròn. Để tính độ dài 1 đoạn thẳng, hàm `length()` sẽ coi đoạn thẳng đó là [cạnh huyền của một tam giác](http://en.wikipedia.org/giác/Hypotenuse) rồi áp dụng định lý Pythagores là xong, và công thức đó cần tới hàm căn bậc 2 ([`sqrt()`](../glossary/?lan=vi&search=sqrt)).
![](hypotenuse.png)
Bạn có thể dùng hàm [`distance()`](../glossary/?lan=vi&search=distance), [`length()`](../glossary/?lan=vi&search=length) hay [`sqrt()`](../glossary/?lan=vi&search=sqrt) tuỳ ý để tính khoảng cách của từng điểm ảnh tới tâm canvas. Đoạn code dưới đây dùng cả 3 hàm trên và không có gì ngạc nhiên là kết quả của chúng đều giống nhau.
* Hãy comment và uncomment từng cách tính để kiểm tra xem có đúng là chúng cho ra cùng 1 kết quả không nhé
<div class="codeAndCanvas" data="circle-making.frag"></div>
Đoạn code trên minh hoạ khoảng cách từ mỗi điểm ảnh tới tâm canvas bằng màu sắc. Càng gần tâm thì màu càng tối. Ngay cả những điểm xa nhất thì cũng không sáng quá, vì giá trị tối đa chỉ có thể là `vec2(0.5, 0.5)`. Hãy quan sát hình minh hoạ đó và tự hỏi:
* Mình có thể suy luận những gì từ hình này ?
* Làm thế nào để biến nó thành hình tròn ?
* Sửa đoạn code trên để toàn bộ dải màu đen trắng nằm trong canvas thay vì chỉ có nửa tối như hiện tại
### Distance field
Nếu coi hình minh hoạ từ ví dụ trên là 1 bản đồ địa hình đo chiều cao, và vùng tối màu là vùng cao hơn, thì ta có thể tưởng tượng ra địa hình đó giống như hình nón. Ở mặt đất thì khoảng cách từ tâm tới mọi điểm khác cùng mặt phẳng đều là 0.5. Vậy nếu ta bổ ngang hình nón tại một độ cao nào đó, thì ta sẽ có 1 lát cắt hình tròn có bán kính tương ứng với độ cao. Càng gần mặt đất thì lát cắt càng lớn. Và ở chiều ngược lại, nếu ta chồng rất nhiều lát cắt hình tròn lên nhau theo thứ tự của bán kính thì ta cũng sẽ thu được 1 hình nón.
![](distance-field.jpg)
Kỹ thuật này được gọi là "distance field" (trường khoảng cách), và được ứng dụng rất nhiều, từ việc vẽ viền cho các phông chữ cho tới đồ hoạ 3 chiều.
Hãy thử:
* Dùng hàm [`step()`](../glossary/?lan=vi&search=step) tô màu trắng cho tất cả các điểm xa tâm hơn 0.5 đơn vị, các điểm ảnh còn lại thì màu đen.
* Đảo ngược màu của 2 vùng kể trên.
* Dùng hàm [`smoothstep()`](../glossary/?lan=vi&search=smoothstep), và điều chỉnh tham số sao cho thu được 1 đường viền có độ mờ mong muốn.
* Biến nó thành 1 hàm mà bạn có thể sử dụng lại trong tương lai. Cố gắng giữ cho hàm đó thật linh hoạt và hiệu quả nhé.
* Tô màu khác cho hình tròn.
* Bạn có thể làm cho hình tròn to ra rồi nhỏ lại theo nhịp tim không ? (Hãy lấy cảm hứng từ chuyển động của chương trước nhé).
* Vậy nếu muốn đặt hình tròn ở các vị trí khác nhau thay vì chỉ ở chính giữa thì sao ?
* Điều gì sẽ xảy ra nếu ta có nhiều hơn 1 distance field và kết hợp chúng lại bằng các hàm toán học cơ bản như dưới đây ?
```glsl
pct = distance(st,vec2(0.4)) + distance(st,vec2(0.6));
pct = distance(st,vec2(0.4)) * distance(st,vec2(0.6));
pct = min(distance(st,vec2(0.4)),distance(st,vec2(0.6)));
pct = max(distance(st,vec2(0.4)),distance(st,vec2(0.6)));
pct = pow(distance(st,vec2(0.4)),distance(st,vec2(0.6)));
```
* Hãy tạo ra ba hình cấu tạo từ các phép toán kể trên. Nếu nó chuyển động được thì càng tốt.
#### Tiện ích
Thực ra thì hàm [`sqrt()`](../glossary/?lan=vi&search=sqrt) và các hàm gián tiếp sử dụng nó nữa, khá là chậm. Đây là 1 cách khác để tính khoảng cách tới tâm của đường tròn bằng cách tính tích vô hướng của vector dựa vào hàm [`dot()`](../glossary/?lan=vi&search=dot).
<div class="codeAndCanvas" data="circle.frag"></div>
### Các thuộc tính hữu ích của Distance Field
![Vườn phong cách Thiền](zen-garden.jpg)
Các distance field có thể được kết hợp lại với nhau để vẽ hầu như tất cả mọi thứ. Rõ ràng là hình vẽ càng phức tạp thì phương pháp kết hợp chúng sẽ càng rối rắm, nhưng nếu bạn có 1 công thức để tính 1 distance field cho 1 khối hình học cơ bản nào đó rồi, thì việc tạo thêm hiệu ứng cho nó lại rất dễ, uốn cong các cạnh hay vẽ nhiều đường viền cùng lúc chẳng hạn. Chình vì điều này mà distance field là kỹ thuật rất phổ biến khi cần vẽ các ký tự của các phông chữ khác nhau, ví dụ như các phần mềm [Mapbox GL Labels](https://www.mapbox.com/blog/text-signed-distance-fields/), [Matt DesLauriers](https://twitter.com/mattdesl) [Material Design Fonts](http://mattdesl.svbtle.com/material-design-on-the-gpu) và [được mô tả chi tiết trong chương 7 của quyển sách iPhone 3D Programming của nhà xuất bản OReilly](http://chimera.labs.oreilly.com/books/1234000001814/ch07.html#ch07_id36000921).
Hãy xem đoạn code sau:
<div class="codeAndCanvas" data="rect-df.frag"></div>
Đầu tiên tôi đặt gốc của trục toạ độ ở tâm của canvas rồi *remap* toạ độ lại để chúng nằm trong khoảng [-1, 1]. Ở *dòng 24* tôi có minh hoạ giá trị của các distance field bằng hàm [`fract()`](../glossary/?lan=vi&search=fract) để bạn nhìn rõ tâm của các distance field và tạo nên nhiều đường tròn lồng nhau giống như những cái vòng trên cát trong một khu vườn Thiền vậy.
Hãy nhìn công thức tính distance field ở *dòng 19*. Tôi tính khoảng cách từ mỗi điểm tới vị trí `(.3,.3)` viết tắt là `vec3(.3)`, cho mỗi góc phần tư riêng biệt (hàm [`abs()`](../glossary/?lan=vi&search=abs) đã giúp tôi tách riêng 4 góc phần tư ra).
Nếu bạn uncomment *dòng 20*, bạn sẽ nhận thấy bằng cách dùng hàm [`min()`](../glossary/?lan=vi&search=min) tôi đã tạo ra hoa văn mới.
Hãy uncomment tiếp *dòng 21*; tôi dùng hàm [`max()`](../glossary/?lan=vi&search=max) thay cho hàm `min()` ở trên. Kết quả là thu được một hình chữ nhật có 4 góc được bo tròn. Hơn thế nữa, càng xa tâm thì 4 góc ngày càng được bo tròn hơn.
Cuối cùng, hãy lần lượt uncomment từng dòng *từ 27 tới 29* để thấy các phương pháp vẽ hoa văn khác nhau từ distance field.
### Polar shapes - Các hình được tạo ra từ hệ toạ độ cực
![Bức 'Untitled' của Robert Mangold (2008)](mangold.jpg)
Ở chương về màu sắc ta đã chuyển hệ quy chiếu từ hệ toạ độ Đề-các sang hệ toạ độ cực bằng cách tính *khoảng cách**góc* của mỗi điểm ảnh tới tâm bằng công thức sau:
```glsl
vec2 pos = vec2(0.5)-st;
float r = length(pos)*2.0;
float a = atan(pos.y,pos.x);
```
Ta cũng đã dùng một phần của công thức này để vẽ hình tròn, cụ thể là dùng hàm [`length()`](../glossary/?lan=vi&search=length) để tính khoảng cách tới tâm hình tròn. Với kiến thức về distance field, ta có thể áp dụng để vẽ nhiều hình khác trên hệ toạ độ cực.
Kỹ thuật này rất đơn giản, chỉ cần thay đổi khoảng cách tới tâm dựa vào góc nghiêng, là ta sẽ có các hình khác nhau. Nhưng từ góc nghiêng làm thế nào để tính ra khoảng cách mong muốn nhỉ ? Đương nhiên phải dùng hàm số rồi.
Dưới đây là đồ thị của các hàm số được vẽ trên hệ toạ độ Đề-các, còn ở đoạn code sau đó là vẽ trên hệ toạ độ cực *(từ dòng 21 tới 25)*. Hãy uncomment từng hàm số một và chú ý tìm xem mỗi điểm ở hệ toạ độ này nằm ở đâu trong hệ toạ độ kia.
<div class="simpleFunction" data="y = cos(x*3.);
//y = abs(cos(x*3.));
//y = abs(cos(x*2.5))*0.5+0.3;
//y = abs(cos(x*12.)*sin(x*3.))*.8+.1;
//y = smoothstep(-.5,1., cos(x*10.))*0.2+0.5;"></div>
<div class="codeAndCanvas" data="polar.frag"></div>
Hãy thử:
* Làm những hình này chuyển động.
* Kết hợp thêm vaì hàm số nữa để *đục lỗ* những hình trên, có thể sẽ tạo ra hình bông hoa, bông tuyết hay bánh răng.
* Sử dụng hàm `plot()` từ *Chương Các hàm số cơ bản* để chỉ vẽ mỗi đường viền thôi thay vì tô màu đặc.
### Kết hợp các kiến thức đã học
Ta đã học cách ánh xạ góc nghiêng của hệ toạ độ cực sang khoảng cách để vẽ các hình thú vị, giờ hãy thử áp dụng tất cả những gì đã học với hàm `atan()` xem sao.
Mẹo ở đây là ta sẽ dựng các distance field dựa vào số đỉnh của đa giác. Hãy xem [đoạn code này](http://thndl.com/square-shaped-shaders.html) của [Andrew Baldwin](https://twitter.com/baldand).
<div class="codeAndCanvas" data="shapes.frag"></div>
* Dựa vào ví dụ trên đây, hãy tạo một hàm nhận tham số đầu vào là vị trí của tâm và số đỉnh của đa giác để tính distance field nhằm dựng nên đa giác đó.
* Kết hợp các distance field bằng hàm [`min()`](../glossary/?lan=vi&search=min) và [`max()`](../glossary/?lan=vi&search=max).
* Vẽ lại một logo hình học nào đó bằng distance field
Xin chúc mừng! Bạn đã vượt qua được phần khó nhằn này rồi ! Hãy tạm nghỉ để suy ngẫm dần các khái niệm này. Vẽ các hình này bằng shader rất thú vị nhưng cũng rất loằng ngoằng.
Đây là URL tới [PixelSpirit Deck](https://patriciogonzalezvivo.github.io/PixelSpiritDeck/). Một bộ bài có các biểu tượng được tạo ra bởi các hàm SDF (Signed Distance Field). Bạn hãy thử vẽ lại các biểu tượng đó bằng shader nhé. Các hình vẽ sẽ khó dần lên đấy, thế nên cứ từ từ, mỗi ngày vẽ một hình cũng đủ để mài dũa kỹ năng của bạn về Distance Field ngày càng điêu luyện hơn rồi.
Ở chương tới, chúng ta sẽ học cách di chuyển, quay và thay đổi kích thước của các hình. Từ đó bạn có thể ghép chúng lại thành những hình phức tạp hơn.

@ -0,0 +1,101 @@
## Ma trận 2 chiều
<canvas id="custom" class="canvas" data-fragment-url="matrix.frag" width="700px" height="200px"></canvas>
### Chuyển động thẳng / tịnh tiến (Translate)
Ở chương trước ta đã học cách vẽ nhiều hình khác nhau. Để di chuyển tịnh tiến các hình đó, thay vì di chuyển chính hình vẽ, ta có thể di chuyển trục toạ độ. Rất đơn giản, chỉ cần cộng thêm 1 vector vào biến ```st``` lưu vị trí của mỗi fragment, sẽ khiến cho cả trục toạ độ di chuyển.
![](translate.jpg)
Nhìn trực tiếp thì sẽ dễ hơn giải thích, nên bạn hãy thử:
* Uncomment dòng 35 của đoạn code bên dưới
<div class="codeAndCanvas" data="cross-translate.frag"></div>
Thử sửa code tiếp nhé:
* Kết hợp biến ```u_time``` với các hàm số đã biết để di chuyển hình chữ thập. Hãy tìm một chuyển động nào đó mà bạn thích, sau đó hãy mô phỏng chuyển động đó cho hình chữ thập này. Thực tế có rất nhiều chuyển động thú vị ở quanh bạn: những con sóng liên tiếp vỗ bờ này, chuyển động của con lắc này, quả bóng nảy trên mặt đất này, ô tô tăng tốc này rồi thì cái xe đạp dừng lại nữa.
### Chuyển động quay
Để quay một vật thể ta cũng sẽ quay hệ trục toạ độ. Để làm việc đó, ta sẽ dùng tới [ma trận](http://en.wikipedia.org/wiki/Matrix_%28mathematics%29). Ma trận gồm các số được xếp thành nhiều hàng và cột. Phép nhân một vector với một ma trận phải tuân thủ quy tắc một cách chính xác về thứ tự phép tính.
[![Ma trận trên Wikipedia](matrixes.png)](https://en.wikipedia.org/wiki/Matrix)
GLSL có sẵn một vài ma trận có kích thước khác nhau: [```mat2```](../glossary/?lan=vi&search=mat2) (2x2), [```mat3```](../glossary/?lan=vi&search=mat3) (3x3) và [```mat4```](../glossary/?lan=vi&search=mat4) (4x4). GLSL cũng có sẵn phép nhân ma trận thông thường (toán tử ```*```) và cả phép nhân 2 ma trận theo từng cặp phần tử nữa ([```matrixCompMult()```](../glossary/?lan=vi&search=matrixCompMult)).
Ta có thể sử dụng ma trận để làm vài thứ hay ho. Ví dụ ta có thể dùng nó để tịnh tiến một vector:
![](3dtransmat.png)
Và hay hơn nữa, ta còn có thể dùng nó để xoay trục toạ độ:
![](rotmat.png)
Đoạn code dưới đây khởi tạo một ma trận 2 chiều. Hàm này áp dụng [công thức ở hình trên](http://en.wikipedia.org/wiki/Rotation_matrix) để xoay một vector 2 chiều quanh gốc toạ độ ```vec2(0.0)```.
```glsl
mat2 rotate2d(float _angle){
return mat2(cos(_angle),-sin(_angle),
sin(_angle),cos(_angle));
}
```
Thực ra thì xoay 1 điểm quanh gốc toạ độ không phải là điều mà ta muốn, ta cần nó xoay quanh tâm của chính nó cơ. Và để làm điều đó, ta sẽ chữa mẹo như sau: đầu tiên di chuyển hình chữ thập về đúng gốc toạ độ để tâm của hình chữ thập trùng vị trí với gốc toạ độ, sau đó thực hiện phép quay, cuối cùng di chuyển hình chữ thập đã quay về lại vị trí cũ.
![](rotate.jpg)
Code sẽ trông như sau:
<div class="codeAndCanvas" data="cross-rotate.frag"></div>
Hãy thử:
* Uncomment dòng 45 xem chuyện gì xảy ra.
* Comment đoạn code thực hiện việc di chuyển hình chữ thập trước và sau khi thực hiện phép quay, ở dòng 37 và 39, xem chuyện gì xảy ra.
* Kết hợp phép quay với chuyển động tịnh tiến ở phần trước.
### Phép thu phóng
Ta đã thấy cách mà ma trận được sử dụng để di chuyển và quay vật thể trong không gian. Nếu bạn đã từng sử dụng các phần mềm tạo mô hình 3D hay sử dụng tính năng push và pop ma trận trong Processing, thì bạn sẽ biết rằng ma trận còn có thể phóng to thu nhỏ vật thể nữa.
![](scale.png)
Từ công thức thu phóng 3 chiều trên đây, ta có thể suy luận ra cách tạo nên ma trận thu phóng 2 chiều:
```glsl
mat2 scale(vec2 _scale){
return mat2(_scale.x,0.0,
0.0,_scale.y);
}
```
<div class="codeAndCanvas" data="cross-scale.frag"></div>
Hãy thử làm như sau để có thể hiểu sâu hơn cách ma trận thu phóng hoạt động:
* Uncomment dòng 42 để thấy rõ rằng cả hệ toạ độ được thu phóng chứ không phải chỉ mỗi hình chữ thập.
* Xem điều gì xảy ra nếu comment 2 dòng code di chuyển trước và sau khi thu phóng (37 và 39).
* Thử kết hợp ma trận quay với ma trận thu phóng. Chú ý rằng thứ tự thực hiện sẽ ảnh hưởng tới kết quả sau cùng. Hãy nhân các ma trận trước rồi nhân với vector sau cùng.
* Giờ thì không những bạn biết cách vẽ các hình rồi, mà còn biết cách di chuyển, quay và phóng to thu nhỏ chúng nữa. Hãy kết hợp nhiều hình lại để tạo nên [một giao diện ảo](https://www.pinterest.com/patriciogonzv/huds/). Bạn có thể tham khảo [ví dụ sau](https://www.shadertoy.com/user/ndel).
<iframe width="800" height="450" frameborder="0" src="https://www.shadertoy.com/embed/4s2SRt?gui=true&t=10&paused=true" allowfullscreen></iframe>
### Công dụng khác của ma trận: Không gian màu YUV
[YUV](http://en.wikipedia.org/wiki/YUV) là không gian màu sử dụng cách mã hoá analog truyền thống của ảnh về video, chỉ quan tâm tới dải màu mà con người có thể nhìn thấy được để thu gọn dữ liệu cần lưu trữ.
Đoạn code dưới đây sử dụng ma trận để chuyển đổi màu từ không gian này sang một không gian khác.
<div class="codeAndCanvas" data="yuv.frag"></div>
Bạn có thể thấy tôi thao tác với màu tương tự như với các vector khi đem chúng nhân với các ma trận.
Trong chương này ta đã học cách biến đổi ma trận để di chuyển, quay và thu phóng các vector. Các phép biến đổi này rất cần thiết để có thể kết hợp các hình mà ta đã học ở các chương trước. Ở chương tiếp theo, ta sẽ áp dụng tất cả những kiến thức này để tạo nên các hoa văn tuyệt đẹp. Bạn sẽ thấy việc đó ngầu thế nào cho mà xem.

@ -0,0 +1,117 @@
## Mẫu hoa văn
Vì các chương trình shader được tính toán cho từng pixel nên dù bạn có vẽ một hình bao nhiêu lần thì cũng không ảnh hưởng tới số phép tính. Điều đó đặc biệt thích hợp nếu ta dùng shader để vẽ các mẫu hoa văn hoạ tiết lặp lại.
[ ![Nina Warmerdam - The IMPRINT Project (2013)](warmerdam.jpg) ](../edit.php#09/dots5.frag)
Ở chương này ta sẽ dùng hết tất cả những gì đã học để vẽ rồi lặp lại hình vẽ đó ra toàn bộ canvas. Cũng giống như các chương trước, ta sẽ vẽ hình trong một khu vực khoảng [0.0, 1.0] trên hệ toạ độ, rồi tạo bản sao của các khu vực đó để tạo nên một lưới các hình vẽ lặp lại.
*"Hệ thống lưới tạo điều kiện để loài người khám phá và phát minh. Trong thiên nhiên hoang dã, các hoa văn giúp ta nhận biết dễ hơn. Người xưa đã dùng các hoa văn từ rất sớm để trang trí, từ các hoạ tiết trên đồ gốm tới các hoạ tiết khảm hình học trong các nhà tắm La Mã."* [*10 PRINT*, Mit Press, (2013)](http://10print.org/)
Trước hết tôi sẽ nhắc lại hàm [```fract()```](../glossary/?lan=vi&search=fract). Nó trả về phần thập phân của số thực, tương đương với phép tính phần dư khi chia cho 1 ([```mod(x,1.0)```](../glossary/?lan=vi&search=mod)). Nói cách khác, hàm [```fract()```](../glossary/?lan=vi&search=fract) trả về phần lẻ sau dấu thập phân. Hệ toạ độ của ta thì đã nằm trong khoảng [0.0, 1.0] rồi nên dùng hàm ```fract()``` trong khoảng này chẳng có ý nghĩa gì:
```glsl
void main(){
vec2 st = gl_FragCoord.xy/u_resolution;
vec3 color = vec3(0.0);
st = fract(st);
color = vec3(st,0.0);
gl_FragColor = vec4(color,1.0);
}
```
Nhưng nếu ta tăng kích thước của hệ toạ độ lên - gấp 3 lần chẳng hạn - thì cũng với đoạn code trên, ta sẽ thu được 3 đoạn biến thiên tuyến tính giống nhau: đoạn đầu tiên nằm trong khoảng 0-1, đoạn thứ hai nằm trong khoảng 1-2 và đoạn cuối nằm trong khoảng 2-3.
<div class="codeAndCanvas" data="grid-making.frag"></div>
Vậy là ta đã có 1 lưới 3x3 rồi, hãy coi mỗi ô là một không gian thu nhỏ. Bạn hãy uncomment dòng 27 để vẽ một hình gì đó ở mỗi không gian thu nhỏ nhé. (Do ta đã giãn cả 2 trục x và y với cùng 1 tỉ lệ nên các hình vẽ sẽ không bị bóp méo gì cả). *Chú ý*: Đoạn code không hề dùng vòng lặp.
Hãy thử sửa code như sau để hiểu rõ hơn:
* Giãn 2 trục x và y với 1 tỉ lệ khác, thậm chí là số thực và mỗi trục 1 tỉ lệ khác nhau.
* Tạo 1 hàm để sử dụng lại sau này cho mẹo lặp hoạ tiết này.
* Chia lưới thành 3 hàng và 3 cột. Tìm cách nhận biết từng hàng và cột đang được xử lý để có thể tuỳ ý thay đổi hình vẽ trong mỗi ô. Thử vẽ một bàn cờ caro xem sao.
### Apply matrices inside patterns
Vì mỗi ô là một hệ toạ độ thu nhỏ, ta có thể áp dụng các phép biến đổi ma trận để di chuyển, quay và thu phóng từng ô.
<div class="codeAndCanvas" data="checks.frag"></div>
* Hãy nghĩ cách làm cho hoạ tiết này chuyển động. Thử thay đổi màu sắc, hình dáng và cả chuyển động xem sao. Hãy tạo 3 animation riêng biệt.
* Tạo các hoạ tiết phức tạp hơn bằng cách kết hợp nhiều hình cơ bản.
[![](diamondtiles-long.png)](../edit.php#09/diamondtiles.frag)
* Hãy kết hợp nhiều lớp hoạ tiết chồng lên nhau để tạo nên hoạ tiết [kẻ ca-rô Tartan đặc trưng của Scotland](https://www.google.com/search?q=scottish+patterns+fabric&tbm=isch&tbo=u&source=univ&sa=X&ei=Y1aFVfmfD9P-yQTLuYCIDA&ved=0CB4QsAQ&biw=1399&bih=799#tbm=isch&q=Scottish+Tartans+Patterns).
[ ![Hoạ tiết kẻ ca-rô Tartan kiểu Scotland tạo bởi Kavalenkava](tartan.jpg) ](http://graphicriver.net/item/vector-pattern-scottish-tartan/6590076)
### Hoạ tiết so le
Giả sử ta muốn tạo bên một bức tường gạch. Hãy nhìn ảnh dưới đây và bạn sẽ thấy các hàng gạch được xếp so le với nhau. Làm thế nào ta có thể vẽ được hình đó ?
![](brick.jpg)
Đầu tiên ta cần xác định được tính chẵn lẻ của mỗi hàng, thì mới biết có cần dịch chuyển hàng đó để tạo nên hoạ tiết so le không.
Để biết hàng đang vẽ là chẵn hay lẻ, ta sẽ dùng hàm tính phần dư ([```mod()```](../glossary/?lan=vi&search=mod)) khi chia cho ```2.0```. Nếu là phép chia hết thì đó là số chẵn, ngược lại là số lẻ. Hãy uncomment 2 dòng cuối của đoạn code dưới đây.
<div class="simpleFunction" data="y = mod(x,2.0);
// y = mod(x,2.0) < 1.0 ? 0. : 1. ;
// y = step(1.0,mod(x,2.0));"></div>
Bạn có thể thấy tôi đã dùng [toán tử ba ngôi (ternary)](https://en.wikipedia.org/wiki/%3F:) để kiểm tra xem hàm [```mod()```](../glossary/?lan=vi&search=mod) khi chia cho ```2.0``` thì phần dư có nhỏ hơn ```1.0``` không. Dòng cuối cùng, thì tôi chọn cách khác mà vẫn cho ra kết quả tương tự, đó là dùng hàm [```step()```](../glossary/?lan=vi&search=step), cách này nhanh hơn. Tại sao lại thế ? Thực ra thì khó mà biết được card đồ hoạ biên dịch và tối ưu code ra sao, nhưng nhiều khả năng là các hàm có sẵn sẽ nhanh hơn các hàm ta tự viết. Còn trong trường hợp này thì `step()` nhanh hơn vì nó được tối ưu bằng phần cứng, còn các lệnh điều kiện thì rất chậm trên các card đồ hoạ. Vì thế nếu có thể hãy cố gắng dùng các hàm có sẵn và hạn chế dùng các lệnh điều kiện.
Giờ thì ta có công thức tìm ra các hàng lẻ rồi nên chỉ cần dịch chuyển các viên gạch một nửa đơn vị theo trục ```x``` trên những hàng này thì sẽ tạo nên hoạ tiết so le thôi. Dòng 14 trong đoạn code dưới đây thực hiện đúng như vậy. Tuy nhìn qua thì thấy ta cũng áp dụng dịch chuyển cho cả các hàng chẵn nữa, nhưng vì kết quả phép tính phần dư bằng ```0``` cho các hàng này nên thành ra các viên gạch không bị dịch chuyển chút nào và vẫn giữ nguyên vị trí như ta mong muốn.
Hãy thử uncomment dòng 32 nhé, đó là dòng code tôi dùng để kéo giãn 2 trục theo tỉ lệ khác nhau để tạo nên hình viên gạch. Uncomment tiếp dòng 40, bạn sẽ thấy hệ trục toạ độ của mỗi ô lưới được minh hoạ bằng màu đỏ và xanh lá.
<div class="codeAndCanvas" data="bricks.frag"></div>
* Hãy thử làm các hàng gạch chuyển động theo thời gian nhé
* Hãy chuyển động các hàng chẵn sang bên trái còn các hàng lẻ sang bên phải thử xem.
* Nếu phải chuyển sang dịch các cột so le nhau thì bạn làm được không ?
* Kết hợp dịch chuyển cả 2 trục toạ độ để tạo nên chuyển động như dưới đây:
<a href="../edit.php#09/marching_dots.frag"><canvas id="custom" class="canvas" data-fragment-url="marching_dots.frag" width="520px" height="200px"></canvas></a>
## Hoạ tiết Truchet
Ta có thể nhận biết từng ô ở hàng chẵn hay lẻ, ở cột chẵn hay lẻ, nên có thể tuỳ ý xử lý hình vẽ mỗi ô dựa vào tính chẵn lẻ này. [Hoạ tiết Truchet](http://en.wikipedia.org/wiki/Truchet_tiles) chỉ cần 1 hình vẽ nhưng chỉ cần quay 90 độ là có thêm phiên bản nữa.
![](truchet-00.png)
Bằng cách quay từng ô, ta có thể tạo nên vô số thiết kế khác nhau.
![](truchet-01.png)
Hãy chú ý vào hàm ```rotateTilePattern()``` dưới đây, nó chia mỗi ô thành 4 ô nhỏ hơn để chứa cả 4 phiên bản của hoạ tiết Truchet.
<div class="codeAndCanvas" data="truchet.frag"></div>
* Hãy comment, uncomment và tạo bản sao của các dòng 69 và 72 để tạo nên các thiết kế mới.
* Hãy thay hoạ tiết gốc gồm 2 hình tam giác đen trắng bằng các hoạ tiết khác như: 2 nửa hình tròn, hình thoi, đường thẳng.
* Tạo nên các hoạ tiết mà mỗi ô xác định góc xoay dựa vào vị trí của chính ô đó
* Tạo nên các hoạ tiết có các thuộc tính khác nhau dựa vào vị trí của chính ô đó
* Áp dụng các kỹ thuật học được ở chương này để tạo ra thứ gì đó không phải hoạ tiết (Ví dụ: quẻ bói Kinh Dịch)
<a href="../edit.php#09/iching-01.frag"><canvas id="custom" class="canvas" data-fragment-url="iching-01.frag" width="520px" height="200px"></canvas></a>
## Tự thiết kế
Tạo nên các hoạ tiết lặp lại là một bài tập tốt cho trí não khi phải tìm cách tái sử dụng một số lượng rất nhỏ các hoạ tiết ban đầu. Con người đã dùng kỹ thuật này từ rất lâu rồi, từ việc trang trí hoạ tiết trên những tấm vải, cho tới các sàn gạch được lát tỉ mỉ; từ các đường viền Hy Lạp cổ tới các thiết kế cửa Trung Quốc, sự hoà quyện của các hoạ tiết dù lặp lại mà vẫn đa dạng kích thích trí tưởng tượng của con người. Hãy dành chút thời gian để ngắm nhìn các [hoạ tiết](https://www.pinterest.com/patriciogonzv/paterns/) [trang trí](https://archive.org/stream/traditionalmetho00chririch#page/130/mode/2up) mà các hoạ sỹ đã sử dụng để che giấu phần lặp lại và làm ta ngạc nhiên với những sự sắp đặt đầy bất ngờ. Từ các hoạ tiết hình học Ả Rập, tới các hoa văn trên vải của Châu Phi, có cả một vũ trụ các hoa văn hoạ tiết mà ta có thể khám phá và học hỏi.
![Quyển 'A handbook of ornament' của Franz Sales Meyer (1920)](geometricpatters.png)
Đây cũng là chương cuối của phần Các thuật toán vẽ hình. Ở các chương tiếp theo ta sẽ học cách đưa một ít xáo trộn từ entropy vào shader để tạo nên những hình ảnh độc nhất, không thể đoán trước.

@ -0,0 +1,92 @@
# Các thiết kế ngẫu nhiên
Sẽ không quá bất ngờ nếu ta đưa thêm một chút phá cách vào thiết kế sau khi đã vẽ rất nhiều họa tiết lặp lại mang nặng tính sắp đặt.
## Ngẫu nhiên
[![Họa tiết test - Ryoji Ikeda (2008) ](ryoji-ikeda.jpg) ](http://www.ryojiikeda.com/project/testpattern/#testpattern_live_set)
Ngẫu nhiên là kết quả của của entropy, sau một quá trình vận động tự do. Làm thế nào để ta đưa được sự ngẫu nhiên đó vào trong các khung hình dễ đoán vẫn vẽ trước giờ ?
Hãy cùng phân tích đoạn code sau:
<div class="simpleFunction" data="y = fract(sin(x)*1.0);"></div>
Tôi tách phần thập phân của sóng sin ra. Hàm [```sin()```](../glossary/?lan=vi&search=sin) cho kết quả nằm trong khoảng [-1.0, 1.0], đã trở thành nhiều khoảng gián đoạn có giá trị trong phạm vi [0.0, 1.0]. Đồ thị này khá là khó để dự đoán với nhiều người, vì thế ta có thể tận dụng để làm giả sự ngẫu nhiên, đặc biệt là khi ta giãn đồ thị trên theo trục tung bằng cách nhân kết quả từ hàm [```sin(x)```](../glossary/?lan=vi&search=sin) với một số lớn. Hãy thử sửa code phía trên để chứng kiến tận mắt nhé.
Khi bạn tăng dần con số lên tới mức ```100000.0``` (công thức sẽ nhìn như sau: ```y = fract(sin(x)*100000.0)```), bạn sẽ không thể nhìn ra chu kỳ của sóng sin nữa. Chính sự gián đoạn của phần thập phân đã bẻ gãy đường cong liên tục của sóng sin và tạo nên sự hỗn loạn, nhìn như chúng được phân bổ một cách ngẫu nhiên vậy.
## Ngẫu nhiên có kiểm soát
Sử dụng cơ chế ngẫu nhiên không hề đơn giản; đôi khi nó không đủ ngẫu nhiên, có lúc lại quá hỗn loạn. Hãy nhìn đồ thị dưới đây, nó được vẽ bằng cách dùng hàm ```rand()```, vốn sử dụng chính công thức phía trên để tạo ra sự ngẫu nhiên.
Nếu nhìn kỹ, bạn sẽ thấy có khá nhiều điểm hội tụ ở ```-1.5707``` và ```1.5707```. Tôi cá là bạn biết tại sao - đó chính là vị trí các đỉnh sóng sin.
Nhìn kỹ hơn nữa, bạn sẽ thấy ở giữa tập trung nhiều hơn phía trên và phía dưới.
<div class="simpleFunction" data="y = rand(x);
//y = rand(x)*rand(x);
//y = sqrt(rand(x));
//y = pow(rand(x),5.);"></div>
Cách đây vài năm, [Pixelero](https://pixelero.wordpress.com) đã đăng một [bài báo thú vị về phân bổ ngẫu nhiên](https://pixelero.wordpress.com/2008/04/24/various-functions-and-various-distributions-with-mathrandom/). Tôi có đặt vài hàm mà ông mô tả trong bài báo vào đoạn code phía trên. Hãy thử dùng các hàm đó để xem phân bố ngẫu nhiên thay đổi như thế nào nhé.
Nếu bạn đọc [bài báo của Pixelero](https://pixelero.wordpress.com/2008/04/24/various-functions-and-various-distributions-with-mathrandom/), thì hãy nhớ là hàm ```rand()``` không thật sự ngẫu nhiên, nó được gọi là ngẫu nhiên giả (pseudo-random). Điều đó có nghĩa là nếu ta gọi hàm ```rand(1.)``` thì nó sẽ luôn trả về cùng 1 giá trị duy nhất. [Pixelero](https://pixelero.wordpress.com/2008/04/24/various-functions-and-various-distributions-with-mathrandom/) có nhắc tới hàm ```Math.random()``` của ActionScript, là hàm ngẫu nhiên không xác định, mỗi lần gọi hàm này sẽ cho một kết quả khác nhau.
## Ngẫu nhiên hai chiều
Ta đã hiểu thêm một chút về việc sinh số ngẫu nhiên từ một số khác, giờ hãy thử tạo ra các số ngẫu nhiên từ một cặp số xem sao. Để làm được việc đó, ta cần một cách mã hoá vector 2 chiều thành 1 số thực. Có rất nhiều cách để làm việc này, nhưng tôi thấy hàm [```dot()```](../glossary/?lan=vi&search=dot) rất tiện trong việc này. Kết quả của phép tích vô hướng sẽ nằm trong khoảng [0.0, 1.0], tuỳ vào góc giữa 2 vector.
<div class="codeAndCanvas" data="2d-random.frag"></div>
Hãy xem các dòng 13 và 15, bạn sẽ thấy tôi kết hợp ```vec2 st``` với một vector khác ( ```vec2(12.9898,78.233)```).
* Thử thay đổi các giá trị ở hai dòng 14 và 15 xem các điểm ngẫu nhiên thay đổi thế nào.
* Thử kết hợp hàm ngẫu nhiên này với vị trí con trỏ chuột (```u_mouse```) và thời gian (```u_time```) để hiểu rõ hơn.
## Tận dụng sự ngẫu nhiên
Ngẫu nhiên 2 chiều trông khá giống màn hình TV bị nhiễu phải không ? Nó là thành phần quan trọng trong việc tạo ra các hình ảnh đấy. Cùng tìm hiểu xem làm như vậy bằng cách nào nhé.
Bước đầu tiên là áp dụng vào một lưới; dùng hàm [```floor()```](../glossary/?lan=vi&search=floor) để tạo ra một lưới mà mỗi ô ta sẽ chỉ tô đúng 1 màu. Hãy xem đoạn code dưới đây, đặc biệt là dòng 22 và 23.
<div class="codeAndCanvas" data="2d-random-mosaic.frag"></div>
Sau khi chia canvas thành lưới 10x10 (dòng 21), ta có thể coi mỗi ô có 1 tọa độ riêng nằm trong khoảng [0.0, 1.0]. Phần này thì quen thuộc quá rồi thì ta đã làm tương ở chương trước. Để tô cùng một màu cho toàn bộ các điểm ảnh trong mỗi ô đó, ta sẽ chỉ dùng phần nguyên của tọa độ thay vì dùng cả phần thập phân. Sử dụng phần nguyên đó làm tham số cho hàm ngẫu nhiên, ta sẽ được một số khác, chính là màu mà ta sẽ tô cho các điểm ảnh trong từng ô. Và chính vì hàm ngẫu nhiên của ta được tính bằng công thức chung, nên tất cả điểm ảnh trong một ô sẽ có màu giống nhau.
Uncomment dòng 29 để kiểm chứng, mỗi ô vẫn là một "không gian con" có đầy đủ thông tin của từng điểm ảnh.
Kết hợp phần nguyên và phần thập phân, ta sẽ tạo ra vô vàn phiên bản ngẫu nhiên có kiểm soát.
Hãy xem đoạn code sinh ra mê cung dưới đây, nó sử dụng công thức nổi tiếng ```10 PRINT CHR$(205.5+RND(1)); : GOTO 10```.
<div class="codeAndCanvas" data="2d-random-truchet.frag"></div>
Ở đây tôi dùng các số ngẫu nhiên để vẽ những đường chéo, bằng cách sử dụng lại hàm ```truchetPattern()```, có tác dụng vẽ các họa tiết Truchet từ chương trước (dòng từ 41 tới 47).
Một họa tiết khác cũng thú vị không kém sẽ hiện ra khi bạn uncomment các dòng từ 50 tới 53, thậm chí họa tiết sẽ chuyển động khi bạn uncomment dòng 35 và 36.
## Sử dụng thành thạo sự ngẫu nhiên
[Ryoji Ikeda](http://www.ryojiikeda.com/), một nhạc sỹ nhạc điện tử và là một nghệ sỹ hiệu ứng thị giác, đã thành thục kỹ năng sử dụng sự ngẫu nhiên; sẽ thật khó để không xúc động và bị mê hoặc khi xem các tác phẩm của ông. Cách mà ông đưa sự ngẫu nhiên vào trong các tác phẩm âm nhạc và thị giác đã được mài dũa để không đem lại cảm giác lộn xộn mà vẫn phản ánh được sự phức tạp trong quá trình phát triển của văn hoá công nghệ.
<iframe src="https://player.vimeo.com/video/76813693?title=0&byline=0&portrait=0" width="800" height="450" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
Hãy chiêm ngưỡng các tác phẩm của [Ikeda](http://www.ryojiikeda.com/) và thử:
* Vẽ các mã vạch di chuyển ngược chiều nhau với vận tốc thay đổi theo thời gian.
<a href="../edit.php#10/ikeda-00.frag"><canvas id="custom" class="canvas" data-fragment-url="ikeda-00.frag" width="520px" height="200px"></canvas></a>
* Tương tự như trên, hãy vẽ nhiều hàng hơn nữa, vận tốc đa dạng hơn nữa. Sử dụng tọa độ X của con trỏ chuột làm ngưỡng thập phân để ẩn hiện các ô tuỳ ý.
<a href="../edit.php#10/ikeda-03.frag"><canvas id="custom" class="canvas" data-fragment-url="ikeda-03.frag" width="520px" height="200px"></canvas></a>
* Tạo bất kỳ hiệu ứng hay ho nào mà bạn muốn
<a href="../edit.php#10/ikeda-04.frag"><canvas id="custom" class="canvas" data-fragment-url="ikeda-04.frag" width="520px" height="200px"></canvas></a>
Làm thế nào để ứng dụng sự ngẫu nhiên vào trong mỹ thuật rất khó, đặc biệt khi bạn muốn mô phỏng các hình dáng và chuyển động tự nhiên. Bản thân sự ngẫu nhiên sẽ quá hỗn loạn và có rất ít sự vật tự nhiên trông giống như vậy. Nếu không tin bạn cứ thử quan sát các giọt mưa hoặc bảng giá cổ phiếu trên sàn chứng khoán mà xem, chúng cũng khá ngẫu nhiên đó, nhưng không hề giống như các họa tiết lộn xộn mà ta vẽ ở đầu chương này đâu. Tại sao à ? Vì các giá trị ngẫu nhiên mà ta dùng không có sự liên quan, còn trong tự nhiên thì trạng thái tại một thời điểm của sự vật chịu ảnh hưởng từ trạng thái tại thời điểm trước đó.
Trong chương kế tiếp ta sẽ học về nhiễu, một cách để tạo ra sự hỗn loạn có kiểm soát và trông tự nhiên hơn.

@ -0,0 +1,225 @@
![Nhóm nghiên cứu WMAP - NASA](mcb.jpg)
## Nhiễu
Sau một hồi vật lộn với các hàm ngẫu nhiên nhìn như màn hình TV hỏng, ít nhiều thì ta cũng sẽ quay cuồng và hoa mắt, giờ thì cùng nghỉ ngơi và dạo chơi tí nhé.
Ta cảm nhận được gió thổi qua da, nắng ấm rọi lên mặt. Thế giới quả là sống động. Màu sắc, họa tiết rồi cả âm thanh nữa. Hãy nhìn bề mặt của những con đường, hòn đá, ngọn cây và cả những đám mây nữa.
![](texture-00.jpg)
![](texture-01.jpg)
![](texture-02.jpg)
![](texture-03.jpg)
![](texture-04.jpg)
![](texture-05.jpg)
![](texture-06.jpg)
họa tiết khó đoán của những bề mặt này có thể coi là "ngẫu nhiên", nhưng tuyệt nhiên không giống như sự lộn xộn ở chương trước. Thế giới thực quả là rất phức tạp ! Làm sao để ta lột tả được sự đa dạng đó bằng máy tính đây ?
Đó cũng chính là câu hỏi mà [Ken Perlin](https://mrl.nyu.edu/~perlin/) đã đi tìm câu trả lời suốt những năm đầu thập niên 80, khi ông chịu trách nhiệm tạo ra các họa tiết tự nhiên hết mức có thể cho bộ phim "Tron". Và kết quả là ông đã nhận được một *giải Oscar* với thuật toán tạo nhiễu của mình.
![Tron - Disney (1982)](tron.jpg)
Đoạn code dưới đây không hẳn là thuật toán tạo nhiễu Perlin cổ điển, nhưng là một bước đệm tốt để bắt đầu.
<div class="simpleFunction" data="
float i = floor(x); // phần nguyên
float f = fract(x); // phần thập phân
y = rand(i); // Hàm rand() được mô tả ở chương trước
//y = mix(rand(i), rand(i + 1.0), f);
//y = mix(rand(i), rand(i + 1.0), smoothstep(0.,1.,f));
"></div>
Trông na ná như những gì ta đã làm ở chương trước. Ta tách riêng phần nguyên và phần thập phân của số thực ```x``` vào hai biến ```i``` và ```f``` bằng hai hàm [```floor()```](../glossary/?lan=vi&search=floor) và [```fract()```](../glossary/?lan=vi&search=fract). Sau đó chỉ sinh số ngẫu nhiên từ phần nguyên, kết quả sẽ giống nhau cho dù bạn có chạy bao nhiêu lần đi chăng nữa.
Hai dòng cuối đã bị comment đi. Dòng đầu tiên sẽ nội suy tuyến tính 2 giá trị ngẫu nhiên ở 2 ô liên tiếp.
```glsl
y = mix(rand(i), rand(i + 1.0), f);
```
Hãy uncomment xem đồ thị thay đổi thế nào. Tôi dùng chính phần thập phân có được từ hàm [```fract()```](../glossary/?lan=vi&search=fract) và lưu trong biến `f` phía trên, để quyết định tỉ lệ nội suy giữa 2 số dùng hàm [```mix()```](../glossary/?lan=vi&search=mix).
Nếu bạn đã đọc tới đây, hẳn là bạn đã biết chúng ta có những cách nội suy khác ngoài tuyến tính phải không ?
Hãy thử uncomment dòng cuối cùng trong đoạn phía trên, tôi đã dùng hàm [```smoothstep()```](../glossary/?lan=vi&search=smoothstep) thay cho nội suy tuyến tính đó.
```glsl
y = mix(rand(i), rand(i + 1.0), smoothstep(0.,1.,f));
```
Bạn sẽ thấy các đỉnh bớt nhọn hơn. Ở rất nhiều thuật toán sinh nhiễu, bạn sẽ để ý thấy các lập trình viên thường sử dụng đường cong của riêng họ (ví dụ công thức dưới đây), thay vì dùng hàm [```smoothstep()```](../glossary/?lan=vi&search=smoothstep).
```glsl
float u = f * f * (3.0 - 2.0 * f ); // công thức đường cong bậc 3 với tham số riêng
y = mix(rand(i), rand(i + 1.0), u); // dùng kết quả để xác định tỉ lệ nội suy
```
*Sự ngẫu nhiên có chuyển tiếp rất êm* này thực sự là một cuộc cách mạng cho kỹ sư đồ hoạ và cả hoạ sỹ nữa - nó cho ta khả năng sinh ra các ảnh và hình khối có cảm giác hết sức tự nhiên. Thuật toán sinh nhiễu của Perlin (Perlin Noise) đã được code đi code lại trên các ngôn ngữ lập trình khác nhau để tạo nên vô vàn tác phẩm mê hoặc có tính sáng tạo cao.
![Tác phẩm 'Written Images' - Robert Hodgin (2010)](robert_hodgin.jpg)
Giờ tới lượt bạn:
* Hãy tự tạo một hàm ```float noise(float x)``` của riêng mình.
* Dùng chính hàm sinh nhiễu đó để di chuyển, quay hoặc thu phóng một hình vẽ.
* Kết hợp nhiều hình để xem chúng nhảy múa trên màn hình.
* Vẽ một sinh vật gì đó "trông tự nhiên".
* Khi đã có sinh vật của mình rồi, hãy thổi hồn cho nó bằng cách thêm 1 chuyển động nhé.
## Nhiễu 2 chiều
![](02.png)
Trên đây là cách tạo nhiễu 1 chiều, giờ hãy thử 2 chiều xem sao. Thay vì nội suy giữa 2 điểm ```fract(x)``` và ```fract(x)+1.0```, ở không gian 2 chiều, ta sẽ nội suy giữa 4 đỉnh của một tứ giác (```fract(st)```, ```fract(st)+vec2(1.,0.)```, ```fract(st)+vec2(0.,1.)``` và ```fract(st)+vec2(1.,1.)```).
![](01.png)
Tương tự như vậy, nếu ta muốn sinh nhiễu 3 chiều thì sẽ phải nội suy giữa 8 đỉnh của một khối lập phương (có thể vát). Kỹ thuật này hoàn toàn dựa trên phép nội suy giữa các điểm ngẫu nhiên cố định (value), nó được gọi là **value noise**.
![](04.jpg)
Và cũng giống như phần sinh nhiễu 1 chiều, nếu sử dụng hàm nội suy bậc 3 (cubic) thay vì tuyến tính, thì ta sẽ thu được nhiễu rất mịn.
![](05.jpg)
Hãy xem hàm sinh nhiễu dưới đây:
<div class="codeAndCanvas" data="2d-noise.frag"></div>
Đầu tiên ta tạo lưới 5x5 (dòng 45) để nhìn rõ vùng chuyển tiếp giữa các ô. Trong hàm sinh nhiễu, ta xác định toạ độ của mỗi ô (số nguyên) để sinh số ngẫu nhiên đại diện cho 4 đỉnh (dòng từ 23 tới 26). Cuối cùng ở dòng 35 ta nội suy cả 4 giá trị dựa vào phần thập phân.
Giờ tới lượt bạn:
* Thử các kích thước lưới khác (dòng 45), và chuyển động nếu được
* Kích thước lớn tới đâu thì nhiễu trông giống như ngẫu nhiên
* Với kích thước nào thì không thể nhận ra nhiễu nữa
* Thử kết hợp hàm sinh nhiễu với toạ độ con trỏ chuột
* Nếu ta coi dải màu gradient sinh ra bởi nhiễu như là 1 distance field thì sao nhỉ ? Thử làm một cái gì đó hay ho với ý tưởng này xem sao.
* Giờ thì bạn kiểm soát được cả trật tự lẫn sự hỗn loạn ở một mức độ nào đó rồi, hãy thử xem bạn hiểu sâu tới đâu nhé. Hãy dùng nhiều hình chữ nhật với màu sắc khác nhau và nhiễu để mô phỏng lại bức tranh của [Mark Rothko](http://en.wikipedia.org/wiki/Mark_Rothko).
![Bức tranh 'Three ' của Mark Rothko (1950)](rothko.jpg)
## Ứng dụng nhiễu vào các thiết kế ngẫu nhiên
Các thuật toán sinh nhiễu ban đầu được thiết kế để thổi hồn vào các bức tranh kỹ thuật số. Các thuật toán 1 chiều và 2 chiều ta thấy trong chương này đều chỉ nội suy giữa các số ngẫu nhiên có sẵn, nên được gọi là **Value Noise**, nhưng còn nhiều cách khác để sinh nhiễu ...
[ ![Value Noise của Inigo Quilez](value-noise.png) ](../edit.php#11/2d-vnoise.frag)
Ở ví dụ phía trên, ta có thể thấy nhiễu sinh bởi nội suy trông như bị "vỡ ảnh". Để loại bỏ hiệu ứng này, [Ken Perlin](https://mrl.nyu.edu/~perlin/) đã phát minh ra một thuật toán khác năm 1985 gọi là **Gradient Noise**. Ken tìm ra cách để nội suy giữa các dải màu gradient thay vì giữa các số cố định. Các dải màu này lại lại kết quả của một hàm sinh nhiễu 2 chiều khác. Click vào ảnh dưới đây để xem code của mẫu thiết kế này.
[ ![Gradient Noise của Inigo Quilez](gradient-noise.png) ](../edit.php#11/2d-gnoise.frag)
Hãy dành một chút thời gian để quan sát 2 ví dụ sau của [Inigo Quilez](http://www.iquilezles.org/) để so sánh sự khác nhau giữa [value noise](https://www.shadertoy.com/view/lsf3WH) và [gradient noise](https://www.shadertoy.com/view/XdXGW8).
Nếu một hoạ sỹ phải nắm rất rõ cách kết hợp màu trong các bức tranh, thì ta cũng phải hiểu tường tận các cách sinh nhiễu khác nhau thì mới tận dụng được. Ví dụ, nếu ta dùng nhiễu hai chiều để bẻ cong canvas đang có rất nhiều đường thẳng song song, ta có thể tạo nên vân bề mặt trông giống như gỗ vậy. Bạn có thể click vào ảnh dưới đây để xem code.
[ ![Họa tiết gỗ](wood-long.png) ](../edit.php#11/wood.frag)
```glsl
pos = rotate2d( noise(pos) ) * pos; // xoay trục toạ độ theo nhiễu
pattern = lines(pos,.5); // vẽ các đường thẳng
```
Một cách khác để tạo nên các họa tiết thú vị từ nhiễu là coi nó như 1 distance field và áp dụng các kỹ thuật được mô tả ở [Chương Hình dạng](../07/?lan=vi).
[ ![Họa tiết giọt rơi vãi trên sàn](splatter-long.png) ](../edit.php#11/splatter.frag)
```glsl
color += smoothstep(.15,.2,noise(st*10.)); // Các giọt bắn
color -= smoothstep(.35,.4,noise(st*10.)); // Các vũng bắn
```
Cách thứ ba là dùng hàm sinh nhiễu để khiến các hình vẽ chuyển động. Để làm được thì ta cũng cần tới vài kỹ thuật được nhắc tới ở [Chương Hình dạng](../07/?lan=vi).
<a href="../edit.php#11/circleWave-noise.frag"><canvas id="custom" class="canvas" data-fragment-url="circleWave-noise.frag" width="300px" height="300"></canvas></a>
Để tập luyện:
* Bạn còn có thể tạo nên các hoa văn ngẫu nhiên nào nữa ? Vân đá ? Mắc ma ? Mặt nước ? Hãy tìm ảnh của những vân bề mặt bạn thích rồi dùng thuật toán để vẽ lại.
* Dùng nhiễu để bóp méo hình vẽ.
* Dùng nhiễu để di chuyển hình vẽ. Hãy quay lại [Chương Ma trận](../08/?lan=vi) và sửa code di chuyển hình chữ thập để áp dụng thêm nhiễu nhằm tạo ra những chuyển động khó đoán hơn.
* Vẽ lại bức tranh dưới đây của Jackson Pollock.
![Bức tranh 'Number 14 gray' của Jackson Pollock (1948)](pollock.jpg)
## Nhiễu cải tiến
Perlin đã tự cải tiến thuật toán sinh nhiễu ban đầu của ông để tạo ra **Simplex Noise**, bằng cách thay thế đường cong Hermite( _f(x) = 3x^2-2x^3_ , cho kết quả giống với hàm [```smoothstep()```](../glossary/?lan=vi&search=smoothstep)) bằng một đường cong bậc 5 (quintic) có công thức _f(x) = 6x^5-15x^4+10x^3_. Công thức nội suy này khiến cho hai đầu đường cong phẳng hơn và che được sự chuyển tiếp giữa 2 ô. Bạn có thể kiểm chứng điều đó bằng cách uncomment công thức thứ 2 ở đoạn code dưới đây ([hoặc so sánh 2 phương trình cạnh nhau](https://www.desmos.com/calculator/2xvlk5xp8b))
<div class="simpleFunction" data="
// Đồ thị bậc 3 Hermite Curve, kết quả giống với hàm SmoothStep()
y = x*x*(3.0-2.0*x);
// Đồ thị bậc 5
//y = x*x*x*(x*(x*6.-15.)+10.);
"></div>
Chú ý hai đầu của đồ thị để dễ so sánh. Bạn có thể tìm hiểu thêm bằng [nghiên cứu của chính Ken](http://mrl.nyu.edu/~perlin/paper445.pdf).
## Simplex Noise
Đối với Ken Perlin thì sự thành công của thuật toán sinh nhiễu là chưa đủ, ông ấy nghĩ rằng có thể làm tốt hơn nữa. Ở hội nghị Siggraph 2001 ông đã thuyết trình thuật toán mới cải tiến hơn thuật toán cũ, gọi là "simplex noise":
* Hiệu quả hơn với ít phép tính hơn và độ phức tạp cũng thấp hơn.
* Sinh nhiễu ở các không gian nhiều chiều yêu cầu ít dữ liệu hơn thuật toán cũ.
* Các điểm ảnh dọc theo trục toạ độ không còn bị lộ nữa (vì quá gần với các điểm nội suy nên thường có giá trị tương đương)
* Các dải màu gradient dùng để sinh nhiễu có thể được tính rất nhanh và dễ kiểm soát
* Phần cứng có thể hỗ trợ để tối ưu thuật toán này dễ dàng
Tôi biết bạn đang tự hỏi ... "Đây là cao nhân phương nào?" Đúng vậy, các sản phẩm của ông thật siêu đẳng! Nhưng nói một cách nghiêm túc thì ông tự cải tiến thuật toán bằng cách nào ? Đầu tiên ông ấy quan sát cách sinh nhiễu ở không gian 2 chiều và thấy cần dùng 4 đỉnh, ở không gian 3 chiều sẽ cần 8 đỉnh và 4 chiều thì cần 16 đỉnh. Vậy là để sinh nhiễu ở không gian N chiều thì ta sẽ cần 2^N đỉnh. Và Ken đã nhanh chóng nhận ra rằng một tứ giác 4 đỉnh đâu phải hình học đơn giản nhất ở không gian 2 chiều đâu, đó phải là [hình tam giác](../edit.php#11/3d-noise.frag) mới đúng. Và nếu muốn lấp đầy không gian bằng một lưới các tam giác thì tam giác đều sẽ giúp việc code đơn giản hơn. Ý tưởng cải tiến chỉ có vậy.
![](simplex-grid-00.png)
Và để sinh nhiễu cho không gian N chiều, ta sẽ chỉ cần N + 1 đỉnh. So với thuật toán cũ, thuật toán mới này giúp ta tiết kiệm 1 đỉnh ở không gian 2 chiều, 4 đỉnh ở không gian 3 chiều và 11 đỉnh ở không gian 4 chiều! Số lượng phép tính được cắt bớt cực kỳ nhiều. Quả là một cải tiến vượt bậc.
Lúc này, để tính toán giá trị nhiễu tại một điểm ảnh, ta chỉ cần mix giá trị ngẫu nhiên tại 3 đỉnh.
![](simplex-grid-01.png)
Vậy để tạo ra lưới tam giác (simplex grid) này thì làm thế nào ? Lại thêm một nước đi sáng suốt và tinh tế nữa, chỉ bằng cách chia đôi mỗi ô vuông ở lưới dạng bảng thông thường thành 2 nửa tam giác, rồi xô nghiêng (skew) cả lưới tới khi 3 cạnh của mỗi tam giác bằng nhau là được.
![](simplex-grid-02.png)
[Stefan Gustavson đã mô tả thuật toán này trong báo cáo của ông](http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf) như sau:
_"...chỉ cần kiểm tra phần nguyên trong toạ độ (x,y) của điểm ảnh ta cần tô màu, ta có thể nhanh chóng xác định nó ở ô vuông nào trong lưới dạng bảng. Rồi cũng chỉ cần so sánh x với y là ta biết được điểm ảnh nằm ở tam giác nào trong ô vuông đó, cuối cùng chỉ cần nội suy giá trị giữa 3 đỉnh tam giác là xong."_
Trong đoạn code dưới đây, bạn có thể uncomment dòng 44 để thấy lưới vuông bị xô nghiêng như thế nào, và uncomment tiếp dòng 47 để thấy một lưới tam giác được hình thành. Ở dòng 22, chỉ với một phép so sánh đơn giản ```x > y```, ta có thể xác định được điểm ảnh nằm ở tam giác nào trong số 2 tam giác tạo thành mỗi ô vuông.
<div class="codeAndCanvas" data="simplex-grid.frag"></div>
Các cải tiến này đã góp phần tạo nên một thuật toán kiệt tác gọi là **Simplex Noise**. Hình vẽ dưới đây được tạo bởi chính thuật toán này trong GLSL bởi Ian McEwan và Stefan Gustavson (và được mô tả trong [báo cáo này](http://webstaff.itn.liu.se/~stegu/jgt2012/article.pdf)) với một mức độ phức tạp quá tầm với một dự án giáo dục như thế này, nhưng nếu bạn click vào hình thì sẽ thấy ngạc nhiên là code rất ngắn gọn và tối ưu.
[ ![Simplex Noise - Ian McEwan từ Ashima Arts](simplex-noise.png) ](../edit.php#11/2d-snoise-clear.frag)
Chà... lý thuyết thế đủ rồi nhỉ, giờ hãy xắn tay áo lên nào:
* Hãy chiêm ngưỡng các vân bề mặt tuyệt đẹp của thiên nhiên và nghĩ xem có thể dùng thuật toán sinh nhiễu nào để mô phỏng không. Nếu cần thì cứ nheo mắt lại mà tưởng tượng như lúc đoán xem các đám mây có hình gì ấy.
* Tạo shader mô phỏng một dòng chảy, như dòng dung nham, giọt mực, dòng nước ...
<a href="../edit.php#11/lava-lamp.frag"><canvas id="custom" class="canvas" data-fragment-url="lava-lamp.frag" width="520px" height="200px"></canvas></a>
* Sử dụng Simplex Noise để thêm vân bề mặt vào một hình vẽ nào đó bạn đã tạo ra.
<a href="../edit.php#11/iching-03.frag"><canvas id="custom" class="canvas" data-fragment-url="iching-03.frag" width="520px" height="520px"></canvas></a>
Ở chương này chúng tôi đã giới thiệu một vài kỹ thuật giúp kiểm soát sự hỗn loạn. Không dễ chút nào! Để trở thành một noise-bender thành thạo tốn rất nhiều thời gian và công sức.
Ở các chương tiếp theo ta sẽ thấy một vài kỹ thuật nổi tiếng khác giúp bạn hoàn thiện bộ kỹ năng của mình để bắt đầu thiết kế ra những tác phẩm có chất lượng cao bằng shader. Cho tới lúc đó, hãy tận hưởng thiên nhiên và cố gắng tìm ra các họa tiết ẩn giấu từ Mẹ Trái Đất nhé. Kỹ năng quan sát của bạn ít ra cũng phải tương đồng với kỹ năng chế tác. Hãy thư giãn nhé!
<p style="text-align:center; font-style: italic;">"Hãy làm bạn và nói chuyện với cái cây" - Bob Ross</p>

@ -0,0 +1,192 @@
![](dragonfly.jpg)
## Nhiễu mô phỏng tế bào (Cellular noise)
Vào năm 1996, mười sáu năm sau khi thuật toán sinh nhiễu đầu tiên của Perlin được công bố, và năm năm sau khi Thế giới biết đến thuật toán Simplex Noise của ông, [Steven Worley đã viết một báo cáo khoa học với tên gọi "Vân bề mặt mô phỏng tế bào" (A Cellular Texture Basis Function)](http://www.rhythmiccanvas.com/research/papers/worley.pdf). Trong nghiên cứu của mình, ông ấy đã mô tả một kỹ thuật sinh vân bề mặt mà giờ đây được sử dụng rộng khắp bởi cộng đồng đồ hoạ.
Để hiểu được các nguyên lý đằng sau, ta cần tư duy theo kiểu **duyệt (iteration)**. Có thể bạn nghe thấy quen quen, đúng, ta đã suy nghĩ tương tự khi sử dụng vòng lặp ```for``` rồi. Có một hạn chế về vòng lặp ```for``` trong GLSL, đó là: số vòng lặp phải cố định.
Hãy cùng nhìn một ví dụ.
### Các điểm trong một distance field
Nhiễu mô phỏng tế bào (Cellular noise) dựa trên distance field, khoảng cách tới một trong số những đỉnh gần nhất. Giả sử ta muốn tạo một distance field có 4 đỉnh. Ta phải làm gì ? **Với mỗi điểm ảnh, tính khoảng cách tới đỉnh gần nhất**. Điều đó có nghĩa là ta cần phải duyệt qua tất cả các đỉnh, tính khoảng cách từ điểm ảnh tới từng đỉnh một và lưu lại khoảng cách gần nhất.
```glsl
float min_dist = 100.; // Biến lưu lại khoảng cách gần nhất
min_dist = min(min_dist, distance(st, point_a));
min_dist = min(min_dist, distance(st, point_b));
min_dist = min(min_dist, distance(st, point_c));
min_dist = min(min_dist, distance(st, point_d));
```
![](cell-00.png)
Nhìn thì không tinh tế lắm nhưng được việc. Giờ hãy chuyển đoạn code kia thành một vòng lặp nhé.
```glsl
float m_dist = 100.; // khoảng cách nhỏ nhất
for (int i = 0; i < TOTAL_POINTS; i++) {
float dist = distance(st, points[i]);
m_dist = min(m_dist, dist);
}
```
Chú ý cách tôi dùng vòng lặp ```for``` để duyệt qua mảng chứa vị trí từng đỉnh, và lưu lại khoảng cách nhỏ nhất bằng hàm [```min()```](../glossary/?lan=vi&search=min). Đoạn code dưới đây làm đúng như vậy:
<div class="codeAndCanvas" data="cellnoise-00.frag"></div>
Trong đoạn code phía trên, một trong các đỉnh là vị trí của con trỏ chuột. Di chuyển con trỏ chuột để có minh hoạ trực quan về thuật toán này nhé. Sau đó hãy thử nghĩ:
- Làm sao để di chuyển các đỉnh còn lại ?
- Sau khi đọc [Chương hình dạng](../07/?lan=vi), hãy nghĩ ra các cách khác để sử dụng distance field
- Nếu muốn thêm nhiều đỉnh hơn thì làm thế nào ? Nếu muốn tự do thêm bớt đỉnh thì sao ?
### Vòng lặp duyệt các phần tử
Bạn có thể đã nhận ra vòng lặp ```for``` và *mảng* không phải là bạn tốt của GLSL. Như tôi đã nói trước đó, vòng lặp yêu cầu cố định số vòng lặp ngay từ đầu. Ngoài ra thì việc duyệt từng phần tử cũng khiến code shader chậm hơn đáng kể. Điều đó có nghĩa là nếu có nhiều đỉnh ta không thể dùng cách này. Ta phải tìm một chiến thuật khác, sao cho tận dụng được ưu thế về kiến trúc tính toán song song của GPU.
![](cell-01.png)
Một giải pháp được đưa ra là chia nhỏ không gian thành nhiều phần. Đâu nhất thiết phải tính khoảng cách của từng điểm ảnh với tất cả các đỉnh, phải không ? Giả sử mỗi điểm ảnh được xử lý trên một thread riêng, ta có thể chia canvas thành nhiều ô, mỗi ô chỉ có 1 đỉnh trong đó, gọi là nhân. Để tránh lỗi ở phần biên giới giữa các ô, ta sẽ cần tính khoảng cách của các pixel trong mỗi ô với nhân của các ô lân cận nữa. Đó chính là ý tưởng chủ đạo trong [nghiên cứu của Steven Worley](http://www.rhythmiccanvas.com/research/papers/worley.pdf). Tóm lại, mỗi điểm ảnh chỉ cần tính khoảng cách với tối đa 9 nhân: 1 nhân ở cùng ô với điểm ảnh, và 8 nhân ở các ô xung quanh. Ta cũng đã biết cách chia nhỏ canvas thành nhiều ô ở các chương trước khi bàn về: [Mẫu họa tiết](../09/?lan=vi), [Sự ngẫu nhiên](../10/?lan=vi) và [Nhiễu](../11/?lan=vi), nên tôi hy vọng bạn đã thuần thục kỹ năng này rồi.
```glsl
// Phóng to
st *= 3.;
// Chia nhỏ
vec2 i_st = floor(st);
vec2 f_st = fract(st);
```
Vậy, kế hoạch là gì ? Ta sẽ dùng toạ độ hàng cột (phần nguyên) của mỗi ô (lưu trong ```i_st```) để sinh một cặp số ngẫu nhiên, chính là vị trí của nhân trong ô đó. Hàm ```random2```
nhận một ```vec2``` và trả về một ```vec2``` gồm 2 số ngẫu nhiên, chính là để thực hiện việc sinh ra một nhân có vị trí ngẫu nhiên trong ô này.
```glsl
vec2 point = random2(i_st);
```
Mỗi điểm ảnh trong ô này sẽ tính khoảng cách từ toạ độ của chính nó (lưu trong ```f_st```) với nhân.
```glsl
vec2 diff = point - f_st;
float dist = length(diff);
```
Kết quả thu được như sau:
<a href="../edit.php#12/cellnoise-01.frag"><img src="cellnoise.png" width="520px" height="200px"></img></a>
Ta sẽ cần tính thêm khoảng cách tới nhân của các ô xung quanh nữa. Để làm được việc đó, ta cần *duyệt* qua các ô xung quanh, tức là các ô nằm trong phạm vi 3x3 có toạ độ X và Y trong khoảng [-1, 1] so với ô hiện tại. Ta có thể dùng 2 vòng lặp ```for``` lồng nhau để làm việc đó:
```glsl
for (int y= -1; y <= 1; y++) {
for (int x= -1; x <= 1; x++) {
// Các ô liền kề trong lưới
vec2 neighbor = vec2(float(x),float(y));
...
}
}
```
![](cell-02.png)
Hãy nhớ rằng các hàm ngẫu nhiên không thật sự ngẫu nhiên, vì thế ta có thể biết vị trí của nhân ở một ô bất kỳ bằng cách gọi lại hàm random2 với tham số giống như ở chính ô đó vậy.
```glsl
...
// Tính vị trí nhân ở ô liền kề
vec2 point = random2(i_st + neighbor);
...
```
Phần còn lại chỉ là tính khoảng cách với các nhân và lưu lại khoảng cách nhỏ nhất vào biến ```m_dist```.
```glsl
...
vec2 diff = neighbor + point - f_st;
// Khoảng cách tới nhân
float dist = length(diff);
// Lưu khoảng cách nhỏ nhất
m_dist = min(m_dist, dist);
...
```
Đoạn code trên lấy cảm hứng từ [một bài báo của Inigo's Quilez](http://www.iquilezles.org/www/articles/smoothvoronoi/smoothvoronoi.htm) mà trong đó ông có viết:
*"... đoạn code trên có thể cải tiến hơn nữa chỉ nhờ một mẹo nhỏ. Các đoạn code tương tự có rủi ro về sai số, vì vị trí nhân được lưu là toạ độ tuyệt đối. Ta có thể sử dụng các kiểu dữ liệu có độ chính xác cao hơn để khắc phục, nhưng còn cách khác hay hơn. Code của tôi không dùng toạ độ tuyệt đối mà chỉ dùng toạ độ tương đối trong mỗi ô thôi: sau khi tách được phần nguyên và phần thập phân, việc sinh vị trí ngẫu nhiên của nhân được thực hiện giống nhau trong mọi ô, vì thế mà phần nguyên không còn cần thiết nữa, tiết kiệm rất nhiều không gian bộ nhớ. Thực tế thì ở các đoạn code sinh voronoi thông thường, tuy sử dụng toạ độ tuyệt đối nhưng nó vẫn phải đảm bảo nằm trong ô, tức là vẫn có ràng buộc gián tiếp về toạ độ tương đối. Còn ở đoạn code trên, ta thậm chí không cần quan tâm tới ràng buộc đó, bởi toạ độ được sinh ra chắc chắn sẽ nằm trong ô. Mẹo nhỏ này có thể áp dụng cả trong trường hợp số lượng điểm ảnh khổng lồ - ta sẽ cần sử dụng kiểu dữ liệu double có khoảng giá trị lớn hơn float, nhưng ngay sau khi tách được phần thập phân, ta chỉ cần làm việc với phần thập phân lưu bằng một biến float là đủ. Tất nhiên là mẹo này cũng có thể áp dụng cho Perlin noise (nhưng tôi chưa từng thấy nó được implement hay mô tả theo cách này)."*
Tóm tắt: Ta chia canvas thành nhiều ô, mỗi ô có một nhân; mỗi điểm ảnh sẽ tính khoảng cách tới nhân trong ô tương ứng và cả 8 ô xung quanh; lưu lại khoảng cách nhỏ nhất. Kết quả thu được là 1 distance field trông như sau:
<div class="codeAndCanvas" data="cellnoise-02.frag"></div>
Hãy thử vài thứ phức tạp hơn:
- Chia canvas thành lưới có kích thước khác
- Làm thế nào để di chuyển các nhân trong mỗi ô ?
- Nếu ta coi vị trí của con trỏ chuột là một nhân thì sao ?
- Nếu không dùng công thức ```m_dist = min(m_dist, dist);``` thì còn cách nào khác để tìm khoảng cách gần nhất không ?
- Dùng distance field này tạo được các họa tiết nào ?
Thuật toán này cũng có thể được mô tả theo cách khác, thay vì bắt đầu bằng các điểm ảnh ta sẽ lấy các nhân làm cơ sở. Cụ thể như sau: Mỗi nhân sẽ tự lan rộng vùng ảnh hưởng ra xung quanh tới khi chạm với các vùng ảnh hưởng khác. Trong tự nhiên thì nhiều loài động thực vật trưởng thành theo cách này. Các hình thái sinh vật sống được hình thành bởi hai tác động chính: một là nội lực đẩy từ bên trong ra khiến nó ngày càng sinh sôi và lớn hơn, hai là ngoại lực từ bên ngoài kiềm chế mức độ sinh trưởng lẫn nhau. Thuật toán mô phỏng theo quan sát này được đặt tên theo nhà toán học đã định nghĩa nó, đó là [Georgy Voronoi](https://en.wikipedia.org/wiki/Georgy_Voronoy).
![](monokot_root.jpg)
### Thuật toán Voronoi
Việc tạo ra sơ đồ Voronoi từ cellular noise không khó như hình dung. Ta chỉ cần *tốn thêm ít bộ nhớ* để lưu các thông tin. Để làm điều đó ta sẽ dùng một biến kiểu ```vec2``` có tên là ```m_point```. Bằng cách lưu lại hướng từ điểm ảnh tới nhân gần nhất, thay vì chỉ có thông tin về khoảng cách, ta còn xác định cả đỉnh gần nhất luôn.
```glsl
...
if( dist < m_dist ) {
m_dist = dist;
m_point = point;
}
...
```
Trong đoạn code dưới đây, tôi không dùng hàm ```min``` nữa mà dùng lệnh ```if``` cơ bản. Vì tôi không chỉ muốn lưu lại khoảng cách gần nhất mà còn muốn biết nhân gần nhất nữa (dòng 32 tới 37).
<div class="codeAndCanvas" data="vorono-00.frag"></div>
Nếu bạn di chuyển con trỏ chuột trong canvas, bạn sẽ thấy con trỏ chuột cũng được tính là một nhân. Tôi làm vậy để bạn có thể hiểu hơn về thuật toán này. Thậm chí, màu của các pixel sẽ thay đổi theo toạ độ con trỏ chuột.
Giờ hãy chuyển qua thuật toán tương tự được [mô tả trong báo cáo của Steven Worley](http://www.rhythmiccanvas.com/research/papers/worley.pdf). Bạn hãy tự code thuật toán này. Bạn cũng có thể tham khảo đoạn code dưới đây bằng cách click vào ảnh minh hoạ. Thuật toán ban đầu của Steven Worley có nhiều nhân trong một ô thay vì chỉ có một nhân, nhưng vì ông dùng ngôn ngữ C để lập trình, nên có thể thay đối số vòng lặp tuỳ ý. GLSL không cho phép điều đó, nên ta sẽ giới hạn số nhân ở mỗi ô là 1.
<a href="../edit.php#12/vorono-01.frag"><canvas id="custom" class="canvas" data-fragment-url="vorono-01.frag" width="520px" height="200px"></canvas></a>
Khi bạn đã hiểu thuật toán này rồi, hãy nghĩ ra cách để tận dụng nó.
![Voronoi mở rộng - Leo Solaas (2011)](solas.png)
![Những thành phố trên mây - Tomás Saraceno (2011)](saraceno.jpg)
![Sê-ri 'Accretion Disc' - Clint Fulkerson](accretion.jpg)
![Câu đố Vonoroi - Reza Ali (2015)](reza.png)
### Cải tiến Voronoi
Năm 2011, [Stefan Gustavson đã cải tiến thuật toán của Steven Worley cho GPU](http://webstaff.itn.liu.se/~stegu/GLSL-cellular/GLSL-cellular-notes.pdf) chỉ bằng cách duyệt qua ma trận 2x2 thay vì 3x3. Điều này giúp giảm số lượng phép tính đáng kể, nhưng cũng dễ làm cho các cạnh không đồng đều. Hãy thử nhìn các ví dụ sau:
<div class="glslGallery" data="12/2d-cnoise-2x2,12/2d-cnoise-2x2x2,12/2d-cnoise,12/3d-cnoise" data-properties="clickRun:editor,openFrameIcon:false"></div>
Khoảng cuối năm 2012, [Inigo Quilez đã đăng một bài báo về cách vẽ các đường viền Voronoi một cách chính xác](http://www.iquilezles.org/www/articles/voronoilines/voronoilines.htm).
<a href="../edit.php#12/2d-voronoi.frag"><img src="2d-voronoi.gif" width="520px" height="200px"></img></a>
Thí nghiệm của Inigo với Voronoi không chỉ dừng lại ở đó. Vào năm 2014, ông đã công bố một nghiên cứu thú vị mà ông gọi là [voro-noise](http://www.iquilezles.org/www/articles/voronoise/voronoise.htm), một hàm có khả năng chuyển tiếp giữa nhiễu ngẫu nhiên và Voronoi. Trong báo cáo đó ông có viết:
*"Dù cả hai thuật toán sinh nhiễu đều dùng hệ thống lưới nhưng cách dùng rất khác nhau. Value noise thì nội suy giữa các đỉnh có giá trị đi kèm ngẫu nhiên, Gradient noise thì nội suy giữa các dải màu ngẫu nhiên, còn Voronoi thì tính khoảng cách tới các đỉnh ngẫu nhiên. Nếu cả nội suy có lọc bilinear và công thức xác định khoảng cách gần nhất có thể tổng quát hoá thành một phép toán thì cả nhiễu thông thường và sơ đồ Voronoi sẽ được quy về 2 trường hợp của cùng một thuật toán sinh họa tiết trên một lưới xác định phải không ?"*
<a href="../edit.php#12/2d-voronoise.frag"><canvas id="custom" class="canvas" data-fragment-url="2d-voronoise.frag" width="520px" height="200px"></canvas></a>
Bạn hãy quan sát thật kỹ thế giới xung quanh, thiên nhiên đầy nhiệm màu và sẽ giúp bạn tự tìm ra kỹ thuật sinh nhiễu của riêng mình !
![Ảnh chụp phim thủy tinh Deyrolle - 1831](DeyrolleFilm.png)
<div class="glslGallery" data="12/metaballs,12/stippling,12/cell,12/tissue,12/cracks,160504143842" data-properties="clickRun:editor,openFrameIcon:false"></div>

@ -0,0 +1,122 @@
![Bức tranh 'Due East over Shadequarter Mountain' của Matthew Rangel (2005) ](rangel.jpg)
## Chuyển động Brown (Fractal Brownian motion)
Nói về nhiễu, mỗi người sẽ có một suy nghĩ khác nhau. Các nhạc công sẽ nghĩ về những tiếng ồn khó chịu, các nhà vật lý thiên văn học thì nghĩ tới bức xạ nền vi sóng vũ trụ, còn trong giao tiếp thì nhiễu dùng để chỉ các yếu tố gây mất tập trung. Các khái niệm này đưa chúng ta trở lại với môn Vật lý phổ thông để giải thích những tính chất đằng sau sự ngẫu nhiên. Tuy nhiên, hãy bắt đầu với thứ gì đó cơ bản hơn: Sóng và các tính chất của nó. Một sóng sự dao động của một vài thuộc tính vật lý theo thời gian. Sóng âm là sự dao động của áp suất không khí, sóng điện từ là dao động trong điện trường và từ trường. Hai tính chất quan trọng của sóng là biên độ và tần số. Phương trình sóng tuyến tính (một chiều) nhìn như sau:
<div class="simpleFunction" data="
float amplitude = 1.;
float frequency = 1.;
y = amplitude * sin(x * frequency);
"></div>
* Hãy thử thay đổi giá trị của biên độ và tần số để xem đồ thị thay đổi tương ứng như thế nào.
* Thay đổi biên độ theo thời gian bằng các hàm hình dạng
* Thay đổi tần số theo thời gian bằng các hàm hình dạng
Người dịch: Nếu mỗi khi tăng tần số bạn lại giảm biên độ một đại lượng tương ứng, bạn sẽ thấy "một phiên bản thu nhỏ" của sóng ban đầu, như đoạn code dưới đây. Bạn có thể dùng [GraphToy](http://www.iquilezles.org/apps/graphtoy/) để vẽ từng sóng này rời nhau, bạn sẽ thấy rõ hơn. Các họa tiết nhìn giống nhau chỉ khác về kích thước như vậy được gọi là Fractal.
<div class="simpleFunction" data="
float amplitude = 1.;
float frequency = 1.;
float scale = 8.; // sửa dòng này để thấy từng cấp của pattern Fractal
y = amplitude * 1. / scale * sin(x * frequency * scale);
"></div>
Hai yêu cầu cuối sẽ giúp bạn thuần thục hơn trong kỹ năng kiểm soát (modulate) sóng sin, và hai sóng mà bạn vừa tạo ra có tên gọi riêng, đó là AM (Amplitude Modulated) và FM (Frequency Modulated). Chúc mừng nhé !
Một đặc điểm khác cũng thú vị không kém của các sóng là chúng có thể cộng dồn và chồng chất lên nhau. Hãy comment/Uncomment và sửa các dòng code sau và chú ý vào sự thay đổi của đồ thị khi ta chồng các sóng có biên độ và tần số khác nhau.
<div class="simpleFunction" data="
float amplitude = 1.;
float frequency = 1.;
y = sin(x * frequency);
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
y += sin(x*frequency*1.72 + t*1.121)*4.0;
y += sin(x*frequency*2.221 + t*0.437)*5.0;
y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;
"></div>
* Hãy thử thêm bớt từng sóng.
* Có thể tạo hai sóng triệt tiêu lẫn nhau không ? Nếu có thì trông chúng như thế nào ?
* Có thể tạo hai sóng hoàn toàn khuếch đại lẫn nhau không ? Nếu có thì trông chúng như thế nào ?
Trong âm nhạc, mỗi nốt nhạc tương ứng với một tần số. Những tần số này tuân theo một pattern mà ta gọi là gam / âm giai, khi tăng gấp đôi hoặc giảm một nửa tần số ta được các nốt nhạc ở một quãng tám ngay trên và dưới các nốt ban đầu.
Giờ hãy dùng Perlin noise thay vì sóng sin. Perlin noise ở dạng cơ bản nhất nhìn cũng na ná sóng sin. Dù cho biên độ và tần số của nó có sai số đi nữa, biên độ vẫn có mức ổn định chấp nhận được còn tần số thì bị giới hạn để chỉ dao động quanh mốc tần số trung tâm. Tuy không phổ biến như sóng sin, nhưng, nếu chồng nhiều sóng nhiễu lên ta có thể tạo nên sự ngẫu nhiên mong muốn. Còn nếu chồng các sóng sin lên nhau mà vẫn muốn tạo ra sự ngẫu nhiên thì sẽ tốn khá nhiều công để che giấu tần số.
Thay vì dùng các sóng nhiễu giống hệt nhau, ta sẽ dùng các cấp fractal của sóng. Cứ mỗi lần chồng thêm một sóng nhiễu (tức thêm một *quãng tám - octaves*), ta sẽ tăng tần số bằng cách nhân với một đại lượng cố định (*lacunarity*) và giảm biên độ cũng bằng cách nhân với một đại lượng cố định (*gain*), kết quả thu được là sóng nhiễu ngày càng chi tiết hơn. Kỹ thuật này gọi là chuyển động Brown - "fractal Brownian Motion" (*fBm*), hoặc đơn giản là nhiễu phân dạng - "fractal noise", có thể sinh bởi đoạn code sau:
<div class="simpleFunction" data="// Các thuc tính
const int octaves = 1;
float lacunarity = 2.0;
float gain = 0.5;
//
// Giá trị ban đầu
float amplitude = 0.5;
float frequency = 1.;
//
// Vòng lặp các quãng tám
for (int i = 0; i < octaves; i++) {
&#9;y += amplitude * noise(frequency*x);
&#9;frequency *= lacunarity;
&#9;amplitude *= gain;
}"></div>
* Hãy thử thay đổi giá trị của quãng tám từ 1 lên 2, 4, 8, 10 và xem đồ thị thay đổi ra sao.
* Với số quãng tám lớn hơn 4, hãy thử thay đổi giá trị lacunarity.
* Cũng với số quãng tám lớn hơn 4, hãy thử thay đổi giá trị gain.
Bạn sẽ dễ dàng nhận ra cứ chồng thêm một quãng tám, đồ thị lại càng chi tiết hơn. Mỗi cấp sóng nhiễu được thêm vào lại giống như một phiên bản tí hon của cấp trước đó. Đó là một tính chất quan trọng của phân dạng (fractal) trong Toán học. Thực ra thì ta không tạo ra một phân dạng *thực thụ*, vì số vòng lặp giới hạn, nhưng về lý thuyết thì điều đó có thể với một vòng lặp vô hạn. Trong lĩnh vực đồ họa máy tính thì ta luôn phải đánh đổi sự chi tiết với số lượng phép tính cần thực hiện. Trong trường hợp này, nếu sự thay đổi của đồ thị nhỏ hơn kích thước của một điểm ảnh thì việc tiếp tục chồng thêm quãng tám là không cần thiết nữa.
Dưới đây là ví dụ tôi dùng fBm trong không gian 2 chiều để tạo nên họa tiết phân dạng:
<div class='codeAndCanvas' data='2d-fbm.frag'></div>
* Hãy thử giảm số quãng tám ở dòng 37
* Hãy thử sửa tham số lacunarity ở dòng 47
* Hãy thử sửa tham số gain ở dòng 48
Kỹ thuật này rất hay được sử dụng để tạo nên các địa hình ngẫu nhiên. Tính chất tương tự của các cấp fractal trong fBm vô cùng thích hợp để mô tả các ngọn núi, vì quá trình ăn mòn để tạo nên các ngọn núi cũng được mô phỏng bằng chính đặc tính này. Nếu bạn thích chủ đề này, tôi đặc biệt giới thiệu [nghiên cứu tuyệt vời này của Inigo Quiles về nhiễu cao cấp](http://www.iquilezles.org/www/articles/morenoise/morenoise.htm).
![Blackout - Dan Holdsworth (2010)](holdsworth.jpg)
Sử dụng các kỹ thuật tương tự, ta có thể tạo nên hiệu ứng **nhiễu loạn - turbulence**. Thực ra nó chính là fBm nhưng sử dụng các giá trị tuyệt đối của nhiễu để tạo nên các hố sâu, gọi là thung lũng.
```glsl
for (int i = 0; i < OCTAVES; i++) {
value += amplitude * abs(snoise(st));
st *= 2.;
amplitude *= .5;
}
```
<a href="../edit.php#13/turbulence.frag"><img src="turbulence-long.png" width="520px" height="200px"></img></a>
Một thành viên khác trong họ thuật toán này là **dãy núi - ridge**, chỉ khác là thay vì có các thung lũng sâu, ta có các dãy núi dựng đứng:
```glsl
n = abs(n); // tạo các rãnh thung lũng
n = offset - n; // đảo ngược thung lũng thành đỉnh núi
n = n * n; // tăng góc nghiêng của các đỉnh núi
```
<a href="../edit.php#13/ridge.frag"><img src="ridge-long.png" width="520px" height="200px"></img></a>
Một biến thể khác để tạo ra các sóng nhiễu đa dạng hơn, đó là nhân giá trị của các vị trí tương ứng trên các sóng nhiễu lại với nhau, thay vì cộng dồn. Và các cấp sóng fractal có thể được scale dựa vào cấp trước đó, những sóng kết quả sẽ được gọi là "multifractal". Tuy các sóng "multifractal" không chặt chẽ về mặt Toán học lắm, nhưng điều đó không làm giảm tính hữu dụng của chúng trong đồ họa máy tính. Thực tế thì chúng rất hay được sử dụng ở các phần mềm thương mại giúp sinh ra các loại địa hình khác nhau. Để tìm hiểu thêm, bạn có thể đọc chương 16 của quyển "Texturing and Modeling: a Procedural Approach" (lần xuất bản thứ 3), được viết bởi Kenton Musgrave. Rất tiếc là bản in của quyển sách này đã hết sạch từ vài năm trước rồi, nhưng bạn vẫn có thể tìm mượn ở thư viện hoặc mua lại sách cũ. (Tuy rằng có phiên bản PDF được rao bán trên mạng nhưng bạn đừng phí tiền mua nó, bởi đó là phiên bản cũ từ năm 1994, không hề đề cập tới việc sinh địa hình tự động mà tôi nhắc tới ở đây).
### Bẻ cong không gian
[Inigo Quiles có đăng một nghiên cứu thú vị](http://www.iquilezles.org/www/articles/warp/warp.htm) về cách dùng fBm để bẻ cong không gian (tức hệ tọa độ) của một fBm khác. Xoắn não thật sự luôn. Chẳng khác gì những giấc mơ lồng nhau trong bộ phim Inception cả.
![ f(p) = fbm( p + fbm( p + fbm( p ) ) ) - Inigo Quiles (2002)](quiles.jpg)
Một ví dụ dễ thở hơn của kỹ thuật này được thực hiện bởi đoạn code dưới đây, để sinh ra các họa tiết mô phỏng những đám mây. Nếu để ý bạn sẽ đặc tính tương tự nhiều cấp (self-similarity) vẫn có trong họa tiết sau cùng.
<div class='codeAndCanvas' data='clouds.frag'></div>
Việc bẻ công hệ tọa độ của texture bằng nhiễu rất có ích lại còn hay ho nữa, nhưng rất khó để sử dụng thành thạo nếu không kiên trì tập luyện.
Một kỹ thuật khác cũng hay được dùng là dùng nhiễu để can thiệp vào dải gradient khi blend giữa các đỉnh (xét về mặt Toán học, đó chính là đạo hàm của phương trình sóng). Đây là [một bài nghiên cứu của Ken Perlin và Fabrice Neyret về "flow noise"](http://evasion.imag.fr/Publications/2001/PN01/) dựa trên ý tưởng này. Một vài phiên bản code hiện tại của Perlin noise đã tính cả sóng nhiễu và tích phân của dải màu gradient luôn. Nếu thông tin về dải màu "chi tiết" không được cung cấp thì bạn hoàn toàn có thể dùng phương pháp sai phân hữu hạn để tính xấp xỉ, dù cho kết quả sẽ không được chính xác tuyệt đối và cũng hơi mất công nữa.

@ -0,0 +1,33 @@
## Làm thế nào để đọc quyển sách này mà không cần Internet ?
Giả sử bạn có một chuyến đi dài và muốn tranh thủ học shader. Trong trường hợp đó bạn có thể tạo một bản sao của quyển sách này và hiển thị nó bằng server nội bộ.
Để làm điều đó, bạn chỉ cần có PHP, Python 2.6 và git. Trên các máy MacOS và Raspberry Pi thì Python đã được cài đặt sẵn rồi nhưng bạn vẫn cần cài thêm PHP và git. Cụ thể:
Trên **MacOSX** hãy cài [homebrew](http://brew.sh/) trước rồi mở terminal ra và gõ:
```bash
brew update
brew upgrade
brew install git php
```
Trên **Raspberry Pi** bạn cần cài [Raspbian](https://www.raspberrypi.org/downloads/raspbian/), là một phiên bản Linux dựa trên Debian dành riêng cho Raspberry Pi, sau đó gõ lệnh:
```bash
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install git-core glslviewer php
```
Khi đã cài đặt xong các phần mềm cần thiết, bạn chỉ cần gõ:
```bash
cd ~
git clone --recursive https://github.com/patriciogonzalezvivo/thebookofshaders.git
cd thebookofshaders
git submodule foreach git submodule init && git submodule update
php -S localhost:8000
```
Rồi truy cập server nội bộ ở địa chỉ [`http://localhost:8000/`](http://localhost:8000/)

@ -0,0 +1,18 @@
## Làm thế nào để chạy các ví dụ trên Raspberry Pi ?
Vài năm trước, việc một ai đó sở hữu một chiếc máy tính có card đồ hoạ còn khá hiếm. Giờ thì hầu nhưu máy nào cũng có, nhưng vẫn còn lâu nữa thì các lớp học mới được trang bị đầy đủ.
Nhờ có [Raspberry Pi Foundation](http://www.raspberrypi.org/), một thế hệ máy tính mới rất nhỏ và rẻ (chỉ khoảng 35$ một chiếc) đã ra đời và được trang bị cho các lớp học. Điều quan trọng nhất đối với quyển sách này là, các máy [Raspberry Pi](http://www.raspberrypi.org/) được trang bị GPU đời mới của hãng Broadcom sẽ cho phép truy cập trực tiếp ngay từ cửa sổ dòng lệnh. Tôi đã tạo ra [một công cụ hỗ trợ code GLSL gọi là glslViewer](https://github.com/patriciogonzalezvivo/glslViewer) có thể chạy tất cả các ví dụ trong quyển sách này. Công cụ này còn có khả năng cập nhật hình ảnh tự động, ngay khi bạn sửa code.
Bằng cách tạo một bản sao của quyển sách này ở máy tính của bạn (xem hướng dẫn ở phần trước) và cài đặt [`glslViewer`](https://github.com/patriciogonzalezvivo/glslViewer), bạn có thể chạy các ví dụ với `glslViewer`. Ngoài ra nếu thêm tham số `-l` khi chạy thì một góc màn hình sẽ được dùng để dựng hình ví dụ đó, trong khi bạn có thể dùng bất kỳ chương trình biên soạn nào để sửa code. Nó còn hoạt động ngay cả khi bạn kết nối từ một máy tính khác thông qua ssh/sftp.
Để tạo bản sao của quyển sách và cài glslViewer sau khi đã có [Raspbian](https://www.raspberrypi.org/downloads/raspbian/), gõ các lệnh sau:
```bash
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install git-core glslviewer
cd ~
git clone https://github.com/patriciogonzalezvivo/thebookofshaders.git
cd thebookofshaders
```

@ -0,0 +1,71 @@
## Làm thế nào để in quyển sách này
Giả sử bạn không có nhu cầu tương tác với các ví dụ mà chỉ muốn đọc quyển sách theo cách cổ điển như khi đang nằm trên bãi biển hoặc trên tàu điện. Trong trường hợp đó bạn có thể in quyển sách này.
#### Cài đặt glslViewer
Để in quyển sách này đầu tiên phải biên dịch nó đã. Để làm được việc đó thì bạn cần [`glslViewer`](https://github.com/patriciogonzalezvivo/glslViewer) để biến các đoạn code shader thành ảnh minh hoạ.
Trên **MacOSX** nếu đã có [homebrew](http://brew.sh/) thì cần gõ lệnh sau:
```bash
brew install glslviewer
```
Trên **Raspberry Pi** bạn cần cài [Raspbian](https://www.raspberrypi.org/downloads/raspbian/), một phiên bản Linux dựa trên Debian dành riêng cho Raspberry PI rồi gõ lệnh sau:
```bash
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install git-core glslviewer
```
#### Cài đặt Python 2.7, Latex Engine và Pandoc
Để biên dịch các chương sách viết theo cú pháp Markdown bằng phần mềm Latex rồi xuất ra định dạng PDF thì bạn cần có Latex Engine và Pandoc.
Trên **MacOSX**:
Tải và cài đặt MacTeX bằng lệnh:
```bash
brew cask install mactex-no-gui
```
sau đó cài thêm [Pandoc](http://johnmacfarlane.net/pandoc/) và Python 2 bằng lệnh:
```bash
brew install pandoc python@2
```
Trên **Raspberry Pi** (Raspbian):
```bash
sudo apt-get install texlive-xetex pandoc python2.7
```
#### Chuyển đổi quyển sách sang định dạng PDF
Giờ bạn đã có đủ công cụ cần thiết, hãy tạo một bản sao của [quyển sách này](https://github.com/patriciogonzalezvivo/thebookofshaders) và in nó thôi.
Ở cửa sổ terminal, hãy gõ lệnh:
```bash
cd ~
git clone https://github.com/patriciogonzalezvivo/thebookofshaders.git
cd thebookofshaders
make clean pdf
```
Nếu mọi thứ ổn, bạn sẽ thấy file `book.pdf` mà bạn có thể đọc bằng bất kỳ thiết bị nào hoặc in ra.
#### Chuyển đổi quyển sách sang định dạng epub để đọc trên thiết bị kỹ thuật số
```bash
cd ~
git clone https://github.com/patriciogonzalezvivo/thebookofshaders.git
cd thebookofshaders
make clean epub
```
File `book.epub` có thể đọc trực tiếp hoặc cần chuyển sang định dạng `.mobi` để đọc trên Kindle bằng một phần mềm khác như Calibre chẳng hạn.

@ -0,0 +1,63 @@
## Làm thế nào để đóng góp cho quyển sách này
Cảm ơn bạn vì muốn góp sức! Có rất nhiều cách để làm việc đó:
- Dịch quyển sách sang ngôn ngữ khác
- Cải thiện phần [```Chú giải```](https://github.com/patriciogonzalezvivo/thebookofshaders/tree/master/glossary)
- Biên tập lại nội dung
- Chia sẻ code shader của bạn trên [editor online](http://editor.thebookofshaders.com/)
### Dịch quyển sách sang ngôn ngữ khác
Quyển sách này được viết theo [cú pháp Markdown](https://daringfireball.net/projects/markdown/syntax) nên rất dễ chỉnh sửa.
1. Bạn có thể truy cập [repository ```github.com/patriciogonzalezvivo/thebookofshaders``` trên GitHub](https://github.com/patriciogonzalezvivo/thebookofshaders). Hãy xem các file và thư mục ở đó. Bạn sẽ thấy nội dung của các chương nằm ở các file ```README.md``` và các file có tên viết hoa như: ```TITLE.md```, ```SUMMARY.md```, vân vân. Ngoài ra thì các bản dịch sẽ có file riêng và có tên kết thúc bằng mã ngôn ngữ 2 ký tự, ví dụ: ```README-jp.md```, ```README-es.md```, vân vân.
2. Fork repository này rồi tạo một bản sao trên máy của bạn.
3. Tạo một bản sao cho mỗi file mà bạn muốn dịch. Hãy nhớ thêm mã ngôn ngữ 2 ký tự vào cuối tên file.
4. Dịch nội dung sang ngôn ngữ khác (xem mục **Chú ý khi dịch thuật**).
5. Kiểm tra lại (xem mục **Kiểm tra**).
6. Push lên repository riêng của bạn rồi tạo một [Pull Request](https://help.github.com/articles/using-pull-requests/) vào repository của chúng tôi.
#### Chú ý khi dịch thuật
Không được xoá hoặc thay đổi mã nhúng các ví dụ tương tự như các đoạn mã sau:
```html
<div class="codeAndCanvas" data="grid-making.frag"></div>
```
hoặc
```html
<div class="simpleFunction" data="y = mod(x,2.0);"></div>
```
#### Kiểm tra
Hãy thử chạy trên server PHP nội bộ của bạn:
```bash
php -S localhost:8000
```
Rồi truy cập ```localhost:8000``` trên trình duyệt và tới chương mà bạn đã dịch rồi thêm đuôi ```?lan=``` kèm mã ngôn ngữ 2 ký tự.
Ví dụ, nếu bạn dịch chương ```03``` sang tiếng Pháp tức là bạn sửa file ```03/README-fr.md``` nên giờ bạn sẽ tự kiểm tra lại tại địa chỉ: ```http://localhost:8000/03/?lan=fr```
### Cải thiện phần chú giải
Phần này vẫn đang được bổ sung. Chúng tôi rất vui lòng lắng nghe các ý tưởng của bạn để khiến nó trở nên dễ hiểu hơn. Hãy gửi tin nhắn tới [@bookofshaders](https://twitter.com/bookofshaders).
### Biên tập nội dung
Chúng ta đều là những người bình thường có nhiều điểm có thể cải thiện hơn nữa. Nếu bạn có gì muốn góp ý, cứ tạo Pull Request hoặc một issue. Cảm ơn!
### Chia sẻ code shader
Bạn sẽ thấy rất nhiều link trỏ tới [editor online](http://editor.thebookofshaders.com/) hoặc các phiên bản nhúng của nó ở quyển sách này.
Nếu bạn hoàn thành 1 đoạn code hay ho nào đó, hãy click nút "Export" (hoặc biểu tượng ```⇪```) rồi copy "URL to code...". Sau đó gửi URL tới [@bookofshaders](https://twitter.com/bookofshaders) hoặc [@kyndinfo](https://twitter.com/kyndinfo). Chúng tôi rất mong chờ được bổ sung nó vào [phần thư viện các ví dụ](https://thebookofshaders.com/examples/).

@ -0,0 +1,437 @@
## Giới thiệu cho người đã biết Javascript
Tác giả: [Nicolas Barradeau](http://www.barradeau.com/)
Nếu bạn là developer Javascript, khả năng cao là bạn sẽ thấy hoang mang một chút khi đọc quyển sách này.
Thực tế là có rất nhiều điểm khác biệt khi code JavaScript vốn chỉ là bề nổi, so với việc phải đụng tới shader ở sâu bên dưới tảng băng chìm.
Tuy nhiên, không giống với ngôn ngữ nền tảng là Assembly, GLSL rất gần với ngôn ngữ mà con người có thể hiểu được, và tôi tin rằng một khi bạn đã nắm được các đặc tính của nó thì bạn sẽ bắt kịp rất nhanh thôi.
Coi như bạn đã biết về Javascript và cả Canvas API đi.
Mà nếu có chưa biết mấy thì cũng đừng lo, bạn vẫn sẽ hiểu được phần lớn nội dung của phần này thôi.
Tất nhiên tôi sẽ không đi sâu vào chi tiết và một vài điều tôi nói có thể _không chính xác hoàn toàn_, nên đừng kỳ vọng nó được như "cầm tay chỉ việc" mà hãy coi nó như
### MỘT CÁI ÔM NỒNG ẤM
JavaScript rất thích hợp để thử nghiệm nhanh ; bạn chỉ việc viết vài hàm đơn giản, không có ràng buộc gì về kiểu dữ liệu, tuỳ ý thêm bớt các hàm của class, tải lại trang web là đã thấy ngay kết quả rồi,
sau đó lại sửa một tí, tải lại trang, cứ thế lặp lại, dễ như ăn kẹo.
Thế GLSL thì có gì khác JavaScript chứ.
Suy cho cùng thì cả 2 đều chạy trên trình duyệt mà, chúng đều vẽ một vài thứ lên màn hình đó thôi, mà riêng về khía cạnh đó thì dùng JavaScript dễ hơn.
Ừ thì, điểm khác biệt chính nằm ở chỗ Javascript là một ngôn ngữ **thông dịch (interpreted)** còn GLSL thì là một ngôn ngữ **biên dịch (compiled)**.
Một chương trình **biên dịch** được thực thi trực tiếp bởi hệ điều hành, là một chương trình bậc thấp và thường chạy rất nhanh.
Còn một chương trình **thông dịch** thì lại cần một [Máy ảo (Virtual Machine / VM)](https://en.wikipedia.org/wiki/Virtual_machine) để thực thi, nó là một chương trình bậc cao và thường chậm hơn.
Khi một trình duyệt (chính xác phải là _**máy ảo** JavaScript_ mới đúng) **thực thi** hoặc **thông dịch** một đoạn mã, nó chẳng biết biến nào có ý nghĩa gì hay hàm này sẽ cho kết quả gì (ngoại trừ **TypedArrays**).
Vì thế nó chẳng thể tối ưu bất kỳ cái gì _trước khi thực thi_ cả, nó còn cần thời gian để dịch code của bạn này, rồi thì đoán xem kiểu dữ liệu của các biến là gì này và nếu được thì cố gắng chuyển một phần sang dạng mã Assembly để code chạy nhanh hơn.
Đó là cả một quá trình cồng kềnh, phức tạp và có phần lề mề, nếu bạn hứng thú đi vào chi tiết thì tôi xin giới thiệu tìm hiểu [cách mà trình thông dịch V8 của Chrome hoạt động](https://developers.google.com/v8/).
Điểm tệ nhất đó là, mỗi trình duyệt lại tự tối ưu mã JavaScript theo một cách riêng mà quá trình này _hoàn toàn_ nằm ngoài tầm với của bạn.
Còn một chương trình **biên dịch** thì không như thế ; hệ điều hành thực thi nó, nếu chương trình không có lỗi biên dịch thì cứ thế mà chạy thôi.
Nếu bạn quên một dấu chấm phẩy cuối dòng thì khả năng cao là bạn sẽ được thông báo còn code của bạn thì thậm chí còn chưa được biên dịch thành chương trình cơ.
Hơi phũ, nhưng đó chính là cách mà **shader** hoạt động: _một chương trình được biên dịch để thực thi trên GPU_.
Đừng sợ! Một **trình biên dịch** sẽ là chiến hữu đáng tin cậy nhất của bạn.
Các ví dụ trong quyển sách này và [người đồng hành editor online](http://editor.thebookofshaders.com/) rất thân thiện.
Nó sẽ chỉ ra cho bạn thấy tại sao code của bạn không biên dịch được và bạn phải sửa chỗ nào, và nếu bạn làm đúng thì kết quả hiển thị ngay lập tức luôn.
Đó là một cách tuyệt vời để học vì nó rất trực quan và bạn chẳng phải sợ sẽ làm hỏng cái gì cả.
Điểm lưu ý cuối cùng, một chương trình **shader** được tạo nên bởi 2 chương trình con, đó là **vertex shader****fragment shader**.
Về cơ bản thì **vertex shader** sẽ nhận tham số đầu vào là các khối hình học rồi biến chúng thành các **điểm ảnh (pixel)** (hoặc *fragment*) rồi chuyển kết quả cho **fragment shader** xử lý tiếp, vốn công việc chính là tô màu từng điểm ảnh.
Quyển sách này hầu như chỉ tập trung vào chương trình thứ hai. Trong tất cả các ví dụ, khối hình được sử dụng chỉ là một tứ giác lấp đầy cả màn hình.
Vậy! Bạn đã sẵn sàng chưa ?
Tiếp tục nhé!
### Quy định kiểu dữ liệu một cách chặt chẽ
![Kết quả tìm kiếm đầu tiên với từ khoá 'strong type' trên Google Image, ngày 2016/05/20](strong_type.jpg)
Khi bạn đã quen với JavaScript hay các ngôn ngữ không quan trọng về kiểu dữ liệu, thì việc phải **quy định kiểu dữ liệu** cho mỗi biến là một khái niệm xa lạ, cũng khiến cho nó trở thành rào cản lớn nhất khi làm quen với GLSL.
**Kiểu dữ liệu**, như chính cái tên của nó, có nghĩa là bạn phải chỉ định mỗi biến (và cả hàm nữa) sử dụng kiểu dữ liệu gì.
Về cơ bản thì việc dùng chung một từ khoá **`var`** cho tất cả các biến đã không còn nữa.
GLSL không cho phép điều đó xảy ra nên bạn có muốn cũng không được.
Thay vì dùng từ khoá **`var`** thần thánh, bạn sẽ phải _chỉ đích danh kiểu dữ liệu cho từng biến một_, sau đó thì trình biên dịch sẽ biết chính xác đang phải xử lý cái gì và làm thế nào thì hiệu quả nhất. Nhược điểm của việc này là bạn phải hiểu tất cả các kiểu dữ liệu, mà lại còn phải hiểu tường tận nữa cơ.
May thay, chỉ có một vài kiểu dữ liệu thôi và cũng khá đơn giản nữa.
Nghe thì đáng sợ chứ thực ra nó không quá khác với code JavaScript mà bạn vẫn hay dùng đâu ; nếu một biến có kiểu `boolean` thì bạn sẽ trông đợi nó chỉ lưu trữ một trong hai giá trị `true` hoặc `false` mà thôi.
Nếu một biến được khai báo là `var uid = XXX;`, thì có khả năng đó là một số nguyên còn nếu nó được khai báo là `var y = YYY;` có thể nó trỏ tới một số thực.
Còn với ngôn ngữ **quy định kiểu dữ liệu (strong type)**, bạn sẽ không phí thời gian đoán xem 2 biến đó có cùng kiểu không, bằng các biểu thức `X == Y` (hay `typeof X == typeof Y` ? .. hoặc `typeof X !== null && Y...` ...) ; bạn sẽ biết chắc điều đó đúng hay sai mà kể cả bạn không để ý thì trình biên dịch sẽ làm thay việc đó.
Đây là các **kiểu dữ liệu đơn (scalar)** trong GLSL: `bool` (Đúng sai), `int`(Số nguyên), `float`(Số thực).
Còn vài kiểu nữa nhưng cứ từ từ, đoạn code mẫu dưới đây khai báo các biến (đừng quên **`var`** không tồn tại trong thế giới GLSL nhé):
```glsl
// Khai báo một biến boolean
JavaScript: var b = true; GLSL: bool b = true;
// Khai báo một số nguyên
JavaScript: var i = 1; GLSL: int i = 1;
// Khai báo một số thực
JavaScript: var f = 3.14159; GLSL: float f = 3.14159;
```
Không có gì khó phải không ? Như đã nói ở trên, nó thậm chí còn giúp bạn đỡ đau đầu khi code ấy chứ. Nếu còn nghi ngờ về điều đó thì cứ tạm bỏ qua, chỉ cần biết nó giúp chương trình của bạn chạy nhanh hơn JavaScript nhiều là cũng đủ rồi.
#### void
Có kiểu `void` tương đương với `null`, nó được dùng khi hàm không trả về kết quả gì cả.
Và bạn không thể khai báo biến kiểu này.
#### boolean
Các biến kiểu boolean hầu hết được sử dụng trong các câu lệnh điều kiện như ; `if( myBoolean == true ){}else{}`.
Nếu các nhánh điều kiện rất hay gặp ở CPU, thì [kiến trúc song song của GPU](http://thebookofshaders/01/) lại hạn chế đất diễn của chúng.
Thậm chí việc sử dụng các lệnh điều kiện còn không được khuyến khích ở đa phần các trường hợp, trong quyển sách này có một vài kỹ thuật để xử lý các trường hợp đó.
#### Ép kiểu
Như [Boromir](https://en.wikipedia.org/wiki/Boromir) đã nói, "One does not simply combine Typed primitives". Không như JavaScript, GLSL không cho phép thực hiện các phép toán giữa các toán hạng không cùng kiểu.
Ví dụ sau:
```glsl
int i = 2;
float f = 3.14159;
// thử nhân một số nguyên với một số thực
float r = i * f;
```
sẽ không cho kết quả tốt vì bạn đang cố lai con **_mèo_** với con **_hươu cao cổ_**.
Giải pháp là **ép kiểu (type casting)** ; đoạn code sau sẽ giúp _trình biên dịch tin rằng_ *`i`* cũng có kiểu `float`*`i`* vẫn giữ nguyên kiểu vốn có:
```glsl
// ép biến `i` từ kiểu int sang float
float r = float( i ) * f;
```
Điều này giống như việc cho con **_mèo_** mặc đồ của con **_hươu cao cổ_** vậy, và nó sẽ có hiệu quả (`r` sẽ kết quả của phép toán `i` x `f`).
Bạn có thể ép kiểu qua lại giữa tất cả các kiểu phía trên, chú ý là khi chuyển từ số thực sang số nguyên thì phần thập phân sẽ biến mất, tương đương với việc dùng hàm `Math.floor()`. Ép một số thực `float` hoặc một số nguyên `int` sang kiểu `bool` sẽ cho kết quả `true` nếu số đó khác 0.
#### Hàm khởi tạo (constructor)
Kiểu dữ liệu của biến cũng chính là hàm khởi tạo của class tương ứng ; thực tế thì một số thực `float` có thể coi là 1 _`instance`_ của class _`Float`_.
Các lệnh sau đây đều hợp lệ và cho kết quả giống nhau
```glsl
int i = 1;
int i = int( 1 );
int i = int( 1.9995 );
int i = int( true );
```
Trông thì không giống kiểu `scalar` lắm, và cũng na ná **ép kiểu**, nhưng mọi sự sẽ sáng tỏ ở phần *overload*.
OK, vậy là ta đã biết về ba `kiểu dữ liệu cơ bản`, những thứ mà bạn không thể sống nếu thiếu được và đương nhiên là GLSL còn nhiều kiểu khác.
### Vector
![Kết quả tìm kiếm đầu tiên với từ khoá 'vector villain' trên Google Image, ngày 2016/05/20](vector.jpg)
Trong cả JavaScript lẫn GLSL, bạn sẽ cần những cách tinh vi hơn để xử lý dữ liệu, và **`vectors`** khi đó rất hữu ích.
Tôi cho rằng bạn đã sử dụng class `Point` trong JavaScript để lưu 2 giá trị `x``y` cùng lúc rồi, code sẽ trông thế này:
```glsl
// Khai báo 'class':
var Point = function( x, y ){
this.x = x || 0;
this.y = y || 0;
}
// và bạn sẽ tạo một instance mới như sau
var p = new Point( 100,100 );
```
Như ta thấy, có quá nhiều điểm không hợp lý. Từ khoá **`var`** vồn dùng cho biến thì lại được dùng để khai báo class rồi thì **`this`** chả hiểu ở đâu ra, xong lại `x` với `y` chả biết kiểu dữ liệu gì ...
Kiểu này là không ổn với shader đâu.
Thay vào đó, GLSL có sẵn các cấu trúc dữ liệu để lưu trữ các biến đồng thời, có thể kể ra:
* `bvec2`: Vector boolean 2 chiều, `bvec3`: Vector boolean 3 chiều, `bvec4`: Vector boolean 4 chiều
* `ivec2`: Vector số nguyên 2 chiều, `ivec3`: Vector số nguyên 3 chiều, `ivec4`: Vector số nguyên 4 chiều
* `vec2`: Vector số thực 2 chiều, `vec3`: Vector số thực 3 chiều, `vec4`: Vector số thực 4 chiều
Bạn nhận ra là có đủ các loại **vector** cho mỗi kiểu dữ liệu cơ bản, tuyệt vời ông mặt giời.
Từ giải thích trên đây, ta có thể suy luận được rằng `bvec2` sẽ gồm 2 giá trị kiểu `bool` còn `vec4` sẽ gồm 4 số thực kiểu `float`
Một điểm mới nữa từ vector là các **chiều (dimension)**, không phải bạn dựng hình 2D thì dùng vector 2 chiều còn dựng hình 3D thì dùng vector 3 chiều đâu nhé. Nếu vậy thì vector 4 chiều dùng để dựng hình gì ? (Thực ra thì không gian 4 chiều có tên riêng đấy, là tesseract hoặc khối siêu lập phương - hypercube)
Nhưng không phải thế đâu nhé, từ **chiều** ở đây chỉ nói về số giá trị được lưu giữ trong mỗi **vector** thôi:
```glsl
// tạo một vector boolean 2 chiều
bvec2 b2 = bvec2 ( true, false );
// tạo một vector số nguyên 3 chiều
ivec3 i3 = ivec3( 0,0,1 );
// tạo một vector số thực 4 chiều
vec4 v4 = vec4( 0.0, 1.0, 2.0, 1. );
```
`b2` gồm 2 giá trị boolean, `i3` gồm 3 số nguyên `v4` gồm 4 số thực.
Làm thế nào để sử dụng từng giá trị trong vector?
Với kiểu dữ liệu đơn thì quá đơn giản ; nếu ta có `float f = 1.2;` thì biến `f` sẽ có giá trị `1.2`.
Còn với **vector** thì hơi khác và cũng kỳ diệu hơn một chút.
#### Truy cập
Có nhiều cách để truy cập các giá trị bên trong vector
```glsl
// Đầu tiên hãy tạo một vector số thực 4 chiều
vec4 v4 = vec4( 0.0, 1.0, 2.0, 3.0 );
```
Để lấy từng giá trị trong vector, ta có thể viết:
```glsl
float x = v4.x; // x = 0.0
float y = v4.y; // y = 1.0
float z = v4.z; // z = 2.0
float w = v4.w; // w = 3.0
```
rất dễ dàng, nhưng còn nhiều cách khác cũng cho kết quả tương tự:
```glsl
float x = v4.x = v4.r = v4.s = v4[0]; // x = 0.0
float y = v4.y = v4.g = v4.t = v4[1]; // y = 1.0
float z = v4.z = v4.b = v4.p = v4[2]; // z = 2.0
float w = v4.w = v4.a = v4.q = v4[3]; // w = 3.0
```
Nếu bạn tinh ý thì sẽ nhận ra:
* `X`, `Y`, `Z``W` được dùng để mô tả các vector trong đồ hoạ 3D
* `R`, `G`, `B``A` được dùng để mô tả các kênh màu và alpha
* `[0]`, `[1]`, `[2]``[3]` được dùng như một mảng cố định
Vậy nên tuỳ vào việc bạn đang xử lý các toạ độ 2D hay 3D, màu kèm theo alpha hoặc không, hay một vài con số bất kỳ, bạn có thể tuỳ chọn cách dùng **vector** bạn muốn.
Thường thì các toạ độ 2 chiều và các vector sẽ được lưu bằng các cấu trúc `vec2`, `vec3` hoặc `vec4`, màu sắc thì được lưu trong `vec3` hoặc `vec4` nếu bạn muốn lưu thêm kênh alpha nữa, không có ràng buộc nào cả.
Thậm chí, nếu bạn dùng cả một vector `bvec4` chỉ để lưu 1 giá trị boolean thì cũng được nốt, có điều hơi lãng phí bộ nhớ.
**Chú ý**: Trong shader, các giá trị của các kênh (`R`, `G`, `B`, `A`) đều được chuẩn hoá để nằm trong khoảng [0-1] chứ không phải [0x00-0xFF], vì thế tốt nhất là nên sử dụng vector số thực 4 chiều `vec4` để lưu trữ giá trị màu.
Hay quá phải không, chưa hết đâu nhé!
#### Tráo đổi (swizzle)
Ta có thể lấy ra nhiều hơn một giá trị nữa cơ ; giả sử bạn chỉ cần 2 giá trị `X``Y` từ một `vec4`, thì trong JavaScript, bạn sẽ phải làm như sau:
```glsl
var needles = [0, 1]; // vị trí của 'x' và 'y' trong array dưới đây
var a = [ 0,1,2,3 ]; // giả lập `vec4`
var b = a.filter( function( val, i, array ) {
return needles.indexOf( array.indexOf( val ) ) != -1;
});
// b = [ 0, 1 ]
// hoặc trực diện luôn
var needles = [0, 1];
var a = [ 0,1,2,3 ]; // giả lập `vec4`
var b = [ a[ needles[ 0 ] ], a[ needles[ 1 ] ] ]; // b = [ 0, 1 ]
```
Quá cồng kềnh. Hãy xem trong GLSL làm như thế nào:
```glsl
// Tạo một `vec4`
vec4 v4 = vec4( 0.0, 1.0, 2.0, 3.0 );
// rồi chỉ lấy mỗi x và y ra
vec2 xy = v4.xy; // xy = vec2( 0.0, 1.0 );
```
Ủa cái gì vậy ?! Khi bạn **truy cập liên tiếp (concatenate accessors)**, GLSL sẽ trả về tập con của các giá trị bạn muốn, gói gọn trong một **vector** khác.
Thực ra thì vector là một cấu trúc dữ liệu cho phép truy cập ngẫu nhiên, nếu muốn bạn có thể tượng tượng nó giống array bên JavaScript vậy.
Vì thế, bạn không chỉ lấy được 1 tập con mà còn có thể chỉ định **thứ tự từng phần tử** muốn lấy nữa cơ. Đoạn code sau sẽ tráo đổi giá trị của các vector theo thứ tự ngược lại:
```glsl
// Tạo một vector R,G,B,A
vec4 color = vec4( 0.2, 0.8, 0.0, 1.0 );
// Truy cập các gía trị theo thứ tự ngược lại
vec4 backwards = v4.abgr; // backwards = vec4( 1.0, 0.0, 0.8, 0.2 );
```
Hơn thế nữa, chẳng ai ngăn bạn lấy một phần tử nhiều lần:
```glsl
// Tạo một vector R,G,B,A
vec4 color = vec4( 0.2, 0.8, 0.0, 1.0 );
// Và tạo một vector mới chỉ dùng giá trị của kênh G (2 lần) và A
vec3 GAG = v4.gag; // GAG = vec4( 0.8, 1.0, 0.8 );
```
Khă năng này quá ngầu khi phải xử lý vector, ví dụ như khi chỉ muốn lấy các kênh RGB của một màu có đủ RGBA chẳng hạn.
#### Overload tất cả
Ở phần kiểu dữ liệu, tôi đã nhắc tới điều gì đó liên quan tới **hàm khởi tạo (constructor)** và đây lại là 1 tính năng tuyệt vời nữa của GLSL ; **overload**.
Cho ai chưa biết, **overload** là một toán tử hoặc hàm số đại loại sẽ _'tự động thay đổi cách thực thi sao cho khớp với kiểu dữ liệu'_.
JavaScript không có overload, nên bạn có thể thấy nó hơi lạ lúc đầu, nhưng khi đã quen rồi thì bạn sẽ thắc mắc sao JavaScript lại không có tính năng này (ngắn gọn là do không ràng buộc kiểu dữ liệu đó).
Hãy xem ví dụ đơn giản nhất để hiểu overload là gì:
```glsl
vec2 a = vec2( 1.0, 1.0 );
vec2 b = vec2( 1.0, 1.0 );
// overload phép cộng
vec2 c = a + b; // c = vec2( 2.0, 2.0 );
```
HẢ ? Hai giá trị không phải số đơn thuần mà cũng cộng được ?!
Chính xác là thế đó. Tất nhiên là áp dụng cho toàn bộ các toán tử khác (`+`, `-`, `*` & `/`) nữa nhưng đây mới là mở đầu thôi.
Hãy xem đoạn code sau:
```glsl
vec2 a = vec2( 0.0, 0.0 );
vec2 b = vec2( 1.0, 1.0 );
// overload hàm khởi tạo
vec4 c = vec4( a , b ); // c = vec4( 0.0, 0.0, 1.0, 1.0 );
```
Ta vừa mới tạo nên một `vec4` từ 2 `vec2`, bằng cách gán giá trị của `a.x``a.y` cho `X`, `Y` của `vec4`, rồi lại gán tiếp `b.x``b.y` cho `Z`, `W` của `vec4`
Đây là điều sẽ xảy ra khi một **hàm** được overload để chấp nhận các loại tham số khác nhau, cụ thể trong trường hợp này là hàm khởi tạo của `vec4`.
Điều đó có nghĩa là rất nhiều phiên bản khác nhau của cùng 1 hàm có thể cùng tồn tại, ví dụ các lệnh khai báo sau hoàn toàn hợp lệ:
```glsl
vec4 a = vec4(1.0, 1.0, 1.0, 1.0);
vec4 a = vec4(1.0);// cả 4 giá trị x, y, z, w đều bằng 1.0
vec4 a = vec4( v2, float, v4 );// vec4( v2.x, v2.y, float, v4.x );
vec4 a = vec4( v3, float );// vec4( v3.x, v3.y, v3.z, float );
etc.
```
Điều duy nhất bạn cần bận tâm là đảm bảo hàm tạo có đủ dữ liệu nó cần mà thôi.
Điều cuối cùng, bạn hoàn toàn có thể overload một hàm có sẵn bất kỳ sao cho nó phù hợp với yêu cầu của bạn (cũng không nên lạm dụng quá).
#### Các kiểu dữ liệu khác
Vector thật thú vị, và là vũ khí chính trong code shader.
Còn các cấu trúc dữ liệu khác như Ma trận và Texture sampler sẽ được bàn tới ở phần sau của quyển sách.
Bạn cũng có thể dùng Array. Tất nhiên là phải quy định kiểu rồi và sau đây là một vài điểm đáng lưu tâm:
* Kích thước Array cố định
* Không thể dùng các hàm push(), pop(), splice() vân vân và cũng không có thuộc tính ```length``` luôn nha
* Không thể gán giá trị hàng loạt khi khởi tạo mà phải gán giá trị từng phần tử một
Code như sau sẽ không đúng:
```glsl
int values[3] = [0,0,0];
```
phải như thế này cơ:
```glsl
int values[3];
values[0] = 0;
values[1] = 0;
values[2] = 0;
```
Điều này cũng không quá tệ nếu bạn thật sự cần can thiệp từng phần tử trong array.
Còn nếu muốn đa dạng hơn thì có thể dùng kiểu ```struct```. Chúng giống như các _object_ nhưng không có hàm đi kèm ;
chúng chỉ cho phép đóng gói các biến vào bên trong thôi
```glsl
struct ColorStruct {
vec3 color0;
vec3 color1;
vec3 color2;
}
```
sau đó bạn có thể sử dụng như sau:
```glsl
// Khởi tạo cấu trúc với giá trị nào đó
ColorStruct sandy = ColorStruct( vec3(0.92,0.83,0.60),
vec3(1.,0.94,0.69),
vec3(0.95,0.86,0.69) );
// Truy cập biến trong cấu trúc
sandy.color0 // vec3(0.92,0.83,0.60)
```
Cú pháp kiểu này có thể hơi tự do chút, nhưng nó sẽ giúp bạn viết code rõ ràng hơn hay ít nhất là nhìn dễ hiểu hơn.
#### Các lệnh điều khiển
Cấu trúc dữ liệu hay thật đấy nhưng tới lúc nào đó ta vẫn _có thể_ phải cần tới các lệnh điều khiển.
May mán là cú pháp rất giống với JavaScript.
Ví dụ về lệnh điều kiện:
```glsl
if( condition ){
//true
}else{
//false
}
```
Còn một vòng lặp thì thường giống như:
```glsl
const int count = 10;
for( int i = 0; i <= count; i++){
// làm gì đó
}
```
hoặc với iterator:
```glsl
const float count = 10.;
for( float i = 0.0; i <= count; i+= 1.0 ){
// làm gì đó
}
```
Chú ý là biến ```count``` phải được khai báo là một ```hằng số (constant)```.
Điều đó có nghĩa là ta phải đặt thêm từ khoá ```const``` vào trước như ví dụ dưới đây.
Ngoài ra ta cũng có các lệnh ```break``` và ```continue```:
```glsl
const float count = 10.;
for( float i = 0.0; i <= count; i+= 1.0 ){
if( i < 5. )continue;
if( i >= 8. )break;
}
```
Chú ý là trên một vài nền tảng phần cứng, lệnh ```break``` không hoạt động giống nhau nên vòng lặp vẫn chạy.
Nhìn chung thì bạn nên giữ số vòng lặp càng ít càng tốt và tránh sử dụng các lệnh điều kiện càng nhiều càng tốt.
#### qualifiers
Không chỉ có kiểu dữ liệu mà GLSL còn dùng **qualifier** để giúp trình biên dịch biết mỗi biến có gì đặc biệt.
Ví dụ có những dữ liệu chỉ được truyền từ CPU sang GPU gọi là **attribute****uniform**.
Từ khoá **attribute** được dùng trong vertex shader, còn **uniform** được dùng cho cả vertex shader và fragment shader.
Từ khoá ```varying``` để đánh dấu các biến luân chuyển giữa vertex shader và fragment shader.
Tôi sẽ không đi vào chi tiết ở đây vì ta chủ yếu tập trung vào **fragment shader** nhưng ở phần sau của quyển sách, bạn sẽ thấy code tương tự như:
```glsl
uniform vec2 u_resolution;
```
Tôi mới đặt từ khoá ```uniform``` vào trước kiểu dữ liệu của biến khi khai báo đó.
Điều này có nghĩa là CPU sẽ gửi thêm thông tin cho shader thông qua biến này. Cụ thể đó là độ phân giải của canvas, chiều rộng và chiều cao của canvas sẽ được lưu vào biến x và y của một vector 2 chiều.
Khi trình biên dịch thấy một biến có đánh dấu qualifier này, nó sẽ đảm bảo bạn không thể thay đổi giá trị trong shader.
Điều tương tự cũng được áp dụng cho biến ```count``` được dùng để giới hạn số vòng lặp ```for```:
```glsl
const float count = 10.;
for( ... )
```
Khi ta dùng qualifier ```const```, trình biên dịch sẽ đảm bảo rằng giá trị của biến này chỉ được khởi tạo một lần duy nhất, nếu không thì nó không phải là hằng số nữa rồi.
Còn 3 qualifier hay được dùng nữa cho các tham số của hàm là : ```in```, ```out``` và ```inout```.
Trong JavaScript, khi bạn truyền giá trị vào 1 hàm thì mọi thay đổi với giá trị đó chỉ có tác dụng trong hàm chứ không ảnh hưởng gì tới biến bên ngoài.
```glsl
function banana( a ){
a += 1;
}
var value = 0;
banana( value );
console.log( value );// > 0 ; ra khỏi hàm thì value vẫn giữ nguyên giá trị như trước khi gọi hàm
```
Ý nghĩa của 3 qualifier tham số:
* ```in``` không thay đổi giá trị của biến bên ngoài (mặc định cho mọi tham số)
* ```out``` chỉ dùng để lưu giá trị mới mà vẫn giữ nguyên khi đã ra khỏi hàm
* ```inout``` tuỳ ý đọc ghi
Viết lại hàm banana trong GLSL:
```glsl
void banana( inout float a ){
a += 1.;
}
float A = 0.;
banana( A ); // lúc này A = 1.;
```
Điều này rất khác so với JavaScript và cũng rất tiện.
#### Không gian và toạ độ
Chú ý cuối cùng, trong DOM và Canvas 2D, trục Y hướng xuống dưới.
Điều này có lý trong bối cảnh DOM dựng trang web có thể scroll được ; từ trên xuống dưới.
Còn trong canvas của WebGL thì trục Y hướng lên trên.
Điều đó có nghĩa là gốc tọa độ, điểm (0, 0) nằm ở góc dưới cùng bên trái của WebGL canvas, chứ không phải góc trên cùng bên trái như 2D Canvas.
Toạ độ của texture cũng vì thế mà có thể gây lú một chút nếu chưa quen.
## Và thế là hết !
Tất nhiên ta có thể đi sâu hơn vào rất nhiều khái niệm phía trên, nhưng nhớ rằng đây chỉ là lời chào dành cho người mới.
Đúng là có rất nhiều thứ phải tìm hiểu nhưng với sự kiên trì và chăm chỉ thì sẽ quen nhanh thôi.
Tôi hy vọng bạn thấy một vài phần hữu ích ở đây, giờ thì bạn sẵn sàng bắt đầu chuyển phiêu lưu vào quyển sách chưa ?

@ -0,0 +1,3 @@
## Giới thiệu về vector
Đang hoàn thiện...

@ -0,0 +1,3 @@
## Giới thiệu về nội suy
Đang hoàn thiện...

@ -0,0 +1,15 @@
# Phụ lục
1. [Làm thế nào để đọc quyển sách này mà không cần Internet ?](00/?lan=vi)
2. [Làm thế nào để chạy thử ví dụ trên Raspberry Pi ?](01/?lan=vi)
3. [Làm thế nào để in quyển sách này ?](02/?lan=vi)
4. [Làm thế nào để góp sức cho quyển sách này ?](03/?lan=vi)
5. [Giới thiệu cho người đã biết Javascript](04/?lan=vi) từ [Nicolas Barradeau](http://www.barradeau.com/)
6. [Giới thiệu về vector](05/?lan=vi) từ ...
7. [Giới thiệu về nội suy](06?lan=vi) từ ...

@ -0,0 +1,5 @@
# Thư viện các ví dụ
<p class="gallery_author">Tuyển chọn bởi <a href="https://www.kynd.info">kynd</a>(<a href="https://twitter.com/kyndinfo">@kyndinfo</a>) và Patricio Gonzalez Vivo(<a href="https://twitter.com/patriciogv">@patriciogv</a>)</p>
Đây là một bộ sưu tập các ví dụ được trích từ các chương của quyển sách kèm theo các shader được những độ giả khác đóng góp trên [editor online](http://editor.thebookofshaders.com/). Bạn có thể tự do khám phá và sửa code tuỳ ý. Nếu bạn thấy ưng ý với bất kỳ tác phẩm nào của mình, bạn có thể click nút "Export" và copy "URL to code...". Sau đó gửi tới địa chỉ [@bookofshaders](https://twitter.com/bookofshaders) hoặc [@kyndinfo](https://twitter.com/kyndinfo). Chúng tôi rất mong chờ được bổ sung các tác phẩm của bạn vào thư viện.
Loading…
Cancel
Save