Lưu trữ voxels cho Công cụ voxel trong C ++


9

Tôi đang cố gắng viết một công cụ voxel nhỏ vì nó thú vị, nhưng đấu tranh để tìm ra cách tốt nhất để lưu trữ các voxels thực tế. Tôi nhận thức được rằng tôi sẽ cần một số loại vì vậy tôi không cần phải có toàn bộ thế giới trong bộ nhớ và tôi nhận thức được rằng tôi cần kết xuất chúng với hiệu suất hợp lý.

Tôi đã đọc về octrees và từ những gì tôi hiểu, nó bắt đầu bằng 1 khối lập phương và trong khối đó có thể có thêm 8 khối nữa, và trong tất cả 8 khối đó có thể là 8 khối khác, v.v. Nhưng tôi không nghĩ rằng nó phù hợp với động cơ voxel của tôi bởi vì khối / vật phẩm voxel của tôi tất cả sẽ có cùng kích thước.

Vì vậy, một lựa chọn khác là chỉ cần tạo một mảng có kích thước 16 * 16 * 16 và có một mảng, và bạn điền nó với các mục. Và những phần không có vật phẩm nào sẽ có 0 là giá trị (0 = không khí). Nhưng tôi sợ điều này sẽ lãng phí rất nhiều bộ nhớ và sẽ không nhanh lắm.

Sau đó, một tùy chọn khác là một vectơ cho mỗi khối, và điền nó với các hình khối. Và khối lập phương giữ vị trí của nó trong khối. Điều này giúp tiết kiệm bộ nhớ (không có khối không khí), nhưng làm cho việc tìm kiếm một khối ở một vị trí cụ thể chậm hơn rất nhiều.

Vì vậy, tôi thực sự không thể tìm thấy một giải pháp tốt và tôi hy vọng ai đó có thể giúp tôi với điều đó. Vậy bạn sẽ sử dụng cái gì và tại sao?

Nhưng một vấn đề khác là kết xuất. Chỉ cần đọc từng đoạn và gửi nó đến GPU bằng OpenGL thì dễ, nhưng rất chậm. Tạo một lưới trên mỗi khối sẽ tốt hơn, nhưng điều đó có nghĩa là mỗi lần tôi phá vỡ một khối, tôi phải xây dựng lại toàn bộ khối có thể mất một chút thời gian gây ra một tiếng nấc nhỏ nhưng đáng chú ý, điều mà rõ ràng tôi không muốn. Vì vậy, sẽ khó hơn. Vì vậy, làm thế nào tôi sẽ làm cho các hình khối? Chỉ cần tạo tất cả các hình khối trong một bộ đệm đỉnh trên mỗi đoạn và kết xuất nó và có thể thử đặt nó vào một luồng khác, hoặc có cách nào khác không?

Cảm ơn!


1
Bạn nên sử dụng instaging để kết xuất các hình khối của bạn. Bạn có thể tìm thấy một hướng dẫn ở đây learnopengl.com/Advified-OpenGL/Instance . Để lưu trữ các hình khối: bạn có hạn chế bộ nhớ mạnh trên phần cứng không? 16 ^ 3 khối không có vẻ quá nhiều bộ nhớ.
Turms

@Turms Cảm ơn bình luận của bạn! Tôi không bị hạn chế bộ nhớ mạnh, nó chỉ là một PC thông thường. Nhưng tôi nghĩ, nếu mỗi phần lớn nhất là 50% không khí, và thế giới là rất lớn, thì phải có khá nhiều bộ nhớ lãng phí. Nhưng nó có thể không nhiều như bạn nói. Vì vậy, tôi chỉ nên đi cho khối 16 * 16 * 16 với một khối tĩnh? Và ngoài ra, bạn nói rằng tôi nên sử dụng instaging, điều đó có thực sự cần thiết? Ý tưởng của tôi là tạo ra một lưới cho mỗi khối, bởi vì cách đó tôi có thể bỏ qua tất cả các hình tam giác vô hình.

6
Tôi không khuyên bạn nên sử dụng tính năng kích hoạt cho các hình khối như Turms mô tả. Điều này sẽ chỉ làm giảm các cuộc gọi rút thăm của bạn, nhưng không làm gì để rút tiền và ẩn mặt - thực tế là nó buộc tay bạn giải quyết vấn đề đó, vì để thực hiện tất cả các hình khối phải giống nhau - bạn không thể xóa các mặt ẩn của một số hình khối hoặc hợp nhất các mặt coplanar thành các đa giác đơn lớn hơn.
DMGregory

Chọn động cơ voxel tốt nhất có thể là một thách thức. Câu hỏi lớn để tự hỏi mình là "tôi cần thực hiện những thao tác nào trên voxels của mình?" Điều đó hướng dẫn các hoạt động. Ví dụ, bạn lo ngại về việc khó tìm ra voxel ở đâu trong cây oct. Thuật toán Oct-tree rất tốt cho các vấn đề có thể tạo ra thông tin này khi cần thiết khi nó đi trên cây (thường theo cách đệ quy). Nếu bạn có vấn đề cụ thể khi điều này quá đắt, thì bạn có thể xem xét các lựa chọn khác.
Cort Ammon

Một câu hỏi lớn khác là tần suất cập nhật voxels. Một số thuật toán rất tuyệt nếu chúng có thể xử lý trước dữ liệu để lưu trữ dữ liệu hiệu quả, nhưng kém hiệu quả hơn nếu dữ liệu được cập nhật liên tục (như dữ liệu có thể trong mô phỏng chất lỏng dựa trên hạt)
Cort Ammon

Câu trả lời:


23

Lưu trữ các khối như các vị trí và các giá trị thực sự rất không hiệu quả. Ngay cả khi không có bất kỳ chi phí nào gây ra bởi cấu trúc hoặc đối tượng bạn sử dụng, bạn cần lưu trữ 4 giá trị riêng biệt cho mỗi khối. Sẽ chỉ có ý nghĩa khi sử dụng nó trong phương pháp "lưu trữ các khối trong các mảng cố định" (cách mà bạn đã mô tả trước đó) là khi chỉ một phần tư các khối là vững chắc và theo cách này, bạn thậm chí không áp dụng bất kỳ phương pháp tối ưu hóa nào khác vào tài khoản.

Octrees thực sự tuyệt vời cho các trò chơi dựa trên voxel, vì chúng chuyên lưu trữ dữ liệu với các tính năng lớn hơn (ví dụ: các bản vá của cùng một khối). Để minh họa điều này, tôi đã sử dụng một quadtree (về cơ bản là octrees trong 2d):

Đây là bộ bắt đầu của tôi chứa các ô 32x32, sẽ bằng 1024 giá trị: nhập mô tả hình ảnh ở đây

Việc lưu trữ giá trị này là 1024 giá trị riêng biệt có vẻ không hiệu quả, nhưng một khi bạn đạt được kích thước bản đồ tương tự như các trò chơi, như Terraria , việc tải màn hình sẽ mất nhiều giây. Và nếu bạn tăng nó lên chiều thứ ba, nó bắt đầu sử dụng hết dung lượng trong hệ thống.

Quadtrees (hoặc octrees trong 3d) có thể giúp tình hình. Để tạo một cái, bạn có thể đi từ các ô và nhóm chúng lại với nhau hoặc đi từ một ô lớn và bạn chia nó cho đến khi bạn đạt được các ô. Tôi sẽ sử dụng cách tiếp cận đầu tiên, bởi vì nó dễ hình dung hơn.

Vì vậy, trong lần lặp đầu tiên, bạn nhóm mọi thứ thành các ô 2x2 và nếu một ô chỉ chứa các ô cùng loại, bạn bỏ các ô đó và chỉ lưu trữ loại. Sau một lần lặp, bản đồ của chúng ta sẽ như thế này:

nhập mô tả hình ảnh ở đây

Các dòng màu đỏ đánh dấu những gì chúng tôi lưu trữ. Mỗi ô vuông chỉ là 1 giá trị. Điều này đã đưa kích thước giảm từ 1024 giá trị xuống còn 439, giảm 57%.

Nhưng bạn biết câu thần chú . Chúng ta hãy tiến thêm một bước và nhóm chúng vào các ô:

nhập mô tả hình ảnh ở đây

Điều này đã giảm lượng giá trị được lưu trữ xuống 367. Đó chỉ là 36% kích thước ban đầu.

Bạn rõ ràng cần phải thực hiện phân chia này cho đến khi mỗi 4 ô liền kề (8 khối liền kề trong 3d) bên trong một khối được lưu trữ bên trong một ô, về cơ bản là chuyển một khối thành một ô lớn.

Điều này cũng có một số lợi ích khác, chủ yếu là khi va chạm, nhưng bạn có thể muốn tạo một octree riêng cho điều đó, điều này chỉ quan tâm đến việc một khối duy nhất có rắn hay không. Bằng cách đó, thay vì kiểm tra chống va chạm cho mọi khối bên trong một khối, bạn chỉ có thể làm điều đó chống lại các tế bào.


Cảm ơn vì đã trả lời! Dường như octree là con đường để đi. (Vì động cơ voxel của tôi sẽ là 3D) Tôi có một câu hỏi được đặt ra mà tôi muốn hỏi: Hình ảnh cuối cùng của bạn cho thấy các phần màu đen có hình vuông lớn hơn, vì tôi dự định có một minecraft như động cơ nơi bạn có thể sửa đổi địa hình voxel, tôi muốn giữ mọi thứ có cùng một khối, bởi vì nếu không nó sẽ khiến mọi thứ trở nên rất phức tạp, điều đó có thể đúng không? (Tôi vẫn sẽ đơn giản hóa các khe trống / không khí.) Thứ hai , có một số loại hướng dẫn về cách người ta sẽ lập trình một octree? Cảm ơn!

7
@ appmaker1353 không có vấn đề gì cả. Nếu người chơi cố gắng sửa đổi một khối lớn, thì bạn sẽ chia nó thành các khối nhỏ hơn tại thời điểm đó . Không cần lưu trữ các giá trị "đá" 16x16x16 khi bạn có thể nói thay vào đó "toàn bộ khối này là đá rắn" cho đến khi điều đó không còn đúng nữa.
DMGregory

1
@ appmaker1353 Như DMGregory đã nói, việc cập nhật dữ liệu được lưu trữ trong một octree là tương đối dễ dàng. Tất cả những gì bạn phải làm là phân chia ô mà thay đổi đã xảy ra cho đến khi mọi ô phụ chỉ chứa một loại khối duy nhất. Đây là một ví dụ tương tác với một phần tư . Tạo một cái là đơn giản. Bạn tạo một ô lớn, chứa hoàn toàn khối, sau đó bạn đệ quy đi qua từng ô lá (các ô không có con), kiểm tra xem phần địa hình mà nó đại diện có chứa nhiều loại khối hay không, nếu có, chia nhỏ ô
Bálint

@ appmaker1353 Vấn đề lớn hơn thực sự là ngược lại - đảm bảo rằng octree không trở nên đầy lá chỉ với một khối, điều này có thể dễ dàng xảy ra trong một trò chơi theo phong cách Minecraft. Tuy nhiên, có nhiều giải pháp cho vấn đề - chỉ là chọn bất cứ thứ gì bạn thấy phù hợp. Và nó chỉ trở thành một vấn đề thực sự khi có rất nhiều tòa nhà đang diễn ra.
Luaan

Octrees không nhất thiết là sự lựa chọn tốt nhất. đây là một bài đọc thú vị: 0fps.net/2012/01/14/an-analysis-of-minecraft-like-engines
Polygnome

7

Octrees tồn tại để giải quyết chính xác vấn đề bạn mô tả, cho phép lưu trữ dữ liệu thưa thớt mà không có thời gian tìm kiếm lớn.

Thực tế là các voxels của bạn có cùng kích thước chỉ có nghĩa là octree của bạn có độ sâu cố định. ví dụ. đối với một đoạn 16x16x16, bạn cần tối đa 5 cấp độ của cây:

  • gốc chunk (16x16x16)
    • quãng tám đầu tiên (8x8x8)
      • quãng tám thứ hai (4x4x4)
        • octant tầng thứ ba (2x2x2)
          • voxel đơn (1x1x1)

Điều này có nghĩa là bạn có tối đa 5 bước để tìm hiểu xem có một voxel tại một vị trí cụ thể trong khối hay không:

  • chunk root: toàn bộ chunk có cùng giá trị (ví dụ: tất cả không khí)? Nếu vậy, chúng ta đã xong. Nếu không...
    • Cấp thứ nhất: là octant chứa vị trí này có cùng giá trị không? Nếu không...
      • bậc thứ hai...
        • tầng thứ ba ...
          • bây giờ chúng tôi đang giải quyết một voxel duy nhất và có thể trả về giá trị của nó.

Ngắn hơn nhiều so với việc quét thậm chí 1% trên đường đi qua một mảng lên tới 4096 voxels!

Lưu ý rằng điều này cho phép chúng tôi nén dữ liệu ở bất cứ nơi nào có một quãng tám đầy đủ có cùng giá trị - cho dù giá trị đó là tất cả không khí hay tất cả đá hay bất cứ thứ gì khác. Đó chỉ là nơi các octant chứa các giá trị hỗn hợp mà chúng ta cần chia nhỏ hơn nữa, đến giới hạn của các nút lá đơn voxel.


Để giải quyết vấn đề con cái, thông thường chúng ta sẽ tiến hành theo thứ tự Morton , đại loại như thế này:

  1. XYZ-
  2. X- Y- Z +
  3. X- Y + Z-
  4. X- Y + Z +
  5. X + Y- Z-
  6. X + Y- Z +
  7. X + Y + Z-
  8. X + Y + Z +

Vì vậy, điều hướng nút Octree của chúng tôi có thể trông giống như thế này:

GetOctreeValue(OctreeNode node, int depth, int3 nodeOrigin, int3 queryPoint) {
    if(node.IsAllOneValue)
        return node.Value;

    int childIndex =  0;
    childIndex += (queryPoint.x > nodeOrigin.x) ? 4 : 0;
    childIndex += (queryPoint.y > nodeOrigin.y) ? 2 : 0;
    childIndex += (queryPoint.z > nodeOrigin.z) ? 1 : 0;

    OctreeNode child = node.GetChild(childIndex);

    return GetOctreeValue(
                child, 
                depth + 1,
                nodeOrigin + childOffset[depth, childIndex],
                queryPoint
    );
}

Cảm ơn vì đã trả lời! Dường như octree là con đường để đi. Nhưng tôi có 2 câu hỏi, bạn nói rằng octree nhanh hơn quét qua một mảng, điều này là chính xác. Nhưng tôi không cần phải làm điều đó vì mảng có thể là tĩnh có nghĩa là tôi có thể tính toán nơi khối tôi cần. Vậy tại sao tôi cần quét? Câu hỏi thứ hai, ở tầng cuối cùng của octree (1x1x1), làm thế nào để tôi biết khối nào ở đâu, vì nếu tôi hiểu đúng và nút octree có thêm 8 nút, làm sao bạn biết nút nào thuộc về vị trí 3d nào ? (Hay tôi phải nhớ chính mình?)

Có, bạn đã bao gồm trường hợp một mảng đầy đủ 16x16x16 voxels trong câu hỏi của bạn và dường như từ chối dấu chân bộ nhớ 4K trên mỗi đoạn (giả sử mỗi ID voxel là một byte) là quá mức. Tìm kiếm bạn đề cập đến khi lưu trữ danh sách các voxel với một vị trí, buộc bạn phải quét qua danh sách để tìm voxel ở vị trí mục tiêu của bạn. 4096 ở đây là giới hạn trên của độ dài danh sách đó - thông thường nó sẽ nhỏ hơn thế, nhưng nhìn chung vẫn sâu hơn so với tìm kiếm quãng tám tương ứng.
DMGregory
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.