# UniVRM — Unity SDK for VRM 3D Avatar Format UniVRM is an open-source Unity SDK (version 0.131.1) developed by the VRM Consortium that enables importing, exporting, and runtime manipulation of VRM 3D humanoid avatar files. VRM is a platform-independent format built on top of glTF 2.0, purpose-built for use in VR, VTubing, and social applications. UniVRM provides three Unity packages: `com.vrmc.gltf` (UniGLTF base), `com.vrmc.univrm` (VRM 0.x / spec version 0.x), and `com.vrmc.vrm` (VRM 1.0), all requiring Unity 2021.3 or later. The SDK's core functionality covers the complete VRM avatar lifecycle. On import, it parses GLB/GLTF binaries and reconstructs humanoid skeletons (`Avatar`), blend shapes / expressions, first-person rendering layers, SpringBone physics, LookAt eye control, and material properties (MToon and PBR, for both Built-In and URP render pipelines). At runtime, the `Vrm10Runtime` / `VRMBlendShapeProxy` APIs allow frame-by-frame control of facial expressions, eye gaze direction, and spring-bone physics. On export, `VRMExporter` / `Vrm10Exporter` round-trip a live GameObject back into a valid `.vrm` binary. VRM 0.x models can be transparently migrated to VRM 1.0 at load time. --- ## APIs and Key Functions ### `VrmUtility.LoadAsync` — Load a VRM 0.x file from a file path High-level convenience method that parses the file, creates all VRM components, and returns a `RuntimeGltfInstance`. Automatically falls back to plain glTF if the file is not a valid VRM 0.x model. ```csharp using VRM; using UniGLTF; using UnityEngine; public class Vrm0Loader : MonoBehaviour { async void Start() { string path = "/path/to/avatar.vrm"; // Optional: receive meta before the full model loads VrmUtility.MetaCallback onMeta = (VRMMetaObject meta) => { Debug.Log($"Title: {meta.Title} Author: {meta.Author} License: {meta.LicenseType}"); if (meta.Thumbnail != null) GetComponent().texture = meta.Thumbnail; }; // Use RuntimeOnlyAwaitCaller to spread loading across frames (avoids frame drops) var instance = await VrmUtility.LoadAsync( path, awaitCaller: new RuntimeOnlyAwaitCaller(), metaCallback: onMeta ); instance.ShowMeshes(); instance.EnableUpdateWhenOffscreen(); instance.gameObject.transform.SetParent(transform, false); // instance.gameObject now has: Animator, VRMMeta, VRMBlendShapeProxy, // VRMFirstPerson, VRMLookAtHead, VRMSpringBone components attached } } ``` --- ### `VrmUtility.LoadBytesAsync` — Load a VRM 0.x file from a byte array Identical to `LoadAsync` but accepts raw bytes. Useful for loading from web requests (UnityWebRequest), memory streams, or addressables. Supports a custom `IVrm0XSpringBoneRuntime` to select the spring-bone physics implementation. ```csharp using VRM; using UniGLTF; using UnityEngine; using UnityEngine.Networking; public class Vrm0WebLoader : MonoBehaviour { async void Start() { string url = "https://example.com/avatar.vrm"; using var request = UnityWebRequest.Get(url); await request.SendWebRequest(); if (request.result != UnityWebRequest.Result.Success) { Debug.LogError(request.error); return; } byte[] bytes = request.downloadHandler.data; // Use the Job-system accelerated spring bone runtime IVrm0XSpringBoneRuntime springbone = new Vrm0XFastSpringboneRuntime(); var instance = await VrmUtility.LoadBytesAsync( path: url, // used only for error messages / asset naming bytes: bytes, awaitCaller: new RuntimeOnlyAwaitCaller(), springboneRuntime: springbone ); instance.ShowMeshes(); } } ``` --- ### `Vrm10.LoadPathAsync` — Load a VRM 1.0 file (with optional VRM 0.x migration) The primary high-level API for VRM 1.0. Automatically migrates VRM 0.x models into the VRM 1.0 runtime when `canLoadVrm0X: true`. Returns a `Vrm10Instance` MonoBehaviour that owns the `Runtime` object. ```csharp using UniVRM10; using UniGLTF; using UnityEngine; using System.Threading; public class Vrm10Loader : MonoBehaviour { CancellationTokenSource _cts; async void Start() { _cts = new CancellationTokenSource(); var instance = await Vrm10.LoadPathAsync( path: "/path/to/avatar.vrm", canLoadVrm0X: true, // migrate VRM 0.x on-the-fly controlRigGenerationOption: ControlRigGenerationOption.Generate, showMeshes: true, awaitCaller: new RuntimeOnlyAwaitCaller(), vrmMetaInformationCallback: (thumbnail, vrm10Meta, vrm0Meta) => { if (vrm10Meta != null) Debug.Log($"VRM1 model: {vrm10Meta.Name}, authors: {string.Join(", ", vrm10Meta.Authors)}"); else if (vrm0Meta != null) Debug.Log($"VRM0 model (migrated): {vrm0Meta.title}"); }, ct: _cts.Token, springboneRuntime: new Vrm10FastSpringboneRuntimeStandalone() ); // instance is a Vrm10Instance (MonoBehaviour) with a live Runtime instance.LookAtTarget = Camera.main.transform; instance.LookAtTargetType = VRM10ObjectLookAt.LookAtTargetTypes.SpecifiedTransform; } void OnDestroy() => _cts?.Cancel(); } ``` --- ### `Vrm10.LoadBytesAsync` — Load VRM 1.0 from a byte array Same semantics as `LoadPathAsync` but reads from a `byte[]`. Required for WebGL where file-system access is unavailable. ```csharp using UniVRM10; using UniGLTF; using UnityEngine; using System.Threading; public class Vrm10BytesLoader : MonoBehaviour { // Called from a WebGL jslib or after UnityWebRequest public async void LoadFromBytes(byte[] bytes) { var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); Vrm10Instance vrm = await Vrm10.LoadBytesAsync( bytes: bytes, canLoadVrm0X: true, showMeshes: false, // show manually after setup awaitCaller: new RuntimeOnlyNoThreadAwaitCaller(), // WebGL: no threads ct: cts.Token ); if (vrm == null) { Debug.LogError("Load failed"); return; } vrm.GetComponent().ShowMeshes(); vrm.GetComponent().EnableUpdateWhenOffscreen(); } } ``` --- ### `VRMImporterContext` — Low-level VRM 0.x importer Provides fine-grained control over the import pipeline. Use this when you need to inspect or modify resources (meta, materials, textures) before fully constructing the scene graph. ```csharp using VRM; using UniGLTF; using UnityEngine; public class Vrm0LowLevelLoader : MonoBehaviour { async void Start() { string path = "/path/to/avatar.vrm"; // 1. Parse the GLB binary using GltfData gltfData = new AutoGltfFileParser(path).Parse(); // 2. Interpret the VRM extension var vrmData = new VRMData(gltfData); // 3. Create the import context (wraps glTF loader with VRM-specific steps) using var ctx = new VRMImporterContext(vrmData); // 4. Read meta without building the full hierarchy var awaitCaller = new RuntimeOnlyAwaitCaller(); VRMMetaObject meta = await ctx.ReadMetaAsync(awaitCaller, createThumbnail: true); Debug.Log($"Model: {meta.Title} | Author: {meta.Author} | Version: {meta.Version}"); Debug.Log($"Commercial use: {meta.CommercialUssage} | License: {meta.LicenseType}"); // 5. Build the complete hierarchy RuntimeGltfInstance instance = await ctx.LoadAsync(awaitCaller); instance.ShowMeshes(); // Access importer outputs directly Debug.Log($"BlendShape clips: {ctx.BlendShapeAvatar?.Clips?.Count}"); Debug.Log($"Avatar valid: {ctx.HumanoidAvatar?.isValid}"); } } ``` --- ### `VRMBlendShapeProxy` — VRM 0.x facial expression control `VRMBlendShapeProxy` is a MonoBehaviour attached to every imported VRM 0.x model. It maps preset keys (Happy, Blink, A, I, U, E, O, …) to underlying mesh morph targets and material property changes. ```csharp using VRM; using UnityEngine; public class Vrm0ExpressionController : MonoBehaviour { VRMBlendShapeProxy _proxy; void Start() { _proxy = GetComponent(); } void Update() { // Immediately apply a single expression (overrides previous value for that key) _proxy.ImmediatelySetValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Joy), 1.0f); // Accumulate multiple expressions before applying (for blending) _proxy.AccumulateValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Blink), Mathf.PingPong(Time.time, 1f)); _proxy.AccumulateValue(BlendShapeKey.CreateUnknown("MyCustom"), 0.5f); _proxy.Apply(); // flush accumulated values to mesh in one pass // Read current value float currentJoy = _proxy.GetValue(BlendShapeKey.CreateFromPreset(BlendShapePreset.Joy)); // Enumerate all registered expressions foreach (var (key, value) in _proxy.GetValues()) Debug.Log($"{key} = {value}"); } } ``` --- ### `Vrm10RuntimeExpression` — VRM 1.0 facial expression control Accessed via `Vrm10Instance.Runtime.Expression`. Validates and merges expression weights according to the VRM 1.0 override rules (e.g., a LookAt expression overrides manual Blink weights). ```csharp using UniVRM10; using UnityEngine; public class Vrm10ExpressionController : MonoBehaviour { Vrm10Instance _vrm; void Start() => _vrm = GetComponent(); void Update() { var expr = _vrm.Runtime.Expression; // Set a single preset expression weight [0..1] expr.SetWeight(ExpressionKey.Happy, Mathf.Abs(Mathf.Sin(Time.time))); expr.SetWeight(ExpressionKey.Blink, Mathf.PingPong(Time.time * 2f, 1f)); // Lip sync: set phoneme weights expr.SetWeight(ExpressionKey.Aa, 0.8f); expr.SetWeight(ExpressionKey.Ih, 0.0f); expr.SetWeight(ExpressionKey.Ou, 0.0f); // Custom expressions by name expr.SetWeight(ExpressionKey.CreateCustom("WinkLeft"), 1.0f); // Batch update without per-key allocation var weights = new Dictionary { { ExpressionKey.Angry, 0.6f }, { ExpressionKey.Sad, 0.3f }, }; expr.SetWeightsNonAlloc(weights); // Query actual (post-validation) weights float actualHappy = expr.ActualWeights[ExpressionKey.Happy]; float blinkOverride = expr.BlinkOverrideRate; // 0..1, indicates how much LookAt suppresses blink } } ``` --- ### `Vrm10RuntimeLookAt` — VRM 1.0 eye gaze control Accessed via `Vrm10Instance.Runtime.LookAt`. Supports two modes: pointing at a world-space position, or supplying explicit yaw/pitch angles. Automatically handles bone-based vs. expression-based eye direction according to the model's configuration. ```csharp using UniVRM10; using UnityEngine; public class Vrm10GazeController : MonoBehaviour { Vrm10Instance _vrm; Transform _gazeTarget; // e.g. camera or player head void Start() { _vrm = GetComponent(); _gazeTarget = Camera.main.transform; } void Update() { var lookAt = _vrm.Runtime.LookAt; // --- Mode A: track a world-space target --- var (yaw, pitch) = lookAt.CalculateYawPitchFromLookAtPosition(_gazeTarget.position); lookAt.SetYawPitchManually(yaw, pitch); // --- Mode B: manual yaw/pitch control (degrees) --- // lookAt.SetYawPitchManually(yawDeg: 15f, pitchDeg: -5f); // Read current computed eye direction Debug.Log($"Eye yaw={lookAt.Yaw:F1}° pitch={lookAt.Pitch:F1}°"); // Alternatively set via Vrm10Instance (drives from Transform every frame): // _vrm.LookAtTargetType = VRM10ObjectLookAt.LookAtTargetTypes.SpecifiedTransform; // _vrm.LookAtTarget = _gazeTarget; } } ``` --- ### `VRMFirstPerson.Setup` — VRM 0.x first-person layer setup Splits renderers between `VRMFirstPersonOnly` and `VRMThirdPersonOnly` Unity layers. Call `Setup()` once after loading to make the avatar headless from the HMD camera's perspective. ```csharp using VRM; using UnityEngine; public class Vrm0FirstPersonSetup : MonoBehaviour { async void Start() { var instance = await VrmUtility.LoadAsync("/path/to/avatar.vrm", awaitCaller: new UniGLTF.RuntimeOnlyAwaitCaller()); instance.ShowMeshes(); var firstPerson = instance.gameObject.GetComponent(); // Standard setup: creates a headless mesh for the HMD camera firstPerson.Setup( isSelf: true, // true = player avatar (make head invisible to self-cam) setVisibility: VRMFirstPerson.SetVisibility // default layer-based visibility ); // Configure Unity cameras // HMD camera: cullingMask includes VRMFirstPersonOnly (layer 9), excludes VRMThirdPersonOnly (10) // Mirror/stream camera: cullingMask excludes VRMFirstPersonOnly, includes VRMThirdPersonOnly Camera.main.cullingMask |= (1 << VRMFirstPerson.FIRSTPERSON_ONLY_LAYER); Camera.main.cullingMask &= ~(1 << VRMFirstPerson.THIRDPERSON_ONLY_LAYER); } } ``` --- ### `VRMExporter.Export` — Export a VRM 0.x GameObject to bytes Serializes a live Unity GameObject (with `VRMMeta`, `VRMBlendShapeProxy`, `VRMFirstPerson`, `VRMSpringBone`, and `Animator` components) back to a `.vrm` binary. ```csharp using VRM; using UniGLTF; using System.IO; using UnityEngine; public class Vrm0Exporter : MonoBehaviour { public GameObject avatarRoot; // Must have VRM components attached [ContextMenu("Export VRM 0.x")] void ExportVrm() { // Normalize the skeleton to T-pose for spec compliance VRMBoneNormalizer.Execute(avatarRoot, forceTPose: false, bakeBlendShape: false); var settings = new GltfExportSettings { InverseAxis = Axes.Z // VRM 0.x specification requires Z-inverse }; ExportingGltfData data = VRMExporter.Export( settings, avatarRoot, new RuntimeTextureSerializer() ); byte[] bytes = data.ToGlbBytes(); string outPath = Path.Combine(Application.persistentDataPath, "export.vrm"); File.WriteAllBytes(outPath, bytes); Debug.Log($"Exported {bytes.Length} bytes to {outPath}"); } } ``` --- ### `Vrm10Instance.Runtime.SpringBone` — VRM 1.0 spring-bone physics control `Vrm10Instance.Runtime.SpringBone` is an `IVrm10SpringBoneRuntime`. Use it to apply external forces (wind, impact), pause simulation, reset to the default pose, or fully reconstruct the spring system after changing the hierarchy. ```csharp using UniVRM10; using UniGLTF.SpringBoneJobs.Blittables; using UnityEngine; public class Vrm10SpringBoneController : MonoBehaviour { Vrm10Instance _vrm; void Start() => _vrm = GetComponent(); void Update() { var sb = _vrm.Runtime.SpringBone; // Apply a per-model-level setting (wind, pause, scaling support) sb.SetModelLevel(_vrm.transform, new BlittableModelLevel( externalForce: new Vector3( Mathf.Sin(Time.time) * 0.5f, // oscillating wind X 0f, 0f ), stopSpringBoneWriteback: false, // false = running supportsScalingAtRuntime: true )); // Reset joints to their initial T-pose positions (e.g. after teleport) if (Input.GetKeyDown(KeyCode.R)) sb.RestoreInitialTransform(); // Fully rebuild spring-bone buffers (needed after runtime bone changes) if (Input.GetKeyDown(KeyCode.B)) sb.ReconstructSpringBone(); } } ``` --- ### `Vrm10.LoadGltfDataAsync` — Advanced VRM 1.0 loading from pre-parsed GltfData For situations where the GLB data has already been parsed (e.g., shared with other loaders, or loaded from a custom storage backend), this overload skips the binary-parsing step. ```csharp using UniVRM10; using UniGLTF; using UnityEngine; public class Vrm10AdvancedLoader : MonoBehaviour { async void Start() { // 1. Parse once, reuse for multiple purposes using GltfData gltfData = new GlbLowLevelParser("/path/to/avatar.vrm", System.IO.File.ReadAllBytes("/path/to/avatar.vrm")).Parse(); // 2. Inspect raw glTF JSON before loading Debug.Log($"glTF version: {gltfData.GLTF.asset.version}"); Debug.Log($"Mesh count: {gltfData.GLTF.meshes.Count}"); // 3. Load as VRM 1.0 (with 0.x migration fallback) Vrm10Instance instance = await Vrm10.LoadGltfDataAsync( gltfData, canLoadVrm0X: true, controlRigGenerationOption: ControlRigGenerationOption.Generate, showMeshes: true, awaitCaller: new RuntimeOnlyAwaitCaller() ); // Access meta VRM10ObjectMeta meta = instance.Vrm.Meta; Debug.Log($"Name: {meta.Name}"); Debug.Log($"Authors: {string.Join(", ", meta.Authors)}"); Debug.Log($"Commercial usage: {meta.CommercialUsage}"); Debug.Log($"Modification: {meta.Modification}"); } } ``` --- ### `Vrm10PoseLoader.LoadVrmAnimationPose` — Apply a VRMA / JSON pose snapshot Loads a `.vrma` (VRM Animation) file or a JSON pose clipboard string and returns a `Vrm10AnimationInstance` that can be assigned to `Vrm10Runtime.VrmAnimation` for single-frame or animated retargeting. ```csharp using UniVRM10; using UniGLTF; using UnityEngine; public class Vrm10PoseApplier : MonoBehaviour { Vrm10Instance _vrm; void Start() => _vrm = GetComponent(); // Load a .vrma animation file and assign it for retargeting async void LoadVrmaFile(string vrmaPath) { using GltfData data = new AutoGltfFileParser(vrmaPath).Parse(); var vrmaData = new VrmAnimationData(data); using var loader = new VrmAnimationImporter(vrmaData); var animInstance = await loader.LoadAsync(new ImmediateCaller()); animInstance.GetComponent().Play(); // Assign to runtime: Vrm10Runtime.Process() retargets every frame _vrm.Runtime.VrmAnimation = animInstance.GetComponent(); } // Paste a JSON pose from clipboard (UNIVRM_pose format) async void PastePoseFromClipboard() { string json = GUIUtility.systemCopyBuffer; if (string.IsNullOrEmpty(json)) return; try { var poseInstance = await Vrm10PoseLoader.LoadVrmAnimationPose(json); _vrm.Runtime.VrmAnimation = poseInstance; } catch (UniJSON.ParserException ex) { Debug.LogWarning($"Invalid pose JSON: {ex.Message}"); } } } ``` --- ### `VRM10ObjectMeta` — Read VRM 1.0 avatar metadata `VRM10ObjectMeta` is the serializable data class holding the avatar's identity and license fields. Accessible on a loaded `Vrm10Instance` via `instance.Vrm.Meta`. ```csharp using UniVRM10; using UniGLTF.Extensions.VRMC_vrm; using UnityEngine; public class Vrm10MetaReader : MonoBehaviour { void DisplayMeta(Vrm10Instance instance) { VRM10ObjectMeta meta = instance.Vrm.Meta; // Identity Debug.Log($"Name: {meta.Name}"); Debug.Log($"Version: {meta.Version}"); Debug.Log($"Authors: {string.Join(", ", meta.Authors)}"); Debug.Log($"Copyright: {meta.CopyrightInformation}"); Debug.Log($"Contact: {meta.ContactInformation}"); // Thumbnail texture (Texture2D, can be shown in UI) if (meta.Thumbnail != null) FindObjectOfType().texture = meta.Thumbnail; // Usage permissions Debug.Log($"Avatar permission: {meta.AvatarPermission}"); // OnlyAuthor / OnlySeparatelyLicensedPerson / Everyone Debug.Log($"Commercial use: {meta.CommercialUsage}"); // PersonalNonProfit / Corporation / etc. Debug.Log($"Violent usage allowed: {meta.ViolentUsage}"); Debug.Log($"Sexual usage allowed: {meta.SexualUsage}"); // Distribution license Debug.Log($"Credit notation required: {meta.CreditNotation}"); // Required / Unnecessary Debug.Log($"Redistribution allowed: {meta.Redistribution}"); Debug.Log($"Modification: {meta.Modification}"); // Prohibited / AllowModification / AllowModificationRedistribution Debug.Log($"Other license URL: {meta.OtherLicenseUrl}"); // Validate meta completeness (returns IEnumerable) foreach (var error in meta.Validate()) Debug.LogError($"Meta validation error: {error}"); } } ``` --- ## Summary UniVRM's two main use cases are **real-time avatar applications** and **VRM creation/conversion tools**. For real-time use (VRChat-style social VR, VTubing, digital human apps), the typical integration pattern is: load with `Vrm10.LoadPathAsync` or `Vrm10.LoadBytesAsync`, assign a `LookAtTarget`, then drive `Runtime.Expression.SetWeight(...)` and `Runtime.LookAt.SetYawPitchManually(...)` every frame inside `Update` or `LateUpdate`. Spring-bone physics run automatically via `Vrm10Instance`'s `DefaultExecutionOrder(11000)` `LateUpdate`. The `Vrm10FastSpringboneRuntime` (singleton, Job-system) is preferred for scenes with many avatars; `Vrm10FastSpringboneRuntimeStandalone` suits single-avatar use cases. For **tooling and pipeline integration** (avatar editors, converters, upload flows), use the low-level `VRMImporterContext` / `VRMExporter` APIs to read, modify, and re-serialize VRM 0.x data, or `Vrm10Importer` / `Vrm10Exporter` for VRM 1.0. The `canLoadVrm0X: true` flag in the VRM 1.0 loader makes it straightforward to accept both spec versions transparently, auto-migrating older files to the VRM 1.0 runtime. Custom material pipelines can be injected via `IMaterialDescriptorGenerator`, and custom texture loaders via `ITextureDeserializer`, making the SDK suitable for embedding in larger asset management systems.