क्षितिज शून्य डॉन के रूप में शारीरिक रूप से सही बड़ा बादलों को लागू करना

पहले, गेम्स में बादलों को साधारण 2 डी स्प्राइट्स के साथ खींचा जाता था, जो हमेशा कैमरे की दिशा में घुमाया जाता है, लेकिन हाल के वर्षों में, वीडियो कार्ड के नए मॉडल आपको ध्यान देने योग्य प्रदर्शन के नुकसान के बिना शारीरिक रूप से सही बादल खींचने की अनुमति देते हैं। ऐसा माना जाता है कि इस खेल में बादलों ने स्टूडियो गुरिल्ला गेम्स को खेल क्षितिज जीरो डॉन के साथ लाया। बेशक, ऐसे बादल पहले रेंडर करने में सक्षम थे, लेकिन स्टूडियो ने स्रोत संसाधनों और उपयोग किए गए एल्गोरिदम के लिए एक उद्योग मानक जैसा कुछ बनाया, और अब वॉल्यूमेट्रिक बादलों का कोई भी कार्यान्वयन किसी भी तरह इस मानक का अनुपालन करता है।



बादलों को प्रस्तुत करने की पूरी प्रक्रिया को चरणों में बहुत अच्छी तरह से विभाजित किया गया है और यह ध्यान रखना महत्वपूर्ण है कि उनमें से किसी एक पर भी गलत तरीके से क्रियान्वयन से ऐसे परिणाम हो सकते हैं कि यह स्पष्ट नहीं होगा कि त्रुटि कहाँ है और इसे कैसे ठीक किया जाए, इसलिए हर बार परिणाम का एक नियंत्रण निष्कर्ष बनाना उचित है।

टोन मैपिंग, sRGB


प्रकाश व्यवस्था के साथ काम शुरू करने से पहले, दो काम करना ज़रूरी है:

  1. स्क्रीन पर अंतिम छवि प्रदर्शित करने से पहले, कम से कम सबसे सरल टोन मैपिंग लागू करें:

    tunedColor=color/(1+color) 

    यह आवश्यक है क्योंकि गणना किए गए रंग मूल्य एकता की तुलना में बहुत अधिक होंगे।
  2. सुनिश्चित करें कि आप जिस अंतिम फ्रेमबफ़र में ड्राइंग कर रहे हैं और स्क्रीन पर प्रदर्शित है वह sRGB प्रारूप में है। यदि sRGB मोड की सक्रियता एक समस्या है, तो रूपांतरण को shader में मैन्युअल रूप से किया जा सकता है:

     finalColor=pow(color, vec3(1.0/2.2)) 

    सूत्र अधिकांश मामलों के लिए उपयुक्त है, लेकिन मॉनिटर के आधार पर 100% नहीं। यह महत्वपूर्ण है कि sRGB रूपांतरण हमेशा अंतिम रूप से किया जाता है।

प्रकाश मॉडल


विभिन्न घनत्वों के आंशिक रूप से पारदर्शी मामले से भरे स्थान पर विचार करें। जब प्रकाश की किरण इस तरह के पदार्थ से गुजरती है, तो यह चार प्रभावों के संपर्क में आती है: अवशोषण, बिखरना, बिखरना और आत्म-विकिरण। उत्तरार्द्ध एक पदार्थ में रासायनिक प्रक्रियाओं के मामले में होता है, और यहां प्रभावित नहीं होता है।

मान लीजिए कि हमारे पास प्रकाश की किरण है जो बिंदु A से बिंदु B तक गुजरती है:


तेज

एक पदार्थ के माध्यम से गुजरने वाला प्रकाश इस पदार्थ द्वारा अवशोषण से गुजरता है। प्रकाश का गैर-अवशोषित अंश सूत्र द्वारा पाया जा सकता है:


जहाँ - अवशोषण के बाद बिंदु पर शेष प्रकाश - दूरी पर खंड AB पर इंगित करें ए से।

अपव्यय

पदार्थ के कणों के प्रभाव में प्रकाश का हिस्सा इसकी दिशा बदल देता है। प्रकाश का अंश जिसने अपनी दिशा नहीं बदली है वह सूत्र द्वारा पाया जा सकता है:


जहाँ - प्रकाश का अंश जो एक बिंदु पर बिखरने के बाद दिशा नहीं बदला है

अवशोषण और फैलाव संयुक्त होना चाहिए:


समारोह क्षीणन या विलोपन कहा जाता है। एक समारोह - स्थानांतरण समारोह। यह दर्शाता है कि बिंदु A से बिंदु B तक गुजरने पर प्रकाश कितना रहता है।

जैसा संबंध है और : , जहां C एक निश्चित स्थिरांक है, जो RGB में प्रत्येक चैनल के लिए एक अलग मान हो सकता है, बिंदु पर माध्यम का घनत्व है

अब कार्य को जटिल करते हैं। बिंदु A से बिंदु B तक प्रकाश चलता है, यह आंदोलन के दौरान मर जाता है। बिंदु X पर, प्रकाश का हिस्सा अलग-अलग दिशाओं में बिखरा हुआ है, दिशाओं में से एक बिंदु O पर पर्यवेक्षक से मेल खाता है। अगला, बिखरे हुए प्रकाश का एक भाग बिंदु X से बिंदु O तक जाता है और फिर से खराब हो जाता है। हमारे लिए एक्सो लाइट ऑफ इंटरेस्ट का रास्ता।


A से X तक जाने पर प्रकाश की हानि हम जानते हैं: , जैसा कि हम जानते हैं कि एक्स से ओ तक प्रकाश की हानि - यह । लेकिन प्रकाश के उस अंश के बारे में क्या जो प्रेक्षक की दिशा में बिखरा होगा?

प्रवर्धन फैलाव

यदि साधारण प्रकीर्णन के मामले में, प्रकाश की तीव्रता कम हो जाती है, तो बिखरने के प्रवर्धन के मामले में, यह पड़ोसी क्षेत्रों में होने वाले प्रकाश के बिखरने के कारण बढ़ जाता है। पड़ोसी क्षेत्रों से आने वाले प्रकाश की कुल मात्रा सूत्र द्वारा पाई जा सकती है:


जहाँ क्षेत्र पर अभिन्न लेने का मतलब है, - चरण समारोह - दिशा से आने वाली रोशनी

सभी दिशाओं से प्रकाश की गणना करना काफी मुश्किल है, हालांकि, हम जानते हैं कि प्रकाश का मूल भाग हमारे मूल एबी बीम द्वारा किया जाता है। सूत्र को बहुत सरल बनाया जा सकता है:


जहाँ - प्रकाश किरण और प्रेक्षक किरण के बीच का कोण (यानी, कोण AXO), - प्रकाश की तीव्रता का प्रारंभिक मूल्य। उपरोक्त सभी को संक्षेप में, हम सूत्र प्राप्त करते हैं:


जहाँ - आने वाली रोशनी - प्रकाश पर्यवेक्षक तक पहुंच रहा है।

हम कार्य को थोड़ा और जटिल करते हैं। चलो कहते हैं कि प्रकाश एक दिशात्मक प्रकाश द्वारा उत्सर्जित होता है, अर्थात्। सूरज:


सब कुछ पिछले मामले की तरह ही होता है, लेकिन कई बार। बिंदु A1 से प्रकाश बिंदु X पर बिखरा हुआ है, बिंदु O पर प्रेक्षक की ओर, बिंदु A2 से प्रकाश बिंदु X2 पर पर्यवेक्षक की ओर बिंदु X2 पर बिखरा हुआ है, आदि। हम देखते हैं कि पर्यवेक्षक तक पहुंचने वाला प्रकाश योग के बराबर है:


या अधिक सटीक अभिन्न अभिव्यक्ति:


यह समझना महत्वपूर्ण है कि यहाँ , यानी। खंड को अनंत लंबाई के वर्गों में विभाजित किया गया है।

आकाश


एक मामूली सरलीकरण के साथ, वातावरण से गुजरने वाला एक धूप का चश्मा केवल बिखराव से गुजरता है, अर्थात।


और एक प्रकार का प्रकीर्णन भी नहीं, लेकिन दो: रेले स्कैटरिंग और एमआई स्कैटरिंग। पहला हवा के अणुओं के कारण होता है, और दूसरा पानी के एक एरोसोल के कारण होता है।

हवा की कुल घनत्व (या एरोसोल) जिसके माध्यम से प्रकाश की किरण गुजरती है, बिंदु A से बिंदु B तक जाती है:
जहाँ - स्केलिंग ऊँचाई, h - वर्तमान ऊँचाई।

एक सरल अभिन्न समाधान होगा:

जहाँ dh वह चरण आकार है जिसके साथ ऊँचाई का नमूना लिया जाता है।

अब "प्रकाश मॉडल" के पिछले भाग में प्राप्त आकृति और आकृति का उपयोग करें:


प्रेक्षक ओ से ओ तक दिखता है। हम उन सभी प्रकाश को इकट्ठा करना चाहते हैं जो अंक X1, X2, ..., Xn तक पहुँचते हैं, उनमें बिखरे हुए हैं, और फिर पर्यवेक्षक तक पहुँचते हैं:


जहाँ सूर्य द्वारा उत्सर्जित प्रकाश की तीव्रता, - बिंदु पर ऊंचाई ; आकाश के मामले में, निरंतर सी, जो फ़ंक्शन में है के रूप में चिह्नित

अभिन्न का समाधान इस प्रकार हो सकता है:

यह सूत्र रेले स्कैटरिंग और माइ स्कैटरिंग दोनों के लिए मान्य है। नतीजतन, प्रत्येक बिखरने के लिए प्रकाश मान बस जोड़ते हैं:


रेले फैलाव



(प्रत्येक RGB चैनल के लिए मान शामिल हैं)



परिणाम:


मि बिखेरना



(सभी RGB चैनलों के लिए मान समान हैं)



परिणाम:


प्रति खंड नमूनों की संख्या और सेगमेंट पर आप 32 और ऊपर ले जा सकते हैं। पृथ्वी की त्रिज्या 6371000 मीटर है, वायुमंडल 100000 मीटर है।

यह सब क्या करना है:

  1. स्क्रीन के प्रत्येक पिक्सेल में, हम पर्यवेक्षक वी की दिशा की गणना करते हैं
  2. हम प्रेक्षक O की स्थिति को {0, 6371000, 0} के बराबर लेते हैं
  3. हम पाते हैं बिंदु O पर उत्पन्न होने वाली किरण के प्रतिच्छेदन के परिणामस्वरूप, और V की दिशा और बिंदु {0,0,0} पर केंद्रित है और 6471000 का त्रिज्या
  4. रेखा खंड बराबर लंबाई के 32 वर्गों में विभाजित करें
  5. प्रत्येक अनुभाग के लिए, हम रेले स्कैटरिंग और मि स्कैटरिंग की गणना करते हैं और सब कुछ जोड़ते हैं। इसके अलावा, गणना करने के लिए हमें खंड को विभाजित करने की भी आवश्यकता होगी प्रत्येक मामले में 32 बराबर भूखंड। एक चर के माध्यम से पढ़ा जा सकता है, जिसका मूल्य चक्र में प्रत्येक चरण पर बढ़ता है।

अंतिम परिणाम:


क्लाउड मॉडल


हमें 3 डी में कई प्रकार के शोर की आवश्यकता होगी। पहला है पेर्लिन का गुप्त भग्न ब्राउनियन गति (fBm) शोर:

2 डी स्लाइस के लिए परिणाम:


दूसरा है वोरोनोई का क्लॉजिंग एफबीएम शोर।

2 डी स्लाइस के लिए परिणाम:


वोरले के क्लोकिंग एफबीएम शोर को प्राप्त करने के लिए, आपको वोरोनोज के क्लोकिंग एफबीएम शोर को उलटने की आवश्यकता है। हालाँकि, मैंने अपने विवेक से मूल्यों की सीमाओं को थोड़ा बदल दिया:

 float fbmTiledWorley3(...) { return clamp((1.0-fbmTiledVoronoi3(...))*1.5-0.25, 0.0, 1.0); } 

परिणाम तुरंत बादल संरचनाओं जैसा दिखता है:


बादलों के लिए, आपको दो विशेष बनावट प्राप्त करने की आवश्यकता है। पहले का आकार 128x128x128 है और कम-आवृत्ति शोर के लिए जिम्मेदार है, दूसरे का आकार 32x32x32 है और उच्च आवृत्ति शोर के लिए जिम्मेदार है। प्रत्येक बनावट R8 प्रारूप में केवल एक चैनल का उपयोग करती है। कुछ उदाहरणों में, R8G8B8A8 के 4 चैनलों का उपयोग पहले बनावट के लिए और दूसरे के लिए R8G8B8 के तीन चैनलों के लिए किया जाता है, और फिर चैनलों को एक मिलावट में मिलाया जाता है। मैं इस बिंदु को नहीं देखता, क्योंकि मिश्रण को पहले से किया जा सकता है, जिससे कैश सुसंगतता में एक बड़ी हिट हो सकती है।

मिश्रण के लिए, और कुछ स्थानों पर, रीमैप () फ़ंक्शन का उपयोग किया जाएगा, जो मानों को एक सीमा से दूसरी सीमा तक ले जाता है:

 float remap(float value, float minValue, float maxValue, float newMinValue, float newMaxValue) { return newMinValue+(value-minValue)/(maxValue-minValue)*(newMaxValue-newMinValue); } 

चलो कम-आवृत्ति शोर के साथ बनावट तैयार करना शुरू करें:
आर-चैनल - पेरलिन का fBm शोर
जी-चैनल - tb fBm Vorley शोर
बी-चैनल - छोटे पैमाने के साथ छोटे एफबीएम वर्ली शोर
ए-चैनल - वर्ली के taylable fBm शोर के साथ और भी छोटे पैमाने पर


मिश्रण इस तरह से किया जाता है:

 finalValue=remap(noise.x, (noise.y * 0.625 + noise.z*0.25 + noise.w * 0.125)-1, 1, 0, 1) 

2 डी स्लाइस के लिए परिणाम:


अब उच्च आवृत्ति शोर के साथ बनावट तैयार करें:
R-channel - tiled fBm Vorley शोर
जी-चैनल - छोटा स्केल एफबीएम वॉर्ले शोर
बी-चैनल - वर्ले taylivaya fBm शोर और भी छोटे पैमाने पर


 finalValue=noise.x * 0.625 + noise.y*0.25 + noise.z * 0.125; 

2 डी स्लाइस के लिए परिणाम:


हमें एक 2 डी बनावट-मौसम का नक्शा भी चाहिए जो अंतरिक्ष के निर्देशांक के आधार पर बादलों की उपस्थिति, घनत्व और आकार का निर्धारण करेगा। इसे कलाकारों ने फाइन-ट्यून क्लाउड कवर से पेंट किया है। मौसम के नक्शे के रंग चैनलों की व्याख्या भिन्न हो सकती है, जिस संस्करण में मैंने उधार दिया था, वह इस प्रकार है:


आर-चैनल - कम ऊंचाई वाले बादल कवर
जी-चैनल - उच्च ऊंचाई वाले बादल कवर
बी-चैनल - अधिकतम बादल ऊंचाई
ए-चैनल - क्लाउड घनत्व

अब हम एक ऐसा समारोह तैयार करने के लिए तैयार हैं जो 3 डी अंतरिक्ष के निर्देशांक के आधार पर बादलों के घनत्व को लौटाएगा।

प्रवेश द्वार पर, किमी में निर्देशांक के साथ अंतरिक्ष में एक बिंदु

 vec3 position 

तुरंत हवा में ऑफसेट जोड़ें

 position.xz+=vec2(0.2f)*ufmParams.time; 

मौसम मानचित्र मान प्राप्त करें

 vec4 weather=textureLod(ufmWeatherMap, position.xz/4096.0f, 0); 
हमें ऊंचाई का प्रतिशत मिलता है (0 से 1 तक)

 float height=cloudGetHeight(position); 

नीचे बादलों की एक छोटी गोलाई जोड़ें:
 float SRb=clamp(remap(height, 0, 0.07, 0, 1), 0, 1); 
हम मौसम के मानचित्र के बी-चैनल के अनुसार बढ़ती ऊंचाई के साथ 0 में घनत्व में एक रैखिक कमी करते हैं:

 float SRt=clamp(remap(height, weather.b*0.2, weather.b, 1, 0), 0, 1); 
परिणाम को मिलाएं:

 float SA=SRb*SRt; 

नीचे बादलों की गोलाई जोड़ें:

 float DRb=height*clamp(remap(height, 0, 0.15, 0, 1), 0, 1); 

शीर्ष पर बादलों के गोलाई को भी जोड़ें:

 float DRt=height*clamp(remap(height, 0.9, 1, 1, 0), 0, 1); 
हम परिणाम को जोड़ते हैं, यहां हम मौसम के नक्शे से घनत्व के प्रभाव और घनत्व के प्रभाव को जोड़ते हैं, जो गुई के माध्यम से निर्धारित होता है:

 float DA=DRb*DRt*weather.a*2*ufmProperties.density; 

हमारे बनावट से कम आवृत्ति और उच्च आवृत्ति वाले शोर को मिलाएं:

 float SNsample=textureLod(ufmLowFreqNoiseTexture, position/48.0f, 0).x*0.85f+textureLod(ufmHighFreqNoiseTexture, position/4.8f, 0).x*0.15f; 

मेरे द्वारा पढ़े गए सभी दस्तावेजों में, विलय एक अलग तरीके से होता है, लेकिन मुझे यह विकल्प पसंद आया।

हम कवरेज की मात्रा (बादलों के कब्जे वाले आकाश का%) का निर्धारण करते हैं, जिसे गुई के माध्यम से सेट किया जाता है, मौसम मानचित्र के आर- और जी-चैनलों का भी उपयोग किया जाता है:

 float WMc=max(weather.r, clamp(ufmProperties.coverage-0.5, 0, 1)*weather.g*2); 

अंतिम घनत्व की गणना करें:

 float d=clamp(remap(SNsample*SA, 1-ufmProperties.coverage*WMc, 1, 0, 1), 0, 1)*DA; 

संपूर्ण कार्य:

 float cloudSampleDensity(vec3 position) { position.xz+=vec2(0.2f)*ufmParams.time; vec4 weather=textureLod(ufmWeatherMap, position.xz/4096.0f+vec2(0.2, 0.1), 0); float height=cloudGetHeight(position); float SRb=clamp(remap(height, 0, 0.07, 0, 1), 0, 1); float SRt=clamp(remap(height, weather.b*0.2, weather.b, 1, 0), 0, 1); float SA=SRb*SRt; float DRb=height*clamp(remap(height, 0, 0.15, 0, 1), 0, 1); float DRt=height*clamp(remap(height, 0.9, 1, 1, 0), 0, 1); float DA=DRb*DRt*weather.a*2*ufmProperties.density; float SNsample=textureLod(ufmLowFreqNoiseTexture, position/48.0f, 0).x*0.85f+textureLod(ufmHighFreqNoiseTexture, position/4.8f, 0).x*0.15f; float WMc=max(weather.r, clamp(ufmProperties.coverage-0.5, 0, 1)*weather.g*2); float d=clamp(remap(SNsample*SA, 1-ufmProperties.coverage*WMc, 1, 0, 1), 0, 1)*DA; return d; } 

वास्तव में यह फ़ंक्शन क्या होना चाहिए यह एक खुला प्रश्न है, क्योंकि मानकों की अनदेखी करते हुए कि मानकों को निर्धारित करते समय बादल मानते हैं, आप एक बहुत ही असामान्य और सुंदर परिणाम प्राप्त कर सकते हैं। यह सब आवेदन पर निर्भर करता है।


एकीकरण


पृथ्वी का वायुमंडल दो परतों में विभाजित है: आंतरिक और बाहरी, जिसके बीच में बादल स्थित हो सकते हैं। इन परतों को गोले द्वारा, बल्कि विमानों द्वारा भी दर्शाया जा सकता है। मैं आंचल पर बस गया। पहली परत के लिए, मैंने 6415 किमी के क्षेत्र को लिया, दूसरी परत के लिए, 6435 किमी की त्रिज्या। पृथ्वी की त्रिज्या 6400 किमी तक फैली हुई है। कुछ पैरामीटर वायुमंडल के "बादल" भाग (20 किमी) की सशर्त मोटाई पर निर्भर करेंगे।



आकाश के विपरीत, बादल अपारदर्शी होते हैं, और एकीकरण के लिए न केवल रंग प्राप्त करने की आवश्यकता होती है, बल्कि अल्फा चैनल के लिए मूल्य भी प्राप्त करना होता है। पहले आपको एक फ़ंक्शन की आवश्यकता होती है जो बादल के कुल घनत्व को लौटाता है जिसके माध्यम से सूर्य से प्रकाश की एक किरण गुजरती है।


कोई भी इस ओर ध्यान आकर्षित नहीं करता है, लेकिन अभ्यास से पता चला है कि बीम के पूरे मार्ग को ध्यान में रखना आवश्यक नहीं है, केवल सबसे चरम अंतराल की आवश्यकता है। हम मानते हैं कि एक खंडित खंड के ऊपर बादल बिल्कुल मौजूद नहीं हैं।


इसके अलावा, हम घनत्व नमूने की संख्या में बहुत सीमित हैं जो प्रदर्शन को मारे बिना किया जा सकता है। गुरिल्ला खेल 6. करते हैं। इसके अलावा, प्रस्तुतियों में से एक में, डेवलपर ने कहा कि वे इन नमूनों को शंकु के अंदर बिखेरते हैं, और अंतिम नमूना विशेष रूप से जितना संभव हो उतना बाकी जगह से बहुत दूर बनाया जाता है। परिणामस्वरूप अशुद्धि और शोर अभी भी पड़ोसी नमूनों की पृष्ठभूमि के खिलाफ सुचारू किया जाएगा, और यह, इसके विपरीत, बढ़ी हुई सटीकता में बदल जाएगा।


अंत में, मैं 4 नमूनों पर बस गया, जो एक ही पंक्ति में झूठ बोलते हैं, लेकिन बाद वाले को 6 गुना बढ़ाकर एक कदम के साथ लिया जाता है। चरण का आकार 20 किमी * 0.01 है, जो 200 मीटर है।

समारोह बहुत सरल है:

 float cloudSampleDirectDensity(vec3 position, vec3 sunDir) { //   float avrStep=(6435.0-6415.0)*0.01; float sumDensity=0.0; for(int i=0;i<4;i++) { float step=avrStep; //      6 if(i==3) step=step*6.0; //  position+=sunDir*step; //  ,  ,   //  float density=cloudSampleDensity(position)*step; sumDensity+=density; } return sumDensity; } 

अब आप अधिक कठिन भाग पर आगे बढ़ सकते हैं। हम बिंदु {0, 6400,0} पर पृथ्वी की सतह पर पर्यवेक्षक को निर्धारित करते हैं और अवलोकन बीम के चौराहे को त्रिज्या 6415 किमी और केंद्र {0,0,0} के गोले के साथ देखते हैं - हम शुरुआती बिंदु एस प्राप्त करते हैं।


नीचे फ़ंक्शन का मूल संस्करण है:

 vec4 mainMarching(vec3 viewDir, vec3 sunDir) { vec3 position; crossRaySphereOutFar(vec3(0.0, 6400.0, 0.0), viewDir, vec3(0.0), 6415.0, position); float avrStep=(6435.0-6415.0)/64.0; for(int i=0;i<128;i++) { position+=viewDir*step; if(length(position)>6435.0) break; } return vec4(0.0); } 

चरण का आकार 20 किमी / 64 के रूप में परिभाषित किया गया है। पर्यवेक्षक के बीम की सख्ती से ऊर्ध्वाधर दिशा के मामले में, हम 64 नमूने बनाएंगे। हालांकि, जब यह दिशा अधिक क्षैतिज होगी, तो नमूने थोड़े बड़े होंगे, इसलिए चक्र में 64 चरण नहीं हैं, लेकिन मार्जिन के साथ 128 हैं।

शुरुआत में, हम मानते हैं कि अंतिम रंग काला है, और पारदर्शिता एकता है। प्रत्येक चरण के साथ, हम रंग मूल्य बढ़ाएंगे और पारदर्शिता मूल्य कम करेंगे। यदि पारदर्शिता 0 के करीब है, तो आप लूप से बाहर निकल सकते हैं:

 vec3 color=vec3(0.0); float transmittance=1.0; … //    //      float density=cloudSampleDensity(position)*avrStep; //   ,   //   float sunDensity=cloudSampleDirectDensity(position, sunDir); //      float m2=exp(-ufmProperties.attenuation*sunDensity); float m3=ufmProperties.attenuation2*density; float light=ufmProperties.sunIntensity*m2*m3; //       color+=sunColor*light*transmittance; transmittance*=exp(-ufmProperties.attenuation*density); … return vec4(color, 1.0-transmittance); 

ufmProperties.attenuation - इसमें C के अलावा कुछ भी नहीं है और ufmProperties.attenuation2 C में है । ufmProperties.sunInt घनत्व - सूर्य की विकिरण तीव्रता। sunColor - सूरज का रंग।

परिणाम:


एक दोष तुरंत स्पष्ट है - गंभीर छायांकन। लेकिन अब हम सूरज के पास प्रवर्धित प्रकाश की कमी को ठीक करेंगे। यह इसलिए हुआ क्योंकि हमने एक फेज फंक्शन नहीं जोड़ा था। बादलों से गुजरने वाले प्रकाश के प्रकीर्णन की गणना करने के लिए, हेंगि-ग्रीनस्टीन के चरण-समारोह का उपयोग किया जाता है, जिसने इसे 1941 में अंतरिक्ष में गैस समूहों में समान गणना के लिए खोला था:


यहां एक विषयांतर बनाया जाना चाहिए। विहित प्रकाश मॉडल के अनुसार, चरण फ़ंक्शन एक होना चाहिए। हालांकि, वास्तव में, प्राप्त परिणाम किसी को भी सूट नहीं करता है और हर कोई दो चरण के कार्यों का उपयोग करता है, और यहां तक ​​कि उनके मूल्यों को एक विशेष तरीके से जोड़ता है। मैंने दो चरण के कार्यों पर भी ध्यान केंद्रित किया, लेकिन मैं केवल उनके मूल्यों को जोड़ता हूं। पहले चरण के कार्य में 1 के करीब जी है और आपको सूरज के पास उज्ज्वल प्रकाश व्यवस्था करने की अनुमति देता है। दूसरे चरण के समारोह में 0.5 के करीब जी है और आपको आकाशीय क्षेत्र में रोशनी में क्रमिक कमी करने की अनुमति देता है।

अपडेटेड कोड:

 // cos(theta) float mu=max(0, dot(viewDir, sunDir)); float m11=ufmProperties.phaseInfluence*cloudPhaseFunction(mu, ufmProperties.eccentrisy); float m12=ufmProperties.phaseInfluence2*cloudPhaseFunction(mu, ufmProperties.eccentrisy2); float m2=exp(-ufmProperties.attenuation*sunDensity); float m3=ufmProperties.attenuation2*density; float light=ufmProperties.sunIntensity*(m11+m12)*m2*m3; 

ufmProperties.eccentrisy, ufmProperties.eccentrisy2 जी मान हैं

परिणाम:


अब आप बहुत अधिक छायांकन के साथ लड़ाई शुरू कर सकते हैं। यह मौजूद है क्योंकि हमने आसपास के बादलों और आकाश से प्रकाश को ध्यान में नहीं रखा, जो वास्तविक जीवन में है।

मैंने इस समस्या को इस तरह हल किया:

 return vec4(color+ambientColor*ufmProperties.ambient, 1.0-transmittance); 

जहाँ एंबिएंटकोलर अवलोकन बीम की दिशा में आकाश का रंग है, ufmProperties.ambient ट्यूनिंग पैरामीटर है।

परिणाम:


यह अंतिम समस्या को हल करने के लिए बनी हुई है। वास्तविक जीवन में, जितना अधिक क्षैतिज दृश्य होता है, उतना ही हम एक निश्चित कोहरे या धुंध को देखते हैं जो हमें बहुत दूर की वस्तुओं को देखने की अनुमति नहीं देता है। यह भी कोड में परिलक्षित होना चाहिए। मैंने टकटकी कोण के सामान्य कोसाइन और घातीय फ़ंक्शन को लिया। इसके आधार पर, एक निश्चित सम्मिश्रण गुणांक की गणना की जाती है, जो परिणामस्वरूप रंग और पृष्ठभूमि रंग के बीच रैखिक प्रक्षेप की अनुमति देता है।

 float blending=1.0-exp(-max(0.0, dot(viewDir, vec3(0.0,1.0,0.0)))*ufmProperties.fog); blending=blending*blending*blending; return vec4(mix(ambientColor, color+ambientColor*ufmProperties.ambient, blending), 1.0-transmittance); 

ufmProperties.fog - मैनुअल कॉन्फ़िगरेशन के लिए।


सारांश समारोह:

 vec4 mainMarching(vec3 viewDir, vec3 sunDir, vec3 sunColor, vec3 ambientColor) { vec3 position; crossRaySphereOutFar(vec3(0.0, 6400.0, 0.0), viewDir, vec3(0.0), 6415.0, position); float avrStep=(6435.0-6415.0)/64.0; vec3 color=vec3(0.0); float transmittance=1.0; for(int i=0;i<128;i++) { float density=cloudSampleDensity(position)*avrStep; if(density>0.0) { float sunDensity=cloudSampleDirectDensity(position, sunDir); float mu=max(0.0, dot(viewDir, sunDir)); float m11=ufmProperties.phaseInfluence*cloudPhaseFunction(mu, ufmProperties.eccentrisy); float m12=ufmProperties.phaseInfluence2*cloudPhaseFunction(mu, ufmProperties.eccentrisy2); float m2=exp(-ufmProperties.attenuation*sunDensity); float m3=ufmProperties.attenuation2*density; float light=ufmProperties.sunIntensity*(m11+m12)*m2*m3; color+=sunColor*light*transmittance; transmittance*=exp(-ufmProperties.attenuation*density); } position+=viewDir*avrStep; if(transmittance<0.05 || length(position)>6435.0) break; } float blending=1.0-exp(-max(0.0, dot(viewDir, vec3(0.0,1.0,0.0)))*ufmProperties.fog); blending=blending*blending*blending; return vec4(mix(ambientColor, color+ambientColor*ufmProperties.ambient, blending), 1.0-transmittance); } 

डेमो वीडियो:


अनुकूलन और संभव सुधार


बुनियादी प्रतिपादन एल्गोरिथ्म को लागू करने के बाद, अगली समस्या यह है कि यह बहुत धीरे-धीरे काम करता है। मेरे संस्करण ने रैडॉन आरएक्स 480 पर पूर्ण एचडी में 25 एफपीएस का उत्पादन किया। समस्या को हल करने के लिए निम्नलिखित दो दृष्टिकोणों का सुझाव खुद गुरेरियन गेम्स ने दिया था।

हम आकर्षित करते हैं जो वास्तव में दिखाई देता है

स्क्रीन को 16x16 पिक्सेल आकार में विभाजित किया गया है। सबसे पहले, सामान्य 3D वातावरण तैयार किया जाता है। यह पता चला है कि अधिकांश आकाश पहाड़ों या बड़ी वस्तुओं द्वारा कवर किया गया है। तदनुसार, आपको केवल उन टाइलों में गणना करने की आवश्यकता है जिसमें बादल किसी भी चीज से अवरुद्ध नहीं होते हैं।

पुनः प्रक्षेपण

जब कैमरा स्थिर होता है, तो यह पता चलता है कि सामान्य रूप से बादलों को अपडेट नहीं किया जा सकता है। हालांकि, अगर कैमरा स्थानांतरित हो गया है, तो इसका मतलब यह नहीं है कि हमें पूरी स्क्रीन को अपडेट करने की आवश्यकता है। सब कुछ पहले से ही तैयार है, आपको बस नए निर्देशांक के अनुसार छवि को फिर से बनाने की आवश्यकता है। वर्तमान और पिछले फ्रेम के प्रक्षेपण और दृश्य मैट्रिसेस के माध्यम से नए लोगों पर पुराने निर्देशांक खोजना, प्रक्षेपण कहलाता है। इस प्रकार, एक कैमरा शिफ्ट के मामले में, हम बस नए निर्देशांक के अनुसार रंगों को स्थानांतरित करते हैं। ऐसे मामलों में जहां ये निर्देशांक ऑफ-स्क्रीन को इंगित करते हैं, बादलों को ईमानदारी से पुन: व्यवस्थित किया जाना चाहिए।

आंशिक अद्यतन

मैं पुनरुत्थान के विचार को पसंद नहीं करता क्योंकि कैमरा के एक तीव्र मोड़ के साथ यह पता चल सकता है कि बादलों को स्क्रीन के एक तिहाई के लिए प्रदान करना होगा, जो अंतराल का कारण बन सकता है। मुझे नहीं पता कि गुरिल्ला खेल इससे कैसे निपटते हैं, लेकिन कम से कम क्षितिज शून्य डॉन में, जॉयस्टिक को नियंत्रित करते समय, कैमरा आसानी से चलता है और तेज कूद के साथ कोई समस्या नहीं होती है। इसलिए, एक प्रयोग के रूप में, मैं अपने दृष्टिकोण के साथ आया। बादल एक क्यूबिक नक्शे में, 5 चेहरे में खींचे जाते हैं, क्योंकि नीचे हमें कोई दिलचस्पी नहीं है।क्यूबिक मैप के किनारे पर स्क्रीन की ऊंचाई के बराबर कम रिज़ॉल्यूशन होता है। क्यूबिक मैप के प्रत्येक चेहरे को 8x8 टाइलों में विभाजित किया गया है। प्रत्येक चेहरे पर प्रत्येक फ्रेम प्रत्येक टाइल में केवल 64 पिक्सेल के साथ अद्यतन किया जाता है। यह अचानक परिवर्तन के दौरान ध्यान देने योग्य कलाकृतियां देता है, लेकिन क्योंकि बादल काफी स्थिर हैं, फिर ऐसी चाल अदृश्य है। नतीजतन, रैडॉन आरएक्स 480 ज्वालामुखी के लिए पूर्ण एचडी में 500 एफपीएस और ऑपेंग के लिए 330 एफपीएस का उत्पादन करता है। Radeon HD 5700 श्रृंखला opengl के तहत पूर्ण एचडी में 109 एफपीएस का उत्पादन करती है (वल्कन समर्थन नहीं करता है)।

एमआईपी स्तरों का उपयोग करना

जब शोर के साथ बनावट तक पहुंच होती है, तो आप केवल पहले नमूनों में शून्य एमआईपी स्तर से डेटा ले सकते हैं, और फिर हम जितने नमूने बनाते हैं, उतना बड़ा एमआईपी स्तर लिया जा सकता है।

ऊँचे बादल

एकीकरण के दौरान गुरिल्ला खेलों में सिरस-ऊंचाई और cirrocumulus बादलों की उपस्थिति का अनुकरण करने के लिए, नवीनतम नमूने 3D बनावट से नहीं बनाए जाते हैं जिनके बारे में मैंने बात की थी, लेकिन एक विशेष 2D बनावट से।


कर्ल शोर कर्ल शोर में

कई अतिरिक्त बनावट हवा बहने वाले बादलों के प्रभाव को बनाने के लिए उपयोग की जाती हैं। मूल निर्देशांक को स्थानांतरित करने के लिए इन बनावटों की आवश्यकता होती है।


दिव्य किरणें


ऐसी किरणें, नाटक पर पकड़, पोस्टप्रोसेसिंग में महसूस की जाती हैं। सबसे पहले, सूरज के चारों ओर एक उज्ज्वल रोशनी खींची जाती है, जहां यह बादलों द्वारा अवरुद्ध नहीं होता है। फिर इस बैकलाइट को रेडियल रूप से सूर्य से दूर होना चाहिए।


अब आपको रेडियल स्मूदी लगाने की आवश्यकता है।


वास्तव में, बहुत अधिक सुधार और सूक्ष्मताएं हैं, लेकिन मैंने उन सभी की जांच नहीं की है, इसलिए मैं उनके बारे में विश्वास के साथ नहीं बता सकता। हालांकि, आप खुद को उनके साथ खुद को परिचित कर सकते हैं। मुझे लगता है कि सबसे मजबूत फ्रॉस्टबाइट इंजन से क्लाउड प्रलेखन है।

उपयोगी लिंक


Guerrilla Games
d1z4o56rleaq4j.cloudfront.net/downloads/assets/Nubis-Authoring-Realtime-Volumetric-Cloudscapes-with-the-Decima-Engine-Final.pdf?mtime=20170807141817
killzone.dl.playstation.net/killzone/horizonzerodawn/presentations/Siggraph15_Schneider_Real-Time_Volumetric_Cloudscapes_of_Horizon_Zero_Dawn.pdf
www.youtube.com/watch?v=-d8qT5-1LOI

GPU Pro 7
vk.com/doc179245989_437393482?hash=a9af5f665eda4edf58&dl=806d4dbdac0f7a761c


www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/simulating-sky/simulating-colors-of-the-sky

Frostbite
media.contentapi.ea.com/content/dam/eacom/frostbite/files/s2016-pbs-frostbite-sky-clouds-new.pdf
www.shadertoy.com/view/XlBSRz

Source: https://habr.com/ru/post/hi459948/


All Articles