การจัดเก็บ voxels สำหรับ voxel Engine ใน C ++


9

ฉันกำลังพยายามเขียนโปรแกรม voxel เพียงเล็กน้อยเพราะมันสนุก แต่ก็พยายามหาวิธีที่ดีที่สุดในการเก็บ voxels จริง ฉันรู้ว่าฉันจะต้องมีชิ้นส่วนบางอย่างดังนั้นฉันไม่จำเป็นต้องมีทั้งโลกในความทรงจำและฉันรู้ว่าฉันต้องการให้พวกเขามีประสิทธิภาพที่เหมาะสม

ฉันอ่านเกี่ยวกับ octrees และจากสิ่งที่ฉันเข้าใจมันเริ่มต้นด้วย 1 cube และในลูกบาศก์นั้นสามารถเพิ่มได้อีก 8 ลูกและใน 8 ลูกบาศก์เหล่านั้นอาจเป็นอีก 8 ลูกเป็นต้น แต่ฉันไม่คิดว่ามันเหมาะกับเครื่องยนต์ voxel ของฉันเพราะ ลูกบาศก์ / รายการ voxel ของฉันทั้งหมดจะมีขนาดเท่ากันทุกประการ

อีกทางเลือกหนึ่งคือเพียงสร้างอาร์เรย์ขนาด 16 * 16 * 16 และมีขนาดนั้นหนึ่งอันและคุณเติมด้วยรายการ และส่วนที่ไม่มีรายการใด ๆ จะมีค่า 0 เป็นค่า (0 = อากาศ) แต่ฉันเกรงว่านี่จะเสียความทรงจำมากมายและจะไม่เร็วมาก

จากนั้นอีกตัวเลือกหนึ่งคือเวกเตอร์สำหรับแต่ละอันและเติมด้วยลูกบาศก์ และคิวบ์ยังคงตำแหน่งในก้อน สิ่งนี้จะช่วยประหยัดหน่วยความจำ (ไม่มีบล็อกอากาศ) แต่ทำให้มองหาลูกบาศก์ที่ตำแหน่งเฉพาะช้ากว่ามาก

ดังนั้นฉันไม่สามารถหาทางออกที่ดีได้และฉันหวังว่าจะมีคนช่วยฉันได้ แล้วคุณจะใช้อะไรและทำไม

แต่ปัญหาอื่นคือการแสดงผล เพียงอ่านแต่ละอันและส่งไปยัง GPU โดยใช้ OpenGL นั้นง่าย แต่ช้ามาก การสร้างตาข่ายหนึ่งอันต่ออันจะดีกว่า แต่นั่นหมายความว่าทุกครั้งที่ฉันทำลายหนึ่งบล็อกฉันต้องสร้างทั้งก้อนซึ่งอาจใช้เวลาสักหน่อยทำให้เกิดอาการสะอึกเล็กน้อย แต่เห็นได้ชัดซึ่งฉันไม่ต้องการเช่นกัน นั่นจะยากขึ้น แล้วฉันจะทำให้ลูกบาศก์เป็นอย่างไร เพียงสร้างคิวบ์ทั้งหมดในบัฟเฟอร์จุดสุดยอดหนึ่งอันต่อหนึ่งก้อนและแสดงผลนั้นและอาจลองใส่มันในเธรดอื่นหรือมีอีกวิธีหนึ่ง?

ขอบคุณ!


1
คุณควรใช้อินสแตนซ์สำหรับการสร้างคิวบ์ของคุณ คุณสามารถค้นหากวดวิชาที่นี่learnopengl.com/Advanced-OpenGL/Instancing สำหรับการจัดเก็บคิวบ์: คุณมีข้อ จำกัด ของหน่วยความจำที่แข็งแกร่งบนฮาร์ดแวร์หรือไม่? 16 ^ 3 คิวบ์ดูไม่ทรงจำมากเกินไป
Turms

@Turms ขอบคุณสำหรับความคิดเห็นของคุณ! ฉันไม่มีข้อ จำกัด ด้านหน่วยความจำที่แข็งแกร่งมันเป็นเพียงพีซีปกติ แต่ฉันคิดว่าถ้าทุก ๆ ชิ้นส่วนใหญ่มีอากาศ 50% และโลกนั้นใหญ่มากดังนั้นต้องมีความทรงจำที่สูญเปล่า แต่มันอาจไม่เหมือนที่คุณพูด ดังนั้นฉันควรไปชิ้น 16 * 16 * 16 กับบล็อกจำนวนคงที่หรือไม่ และคุณก็บอกว่าฉันควรใช้อินสแตนซ์นั่นเป็นสิ่งที่จำเป็นจริงๆเหรอ? ความคิดของฉันคือการสร้างตาข่ายสำหรับแต่ละชิ้นเพราะวิธีการที่ฉันสามารถออกจากสามเหลี่ยมที่มองไม่เห็นทั้งหมด

6
ฉันไม่แนะนำให้ใช้อินสแตนซ์สำหรับคิวบ์ตามที่ Turms อธิบายสิ่งนี้จะลดการเรียกสายของคุณเท่านั้น แต่ไม่ทำอะไรเลยสำหรับใบหน้าที่เกินขนาดและซ่อนเร้น - อันที่จริงมันเชื่อมโยงมือของคุณจากการแก้ปัญหานั้น ต้องเหมือนกัน - คุณไม่สามารถลบใบหน้าที่ซ่อนของลูกบาศก์บางส่วนหรือรวมใบหน้า coplanar เป็นรูปหลายเหลี่ยมขนาดใหญ่
DMGregory

การเลือกเครื่องยนต์ voxel ที่ดีที่สุดอาจเป็นเรื่องท้าทาย คำถามใหญ่ที่ถามตัวเองคือ "ฉันต้องทำอะไรจึงต้องทำกับ voxels ของฉัน" ที่แนะนำการดำเนินงาน ตัวอย่างเช่นคุณมีความกังวลเกี่ยวกับความยากลำบากในการพิจารณาว่า voxel ใดอยู่ที่ไหนในต้นเดือนตุลาคม อัลกอริธึม ต.ค. ของต้นไม้นั้นยอดเยี่ยมสำหรับปัญหาที่สามารถสร้างข้อมูลนี้ได้ตามต้องการในขณะที่เดินบนต้นไม้ หากคุณมีปัญหาเฉพาะซึ่งมีราคาแพงเกินไปคุณอาจพิจารณาทางเลือกอื่น ๆ
Cort Ammon

คำถามสำคัญอีกข้อหนึ่งคือความถี่ในการอัพเดท voxels อัลกอริธึมบางอย่างยอดเยี่ยมถ้าพวกเขาสามารถประมวลผลข้อมูลล่วงหน้าเพื่อเก็บไว้ได้อย่างมีประสิทธิภาพ แต่มีประสิทธิภาพน้อยลงหากข้อมูลได้รับการอัปเดตอย่างสม่ำเสมอ (ข้อมูลอาจเป็นแบบจำลองการจำลองของเหลวตามอนุภาค)
Cort Ammon

คำตอบ:


23

การจัดเก็บบล็อกเป็นตำแหน่งและค่าจริง ๆ แล้วไม่มีประสิทธิภาพมาก แม้จะไม่มีค่าใช้จ่ายใด ๆ ที่เกิดจากโครงสร้างหรือวัตถุที่คุณใช้คุณต้องเก็บค่าที่แตกต่างกัน 4 ค่าต่อบล็อก มันจะสมเหตุสมผลที่จะใช้มันเหนือวิธี "การจัดเก็บบล็อกในอาร์เรย์คงที่" (ที่คุณอธิบายไว้ก่อนหน้า) คือเมื่อเพียงหนึ่งในสี่ของบล็อกมีความมั่นคงและวิธีนี้คุณไม่ได้ใช้วิธีการเพิ่มประสิทธิภาพอื่น ๆ บัญชีผู้ใช้.

Octrees นั้นยอดเยี่ยมมากสำหรับเกมที่ใช้ voxel เนื่องจากพวกเขาเชี่ยวชาญในการเก็บข้อมูลที่มีฟีเจอร์ที่ใหญ่กว่า (เช่น patch ของบล็อกเดียวกัน) เพื่อแสดงสิ่งนี้ฉันใช้ quadtree (โดยทั่วไปเป็น octrees ใน 2d):

นี่เป็นชุดเริ่มต้นของฉันที่มีไทล์ 32x32 ซึ่งจะเท่ากับ 1024 ค่า: ป้อนคำอธิบายรูปภาพที่นี่

การจัดเก็บสิ่งนี้เป็น 1024 ค่าที่แยกกันดูเหมือนจะไม่มีประสิทธิภาพ แต่เมื่อคุณไปถึงขนาดแผนที่คล้ายกับเกมเช่นTerrariaหน้าจอการโหลดจะใช้เวลาหลายวินาที และถ้าคุณเพิ่มเป็นมิติที่สามมันจะเริ่มใช้พื้นที่ทั้งหมดในระบบ

Quadtrees (หรือ octrees ใน 3d) สามารถช่วยสถานการณ์ได้ ในการสร้างหนึ่งคุณสามารถไปจากแผ่นกระเบื้องและจัดกลุ่มเข้าด้วยกันหรือไปจากเซลล์ขนาดใหญ่หนึ่งและคุณแบ่งมันจนกว่าคุณจะถึงกระเบื้อง ฉันจะใช้วิธีแรกเพราะง่ายต่อการมองเห็น

ดังนั้นในการวนซ้ำครั้งแรกคุณจัดกลุ่มทุกอย่างลงในเซลล์ 2x2 และหากเซลล์มีไพ่ประเภทเดียวกันคุณจะลดขนาดไทล์และเพียงเก็บประเภท หลังจากหนึ่งรอบซ้ำแผนที่ของเราจะมีลักษณะเช่นนี้:

ป้อนคำอธิบายรูปภาพที่นี่

เส้นสีแดงทำเครื่องหมายสิ่งที่เราจัดเก็บ แต่ละตารางมีค่าเพียง 1 สิ่งนี้ทำให้ขนาดลดลงจาก 1024 ค่าเป็น 439 นั่นคือลดลง 57%

แต่คุณรู้มนต์ ไปอีกขั้นหนึ่งแล้วจัดกลุ่มเหล่านี้ลงในเซลล์:

ป้อนคำอธิบายรูปภาพที่นี่

สิ่งนี้จะลดจำนวนของค่าที่เก็บไว้เป็น 367 นั่นเป็นเพียง 36% ของขนาดดั้งเดิม

เห็นได้ชัดว่าคุณต้องทำหารนี้จนกว่าทุกเซลล์ที่อยู่ติดกัน 4 (8 บล็อกที่อยู่ติดกันใน 3 มิติ) ภายในก้อนจะถูกเก็บไว้ในเซลล์เดียวโดยการแปลงก้อนเป็นเซลล์ขนาดใหญ่

สิ่งนี้ยังมีประโยชน์อื่น ๆ ส่วนใหญ่เมื่อทำการชนกัน แต่คุณอาจต้องการสร้าง octree แยกต่างหากสำหรับสิ่งนั้นซึ่งสนใจเฉพาะว่าบล็อกเดียวนั้นมั่นคงหรือไม่ ด้วยวิธีนี้แทนที่จะตรวจสอบการชนกันของทุกบล็อคที่อยู่ในกลุ่มก้อนคุณสามารถทำได้กับเซลล์


ขอบคุณสำหรับการตอบกลับของคุณ! ดูเหมือนว่า octree เป็นวิธีที่จะไป (เนื่องจากเครื่องยนต์ voxel ของฉันจะเป็นแบบ 3 มิติ) ฉันมีคำถามแบบ frew ที่ฉันอยากจะถาม: ภาพสุดท้ายของคุณแสดงชิ้นส่วนสีดำที่จะมีช่องสี่เหลี่ยมขนาดใหญ่กว่า เอ็นจิ้นที่คุณสามารถดัดแปลงภูมิประเทศ voxel ได้ฉันต้องการเก็บทุกอย่างที่มีบล็อกขนาดเท่ากันเพราะไม่อย่างนั้นมันจะทำให้สิ่งที่ซับซ้อนมากเป็นไปได้ใช่มั้ย (ฉันจะลดความว่างเปล่า / ช่องอากาศของหลักสูตร) มีการสอนบางอย่างเกี่ยวกับวิธีการตั้งโปรแกรมแปดเดือนหรือไม่? ขอบคุณ!

7
@ appmaker1358 ไม่เป็นปัญหาเลย หากผู้เล่นพยายามที่จะปรับเปลี่ยนบล็อกขนาดใหญ่จากนั้นคุณแบ่งมันเป็นบล็อกเล็ก ๆในขณะนั้น ไม่จำเป็นต้องเก็บค่า "ร็อค" ขนาด 16x16x16 ไว้เมื่อคุณสามารถพูดแทน "ก้อนทั้งหมดนี้เป็นหินแข็ง" ได้จนกว่าจะไม่เป็นจริงอีกต่อไป
DMGregory

1
@ appmaker1358 ตามที่ DMGregory กล่าวว่าการอัปเดตข้อมูลที่เก็บไว้ในเดือนตุลาคมนั้นค่อนข้างง่าย สิ่งที่คุณต้องทำคือแบ่งเซลล์ที่มีการเปลี่ยนแปลงเกิดขึ้นจนกระทั่งทุกเซลล์ย่อยมีบล็อกประเภทเดียวเท่านั้น นี่คือตัวอย่างการโต้ตอบกับควอดทรี สร้างหนึ่งง่ายเช่นกัน คุณสร้างเซลล์ขนาดใหญ่หนึ่งเซลล์ที่มีส่วนประกอบทั้งหมดจากนั้นคุณวนซ้ำเซลล์ทุกเซลล์ (เซลล์ที่ไม่มีลูก) ตรวจสอบว่าส่วนของพื้นที่ที่มันแทนนั้นประกอบด้วยบล็อกหลายประเภทหรือไม่ถ้าใช่ให้แบ่ง มือถือ
Bálint

@ appmaker1358 ปัญหาที่ใหญ่กว่านั้นจริง ๆ แล้วกลับกัน - ทำให้แน่ใจว่า octree ไม่เต็มไปด้วยใบไม้ที่มีเพียงบล็อกเดียวซึ่งสามารถเกิดขึ้นได้ง่ายในเกมสไตล์ Minecraft อย่างไรก็ตามมีวิธีแก้ไขปัญหามากมาย - เพียงแค่เลือกสิ่งที่คุณเห็นว่าเหมาะสม และมันจะกลายเป็นปัญหาที่แท้จริงเมื่อมีสิ่งปลูกสร้างมากมายเกิดขึ้น
Luaan

Octrees ไม่จำเป็นต้องเป็นตัวเลือกที่ดีที่สุด นี่คือการอ่านที่น่าสนใจ: 0fps.net/2012/01/14/an-analysis-of-minecraft-like-engines
Polygnome

7

Octrees มีอยู่เพื่อแก้ปัญหาที่คุณอธิบายอย่างแม่นยำทำให้สามารถจัดเก็บข้อมูลที่กระจัดกระจายได้อย่างหนาแน่นโดยไม่ต้องใช้เวลาค้นหามาก

ความจริงที่ว่า voxels ของคุณมีขนาดเท่ากันก็หมายความว่าแปดของคุณมีความลึกคงที่ เช่น. สำหรับ 16x16x16 อันคุณจำเป็นต้องมีอย่างน้อย 5 ระดับของต้นไม้:

  • รากอันหนา (16x16x16)
    • ชั้นแปด octant (8x8x8)
      • ชั้นสอง octant (4x4x4)
        • ออกเทนชั้นสาม (2x2x2)
          • voxel เดียว (1x1x1)

ซึ่งหมายความว่าคุณมีอย่างน้อย 5 ขั้นตอนในการค้นหาว่ามี voxel ที่ตำแหน่งเฉพาะในกลุ่มหรือไม่:

  • chunk root: chunk ทั้งหมดมีค่าเท่ากัน (เช่น all air) หรือไม่ ถ้าเป็นเช่นนั้นเราเสร็จแล้ว ถ้าไม่...
    • ชั้นแรก: octant ที่มีตำแหน่งนี้มีค่าเท่ากันทั้งหมดหรือไม่ ถ้าไม่...
      • ชั้นที่สอง...
        • ชั้นที่สาม ...
          • ตอนนี้เรากำลังพูดถึง voxel เดี่ยวและสามารถคืนค่าได้

สั้นกว่าการสแกนถึง 1% ของจำนวนทั้งหมดผ่านอาร์เรย์มากถึง 4096 voxels!

โปรดสังเกตว่าสิ่งนี้ช่วยให้เราบีบอัดข้อมูลทุกที่ที่มีค่าออกเทนเต็มของค่าเดียวกัน - ไม่ว่าค่านั้นจะเป็นอากาศทั้งหมดหรือหินทั้งหมดหรืออย่างอื่น มันเป็นเพียงที่ octants มีค่าผสมที่เราต้องแบ่งย่อยต่อไปจนถึงขีด จำกัด ของโหนดใบเดียว voxel


สำหรับการพูดถึงเด็ก ๆ โดยทั่วไปเราจะดำเนินการตามลำดับของ Mortonดังนี้:

  1. X- Y- Z-
  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 +

ดังนั้นการนำทางโหนด Octree ของเราอาจมีลักษณะเช่นนี้:

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
    );
}

ขอบคุณสำหรับการตอบกลับของคุณ! ดูเหมือนว่าแปดของเป็นวิธีที่จะไป แต่ฉันมีคำถาม 2 ข้อคุณบอกว่า octree นั้นเร็วกว่าการสแกนผ่านอาเรย์ซึ่งถูกต้อง แต่ฉันไม่จำเป็นต้องทำเช่นนั้นเพราะอาเรย์นั้นอาจเป็นแบบคงที่ซึ่งหมายถึงฉันสามารถคำนวณว่าคิวบ์ที่ฉันต้องการอยู่ที่ไหน เหตุใดฉันจึงต้องสแกน คำถามที่สองในชั้นสุดท้ายของ octree (1x1x1) ฉันจะรู้ได้อย่างไรว่า cube อยู่ที่ไหนเพราะถ้าฉันเข้าใจถูกต้องและโหนด octree มีอีก 8 โหนดคุณจะรู้ได้อย่างไรว่าโหนดใดเป็นของตำแหน่ง 3d (หรือฉันควรจะจำได้ว่าตัวเอง?)

ใช่คุณครอบคลุมกรณีของอาร์เรย์ที่ละเอียดถึง 16x16x16 voxels ในคำถามของคุณและดูเหมือนจะปฏิเสธ 4K ต่อหน่วยความจำอันละอันของรอยเท้า (สมมติว่า voxel ID แต่ละตัวมีขนาดใหญ่เกินไป) การค้นหาที่คุณพูดถึงนั้นมาจากการจัดเก็บรายการ voxels ด้วยตำแหน่งบังคับให้คุณสแกนผ่านรายการเพื่อค้นหา voxel ที่ตำแหน่งเป้าหมายของคุณ 4096 นี่คือขอบเขตสูงสุดของความยาวของรายการนั้น - โดยทั่วไปแล้วจะมีขนาดเล็กกว่านั้น แต่โดยทั่วไปแล้วยังคงลึกกว่าการค้นหาแบบแปดเดือนที่เกี่ยวข้อง
DMGregory
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.