Skip to content

Three.js + USDC di Browser

Jadi, Anda memiliki file .usdc dari Maya atau Houdini dan ingin menampilkannya dalam scene Three.js. Saya pernah mengalaminya. Sampai baru-baru ini, pilihan yang tersedia kurang ideal. Anda bisa mengonversinya ke glTF secara offline, menyiapkan build WASM dari OpenUSD, atau menyerah dan menggunakan format lain.

Panduan ini menunjukkan cara mengurai USDC secara langsung di JavaScript menggunakan @cinevva/usdjs, serta cara memasukkan data tersebut ke Three.js.

Apa Itu USDC?

File USD hadir dalam tiga jenis. USDA adalah versi teks yang mudah dibaca manusia dan mudah di-debug, tetapi panjang. USDC (terkadang disebut "Crate") adalah format biner yang benar-benar digunakan oleh pipeline produksi karena ringkas dan cepat dimuat dalam alat native. USDZ adalah arsip zip yang berisi USDC beserta tekstur, yang digunakan Apple untuk AR Quick Look.

Masalahnya: USDC adalah format biner proprieter. Pixar menulis implementasi referensinya dalam C++, dan hingga kini belum ada cara berbasis JavaScript murni untuk membacanya. Hal itu mulai berubah: pada Desember 2025, Alliance for OpenUSD menerbitkan Core Specification 1.0, standar pertama yang diratifikasi untuk mendokumentasikan cara data scene OpenUSD disusun, dikomposisikan, dan dipertukarkan, sementara revisi 1.1 sudah dalam proses pengerjaan. Namun, implementasi referensinya masih berada dalam basis kode C++ milik Pixar, sehingga pembaca terpisah tetap menjadi cara praktis untuk mengakses format tersebut dari JavaScript.

Mengapa Ini Mungkin Penting bagi Anda

Jika Anda membuat aplikasi web 3D yang perlu bekerja dengan konten dari pipeline film atau VFX, Anda akan berhadapan dengan USD. Seniman mengekspor dari Maya, Houdini, atau Blender, dan hasil ekspor tersebut sering kali berupa USDC.

Sebelum @cinevva/usdjs, Anda memiliki tiga pilihan. Anda bisa menjalankan usdcat untuk mengonversi USDC ke format teks, yang menambahkan satu langkah build dan menghilangkan keringkasan format binernya. Anda bisa mengompilasi OpenUSD atau TinyUSDZ ke WebAssembly, yang menambahkan beberapa megabita ke bundle dan memerlukan header server khusus untuk threading. Atau Anda bisa menggunakan USDLoader bawaan Three.js, yang menangani USDZ tetapi memiliki dukungan USDC terbatas dan komposisi minimal.

Sekarang ada pilihan keempat: mengurai USDC secara native di JavaScript.

Cara Kerjanya

Library @cinevva/usdjs mengimplementasikan ulang fungsionalitas inti USD dalam TypeScript. Saya membuat parser USDC dengan membaca kode sumber C++ milik Pixar dan menerjemahkan spesifikasi format Crate ke JavaScript. Jika terdapat ambiguitas dalam perilakunya, kami mengikuti cara kerja OpenUSD.

Dalam praktiknya, penggunaannya terlihat seperti ini:

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);

Tanpa WASM. Tanpa kode native. Hanya JavaScript yang membaca byte dan membangun representasi terstruktur.

Menghubungkannya ke Three.js

Mengurai USD hanyalah separuh dari persoalan. Anda juga perlu mengonversi konsep USD (prim, transformasi, dan skema mesh) menjadi objek Three.js.

Berikut contoh minimal yang memuat file USDC dan membuat mesh Three.js:

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];
}

Contoh ini disederhanakan. Kode nyata perlu menangani penumpukan transformasi (prim USD dapat memiliki beberapa atribut xformOp yang dikomposisikan bersama), subdivision surface (mesh dengan subdivisi catmullClark memerlukan proses subdivisi yang sebenarnya), material (parameter UsdPreviewSurface dipetakan ke MeshStandardMaterial milik Three.js), tekstur (path aset perlu di-resolve dan dimuat), serta animasi skeletal (binding UsdSkel perlu dikonversi menjadi SkinnedMesh).

Jika Anda ingin melihat bagaimana semua ini bekerja bersama, lihat @cinevva/usdjs-viewer. Ini adalah implementasi referensi yang lengkap.

Komposisi Lebih Penting daripada yang Anda Kira

Mengurai satu file USDC adalah bagian yang mudah. Scene USD di dunia nyata menggunakan komposisi, yang berarti sublayer, referensi, payload, dan varian bekerja bersama.

Bayangkan sebuah model mobil. Bodi mereferensikan body.usdc, roda mereferensikan wheel.usdc, dan tersedia varian untuk konfigurasi "sport" serta "sedan". Scene akhir dirakit dari semua bagian tersebut saat runtime.

Berikut cara menanganinya:

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 mengambil file USDC yang direferensikan sesuai kebutuhan. Komposisi berlangsung di JavaScript dan menghasilkan scene yang telah diratakan, seperti yang akan Anda dapatkan dari usdcat --flatten.

Performa

Mari jujur: mengurai USDC di JavaScript lebih lambat daripada C++ native. Itulah kompromi karena tidak menggunakan WASM dan langkah build tambahan.

Dalam praktiknya, untuk kasus penggunaan web umum dengan model berukuran di bawah 10 MB dan kurang dari 100 ribu segitiga, proses parsing memerlukan 50–200 md pada perangkat keras modern. Waktu tersebut masih wajar untuk pemuatan awal jika Anda menampilkan indikator pemuatan.

Anda dapat mempercepatnya. Jangan memuat seluruh scene di awal. Muat layer root, render apa yang tersedia, lalu ambil payload sesuai kebutuhan. Pindahkan proses parsing ke Web Worker agar UI tetap responsif. Cache layer yang telah diurai di IndexedDB agar kunjungan berikutnya berlangsung seketika. Tampilkan pratinjau beresolusi rendah selagi scene lengkap di-stream.

Berikut konfigurasi 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
};

Apa yang Berfungsi dan Apa yang Tidak

Sebagian besar hal umum sudah berfungsi. Geometri (mesh, titik, dan kurva) diurai dengan benar, termasuk array vertex terkompresi. Transformasi berfungsi dengan penumpukan xformOp penuh. Material dipetakan dari UsdPreviewSurface ke PBR. Komposisi menangani sublayer, referensi, payload, varian, dan pewarisan. Tekstur dapat di-resolve dan dimuat jika Anda menyediakan resolver. Animasi skeletal dasar berfungsi untuk rig karakter umum.

Namun, masih ada kekurangan. Ini bukan implementasi Hydra, jadi Anda bertanggung jawab mengonversi data USD ke mesin rendering apa pun yang digunakan. Tidak tersedia API skema bertipe seperti UsdGeomMesh dengan berbagai metode praktis. Anda bekerja dengan prim dan atribut generik. Beberapa fitur komposisi seperti specializes, relocates, dan value clips belum diimplementasikan. Jaringan material kompleks di luar UsdPreviewSurface sederhana memerlukan penanganan khusus.

Kapan Sebaiknya Menggunakan Ini

Solusi ini cocok ketika Anda perlu memuat file USD dalam aplikasi web tanpa langkah build WASM, ketika pipeline Anda menghasilkan USDC dan Anda tidak ingin mengonversinya ke glTF, ketika Anda membuat penampil atau editor USD untuk browser, atau ketika Anda ingin memeriksa struktur USD alih-alih sekadar merendernya.

Gunakan solusi lain jika Anda memerlukan kesetaraan penuh dengan OpenUSD untuk setiap kasus khusus, jika scene Anda sangat besar (100 MB+) dan memerlukan performa native, atau jika Anda sudah menggunakan build WASM dan kompleksitasnya masih dapat diterima untuk proyek Anda.

Sumber Daya

Ketiga paket tersebut adalah @cinevva/usdjs untuk parsing dan komposisi inti, @cinevva/usdjs-viewer untuk penampil browser berbasis Three.js, serta @cinevva/usdjs-renderer untuk rendering PNG headless dalam pengujian.

Untuk dokumentasi, lihat Referensi API usdjs, Spesifikasi OpenUSD Pixar, dan Dokumentasi Three.js.

Jika Anda ingin memahami bagaimana parser USDC dipetakan ke implementasi Pixar, lihat src/usdc/PIXAR_PARITY.md di repositori usdjs.

Cobalah

Cara tercepat untuk melihatnya bekerja adalah dengan mengunjungi demo usdjs-viewer dan meletakkan file USDC di sana.

Untuk mengintegrasikannya ke proyek Anda sendiri, instal paket tersebut dan mulai dengan contoh kode di atas:

bash
npm install @cinevva/usdjs

USD di browser itu memungkinkan. Ini tidak selalu menjadi pilihan yang tepat, tetapi saat Anda membutuhkannya, parsing JavaScript murni berarti satu langkah build dan satu dependensi lebih sedikit untuk dipikirkan.

Terkait