ब्राउज़र में open world बनाना, भाग 12: Rings, sky fog, और जो हम दोबारा करेंगे
लिखा है Oleg Sidorkin ने, Cinevva के CTO और को-फाउंडर
यहाँ नए हैं? सीरीज़ गाइड देखिए। यह बताती है कि spike क्या होता है और सारे भागों के लिंक देती है।
Spike 24 को बस "terrain में clipmap rings जोड़ना" होना था। यह एक पूरा finale बन गया जिसने rendering, shaders, module infrastructure, और visual integration सबको एक साथ छुआ।
Core terrain task vertex shader में concentric clipmap rings generate करना था। हर ring एक flat grid mesh है जो camera पर centered है, और जिसके vertices heightmap samples से displace होते हैं। Inner ring full resolution इस्तेमाल करता है। हर अगला ring vertex spacing को दोगुना करता है और बड़े area को cover करता है। मुश्किल हिस्सा rings के बीच की boundary है: जहाँ एक high-resolution ring एक low-resolution ring से मिलता है, वहाँ finer mesh के edge vertices को coarser mesh के edge के midpoint पर snap करना पड़ता है। हमने 2:1 edge morphing किया, जिसमें boundary vertices (वे जिनका grid coordinate ring edge के साथ odd होता है) को detect करके उनकी height को उनके दो even neighbors के midpoint पर snap किया,
Spike 24 को नए tab में खोलें ↗ · Source देखें
फिर आया fog और sky integration। हम चाहते थे कि दूर का terrain असली sky color में fade हो, किसी flat constant में नहीं। इसका मतलब था कि fog shader को पता होना चाहिए कि हर fragment की direction में sky किस color का होगा। हमने एक equirectangular HDR skybox texture load किया और इसे fragment shader में camera से fragment तक की view direction से sample किया, जिसे TSL के equirectUV node के ज़रिये equirectangular UV coordinates में convert किया। Fog factor distance-based था, जो camera-space depth के लिए positionView.z.negate() इस्तेमाल करता था, और एक near और far distance के बीच smoothstep से blend होता था।
Module wiring किसी भी geometry से ज़्यादा परेशान करने वाला निकला। हमने Three.js 0.183.1 में upgrade किया, जिसने build outputs को restructure कर दिया। three/tsl import को three.tsl.js पर resolve होना था, और TSL internally three/webgpu को एक bare specifier के तौर पर import करता था। दोनों mappings को HTML import map में explicit होना पड़ा। इनमें से एक भी छूटने पर "does not provide an export" या "failed to resolve module specifier" जैसी cryptic errors आती थीं, जिनमें यह कोई संकेत नहीं होता था कि कौन सी mapping गलत है। एक बार दोनों import map में आ जाने पर, shader graph सही से load हो गया।
हमें एक skybox orientation issue भी था जहाँ texture उल्टा render हो रहा था। इसका fix था equirectangular texture पर flipY = true, जो loaded textures के लिए Three.js का default है पर हमारे शुरुआती code में false set था।
मूल fog implementation sky को एक लगभग-constant direction पर sample करता था, जिससे natural gradient की जगह एक पतली horizon-color वाली पट्टी बनती थी। इसका fix था per pixel असली camera-to-fragment world direction compute करना, positionWorld.sub(cameraPosition).normalize() से, और उसे fog color lookup के लिए equirectUV में pass करना। इससे terrain fragments उस sky color में fade होने लगे जो असल में उनके पीछे है, और यह किसी भी camera angle से सही दिखता है।
इन सारे individual fixes के नीचे, core outcome बना रहा। अब हमारे पास एक terrain system है जो near-field volumetric editing (Transvoxel seams के साथ marching cubes), mid-field heightmap chunks, और far-field clipmap rings को मिलाता है, और इन सबको एक policy layer govern करती है जो mode, LOD, और transition behavior तय करती है।
अगर मुझे उन patterns के नाम बताने हों जो मैं अगले project में दोहराऊँगा, तो ये होंगे:
Feature work से पहले risk spikes से शुरू करें। Spike 1 ने "क्या हम इतनी तेज़ render भी कर सकते हैं" वाले सवाल को content pipelines में invest करने से पहले ही खत्म कर दिया।
Integration jumps से पहले known-good baselines को freeze करें। Spikes 13 और 14 ने regressions bisect करने के हमारे कई दिन बचाए।
Optimization marathons से पहले policy और observability को force करें। Spike 23 ने mystery bugs को trigger rules वाली named conditions में बदल दिया।
Screenshots नहीं, motion के तहत test करें। Clipmap pops, seam flicker, और streaming hitches सब still frames में छुप जाते हैं।
Average FPS नहीं, हर feature की frame-time cost measure करें। Averages उन spikes को छुपा देते हैं जो users असल में महसूस करते हैं।
और बेतरतीब हिस्से publish करें। गलत मोड़, stale-buffer ghost hunts, वे दो दिन जब draw range गलत था और हम transition logic को दोष दे रहे थे। यही वे हिस्से हैं जिनसे लोग असल में सीख सकते हैं।
External reality check: Vuntra City devlogs
यह सीरीज़ खत्म करने के बाद, हमने अपनी open-world assumptions के against एक external implementation check के तौर पर @VuntraCity devlogs को review किया। यह एक native UE5 project है, browser stack नहीं, पर system patterns इतने अच्छे से map हो जाते हैं कि comparison काम का रहता है।
पहला signal यह है कि traversal speed को सिर्फ़ gameplay नहीं, बल्कि एक streaming control के तौर पर लेना होगा। Vuntra City में high-speed transit को जानबूझकर ज़्यादातर interiors के ऊपर से route किया जाता है, और detail range movement speed के साथ scale होता है ताकि spawn churn और stalls से बचा जा सके (transport system, performance techniques)। यह हमारी policy-layer direction से मेल खाता है: movement mode को chunk radius, interior activation, और per frame allowed work पर सीधे असर डालना चाहिए।
दूसरा signal architecture है। उनके maps और address system को world topology को rendered objects से अलग करना पड़ा ताकि unloaded regions के लिए भी global queries चल सकें (maps and addresses)। यह वही separation है जो हमें browser में world search, quest routing, moderation scans, और POI indexing के लिए चाहिए, render-bound data paths को force किए बिना।
तीसरा signal simulation tiering है। उनका million-NPC design coarse schedule state को सस्ता और global रखता है, और महँगा behavior budget सिर्फ़ player के पास खर्च करता है (million-NPC overview, system deep dive)। यह हमारे अपने AOI-first simulation model को मज़बूत करता है, जहाँ near-field fidelity और far-field determinism अलग-अलग concerns हैं जिनके budgets भी अलग हैं।
और चौथा signal design quality है, raw scale नहीं। उनके सबसे मज़बूत exploration moments constant UI overlays की जगह weighted distributions, rare outliers, और diegetic navigation clues से आते हैं (procedural environment notes, no minimap loop)। हमारे लिए यह एक याद दिलाने वाली बात है कि technical systems को सिर्फ़ maximal throughput के लिए नहीं, बल्कि discoverable variation पैदा करने के लिए tune करना चाहिए।
इस चैप्टर में referenced technology
Clipmap ring geometry. हर ring एक flat grid mesh है जो camera पर centered है और जिसके vertices heightmap samples से displace होते हैं। Inner ring full resolution इस्तेमाल करता है। हर अगला ring vertex spacing दोगुना करता है और बड़े area को cover करता है। मुश्किल हिस्सा boundary है: जहाँ एक high-res ring एक low-res ring से मिलता है, वहाँ finer mesh के edge vertices coarser mesh के edge के midpoint पर snap होते हैं। यह technique Losasso और Hoppe के SIGGRAPH 2004 paper (PDF) से आती है और GPU Gems 2, Chapter 2 में detail में बताई गई है। हमारी geometry clipmaps वाली landscape guide देखें।
2:1 edge morphing. दो clipmap rings के बीच की boundary पर, finer ring के पास उन positions पर vertices होते हैं जो coarser ring share नहीं करता। जिन boundary vertices का grid coordinate ring edge के साथ odd होता है उन्हें detect किया जाता है और उनकी height दो पड़ोसी even vertices के बीच interpolate की जाती है। इससे dedicated transition geometry के बिना watertight seams बनते हैं। Interpolation vertex shader में चलता है: boundary vertices के लिए morphedHeight = mix(heightLeft, heightRight, 0.5), और इसमें वही geomorphing framework इस्तेमाल होता है जो हमारी guide में बताया गया है।
Equirectangular skybox mapping. एक single 2D image जो longitude-latitude projection से sky directions के पूरे sphere को map करती है। Horizontal axis 0-360 degrees cover करती है, vertical axis 0-180 degrees। एक normalized view direction
Three.js में, texture.mapping = EquirectangularReflectionMapping को SRGBColorSpace के साथ set करने से यह scene background के तौर पर enable हो जाता है। TSL में, equirectUV(direction) वही conversion apply करता है, और एक 3D view direction को texture sampling के लिए 2D UV coordinates में बदल देता है।
Per-fragment fog color from sky. Standard fog fragments को एक single constant color की तरफ़ blend करता है। एक detailed skybox वाले scene के लिए यह गलत दिखता है क्योंकि sky color direction के हिसाब से बदलता है। इसका fix है per pixel camera-to-fragment world direction compute करना (positionWorld.sub(cameraPosition).normalize()) और fog color के लिए skybox को उस direction पर sample करना। हर fragment उस sky color की तरफ़ fade होता है जो असल में उसके पीछे है, जिससे किसी भी camera angle से सही blending बनती है। Fog factor camera-space depth के लिए positionView.z.negate() के साथ smoothstep(nearDist, farDist, viewDepth) इस्तेमाल करता है।
Import maps for ES modules. एक browser-native mechanism (<script type="importmap">) जो bare module specifiers (जैसे three/tsl) को असली URLs पर map करता है। जब Three.js 0.183.1 ने अपने build outputs restructure किए, तो three/tsl को three.tsl.js पर resolve होना था और TSL internally three/webgpu को एक bare specifier के तौर पर import करता था। दोनों mappings को import map में explicit होना पड़ा, वरना browser "does not provide an export" या "failed to resolve module specifier" errors देता था।
आगे पढ़ें
इस सीरीज़ में इस्तेमाल हुई technologies की गहरी जानकारी के लिए, हमारी companion guides देखें:
- Landscape Generation with Dynamic LOD and Streaming for Browser Open Worlds heightmaps, SDFs, marching cubes, Transvoxel, geometry clipmaps, geomorphing, streaming architecture, terrain materials, और vegetation rendering को cover करती है।
- Browser 3D Open World Tech for Multiplayer Creator Worlds rendering stacks, WebGPU, physics, networking, multiplayer architecture, और Skyrim, The Witcher 3, Breath of the Wild, और GTA V से सीखे सबकों को cover करती है।
इस बारह-भाग वाली सवारी में हमारे साथ चलने के लिए शुक्रिया।
भाग 1: हमने इसे तोड़ने की कोशिश से शुरू किया
भाग 2: Worker physics और input lag का डर
भाग 3: वे unflashy spikes जिन्होंने हमें बचाया
भाग 4: Fancy terrain से पहले streaming
भाग 5: खूबसूरत चीज़ों का budget बनाना
भाग 6: Clipmaps ने कहानी बदल दी
भाग 7: Marching cubes और पहली असली caves
भाग 8: Baseline खोए बिना integration
भाग 9: Transvoxel एक scaffold से शुरू हुआ
भाग 10: Seam chaos और corner boss fight
भाग 11: Policy mode, hardcoded mode नहीं
भाग 12, कुल 14 में से।
पिछला: भाग 11 - Policy mode, hardcoded mode नहीं
अगला: भाग 13 - Terrain sculpting और math function की मौत
सीरीज़ गाइड: /hi/blog/2026-02-25-open-world-browser-series-guide