Skip to content

Latest commit

 

History

261 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VRMKit

VRM loader and VRM renderer

demo demo

For "VRM", please refer to this page.

Features

  • Load VRM file
  • Render VRM models on RealityKit (experimental)
  • Face morphing (blend shape)
  • Bone animation (skin / joint)
  • Physics (spring bone)
  • MToon rendering and custom material shaders
  • Render plain glTF / GLB with animations

Requirements

  • Swift 6.0+
  • iOS 15.0+ / macOS 12.0+ / visionOS 2.0+ / watchOS 8.0+ (experimental)
  • VRMRealityKit: iOS 18.0+ / macOS 15.0+ / visionOS 2.0+

Installation

Swift Package Manager

You can install this package with Swift Package Manager.

Usage

Load VRM

import VRMKit

let loader = VRMLoader()
let vrm = try loader.load(named: "model.vrm")
// let vrm = try loader.load(withUrl: URL(string: "/path/to/model.vrm")!)
// let vrm = try loader.load(withData: data)

// VRM meta data
vrm.meta.title
vrm.meta.author

// model data
vrm.gltf.jsonData.nodes[0].name

// thumbnail
try loader.loadThumbnail(from: vrm)

Render VRM

import RealityKit
import SwiftUI
import VRMRealityKit

struct ContentView: View {
    var body: some View {
        RealityView { content in
            guard let entity = try? VRMEntityLoader(named: "model.vrm").loadEntity() else { return }
            content.add(entity)
        }
    }
}

VRMEntity is an Entity, so it drops into any RealityKit scene, ARView included. Once it is in a scene, skinning, constraints and spring bones update every frame automatically; set isAutomaticUpdateEnabled = false and call update(deltaTime:) to drive the timing yourself. Animation code that must run in a fixed order relative to that update belongs in a System declared with SystemDependency.before(VRMUpdateSystem.self).

VRMSceneKit, the SceneKit renderer, is deprecated. Use VRMRealityKit instead.

Expressions / blend shapes

joy

angry

><

// VRM 1.0
vrmEntity.setExpression(value: 1.0, for: .preset(.happy))
vrmEntity.setExpression(value: 1.0, for: .custom("customExpressionName"))

// VRM 0.x
vrmEntity.setBlendShape(value: 1.0, for: .preset(.joy))
vrmEntity.setBlendShape(value: 1.0, for: .custom("><"))

Bone animation

Humanoid

let neckRotation = simd_quatf(angle: 20 * .pi / 180, axis: SIMD3<Float>(0, 0, 1))
vrmEntity.humanoid.node(for: .neck)?.transform.rotation *= neckRotation

MToon rendering

Details

MToon materials render by default on iOS and macOS. visionOS falls back to Unlit / PBR materials, because RealityKit's CustomMaterial is unavailable there.

vrmEntity.setMToonLightDirection(SIMD3<Float>(0, 0, -1))
vrmEntity.setMToonLightColor(SIMD3<Float>(1, 1, 1))
vrmEntity.setMToonAmbientColor(SIMD3<Float>(0.1, 0.1, 0.1))

Both loaders take a material shader chain: each shader is asked in order, and materials no shader claims render through the built-in Unlit / PBR path.

// The default chain is [MToonShader()]: MToon with outlines.
let noOutlines = try VRMEntityLoader(named: "model.vrm", shaders: [MToonShader(isOutlineEnabled: false)])
let noMToon = try VRMEntityLoader(named: "model.vrm", shaders: [])

// Toon-shade a plain glTF, or a VRM whose materials are not MToon.
// Pass .convertAll(MToonConversionStyle(...)) to tune the conversion.
let converted = try GLTFEntityLoader(withURL: url, shaders: [MToonShader(source: .convertAll)])

// Your own shader joins the same chain.
final class MyShader: GLTFMaterialShader {
    func makeMaterial(for context: GLTFMaterialShaderContext) throws -> GLTFShadedMaterial? {
        // Return nil to pass the material on to the next shader / built-in path,
        // or start from try context.standardMaterial() to adjust the standard result.
        var material = UnlitMaterial()
        if let texture = context.material.pbrMetallicRoughness?.baseColorTexture {
            material.color = .init(texture: try context.materialTexture(withTextureIndex: texture.index))
        }
        return GLTFShadedMaterial(material: material)
    }
}

let custom = try VRMEntityLoader(withData: data, shaders: [MyShader(), MToonShader()])

GLTFShadedMaterial also carries extra render passes (MToon draws its outline as one) and a makeAnimatableState closure that lets VRM expressions animate a custom material. The GLTFMaterialShader documentation comments cover both, along with what a shader may assume about the mesh it draws.

Render glTF / GLB

Details

VRMRealityKit also renders plain glTF assets (.glb and JSON .gltf, including external resources and data URIs).

let entity: GLTFEntity = try GLTFEntityLoader(withURL: url).loadEntity()

entity.animations          // [GLTFAnimation]: index, name, duration
let controller = try entity.playAnimation(at: 0, loops: true)
controller.speed = 2       // a negative speed plays backwards
controller.seek(to: 0.5)
controller.stop()

loadEntity() renders the asset's default scene, and throws when the glTF names none; pick one with loadEntity(withSceneIndex:). A clone(recursive:) copy shares the loaded meshes and materials but not the animation bindings, so load the scene again for a second animatable instance.

Renderer limitations

RealityKit meshes and materials cannot express every part of glTF and MToon. Each case below logs a warning once per affected material.

  • Only triangle primitives are drawn; POINTS and LINES primitives are skipped.
  • COLOR_0 vertex colors are ignored: the mesh buffers this renderer builds carry no vertex-color channel.
  • One UV set and one KHR_texture_transform per material: the first UV-accessed texture decides both for every texture of that material. A glTF load rejects a document that lists KHR_texture_transform in extensionsRequired and needs more than that, instead of drawing it wrong; a VRM load renders the approximation.
  • Tangents for a primitive without TANGENT are averaged from its UV gradients rather than generated with MikkTSpace, which the spec recommends, so a normal map baked against MikkTSpace can differ slightly along UV seams.
  • Blend shapes morph POSITION only, since RealityKit blend shapes have no NORMAL / TANGENT channel.
  • Skinning reads JOINTS_0 / WEIGHTS_0 only, so a vertex is driven by at most four joints; the further sets a glTF may carry are ignored.
  • MToon's renderQueueOffsetNumber is parsed but ignored, because RealityKit has no material-level draw-order hook. (transparentWithZWrite is supported through CustomMaterial.writesDepth.)

ToDo

  • Improve rendering quality
  • Animation support (vrma)
  • VRM editing function

Contributing

Pull requests are welcome. Fork the repository, work on a feature branch, and open a PR :D

Support this project

Donating to help me continue working on this project.

Donate

License

VRMKit is released under the MIT license. See LICENSE for details.

Author

Tatsuya Tanaka

Twitter GitHub

About

VRM loader and VRM renderer (3D model / gltf)

Topics

Resources

Stars

188 stars

Watchers

4 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages