Skip to content

ब्राउज़र में Three.js + USDC

तो आपके पास Maya या Houdini से एक .usdc फाइल है, और आप उसे एक Three.js सीन में दिखाना चाहते हैं। मैं यहाँ से गुज़र चुका हूँ। हाल तक, आपके पास अच्छे विकल्प नहीं थे। आप ऑफलाइन glTF में कन्वर्ट कर सकते थे, OpenUSD का एक WASM बिल्ड चला सकते थे, या बस हार मानकर कोई दूसरा फॉर्मैट इस्तेमाल कर सकते थे।

यह गाइड आपको दिखाती है कि @cinevva/usdjs का इस्तेमाल करके USDC को सीधे JavaScript में कैसे पार्स करें, और उस डेटा को Three.js में कैसे लाएँ।

USDC क्या है?

USD फाइलें तीन रूपों में आती हैं। USDA टेक्स्ट वर्शन है, इंसानों के पढ़ने लायक और डीबग करने में आसान लेकिन बड़ा-सा। USDC (जिसे कभी-कभी "Crate" कहते हैं) वह बाइनरी फॉर्मैट है जिसे प्रोडक्शन पाइपलाइनें असल में इस्तेमाल करती हैं क्योंकि यह कॉम्पैक्ट है और नेटिव टूल्स में तेज़ी से लोड होता है। USDZ एक zip आर्काइव है जिसमें USDC के साथ टेक्सचर होते हैं, जिसे Apple अपने AR Quick Look के लिए इस्तेमाल करता है।

यहाँ पेच यह है: USDC एक प्रोप्राइटरी बाइनरी फॉर्मैट है। Pixar ने रेफरेंस इम्प्लीमेंटेशन C++ में लिखा, और अब तक इसे पढ़ने का कोई शुद्ध JavaScript तरीका नहीं था। यह अब बदलने लगा है: दिसंबर 2025 में Alliance for OpenUSD ने Core Specification 1.0 प्रकाशित किया, जो यह दस्तावेज़ करने वाला पहला अनुमोदित मानक है कि OpenUSD सीन डेटा कैसे संरचित, कंपोज़ और एक्सचेंज होता है, और एक 1.1 रिवीज़न पहले से प्रगति पर है। हालाँकि रेफरेंस इम्प्लीमेंटेशन अब भी Pixar के C++ कोडबेस में रहता है, इसलिए JavaScript से इस फॉर्मैट में जाने का व्यावहारिक रास्ता अब भी एक अलग रीडर ही है।

आपको इसकी परवाह क्यों हो सकती है

अगर आप ऐसे 3D वेब ऐप्स बना रहे हैं जिन्हें फिल्म या VFX पाइपलाइनों के कंटेंट के साथ काम करना है, तो आप USD से टकराएँगे। आर्टिस्ट Maya, Houdini, या Blender से एक्सपोर्ट करते हैं, और वे एक्सपोर्ट अक्सर USDC होते हैं।

@cinevva/usdjs से पहले, आपके पास तीन विकल्प थे। आप USDC को टेक्स्ट फॉर्मैट में कन्वर्ट करने के लिए usdcat चला सकते थे, जो एक बिल्ड स्टेप जोड़ता है और बाइनरी फॉर्मैट की कॉम्पैक्टनेस खो देता है। आप OpenUSD या TinyUSDZ को WebAssembly में कंपाइल कर सकते थे, जो आपके बंडल में मेगाबाइट्स जोड़ता है और थ्रेडिंग के लिए खास सर्वर हेडर चाहता है। या आप Three.js के बिल्ट-इन USDLoader का इस्तेमाल कर सकते थे, जो USDZ को संभालता है लेकिन सीमित USDC सपोर्ट और न्यूनतम कंपोज़िशन देता है।

अब एक चौथा विकल्प है: USDC को सीधे JavaScript में नेटिव तरीके से पार्स करें।

यह कैसे काम करता है

@cinevva/usdjs लाइब्रेरी USD की कोर फंक्शनैलिटी को TypeScript में फिर से लागू करती है। मैंने USDC पार्सर Pixar के C++ सोर्स कोड को पढ़कर और Crate फॉर्मैट स्पेसिफिकेशन को JavaScript में अनुवाद करके बनाया। जब बर्ताव में कोई अस्पष्टता होती है, तो हम वही करते हैं जो OpenUSD करता है।

व्यवहार में, यह ऐसा दिखता है:

typescript
import { parseUsdcToLayer } from '@cinevva/usdjs';

// Fetch the USDC file
const response = await fetch('/model.usdc');
const buffer = await response.arrayBuffer();

// Parse it
const layer = parseUsdcToLayer(buffer, { identifier: 'model.usdc' });

// Now you have a structured layer with prims, properties, and metadata
console.log(layer.pseudoRoot.children);

कोई WASM नहीं। कोई नेटिव कोड नहीं। बस JavaScript बाइट्स पढ़ रहा है और एक संरचित रिप्रेज़ेंटेशन बना रहा है।

इसे Three.js में जोड़ना

USD पार्स करना समस्या का आधा हिस्सा ही है। आपको USD अवधारणाओं (prims, transforms, mesh schemas) को Three.js ऑब्जेक्ट्स में कन्वर्ट भी करना होता है।

यहाँ एक न्यूनतम उदाहरण है जो एक USDC फाइल लोड करता है और Three.js mesh बनाता है:

typescript
import * as THREE from 'three';
import { UsdStage, parseUsdcToLayer } from '@cinevva/usdjs';

async function loadUsdcToThree(url: string, scene: THREE.Scene) {
  // 1. Fetch and parse
  const buffer = await fetch(url).then(r => r.arrayBuffer());
  const layer = parseUsdcToLayer(buffer, { identifier: url });
  
  // 2. Create a stage (handles composition if there are references)
  const stage = UsdStage.open(layer);
  
  // 3. Walk the prim tree
  for (const prim of stage.traverse()) {
    // Skip non-geometry
    if (prim.typeName !== 'Mesh') continue;
    
    // Get geometry data
    const points = prim.getAttribute('points')?.value;
    const faceVertexIndices = prim.getAttribute('faceVertexIndices')?.value;
    const faceVertexCounts = prim.getAttribute('faceVertexCounts')?.value;
    
    if (!points || !faceVertexIndices || !faceVertexCounts) continue;
    
    // Convert to Three.js geometry
    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', 
      new THREE.Float32BufferAttribute(points.flat(), 3)
    );
    
    // USD uses faceVertexCounts + faceVertexIndices, Three.js wants a flat index array
    // For triangulated meshes, this is straightforward:
    geometry.setIndex(Array.from(faceVertexIndices));
    geometry.computeVertexNormals();
    
    // Create mesh
    const material = new THREE.MeshStandardMaterial({ color: 0x888888 });
    const mesh = new THREE.Mesh(geometry, material);
    
    // Apply transform
    const xform = getWorldTransform(prim);
    mesh.matrix.fromArray(xform);
    mesh.matrixAutoUpdate = false;
    
    scene.add(mesh);
  }
}

function getWorldTransform(prim): number[] {
  // Simplified: in practice you'd compose parent transforms
  const xformOp = prim.getAttribute('xformOp:transform');
  if (xformOp?.value) {
    return xformOp.value.flat();
  }
  return [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
}

यह उदाहरण सरल किया हुआ है। असली कोड को transform stacking संभालनी होती है (USD prims के पास कई xformOp एट्रिब्यूट हो सकते हैं जो आपस में कंपोज़ होते हैं), subdivision surfaces (catmullClark subdivision वाले meshes को असल subdivision चाहिए), materials (UsdPreviewSurface पैरामीटर Three.js के MeshStandardMaterial से मैप होते हैं), textures (asset पाथ को रिज़ॉल्व और लोड करना होता है), और skeletal animation (UsdSkel bindings को SkinnedMesh में कन्वर्ट करना होता है)।

अगर आप देखना चाहते हैं कि यह सब एक साथ कैसे काम करता है, तो @cinevva/usdjs-viewer देखें। यह एक पूरा रेफरेंस इम्प्लीमेंटेशन है।

कंपोज़िशन आपकी सोच से ज़्यादा मायने रखती है

एक अकेली USDC फाइल पार्स करना आसान हिस्सा है। असली USD सीन कंपोज़िशन इस्तेमाल करते हैं, यानी sublayers, references, payloads, और variants सब एक साथ काम करते हैं।

एक कार मॉडल की कल्पना करें। बॉडी body.usdc को रेफरेंस करती है, पहिए wheel.usdc को रेफरेंस करते हैं, और "sport" व "sedan" कॉन्फ़िगरेशन के लिए variants हैं। फाइनल सीन रनटाइम पर इन सभी टुकड़ों से जोड़कर बनाया जाता है।

इसे आप ऐसे संभालते हैं:

typescript
import { UsdStage, parseUsdcToLayer, FetchResolver } from '@cinevva/usdjs';

// Create a resolver that knows how to fetch referenced files
const resolver = new FetchResolver({
  baseUrl: '/assets/',
});

// Open with composition
const stage = UsdStage.open(rootLayer, { resolver });

// Set a variant selection
stage.setVariantSelection('/Car', 'bodyStyle', 'sport');

// Now traverse the composed scene
for (const prim of stage.traverse()) {
  // You'll see resolved references and the selected variant
}

resolver रेफरेंस की गई USDC फाइलों को ज़रूरत पड़ने पर fetch करता है। कंपोज़िशन JavaScript में होती है, जिससे वही फ्लैटन किया हुआ सीन बनता है जो आपको usdcat --flatten से मिलता।

परफॉर्मेंस

ईमानदार रहते हैं: JavaScript में USDC पार्स करना नेटिव C++ से धीमा है। यह WASM और बिल्ड स्टेप्स को छोड़ने का सौदा है।

व्यवहार में, 10MB से कम और 100K त्रिभुजों से कम वाले मॉडल के साथ आम वेब उपयोग के लिए, मॉडर्न हार्डवेयर पर पार्सिंग में 50-200ms लगते हैं। अगर आप एक लोडिंग इंडिकेटर दिखाते हैं तो शुरुआती लोड के लिए यह ठीक है।

आप इसे तेज़ बना सकते हैं। पूरा सीन एक बार में लोड न करें। रूट लेयर लोड करें, जो दिखा सकें वह रेंडर करें, फिर ज़रूरत पड़ने पर payloads fetch करें। पार्सिंग को एक Web Worker में ले जाएँ ताकि UI रिस्पॉन्सिव बना रहे। पार्स की गई लेयर्स को IndexedDB में कैश करें ताकि दोबारा आने पर सब तुरंत हो। पूरा सीन स्ट्रीम होते समय एक low-res प्रीव्यू दिखाएँ।

यहाँ एक Web Worker सेटअप है:

typescript
// worker.ts
import { parseUsdcToLayer } from '@cinevva/usdjs';

self.onmessage = async (e) => {
  const { buffer, identifier } = e.data;
  const layer = parseUsdcToLayer(buffer, { identifier });
  
  // Serialize layer data (not the methods)
  const data = serializeLayer(layer);
  self.postMessage(data);
};

// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url));

worker.postMessage({ buffer, identifier: 'model.usdc' });
worker.onmessage = (e) => {
  const layerData = e.data;
  // Build Three.js scene from layerData
};

क्या काम करता है और क्या नहीं

ज़्यादातर आम चीज़ें काम करती हैं। Geometry (meshes, points, curves) सही पार्स होती है, जिसमें compressed vertex arrays भी शामिल हैं। Transforms पूरे xformOp stacking के साथ काम करते हैं। Materials UsdPreviewSurface से PBR में मैप होते हैं। कंपोज़िशन sublayers, references, payloads, variants, और inherits को संभालती है। अगर आप एक resolver देते हैं तो Textures रिज़ॉल्व और लोड होते हैं। आम character rigs के लिए बेसिक skeletal animation काम करती है।

हालाँकि कुछ कमियाँ हैं। यह एक Hydra इम्प्लीमेंटेशन नहीं है, इसलिए USD डेटा को जिस भी रेंडरिंग engine का इस्तेमाल कर रहे हैं उसमें कन्वर्ट करने की ज़िम्मेदारी आपकी है। UsdGeomMesh जैसी सुविधाजनक मेथड वाली typed schema APIs नहीं हैं। आप जेनेरिक prims और एट्रिब्यूट्स के साथ काम करते हैं। कुछ कंपोज़िशन फीचर्स जैसे specializes, relocates, और value clips अभी लागू नहीं हुए हैं। साधारण UsdPreviewSurface से आगे के जटिल material networks को कस्टम हैंडलिंग चाहिए।

इसे कब इस्तेमाल करें

यह तब समझ में आता है जब आपको एक वेब ऐप में बिना WASM बिल्ड स्टेप के USD फाइलें लोड करनी हों, जब आपकी पाइपलाइन USDC आउटपुट करती है और आप glTF में कन्वर्ट नहीं करना चाहते, जब आप ब्राउज़र के लिए एक USD viewer या editor बना रहे हों, या जब आप USD स्ट्रक्चर को सिर्फ रेंडर करने के बजाय उसकी जाँच करना चाहते हों।

कुछ और इस्तेमाल करें अगर आपको हर एज केस के साथ पूरी OpenUSD पैरिटी चाहिए, अगर आपके सीन बहुत बड़े हैं (100MB+) और उन्हें नेटिव परफॉर्मेंस चाहिए, या अगर आप पहले से एक WASM बिल्ड इस्तेमाल कर रहे हैं और आपके प्रोजेक्ट के लिए जटिलता स्वीकार्य है।

संसाधन

तीन पैकेज हैं: कोर पार्सिंग और कंपोज़िशन के लिए @cinevva/usdjs, Three.js-आधारित ब्राउज़र viewer के लिए @cinevva/usdjs-viewer, और टेस्ट में headless PNG रेंडरिंग के लिए @cinevva/usdjs-renderer

डॉक्युमेंटेशन के लिए, usdjs API Reference, Pixar OpenUSD Specification, और Three.js Documentation देखें।

अगर आप समझना चाहते हैं कि USDC पार्सर Pixar के इम्प्लीमेंटेशन से कैसे मैप होता है, तो usdjs रिपो में src/usdc/PIXAR_PARITY.md देखें।

इसे आज़माएँ

इसे काम करते देखने का सबसे तेज़ तरीका है usdjs-viewer demo पर जाना और उस पर एक USDC फाइल ड्रॉप करना।

इसे अपने प्रोजेक्ट में जोड़ने के लिए, पैकेज इंस्टॉल करें और ऊपर दिए कोड उदाहरणों से शुरुआत करें:

bash
npm install @cinevva/usdjs

ब्राउज़र में USD संभव है। यह हमेशा सही चुनाव नहीं होता, लेकिन जब आपको इसकी ज़रूरत हो, तो शुद्ध JavaScript पार्सिंग का मतलब है एक कम बिल्ड स्टेप और एक कम डिपेंडेंसी के बारे में सोचना।

संबंधित