जावा बनाम सी ++ के संबंध में, मैंने दोनों में एक voxel इंजन लिखा है (C ++ संस्करण ऊपर दिखाया गया है)। मैं भी 2004 से (जब वे प्रचलन में नहीं थे) voxel इंजन लिख रहे हैं। :) मैं थोड़ी झिझक के साथ कह सकता हूं कि C ++ का प्रदर्शन कहीं बेहतर है (लेकिन इसे कोड करना भी अधिक कठिन है)। इसकी कम्प्यूटेशनल गति के बारे में कम, और स्मृति प्रबंधन के बारे में अधिक। नीचे हाथ, जब आप एक voxel दुनिया में व्हाट्स के रूप में ज्यादा डेटा आवंटित / डील कर रहे हैं, तो C (++) को हराने के लिए भाषा है। हालाँकि, आपको अपने लक्ष्य के बारे में सोचना चाहिए। यदि प्रदर्शन आपकी सर्वोच्च प्राथमिकता है, तो C ++ के साथ जाएं। यदि आप केवल रक्तस्राव-धार प्रदर्शन के बिना एक खेल लिखना चाहते हैं, तो जावा निश्चित रूप से स्वीकार्य है (जैसा कि Minecraft द्वारा स्पष्ट किया गया है)। कई तुच्छ / धार मामले हैं, लेकिन सामान्य तौर पर आप जावा से (1. लिखी गई) C ++ की तुलना में लगभग 1.75-2.0 गुना धीमी गति से चलने की उम्मीद कर सकते हैं। आप यहां मेरे इंजन के खराब रूप से अनुकूलित, पुराने संस्करण को देख सकते हैं (EDIT: नया संस्करण यहां )। जबकि चंक पीढ़ी धीमी लग सकती है, ध्यान रखें कि यह 3 डी वोरोनोई आरेखों को स्वैच्छिक रूप से पैदा कर रहा है, सीपीयू पर सतह के मानदंडों, प्रकाश व्यवस्था, एओ और छाया की गणना ब्रूट-फोर्स विधियों के साथ कर रहा है। मैंने विभिन्न तकनीकों की कोशिश की है और मैं विभिन्न कैशिंग और इंस्टेंसेसिंग तकनीकों का उपयोग करके लगभग 100x तेज चंक पीढ़ी प्राप्त कर सकता हूं।
आपके बाकी सवालों का जवाब देने के लिए, कई चीजें हैं जो आप प्रदर्शन को बेहतर बनाने के लिए कर सकते हैं।
- कैशिंग। जहां भी आप कर सकते हैं, आपको एक बार डेटा की गणना करनी चाहिए। उदाहरण के लिए, मैं प्रकाश को दृश्य में सेंकता हूं। यह डायनेमिक लाइटिंग (स्क्रीन स्पेस में, पोस्ट-प्रोसेस के रूप में) का उपयोग कर सकता है, लेकिन लाइटिंग में बेकिंग का मतलब है कि मुझे त्रिकोण के लिए मानदंडों में पास नहीं करना है, जिसका अर्थ है ...।
वीडियो कार्ड के लिए जितना संभव हो उतना कम डेटा पास करें। एक बात जो लोग भूल जाते हैं, वह यह है कि आप जितना अधिक डेटा जीपीयू के पास जाएंगे, उतना ही अधिक समय लगेगा। मैं एक ही रंग और एक शीर्ष स्थिति में गुजरता हूं। यदि मैं दिन / रात चक्र करना चाहता हूं, तो मैं बस रंग ग्रेडिंग कर सकता हूं, या मैं दृश्य को फिर से जोड़ सकता हूं क्योंकि सूरज धीरे-धीरे बदलता है।
चूंकि GPU को डेटा पास करना इतना महंगा है, इसलिए सॉफ्टवेयर में एक इंजन लिखना संभव है जो कुछ मामलों में तेज है। सॉफ्टवेयर का लाभ यह है कि यह सभी प्रकार के डेटा हेरफेर / मेमोरी एक्सेस कर सकता है जो कि बस GPU पर संभव नहीं है।
बैच के आकार के साथ खेलते हैं। यदि आप एक GPU का उपयोग कर रहे हैं, तो प्रदर्शन आपके द्वारा पारित किए जाने वाले प्रत्येक शीर्ष सरणी के आधार पर नाटकीय रूप से भिन्न हो सकता है। तदनुसार, चंक्स के आकार के साथ चारों ओर खेलें (यदि आप चंक्स का उपयोग करते हैं)। मैंने पाया है कि 64x64x64 विखंडू बहुत अच्छी तरह से काम करते हैं। कोई फर्क नहीं पड़ता कि, अपने विखंडू घन (कोई आयताकार प्रिज्म) रखें। इससे कोडिंग और विभिन्न ऑपरेशन (जैसे परिवर्तन) आसान हो जाएंगे, और कुछ मामलों में, अधिक प्रदर्शन करने वाले। यदि आप हर आयाम की लंबाई के लिए केवल एक मूल्य संग्रहीत करते हैं, तो ध्यान रखें कि दो कम रजिस्टरों को गणना के दौरान चारों ओर स्वैप किया जाए।
प्रदर्शन सूचियों पर विचार करें (OpenGL के लिए)। भले ही वे "पुराने" तरीके से हों, लेकिन वे तेज हो सकते हैं। आपको एक प्रदर्शन सूची को एक चर में सेंकना चाहिए ... यदि आप वास्तविक समय में प्रदर्शन सूची निर्माण कार्यों को कॉल करते हैं, तो यह धीमी गति से धीमा होगा। प्रदर्शन सूची कैसे तेज होती है? यह केवल राज्य, बनाम प्रति-शीर्ष विशेषताएँ अद्यतन करता है। इसका मतलब है कि मैं छह चेहरों को पार कर सकता हूं, फिर एक रंग (बनाम स्वर के प्रत्येक शीर्ष के लिए एक रंग)। यदि आप GL_QUADS और क्यूबिक स्वर का उपयोग कर रहे हैं, तो यह प्रति वॉक्सेल तक 20 बाइट्स (160 बिट्स) तक बचा सकता है! (बिना किसी अल्फ़ा के 15 बाइट्स, हालाँकि आमतौर पर आप 4-बाइट को संरेखित रखना चाहते हैं।)
मैं "चंक्स", या डेटा के पृष्ठों को प्रस्तुत करने की एक क्रूर-बल विधि का उपयोग करता हूं, जो एक सामान्य तकनीक है। ऑक्ट्रेसेस के विपरीत, डेटा पढ़ना / प्रोसेस करना बहुत आसान / तेज़ है, हालाँकि मेमोरी फ्रेंडली कम है (हालाँकि, इन दिनों आपको $ 200- $ 300 के लिए 64 गीगाबाइट मेमोरी मिल सकती है ... ऐसा नहीं है कि औसत उपयोगकर्ता के पास है। जाहिर है, आप पूरी दुनिया के लिए एक विशाल सरणी आवंटित नहीं कर सकते हैं (एक 1024x1024x1024 voxels का सेट 4 गीगाबाइट मेमोरी है, यह मानते हुए कि 32-बिट इंट प्रति स्वर का उपयोग किया जाता है)। इसलिए आप दर्शक से उनकी निकटता के आधार पर कई छोटे सरणी आवंटित / डील करते हैं। आप डेटा को आवंटित भी कर सकते हैं, आवश्यक प्रदर्शन सूची प्राप्त कर सकते हैं, फिर मेमोरी को बचाने के लिए डेटा को डंप कर सकते हैं। मुझे लगता है कि आदर्श कॉम्बो ऑक्टर्स और सरणियों के एक संकर दृष्टिकोण का उपयोग करने के लिए हो सकता है - दुनिया की प्रक्रियात्मक पीढ़ी, प्रकाश व्यवस्था आदि करते समय डेटा को एक सरणी में संग्रहीत करें।
दूर तक रेंडर ... एक पिक्सेल काटा गया समय बच जाता है। अगर यह गहराई बफर टेस्ट पास नहीं करता है तो gpu एक पिक्सेल को टॉस करेगा।
व्यूपोर्ट (आत्म व्याख्यात्मक) में केवल खंड / पृष्ठ रेंडर करें। यहां तक कि अगर gpu व्यूपोर्ट के बाहर पोलगियन को क्लिप करना जानता है, तो भी इस डेटा को पारित करने में समय लगता है। मुझे नहीं पता कि इसके लिए सबसे कुशल संरचना क्या होगी ("शर्म की बात है," मैंने बीएसपी का पेड़ कभी नहीं लिखा है), लेकिन प्रति चंक आधार पर एक साधारण रेकास्ट भी प्रदर्शन में सुधार कर सकता है, और जाहिर तौर पर देखने वाले फ्रुम के खिलाफ परीक्षण होगा। समय बचाओ।
स्पष्ट जानकारी, लेकिन newbies के लिए: हर एक बहुभुज को हटा दें जो सतह पर नहीं है - अर्थात यदि एक स्वर में छह चेहरे होते हैं, तो उन चेहरों को हटा दें जो कभी नहीं मिलते हैं (दूसरे स्वर को छू रहे हैं)।
प्रोग्रामिंग में आप जो कुछ भी करते हैं, उसके सामान्य नियम के रूप में: CACHE LOCALITY! यदि आप चीजों को कैशे-लोकल (थोड़ी-थोड़ी देर के लिए भी) रख सकते हैं, तो इससे काफी फर्क पड़ेगा। इसका मतलब है कि अपने डेटा को कंफर्टेबल (उसी मेमोरी रीजन में) रखना, और मेमोरी के क्षेत्रों को स्विच करना भी अक्सर नहीं। , आदर्श रूप से, एक धागे पर प्रति काम करते हैं, और उस मेमोरी को थ्रेड के लिए अनन्य रखते हैं। यह सिर्फ सीपीयू कैश पर लागू नहीं होता है। इस तरह से कैश पदानुक्रम के बारे में सोचो (सबसे तेजी से सबसे धीमा): नेटवर्क (क्लाउड / डेटाबेस / आदि) -> हार्ड ड्राइव (एक SSD प्राप्त करें यदि आपके पास पहले से ही नहीं है), ram (यदि आपके पास पहले से नहीं है तो ट्रिपल चैनल या अधिक रैम प्राप्त करें), सीपीयू कैश (ओं), रजिस्टरों पर अपने डेटा को रखने का प्रयास करें। बाद वाला छोर, और जितना आपके पास है उससे अधिक स्वैप नहीं।
थ्रेडिंग। कर दो। वॉक्सल दुनिया थ्रेडिंग के लिए अच्छी तरह से अनुकूल हैं, क्योंकि प्रत्येक भाग की गणना (ज्यादातर) दूसरों से स्वतंत्र रूप से की जा सकती है ... मैंने शाब्दिक दुनिया की पीढ़ी में लगभग 4x सुधार (4 कोर, 8 थ्रेड कोर i7 पर) देखा जब मैंने लिखा था। थ्रेडिंग के लिए दिनचर्या।
चार / बाइट डेटा प्रकार का उपयोग न करें। या शॉर्ट्स। आपके औसत उपभोक्ता के पास एक आधुनिक एएमडी या इंटेल प्रोसेसर होगा (जैसा कि आप, शायद)। इन प्रोसेसर में 8 बिट रजिस्टर नहीं है। वे 32 बिट स्लॉट में डालकर बाइट्स की गणना करते हैं, फिर उन्हें मेमोरी में वापस (शायद) में परिवर्तित करते हैं। आपका कंपाइलर सभी प्रकार के वूडू कर सकता है, लेकिन एक 32 या 64 बिट नंबर का उपयोग आपको सबसे अधिक अनुमानित (और सबसे तेज़) परिणाम देने वाला है। इसी तरह, "बूल" मान 1 बिट नहीं लेता है; संकलक अक्सर एक बूल के लिए पूर्ण 32 बिट का उपयोग करेगा। यह आपके डेटा पर कुछ प्रकार के संपीड़न करने के लिए लुभावना हो सकता है। उदाहरण के लिए, आप 8 स्वरों को एक ही संख्या (2 ^ 8 = 256 संयोजनों) के रूप में संग्रहीत कर सकते हैं यदि वे सभी एक ही प्रकार / रंग के हों। हालाँकि, आपको इसके बारे में सोचना होगा - यह स्मृति का एक अच्छा सौदा बचा सकता है, लेकिन यह प्रदर्शन में भी बाधा डाल सकता है, यहां तक कि एक छोटे से अपघटन समय के साथ भी, क्योंकि यहां तक कि अतिरिक्त समय की छोटी मात्रा आपके दुनिया के आकार के साथ बहुत कम है। एक रेकास्ट की गणना करने की कल्पना करो; रेकास्ट के हर चरण के लिए, आपको विघटन एल्गोरिथ्म चलाना होगा (जब तक कि आप एक किरण चरण में 8 voxels के लिए गणना को सामान्य करने का एक स्मार्ट तरीका नहीं लेते हैं)।
जैसा कि जोस शावेज उल्लेख करते हैं, फ्लाईवेट डिजाइन पैटर्न उपयोगी हो सकता है। जैसे आप एक 2D गेम में टाइल का प्रतिनिधित्व करने के लिए बिटमैप का उपयोग करेंगे, आप अपनी दुनिया को कई 3D टाइल (या ब्लॉक) प्रकारों से बना सकते हैं। इसके लिए नकारात्मक पक्ष बनावट की पुनरावृत्ति है, लेकिन आप एक साथ फिट होने वाले विचरण बनावट का उपयोग करके इसे संशोधित कर सकते हैं। अंगूठे के एक नियम के रूप में, आप जहां चाहें वहां इंस्टेंसिंग का उपयोग करना चाहते हैं।
ज्योमेट्री को आउटपुट करते समय शेडर में वर्टेक्स और पिक्सेल प्रोसेसिंग से बचें। एक voxel इंजन में आप अनिवार्य रूप से कई त्रिकोण होंगे, इसलिए एक साधारण पिक्सेल shader आपके रेंडर समय को बहुत कम कर सकता है। बफर को रेंडर करने के लिए इसका बेहतर है, फिर आप एक पोस्ट-प्रोसेस के रूप में पिक्सेल shader करते हैं। यदि आप ऐसा नहीं कर सकते हैं, तो अपने वर्टिकल शेडर में गणना करने का प्रयास करें। अन्य गणनाओं को जहां संभव हो, शीर्ष डेटा में बेक किया जाना चाहिए। अतिरिक्त पास बहुत महंगे हो जाते हैं यदि आपको सभी ज्यामिति (जैसे छाया मानचित्रण या पर्यावरण मानचित्रण) को फिर से प्रस्तुत करना होगा। कभी-कभी समृद्ध विवरण के पक्ष में एक गतिशील दृश्य छोड़ना बेहतर होता है। यदि आपके खेल में परिवर्तनशील दृश्य हैं (यानी विनाशकारी इलाका) तो आप हमेशा दृश्य को फिर से जोड़ सकते हैं क्योंकि चीजें नष्ट हो जाती हैं। पुनर्नवीनीकरण महंगा नहीं है और एक सेकंड के तहत लेना चाहिए।
अपने छोरों को उल्टा करें और सरणियों को सपाट रखें! ऐसा न करें:
for (i = 0; i < chunkLength; i++) {
for (j = 0; j < chunkLength; j++) {
for (k = 0; k < chunkLength; k++) {
MyData[i][j][k] = newVal;
}
}
}
//Instead, do this:
for (i = 0; i < chunkLengthCubed; i++) {
//figure out x, y, z index of chunk using modulus and div operators on i
//myData should have chunkLengthCubed number of indices, obviously
myData[i] = newVal;
}
EDIT: अधिक व्यापक परीक्षण के माध्यम से, मैंने पाया है कि यह गलत हो सकता है। उस मामले का उपयोग करें जो आपके परिदृश्य के लिए सबसे अच्छा काम करता है। आम तौर पर, सरणियां सपाट होनी चाहिए, लेकिन बहु-सूचकांक छोरों का उपयोग करना अक्सर मामले के आधार पर तेज हो सकता है