Merge pull request #359 from vnlc/master

Edit VI translation: check typos, rephrase, fix broken URL
pull/352/head^2
Patricio Gonzalez Vivo 2 years ago committed by GitHub
commit 23508a6528
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,7 +12,7 @@ Fragment shader cho bạn toàn quyền kiểm soát các điểm ảnh đượ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.
Ở các chương tiếp theo, bạn sẽ khám phá ra 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 ?
@ -23,8 +23,7 @@ Quyển sách này sẽ chỉ cho bạn cách dùng các shader và cách áp d
## 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.
(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ễ.

@ -1,7 +1,7 @@
# Mở đầu
## Fragment shader là gì
## 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ì ?
Ở 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)
@ -11,7 +11,7 @@ Shader cũng là một tập hợp các chỉ dẫn, nhưng các chỉ dẫn đ
![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 ?
## 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*.
@ -21,7 +21,7 @@ Hãy tưởng tượng CPU là một dây chuyền công nghiệp lớn, và m
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 ?
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)
@ -29,21 +29,21 @@ Quay trở lại phép so sánh về dây chuyền và tác vụ. Mỗi điể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.
Hình dung các bộ vi xử lý tí hon là hàng loạt những ống xếp dọc và kết lại với nhau thành một cái bàn, 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 vào một ống đơn lẻ trong một giây sẽ rất khó. Nhưng khi rất 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)
[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 khuyên bạn nên xem qua: [openglbook.com/chapter-0-preface-what-is-opengl.html](http://openglbook.com/chapter-0-preface-what-is-opengl.html)
## Shader nổi tiếng khó nhằn, sao lại thế ?
## 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.
Như chú Ben (một nhân vật trong phim Spider Man) 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.
Để 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. Việc cho phép các thread liên thông với nhau (thread-to-thread) sẽ gây rủi ro cho tính toàn vẹn của dữ liệu.
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!
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!

@ -2,7 +2,7 @@
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!
Ở đị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 một cách nồng nhiệt nhất!
<div class="codeAndCanvas" data="hello_world.frag"></div>

@ -50,12 +50,12 @@ Trong shader ta không có nhiều cách để debug lắm bên cạnh việc th
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 ?
* 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ó thể sử dụng `u_mouse` để lấy giá trị điểm ảnh CHƯA chuẩn hoá *(normalize)*? Bạn có thể dùng nó để thay đổi màu khi di chuyển con trỏ chuột?
* 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 ?
* 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.

@ -26,7 +26,7 @@ Nếu bạn thích làm việc offline bằng [SublimeText](https://www.sublimet
![](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/).
**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 một 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)
@ -40,7 +40,7 @@ Trong trường hợp bạn đã có kinh nghiệm lập trình với các frame
### 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.
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ử.
@ -190,7 +190,7 @@ void ofApp::draw(){
}
```
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:
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, chấ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
//--------------------------------------------------------------
@ -224,7 +224,7 @@ void ofApp::draw(){
![](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.
3. Sử dụng ảnh trong chất liệu. Tên của ảnh sẽ dựa trên tên của file shader.
![](blender/02.png)

@ -1,7 +1,7 @@
# 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 thun thục kỹ năng này, võ karate-shader của bạn sẽ càng cao siêu.
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 thun 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)
@ -9,7 +9,7 @@ Cấu trúc code dưới đây sẽ là hàng rào của chúng ta. Trong đó,
<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.
**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.
@ -19,25 +19,25 @@ Cấu trúc code dưới đây sẽ là hàng rào của chúng ta. Trong đó,
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.
[`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`.
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à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.
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 ?
Ở 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);
@ -47,7 +47,7 @@ float y = smoothstep(0.2,0.5,st.x) - smoothstep(0.5,0.8,st.x);
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.
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)
@ -55,7 +55,7 @@ Mặc dù rất khó để mô tả mối liên hệ giữa các hàm lượng g
<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 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:
@ -63,21 +63,21 @@ Hãy thử các thay đổi sau và xem điều gì xảy ra:
* 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.
* 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.
* 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.
* 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í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).
* 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.
* 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!
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)
@ -125,7 +125,7 @@ Hãy nhìn vào bảng các phương trình dưới đây, được tạo bởi
#### 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.
Đây là một vài công cụ 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.

@ -13,7 +13,7 @@ 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]`).
Đị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 của 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:
@ -55,7 +55,7 @@ Hãy xem đoạn code dưới đây và chú ý vào dòng 18 vì tôi sẽ sử
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.
* 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.
@ -115,7 +115,7 @@ Hãy thử sửa code sao cho:
![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ố)
* 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)

@ -68,7 +68,7 @@ Quyển sách này sẽ hướng dẫn người đọc khám phá dần vũ tr
## Tác giả
[Patricio Gonzalez Vivo](http://patriciogonzalezvivo.com/) (sinh năm 1982 tại Buenos Aires, Argentina) là artist kiêm developer ở New York, Anh ấy khám phá sự giao thoa giữa hữu cơ và vô cơ, analog và kỹ thuật số, cá nhân và tập thể. Trong công việc, anh ấy dùng code như một cách đóng góp tạo nên những điều tốt đẹp hơn cho cộng đồng.
[Patricio Gonzalez Vivo](http://patriciogonzalezvivo.com/) (sinh năm 1982 tại Buenos Aires, Argentina) là artist kiêm developer ở New York Anh ấy khám phá sự giao thoa giữa hữu cơ và vô cơ, analog và kỹ thuật số, cá nhân và tập thể. Trong công việc, anh ấy dùng code như một cách đóng góp tạo nên những điều tốt đẹp hơn cho cộng đồng.
Patricio đã học tập và công tác trong ngành tâm lý trị liệu và nghệ thuật biểu cảm. Anh ấy có bằng Thạc sỹ ngành Thiết kế và Công nghệ từ trường Parson The New School, cũng là nơi anh ấy đang giảng dạy. Hiện tại, anh ấy là Kỹ sư đồ hoạ tại Mapzen để phát triển các công cụ cho openSource.
@ -80,7 +80,7 @@ Patricio đã học tập và công tác trong ngành tâm lý trị liệu và
## Lời cảm ơn
Cảm ơn [Scott Murray](http://alignedleft.com/) vì niềm cảm hứng và các video.
Cảm ơn [Scott Murray](http://alignedleft.com/) vì niềm cảm hứng và những lời khuyên.
Cảm ơn [Kenichi Yoneda (Kynd)](https://twitter.com/kyndinfo), [Nicolas Barradeau](https://twitter.com/nicoptere), [Karim Naaji](http://karim.naaji.fr/) vì đã hỗ trợ cả về ý tưởng lẫn code.
@ -102,6 +102,8 @@ Cảm ơn [Michael Tischer](http://www.mitinet.de) vì [Bản dịch tiếng Đ
Cảm ơn [Sergey Karchevsky](https://www.facebook.com/sergey.karchevsky.3) vì [Bản dịch tiếng Nga (russian)](?lan=ru)
Cảm ơn [Vu Phuong Hoang](https://github.com/DancingPhoenix88) và [Minh-Phuc Bui](https://github.com/phucbm) vì [Bản dịch tiếng Việt](?lan=vi)
Cảm ơn [Andy Stanton](https://andy.stanton.is/) vì đã sửa lỗi và cải tiến [cách export quyển sách ra định dạng pdf/epub](https://thebookofshaders.com/appendix/02/?lan=vi)
Cảm ơn tất cả mọi người đã tin tưởng, [cùng sửa lỗi](https://github.com/patriciogonzalezvivo/thebookofshaders/graphs/contributors) và quyên góp cho dự án này.

Loading…
Cancel
Save