हाल ही में मैंने एक ऐसे खेल पर काम करना शुरू किया है जो एक प्रक्रियात्मक रूप से उत्पन्न सौर प्रणाली में होता है। कुछ सीखने की अवस्था के बाद (न तो स्काला, OpenGL 2 ES या लिबगडेक्स से पहले काम किया है), मेरे पास एक बुनियादी टेक डेमो है जहां आप एक एकल प्रक्रियात्मक ग्रह के चारों ओर घूमते हैं:
मैं जिस समस्या को लेकर चल रहा हूं वह बनावट निर्माण का प्रदर्शन है। मैं जो कर रहा हूं उसका एक त्वरित अवलोकन: एक ग्रह एक घन है जिसे एक गोले में विकृत किया गया है। प्रत्येक तरफ, चिंता (जैसे 256 x 256) बनावट को लागू किया जाता है, जिसे एक 8n xn बनावट में बांधा जाता है जिसे टुकड़ा shader को भेजा जाता है। अंतिम दो रिक्त स्थान का उपयोग नहीं किया जाता है, वे केवल यह सुनिश्चित करने के लिए वहां हैं कि चौड़ाई 2 की शक्ति है। बनावट वर्तमान में सीपीयू पर उत्पन्न होती है, सिंपलेक्स शोर एल्गोरिथ्म के अपडेट किए गए 2012 संस्करण का उपयोग करके कागज में 'सिम्प्लेक्स' से जुड़ा हुआ है। शोर ध्वस्त '। एल्गोरिथ्म का परीक्षण करने के लिए मैं जिस दृश्य का उपयोग कर रहा हूं, उसमें दो गोले हैं: ग्रह और पृष्ठभूमि। दोनों 3 डी सिंप्लेक्स शोर के छह सप्तक से युक्त एक ग्रेस्केल बनावट का उपयोग करते हैं, इसलिए उदाहरण के लिए अगर हम 128x128 चुनते हैं तो बनावट का आकार 128 x 128 x 6 x 2 x 6 = शोर फ़ंक्शन के बारे में 1.2 मिलियन कॉल हैं।
ग्रह के सबसे करीब आपको स्क्रीनशॉट में दिखाया गया है और गेम के लक्ष्य रिज़ॉल्यूशन 1280x720 है, इसका मतलब है कि मैं 512x512 बनावट का उपयोग करना पसंद करूंगा। इस तथ्य के साथ कि वास्तविक बनावट निश्चित रूप से मूल शोर की तुलना में अधिक जटिल होगी (सूर्य की रोशनी के आधार पर टुकड़े टुकड़े में मिश्रित और दिन और रात की बनावट होगी, और एक मुखौटा मुखौटा। मुझे महाद्वीपों के लिए शोर की आवश्यकता है, भूरा रंग भिन्नता। , बादल, शहर की रोशनी, आदि) और हम कुछ देख रहे हैं जैसे 512 x 512 x 6 x 3 x 15 = 70 मिलियन अकेले ग्रह के लिए शोर कॉल। अंतिम गेम में, ग्रहों के बीच यात्रा करते समय गतिविधियां होंगी, इसलिए 5 या 10 सेकंड की प्रतीक्षा, संभवतः 20, स्वीकार्य होगी क्योंकि मैं यात्रा करते समय पृष्ठभूमि में बनावट की गणना कर सकता हूं, हालांकि स्पष्ट रूप से बेहतर है।
हमारे परीक्षण दृश्य पर वापस जाना, मेरे पीसी पर प्रदर्शन बहुत भयानक नहीं है, हालांकि अभी भी बहुत धीमी गति से अंतिम परिणाम पर विचार 60 बार संभव हो रहा है
128x128 : 0.1s
256x256 : 0.4s
512x512 : 1.7s
यह मेरे द्वारा जावा में सभी प्रदर्शन-महत्वपूर्ण कोड स्थानांतरित करने के बाद है, क्योंकि स्काला में ऐसा करने की कोशिश बहुत खराब थी। हालाँकि, मेरे फ़ोन (सैमसंग गैलेक्सी S3) पर इसे चलाने से अधिक समस्या उत्पन्न होती है:
128x128 : 2s
256x256 : 7s
512x512 : 29s
पहले से ही बहुत लंबा है, और यह भी इस तथ्य में फैक्टरिंग नहीं है कि अंतिम संस्करण में सेकंड के बजाय मिनट होंगे। स्पष्ट रूप से कुछ किया जाना चाहिए। व्यक्तिगत रूप से, मुझे कुछ संभावित रास्ते दिखाई दे रहे हैं, हालांकि मैं उनमें से किसी के लिए विशेष रूप से उत्सुक नहीं हूं:
- बनावट को पहले से न समझें, लेकिन टुकड़े टुकड़े को सब कुछ गणना करने दें। शायद संभव नहीं है, क्योंकि एक बिंदु पर मेरे पास एक पिक्सेल shader के साथ पूर्णस्क्रीन क्वाड के रूप में पृष्ठभूमि थी और मुझे अपने फोन पर लगभग 1 एफपीएस मिला।
- एक बार बनावट को रेंडर करने के लिए GPU का उपयोग करें, इसे स्टोर करें और तब से संग्रहीत बनावट का उपयोग करें। अपसाइड: सीपीयू पर ऐसा करने से तेज हो सकता है क्योंकि जीपीयू फ्लोटिंग पॉइंट कैलकुलेशन में तेज होना चाहिए। डाउनसाइड: प्रभाव जो (आसानी से) सरल शोर के कार्यों के रूप में व्यक्त नहीं किए जा सकते हैं (जैसे गैस ग्रह भंवर, चंद्रमा क्रेटर, आदि) जीएलएएसएल में स्काला / जावा की तुलना में कोड करने के लिए बहुत अधिक कठिन हैं।
- बड़ी मात्रा में शोर बनावट की गणना करें और उन्हें एप्लिकेशन के साथ शिप करें। यदि संभव हो तो मैं इससे बचना चाहूंगा।
- संकल्प कम करो। मुझे एक 4x प्रदर्शन लाभ मिलता है, जो वास्तव में पर्याप्त नहीं है और मैं बहुत अधिक गुणवत्ता खो देता हूं।
- तेजी से शोर एल्गोरिथ्म का पता लगाएं। अगर किसी के पास सभी कान हैं, लेकिन सिम्प्लेक्स पहले से ही पर्लिन से तेज होना चाहिए।
- कम रिज़ॉल्यूशन टेक्सचर और कम शोर वाले ऑक्टेव के लिए एक पिक्सेल आर्ट स्टाइल अपनाएँ। जबकि मैंने मूल रूप से इस शैली में खेल की कल्पना की थी, मैं यथार्थवादी दृष्टिकोण को प्राथमिकता देता हूं।
- मैं कुछ गलत कर रहा हूं और प्रदर्शन पहले से ही एक या दो ऑर्डर बेहतर होना चाहिए। अगर ऐसा है, तो कृपया मुझे बताएं।
अगर किसी के पास इस समस्या के बारे में कोई सुझाव, टिप्स, वर्कअराउंड या अन्य टिप्पणियां हैं, तो मैं उन्हें सुनना पसंद करूंगा।
Layoric के जवाब में, यहां वह कोड है जिसका मैं उपयोग कर रहा हूं:
//The function that generates the simplex noise texture
public static Texture simplex(int size) {
byte[] data = new byte[size * size * columns * 4];
int offset = 0;
for (int y = 0; y < size; y++) {
for (int s = 0; s < columns; s++) {
for (int x = 0; x < size; x++) {
//Scale x and y to [-1,1] range
double tx = ((double)x / (size - 1)) * 2 - 1;
double ty = 1 - ((double)y / (size - 1)) * 2;
//Determine point on cube in worldspace
double cx = 0, cy = 0, cz = 0;
if (s == 0) { cx = 1; cy = tx; cz = ty; }
else if (s == 1) { cx = -tx; cy = 1; cz = ty; }
else if (s == 2) { cx = - 1; cy = -tx; cz = ty; }
else if (s == 3) { cx = tx; cy = - 1; cz = ty; }
else if (s == 4) { cx = -ty; cy = tx; cz = 1; }
else if (s == 5) { cx = ty; cy = tx; cz = - 1; }
//Determine point on sphere in worldspace
double sx = cx * Math.sqrt(1 - cy*cy/2 - cz*cz/2 + cy*cy*cz*cz/3);
double sy = cy * Math.sqrt(1 - cz*cz/2 - cx*cx/2 + cz*cz*cx*cx/3);
double sz = cz * Math.sqrt(1 - cx*cx/2 - cy*cy/2 + cx*cx*cy*cy/3);
//Generate 6 octaves of noise
float gray = (float)(SimplexNoise.fbm(6, sx, sy, sz, 8) / 2 + 0.5);
//Set components of the current pixel
data[offset ] = (byte)(gray * 255);
data[offset + 1] = (byte)(gray * 255);
data[offset + 2] = (byte)(gray * 255);
data[offset + 3] = (byte)(255);
//Move to the next pixel
offset += 4;
}
}
}
Pixmap pixmap = new Pixmap(columns * size, size, Pixmap.Format.RGBA8888);
pixmap.getPixels().put(data).position(0);
Texture texture = new Texture(pixmap, true);
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
return texture;
}
//SimplexNoise.fbm
//The noise function is the same one found in http://webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java
//the only modification being that I replaced the 32 in the last line with 16 in order to end up with
//noise in the range [-0.5, 0.5] instead of [-1,1]
public static double fbm(int octaves, double x, double y, double z, double frequency) {
double value = 0;
double f = frequency;
double amp = 1;
for (int i = 0; i < octaves; i++) {
value += noise(x*f, y*f, z*f) * amp;
f *= 2;
amp /= 2;
}
return value;
}