Browser में Three.js + USDC
तो आपके पास Maya या Houdini से एक .usdc file है, और आप उसे Three.js scene में दिखाना चाहते हैं। मैं भी इस स्थिति से गुज़रा हूं। कुछ समय पहले तक आपके विकल्प अच्छे नहीं थे। आप offline glTF में convert कर सकते थे, OpenUSD का एक WASM build खड़ा कर सकते थे, या बस हार मानकर कोई दूसरा format इस्तेमाल कर सकते थे।
यह guide आपको दिखाती है कि @cinevva/usdjs का इस्तेमाल करके JavaScript में सीधे USDC को कैसे parse करें, और उस data को Three.js में कैसे लाएं।
USDC क्या है?
USD files तीन रूपों में आती हैं। USDA text version है, जो human-readable और debug करने में आसान है पर बहुत लंबा है। USDC (जिसे कभी-कभी "Crate" भी कहते हैं) binary format है जिसे production pipelines असल में इस्तेमाल करती हैं क्योंकि यह compact है और native tools में तेज़ी से लोड होता है। USDZ एक zip archive है जिसमें USDC के साथ textures होते हैं, और Apple इसे AR Quick Look के लिए इस्तेमाल करता है।
यहां एक पेच है: USDC एक proprietary binary format है। Pixar ने reference implementation C++ में लिखी थी, और अब तक इसे पढ़ने का कोई pure JavaScript तरीका नहीं था। यह बदलने लगा है: दिसंबर 2025 में Alliance for OpenUSD ने Core Specification 1.0 प्रकाशित की, जो यह दर्ज करने वाला पहला ratified standard है कि OpenUSD scene data कैसे संरचित, composed, और exchange होता है, और 1.1 revision पहले से काम में है। हालांकि reference implementation अब भी Pixar के C++ codebase में ही रहती है, इसलिए JavaScript से इस format तक पहुंचने का व्यावहारिक तरीका अभी भी एक अलग reader ही है।
आपको इसकी परवाह क्यों हो सकती है
अगर आप ऐसे 3D web apps बना रहे हैं जिन्हें film या VFX pipelines के content के साथ काम करना है, तो आपका USD से सामना होगा। Artists Maya, Houdini, या Blender से export करते हैं, और वे exports अक्सर USDC होते हैं।
@cinevva/usdjs से पहले, आपके पास तीन विकल्प थे। आप USDC को text format में convert करने के लिए usdcat चला सकते थे, जो एक build step जोड़ता है और binary format की compactness खो देता है। आप OpenUSD या TinyUSDZ को WebAssembly में compile कर सकते थे, जो आपके bundle में megabytes जोड़ता है और threading के लिए खास server headers चाहता है। या आप Three.js के built-in USDLoader का इस्तेमाल कर सकते थे, जो USDZ संभालता है पर इसका USDC support सीमित है और composition न्यूनतम है।
अब एक चौथा विकल्प है: USDC को JavaScript में natively parse करना।
यह कैसे काम करता है
@cinevva/usdjs library, USD की core functionality को TypeScript में फिर से लागू करती है। मैंने Pixar का C++ source code पढ़कर और Crate format specification को JavaScript में अनुवाद करके USDC parser बनाया। जब behavior में कोई अस्पष्टता होती है, तो हम वही करते हैं जो OpenUSD करता है।
व्यवहार में, यह ऐसा दिखता है:
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 नहीं। कोई native code नहीं। बस JavaScript bytes पढ़ रहा है और एक structured representation बना रहा है।
इसे Three.js में जोड़ना
USD को parse करना समस्या का सिर्फ आधा हिस्सा है। आपको USD concepts (prims, transforms, mesh schemas) को Three.js objects में भी convert करना होता है।
यहां एक न्यूनतम उदाहरण है जो एक USDC file लोड करता है और Three.js meshes बनाता है:
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];
}यह उदाहरण सरल बनाया गया है। असली code को transform stacking संभालनी पड़ती है (USD prims में कई xformOp attributes हो सकते हैं जो आपस में compose होते हैं), subdivision surfaces (catmullClark subdivision वाले meshes को असल subdivision चाहिए), materials (UsdPreviewSurface parameters Three.js MeshStandardMaterial से map होते हैं), textures (asset paths को resolution और loading चाहिए), और skeletal animation (UsdSkel bindings को SkinnedMesh में convert करना पड़ता है)।
अगर आप देखना चाहते हैं कि यह सब एक साथ कैसे काम करता है, तो @cinevva/usdjs-viewer देखें। यह एक पूरा reference implementation है।
Composition आपकी सोच से ज़्यादा मायने रखती है
एक अकेली USDC file को parse करना आसान हिस्सा है। असली USD scenes composition का इस्तेमाल करती हैं, यानी sublayers, references, payloads, और variants सब एक साथ काम करते हैं।
एक car model की कल्पना करें। body, body.usdc को reference करती है, wheels, wheel.usdc को reference करती हैं, और "sport" तथा "sedan" configurations के लिए variants होते हैं। अंतिम scene इन सभी टुकड़ों से runtime पर इकट्ठा होता है।
इसे आप ऐसे संभालते हैं:
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, referenced USDC files को मांग पर fetch करता है। Composition JavaScript में होती है, और वही flattened scene बनाती है जो आपको usdcat --flatten से मिलता है।
Performance
ईमानदारी से कहें: JavaScript में USDC को parse करना native C++ से धीमा है। यही WASM और build steps छोड़ने की कीमत है।
व्यवहार में, 10MB से छोटे और 100K से कम triangles वाले models के साथ आम web use cases के लिए, modern hardware पर parsing में 50-200ms लगते हैं। अगर आप एक loading indicator दिखाते हैं तो शुरुआती load के लिए यह ठीक है।
आप इसे तेज़ बना सकते हैं। पूरा scene पहले से लोड न करें। root layer लोड करें, जो दिखा सकते हैं वह render करें, फिर payloads को मांग पर fetch करें। parsing को एक Web Worker में ले जाएं ताकि UI responsive रहे। parsed layers को IndexedDB में cache करें ताकि दोबारा आने पर सब तुरंत हो। पूरा scene stream होते वक़्त एक low-res preview दिखाएं।
यहां एक Web Worker setup है:
// 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) सही parse होती है, जिसमें compressed vertex arrays भी शामिल हैं। Transforms पूरी xformOp stacking के साथ काम करते हैं। Materials, UsdPreviewSurface से PBR में map होते हैं। Composition, sublayers, references, payloads, variants, और inherits संभालती है। अगर आप एक resolver देते हैं तो Textures resolve होकर लोड होती हैं। आम character rigs के लिए बुनियादी skeletal animation काम करती है।
हालांकि कुछ कमियां भी हैं। यह कोई Hydra implementation नहीं है, इसलिए USD data को आप जिस भी rendering engine का इस्तेमाल कर रहे हैं उसमें convert करने की ज़िम्मेदारी आपकी है। UsdGeomMesh जैसी convenience methods वाली typed schema APIs नहीं हैं। आप generic prims और attributes के साथ काम करते हैं। specializes, relocates, और value clips जैसी कुछ composition features अभी लागू नहीं हुई हैं। साधारण UsdPreviewSurface से आगे के जटिल material networks को custom handling चाहिए।
इसे कब इस्तेमाल करें
यह तब सही है जब आपको किसी web app में बिना WASM build step के USD files लोड करनी हों, जब आपकी pipeline USDC output देती हो और आप glTF में convert नहीं करना चाहते, जब आप browser के लिए कोई USD viewer या editor बना रहे हों, या जब आप USD structure को सिर्फ render करने के बजाय उसकी जांच करना चाहते हों।
कुछ और इस्तेमाल करें अगर आपको हर edge case के साथ पूरी OpenUSD parity चाहिए, अगर आपके scenes बहुत बड़े (100MB+) हैं और उन्हें native performance चाहिए, या अगर आप पहले से एक WASM build इस्तेमाल कर रहे हैं और आपके project के लिए वह जटिलता स्वीकार्य है।
Resources
तीन packages हैं: core parsing और composition के लिए @cinevva/usdjs, Three.js-based browser viewer के लिए @cinevva/usdjs-viewer, और tests में headless PNG rendering के लिए @cinevva/usdjs-renderer।
documentation के लिए, usdjs API Reference, Pixar OpenUSD Specification, और Three.js Documentation देखें।
अगर आप समझना चाहते हैं कि USDC parser, Pixar के implementation से कैसे map होता है, तो usdjs repo में src/usdc/PIXAR_PARITY.md देखें।
इसे आज़माएं
इसे काम करते देखने का सबसे तेज़ तरीका है usdjs-viewer demo पर जाना और उस पर एक USDC file drop करना।
अपने project में इसे जोड़ने के लिए, package install करें और ऊपर दिए code examples से शुरू करें:
npm install @cinevva/usdjsBrowser में USD संभव है। यह हमेशा सही चुनाव नहीं होता, पर जब आपको इसकी ज़रूरत हो, तो pure JavaScript parsing का मतलब है एक कम build step और सोचने के लिए एक कम dependency।
संबंधित
- Game developers के लिए WebGL की बुनियादी बातें — वह rendering API जो Three.js scenes को चलाती है
- 2026 में Web Games Tech Stack — WebGL/WebGPU/Wasm के परिदृश्य में Three.js कहां फिट बैठता है
- Browser 3D Open World Tech — open worlds के लिए USD समेत 3D assets streaming
- मुफ़्त Game Assets कहां पाएं — Three.js के साथ संगत 3D models के स्रोत