Skip to content

Browser में open world बनाना, भाग 28: क्षितिज तक घास, और वह जमीन जो खुद को छिपा लेती है

Oleg Sidorkin द्वारा, Cinevva के CTO और सह-संस्थापक

यहाँ नए हैं? series गाइड का इस्तेमाल करें। यह बताता है कि spike क्या होता है और सभी भागों को लिंक करता है।

भाग 27 ने द्वीप बनाया और उसे ऐसी जमीन दी जो टाइल की हुई नहीं लगती। यह भाग उन दो चीजों को कवर करता है जो terrain को खाली के बजाय बसा हुआ महसूस कराती हैं। Spike 56 घास है, वह सतही डिटेल जो एक textured ढलान को ऐसी जगह में बदल देती है जहाँ से आप चलकर गुजरना चाहेंगे, और समस्या यह है कि एक मैदान को बिखरी हुई धारियों के बजाय असल मैदान की तरह दिखाया जाए। Spike 57 ज्यादा draw करने का उल्टा है: यह उस चीज को draw न करने के बारे में है जिसे पहाड़ी पहले से ही छिपा रही है, और दिलचस्प नतीजा यह है कि इसकी जांच का तेज तरीका सही तरीका भी निकला।

घास जो मैदान की तरह दिखे, confetti की तरह नहीं

Spike 56 को नए टैब में खोलें ↗ · सोर्स देखें

मुख्य फैसला ज्यामितीय है। एक अकेली पतली नुकीली पत्ती ज्यादातर कैमरा कोणों से sub-pixel होती है, इसलिए आधे मिलियन सपाट पत्तियाँ एक मैदान के बजाय छितरे हुए हरे confetti की तरह दिखती हैं। इसका हल है एक cross-quad गुच्छा: तीन नुकीले quads जो local up अक्ष के चारों ओर साठ डिग्री अलग-अलग घुमाए गए हों, ताकि किसी भी देखने की दिशा से कम से कम एक quad कैमरे के लगभग लंबवत बैठे और हर instance असल स्क्रीन क्षेत्र में करीब तीन पत्ती-चौड़ाई जितनी जगह घेरे। यही अंतर है हरी धारियाँ देखने और घास देखने के बीच।

एक WebGPU compute kernel हर गुच्छे को init पर एक बार रखता है। यह instance index को पाँच असंबद्ध random streams में hash करता है, patch के भीतर एक XZ position चुनता है, उसी FBM से ground height sample करता है जिसका CPU ground mesh इस्तेमाल करता है (एक TSL port जिसमें sign-preserving mod है ताकि मान बिल्कुल मेल खाएँ और पत्तियाँ सतह से ऊपर तैरने के बजाय सतह पर बैठें), एक central-difference normal लेता है, और हर गुच्छे की width, height और hue तय करता है। render की तरफ एक TSL vertex graph है जो instance matrix को पूरी तरह bypass करके सीधे clip space में लिखता है: यह unit cross-quad को scale करता है, Rodrigues के सूत्र से local up को ground normal पर घुमाता है, और गुच्छे की position पर translate करता है। Distance LOD बिना किसी culling pass के मुफ्त में मिलता है, क्योंकि vertex graph गुच्छे की height को 1smoothstep(fadeNear,fadeFar,dist) से गुणा करता है, तो दूर के गुच्छे शून्य height पर सपाट हो जाते हैं और fill पर खर्च होना बंद कर देते हैं। Wind गुच्छे के world XZ और time पर sin और cos के दो octaves हैं, जो क्षैतिज रूप से लगाए जाते हैं और height fraction से gate होते हैं ताकि base टिका रहे जबकि सिरे हिलें, और एक छोटा कैमरा-aware झुकाव हर गुच्छे को दर्शक की ओर झुकाता है ताकि वे सपाट कार्ड की तरह दिखने के बजाय परिप्रेक्ष्य में खुल जाएँ।

रंग का नुस्खा NedMakesGames के Breath of the Wild URP shader से लिया गया है: flat shading जिसमें पत्ती का रंग height fraction के साथ एक root tone से एक tip tone तक lerp होता है, जहाँ root और tip tone खुद हर गुच्छे के hue मान से दो palettes के बीच lerp होते हैं। इससे वही चितकबरा दो-रंग वाला रूप मिलता है जो असली BotW घास के मैदान में होता है। Diffuse सूरज के खिलाफ ground normal का इस्तेमाल एक ambient फ्लोर के साथ करता है, क्योंकि cross-quad के per-quad normals एक stylized रूप के लिए अलग-अलग shade करने में बहुत noisy हैं। पूरा मैदान एक draw है, जो पूरी तरह GPU पर रखा और animate किया गया है।

पहाड़ी जो छिपा रही है उसे cull करने के चार तरीके

Spike 57 को नए टैब में खोलें ↗ · सोर्स देखें

Spike 57 उसी procedural world पर चार culling रास्तों को बेंच करता है जिसे production client stream करता है, जिसे लंबवत रूप से 1.8x scale किया गया है ताकि पहाड़ियाँ सचमुच foliage को occlude करें। T0 केवल distance-आधारित है, जो वही करता है जो shipping chunk visibility पहले से करती है। T1 frustum culling जोड़ता है, सबसे बड़ी सस्ती जीत, जो view cone के बाहर की हर चीज को छोड़ देता है। T2 एक terrain-aware horizon test जोड़ता है: आँख से हर instance तक एक ray march करो और उसे reject कर दो अगर heightmap रास्ते में कहीं भी ray से ऊपर उठती है, तो किसी रिज के पीछे का पेड़ frustum के भीतर होने के बावजूद cull हो जाता है। T3 वही horizon test रखता है लेकिन उसे एक max-height pyramid से तेज करता है, और T4 पूरे distance-plus-frustum-plus-horizon test को एक TSL compute kernel में port करता है जो एक per-instance visibility scale लिखता है जिसे foliage material पढ़कर छिपे हुए vertices को collapse करता है। एक two-frame stay-visible counter उस single-frame झिलमिलाहट को smooth करता है जब कोई sample point किसी texel के बस अंदर या बाहर गिरता है जैसे कैमरा थोड़ा हिलता है, जिसे जानबूझकर छोटा रखा गया है क्योंकि इससे लंबा कुछ भी algorithmic bugs को ठीक करने के बजाय छिपा देता है।

Pyramid वहाँ है जहाँ spike अपनी कीमत वसूल करता है, और सबक यह है कि test को उससे मिलाना जो वास्तव में स्क्रीन पर है। Rendered terrain एक triangle mesh है जिसके vertices एक तय दो-मीटर grid पर हैं, और vertices के बीच rasterizer रैखिक रूप से interpolate करता है, तो किसी भी आयत के भीतर असली rendered height उसके भीतर के vertices का max है, उनके बीच का सतत noise peak कभी नहीं। अगर occlusion pyramid noise को किसी महीन grid पर sample करता है, तो उसे ऐसे phantom peaks मिलते हैं जो mesh कभी नहीं दिखाती और वह ऐसी rays को block करने लगता है जिनके पार से कैमरा साफ देख सकता है। तो pyramid के base texels बिल्कुल vertex grid पर बैठते हैं, हर एक अपने चार कोने vertices का max संग्रहीत करता है, और ऊपरी levels पाठ्यपुस्तक वाली 2x2 max reductions हैं, जो इसे rendered mesh के संबंध में सटीक बना देती है। हर step के ray test के लिए कोड जानबूझकर pyramid को query करने के बजाय mesh को bilinear interpolation के साथ point-sample करता है, क्योंकि एक sub-texel AABB query पूरे texel का max लौटाती है और खड़ी रिज पर height को कई मीटर बढ़ा देती है, जो ठीक वही over-occlusion है जो दिखने वाले props को छिपा देगी।

चौंकाने वाला नतीजा यह है कि T2, brute-force reference, वही है जो गलत है। चूँकि T2 सतत noise को सीधे point-sample करता है, यह mesh vertices के बीच के उन peaks को पकड़ लेता है जो render नहीं होते, तो यह थोड़ा over-occlude करता है और उस foliage को छिपा देता है जिसे खिलाड़ी असल में देख सकता है। Pyramid रास्ता दोनों है, ज्यादा तेज, chunk-level tests के लिए O(logN) AABB reduction की वजह से, और ज्यादा ज्यामितीय रूप से सही, क्योंकि यह केवल वही heights लौटा सकता है जो mesh सचमुच दिखाती है। यही spike का पूरा मतलब है: accelerated structure कोई गुणवत्ता समझौता नहीं है जो आप गति के लिए स्वीकार करते हैं, यह वह संस्करण है जो वास्तविकता से मेल खाता है। Chunk culling हर chunk पर पाँच-बिंदु test चलाता है (chunk की max height पर चार ऊपरी कोने और केंद्र) और हर chunk के अपने footprint को occluder set से बाहर रखता है ताकि कोई chunk खुद को कभी occlude न करे। अनुशंसित production रास्ता T4 है: instance metadata और height field को storage buffers में push करो और उसी loop को एक compute shader में indirect draw के साथ चलाओ, क्योंकि renderer वैसे भी WebGPU की ओर बढ़ रहा है।

इस अध्याय में संदर्भित तकनीक

Cross-quad GPU घास। हर गुच्छे में साठ डिग्री अलग घुमाए गए तीन नुकीले quads किसी भी कोण से एक लगभग-लंबवत quad की गारंटी देते हैं, तो आधे मिलियन instances sub-pixel confetti के बजाय एक मैदान की तरह दिखते हैं। एक compute kernel हर गुच्छे को रखता है (FBM ground height उसी sign-preserving mod से sample की जाती है जो CPU mesh इस्तेमाल करता है, central-difference normal, per-clump size और hue), और एक TSL vertex graph instance matrix को bypass करके scale करता है, Rodrigues-rotate से normal पर घुमाता है, translate करता है, और height-gated wind लगाता है। Distance LOD मुफ्त है: गुच्छे 1smoothstep(fadeNear,fadeFar,dist) से शून्य height पर सिमट जाते हैं। रंग NedMakesGames के BotW नुस्खे का अनुसरण करता है, दो hue-मिश्रित palettes पर root-to-tip gradient। देखें GPU-driven LOD

Terrain horizon occlusion culling। Production world पर चार बेंच किए गए रास्ते: distance, साथ में frustum, साथ में एक heightmap ray test जो रिज के पीछे के instances को reject करता है, साथ में एक max-height pyramid जो उसे तेज करता है, साथ में एक TSL compute port। Pyramid उसी सटीक दो-मीटर vertex grid पर sample करता है जिस पर mesh tessellate होती है (base texel = चार कोने vertices का max, ऊपर की ओर 2x2 max-reduce), तो यह केवल वही heights लौटाता है जो rasterizer असल में दिखाता है।

Accelerated और सही, कोई सौदा नहीं। सतत noise को point-sample करना (brute-force T2 रास्ता) mesh vertices के बीच ऐसे peaks ढूँढ लेता है जो कभी render नहीं होते, जिससे दिखने वाली foliage over-occlude हो जाती है। Vertex-grid pyramid दोनों है, ज्यादा तेज (O(logN) AABB reduction) और ज्यामितीय रूप से सटीक, तो हर step के ray tests pyramid के per-texel max को query करने के बजाय mesh को bilinear-sample करते हैं, जो खड़ी रिज पर heights को मीटरों बढ़ा देता। Chunk culling एक पाँच-बिंदु test का इस्तेमाल करता है और हर chunk के अपने footprint को बाहर रखता है ताकि वह खुद को कभी occlude न करे। Production रास्ता metadata और height field को storage buffers में push करता है ताकि indirect draw के साथ compute-shader cull हो सके।


29 में से भाग 28। पिछला: भाग 27 - noise से एक द्वीप, जमीन जो जमीन जैसी दिखे अगला: भाग 29 - एक controller, कोई भी body Series गाइड: /blog/2026-02-25-open-world-browser-series-guide