+2

Giới thiệu khái quát về Shader trong Unity3D

Hi mọi người, Trong bài viết lần trước mình đã giới thiệu qua cho các bạn về khái niệm về shader. Trong bài viết lần này mình sẽ giới thiệu kỹ hơn về shader trong Unity3D, các thành phần chính trong 1 shader.

Luồng hiển thị trong Unity (Rendering Workflow)

hình ảnh dưới đây sẽ cho chúng ta thấy rõ 3 thành phần của việc hiển thị một đối tượng nào đó trên màn hình: shaderintro01.png

3D Model: đây là những đối tượng được thiết kế bởi các artist 3d. ví dụ như: con gà, con bò, oto, xe máy... các đối tượng này ở trong Unity dc hiểu là 1 tập hợp các điểm tọa độ trong không gian 3d(vertices). Những điểm này có thể kết nối với nhau để tạo thành các hình tam giác (Triangles). mỗi điểm này có thể chứ các thông tin về màu, hướng và tọa độ để gán texture lên đó(hay còn gọi là UV Data). Một đối tượng trong Unity không thể được hiển thị nếu không có 1 material gán vào nó. Một material là 1 lớp bao bọc trên bề mặt đối tượng, nó chứa shader cùng các giá trị và các thuộc tính mà dựa vào đó Unity sẽ hiển thị đối tượng dựa trên những dữ liệu đó. Nhiều material có thể chia sẻ dữ liệu từ cùng một shader và chúng ta có thể thay đổi các dự liệu đó tùy theo yêu cầu của chúng ta đối với từng đối tượng. Ví dụ, hai material có thể dùng chung 1 shader nhưng chúng ta có thể đổi màu của chúng thành màu xanh hoặc đỏ hoặc bất kì một màu nào đó, nếu bạn gán 2 material lên hai đối tượng nào đó, ví dụ như chúng ta có thể gán 2 material đó lên hai chiếc ghế giống nhau thì kết quả sẽ là hai cái ghế với hai màu khác nhau.

Cấu trúc của 1 file Shader

Hiện tại, Unity hỗ trợ 2 kiểu shader: surface shader và fragment and vertex shader. Ngoài ra, chúng ta còn có thể thấy một kiểu shader khác trong các phiên bản Unity cũ trước đây đó là Fixed Function nhưng hiện tại loại shader đó đã được thay thế hoàn toàn bởi 2 kiểu shader trên nên chúng ta sẽ không nói đến nó trong bài viết này. Surface shader và Fragment and Vertex shader đều có cùng 1 cấu trúc:

ântomy.PNG

Như bạn có thể thấy dòng đầu tiên bắt đầu bằng Shader và theo sau đó là tên của shader đó. tiếp theo là phần Properties và SubShader. chúng ta sẽ đi sâu vào từng phần của file shader trong phần tiếp theo.

Properties trong file shader

Properties(các thuộc tính) của shader tương tự như các biến public trong file C#, có nghĩa là chúng ta có thể sửa chúng ngay trên Unity Editor nhưng có một khác biệt so với các biến đó là khi có một sự thay đổi trên material thì sự thay đổi đó sẽ được áp dụng ngay cho material đó kể cả khi bạn đang chạy game. Đoạn code dưới đây sẽ cho các bạn thấy các kiểu thuộc tính cơ bản có trong shader:

property.PNG

Thuộc tính đầu tiên có kiểu 2D, có nghĩa là chúng ta có thể gán 1 bức ảnh vào material chứa shader này. có vài kiểu ảnh khác nhau được gán cho thuộc tính này ví dụ như kiểu normal map như ở thuộc tính thứ hai.

Các thuộc tính tiếp theo là integer, float, Range(đây là kiểu giá trị được giới hạn sẵn trong khoảng nào đó và chúng ta sử dụng thanh trượt để chọn giá trị cho nó). ngoài ra còn có vector và color là các kiểu cũng khá thường dùng trong shader. tương ứng với đoạn code trên, khi chúng ta sử dụng shader đó trong unity chúng ta sẽ thấy đoạn code đó được hiển thị lên Inspector như sau:

shaderintro02.png

việc khai báo các thuộc tính ở trong phần Properties là chưa đủ. Thật ra, phần Properties chỉ là phần Unity sử dụng để kết nối với các biến tương ứng bên trong phần SubShader và nếu bạn xem Shader của những engine khác thì bạn sẽ không thấy phần Properties này. Các biến của shader được định nghĩa trong phần subshader như sau:

subshadẻ.PNG

như các bạn có thể thấy các biến trong phần SubShader có tên trùng với tên của các properties chúng ta đã nói ở phía trên nhưng các kiểu của các biến đã được thay đổi: kiểu 2D chuyển thành sample2D, Vector chuyển thành float4 và Color chuyền thành half4.

Thứ tự hiển thị (Rendering Order)

Code trong phần SubShader được viết bằng ngôn ngữ Cg/HLSL gần giống với ngôn ngữ C. phần code này sẽ được chạy trên mỗi pixel của đối tượng. dựa trên cấu trúc của GPU, có một sự giới hạn các hành động mà chúng ta có thể thực hiện trong một shader. chúng ta có thể tránh khỏi điều này bằng cách phân chia sự thực thi của shader thành từng Pass khác nhau nhưng chúng ta sẽ nói sâu hơn về điều đó trong các bài viết sau này. Phần thân của SubShader bình thường sẽ như sau:

redering order.PNG

phần Cg code sẽ bắt đầu từ CGPROGRAM và kết thúc bằng ENDCG, nhưng trước đó chúng ta thấy sự xuất hiện của thẻ Tags. Tags là một cách thông báo cho Unity biết chúng ta đang set giá trị cho một thuộc tính nào đó. trong ảnh trên chúng ta đang set giá trị cho thuộc tính Queue và RenderType.

Khi hiển thị các triangle, GPU sẽ sắp xếp chúng dựa vào khoảng cách giữa chúng đến camera và theo đó đối tượng nào xa camera nhất sẽ được hiển thị trước. Cách hiển thị này sẽ không sao khi GPU hiển thị các khối đối tượng đặc, nhưng nó có thể hiển thị sai đối với các đối tượng trong suốt. Đây là lý do tại sao Unity cho phép chúng ta thay đổi thứ tự hiển thị đối với từng material. Thuộc tính Queue có thể nhận các giá trị số nguyên dương, giá trị càng nhỏ thì sẽ càng được ưu tiên hiển thị trước. Queue cũng có thể nhận các giá trị text cố định sau:

  • Background(1000): gán cho các đối tượng như hình nền của game, bầu trời... đó là những đối tượng ở phía sau tất cả các đối tượng khác trong game.
  • Geometry(2000): gán cho các đối tượng đặc không trong suốt.
  • Transparent: gán cho các đối tượng trong suốt như cây cỏ, lửa, hiệu ứng cháy nổ và mặt nước.
  • Overlay: gán cho các đối tượng như UI(giao diện) và chữ trên màn hình.

Unity cũng cho phép sử dụng thứ tự tương ứng, ví dụ Background+2 tương đương với giá trị 1002. hãy cẩn thận khi set giá trị cho Queue vì nó có thể dẫn tới các trường hợp các đối tượng hiển thị sai thứ tự hoặc có đối tượng luôn luôn hiển thị trước các đối tượng khác. ss.PNG

ZTest

Có một điều quan trọng cần phải ghi nhớ đó là một đối tượng trong suốt không phải lúc nào cũng hiển thị ở đằng trước một đối tượng đặc. GPU, mặc định sẽ thực hiện một lượt kiểm tra gọi là ZTest và sẽ không hiển thị các pixel bị che bởi các đối tượng khác, để thực hiện điều đó, GPU sử dụng một tấm màn chắn có cùng kích thước với màn hình mà nó đang hiển thị. Mỗi Pixel chứa một giá depth(khoảng cách tới camera) của đối tượng được hiển thị ở pixel đó. Nếu bạn ghi một pixel xa hơn giá trị depth của nó thì pixel đó sẽ không được hiển thị. ZTest loại bỏ những pixel bị che khuất bới các đối tượng khác bất chấp thứ tự hiển thị của nó.

Surface vs Vertex and Fragment

phần cuối của bài viết này là phần code thực sự của shader. Trước khi bắt đầu viết một shader nào đó, bạn phải lựa chọn kiểu shader phù hợp. Do đó, chúng ta sẽ đi sâu hơn vào code của từng loại shader trên.

Surface Shader

mỗi khi bạn muốn đối tượng của mình tương tác với ánh sáng một cách chân thực thì khi đó bạn cần dùng tới Surface shader. Surface shader tính toán sự phản xạ, hướng, phản chiếu... của ánh sáng bên trong một hàm gọi là surf. các giá trị này được gán vào đối tượng được chiếu sáng sau đó các giá trị đó sẽ được chuyển thành giá trị màu RGB cho mỗi pixel.

Cg code của một surface shader sẽ như sau:

ss.PNG

shaderintro03.png

Trong ví dụ này, bức ảnh anh lính được gán giá trị Albedo trong hàm surf. Shader này sử dụng kiểu ánh sáng Lambertian - là kiểu rất hiệu quả để thể hiện ánh sáng lên trên các đối tượng. Các shader chỉ sử dụng thuộc tính albedo được gọi là diffuse shader.

Vertex and Fragment Shader

dạng shader này hoạt động gần giống với cách mà GPU hiển thị các Triangle lên màn hình và ánh sáng sẽ không tác động lên các đối tượng được gán shader này. đầu tiên đối tượng sẽ được truyền qua một hàm gọi là vert, hàm này sẽ chỉnh sửa các vertice trên đối tượng đó. Sau đó, từng triangle được truyền qua một hàm khác gọi là frag - hàm này sẽ quyết đinh màu cho từng pixel trên đối tượng đó. kiểu shader này rất hữu ích cho các hiệu ứng 2D, các hiệu ứng camera và các hiệu ứng 3D mà nếu sử dụng surface shader thì sẽ quá phức tạp. Chúng ta sẽ đến với một ví dụ về shader dạng này ngay sau đây:

vert.PNG

red.png

shader này chỉ làm một việc đơn giản là hiển thị một đối tượng bằng màu đỏ và không có tương tác với ánh sáng. hàm vert chuyển đổi các vertice từ không gian 3D sang vị trí 2D trên màn hình. Unity sử dụng UNITY_MATRIX_MVP để giúp lập trình viên không cần tự làm việc chuyển đổi này. sau đó hàm frag set màu đỏ cho tất cả các pixel trên đối tượng. có một điều cần nhớ là hai hàm vertfrag cần phải đặt trong thẻ Pass.

Tổng kết

qua bài viết này, mình đã cùng các bạn xem qua phần khái quát về shader trong Unity. Shader trong Unity là một phần rất khó rất bí ẩn và mình sẽ cùng các bạn hiểu sâu hơn về nó qua những bài viết tiếp theo. cảm ơn các bạn đã đọc bài. Bài viết được dịch từ https://unity3d.com/learn/tutorials/topics/graphics/gentle-introduction-shaders.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí