ब्राउज़र में open world बनाना, भाग 17: ऐसी animations जिन्हें retargeting की ज़रूरत नहीं पड़ी, और एक live asset search
लेखक Oleg Sidorkin, Cinevva के CTO और सह-संस्थापक
यहां नए हैं? सीरीज़ गाइड का इस्तेमाल करें। इसमें बताया गया है कि spike क्या होता है और सभी भागों के लिंक दिए गए हैं।
भाग 16 ने हमें एक ऐसा world दिया जिसमें आप objects रख सकते हैं। यह भाग player को उसमें करने के लिए कुछ बेहतर देता है: एक combat-grade animation set, और एक तरीका जिससे आप search टाइप करके हज़ारों CC0 models में से कोई भी world में खींच सकते हैं।
262 clips, एक skeleton, तीन retargeting fixes
Spike 35 को नए tab में खोलें ↗ · View source
Player rig एक CC4-style 3MIKE skeleton है जिसमें 213 bones हैं, जिनमें एक 80 से ज़्यादा bones वाला facial rig और पूरे finger joints शामिल हैं। Animation sources इससे मेल नहीं खाते। पहली कोशिश में हाथ से चुने हुए Mixamo combat clips को retarget किया गया था, लेकिन source pool पतला था और locomotion Mixamo और कुछ Kimodo BVH idles के बीच अजीब तरह से बंटा हुआ था। असली जीत तब मिली जब हमने source library को Quaternius के दो Universal Animation Library packs पर शिफ्ट किया, कुल 262 clips एक ही consistent UE5-style 65-bone mannequin पर: sword combos, bow archery, climbs, wallruns, dodges, hit reactions, emotes, और एक कहीं ज़्यादा साफ-सुथरा locomotion set।
यह एक 65-bone source से 213-bone target तक का मामला है, जिसमें न कोई shared bone names हैं, न T-pose orientations, न limb proportions। हर clip तीन steps में remap होता है: एक bone-name map, एक bind-pose alignment ताकि दोनों rigs एक reference orientation साझा करें, और position-track scaling ताकि छोटा source rig character को फर्श में घुटनों तक न धंसा दे। इसे सही करने का मतलब था bugs की एक श्रृंखला का पीछा करना, जिनमें से हर एक ने कुछ खास सिखाया।
Character ज़रूरत से ज़्यादा मुड़ा हुआ निकला, हर shoulder और elbow करीब 30° ज़्यादा घूमा हुआ। वजह थी एक pose mismatch: UAL source एक सच्चा T-pose देता है, CC4 का bind एक A-pose है, और retargeter ने मान लिया कि दोनों rigs मेल खाते reference poses में बैठे हैं, इसलिए A-to-T का फर्क हर per-frame delta में जुड़ गया। Fix CC4 के arm chain को एक सच्चे T-pose में मजबूर करता है ताकि bind को capture किया जा सके, फिर retarget करता है, ताकि deltas छोटे रहें।
फिर body translate होना बंद हो गई। Knockbacks ऊपरी body को पीछे झटक देते थे लेकिन पैर जमे रहते थे। UAL displacement को root bone पर रखता है, pelvis पर नहीं, इसलिए pelvis position पढ़ने से लगभग शून्य motion मिलती थी। Fix root.position और pelvis.position को जोड़ता है, horizontal हिस्से को scale करता है, और एक single hip-position track लिखता है। यह करते वक्त हमें पता चला कि vertical offsets करीब 12% गलत थे, क्योंकि हमने Y axis के लिए global limb-proportion ratio का इस्तेमाल किया था जबकि pelvis height को hip-to-floor ratio चाहिए। दो ratios, दो axes: Y के लिए RL_BoneRoot.position NaN warnings की एक झड़ी इस बात से जुड़ी थी कि UAL के root (जो
सबसे छोटा fix देखने में सबसे संतोषजनक था। Character तलवार को limp bind-pose उंगलियों से पकड़ता था क्योंकि bone map में कोई finger entries नहीं थीं, और retargeter सिर्फ mapped bones के लिए tracks लिखता है। 30 finger bones जोड़ने (पांच उंगलियां, तीन segments, दो हाथ, UAL के चौथे tip helper को छोड़कर जो deform नहीं करता) से हाथ grip पर बंद हुआ और release पर खुला।
262 clips को 262 clips देखे बिना साबित करना
आप 262-clip library को आंखों से verify नहीं कर सकते, इसलिए हमने एक offline trajectory parity test बनाया: एक headless Node script जो हर pack को load करता है, source skeleton को 60Hz पर sample करता है, retargeting pipeline चलाता है, और scaling के बाद per-bone world positions और rotations की तुलना source से करता है। Pelvis Y drift अधिकतम 0.003 m आई। हाथों ने एक स्थिर 2.5° offset दिखाया जो पहले "उंगलियां track नहीं हो रहीं" जैसा लगा, लेकिन एक स्थिर offset flat UAL hand और CC4 के थोड़े cupped bind के बीच का bind delta है, और यह पूरे clip में invariant रहता है। असली animation errors उस drift के रूप में दिखती हैं जो frame दर frame बदलती है। एक बार यह साफ हो गया, तो test एक one-shot regression check बन गया: अगर किसी clip का drift उस स्थिर baseline को पकड़े रखना बंद कर दे, तो किसी हालिया बदलाव ने retargeting तोड़ दी है।
एक side-by-side reference mannequin ने debugging के visual हिस्से को निर्णायक बना दिया। Backslash दबाने पर जो भी source rig मौजूदा clip का मालिक है वह player के बगल में दिखता है, इसलिए "मुड़ा हुआ shoulder" वाला सवाल बन जाता है "क्या मुड़ाव source में है, या retargeting ने जोड़ा?" Numerical tests regressions पकड़ते हैं, visual reference उन bind-pose गलतियों को पकड़ता है जो numbers सामने नहीं लातीं, और दोनों ने मिलकर अनुमान लगाने का खेल खत्म कर दिया।
Pipeline ठोस होने के बाद, WASD/jump/swim baseline को Mixamo से UAL पर बदलना एक पतला alias map था: FSM अब भी idle और walk जैसे generic state names बोलता है, जो playback के वक्त UAL clip names में resolve होते हैं। Swimming को per-clip rig offsets चाहिए थे क्योंकि freestyle और tread-water poses pelvis को अलग-अलग anatomical heights पर anchor करते हैं, इसलिए हम active swimming के लिए rig को आधा मीटर डुबाते हैं और treading के लिए और गहरा, उनके बीच एक smooth 5Hz पर blend करते हुए।
एक शब्द टाइप करें, एक model पाएं
Spike 36 को नए tab में खोलें ↗ · View source
Spike 34 ने एक CC0 pack हाथ से curate करने में एक दिन खा लिया। लंबे समय का जवाब एक search box है। Polyhaven एक permissive JSON API और एक deterministic CDN के पीछे करीब 1,100 CC0 models publish करता है, और यह spike पूरे path (query, thumbnails, load, render) को एक static page से बिना किसी build step के जोड़ता है, करीब 300 lines vanilla JS plus three.js में।
Boot पर यह पूरा catalog एक बार fetch करता है, करीब 600 KB। Search पूरी तरह client-side scoring है (name id को हराता है, id category को, category tag को) एक 120 ms debounce के साथ, top 60 cards render करता है। Thumbnails एक IntersectionObserver के ज़रिए lazy-load होते हैं ताकि typing एक साथ 60 requests न दागे। दिलचस्प हिस्सा loading है। Polyhaven का file endpoint multi-file glTF देता है, GLB नहीं, जिसमें textures resolutions के बीच साझा होते हैं और अलग files में बंटे होते हैं, और यह relative path से absolute CDN URL का एक include map लौटाता है। JSON को खुद download और patch करने के बजाय, हम उस map को LoadingManager.setURLModifier के ज़रिए feed करते हैं, जो loader को जिस भी dependency की ज़रूरत हो (वह .bin, हर texture) उसके लिए fire होता है और उसे CDN के ज़रिए resolve करता है। एक click, एक प्रतीत होने वाली file। API और CDN दोनों permissive CORS set करते हैं, जिसे किसी client code से पहले curl से verify किया गया, इसलिए कोई proxy नहीं। PBR materials RoomEnvironment और ACES tone mapping defaults के साथ बिना किसी per-asset fix-up के सही render होते हैं, और 1k textures एक typical model को 4k पर 20 से 40 MB के बजाय 2 से 5 MB पर रखते हैं।
एक meshoptimizer WASM pass इसे एक non-destructive reducer के साथ पूरा करता है: हर mesh अपनी original geometry का एक clone रख लेता है, और ratio बदलने पर उस clone से एक index buffer फिर बनाता है, बजाय इसके कि cumulatively simplify करे। Multi-material geometry per group simplify होती है और geometry.groups फिर बनाती है ताकि material slots न ढहें। एक armchair full पर 5,626 triangles से half पर 2,812 तक जाती है।
इस अध्याय में संदर्भित technology
Bind-pose alignment के साथ skeleton retargeting। एक skeleton से दूसरे पर animation map करने के लिए, जब bone names, proportions और reference poses अलग हों, तीन corrections चाहिए: एक bone-name map, एक bind-pose alignment ताकि दोनों rigs एक reference orientation साझा करें (target के A-pose arm chain को source के T-pose में मजबूर करते हुए), और position-track scaling। एक pose mismatch हर per-frame delta में A-to-T rotation जोड़ देता है, joint rotation को दोगुना करता है। Unmapped helper bones को हटाना ज़रूरी है, क्योंकि
एक rig के लिए दो scale ratios। Horizontal displacement global limb-proportion ratio (समग्र skeleton size) इस्तेमाल करता है, लेकिन vertical pelvis offset hip-to-floor ratio root bone पर रहता है, pelvis पर नहीं, इसलिए knockback, climb और locomotion clips में motion बनाए रखने के लिए दोनों को एक single hip-position track में जोड़ना ज़रूरी है।
Offline trajectory parity testing। एक headless script source skeleton को 60Hz पर sample करता है, retargeting pipeline चलाता है, और per-bone world transforms की तुलना source से करता है। एक स्थिर per-frame offset हानिरहित bind delta है, जबकि frame दर frame बदलने वाला drift एक असली error है, इसलिए test एक regression check बन जाता है जो तब fire होता है जब कोई बदलाव किसी track को गिरा या विकृत कर दे। Per-pack GLB isolation (नया load, अगले से पहले मुक्त) पूरे sweep में cache contention से बचाता है।
CDN glTF graphs के लिए LoadingManager.setURLModifier। जब कोई CDN glTF को एक relative-URI graph के रूप में एक साथ include map (relative path से absolute URL) के साथ देता है, तो setURLModifier loader द्वारा मांगी गई हर dependency को JSON को फिर लिखे बिना CDN के ज़रिए resolve करता है। यह एक multi-file, multi-resolution distribution को एक single-click load में ढहा देता है। अगले load से पहले हर पिछले model की geometry, materials और texture maps को dispose करना एक browsing session में सैकड़ों MB GPU memory जमा होने से रोकता है।
Non-destructive mesh simplification। हर mesh की original geometry का एक clone store करना और per simplification ratio सिर्फ index buffer फिर बनाना बदलावों को तेज़ रखता है और बार-बार simplification से होने वाले cumulative नुकसान से बचाता है। Per geometry.groups slice चलाना और groups को फिर बनाना multi-material assignments को बनाए रखता है। यह distance-based LOD को कैसे feed करता है, इसके लिए LOD and meshoptimizer देखें।
29 में से भाग 17। पिछला: भाग 16 - एक ऐसे world के लिए structure जो बढ़ता रहता है अगला: भाग 18 - एक scatter brush जो AI-placed महसूस होता है सीरीज़ गाइड: /hi/blog/2026-02-25-open-world-browser-series-guide