Hướng dẫn sử dụng Shader trong Cocos2d-x (P1)

Tram Ho

Hôm nay mình sẽ hướng dẫn các bạn sử dụng shader vào project Cocos2d-x của các bạn

Vd1: Simplest Shader

Có một số loại shader OpenGL khác nhau nhưng trong Cocos2D bạn sẽ chỉ cần xử lý hai loại là vertex shader và fragment shader. Hay nhất là fragment shader hoặc “pixel shader” [^ 1]. Đây chỉ là một chương trình nhỏ mà GPU chạy mỗi lần cho mỗi pixel mà sprite bao phủ trên màn hình. Mỗi CCShader cần cả vertex shader và fragment shader. Cocos2D đã có trình tạo vertex shader mặc định được tích hợp sẵn, vì vậy bạn thường không cần phải viết lại.

[^ 1]: “fragment” là gì? fragment được coi là một pixel trong OpenGL. Pixel cuối cùng được hiển thị trên màn hình có thể là một vài fragment được trộn với nhau. Khi thực hiện kết xuất 3D, một fragment có thể bị che lấp bởi một fragment khác được hiển thị sau đó hoặc một fragment có thể bị xóa đi vì nó nằm sau fragment hiện tại đã được hiển thị.

Loading Shaders:

Trước khi đi vào phần code shader, bạn cần phải nắm được làm cách nào để load được chúng. Trong Cocos2D-x có thể làm điều này một cách dễ dàng và mọi thứ trong hướng dẫn này đều đang giả định rằng bạn đang dùng bản 3.17 hoặc mới hơn. Xem đoạn code dưới đây để nhận biết cách load và sử dụng shader cho sprite của bạn.

Đầu tiên chúng ta các path tới các file shader. Sau đó các bạn có thể tạo chương trình chạy GL bằng GLProgram::createWithFilenames.
Và cuối cùng là chúng ta apply chương trình GL này vào một sprite nào đó.

Dive In:

Đây là một đoạn code đơn giản,nó thực hiện tô màu đỏ lên toàn bộ sprite.

Nội dung file SimplestShader.fsh:

Nội dung file SimplestShader.vsh:

Shader cho Cocos2D được viết bằng ngôn ngữ GLSL được của OpenGL. Bạn có thể cảm thấy quen thuộc vì nó khá giống C.

Mỗi GLSL fragment shader cần 2 thứ:

  1. Nó cần có một hàm main() để gọi mỗi lần run shader.

  2. Nó cần gán màu cho biến gl_FragColor.

Open GL sử dụng vertor để lưu màu sắc và vị trí. Để cho ra một màu thì bạn chỉ cần cung cấp nó 4 giá trị của red, green, blue và alpha. Bạn có thể khởi tạo GLSL băng một vài cách dưới đây:

Vd2: Sprite Color

Ví dụ 1 đã dạy chúng ta cách sử dụng shader để vẽ màu đỏ vào sprite.

Ví dụ tiếp theo này là shader mặc định của Cocos2D và nó chịu trách nhiệm cho hầu hết các kết xuất hình ảnh trong các game Cocos2D (tức là đầu vào thế nào thì đầu ra nó ý chang vậy, vd như đầu vào là một nhân vật game thì nó sẽ vẽ nhân vật game đó lên trên màn hình mà ko có bất cứ thay đổi nào khác).

Sampling Textures:

Các bạn cứ suy nghĩ đơn giản nhưng là lúc chúng ta vẽ tranh vậy lên mặt phẳng vậy. Chúng ta cần biết tại toạ độ này thì chúng ta cần vẽ gì:

Tôi chưa kiếm ra một bức ảnh ưng ý để diễn tả lại ý nghĩa câu trên. Ở hình trên thì nó đã vẽ 1 vùng toạ độ texture này vào vùng toạ độ khác rồi. Còn ở các vd của chúng ta đi qua thì chỉ mới vẽ từng điểm ảnh lên các toạ độ thôi. Thôi thì các bạn chịu khó tưởng tượng đi nhé, lol :v .

Hãy chia nhỏ dòng đầu tiên:

texture2D() là một hàm được tạo sẵn trong GLSL dùng để lấy mẫu texture tại một vị trí cố định. Nó có input là Texture và toạ độ nào đó.

cc_MainTexture là một biến tạo sẵn trong Cocos2D để lấy được texture của sprite hiện tại được render.

cc_FragTexCoord1 là một biến được tạo sẵn trong Cocos2D. Nó là một toạ độ texture mà bạn dùng trong a fragment shader này để có thể vẽ chính xác sprite.

Kết hợp tất cả lại, texture2D(ccMainTexture, ccFragTexCoord1) lấy một màu từ texture của sprite tại toạ độ cc_FragTexCoord1.

Mình nói thêm một tí cho các bạn chưa hình dung được cách hoạt động của nó:

vd như bức ảnh sprite của các bạn là 32×32 render lên một vùng là 64×64 chẳng hạn (dĩ nhiên output của bạn sẽ là bức ảnh to gấp 2 bức ảnh ban đầu).

Như vậy để output ra bức ảnh 64×64 pixel thì chúng ta cần “tô màu” lên từng pixel trong 64×64.

Tại vị trí (0,0), (1,0), (0,1), (1,1) của bức ảnh 64×64 thì sẽ lấy ra màu của bức ảnh 32×32 tại vùng (0,0) (tại vì ouput chúng ta là bức ảnh to gấp đôi nên 1 pixel tại vùng ảnh cũ, có thể tô lên 4 điểm ảnh tại vùng ảnh mới).

Tại vị trí (2,0), (2,1), (3,0), (3,1) của bức ảnh 64×64 thì sẽ lấy ra màu của bức ảnh 32×32 tại vùng (1,0)

Ở đây mình chỉ nêu lên cho các bạn tưởng tượng cách hoạt động thôi, thực tế thì toạ độ đã được chuẩn hoá về đoạn 0 -1 mất rồi, Và quá trình này là chạy song song chứ ko duyệt qua từng pixel. Nói chung các bạn cứ mường tượng cách nó hoạt động trước, chi tiết bên trong thế nào thì từ từ sẽ rõ hơn.

Color Math:

Sprites trong Cocos2D có màu trộn màu. Vd như khi màu sắc trở nên tối hơn thì sprite cũng tối hơn. Khi màu càng đỏ thì sprite càng đỏ. Khi màu sắc trong suốt hơn thì sprite cũng vậy.

Vì vậy, làm thế nào để bạn thực hiện trộn màu trong một shader?. Tất cả bạn cần làm là nhân hai màu với nhau. Ở đây có 2 cách mà bạn có thể thực hiện.

Lưu ý: cc_FragColor là biến được tạo sẵn trong Cocos2D, nó cung cấp màu mặc định của sprite tại vùng toạ độ này (cc_FragTexCoord1).
Đoạn code thứ 2 là nhân 2 vecto với nhau, cách nó hoạt động giống như trong đoạn 1. Có đều nó gọn hơn. Nếu chung ta ko cần xử lí riêng biệt cho từng kênh màu red, blue, green, alpha mà chỉ đơn thuần nhân tương ứng từng toạ độ thì nên dùng 2 để code được gọn gàn hơn.

Exercises:

Thay đổi shader để làm sáng sprite bằng màu của nó.

Vd3: Sprite Distort

Ví dụ 2 cho bạn thấy làm thế nào để vẽ một texture trong shader, nhưng điều đó vẫn thực sự cơ bản. Bây giờ chúng ta hãy thử một cái gì đó thú vị hơn mà bạn chỉ có thể làm với một shader. Điều gì xảy ra nếu chúng ta thay đổi tọa độ texture được tính toán trước khi truy cập vào texture?

Cho đến thời điểm này, chúng ta chỉ sử dụng cho các vectơ thành phần cho màu sắc có 4 thành phần (r,g,b,a). Bạn có thể tạo vectơ với 2 hợc 3 thành phần. Vd như ccFragTexCoord1 là tọa độ x / y, vì vậy chúng ta cần lưu nó trong biến vec2.

Biến cc_Time là biến hợp sẵn của Cocos2D. Đây là một vectơ 4 thành phần lưu trữ các giá trị liên quan đến thời gian.

Một cách khác để truy cập các vectơ. Bạn có thể sử dụng xyzw hoặc stpq hoặc mảng thay vì rgba.

Các hàm GLSL dựng sẵn khác link.

Vì vậy, bây giờ thay vì đọc tọa độ texture trực tiếp như Cocos2D thì hãy sử dụng phép toán sin để làm biến dạng texture.

Điều quan trọng cần ghi nhớ là một fragment shader không thể thay đổi pixel mà sprite sẽ vẽ vào (ý là bức ảnh texture của sprite, chúng ta không thay đổi được giá trị input của nó).

Nó chỉ có thể thay đổi những pixel mà nó vẽ ra (giống như khi các bạn trộn màu với nhau vậy, ví dụ như input tại toạ độ đó là màu trắng, các bạn không thể thay đổi màu input của nó như các bạn có thể trộn màu trắng thành màu nâu chẳng hạn, và ouput của các bạn tại toạ độ này là màu nâu).

Texture Coordinates:

Điều quan trọng cần biết là toạ độ texture được chuẩn hóa và không tính bằng pixel (Chuẩn hoá tức là đưa hết các chiều dài và rộng của nó về đoạn 0->1, vd như bức ảnh texture của bạn là 32×64 pixel các bạn muốn lấy ra màu sắc tại vùng trung tâm của nó thì ko thể nhập toạ độ là 16×32 được, vì nó đã chuẩn hoá 32 và 64 thành 0 tới 1 hết rồi, tức là chúng ta sẽ dùng (0.5, 0.5) để lấy được màu tại vùng trung tâm texture).

Chú ý là (0, 0) là phía dưới bên trái của texture và (1, 1) là phía trên bên phải. Nếu bạn muốn áp dụng các biến dạng cho texture của sprite, bạn cần lưu ý kích thước và tỷ lệ khung hình của texture.

Exercises:

Hãy thử biến dạng texture của bạn theo nhiều cách khác nhau.

Ở các phần tiếp theo mình sẽ tiếp tục hướng dẫn các bạn tạo ra một vài hiệu ứng hay ho bằng cách sử dụng shader

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo