Chào bạn! Hôm nay mình tiếp tục thảo luận về . Bài viết này sẽ lớn hơn hai lần so với bài viết trước. Giữ chặt! rendering trong Unity Shader là gì? Dựa trên những gì đã được mô tả trong bài viết trước, trình đổ bóng là một chương trình nhỏ có thể được sử dụng để tạo các hiệu ứng thú vị trong các dự án của chúng ta. Nó chứa các phép tính toán học và danh sách các hướng dẫn (lệnh). Chúng cho phép chúng tôi xử lý màu cho từng pixel trong khu vực bao phủ đối tượng trên màn hình máy tính của chúng tôi hoặc để làm việc với các phép biến đổi đối tượng (ví dụ: để tạo cỏ hoặc nước động). Chương trình này cho phép chúng tôi vẽ các phần tử (sử dụng hệ tọa độ) dựa trên các thuộc tính của đối tượng đa giác của chúng tôi. Các shader được thực thi trên vì nó có kiến trúc song song bao gồm hàng nghìn lõi nhỏ, hiệu quả được thiết kế để xử lý các tác vụ đồng thời. Nhân tiện, CPU được thiết kế để xử lý nối tiếp tuần tự. GPU Lưu ý rằng có ba loại tệp liên quan đến trình đổ bóng trong Unity. Đầu tiên, chúng tôi có các chương trình có phần mở rộng ".shader" có khả năng biên dịch các loại quy trình kết xuất khác nhau. Thứ hai, chúng tôi có các chương trình có phần mở rộng ".shadergraph" chỉ có thể biên dịch thành URP hoặc HDRP. Ngoài ra, chúng tôi có các tệp có phần mở rộng ".hlsl" cho phép chúng tôi tạo các chức năng tùy chỉnh. Chúng thường được sử dụng trong một loại nút có tên là Chức năng tùy chỉnh, được tìm thấy trong Biểu đồ đổ bóng. Ngoài ra còn có một loại shader khác có phần mở rộng ".cginc" - Compute Shader. Nó được liên kết với CGPROGRAM ".shader", trong khi ".hlsl" được liên kết với HLSLPROGRAM ".shadergraph". Trong Unity, ít nhất bốn loại cấu trúc được xác định để tạo shader. Trong số đó, chúng ta có thể tìm thấy sự kết hợp của bộ đổ bóng đỉnh và mảnh, bộ đổ bóng bề mặt để tính toán ánh sáng tự động và bộ tạo bóng tính toán cho các khái niệm nâng cao hơn. Một chuyến tham quan nhỏ vào ngôn ngữ đổ bóng Trước khi bắt đầu viết các shader nói chung, chúng ta nên tính đến việc có ba ngôn ngữ lập trình shader trong Unity: HLSL (Ngôn ngữ đổ bóng cấp cao - Microsoft) Cg (C cho Đồ họa - NVIDIA) - một định dạng lỗi thời ShaderLab - một ngôn ngữ khai báo - Unity Chúng tôi sẽ nhanh chóng chạy qua Cg, và chạm vào HLSL một chút. ShaderLab Cg là ngôn ngữ lập trình cấp cao được thiết kế để biên dịch trên hầu hết các GPU. NVIDIA đã phát triển nó với sự hợp tác của Microsoft và sử dụng một cú pháp rất giống với HLSL. Lý do các trình tạo bóng hoạt động với ngôn ngữ Cg là vì chúng có thể biên dịch bằng cả HLSL và GLSL (Ngôn ngữ tạo bóng OpenGL), tăng tốc và tối ưu hóa quá trình tạo tài liệu cho trò chơi điện tử. Tất cả các shader trong Unity (ngoại trừ Shader Graph và Compute) đều được viết bằng ngôn ngữ khai báo có tên là ShaderLab. Cú pháp của ngôn ngữ này cho phép chúng tôi hiển thị các thuộc tính của trình đổ bóng trong trình kiểm tra Unity. Điều này rất thú vị vì chúng ta có thể thao tác các giá trị của biến và vectơ trong thời gian thực, tùy chỉnh trình đổ bóng của chúng ta để có được kết quả mong muốn. Trong ShaderLab, chúng ta có thể xác định thủ công một số thuộc tính và lệnh, trong số đó có Dự phòng. Nó tương thích với các loại quy trình kết xuất khác nhau tồn tại trong Unity. Dự phòng là một khối mã cơ bản trong các trò chơi đa nền tảng. Nó cho phép chúng ta biên dịch một shader khác thay vì shader đã tạo ra lỗi. Nếu một shader bị hỏng trong quá trình biên dịch, Dự phòng sẽ trả về một shader khác và phần cứng đồ họa có thể tiếp tục công việc của nó. Điều này là cần thiết để chúng ta không phải viết các shader khác nhau cho Xbox và PlayStation mà sử dụng các shader thống nhất. Các loại shader cơ bản trong Unity Các loại shader cơ bản trong Unity cho phép chúng ta tạo các chương trình con để sử dụng cho các mục đích khác nhau. Hãy thảo luận về những gì mỗi loại chịu trách nhiệm: Loại shader này được đặc trưng bởi việc tối ưu hóa mã viết tương tác với mô hình chiếu sáng cơ sở và chỉ hoạt động với RP tích hợp. Shader bề mặt tiêu chuẩn. Shader không sáng. Nó đề cập đến mô hình màu chính và sẽ là cấu trúc cơ bản mà chúng tôi thường sử dụng để tạo các hiệu ứng của mình. Về mặt cấu trúc, nó rất giống với Unlit shader. Các shader này chủ yếu được sử dụng trong các hiệu ứng hậu xử lý RP Tích hợp và yêu cầu chức năng "OnRenderImage()" (C#). Hiệu ứng hình ảnh Shader. Loại này có đặc điểm là nó được thực thi trên card màn hình và có cấu trúc rất khác so với các shader đã đề cập trước đó. Tính toán Shader. Một loại shader thử nghiệm cho phép thu thập và xử lý dò tia trong thời gian thực. Nó chỉ hoạt động với HDRP và DXR. RayTracing Shader. Trình đổ bóng dựa trên biểu đồ trống mà bạn có thể làm việc mà không cần biết về ngôn ngữ đổ bóng bằng cách sử dụng các nút. Biểu đồ đổ bóng trống. Nó là một trình tạo bóng phụ có thể được sử dụng trong các trình tạo bóng Shader Graph khác. Đồ thị phụ. Kết xuất trong Unity là một chủ đề khó, vì vậy hãy đọc kỹ hướng dẫn này. Cấu trúc đổ bóng Để phân tích cấu trúc của các shader, tất cả những gì chúng ta cần làm là tạo một shader đơn giản dựa trên Unlit và phân tích nó. Khi chúng tôi tạo một shader lần đầu tiên, Unity sẽ thêm mã mặc định để giảm bớt quá trình biên dịch. Trong trình đổ bóng, chúng ta có thể tìm thấy để GPU có thể diễn giải chúng. các khối mã được cấu trúc Nếu chúng ta mở shader của mình, cấu trúc của nó trông tương tự: Shader "Unlit/OurSampleShaderUnlit" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags {"RenderType"="Opaque"} LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler 2D _MainTex; float4 _MainTex; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o, o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } } Với ví dụ hiện tại và cấu trúc cơ bản của nó, nó sẽ trở nên rõ ràng hơn một chút. Trình tạo bóng bắt đầu bằng một đường dẫn trong trình kiểm tra trình soạn thảo Unity (InspectorPath) và tên (shaderName), sau đó là các thuộc tính (ví dụ: kết cấu, vectơ, màu sắc, v.v.), sau đó là SubShader. Cuối cùng, một tham số Dự phòng tùy chọn hỗ trợ các biến thể khác nhau. Làm việc với ShaderLab Hầu hết các shader bắt đầu bằng cách khai báo shader và đường dẫn của nó trong trình kiểm tra Unity, cũng như tên của nó. Cả hai thuộc tính, chẳng hạn như SubShader và Dự phòng, được viết bên trong trường "Shader" bằng ngôn ngữ khai báo ShaderLab. Shader "OurPath/shaderName" { // The shader code will be here } Cả đường dẫn và tên shader đều có thể được thay đổi khi cần trong dự án. Các thuộc tính của trình đổ bóng tương ứng với một danh sách các tham số có thể được thao tác từ bên trong trình kiểm tra Unity. Có tám thuộc tính khác nhau, cả về giá trị và tính hữu dụng. Chúng tôi sử dụng các thuộc tính này liên quan đến trình đổ bóng mà chúng tôi muốn tạo hoặc sửa đổi, động hoặc trong thời gian chạy. Cú pháp khai báo một thuộc tính như sau: PropertyName ("display name", type) = defaultValue "PropertyName" là viết tắt của tên của thuộc tính (ví dụ: _MainTex), "tên hiển thị" chỉ định tên của thuộc tính trong trình kiểm tra Unity (ví dụ: Kết cấu), "loại" cho biết loại của thuộc tính (ví dụ: Màu, Vector, 2D, v.v.). Cuối cùng, "defaultValue" là giá trị mặc định được gán cho thuộc tính (ví dụ: nếu thuộc tính là "Color", chúng ta có thể đặt thuộc tính là màu trắng (1, 1, 1, 1, 1, 1). Thành phần thứ hai của shader là SubShader. Mỗi shader bao gồm ít nhất một SubShader để tải hoàn hảo. Khi có nhiều hơn một SubShader, Unity sẽ xử lý từng cái và chọn cái phù hợp nhất theo thông số kỹ thuật phần cứng, bắt đầu với cái đầu tiên và kết thúc với cái cuối cùng trong danh sách (ví dụ: để tách shader cho iOS và Android). Khi SubShader không được hỗ trợ, Unity sẽ cố gắng sử dụng thành phần Dự phòng tương ứng với bộ đổ bóng tiêu chuẩn để phần cứng có thể tiếp tục nhiệm vụ của mình mà không gặp lỗi đồ họa. Shader "OurPack/OurShader" { Properties { ... } SubShader { // Here will be the shader configuration } } Bạn có thể đọc thêm về các tham số và trình đổ bóng phụ và . tại đây tại đây pha trộn Chúng ta cần pha trộn cho quá trình kết hợp hai pixel thành một. Trộn được hỗ trợ trong cả Tích hợp và SRP. Trộn xảy ra trong bước kết hợp màu cuối cùng của pixel với độ sâu của nó. Giai đoạn này xảy ra ở cuối quy trình kết xuất sau giai đoạn đổ bóng phân đoạn khi thực hiện bộ đệm khuôn tô, bộ đệm z và trộn màu. Theo mặc định, thuộc tính này không được viết trong shader, vì nó là một tính năng tùy chọn và chủ yếu được sử dụng khi làm việc với các đối tượng trong suốt. Ví dụ: nó được sử dụng khi chúng ta cần vẽ một pixel có độ mờ thấp trước pixel khác (điều này thường được sử dụng trong giao diện người dùng). Chúng tôi có thể kích hoạt trộn ở đây: Blend [SourceFactor] [DestinationFactor] Bạn có thể đọc thêm về pha trộn . ở đây Bộ đệm Z (Bộ đệm sâu) Để hiểu cả hai khái niệm, trước tiên chúng ta phải tìm hiểu cách hoạt động của Z-Buffer (còn được gọi là Depth Buffer) và kiểm tra độ sâu. Trước khi bắt đầu, chúng ta phải xem xét rằng pixel có giá trị độ sâu. Các giá trị này được lưu trữ trong Depth-Buffer, xác định xem một đối tượng đi trước hay sau một đối tượng khác trên màn hình. Mặt khác, kiểm tra độ sâu là một điều kiện xác định xem một pixel có được cập nhật hay không trong Depth-Buffer. Như chúng ta đã biết, một pixel có giá trị được gán được đo bằng màu RGB và được lưu trữ trong bộ đệm màu. Bộ đệm Z thêm một giá trị bổ sung để đo độ sâu của pixel theo khoảng cách từ máy ảnh, nhưng chỉ đối với những bề mặt nằm trong khu vực phía trước của nó. Điều này cho phép 2 pixel giống nhau về màu sắc nhưng khác nhau về độ sâu. Đối tượng càng gần máy ảnh, giá trị bộ đệm Z càng nhỏ và các pixel có giá trị bộ đệm nhỏ hơn sẽ ghi đè lên các pixel có giá trị lớn hơn. Để hiểu khái niệm này, giả sử chúng ta có một máy ảnh và một số nguyên mẫu trong cảnh của mình và tất cả chúng đều nằm trên trục không gian "Z". Từ "bộ đệm" đề cập đến "không gian bộ nhớ" nơi dữ liệu sẽ được lưu trữ tạm thời, do đó, bộ đệm Z đề cập đến các giá trị độ sâu giữa các đối tượng trong cảnh của chúng ta và máy ảnh được gán cho từng pixel. Chúng ta có thể kiểm soát Depth test nhờ các thông số ZTest trong Unity. loại bỏ Thuộc tính này, tương thích với cả RP tích hợp và URP/HDRP, kiểm soát các mặt của đa giác sẽ bị xóa khi xử lý độ sâu pixel. Điều đó có nghĩa là gì? Nhớ lại rằng một đối tượng đa giác có các cạnh bên trong và các cạnh bên ngoài. Theo mặc định, các cạnh bên ngoài có thể nhìn thấy (CullBack). Tuy nhiên, chúng ta có thể kích hoạt các cạnh bên trong: Cả hai cạnh của đối tượng được hiển thị Loại bỏ. Theo mặc định, các cạnh sau của đối tượng được hiển thị Rút lui. Các cạnh phía trước của đối tượng được hiển thị. Cull Front. Lệnh này có ba giá trị là Back, Front và Off. Lệnh Quay lại được kích hoạt theo mặc định; tuy nhiên, thông thường, dòng mã liên quan đến loại bỏ không hiển thị trong trình đổ bóng cho mục đích tối ưu hóa. Nếu muốn thay đổi các thông số chúng ta phải thêm chữ "Cull" vào sau đó là chế độ muốn sử dụng. Shader "Culling/OurShader" { Properties { [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0 } SubShader { // Cull Front // Cull Off Cull [_Cull] } } Chúng tôi cũng có thể định cấu hình động thông qua phần phụ thuộc "UnityEngine.Rendering.CullMode". Nó là Enum và được truyền dưới dạng đối số cho hàm. các tham số Culling trong trình kiểm tra Unity Sử dụng Cg/HLSL Trong shader của chúng tôi, chúng tôi có thể tìm thấy ít nhất ba biến thể của các lệnh mặc định. Đây là các chỉ thị bộ xử lý có trong Cg hoặc HLSL. Chức năng của chúng là giúp trình đổ bóng của chúng tôi nhận ra và biên dịch một số chức năng nhất định mà nếu không thì không thể nhận ra như vậy: Nó cho phép giai đoạn trình đổ bóng đỉnh được biên dịch vào GPU dưới dạng trình tạo bóng đỉnh. #pragma đỉnh đỉnh. Lệnh này thực hiện chức năng tương tự như pragma vertex, với sự khác biệt là nó cho phép một giai đoạn đổ bóng phân đoạn được gọi là "frag" được biên dịch thành một trình đổ bóng phân đoạn trong mã #pragma mảnh vỡ. Không giống như các chỉ thị trước đó, nó có chức năng kép. Đầu tiên, multi_compile đề cập đến một trình tạo bóng biến thể cho phép chúng tôi tạo các biến thể có chức năng khác nhau trong trình tạo bóng của chúng tôi. Thứ hai, từ "_fog" bao gồm chức năng sương mù từ cửa sổ Chiếu sáng trong Unity. Nếu chúng ta đi đến Môi trường/Cài đặt khác, chúng ta có thể kích hoạt hoặc hủy kích hoạt các tùy chọn sương mù của trình tạo bóng của chúng ta. #pragma multi_compile_fog. Chúng tôi cũng có thể cắm các tệp Cg/HLSL vào trình đổ bóng của mình. Thông thường, chúng tôi làm điều này khi cắm UnityCG.cginc. Nó bao gồm tọa độ sương mù, vị trí đối tượng để cắt, biến đổi kết cấu, mang theo sương mù, v.v., bao gồm hằng số UNITY_PI. Điều quan trọng nhất chúng ta có thể làm với Cg/HLSL là viết các hàm xử lý trực tiếp cho các trình đổ bóng đỉnh và đoạn, để sử dụng các biến của các ngôn ngữ này và các tọa độ khác nhau như tọa độ kết cấu (TEXCOORD0). #pragma vertex vert #pragma fragment frag v2f vert (appdata v) { // Ability to work with the vertex shader } fixed4 frag (v2f i) : SV_Target { // Ability to work with fragment shader } Bạn có thể đọc thêm về Cg/HLSL . tại đây Biểu đồ đổ bóng Shader Graph là một giải pháp mới cho Unity cho phép bạn tạo các giải pháp của mình mà không cần biết về ngôn ngữ đổ bóng. Các nút trực quan được sử dụng để làm việc với nó (nhưng không ai cấm kết hợp chúng với ngôn ngữ đổ bóng). Shader Graph chỉ hoạt động với HDRP và URP. Bạn phải nhớ rằng khi làm việc với Shader Graph, các phiên bản được phát triển cho Unity 2018 là phiên bản BETA và không nhận được hỗ trợ. Các phiên bản được phát triển cho Unity 2019.1+ tương thích tích cực và nhận được hỗ trợ. Một vấn đề khác là rất có thể các shader được tạo bằng giao diện này có thể không biên dịch chính xác trong các phiên bản khác nhau. Điều này là do các tính năng mới được thêm vào trong mỗi bản cập nhật. Vì vậy, Shader Graph có phải là một công cụ tốt để phát triển shader không? Tất nhiên là thế rồi. Và nó có thể được xử lý không chỉ bởi một lập trình viên đồ họa mà còn bởi một nhà thiết kế kỹ thuật hoặc nghệ sĩ. Để tạo biểu đồ, tất cả những gì chúng ta cần làm là chọn loại chúng ta muốn trong trình chỉnh sửa Unity. Trước khi bắt đầu, hãy giới thiệu ngắn gọn trình đổ bóng đỉnh/đoạn ở cấp độ Đồ thị đổ bóng. Như chúng ta có thể thấy, có ba điểm vào được xác định trong giai đoạn trình tạo bóng đỉnh, đó là: Vị trí (3), Bình thường (3) và Tiếp tuyến (3), giống như trong trình tạo bóng Cg hoặc HLSL. Khi so sánh với một shader thông thường, điều này có nghĩa là Vị trí(3) = VỊ TRÍ[n], Bình thường(3) = BÌNH THƯỜNG[n] và Tangent(3) = TANGENT[n]. Tại sao Biểu đồ đổ bóng có ba chiều, nhưng Cg hoặc HLSL có 4 chiều? Nhớ lại rằng bốn chiều của vectơ tương ứng với thành phần W của nó, là "một hoặc không" trong hầu hết các trường hợp. Khi W = 1, vectơ tương ứng với một vị trí không gian hoặc điểm. Trong khi W = 0, vectơ tương ứng với một hướng trong không gian. Vì vậy, để thiết lập trình đổ bóng của chúng tôi, trước tiên chúng tôi vào trình chỉnh sửa và tạo hai tham số: màu sắc - _Color và Texture2D - _MainTex. Để tạo liên kết giữa các thuộc tính ShaderLab và chương trình của chúng ta, chúng ta phải tạo các biến trong trường CGPROGRAM. Tuy nhiên, quá trình này là khác nhau trong Shader Graph. Chúng ta phải kéo và thả các thuộc tính mà chúng ta muốn sử dụng vào không gian làm việc của nút. Tất cả những gì chúng ta cần làm để Texture2D hoạt động cùng với nút 2D Texture Texture là kết nối đầu ra của thuộc tính _MainTex với Texture đầu vào (T2). Để nhân cả hai nút (màu sắc và kết cấu), chúng ta chỉ cần gọi nút Multiply và chuyển cả hai giá trị làm điểm đầu vào. Cuối cùng, chúng ta cần gửi đầu ra màu ở Màu cơ bản ở giai đoạn đổ bóng phân đoạn. Bây giờ hãy lưu shader và chúng ta đã hoàn tất. Shader đầu tiên của chúng tôi đã sẵn sàng. Chúng ta cũng có thể chuyển sang thiết lập biểu đồ chung, được chia thành các phần Nút và Biểu đồ. Chúng có các thuộc tính có thể tùy chỉnh cho phép chúng tôi thay đổi cách tái tạo màu. Chúng tôi có thể tìm thấy các tùy chọn để trộn, cắt alpha, v.v. Ngoài ra, chúng tôi có thể tùy chỉnh các thuộc tính của các nút trong cấu hình Biểu đồ đổ bóng của mình. Bản thân các nút cung cấp các chức năng tương tự của một số chức năng mà chúng tôi viết trong ShaderLab. Ví dụ, mã của chức năng Kẹp: void Unity_Clamp_float4(float4 In, float4 Min, float4 Max, out float4 Out) { Out = clamp(In, Min, Max); } Bằng cách này, chúng ta có thể đơn giản hóa cuộc sống của mình và giảm thời gian viết các trình đổ bóng thay cho các biểu đồ trực quan. Phần kết luận Tôi có thể nói rất nhiều và rất lâu về shader, cũng như chạm vào chính quá trình kết xuất. Tôi đã thảo luận tất cả những điều cơ bản trong này. Ở đây tôi chưa thảo luận về raytracing shader và Compute-Shading. Tôi đã trình bày sơ qua về các ngôn ngữ đổ bóng và chỉ mô tả các quy trình từ phần nổi của tảng băng chìm. hướng dẫn kết xuất trong Unity