C ++ में एक voxel Engine के लिए स्वर संचय करना


9

मैं थोड़ा voxel इंजन लिखने की कोशिश कर रहा हूं क्योंकि यह मजेदार है, लेकिन वास्तविक स्वरों को संग्रहीत करने का सबसे अच्छा तरीका खोजने के लिए संघर्ष करना है। मुझे पता है कि मुझे किसी प्रकार की विखंडन की आवश्यकता होगी, इसलिए मुझे पूरी दुनिया को स्मृति में रखने की आवश्यकता नहीं है, और मुझे पता है कि मुझे उन्हें उचित प्रदर्शन के साथ प्रस्तुत करना होगा।

मैंने ऑक्ट्रीज़ के बारे में पढ़ा है और जो मैं समझता हूं कि यह 1 क्यूब से शुरू होता है, और उस क्यूब में 8 और क्यूब्स हो सकते हैं, और उन सभी 8 क्यूब्स में एक और 8 क्यूब्स आदि हो सकते हैं, लेकिन मुझे नहीं लगता कि यह मेरे वोकल इंजन को फिट करता है क्योंकि मेरे voxel क्यूब्स / आइटम सभी एक ही आकार के होंगे।

तो एक और विकल्प केवल 16 * 16 * 16 आकार का एक सरणी बनाना है और यह एक हिस्सा है, और आप इसे आइटम से भरते हैं। और ऐसे हिस्से जहां कोई भी वस्तु नहीं है, वहां 0 मान मान (0 = हवा) होगा। लेकिन मुझे डर है कि यह बहुत सारी मेमोरी बर्बाद करने वाला है और यह बहुत तेज़ नहीं होगा।

फिर एक और विकल्प प्रत्येक चंक के लिए एक वेक्टर है, और इसे क्यूब्स के साथ भरें। और क्यूब चंक में अपना स्थान रखता है। यह मेमोरी को बचाता है (कोई एयर ब्लॉक नहीं), लेकिन एक विशिष्ट स्थान पर क्यूब की तलाश बहुत धीमी करता है।

इसलिए मुझे वास्तव में एक अच्छा समाधान नहीं मिल रहा है, और मुझे उम्मीद है कि कोई मेरी मदद कर सकता है। तो आप क्या उपयोग करेंगे और क्यों?

लेकिन एक और समस्या प्रतिपादन है। बस प्रत्येक चंक को पढ़ना और ओपनजीएल का उपयोग करके इसे GPU पर भेजना आसान है, लेकिन बहुत धीमी गति से। प्रति जाली एक जाल उत्पन्न करना बेहतर होगा, लेकिन इसका मतलब है कि हर बार जब मैं एक ब्लॉक को तोड़ता हूं, तो मुझे पूरे चंक को फिर से बनाना होगा जो कि मामूली लेकिन ध्यान देने योग्य हिचकी पैदा करने में थोड़ा समय ले सकता है, जो मैं स्पष्ट रूप से या तो नहीं चाहता हूं। तो यह कठिन होगा। तो मैं क्यूब्स को कैसे प्रस्तुत करूंगा? बस एक क्यूबेक बफर में प्रत्येक क्यूब्स में सभी क्यूब्स बनाएं और उसे रेंडर करें और हो सकता है कि इसे दूसरे धागे में डालने की कोशिश करें, या कोई और तरीका है?

धन्यवाद!


1
आपको अपने क्यूब्स को प्रस्तुत करने के लिए इंस्टेंसिंग का उपयोग करना चाहिए। आप यहां एक ट्यूटोरियल जान सकते हैं learnopengl.com/Advanced-OpenGL/Instancing । क्यूब्स को संग्रहीत करने के लिए: क्या आपके पास हार्डवेयर पर मजबूत मेमोरी बाधाएं हैं? 16 ^ 3 क्यूब बहुत ज्यादा मेमोरी नहीं लगती।
Turms

@ आपकी टिप्पणी के लिए धन्यवाद! मेरे पास मजबूत मेमोरी की कमी नहीं है, यह सिर्फ एक नियमित पीसी है। लेकिन मैंने सोचा, अगर हर शीर्ष पर सबसे अधिक 50% हवा है, और दुनिया बहुत बड़ी है, तो काफी बर्बादी हुई स्मृति होनी चाहिए। लेकिन यह शायद ज्यादा नहीं है जैसा आप कहते हैं। तो क्या मुझे सिर्फ 16 * 16 * 16 चंक के लिए एक स्थिर राशि के साथ जाना चाहिए? और यह भी, आप कहते हैं कि मुझे इंस्टेंटिंग का उपयोग करना चाहिए, क्या वास्तव में इसकी आवश्यकता है? मेरा विचार प्रत्येक चंक के लिए एक जाल उत्पन्न करना था, क्योंकि इस तरह से मैं सभी अदृश्य त्रिकोणों को छोड़ सकता हूं।

6
मैं क्यूब्स के लिए इंस्टेंसेस का उपयोग करने की सलाह नहीं देता हूं, क्योंकि यह केवल आपके ड्रॉ कॉल्स को कम करेगा, लेकिन ओवरड्रॉ और छिपे हुए चेहरों के लिए कुछ भी नहीं करता है - वास्तव में यह आपके हाथों को उस समस्या को हल करने से रोकता है, क्योंकि सभी क्यूब्स को काम करने के लिए इंस्टेंट करने के लिए समान होना चाहिए - आप कुछ क्यूब्स के छिपे हुए चेहरों को हटा नहीं सकते हैं या कॉपलनार के चेहरों को बड़े एकल बहुभुजों में विलय कर सकते हैं।
DMGregory

सबसे अच्छा voxel इंजन चुनना एक चुनौती हो सकती है। अपने आप से पूछने के लिए बड़ा सवाल यह है कि "मुझे अपने स्वरों पर क्या संचालन करने की आवश्यकता है?" जो संचालन का मार्गदर्शन करता है। उदाहरण के लिए, आप इस बारे में चिंतित हैं कि यह पता लगाना कितना कठिन है कि एक ऑक्स-ट्री में कौन सा स्वर है। ऑक्ट-ट्री एल्गोरिदम उन समस्याओं के लिए महान हैं जो इस जानकारी को आवश्यकतानुसार उत्पन्न कर सकते हैं क्योंकि यह पेड़ (अक्सर पुनरावर्ती तरीके से) चलता है। यदि आपके पास विशिष्ट समस्याएं हैं जहां यह बहुत महंगा है, तो आप अन्य विकल्पों को देख सकते हैं।
कोरट अमोन

एक और बड़ा सवाल यह है कि कितनी बार स्वरों को अद्यतन किया जाता है। कुछ एल्गोरिदम महान हैं अगर वे डेटा को कुशलतापूर्वक संग्रहीत करने के लिए पूर्व-प्रक्रिया कर सकते हैं, लेकिन कम कुशल अगर डेटा को लगातार अपडेट किया जा रहा है (जैसा कि डेटा कण आधारित द्रव सिमुलेशन में हो सकता है)
Cort Ammon

जवाबों:


23

ब्लॉकों को पदों और मूल्यों के रूप में संग्रहीत करना वास्तव में बहुत अक्षम है। यहां तक ​​कि आपके द्वारा उपयोग किए जाने वाले संरचना या ऑब्जेक्ट के कारण किसी भी ओवरहेड के बिना, आपको प्रति ब्लॉक 4 अलग-अलग मूल्यों को संग्रहीत करने की आवश्यकता है। इसका केवल यह अर्थ होगा कि "फिक्स्ड एरियर्स में स्टोरिंग स्टॉक्स" विधि (जिसे आपने पहले वर्णित किया गया था) से अधिक उपयोग किया है, जब ब्लॉक का केवल एक चौथाई ठोस होता है, और इस तरह से आप किसी अन्य ऑप्टिमाइज़ेशन के तरीकों को भी नहीं अपनाते हैं। लेखा।

ऑक्ट्रेसेस वास्तव में वॉक्सल आधारित गेम के लिए महान हैं, क्योंकि वे बड़ी सुविधाओं (जैसे ब्लॉक के पैच) के साथ डेटा संग्रहीत करने में विशेषज्ञ हैं। इसका वर्णन करने के लिए, मैंने एक क्वाडट्री (मूल रूप से 2d में ऑक्टर्स) का उपयोग किया:

यह मेरा शुरुआती सेट है जिसमें 32x32 टाइल हैं, जो 1024 मान के बराबर होगा: यहाँ छवि विवरण दर्ज करें

इसे 1024 अलग-अलग मानों के रूप में संग्रहीत करना अक्षम नहीं लगता है, लेकिन एक बार जब आप गेम के आकार जैसे कि टेरारिया , लोडिंग स्क्रीन को कई सेकंड तक ले जाएंगे। और यदि आप इसे तीसरे आयाम तक बढ़ाते हैं, तो यह सिस्टम में सभी जगह का उपयोग करना शुरू कर देता है।

Quadtrees (या 3D में ऑक्ट्रीज़) स्थिति में मदद कर सकते हैं। एक बनाने के लिए, आप या तो टाइलों से जा सकते हैं और उन्हें एक साथ समूहित कर सकते हैं, या एक विशाल सेल से जा सकते हैं और इसे तब तक विभाजित करते हैं जब तक आप टाइल्स तक नहीं पहुंच जाते। मैं पहले दृष्टिकोण का उपयोग करूंगा, क्योंकि यह कल्पना करना आसान है।

तो, पहले पुनरावृत्ति में आप सब कुछ 2x2 कोशिकाओं में समूहित करते हैं, और यदि एक सेल में केवल एक ही प्रकार की टाइलें होती हैं, तो आप टाइल्स को छोड़ देते हैं और बस प्रकार को संग्रहीत करते हैं। एक पुनरावृत्ति के बाद, हमारा नक्शा इस तरह दिखाई देगा:

यहाँ छवि विवरण दर्ज करें

लाल रेखाएं जो हम संग्रहीत करते हैं, उसे चिह्नित करते हैं। प्रत्येक वर्ग सिर्फ 1 मूल्य है। इसने आकार को 1024 मान से घटाकर 439 कर दिया, यह 57% की कमी है।

लेकिन आप मंत्र जानते हैं । चलिए एक कदम आगे बढ़ते हैं और इन्हें कोशिकाओं में समूहित करते हैं:

यहाँ छवि विवरण दर्ज करें

इससे संग्रहीत मानों की मात्रा 367 तक कम हो गई। यह मूल आकार का केवल 36% है।

आपको स्पष्ट रूप से इस विभाजन को तब तक करने की आवश्यकता है जब तक कि प्रत्येक 4 आसन्न सेल (3 डी में 8 आसन्न ब्लॉक) एक चेंक के अंदर संग्रहीत न हो जाए, अनिवार्य रूप से एक बड़े सेल में एक चंक को परिवर्तित करना।

यह भी कुछ अन्य लाभ हैं, मुख्य रूप से टकराव करते समय, लेकिन आप उसके लिए एक अलग ऑक्ट्री बनाना चाहते हैं, जो केवल इस बात की परवाह करता है कि एक एकल ब्लॉक ठोस है या नहीं। इस तरह, एक चंक के अंदर हर ब्लॉक के लिए टकराव के खिलाफ जांच के बजाय, आप इसे केवल कोशिकाओं के खिलाफ कर सकते हैं।


आपके जवाब का धन्यवाद! ऐसा लगता है कि ऑक्ट्री जाने का रास्ता है। (चूंकि मेरा वोक्सल इंजन 3 डी होगा) मेरे पास एक फ़्रीव प्रश्न हैं, हालांकि मैं पूछना चाहता हूं: आपकी आखिरी तस्वीर में बड़े हिस्से होने के लिए काले हिस्से दिखाई देते हैं, क्योंकि मेरा इरादा एक मिनी क्राफ्ट की तरह है। इंजन जहां आप voxel इलाके को संशोधित कर सकते हैं, मैं हर उस चीज को रखना पसंद करूंगा, जिसमें एक ब्लॉक एक ही आकार का हो, क्योंकि अन्यथा यह चीजों को बहुत जटिल बना देगा, यह सही है। (मैं अभी भी खाली / एयर स्लॉट्स को आसान बनाउंगा।) दूसरा , वहाँ कैसे एक ऑक्ट्री कार्यक्रम होगा पर ट्यूटोरियल के कुछ प्रकार है? धन्यवाद!

7
@ appmaker1358 कि कोई समस्या नहीं है। यदि खिलाड़ी एक बड़े ब्लॉक को संशोधित करने की कोशिश करता है, तो आप उसे उसी समय छोटे ब्लॉक में तोड़ देते हैं । "रॉक" के 16x16x16 मूल्यों को संग्रहीत करने की कोई आवश्यकता नहीं है जब आप कह सकते हैं कि "यह पूरी तरह से ठोस चट्टान है" जब तक कि यह अब सच नहीं है।
DMGregory

1
@ appmaker1358 जैसा कि DMGregory ने कहा, एक ऑक्ट्री में संग्रहीत डेटा को अपडेट करना अपेक्षाकृत आसान है। आपको बस इतना करना है कि सेल को विभाजित करें जब तक कि हर उप सेल में केवल एक ही प्रकार का ब्लॉक न हो। यहाँ एक चतुर्भुज के साथ एक इंटरैक्टिव उदाहरण है । एक को उत्पन्न करना भी सरल है। आप एक बड़ी सेल बनाते हैं, जिसमें पूरी तरह से चंक होता है, फिर आप प्रत्येक पत्ती सेल (कोशिकाओं में जिनके बच्चे नहीं होते हैं) से गुजरते हैं, यह जाँचते हैं कि क्या यह जिस भूभाग का प्रतिनिधित्व करता है उसमें कई प्रकार के ब्लॉक हैं, यदि हाँ, तो उप-विभाजित करें सेल
बैलिंट

@ appmaker1358 बड़ी समस्या वास्तव में रिवर्स है - यह सुनिश्चित करना कि ओक्ट्री सिर्फ एक ब्लॉक के साथ पत्तियों से भरा नहीं है, जो आसानी से एक Minecraft शैली के खेल में हो सकता है। हालाँकि, समस्या के कई समाधान हैं - यह केवल आपको जो भी उपयुक्त लगे उसे चुनने के बारे में है। और यह केवल एक वास्तविक समस्या बन जाती है जब बहुत सारी इमारत चल रही होती है।
लुआं

ऑक्ट्रेसेस जरूरी सर्वश्रेष्ठ विकल्प नहीं हैं। यहाँ एक दिलचस्प पढ़ा है: 0fps.net/2012/01/14/an-analysis-of-minecraft-like-engines
पॉलिग्नोम

7

ऑक्ट्रेसेस आपके द्वारा वर्णित समस्या को हल करने के लिए मौजूद हैं, जिससे बड़ी खोज समय के बिना विरल डेटा के घने भंडारण की अनुमति मिलती है।

यह तथ्य कि आपके स्वर एक ही आकार के हैं, इसका मतलब है कि आपकी ओक्ट्री में एक निश्चित गहराई है। जैसे। 16x16x16 चंक के लिए, आपको पेड़ के अधिकतम 5 स्तरों की आवश्यकता है:

  • चंक रूट (16x16x16)
    • पहली श्रेणी अष्टक (8x8x8)
      • दूसरी श्रेणी अष्टक (4x4x4)
        • तृतीय श्रेणी अष्टक (2x2x2)
          • एकल स्वर (1x1x1)

इसका मतलब है कि आपके पास यह जानने के लिए कि चंक में एक विशेष स्थिति में एक voxel है या नहीं, जाने के लिए अधिकतम 5 चरण हैं:

  • chunk root: क्या पूरा हिस्सा एक ही मूल्य है (उदाहरण के लिए। सभी हवा)? यदि हां, तो हम कर रहे हैं। अगर नहीं...
    • प्रथम श्रेणी: ओकटेंट है जिसमें इस स्थिति में सभी समान मूल्य हैं? अगर नहीं...
      • दूसरा स्तर...
        • तीसरी श्रेणी ...
          • अब हम एक एकल स्वर को संबोधित कर रहे हैं, और इसके मूल्य को वापस कर सकते हैं।

4096 voxels की एक सरणी के माध्यम से भी रास्ते का 1% स्कैनिंग की तुलना में बहुत कम!

ध्यान दें कि यह हमें डेटा को उसी स्थान पर संपीड़ित करने देता है जहां एक ही मूल्य का एक पूर्ण ऑक्टेंट है - चाहे वह मूल्य सभी हवा या सभी रॉक या कुछ और हो। यह केवल वही है जहां ऑक्टेंट्स में मिश्रित मूल्य होते हैं, जिन्हें हमें एकल-वोकेल लीफ नोड्स की सीमा तक नीचे की ओर झुकना पड़ता है।


एक बंक के बच्चों को संबोधित करने के लिए, आमतौर पर हम मॉर्टन क्रम में आगे बढ़ेंगे , कुछ इस तरह से:

  1. एक्स- वाई- जेड-
  2. एक्स- वाई- जेड +
  3. X- Y + Z-
  4. एक्स- वाई + जेड +
  5. X + Y- Z-
  6. एक्स + वाई- जेड +
  7. X + Y + Z-
  8. एक्स + वाई + जेड +

तो, हमारी ऑक्ट्री नोड नेविगेशन कुछ इस तरह दिख सकती है:

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 प्रश्न हैं, हालांकि, आप कहते हैं कि ओक्ट्री एक सरणी के माध्यम से स्कैन करने की तुलना में तेज़ है, जो सही है। लेकिन मुझे यह करने की आवश्यकता नहीं है कि जैसा कि सरणी स्थिर अर्थ हो सकता है मैं गणना कर सकता हूं कि मुझे जहां क्यूब की आवश्यकता है वह कहां है। तो मुझे स्कैन करने की आवश्यकता क्यों होगी? दूसरा सवाल, ऑक्ट्री के अंतिम टियर (1x1x1) में, मुझे कैसे पता चलेगा कि कौन सा क्यूब कहाँ है, क्योंकि अगर मैं इसे सही तरीके से समझता हूं, और ऑक्ट्री नोड में 8 और नोड हैं, तो आपको कैसे पता चलेगा कि कौन सा नोड किस 3D स्थिति से संबंधित है ? (या मैं खुद को याद करने वाला हूं?)

हां, आपने पहले ही अपने प्रश्न में 16x16x16 स्वरों के एक विस्तृत सरणी के मामले को कवर किया था, और 4K प्रति chunk मेमोरी फ़ुटप्रिंट को अस्वीकार करना प्रतीत होता था (प्रत्येक voxel आईडी को एक बाइट मानकर) अत्यधिक है। आपके द्वारा उल्लिखित खोज एक स्थिति के साथ voxels की एक सूची को संग्रहीत करते समय आती है, जिससे आप अपने लक्षित स्थान पर voxel को खोजने के लिए सूची के माध्यम से स्कैन करने के लिए मजबूर हो जाते हैं। 4096 यहां उस सूची की लंबाई की ऊपरी सीमा है - आम तौर पर यह उससे छोटा होगा, लेकिन आमतौर पर एक संबंधित ऑक्ट्री खोज की तुलना में अधिक गहरा होता है।
DMGregory
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.