Làm cách nào tôi có thể triển khai ánh sáng dựa trên voxel với sự tắc nghẽn trong trò chơi kiểu Minecraft?


13

Tôi đang sử dụng C # và XNA. Thuật toán hiện tại của tôi cho ánh sáng là một phương pháp đệ quy. Tuy nhiên, nó rất tốn kém , đến mức mà một đoạn 8x128x8 được tính cứ sau 5 giây.

  • Có phương pháp chiếu sáng nào khác sẽ tạo ra bóng tối thay đổi không?
  • Hoặc là phương pháp đệ quy tốt, và có lẽ tôi chỉ đang làm sai?

Có vẻ như các công cụ đệ quy rất tốn kém về cơ bản (buộc phải trải qua khoảng 25 nghìn khối mỗi khối). Tôi đã suy nghĩ về việc sử dụng một phương pháp tương tự như phương pháp dò tia, nhưng tôi không biết làm thế nào nó sẽ hoạt động. Một điều khác tôi đã thử là lưu trữ các nguồn sáng trong Danh sách và cho mỗi khối có khoảng cách đến từng nguồn sáng và sử dụng nó để chiếu sáng đến mức chính xác, nhưng sau đó ánh sáng sẽ đi qua các bức tường.

Mã đệ quy hiện tại của tôi là dưới đây. Điều này được gọi từ bất kỳ nơi nào trong khối không có mức độ ánh sáng bằng 0, sau khi xóa và thêm lại ánh sáng mặt trời và đèn pin.

world.get___atlà một hàm có thể lấy các khối bên ngoài khối này (đây là bên trong lớp chunk). Locationlà cấu trúc của riêng tôi giống như một Vector3, nhưng sử dụng các số nguyên thay vì các giá trị dấu phẩy động. light[,,]là ánh sáng cho chunk.

    private void recursiveLight(int x, int y, int z, byte lightLevel)
    {
        Location loc = new Location(x + chunkx * 8, y, z + chunky * 8);
        if (world.getBlockAt(loc).BlockData.isSolid)
            return;
        lightLevel--;
        if (world.getLightAt(loc) >= lightLevel || lightLevel <= 0)
            return;
        if (y < 0 || y > 127 || x < -8 || x > 16 || z < -8 || z > 16)
            return;
        if (x >= 0 && x < 8 && z >= 0 && z < 8)
            light[x, y, z] = lightLevel;

        recursiveLight(x + 1, y, z, lightLevel);
        recursiveLight(x - 1, y, z, lightLevel);
        recursiveLight(x, y + 1, z, lightLevel);
        recursiveLight(x, y - 1, z, lightLevel);
        recursiveLight(x, y, z + 1, lightLevel);
        recursiveLight(x, y, z - 1, lightLevel);
    }

1
Một cái gì đó là sai lầm khủng khiếp nếu bạn đang thực hiện 2 triệu khối mỗi khối - đặc biệt là vì chỉ có 8.192 khối thực sự trong một khối 8 * 128 * 8. Bạn có thể làm gì khi bạn trải qua mỗi khối ~ 244 lần? (đó có thể là 255 không?)
doppelgreener

1
Tôi đã làm toán sai. Xin lỗi P. Thay đổi. Nhưng lý do tại sao bạn cần phải đi nhiều như vậy là bạn phải "bong bóng" khỏi mọi khối cho đến khi bạn đạt một mức độ ánh sáng lớn hơn mức thiết lập của bạn. Điều đó có nghĩa là mọi khối có thể bị ghi đè lên 5-10 lần trước khi nó đạt mức ánh sáng thực tế. 8x8x128x5 = rất nhiều

2
Làm thế nào bạn lưu trữ Voxels của bạn? Đó là điều quan trọng để giảm thời gian đi qua.
Samaursa

1
Bạn có thể đăng thuật toán ánh sáng của bạn? (bạn hỏi nếu bạn đang làm điều đó tồi tệ, chúng tôi không có ý kiến ​​gì)
doppelgreener

Tôi đang lưu trữ chúng trong một mảng "Khối" và Khối bao gồm một enum cho vật liệu, cộng với một byte siêu dữ liệu để sử dụng trong tương lai.

Câu trả lời:


6
  1. Mỗi ánh sáng có một vị trí (điểm nổi) chính xác và hình cầu giới hạn được xác định bởi giá trị bán kính ánh sáng vô hướng , LR.
  2. Mỗi voxel có một vị trí (điểm nổi) chính xác tại trung tâm của nó, bạn có thể dễ dàng tính toán từ vị trí của nó trong lưới.
  3. Chạy qua mỗi một trong số 8192 voxel một lần, và với mỗi lần, hãy xem liệu nó có nằm trong khối lượng giới hạn hình cầu của mỗi đèn N hay không bằng cách kiểm tra |VP - LP| < LR, trong đó VP là vectơ vị trí của voxel so với gốc và LPlà vectơ vị trí của ánh sáng so với gốc. Đối với mỗi ánh sáng có bán kính của voxel hiện tại, hãy tăng hệ số ánh sáng theo khoảng cách từ tâm ánh sáng , |VP - LP|. Nếu bạn bình thường hóa vectơ đó và sau đó lấy độ lớn của nó, nó sẽ nằm trong phạm vi 0,0-> 1,0. Mức ánh sáng tối đa mà voxel có thể đạt tới là 1.0.

Thời gian chạy là O(s^3 * n) , trong đó sđộ dài cạnh (128) của vùng voxel của bạn và nlà số nguồn sáng. Nếu nguồn sáng của bạn là tĩnh, điều này không có vấn đề gì. Nếu nguồn sáng của bạn di chuyển trong thời gian thực, bạn có thể chỉ làm việc trên đồng bằng thay vì tính toán lại toàn bộ shebang mỗi bản cập nhật.

Bạn thậm chí có thể lưu trữ các voxels mà mỗi ánh sáng ảnh hưởng, như các tham chiếu trong ánh sáng đó. Bằng cách này, khi ánh sáng di chuyển hoặc bị phá hủy, bạn có thể đi qua danh sách đó, điều chỉnh giá trị ánh sáng cho phù hợp, thay vì phải đi qua toàn bộ lưới hình khối một lần nữa.


Nếu tôi hiểu thuật toán của anh ta một cách chính xác, anh ta cố gắng thực hiện một số loại giả giả, bằng cách cho phép ánh sáng đến được những nơi xa ngay cả khi điều đó có nghĩa là nó phải đi "quanh" một số góc. Hay nói cách khác, thuật toán lấp đầy không gian "trống" (không rắn) với khoảng cách tối đa giới hạn từ gốc (nguồn sáng) và khoảng cách (và từ đó, độ suy giảm ánh sáng) được tính theo con đường ngắn nhất đến nguồn gốc. Vì vậy - không hoàn toàn những gì bạn đề xuất hiện tại.
Martin Sojka

Cảm ơn chi tiết @MartinSojka. Yep nghe giống như một trận lũ thông minh. Với bất kỳ nỗ lực chiếu sáng toàn cầu nào, chi phí có xu hướng cao ngay cả với sự tối ưu thông minh. Do đó, thật tốt khi thử những vấn đề này trong 2D trước tiên và nếu chúng thậm chí còn đắt đỏ từ xa, hãy biết rằng bạn sẽ có một thách thức nhất định trong tay 3D.
Kỹ sư

4

Bản thân Minecraft không làm ánh sáng mặt trời theo cách này.

Bạn chỉ cần lấp đầy ánh sáng mặt trời từ trên xuống dưới, mỗi lớp đang thu thập ánh sáng từ các voxels lân cận ở lớp trước với sự suy giảm. Rất nhanh - vượt qua một lần duy nhất, không có danh sách, không có cấu trúc dữ liệu, không có đệ quy.

Bạn phải thêm đèn pin và đèn không ngập khác trong một lần đi sau.

Có rất nhiều cách khác để làm điều này, bao gồm cả việc truyền ánh sáng định hướng lạ mắt, v.v., nhưng rõ ràng là chúng chậm hơn và bạn phải tìm ra nếu bạn muốn đầu tư vào chủ nghĩa hiện thực bổ sung được đưa ra những hình phạt đó.


Đợi đã, vậy chính xác thì Minecraft làm điều đó như thế nào? Tôi không thể hiểu chính xác những gì bạn đang nói ... "Mọi lớp đang thu thập ánh sáng từ các voxels lân cận ở lớp trước với sự suy giảm" nghĩa là gì?

2
Bắt đầu với lớp trên cùng (lát có chiều cao không đổi). Làm đầy nó với ánh sáng mặt trời. Sau đó đi đến lớp bên dưới, và mọi voxel ở đó được chiếu sáng từ các voxels gần nhất trong lớp trước (phía trên nó). Đặt ánh sáng bằng không trong voxels rắn. Bạn có một vài cách để quyết định "hạt nhân", trọng số của các đóng góp từ các voxels ở trên, Minecraft sử dụng giá trị tối đa được tìm thấy, nhưng giảm đi 1 nếu sự lan truyền không đi thẳng xuống. Đây là sự suy giảm bên, do đó, các cột dọc không bị chặn sẽ có được sự lan truyền ánh sáng mặt trời đầy đủ và các uốn cong ánh sáng xung quanh các góc.
Bjorn Wesen

1
Xin lưu ý rằng phương pháp này hoàn toàn không dựa trên bất kỳ vật lý thực tế nào :) Vấn đề chính là về bản chất bạn đang cố gắng ước chừng một ánh sáng không định hướng (tán xạ khí quyển) VÀ phóng xạ với một phép đo đơn giản. Có vẻ khá tốt.
Bjorn Wesen

3
Thế còn một "đôi môi" bị treo, làm thế nào để ánh sáng lên đó? Làm thế nào để ánh sáng đi theo hướng lên? Khi bạn chỉ đi từ trên xuống, sau đó bạn không thể quay lên trên để điền vào phần nhô ra. Ngoài ra ngọn đuốc / nguồn ánh sáng khác. Bạn sẽ làm điều này như thế nào? (họ chỉ có thể đi xuống!)

1
@Fel lov: Đã lâu rồi tôi mới xem xét điều này, nhưng thực chất có một mức độ ánh sáng xung quanh tối thiểu thường đủ cho phần bên dưới của phần nhô ra để chúng không hoàn toàn màu đen. Khi tôi tự thực hiện điều này, tôi đã thêm một lần truyền thứ hai từ dưới lên>, nhưng tôi thực sự không thấy bất kỳ cải tiến thẩm mỹ lớn nào so với phương pháp xung quanh. Đèn pin / đèn pin phải được xử lý riêng - Tôi nghĩ bạn có thể thấy mô hình lan truyền được sử dụng trong MC nếu bạn đặt một ngọn đuốc ở giữa một bức tường và thử nghiệm một chút. Trong các thử nghiệm của tôi, tôi tuyên truyền chúng trong một trường ánh sáng riêng biệt sau đó thêm vào.
Bjorn Wesen

3

Ai đó nói để trả lời câu hỏi của riêng bạn nếu bạn tìm ra nó, vì vậy vâng. Tìm ra một phương pháp.

Những gì tôi đang làm là thế này: Đầu tiên, tạo một mảng boolean 3d gồm "các khối đã thay đổi" được phủ lên trên khối. Sau đó, điền vào ánh sáng mặt trời, đèn pin, vv (chỉ cần chiếu sáng khối mà nó bật, chưa có lũ lụt). Nếu bạn thay đổi điều gì đó, hãy nhấn "khối đã thay đổi" ở vị trí đó thành đúng. Đồng thời đi và thay đổi mọi khối rắn (và do đó không cần tính toán ánh sáng cho) thành "đã thay đổi".

Bây giờ cho các công cụ nặng: Đi qua toàn bộ khối với 16 đường chuyền (cho mỗi cấp độ ánh sáng) và nếu 'đã thay đổi', hãy tiếp tục. Sau đó lấy mức độ ánh sáng cho các khối xung quanh chính nó. Lấy mức độ ánh sáng cao nhất của chúng. Nếu mức ánh sáng đó bằng với mức ánh sáng của hiện tại, hãy đặt khối bạn đang ở mức hiện tại và đặt "đã thay đổi" cho vị trí đó thành đúng. Tiếp tục.

Tôi biết loại phức tạp của nó, tôi đã cố gắng để giải thích tốt nhất của tôi. Nhưng thực tế quan trọng là, nó hoạt động và nhanh chóng.


2

Tôi muốn đề xuất một thuật toán kết hợp giải pháp nhiều lượt của bạn với phương pháp đệ quy ban đầu và rất có thể nhanh hơn một chút so với một trong số chúng.

Bạn sẽ cần 16 danh sách (hoặc bất kỳ loại bộ sưu tập nào), một khối cho mỗi cấp độ ánh sáng. (Trên thực tế, có nhiều cách để tối ưu hóa thuật toán này để sử dụng ít danh sách hơn, nhưng cách này dễ mô tả nhất.)

Đầu tiên, xóa danh sách và đặt mức ánh sáng của tất cả các khối thành 0, sau đó khởi tạo nguồn sáng như bạn làm trong giải pháp hiện tại của mình. Sau (hoặc trong khi đó), thêm bất kỳ khối nào có mức độ ánh sáng khác không vào danh sách tương ứng.

Bây giờ, đi qua danh sách các khối có mức sáng 16. Nếu bất kỳ khối nào liền kề với chúng có mức ánh sáng nhỏ hơn 15, hãy đặt mức ánh sáng của chúng thành 15 và thêm chúng vào danh sách thích hợp. (Nếu chúng đã có trong danh sách khác, bạn có thể xóa chúng khỏi danh sách đó, nhưng nó không gây hại gì ngay cả khi bạn không.)

Sau đó lặp lại tương tự cho tất cả các danh sách khác, theo thứ tự độ sáng giảm dần. Nếu bạn thấy rằng một khối trong danh sách đã có mức độ ánh sáng cao hơn mức cần có trong danh sách đó, bạn có thể cho rằng nó đã được xử lý và thậm chí không cần kiểm tra hàng xóm của nó. (Sau đó, một lần nữa, có thể nhanh hơn khi chỉ kiểm tra hàng xóm - điều này phụ thuộc vào tần suất xảy ra. Bạn có thể nên thử cả hai cách và xem cách nào nhanh hơn.)

Bạn có thể lưu ý rằng tôi chưa chỉ định cách lưu trữ danh sách; thực sự khá nhiều bất kỳ triển khai hợp lý nào cũng phải làm điều đó, miễn là chèn một khối nhất định và trích xuất một khối tùy ý đều là các thao tác nhanh. Một danh sách được liên kết sẽ hoạt động, nhưng cũng sẽ thực hiện bất kỳ việc thực hiện tốt giữa các mảng có độ dài thay đổi. Chỉ cần sử dụng bất cứ điều gì tốt nhất cho bạn.


Phụ lục: Nếu hầu hết các đèn của bạn không di chuyển thường xuyên (và cả các bức tường), thì có thể lưu trữ nhanh hơn, đối với mỗi khối sáng, một con trỏ tới nguồn sáng xác định mức độ ánh sáng của nó (hoặc với một trong các chúng, nếu một số được buộc). Bằng cách đó, bạn có thể tránh cập nhật ánh sáng toàn cầu gần như hoàn toàn: nếu một nguồn sáng mới được thêm vào (hoặc nguồn sáng hiện có), bạn chỉ cần thực hiện một lần chiếu sáng đệ quy duy nhất cho các khối xung quanh nó, trong khi nếu một nguồn bị loại bỏ (hoặc mờ đi), bạn chỉ cần cập nhật những khối trỏ đến nó.

Bạn thậm chí có thể xử lý các thay đổi của tường theo cách này: khi một bức tường được gỡ bỏ, chỉ cần bắt đầu một bước chiếu sáng đệ quy mới tại khối đó; khi một cái được thêm vào, hãy thực hiện tính toán lại ánh sáng cho tất cả các khối trỏ đến cùng một nguồn sáng như khối mới được ốp tường.

(Nếu một vài thay đổi ánh sáng xảy ra cùng một lúc - ví dụ: nếu ánh sáng bị di chuyển, được tính là loại bỏ và bổ sung - bạn nên kết hợp các bản cập nhật thành một, sử dụng thuật toán ở trên. Về cơ bản, bạn sẽ loại bỏ mức độ ánh sáng các khối chỉ vào các nguồn sáng bị loại bỏ, thêm bất kỳ khối sáng nào xung quanh chúng cũng như bất kỳ nguồn sáng mới nào (hoặc các nguồn sáng hiện có trong các khu vực không có) vào danh sách phù hợp và chạy các bản cập nhật như trên.)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.