Skip to content

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

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

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

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

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

Spike 56 को नए tab में खोलें ↗ · View source

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

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

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

कोई पहाड़ी क्या छिपा रही है, उसे cull करने के चार तरीके

Spike 57 को नए tab में खोलें ↗ · View source

Spike 57 उसी procedural world पर चार culling रास्ते bench करता है जिसे production client stream करता है, vertically 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 से ऊपर उठती है, ताकि किसी ridge के पीछे का पेड़ cull हो जाए भले ही वह frustum के अंदर हो। T3 वही horizon test रखता है पर उसे एक max-height pyramid से accelerate करता है, और T4 पूरे distance-plus-frustum-plus-horizon test को एक TSL compute kernel में port करता है जो हर instance के लिए एक visibility scale लिखता है जिसे foliage material पढ़कर छिपे हुए vertices को collapse करता है। एक two-frame stay-visible counter उस single-frame झिलमिलाहट को smooth करता है जब camera हल्का हिलने पर कोई sample point किसी texel के ठीक अंदर या बाहर गिरता है, जिसे जानबूझकर छोटा रखा गया है क्योंकि इससे लंबा कुछ भी algorithmic bugs को ठीक करने के बजाय छिपा देता है।

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

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

इस अध्याय में संदर्भित technology

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

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

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


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