Skip to content

Browser में open world बनाना, भाग 14: दुनिया में जान आ जाती है

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

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

भाग 13 के बाद हमारे पास sculpting थी। terrain उठाओ, caves काटो, cliffs को smooth करो, सब कुछ real time में, seams सलामत। लेकिन दुनिया एक tech demo जैसी दिखती थी। debug रंगों वाली flat shading, wireframe overlays, चंक्स को अलग बताने के लिए LOD रंगों वाली grey geometry। आप इसे edit कर सकते थे। आप इसे महसूस नहीं कर सकते थे।

तीन spikes ने यह बदल दिया। architecture नहीं। बिल्कुल वही pipeline, buffers, और seam stitching जो भाग 13 से थी। हमने बस वे layers जोड़ीं जो terrain को एक जगह जैसा महसूस कराती हैं: ऐसी surfaces जो आकार पर react करती हैं, उन surfaces पर उगता जीवन, और एक शरीर जो उन पर चलता है।

"तकनीकी रूप से काम कर रहा है" और "मैं यहां रहना चाहता हूं" के बीच का फर्क हैरानी की हद तक छोटा निकला।

एक cliff sculpt करो और उसे चट्टान में बदलते देखो

Spike 28 ने एक सीमित सवाल पूछा: क्या triplanar mapping वाला 4-layer material, compute से बने terrain पर frame budget को मारे बिना चल सकता है? जवाब हां था, पर दिलचस्प हिस्सा यह था कि उसके बाद क्या हुआ।

Spike 28 को नए tab में खोलें ↗ · source देखें

startup पर FBM noise से बनी चार procedural textures (grass, rock, sand, snow)। कोई बाहरी files नहीं, कोई asset pipeline नहीं, बस math और एक DataTexture। material के weights surface से ही आते हैं: slope और altitude। treeline के नीचे की flat जगहों को grass मिलती है। तीखी faces को rock मिलती है। नीची ज़मीन को sand मिलती है। ऊंची चोटियों को snow मिलती है। हर raw weight slope और altitude पर एक smoothstep से आता है, फिर उन्हें per-fragment normalize किया जाता है ताकि iwi=1 हो और अंतिम रंग बस weighted blend iwici हो। normalizing ही वह चीज़ है जो surface को वहां dark या blown-out होने से रोकती है जहां biomes आपस में overlap करती हैं। triplanar mapping उन MC chunks के लिए UV projection संभालती है जहां triangles के पास कोई सार्थक UV coordinates नहीं होतीं।

वह पल जिसने इसे बेच दिया: एक तीखी cliff बनाने के लिए brush से terrain उठाओ, और rock texture उसी frame में नई face पर आ जाती है। उसे वापस flat करो, और grass surface पर फिर से कब्जा कर लेती है। material को brush के बारे में पता नहीं होता। वह बस world position और surface normal पढ़ता है, वही data जिससे geometry बनी थी। sculpting-से-visual feedback loop तुरंत और unscripted है।

Spike 8 ने डेढ़ महीने पहले WebGL पर एक static mesh के साथ terrain material की लागत का test किया था। Spike 28 साबित करता है कि यह नीचे चलते पूरे sculpting pipeline के साथ dynamic compute से बने terrain पर काम करता है। हमने इसके लिए budget रखा था, पर सब कुछ चलते हुए इसे 60fps पर टिकते देखना फिर भी राहत की बात थी।

80,000 grass clumps और एक cave की छत

Spike 29 वही जगह है जहां gamedev instincts ने कमान संभाल ली।

Spike 29 को नए tab में खोलें ↗ · source देखें

हम Breath of the Wild वाली grass चाहते थे। poly count नहीं, वह feel। ऐसी grass जो सही जगहों को ढके, हवा के साथ हिले, और आपका उसमें से दौड़ने का मन कर दे।

हर grass clump 60-degree के कोणों पर तीन एक-दूसरे को काटते quads होते हैं, झुकने के लिए चार vertical segments के साथ। यह एक cross आकार है जो हर angle से billboard tricks के बिना volumetric दिखता है। हर chunk पर एक InstancedMesh, पूरी दुनिया में 80,000 clumps, कुल मिलाकर लगभग 2.4 million grass vertices।

जो हिस्सा मायने रखता है वह placement है। CPU हर chunk पर एक jittered grid पर चलता है और वही slope/altitude logic evaluate करता है जो GPU material shader इस्तेमाल करता है। जहां material system कहता है "grass," वहां grass उगती है। जहां rock या sand हावी होती है, density शून्य पर गिर जाती है। falloff smooth होती है क्योंकि नीचे के smoothstep weights biome किनारों पर continuous gradients पैदा करते हैं। आपको कोई सीमा नज़र नहीं आती क्योंकि कोई है ही नहीं।

फिर हमने कुछ ऐसा किया जिसके काम करने पर मुझे यकीन नहीं था। SDF surfaces पर grass। scatter function SDF volume के हर column पर चलता है और साथ वाले voxels के बीच zero-crossings ढूंढता है। जहां SDF negative से positive में crossover करता है, वहां एक surface है। surface normal बस field का normalized gradient है, n^=ϕϕ, जो voxel differences से निकाला जाता है, और slope arccos(n^y^) है, vertical से कोण। अगर वह काफी कोमल है, तो grass रखो।

Spike 27 में SDF brush से एक cave काटो। Spike 29 में वापस आओ और cave की छत के ऊपर grass उग रही होती है। scatter code को पता नहीं होता कि cave क्या होती है। वह बस सही altitude पर सही slope वाली एक surface देखता है। यही वह तरह का emergent behavior है जो open-world systems को बनाने में संतोषजनक बनाता है।

हवा TSL vertex shader में एक sine wave है, जो blade के V coordinate से modulate होती है ताकि tips झूलें और जड़ें टिकी रहें। gentle, strong, और off के बीच घूमने के लिए V toggle करें। हवा सभी 80K clumps पर एक साथ शून्य CPU लागत के साथ चलती है क्योंकि यह पूरी तरह vertex shader में है।

Spike 7 ने 50K grass instances का test किया था और हम limit छूने को लेकर घबराए हुए थे। Spike 29 compute terrain, seam stitching, multi-material texturing, और brushes के ऊपर 80K चलाता है। कम InstancedMesh draws में batch करना अब भी per-blade vertex count घटाने से ज़्यादा मायने रखता है। Spike 7 का सबक टिका रहा।

एक चीज़ हमने टाल दी: जब आप grass के नीचे sculpt करते हैं तो वह update नहीं होती। instance matrices scatter के समय set होती हैं। एक पहाड़ी को घाटी में sculpt करो और grass हवा में लटकती रहती है जब तक आप दोबारा scatter करने के लिए G toggle न करें। एक spike के लिए काफी अच्छा। production को dirty-chunk re-scatter चाहिए होगा।

पहला कदम

Spike 30 वही है जिस पर मैं बार-बार लौटता रहता हूं।

Spike 30 को नए tab में खोलें ↗ · source देखें

कोई physics library नहीं। 120Hz fixed timestep पर custom capsule। production codebase एक web worker में Rapier इस्तेमाल करता है (Spike 2 ने साबित किया कि latency ठीक है), पर इस spike को collision queries को ही साबित करना था। क्या एक character heightmap terrain पर चल सकता है, sculpt किए SDF terrain पर कदम रख सकता है, और नीचे से न गिरे?

मूल हिस्सा terrainQuery(x, y, z) नाम का एक function है। यह जांचता है कि position किसी MC-locked chunk के अंदर पड़ती है या नहीं। अगर हां, तो यह CPU SDF mirror को trilinearly interpolate करता है और gradient को surface normal के रूप में लौटाता है। वरना, central-difference normal के साथ heightmap lookup। character को पता नहीं होता कि वह किस terrain system पर खड़ा है। वह बस ज़मीन मांगता है और एक जवाब पा जाता है।

SDF collision वह हिस्सा था जिसके मुश्किल होने की मुझे उम्मीद थी। capsule के चारों ओर सात probe points (नीचे, बीच, ऊपर, चार cardinal offsets)। position x पर एक probe तब penetrate कर रहा होता है जब ϕ(x)<r, penetration depth p=rϕ(x) के साथ, और push-out दिशा field gradient n^=ϕϕ होती है। capsule को surface में दोबारा घुसने से रोकने के लिए, velocity को उसके inward component को रद्द करने के लिए project किया जाता है:

v=v(vn^)n^

जो केवल surface के साथ slide करने वाला component छोड़ देता है। यह कोई physics engine नहीं है। यह geometry queries और साधारण response है। पर यह caves, overhangs, और sculpt किए tunnels को प्रति आकार शून्य special-case code के साथ संभालता है। आप एक ऐसी cave में चलते हैं जो आपने दस सेकंड पहले काटी थी और capsule छत के contour का बिल्कुल पीछा करता है।

movement model practical शुरू हुआ और मज़ेदार बन गया। walk, run, sprint, jump, 45 degrees से ऊपर slope sliding। फिर मैंने एक BotW paraglider जोड़ा और spike कुछ ऐसा बन गया जिसे मैं बंद नहीं करना चाहता था।

हवा में रहते हुए Space दबाएं। gravity -30 से -4 पर गिर जाती है। गिरने की रफ्तार -3 पर थम जाती है। scale interpolation के साथ capsule से एक delta-wing mesh खुलता है। बाएं bank करें और wing झुक जाता है। capsule का facing velocity vector की ओर अपने आप align हो जाता है ताकि आप हमेशा वहीं देख रहे हों जहां आप जा रहे हैं। गिरने के लिए Space छोड़ दें। ज़मीन पर पहुंचो और आप फिर से दौड़ रहे होते हैं।

camera पीछे हटकर third-person में चली जाती है (P toggle)। यह yaw smoothing के साथ player के पीछे चलती है। player से camera तक एक ray-march 20 steps पर terrain test करता है। एक cave में उड़ो और camera arm rock में से गुज़रने के बजाय smoothly छोटी हो जाती है। दूसरी तरफ निकलो और यह वापस बाहर फैल जाती है।

यह रहा वह पल जिसने spike को सार्थक बनाया: heightmap brush से एक ऊंची cliff sculpt करो। third-person camera पर switch करो। किनारे तक दौड़ो। कूदो। glider deploy करो। उसी terrain के ऊपर बाएं bank करो जो आपने अभी sculpt किया, Spike 29 में आपकी उगाई grass नीचे लहराते हुए, cliff face पर Spike 28 की rock texture, हर chunk सीमा पर टिके Transvoxel seams। दूसरी तरफ land करो। सब कुछ एक browser tab में एक साथ काम कर रहा।

अपने ही पैरों के नीचे sculpting

एक चीज़ को लेकर मुझे यकीन नहीं था: क्या होता है जब आप उस terrain को sculpt करते हैं जिस पर character खड़ा है? heightmap के बदलाव terrainQuery() के ज़रिए तुरंत propagate होते हैं क्योंकि यह सीधे CPU buffer पढ़ता है। SDF बदलाव CPU SDF mirror के ज़रिए propagate होते हैं। capsule अगले physics tick पर penetration हल कर लेता है, जो 120Hz पर 11208.3 ms के भीतर होता है।

यह बस काम करता है। player के नीचे ज़मीन उठाओ और वे उसके साथ उठते हैं। ज़मीन काट दो और वे गिरते हैं। कोई special handling नहीं। physics इतनी तेज़ चलती है कि single-frame terrain बदलाव कभी बड़ी penetrations नहीं पैदा करते। यह timestep के लिए 120Hz चुनने का एक सुखद इत्तेफाक था। हमने इसे smooth movement के लिए चुना था, और इसने terrain editing को मुफ्त में सुरक्षित बना दिया।

30 spikes बाद

हमने इस series की शुरुआत एक flat heightmap और 500 cubes से की थी। अब हमारे पास volumetric caves के साथ sculpt किया terrain है, Transvoxel seam stitching, surface के आकार पर react करती multi-material texturing, हवा में झूलते 80,000 grass clumps, और एक character जो इन सब पर चलता, sprint करता, कूदता, और glide करता है।

इसमें से कुछ भी अभी production में नहीं है। world/client/ codebase अब भी साधारण heightmap chunks के साथ WebGL चलाता है। इन 30 spikes का सब कुछ standalone HTML pages में रहता है। integration का काम अगला है: WebGPURenderer पर migrate करना, hybrid HM/MC policy को chunk manager में wire करना, brush और material systems को multiplayer से जोड़ना।

पर rendering system अब जोखिम नहीं है। खुले सवाल data flow के बारे में हैं: edit persistence, brush strokes का network sync, collaborative sculpting। वह चीज़ें जो players के बीच होती हैं, triangles के बीच नहीं।

अगर आपने इस series को भाग 1 से follow किया है, बिखरे हुए हिस्सों के साथ टिके रहने के लिए शुक्रिया। अगर आपने इसे अभी पाया है, भाग 1 पर वापस जाएं। गलत मोड़ ही वह जगह हैं जहां सबक रहते हैं।

इस अध्याय में जिन तकनीकों का ज़िक्र हुआ

TSL (Three Shading Language). WebGPU renderer के लिए Three.js का node-आधारित shader system। Materials, JavaScript में function composition का इस्तेमाल करते हुए nodes (positionWorld, normalWorld, smoothstep, triplanarTexture) से बनती हैं। shader graph runtime पर WGSL में compile होती है। TSL, WebGPU targets के लिए raw GLSL ShaderMaterial की जगह लेता है और standard Three.js material features (lights, shadows, fog) और custom per-fragment logic के बीच interop देता है।

Triplanar mapping. एक texture projection तकनीक जो एक texture को तीन बार (XY, XZ, YZ planes) sample करती है और surface normal दिशा के आधार पर blend करती है। यह मनमानी mesh geometry पर UV stretching खत्म कर देती है, जो marching cubes output के लिए ज़रूरी है जहां triangles के पास कोई सार्थक UV coordinates नहीं होतीं। TSL एक built-in node के रूप में triplanarTexture() देता है।

Cross-quad blades के साथ instanced vegetation. हर grass clump 60-degree कोणों पर 3 एक-दूसरे को काटते quads होते हैं, जो हर view दिशा से एक volumetric दिखावट बनाते हैं। प्रति quad 4 vertical segments wind animation के लिए smooth bending की अनुमति देते हैं। पूरा field हर chunk पर एक अकेले InstancedMesh के रूप में render होता है। capacity को 25% ज़्यादा allocate किया जाता है ताकि जब terrain edit हो तो नई instances GPU buffer को दोबारा allocate किए बिना reserved slots भर सकें। vegetation पर हमारी landscape generation guide देखें।

Capsule बनाम SDF collision. physics engine के बिना volumetric terrain के खिलाफ character collision। capsule को SDF के खिलाफ कई points पर probe किया जाता है। जहां field value capsule radius से कम होती है, gradient बाहरी normal देता है और अंतर penetration depth देता है। यह caves, overhangs, और tunnels को बिना किसी shape-specific code के संभालता है। SDF terrain collisions देखें।

Fixed-timestep character controller. Physics, frame rate की परवाह किए बिना 120Hz पर step करती है, real time जमा करते हुए और उसे fixed-size steps में खपाते हुए। एक max substep count धीमी frames पर spiral-of-death रोकता है। Ground snapping slope traversal के दौरान capsule को जुड़ा रखता है। fixed timestep भविष्य के multiplayer replay के लिए deterministic behavior सुनिश्चित करता है।


29 में से भाग 14। पिछला: भाग 13 - Terrain sculpting और math function की मौत अगला: भाग 15 - baseline को बदलो, फिर उसे sync करो Series guide: /hi/blog/2026-02-25-open-world-browser-series-guide