원클릭으로
viverse-avatar-sdk
Loading and displaying VIVERSE user avatars (GLB/VRM) in 3D scenes
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Loading and displaying VIVERSE user avatars (GLB/VRM) in 3D scenes
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
Three.js Polygon Streaming .xrg integration and debugging playbook for @polygon-streaming/web-player-threejs, including correct wrapper events, asset publishing, model fitting, and fallback replacement policy
Bundle entry for the exported VIVERSE PlayCanvas Toolkit workflow pack. Use when you want bundled VIVERSE PlayCanvas Toolkit skills, prompts, catalogs, and helper assets in one standalone package.
Collision detection patterns for Three.js games without physics engines
Protect third-party API keys by moving secret-bearing calls into VIVERSE Play Lambda scripts and keeping only non-secret user data in VIVERSE Storage.
Build or fix CPU racing drivers for Three.js or VIVERSE racing games. Use for waypoint loops, tile-to-route reconstruction, lap completion, recovery logic, and low-overhead debug workflows.
VIVERSE Login SDK integration for user authentication and SSO
SOC 직업 분류 기준
| name | viverse-avatar-sdk |
| description | Loading and displaying VIVERSE user avatars (GLB/VRM) in 3D scenes |
| prerequisites | ["VIVERSE Auth integration","PlayCanvas or Three.js"] |
| tags | ["viverse","avatar","glb","vrm","playcanvas","threejs"] |
Load authenticated user avatars (GLB/VRM) into 3D scenes with robust fallback behavior.
Use this when a project needs:
access_token availablewindow.viverse || window.VIVERSE_SDK[!CAUTION] When using
ViverseAuthController(the standard auth module), do NOT callavatarClient.getProfile()yourself.enrichProfile()inside the auth controller already calls it internally and merges the full response intostate.profile.raw. Calling it a second time frommain.jsor your game code will throw "This API is only available to logged-in users" because the second call doesn't have the correct token context.
The auth controller already fetches the avatar profile. Read the 3D URL directly from state.profile.raw:
// In your auth callback:
const auth = new ViverseAuthController(async state => {
if (state.status !== 'ready' || !state.isAuthenticated) return;
// ✅ enrichProfile() already called getProfile() — read from raw
const raw = state.profile.raw || {};
const avatarUrl = raw.activeAvatar?.vrmUrl // VRM-backed (check FIRST)
|| raw.activeAvatar?.avatarUrl // GLB fallback
|| null;
// state.profile.avatarUrl = headIconUrl (2D icon only, NOT the 3D model URL)
// The 3D model URL lives in state.profile.raw.activeAvatar, not state.profile
if (avatarUrl) loadAvatarModel(avatarUrl);
});
Key distinction:
state.profile.avatarUrl → 2D head icon URL only (set by normalizeProfile from headIconUrl)state.profile.raw.activeAvatar.vrmUrl → 3D model URL (full raw response from getProfile())Only if managing auth yourself without the standard auth controller:
const vSdk = window.vSdk || window.viverse || window.VIVERSE_SDK;
const avatarClient = new vSdk.avatar({
baseURL: 'https://sdk-api.viverse.com/',
accessToken: token,
token, // some SDK versions use this field
authorization: token, // and/or this field
});
const profile = await avatarClient.getProfile();
const avatarUrl = profile.activeAvatar?.vrmUrl
|| profile.activeAvatar?.avatarUrl
|| null;
[!CAUTION]
checkAuth()does NOT return avatar URLs or display name. You must use the Avatar SDKgetProfile()method.
[!CAUTION] Always check
vrmUrlbeforeavatarUrl. The VIVERSE default avatar uses VRM format and will returnactiveAvatar.vrmUrlwhile leavingavatarUrlnull. Checking onlyavatarUrlgives a false null and loads the fallback instead of the real avatar.
const asset = new pc.Asset("avatar-asset", "container", {
url: avatarUrl,
filename: "avatar.glb", // force GLB/container handler
});
asset.on("load", () => {
const entity = asset.resource.instantiateRenderEntity();
app.root.addChild(entity);
// Normalize scale to target height if needed.
});
asset.on("error", () => {
// Fallback to placeholder avatar
});
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load(avatarUrl, (gltf) => {
const model = gltf.scene;
// VRM models face -Z by default; rotate 180° to face +Z (away from camera)
model.rotation.y = Math.PI;
// CRITICAL: Force visibility on entire scene graph.
// VRM root nodes are sometimes loaded with visible=false.
model.traverse((child) => {
child.visible = true;
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
// Disable frustum culling — retargeting can shift bounds outside camera view
child.frustumCulled = false;
}
});
scene.add(model);
}, undefined, (err) => {
console.error('Avatar load failed, using fallback', err);
// Use placeholder
});
When replacing a default character model with an authenticated avatar at runtime:
// ✅ DO: Cache animation clips BEFORE removing the default model
if (currentModel) {
cachedAnimations = currentClips; // preserve clips!
scene.remove(currentModel); // remove mesh only
mixer = null;
// DO NOT clear cachedAnimations
}
// ❌ DON'T: Clear animations on model swap
// cachedAnimations = null; // This will kill retargeting!
The reason: animation clips are bundled inside the default model's GLB. Once the model is removed from the scene, the only copy of those clips is in the cachedAnimations reference.
If avatar URL load fails, always render a placeholder avatar so gameplay continues.
For VIVERSE VRMA retargeting:
Normalized_Avatar_* bones.getProfile() is already called by ViverseAuthController — calling it again from main.js throws "This API is only available to logged-in users". Read state.profile.raw.activeAvatar instead.state.profile.avatarUrl is the 2D head icon, NOT the 3D model — normalizeProfile() maps activeAvatar.headIconUrl → profile.avatarUrl. For the 3D GLB/VRM URL, always read state.profile.raw.activeAvatar.vrmUrl || state.profile.raw.activeAvatar.avatarUrl.{ accessToken, token, authorization } all set to the same token value; different SDK versions use different field names.checkAuth() alone has no avatar URL.vrmUrl takes priority over avatarUrl — VRM profiles do not populate avatarUrl.-Z by default. Rotate Math.PI on Y-axis for environments where models should face +Z (away from camera).gltf.scene can load with root nodes set to visible=false. Always traverse and force child.visible = true after loading.child.frustumCulled = false on all SkinnedMesh nodes.SkinnedMesh.skeleton stores bone references by index. Renaming bone.name after load corrupts the skin matrix lookup and makes the avatar invisible. Instead, remap animation clip track names (clone tracks, rewrite .name field) — see vrma-animation-retargeting skill.Avatar_* (original rig, mesh-bound) and Normalized_Avatar_* (identity rest pose). Always target Normalized_Avatar_* for animation tracks. See avatar-animation-troubleshooting.md.