Hero image

Last updated February 2020. 15 min read.

Tối ưu hóa Prefab bằng Light Baked và các mẹo khác để đạt 60 fps trên điện thoại cấu hình thấp

Trang web này đã được dịch bằng máy để thuận tiện cho bạn. Chúng tôi không thể đảm bảo tính chính xác hoặc độ tin cậy của nội dung được dịch. Nếu bạn có thắc mắc về tính chính xác của nội dung được dịch, vui lòng tham khảo phiên bản tiếng Anh chính thức của trang web.

Những gì bạn sẽ nhận được từ trang này : các mẹo từ Michelle Martin, kỹ sư phần mềm tại MetalPop Games, về cách tối ưu hóa trò chơi cho nhiều thiết bị di động khác nhau, để bạn có thể tiếp cận được càng nhiều người chơi tiềm năng càng tốt.

Với tựa game chiến lược trên di động Galactic Colonies, MetalPop Games đã phải đối mặt với thách thức làm sao cho người chơi có thể xây dựng những thành phố khổng lồ trên các thiết bị cấu hình thấp mà không bị giảm tốc độ khung hình hoặc thiết bị quá nóng. Hãy xem họ đã tìm được sự cân bằng giữa hình ảnh đẹp mắt và hiệu năng mạnh mẽ như thế nào.

Trò chơi Galactic Colonies được xây dựng bằng Unity.

Xây dựng thành phố trên các thiết bị cấu hình thấp

Dù các thiết bị di động ngày nay mạnh mẽ đến đâu, việc chạy các môi trường game lớn và có đồ họa đẹp mắt ở tốc độ khung hình ổn định vẫn còn là một thách thức. Đạt được tốc độ khung hình ổn định 60fps trong môi trường 3D quy mô lớn trên thiết bị di động đời cũ có thể là một thách thức.

Với tư cách là nhà phát triển, chúng ta có thể chỉ cần nhắm mục tiêu vào các điện thoại cao cấp và giả định rằng hầu hết người chơi sẽ có phần cứng đủ mạnh để chạy trò chơi của chúng ta một cách mượt mà. Nhưng điều này sẽ dẫn đến việc loại bỏ một lượng lớn người chơi tiềm năng, vì vẫn còn rất nhiều thiết bị cũ đang được sử dụng. Đó đều là những khách hàng tiềm năng mà bạn không nên bỏ qua.

Trong trò chơi Galactic Colonies của chúng tôi, người chơi sẽ thiết lập thuộc địa trên các hành tinh xa lạ và xây dựng những thuộc địa khổng lồ được tạo thành từ vô số các công trình riêng lẻ. Trong khi các thuộc địa nhỏ hơn có thể chỉ có khoảng chục tòa nhà, thì các thuộc địa lớn hơn có thể dễ dàng có hàng trăm tòa nhà.

Đây là danh sách mục tiêu của chúng tôi khi bắt đầu xây dựng hệ thống quản lý dự án:

  • Chúng tôi muốn những bản đồ khổng lồ với số lượng lớn các tòa nhà.
  • Chúng tôi muốn hoạt động nhanh trên các thiết bị di động giá rẻ và/hoặc cũ hơn.
  • Chúng ta muốn có ánh sáng và bóng đổ đẹp mắt.
  • Chúng tôi muốn một quy trình sản xuất dễ dàng và dễ bảo trì.

Thử thách về ánh sáng trên thiết bị di động

Ánh sáng tốt trong trò chơi của bạn là yếu tố then chốt để làm cho các mô hình 3D trông tuyệt vời. Trong Unity thì điều đó rất dễ: thiết lập cấp độ, đặt các nguồn sáng động và bạn đã sẵn sàng. Và nếu bạn cần theo dõi hiệu năng, chỉ cần xử lý tất cả các nguồn sáng và thêm SSAO cùng các hiệu ứng hình ảnh khác thông qua bộ xử lý hậu kỳ. Vậy đấy, gửi hàng đi thôi!

Đối với game di động, bạn cần một kho tàng các thủ thuật và mẹo để thiết lập ánh sáng. Ví dụ, trừ khi bạn nhắm đến các thiết bị cao cấp, bạn không nên sử dụng bất kỳ hiệu ứng xử lý hậu kỳ nào. Tương tự, một cảnh quay lớn với nhiều hiệu ứng ánh sáng động cũng sẽ làm giảm tốc độ khung hình một cách đáng kể.

Việc xử lý ánh sáng thời gian thực trên PC để bàn có thể rất tốn kém. Trên các thiết bị di động, hạn chế về tài nguyên thậm chí còn nghiêm ngặt hơn và bạn không phải lúc nào cũng có thể sử dụng tất cả các tính năng tuyệt vời mà bạn muốn.

Phương pháp nướng nhẹ nhàng sẽ cứu nguy.

Vì vậy, bạn không muốn làm hao pin điện thoại của người dùng nhiều hơn mức cần thiết bằng cách sử dụng quá nhiều hiệu ứng ánh sáng cầu kỳ trong cảnh quay của mình.

Nếu bạn liên tục sử dụng tối đa khả năng của phần cứng, điện thoại sẽ nóng lên – và do đó, sẽ tự động giảm hiệu năng để bảo vệ chính nó. Để tránh điều này, bạn có thể "bake" (nướng) mọi nguồn sáng không tạo ra bóng đổ theo thời gian thực.

Quá trình tính toán trước vùng sáng và vùng tối cho một cảnh (tĩnh) được gọi là "light baking". Thông tin này sau đó được lưu trữ trong một bản đồ ánh sáng (lightmap). Sau đó, trình kết xuất sẽ biết cần làm cho mô hình sáng hơn hoặc tối hơn ở đâu, tạo ra ảo giác về ánh sáng.

Việc hiển thị theo cách này rất nhanh vì tất cả các phép tính ánh sáng tốn kém và chậm chạp đã được thực hiện ngoại tuyến, và trong quá trình chạy, trình kết xuất (shader) chỉ cần tra cứu kết quả trong một texture.

Nhược điểm ở đây là bạn sẽ phải cung cấp thêm một số texture lightmap, điều này sẽ làm tăng kích thước file build và yêu cầu thêm bộ nhớ texture khi chạy. Bạn cũng sẽ mất một ít dung lượng vì các mô hình lưới của bạn sẽ cần UV bản đồ ánh sáng và sẽ lớn hơn một chút. Nhưng nhìn chung, bạn sẽ nhận được một sự tăng tốc đáng kể.

Nhưng đối với trò chơi của chúng tôi, điều đó không khả thi, vì thế giới trong game được người chơi xây dựng theo thời gian thực. Việc liên tục phát hiện ra các khu vực mới, xây dựng các công trình mới hoặc nâng cấp các công trình hiện có, ngăn cản mọi hình thức chiếu sáng hiệu quả. Chỉ nhấn nút "Nướng " thôi sẽ không hiệu quả khi bạn có một thế giới động mà người chơi có thể liên tục thay đổi.

Do đó, chúng tôi đã gặp phải một số vấn đề phát sinh khi lập trình ánh sáng cho các cảnh có tính mô-đun cao.

Một quy trình xử lý cho các Prefab được nung bằng ánh sáng

Dữ liệu xử lý ánh sáng trong Unity được lưu trữ và liên kết trực tiếp với dữ liệu cảnh. Điều này không thành vấn đề nếu bạn có các cấp độ riêng biệt, các cảnh được xây dựng sẵn và chỉ có một số ít đối tượng động. Bạn có thể chuẩn bị trước hệ thống chiếu sáng và hoàn tất.

Rõ ràng, điều này không hiệu quả khi bạn tạo các cấp độ một cách động. Trong game xây dựng thành phố, thế giới không được tạo sẵn. Thay vào đó, nó được lắp ráp phần lớn một cách năng động và tức thời dựa trên quyết định của người chơi về việc xây dựng cái gì và xây dựng ở đâu. Việc này thường được thực hiện bằng cách tạo ra các Prefab ở bất cứ nơi nào người chơi quyết định xây dựng thứ gì đó.

Giải pháp duy nhất cho vấn đề này là lưu trữ tất cả dữ liệu xử lý ánh sáng cần thiết bên trong Prefab thay vì trong cảnh. Đáng tiếc là không có cách nào dễ dàng để sao chép dữ liệu về loại lightmap cần sử dụng, tọa độ và tỷ lệ của nó vào một Prefab.

Cách tốt nhất để xây dựng một quy trình xử lý Prefab ổn định, có thể xử lý ánh sáng nhân tạo, là tạo Prefab trong một cảnh riêng biệt (thậm chí nhiều cảnh) rồi tải chúng vào trò chơi chính khi cần. Mỗi mảnh ghép mô-đun được nung nhẹ và sau đó sẽ được tải vào trò chơi khi cần thiết.

Hãy quan sát kỹ cách hoạt động của kỹ thuật "light baking" trong Unity và bạn sẽ thấy rằng việc render một mô hình đã được "light baking" thực chất chỉ là áp dụng thêm một texture khác lên mô hình đó, làm sáng, tối (hoặc đôi khi tô màu) cho nó một chút. Tất cả những gì bạn cần là kết cấu bản đồ ánh sáng và tọa độ UV, cả hai đều được Unity tạo ra trong quá trình xử lý ánh sáng.

Trong quá trình tạo bản đồ ánh sáng, Unity sẽ tạo ra một bộ tọa độ UV mới (trỏ đến kết cấu bản đồ ánh sáng) cùng với độ lệch và tỷ lệ cho từng mô hình riêng lẻ. Việc làm mới lại hệ thống đèn sẽ thay đổi các tọa độ này mỗi lần.

Cách sử dụng các kênh tia cực tím

Để tìm ra giải pháp cho vấn đề này, việc hiểu cách thức hoạt động của các kênh tia cực tím và cách tận dụng chúng một cách hiệu quả nhất là rất hữu ích.

Mỗi lưới có thể có nhiều bộ tọa độ UV (được gọi là kênh UV trong Unity). Trong hầu hết các trường hợp, chỉ cần một bộ UV là đủ, vì các kết cấu khác nhau (Diffuse, Spec, Bump, v.v.) đều lưu trữ thông tin ở cùng một vị trí trong hình ảnh.

Nhưng khi các đối tượng chia sẻ một kết cấu, chẳng hạn như bản đồ ánh sáng, và cần tra cứu thông tin của một vị trí cụ thể trong một kết cấu lớn, thường không có cách nào khác ngoài việc thêm một bộ tọa độ UV khác để sử dụng với kết cấu được chia sẻ này.

Nhược điểm của việc sử dụng nhiều tọa độ UV là chúng tiêu tốn thêm bộ nhớ. Nếu bạn sử dụng hai bộ tọa độ UV thay vì một, bạn sẽ tăng gấp đôi số lượng tọa độ UV cho mỗi đỉnh của lưới. Mỗi đỉnh giờ đây lưu trữ hai số thực, được tải lên GPU khi hiển thị.

Tạo các Prefab

Unity tạo ra tọa độ và bản đồ ánh sáng bằng cách sử dụng chức năng tạo ánh sáng thông thường. Công cụ sẽ ghi tọa độ UV cho bản đồ ánh sáng vào kênh UV thứ hai của mô hình. Điều quan trọng cần lưu ý là bộ tọa độ UV chính không thể được sử dụng cho việc này, vì mô hình cần phải được trải phẳng (unwrapped).

Hãy tưởng tượng một chiếc hộp có cùng một chất liệu cho tất cả các mặt của nó: Các mặt riêng lẻ của hộp đều có cùng tọa độ UV vì chúng sử dụng lại cùng một họa tiết. Nhưng điều này sẽ không hiệu quả với một đối tượng được chiếu sáng bằng bản đồ ánh sáng, vì mỗi mặt của hộp đều chịu tác động của ánh sáng và bóng đổ một cách riêng biệt. Mỗi mặt cần có không gian riêng trong bản đồ ánh sáng với dữ liệu chiếu sáng riêng biệt. Do đó, cần có một bộ UV mới.

Để thiết lập một Prefab mới được xử lý bằng ánh sáng, tất cả những gì chúng ta cần làm là lưu trữ cả kết cấu và tọa độ của nó để chúng không bị mất và sao chép chúng vào Prefab.

Sau khi quá trình nung ánh sáng hoàn tất, chúng ta chạy một đoạn mã để duyệt qua tất cả các lưới trong cảnh và ghi tọa độ UV vào kênh UV2 thực tế của lưới, với các giá trị bù trừ và tỷ lệ được áp dụng.

Đoạn mã để chỉnh sửa lưới tương đối đơn giản (xem ví dụ bên dưới).

Cụ thể hơn: Thao tác này được thực hiện trên bản sao của các mô hình 3D, chứ không phải bản gốc, vì chúng ta sẽ tiếp tục tối ưu hóa các mô hình này trong quá trình xử lý ảnh.

Các bản sao được tạo tự động, lưu vào một Prefab, và được gán một vật liệu mới với shader tùy chỉnh và lightmap mới được tạo. Điều này giúp giữ nguyên các mô hình 3D ban đầu của chúng ta, và các Prefab đã được xử lý bằng ánh sáng có thể được sử dụng ngay lập tức.

Mesh meshToModify = GetComponent().sharedMesh;
Vector4 lightmapOffsetAndScale = GetComponent().lightmapScaleOffset;

Vector2[] modifiedUV2s = meshToModify.uv2;
for (int i = 0; i < meshToModify.uv2.Length; i++)
{
    modifiedUV2s[i] = new Vector2(meshToModify.uv2[i].x * lightmapOffsetAndScale.x + 
    lightmapOffsetAndScale.z, meshToModify.uv2[i].y * lightmapOffsetAndScale.y + 
    lightmapOffsetAndScale.w);
}
meshToModify.uv2 = modifiedUV2s;
Thiết lập vật liệu prefab nung bằng Unity Light

Shader bản đồ ánh sáng tùy chỉnh

Điều này giúp quy trình làm việc trở nên rất đơn giản. Để cập nhật kiểu dáng và giao diện đồ họa, chỉ cần mở cảnh phù hợp, thực hiện tất cả các chỉnh sửa cho đến khi bạn hài lòng, sau đó bắt đầu quy trình sao chép và xử lý tự động. Khi quá trình này hoàn tất, trò chơi sẽ bắt đầu sử dụng các Prefab và mesh đã được cập nhật cùng với hệ thống chiếu sáng được cập nhật.

Kết cấu bản đồ ánh sáng thực tế được thêm vào bởi một shader tùy chỉnh, shader này áp dụng bản đồ ánh sáng như một kết cấu ánh sáng thứ hai cho mô hình trong quá trình kết xuất. Shader này rất đơn giản và ngắn gọn, ngoài việc áp dụng màu sắc và bản đồ ánh sáng, nó còn tính toán một hiệu ứng phản chiếu/độ bóng giả lập đơn giản.

Đây là mã shader; hình ảnh phía trên là hình ảnh minh họa cách thiết lập vật liệu sử dụng shader này.

Shader "Custom/LightmappedPrefabWithSpec"
{
  Của cải
  {
    _MainTex("Base (RGB)", 2D) = "white" {}
    _Lightmap("Lightmap", 2D) = "white" {}
    _Specmap("Specmap", 2D) = "white" {}
    _SpecularAtt("Glossiness", Range(0.1, 2)) = 0.5
    _SpecularAmt("Specular", Range(0, 1)) = 0.5
  }
	
  SubShader
  {
    Tags{ "Queue" = "Geometry+1" }
    Vượt qua
    {
      CHƯƠNG TRÌNH CG
 
      // Định nghĩa tên của shader đỉnh
      #pragma vertex vert
 
      // Xác định tên của shader mảnh
      #pragma fragment frag

      // Bao gồm một số hàm hỗ trợ thông dụng,
      // Cụ thể là UnityObjectToClipPos và DecodeLightmap.
      #include "UnityCG.cginc"
 
      // Bản đồ khuếch tán màu
      sampler2D _MainTex;
      // Phép lát gạch/Độ lệch cho _MainTex, được sử dụng bởi TRANSFORM_TEX trong shader đỉnh
      float4 _MainTex_ST;

      // Bản đồ ánh sáng (được tạo bằng Unity Lightbaking)
      sampler2D _Lightmap;
      // Phân vùng/Độ lệch cho _Lightmap, được sử dụng bởi TRANSFORM_TEX trong shader đỉnh
      float4 _Lightmap_ST;
 
      // Bản đồ thang độ xám cho biết các phần nào của mô hình có phản chiếu
      // Lưu ý: _Specmap_ST không cần thiết, vì bản đồ này đang sử dụng cùng một tập tin. 
      // Tọa độ UV tương tự như _MainTex.
      sampler2D _Specmap;
 
      // Đây là đầu vào của shader đỉnh: vị trí, UV0, UV1, pháp tuyến
      // UV1 (= kênh UV thứ hai) cần thiết cho tọa độ kết cấu bản đồ ánh sáng
      cấu trúc appdata
      {
        float4 vertex   : CHỨC VỤ;
        float2 texcoord : TEXCOORD0;
        float2 texcoord1: TEXCOORD1;
        float3 bình thường: BÌNH THƯỜNG;
      };
 
      // Đây là dữ liệu được truyền từ shader đỉnh sang shader mảnh
      cấu trúc v2f 
      {
        float4 pos : SV_POSITION; // vị trí của pixel
        float2 txuv : TEXCOORD0; // để truy cập bản đồ màu khuếch tán
        float2 lmuv : TEXCOORD1; // để truy cập bản đồ ánh sáng
        float3 normalDir : TEXCOORD2; // để tạo hiệu ứng phản chiếu giả
      };
 
      // Đây là shader đỉnh, không có gì đặc biệt cả.
      // Điều đáng chú ý nhất là nó đang tính toán pháp tuyến bề mặt, bởi vì điều đó
      // Cần thiết cho hiệu ứng ánh sáng phản chiếu giả trong shader mảnh.
      v2f vert(appdata v)
      {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.txuv = TRANSFORM_TEX(v.texcoord.xy, _MainTex); // using _MainTex_ST
        o.lmuv = TRANSFORM_TEX(v.texcoord1.xy, _Lightmap); // using _Lightmap_ST
 
        // Tính toán pháp tuyến của đỉnh cho shader mảnh
        float4x4 modelMatrixInverse = unity_WorldToObject;
        o.normalDir = normalize(mul(float4(v.normal, 0.0), modelMatrixInverse).xyz);
 
        trả về o;
      }
 
      float đồng nhất _Đặc tính phản xạ;
      nổi đồng nhất _Số float ;
 
      // Shader phân mảnh
      half4 frag(v2f i) : COLOR
      {
        // Đọc màu trực tiếp từ kết cấu khuếch tán, sử dụng kênh UV đầu tiên
        half4 col = tex2D(_MainTex, i.txuv.xy);
        // Đọc giá trị phản chiếu (bật/tắt) từ kết cấu bản đồ phản chiếu
        half4 specVal = tex2D(_Specmap, i.txuv.xy);
        // Đọc giá trị bản đồ ánh sáng từ kết cấu bản đồ ánh sáng
        half4 lm = tex2D(_Lightmap, i.lmuv.xy);
 
        // Tính toán góc phản xạ ánh sáng giả với hướng ánh sáng được mã hóa cứng
        half3 th = normalize(half3(0, 1, -0.25));
        float spec = max(0, dot(i.normalDir, th));
 
        // Điều chỉnh theo tổng lượng phản xạ và độ bóng (thông số vật liệu)
        spec = _SpecularAmt * pow(spec, 40.0 * _SpecularAtt);
        // Chúng ta chỉ đang sử dụng giá trị màu đỏ của kết cấu phản chiếu, giống như một bản đồ thang độ xám.
        // Mặc dù về mặt kỹ thuật, thông số kỹ thuật có thể được tô màu.
        // Ví dụ: float3 specCol = specVal * spec;
        spec = spec * specVal.r;
 
        // Tính toán màu cuối cùng của pixel bằng cách kết hợp tất cả các thành phần lại với nhau.
        col.rgb = min(half4(1,1,1,1), col.rgb * DecodeLightmap(lm) + col.rgb * spec);
        trả về cột;
      }
      ENDCG
    }
  }
  Chế độ dự phòng "Lan tỏa"
}
Unity-light baked prefabs-bake level-MetalPop Games

Thiết lập và xử lý theo lô tĩnh

Trong trường hợp của chúng tôi, chúng tôi có bốn cảnh khác nhau với tất cả các Prefab đã được thiết lập sẵn. Trò chơi của chúng tôi có nhiều hệ sinh thái khác nhau như nhiệt đới, băng giá, sa mạc, v.v. và chúng tôi chia các cảnh theo đó.

Tất cả các Prefab được sử dụng trong một cảnh nhất định đều dùng chung một lightmap. Điều này có nghĩa là sẽ có thêm một họa tiết nữa, ngoài việc các Prefab chỉ dùng chung một chất liệu. Kết quả là chúng tôi có thể hiển thị tất cả các mô hình dưới dạng tĩnh và hiển thị hàng loạt gần như toàn bộ thế giới của mình chỉ trong một lệnh vẽ duy nhất.

Các cảnh quay sử dụng kỹ thuật chiếu sáng, trong đó tất cả các ô/công trình của chúng tôi được bố trí, đều có thêm nguồn sáng để tạo các điểm nhấn cục bộ. Bạn có thể đặt bao nhiêu đèn tùy thích vào các cảnh thiết lập vì tất cả chúng đều sẽ được lưu lại và xử lý xong.

Thiết lập vật liệu cho Prefab được nung bằng Unity-light với shader tùy chỉnh - MetalPop Games

Hộp thoại giao diện người dùng tùy chỉnh

Quá trình nướng được thực hiện trong một hộp thoại giao diện người dùng tùy chỉnh, đảm nhiệm tất cả các bước cần thiết. Nó đảm bảo rằng:

  • Vật liệu chính xác được gán cho tất cả các lưới.
  • Tất cả những thứ không cần nướng trong quá trình chế biến đều được giấu đi.
  • Các lưới được kết hợp/nướng lại.
  • UV được sao chép và các Prefab được tạo ra.
  • Mọi thứ đều được đặt tên chính xác và các tệp cần thiết từ hệ thống quản lý phiên bản đã được lấy ra.

Các Prefab được đặt tên đúng cách được tạo ra từ các mô hình lưới để mã trò chơi có thể tải và sử dụng chúng trực tiếp. Các tệp meta cũng được thay đổi trong quá trình này, để các tham chiếu đến lưới của Prefab không bị mất.

Quy trình này cho phép chúng ta tùy chỉnh các công trình theo ý muốn, chiếu sáng theo cách mình thích, rồi để kịch bản tự động xử lý mọi thứ.

Khi chúng ta chuyển trở lại màn hình chính và chạy trò chơi, nó hoạt động trơn tru - không cần can thiệp thủ công hay cập nhật nào khác.

Các mô hình dựng sẵn được xử lý bằng Unity-light - Thuộc địa thiên hà - MetalPop Games

Ánh sáng giả và các chi tiết động

Một trong những nhược điểm rõ ràng của một cảnh mà 100% ánh sáng được xử lý trước là khó có thể tạo ra các đối tượng động hoặc chuyển động. Bất cứ thứ gì tạo ra bóng đều yêu cầu tính toán ánh sáng và bóng đổ theo thời gian thực, điều mà tất nhiên chúng ta muốn tránh hoàn toàn.

Nhưng nếu không có các vật thể chuyển động, môi trường 3D sẽ trông tĩnh lặng và thiếu sức sống.

Dĩ nhiên, chúng tôi sẵn sàng chấp nhận một số hạn chế, vì ưu tiên hàng đầu của chúng tôi là đạt được chất lượng hình ảnh tốt và tốc độ xử lý nhanh. Để tạo ấn tượng về một thuộc địa hoặc thành phố không gian sống động, di động, không cần quá nhiều vật thể thực sự di chuyển xung quanh. Và hầu hết những điều này không nhất thiết cần đến bóng, hoặc ít nhất là sự vắng mặt của bóng sẽ không được chú ý.

Chúng tôi bắt đầu bằng cách chia tất cả các khối xây dựng thành phố thành hai khối dựng sẵn riêng biệt. Một phần tĩnh, chứa phần lớn các đỉnh, tất cả các chi tiết phức tạp của lưới - và một phần động, chứa càng ít đỉnh càng tốt.

Các phần động của một Prefab là các chi tiết được tạo hiệu ứng chuyển động đặt chồng lên các phần tĩnh. Chúng không được nung bằng ánh sáng và chúng tôi đã sử dụng một shader chiếu sáng giả rất nhanh và rẻ để tạo ảo giác rằng vật thể được chiếu sáng động.

Các đối tượng hoặc không có bóng, hoặc chúng tôi đã tạo ra một bóng giả như một phần của hiệu ứng động. Hầu hết các bề mặt của chúng tôi đều phẳng, vì vậy trong trường hợp của chúng tôi, đó không phải là trở ngại lớn.

Không có bóng đổ trên các chi tiết chuyển động, nhưng điều đó hầu như không đáng chú ý, trừ khi bạn biết để ý đến nó. Hệ thống chiếu sáng của các Prefab động cũng chỉ là giả lập – hoàn toàn không có ánh sáng thời gian thực.

Cách làm tắt đơn giản đầu tiên mà chúng tôi áp dụng là mã hóa cứng vị trí của nguồn sáng (mặt trời) vào shader ánh sáng giả. Điều này giúp giảm bớt một biến số mà shader cần phải tra cứu và điền dữ liệu động từ thế giới thực.

Làm việc với một giá trị hằng số luôn nhanh hơn so với làm việc với một giá trị động. Điều này giúp chúng ta có được ánh sáng cơ bản, các mặt sáng và tối của lưới.

Shader "Custom/FakeLighting" 
{
  Của cải 
  {
    _Color ("Color", Color) = (1,1,1,1)
    _Brightness ("Brightness", Range(0,1)) = 0.4
    _MainTex ("Albedo (RGB)", 2D) = "white" {}
  }
 
  SubShader 
  {
    Tags { "RenderType" = "Opaque" }
    LOD 200
    Vượt qua
    {
      CHƯƠNG TRÌNH CG
 
      // Định nghĩa tên của shader đỉnh
      #pragma vertex vert
      // Định nghĩa tên của shader fragment
      #pragma fragment frag
 
      // Bao gồm một số hàm hỗ trợ thông dụng, chẳng hạn như UnityObjectToClipPos
      #include "UnityCG.cginc"
 
      float4 _Color;
      float _Brightness;
 
      // Bản đồ khuếch tán màu
      sampler2D _MainTex;
      // Phép lát gạch/Độ lệch cho _MainTex, được sử dụng bởi TRANSFORM_TEX trong shader đỉnh
      float4 _MainTex_ST; 
			
      // Đây là đầu vào của shader đỉnh: vị trí, UV0, UV1, pháp tuyến
      cấu trúc appdata
      {
        float4 vertex   : CHỨC VỤ;
        float2 texcoord : TEXCOORD0;
        float3 bình thường: BÌNH THƯỜNG;
      };
 
      // Đây là dữ liệu được truyền từ shader đỉnh sang shader mảnh
      cấu trúc v2f 
      {
        float4 pos : SV_POSITION;
        float2 txuv : TEXCOORD0;
        float3 normalDir : TEXCOORD2;
      };
 
      // Đây là shader đỉnh
      v2f vert(appdata v) 
      {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.txuv = TRANSFORM_TEX(v.texcoord.xy,_MainTex);
 
        // Tính toán vectơ pháp tuyến để có thể sử dụng cho hiệu ứng ánh sáng giả
        // trong shader mảnh
        float4x4 modelMatrixInverse = unity_WorldToObject; 
        o.normalDir = normalize(mul(float4(v.normal, 0.0), modelMatrixInverse).xyz);
		
        trả về o;
      }
 
      // Đây là shader mảnh
      half4 frag(v2f i) : COLOR
      {
        // Đọc màu từ kết cấu khuếch tán
        half4 col = tex2D(_MainTex, i.txuv.xy);
			
        // Sử dụng hướng ánh sáng được mã hóa cứng để tạo hiệu ứng ánh sáng giả
        half3 th = normalize(half3(0.25, 1, -0.25));
        // Sử dụng hướng ánh sáng được mã hóa cứng để tạo hiệu ứng phản chiếu giả
        // Giá trị này khớp với giá trị bên trong shader LightmappedPrefabWithSpec
        half3 sth = normalize(half3(0, 1, -0.25));
 
        // Ánh sáng giả
        float lightVal = max(0, dot (i.normalDir, th));
        float lightScale = 0.75;
        lightVal = lightVal * lightScale;
 
        // Thông số kỹ thuật giả
        float spec = max(0, dot(i.normalDir, sth));
        float specScale = 0.65;
        float specAtt = 0.65;
        spec = specScale * pow (spec, 40.0 * specAtt);
 
        // Thêm độ sáng tổng thể (tương tự như độ sáng môi trường/gamma) và sau đó
        // Tính toán màu sắc cuối cùng của pixel
        col.rgb = min(half4(1,1,1,1), col.rgb * _Brightness + 
                      col.rgb * lightVal * _Color + col.rgb * spec);
        trả về cột;
      }
 
      ENDCG
    }
  }
  Chế độ dự phòng "Khuếch tán"
}

Độ bóng/Độ phản chiếu

Để làm cho mọi thứ trông bóng bẩy hơn một chút, chúng tôi đã thêm một phép tính phản chiếu/độ bóng giả vào các shader cho cả đối tượng động và tĩnh. Các phản xạ gương giúp tạo vẻ ngoài kim loại đồng thời thể hiện được độ cong của bề mặt.

Vì hiện tượng phản xạ ánh sáng là một dạng phản chiếu, nên cần phải biết góc giữa máy ảnh và nguồn sáng để tính toán chính xác hiện tượng này. Khi máy ảnh di chuyển hoặc xoay, độ phản chiếu ánh sáng sẽ thay đổi. Mọi thao tác tính toán shader đều yêu cầu truy cập vào vị trí camera và mọi nguồn sáng trong cảnh.

Tuy nhiên, trong trò chơi của chúng tôi, chúng tôi chỉ sử dụng một nguồn sáng duy nhất cho hiệu ứng phản chiếu: đó là mặt trời. Trong trường hợp của chúng ta, mặt trời không bao giờ di chuyển và có thể được coi là nguồn sáng định hướng. Chúng ta có thể đơn giản hóa shader rất nhiều bằng cách chỉ sử dụng một nguồn sáng duy nhất và giả định vị trí cũng như góc chiếu sáng cố định cho nó.

Tuyệt vời hơn nữa, camera trong Galactic Colonies hiển thị khung cảnh từ trên xuống, giống như hầu hết các trò chơi xây dựng thành phố khác. Camera có thể nghiêng một chút và phóng to thu nhỏ, nhưng không thể xoay quanh trục hướng lên.

Nhìn chung, nó luôn quan sát môi trường từ trên cao. Để tạo hiệu ứng phản chiếu rẻ tiền, chúng tôi giả vờ rằng máy ảnh được cố định hoàn toàn và góc giữa máy ảnh và nguồn sáng luôn không đổi.

Bằng cách này, chúng ta có thể mã hóa cứng một giá trị hằng số vào shader và đạt được hiệu ứng phản chiếu/độ bóng đơn giản.

Về mặt kỹ thuật, việc sử dụng góc cố định cho hiệu ứng phản chiếu là không chính xác, nhưng trên thực tế, rất khó để nhận ra sự khác biệt miễn là góc máy ảnh không thay đổi nhiều.

Đối với người chơi, khung cảnh vẫn sẽ trông chính xác, đó chính là mục đích của ánh sáng thời gian thực.

Việc chiếu sáng môi trường trong trò chơi điện tử thời gian thực luôn hướng đến sự chính xác về mặt hình ảnh, chứ không phải là sự mô phỏng vật lý chính xác.

Vì hầu hết các mô hình lưới của chúng tôi đều dùng chung một chất liệu, với nhiều chi tiết đến từ bản đồ ánh sáng và các đỉnh, chúng tôi đã thêm vào một bản đồ kết cấu phản chiếu để cho shader biết khi nào và ở đâu cần áp dụng giá trị phản chiếu, cũng như cường độ của nó. Kết cấu được truy cập thông qua kênh UV chính, do đó không cần thêm bộ tọa độ nào khác. Và vì không có nhiều chi tiết nên độ phân giải rất thấp, chiếm rất ít dung lượng lưu trữ.

Đối với một số chi tiết động nhỏ hơn với số lượng đỉnh thấp, chúng ta thậm chí có thể tận dụng tính năng gom nhóm động tự động của Unity, giúp tăng tốc độ hiển thị hơn nữa.

Một chồng các Prefab được nung nhẹ

Tất cả những bóng đổ do quá trình nung nóng này đôi khi có thể tạo những vấn đề mới, đặc biệt là khi làm việc với các công trình có cấu trúc tương đối mô-đun. Trong một trường hợp, chúng tôi có một nhà kho mà người chơi có thể xây dựng, và loại hàng hóa được lưu trữ bên trong sẽ được hiển thị ngay trên chính tòa nhà đó.

Điều này gây ra vấn đề vì chúng ta có một vật thể được nung bằng ánh sáng nằm trên một vật thể cũng được nung bằng ánh sáng. Ánh sáng chồng ánh sáng!

Chúng tôi đã tiếp cận vấn đề bằng cách sử dụng một thủ thuật đơn giản khác:

  • Bề mặt nơi đặt vật thể bổ sung phải bằng phẳng và sử dụng màu xám cụ thể phù hợp với màu của tòa nhà chính.
  • Để bù lại sự đánh đổi đó, chúng ta có thể nung các vật thể trên một bề mặt phẳng nhỏ hơn và đặt nó lên trên khu vực đó với một độ lệch nhỏ.
  • Ánh sáng, điểm nhấn, ánh sáng màu và bóng tối đều được tích hợp vào trong viên gạch.
Các Prefab được nung sẵn của Unity-Light - MetalPopGames

Một số hạn chế được áp dụng.

Việc xây dựng và tạo các Prefab theo cách này cho phép chúng tôi có những bản đồ khổng lồ với hàng trăm tòa nhà trong khi vẫn duy trì số lượng lệnh vẽ cực thấp. Toàn bộ thế giới trong game của chúng ta về cơ bản chỉ được hiển thị bằng một chất liệu duy nhất, và hiện tại giao diện người dùng (UI) tiêu tốn nhiều lệnh vẽ hơn cả thế giới trong game. Unity càng ít phải xử lý các loại vật liệu khác nhau thì hiệu năng của trò chơi càng tốt hơn.

Điều này mang lại cho chúng ta nhiều không gian để bổ sung thêm nhiều thứ vào thế giới của mình, chẳng hạn như các hạt, hiệu ứng thời tiết và các yếu tố bắt mắt khác.

Nhờ vậy, ngay cả những người chơi sử dụng thiết bị cũ hơn cũng có thể xây dựng những thành phố lớn với hàng trăm tòa nhà mà vẫn duy trì được tốc độ khung hình ổn định 60fps.

Bạn có thích nội dung này không?