H073.HxGLTF 3.0.0

Prefix Reserved
dotnet add package H073.HxGLTF --version 3.0.0
                    
NuGet\Install-Package H073.HxGLTF -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="H073.HxGLTF" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="H073.HxGLTF" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="H073.HxGLTF" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add H073.HxGLTF --version 3.0.0
                    
#r "nuget: H073.HxGLTF, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package H073.HxGLTF@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=H073.HxGLTF&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=H073.HxGLTF&version=3.0.0
                    
Install as a Cake Tool

HxGLTF

Pure C# loader for glTF 2.0 and GLB: no graphics dependencies, a spec-shaped object model, typed support for every extension in the Khronos registry, a built-in meshopt decoder and a load report that tells you exactly what happened.

Targets .NET 8 / .NET 10. MIT.

dotnet add package H073.HxGLTF

Getting started

1. Load a file

using HxGLTF;
using HxGLTF.Core;

GLTFFile gltf = GLTFReader.Read("Assets/robot.glb"); // .glb or .gltf; the extension may be omitted

The examples below assume using HxGLTF; using HxGLTF.Core; using HxGLTF.Core.Extensions.Khr; (plus .Ext, .Vendor, .Mpeg where those extensions are used).

Other entry points:

gltf = GLTFReader.Read(glbBytes, basePath: "Assets/"); // GLB or JSON from memory
gltf = GLTFReader.Parse(jsonString, binaryChunk: null, basePath: "Assets/"); // raw glTF JSON
gltf = await GLTFReader.LoadAsync("Assets/robot.glb", // async, with progress + cancellation
           progress: new Progress<GLTFLoadProgress>(p => Console.WriteLine(p)),
           cancellationToken: ct);
GLTFFile[] many = await GLTFReader.LoadManyAsync(paths, maxParallelism: 4);

Relative URIs (external .bin buffers, images) resolve against the file's directory (or basePath).

2. Check the report

Loading never silently drops anything. Everything the reader noticed is in gltf.Report:

Console.WriteLine(gltf.Report);
// Load report: clean
//   or, when something was noticed:
// Load report: 0 errors, 2 warnings, 1 infos
//   WARNING INVALID_INDEX @ /nodes/3/extensions/KHR_lights_punctual/light: Light index 5 out of range ...
//   WARNING UNSUPPORTED_EXTENSION @ /extensionsUsed: Extension 'ACME_foo' has no registered parser ...
//   INFO EXTENSION_NEEDS_DECODER @ /meshes/0/primitives/0/extensions/KHR_draco_mesh_compression: ...

if (!gltf.Report.IsClean)
    foreach (LoadMessage m in gltf.Report.Messages) // Severity, Code, Pointer (JSON pointer), Message
        log.Warn($"{m.Code} at {m.Pointer}: {m.Message}");

gltf.Report.UnsupportedExtensions; // used but no parser registered
gltf.Report.UndecodedExtensions; // parsed, but the payload needs a decoder you did not provide (Draco, KTX2, meshopt)

Structural errors (missing asset, invalid GLB header, out-of-range required indices) throw a GLTFException (GLTFParseException, GLTFMissingPropertyException, GLTFInvalidIndexException, GLTFBufferException, GLTFUnsupportedException). Everything recoverable becomes a report message.

3. Options

var options = new GLTFReadOptions
{
    SkipAnimations = false, // don't build Animations[]
    SkipSkins = false, // don't build Skins[]
    StrictExtensionCheck = true, // throw GLTFUnsupportedException if extensionsRequired names something we can't handle
    KeepUnknownExtensions = true, // keep unregistered extensions as UnknownExtension (raw JSON) instead of dropping them
    ReadExtras = true, // capture `extras` on every object (set false to save memory)
    ValidateImageFiles = true, // throw if an external image file is missing
    Extensions = ExtensionRegistry.Default, // which extension parsers to use (see "Custom extensions")
    MeshoptDecoder = MeshoptDecoder.Default, // built-in; set null to keep meshopt views compressed
    DracoDecoder = null, // plug in an IDracoDecoder
    Ktx2Transcoder = null, // plug in an IKtx2Transcoder
};
var gltf = GLTFReader.Read("robot.glb", options);

4. How extension data behaves (read this once)

glTF keeps the core properties of every object in the object itself and everything optional in its extensions member. HxGLTF mirrors that exactly, for every object type:

Material mat = gltf.Materials![0];

mat.BaseColorFactor; // core spec property > a plain field, always there
mat.Extensions.Get<KhrMaterialsTransmission>(); // extension > a typed object, or null when the file doesn't use it

Rules that hold everywhere:

Situation What you get
Extension present in the file a typed object with the parsed values; omitted properties hold the spec defaults (KhrMaterialsIor.Ior == 1.5)
Extension absent Get<T>() returns null, Has<T>() is false: "not used", which is different from "used with default values"
Extension is used but HxGLTF has no parser (or you unregistered it) an UnknownExtension with the raw Json; Report.UnsupportedExtensions lists the name
Extension defines a shared list (lights, variants, XMP packets, physics materials, videos ...) the list is a root extension on gltf.Extensions; the per-object part references into it and is already resolved (node...KhrLightsPunctualNode.Light is the Light object)
Extension needs an external decoder (Draco, KTX2, meshopt without decoder) data is parsed; Report.UndecodedExtensions lists it
Extension is a Khronos draft parsed like any other; one INFO in the report
Extension attached to an object the spec doesn't allow kept as UnknownExtension + WARNING
Extension JSON is invalid typed object with what could be read + ERROR in the report (loading continues)

Reading pattern (identical for every object): Get<T>(), TryGet<T>(out t), Has<T>(), Has("KHR_..."), foreach (var ext in obj.Extensions), and obj.Extras (JsonObject?) for application data. Extension property values are plain fields: same style as the core objects.

The sections below show, for each part of glTF, the basic read and the extension read side by side.

5. Document root and asset

// Basic
Asset a = gltf.Asset; // a.Version, a.Generator, a.Copyright, a.MinVersion
string[] used = gltf.ExtensionsUsed; // gltf.UsesExtension("KHR_lights_punctual"), gltf.RequiresExtension(...)
Scene? scene = gltf.GetDefaultScene();

// Extensions on the root: shared collections other objects reference
Light[] lights = gltf.Extensions.Get<KhrLightsPunctual>()?.Lights ?? []; // KHR_lights_punctual
var variants = gltf.Extensions.Get<KhrMaterialsVariants>()?.Variants; // KHR_materials_variants
var xmp = gltf.Extensions.Get<KhrXmpJsonLd>()?.Packets; // KHR_xmp_json_ld (JsonObject[])
var videos = gltf.Extensions.Get<KhrTextureVideo>()?.Videos; // KHR_texture_video (draft)
var ibl = gltf.Extensions.Get<ExtLightsImageBased>()?.Lights; // EXT_lights_image_based

// Extensions on the asset object
var meta = gltf.Asset.Extensions.Get<KhrXmpJsonLdRef>()?.Packet; // JSON-LD packet describing the asset

6. Scenes and nodes

// Basic
Scene scene = gltf.GetDefaultScene()!;
Node[] roots = scene.Nodes;
foreach (Node node in scene.EnumerateNodes()) // depth-first, whole hierarchy
{
    node.Name; node.Index; node.Parent; node.Children;
    node.Translation; node.Rotation; node.Scale; // or node.Matrix when node.HasMatrix
    Matrix local = node.LocalTransform; // whichever the file used
    Matrix world = node.ComputeWorldTransform();
    node.Mesh; node.Camera; node.Skin; // resolved objects (null when absent), raw indices in MeshIndex etc.
    node.Weights; // morph weights overriding the mesh defaults
}

// Extensions on a scene
var sceneIbl = scene.Extensions.Get<ExtLightsImageBasedScene>()?.Light; // EXT_lights_image_based
var geoStats = scene.Extensions.Get<FbGeometryMetadata>(); // FB_geometry_metadata: VertexCount, SceneBounds

// Extensions on a node
foreach (Node node in gltf.Nodes ?? [])
{
    if (node.Extensions.Get<KhrLightsPunctualNode>() is { Light: { } light }) // KHR_lights_punctual
        PlaceLight(light.Type, light.Color, light.Intensity, light.Range, node.ComputeWorldTransform());

    bool visible = node.Extensions.Get<KhrNodeVisibility>()?.Visible ?? true; // KHR_node_visibility (default true)

    if (node.Extensions.Get<ExtMeshGpuInstancing>() is { } inst) // EXT_mesh_gpu_instancing
        DrawInstanced(node.Mesh!, inst.Translation, inst.Rotation, inst.Scale, inst.InstanceCount);

    if (node.Extensions.Get<MsftLod>() is { } lod) // MSFT_lod
        PickLevel(lod.Nodes, lod.ScreenCoverage);
}

7. Meshes and primitives

// Basic
foreach (Mesh mesh in gltf.Meshes ?? [])
{
    float[]? defaultWeights = mesh.Weights;
    foreach (MeshPrimitive prim in mesh.Primitives)
    {
        PrimitiveMode mode = prim.Mode; // Triangles, Lines, Points, ...
        Accessor pos = prim.GetAttribute("POSITION")!; // POSITION, NORMAL, TANGENT, TEXCOORD_n, COLOR_n, JOINTS_n, WEIGHTS_n
        float[] positions = AccessorReader.ReadData(pos);
        int[]? indices = prim.HasIndices ? AccessorReader.ReadIndices(prim.Indices!) : null;
        Material? material = prim.Material; // null = default material
        Dictionary<string, Accessor>[]? targets = prim.Targets; // morph targets
    }
}

// Extensions on a primitive
var mapping = prim.Extensions.Get<KhrMaterialsVariantsPrimitive>(); // KHR_materials_variants
Material? red = mapping?.GetMaterial(variants!.Find("Red")!) ?? prim.Material;

var draco = prim.Extensions.Get<KhrDracoMeshCompression>(); // KHR_draco_mesh_compression
if (draco is { IsDecoded: false }) { /* no IDracoDecoder registered: prim.Attributes hold the fallback accessors */ }

var outline = prim.Extensions.Get<CesiumPrimitiveOutline>()?.Indices; // CESIUM_primitive_outline: extra line indices
var splats = prim.Extensions.Get<KhrGaussianSplatting>(); // KHR_gaussian_splatting (draft)

Compressed geometry (EXT_meshopt_compression) needs no code on your side: buffer views are decoded during load and AccessorReader reads them like any other data.

8. Materials

// Basic: exactly the glTF 2.0 core material
foreach (Material mat in gltf.Materials ?? [])
{
    Vector4 baseColor = mat.BaseColorFactor; // pass-throughs to mat.PbrMetallicRoughness
    float metallic = mat.MetallicFactor, roughness = mat.RoughnessFactor;
    TextureInfo? baseColorTex = mat.BaseColorTexture;
    TextureInfo? mrTex = mat.MetallicRoughnessTexture;
    NormalTextureInfo? normal = mat.NormalTexture; // + normal.Scale
    OcclusionTextureInfo? ao = mat.OcclusionTexture; // + ao.Strength
    TextureInfo? emissiveTex = mat.EmissiveTexture;
    Vector3 emissive = mat.EmissiveFactor;
    AlphaMode alpha = mat.AlphaMode; // Opaque / Mask (mat.AlphaCutoff) / Blend
    bool doubleSided = mat.DoubleSided;
}

// Extensions: every KHR_materials_* (and vendor material extension) is its own object; absent = null
if (mat.Extensions.TryGet<KhrMaterialsTransmission>(out var tr)) // "glass"
    UseTransmission(tr.TransmissionFactor, tr.TransmissionTexture); // factor default 0, texture may be null

float ior = mat.Extensions.Get<KhrMaterialsIor>()?.Ior ?? 1.5f; // spec default when the extension is absent
float emissiveStrength = mat.Extensions.Get<KhrMaterialsEmissiveStrength>()?.EmissiveStrength ?? 1f;

if (mat.Extensions.Get<KhrMaterialsClearcoat>() is { } cc)
    UseClearcoat(cc.ClearcoatFactor, cc.ClearcoatRoughnessFactor, cc.ClearcoatTexture, cc.ClearcoatNormalTexture);

if (mat.Extensions.Get<KhrMaterialsVolume>() is { } vol) // thickness / attenuation, pairs with transmission
    UseVolume(vol.ThicknessFactor, vol.AttenuationDistance, vol.AttenuationColor);

bool unlit = mat.Extensions.Has<KhrMaterialsUnlit>(); // flag extension, no properties
var sheen = mat.Extensions.Get<KhrMaterialsSheen>();
var specular = mat.Extensions.Get<KhrMaterialsSpecular>();
var iridescence = mat.Extensions.Get<KhrMaterialsIridescence>();
var anisotropy = mat.Extensions.Get<KhrMaterialsAnisotropy>();
var dispersion = mat.Extensions.Get<KhrMaterialsDispersion>();

// Legacy workflow: when present it replaces the metallic-roughness parameters
if (mat.Extensions.Get<KhrMaterialsPbrSpecularGlossiness>() is { } sg)
    UseSpecGloss(sg.DiffuseFactor, sg.DiffuseTexture, sg.SpecularFactor, sg.GlossinessFactor, sg.SpecularGlossinessTexture);

Texture slots inside extensions are the same TextureInfo type as the core slots (with TexCoord and Transform).

9. Textures, images and samplers

// Basic
Texture tex = mat.BaseColorTexture!.Texture; // shared Texture object (mat.BaseColorTexture.TexCoord = which UV set)
Image? img = tex.Source; // may be null when only an extension provides the image
TextureSampler? sampler = tex.Sampler; // null = default (repeat, auto filtering)

if (img != null)
{
    if (img.HasBufferView) bytes = img.EmbeddedBytes; // GLB-embedded, img.MiMeType (sniffed if missing)
    else if (img.IsDataUri) bytes = DecodeDataUri(img.Uri!);
    else path = Path.Combine(gltf.BaseDirectory, img.Uri!);
}
if (sampler != null) { sampler.WrapS; sampler.WrapT; sampler.MinFilter; sampler.MagFilter; sampler.UsesMipmaps; }

// Extension on the texture slot (TextureInfo)
if (mat.BaseColorTexture.Transform is { } t) // KHR_texture_transform: t.Offset, t.Rotation, t.Scale, t.TexCoord
    uvMatrix = t.ToMatrix3();
int uvSet = mat.BaseColorTexture.EffectiveTexCoord; // transform's texCoord override, else the slot's

// Extensions on the texture: alternative image formats
foreach (var (extName, altImage) in tex.AlternateSources) // KHR_texture_basisu, EXT_texture_webp, EXT_texture_avif, MSFT_texture_dds
    if (CanDecode(extName)) { img = altImage; break; }
Ktx2Header? ktx = tex.Extensions.Get<KhrTextureBasisu>()?.Header; // KTX2 size / mips / ETC1S-UASTC / sRGB
var video = tex.Extensions.Get<KhrTextureVideoTexture>(); // KHR_texture_video (draft): Source, Autoplay, Loop

10. Animations

// Basic
foreach (Animation anim in gltf.Animations ?? [])
{
    float duration = anim.ComputeDuration();
    foreach (AnimationChannel ch in anim.Channels)
    {
        AnimationSampler s = ch.Sampler;
        float[] times = AccessorReader.ReadData(s.Input);
        float[] values = AccessorReader.ReadData(s.Output); // CUBICSPLINE: in-tangent, value, out-tangent per key
        InterpolationAlgorithm interp = s.Interpolation;
        Node? target = ch.Target.Node;
        AnimationChannelTargetPath path = ch.Target.Path; // Translation / Rotation / Scale / Weights
    }
}

// Extension on the channel target
if (ch.Target.Path == AnimationChannelTargetPath.Pointer) // KHR_animation_pointer: animate any property
{
    var ptr = ch.Target.Extensions.Get<KhrAnimationPointer>()!;
    ptr.Pointer; // "/materials/0/pbrMetallicRoughness/baseColorFactor"
    ptr.RootCollection; // "materials"      ptr.RootIndex; // 0      ptr.PropertyPath; // "pbrMetallicRoughness/baseColorFactor"
}

11. Skins and cameras

// Basic
foreach (Skin skin in gltf.Skins ?? [])
{
    Node[] joints = skin.Joints;
    Matrix[] ibm = skin.InverseBindMatrices; // identity matrices when the file has none
    Node? skeletonRoot = skin.Skeleton;
}
foreach (Camera cam in gltf.Cameras ?? [])
{
    if (cam.IsPerspective) { var p = cam.Perspective!; p.YFov; p.AspectRatio; p.ZNear; p.ZFar; } // ZFar null = infinite
    else { var o = cam.Orthographic!; o.XMag; o.YMag; o.ZNear; o.ZFar; }
}

// Extensions: none of the Khronos extensions target skins; cameras get e.g.
var viewports = cam.Extensions.Get<MpegViewportRecommended>()?.Viewports; // MPEG_viewport_recommended

12. Buffers, buffer views and accessors

// Basic: you rarely touch these directly; AccessorReader does the work
Accessor acc = gltf.Accessors![0];
acc.Count; acc.StructureType; acc.DataType; acc.Normalized; acc.Min; acc.Max; acc.Sparse;
BufferView? bv = acc.BufferView; // null = all zeros / sparse-only
ReadOnlySpan<byte> raw = bv!.Span; // the bytes of the view
Buffer buf = bv.Buffer; // buf.Bytes (Memory<byte>), buf.Uri, buf.IsLoaded

// Extensions
var meshopt = bv.Extensions.Get<ExtMeshoptCompression>(); // EXT_meshopt_compression: Mode, Filter, Count, IsDecoded, DecodedBuffer
var timed = acc.Extensions.Get<MpegAccessorTimed>(); // MPEG_accessor_timed: Immutable, SuggestedUpdateRate

13. Everything at once

gltf.EnumerateExtendedObjects() yields (pointer, object) for every object in the document that carries an extension: handy to see what a file uses:

foreach (var (pointer, obj) in gltf.EnumerateExtendedObjects())
    Console.WriteLine($"{pointer}: {obj.Extensions}");
// /materials/2: KHR_materials_transmission, KHR_materials_volume, KHR_materials_ior
// /nodes/7: KHR_lights_punctual

Extensions

Class names follow the extension name in PascalCase; namespaces mirror the prefix: HxGLTF.Core.Extensions.Khr (KHR_*), .Ext (EXT_*), .Vendor (ADOBE/AGI/CESIUM/FB/GODOT/GRIFFEL/MSFT/NV), .Mpeg (MPEG_*). Extensions that appear on more than one kind of object have one class per shape, e.g. KhrLightsPunctual (root list) and KhrLightsPunctualNode (node reference).

Extension Category Support Attached to HxGLTF types Spec
EXT_mesh_gpu_instancing Khronos, ratified Full Node ExtMeshGpuInstancing spec
EXT_meshopt_compression Khronos, ratified ParseOnly BufferView, Buffer ExtMeshoptCompression, ExtMeshoptCompressionBuffer spec
EXT_texture_webp Khronos, ratified Full Texture ExtTextureWebp spec
KHR_animation_pointer Khronos, ratified Full AnimationChannelTarget KhrAnimationPointer spec
KHR_draco_mesh_compression Khronos, ratified ParseOnly Primitive KhrDracoMeshCompression spec
KHR_lights_punctual Khronos, ratified Full Root, Node KhrLightsPunctual, KhrLightsPunctualNode spec
KHR_materials_anisotropy Khronos, ratified Full Material KhrMaterialsAnisotropy spec
KHR_materials_clearcoat Khronos, ratified Full Material KhrMaterialsClearcoat spec
KHR_materials_dispersion Khronos, ratified Full Material KhrMaterialsDispersion spec
KHR_materials_emissive_strength Khronos, ratified Full Material KhrMaterialsEmissiveStrength spec
KHR_materials_ior Khronos, ratified Full Material KhrMaterialsIor spec
KHR_materials_iridescence Khronos, ratified Full Material KhrMaterialsIridescence spec
KHR_materials_sheen Khronos, ratified Full Material KhrMaterialsSheen spec
KHR_materials_specular Khronos, ratified Full Material KhrMaterialsSpecular spec
KHR_materials_transmission Khronos, ratified Full Material KhrMaterialsTransmission spec
KHR_materials_unlit Khronos, ratified Full Material KhrMaterialsUnlit spec
KHR_materials_variants Khronos, ratified Full Root, Primitive KhrMaterialsVariants, KhrMaterialsVariantsPrimitive spec
KHR_materials_volume Khronos, ratified Full Material KhrMaterialsVolume spec
KHR_mesh_quantization Khronos, ratified Full : (flag: listed in extensionsUsed only) KhrMeshQuantization spec
KHR_node_visibility Khronos, ratified Full Node KhrNodeVisibility spec
KHR_texture_basisu Khronos, ratified ParseOnly Texture KhrTextureBasisu spec
KHR_texture_transform Khronos, ratified Full TextureInfo KhrTextureTransform spec
KHR_xmp_json_ld Khronos, ratified Full All KhrXmpJsonLd, KhrXmpJsonLdRef spec
EXT_texture_procedurals_mx_1_39 Khronos, in progress Draft : (flag: listed in extensionsUsed only) ExtTextureProceduralsMx139 spec
KHR_accessor_float64 Khronos, in progress Draft : (flag: listed in extensionsUsed only) KhrAccessorFloat64 spec
KHR_audio_emitter Khronos, in progress Draft Root, Scene, Node KhrAudioEmitter, KhrAudioEmitterNode, KhrAudioEmitterScene spec
KHR_audio_graph Khronos, in progress Draft Root, extension objects KhrAudioGraph, KhrAudioGraphAudio, KhrAudioGraphSource spec
KHR_gaussian_splatting Khronos, in progress Draft Primitive KhrGaussianSplatting spec
KHR_implicit_shapes Khronos, in progress Draft Root KhrImplicitShapes spec
KHR_interactivity Khronos, in progress Draft Root KhrInteractivity spec
KHR_materials_diffuse_transmission Khronos, in progress Draft Material KhrMaterialsDiffuseTransmission spec
KHR_materials_subsurface Khronos, in progress Draft Material KhrMaterialsSubsurface spec
KHR_materials_volume_scatter Khronos, in progress Draft Material KhrMaterialsVolumeScatter spec
KHR_node_hoverability Khronos, in progress Draft Node KhrNodeHoverability spec
KHR_node_selectability Khronos, in progress Draft Node KhrNodeSelectability spec
KHR_physics_rigid_bodies Khronos, in progress Draft Root, Node KhrPhysicsRigidBodies, KhrPhysicsRigidBodiesNode spec
KHR_texture_procedurals Khronos, in progress Draft Root, TextureInfo KhrTextureProcedurals, KhrTextureProceduralsTextureInfo spec
KHR_texture_video Khronos, in progress Draft Root, Texture KhrTextureVideo, KhrTextureVideoTexture spec
KHR_materials_pbrSpecularGlossiness Khronos, archived Full Material KhrMaterialsPbrSpecularGlossiness spec
KHR_techniques_webgl Khronos, archived Full Root, Material KhrTechniquesWebgl, KhrTechniquesWebglMaterial spec
KHR_xmp Khronos, archived Full All KhrXmp, KhrXmpRef spec
EXT_lights_ies Multi-vendor Full Root, Node ExtLightsIes, ExtLightsIesNode spec
EXT_lights_image_based Multi-vendor Full Root, Scene ExtLightsImageBased, ExtLightsImageBasedScene spec
EXT_mesh_manifold Multi-vendor Full Mesh ExtMeshManifold spec
EXT_texture_avif Multi-vendor Full Texture ExtTextureAvif spec
ADOBE_materials_clearcoat_specular Vendor Full Material AdobeMaterialsClearcoatSpecular spec
ADOBE_materials_clearcoat_tint Vendor Full Material AdobeMaterialsClearcoatTint spec
ADOBE_materials_thin_transparency Vendor Full Material AdobeMaterialsThinTransparency spec
AGI_articulations Vendor Full Root, Node AgiArticulations, AgiArticulationsNode spec
AGI_stk_metadata Vendor Full Root, Node AgiStkMetadata, AgiStkMetadataNode spec
CESIUM_primitive_outline Vendor Full Primitive CesiumPrimitiveOutline spec
FB_geometry_metadata Vendor Full Scene FbGeometryMetadata spec
GODOT_single_root Vendor Full : (flag: listed in extensionsUsed only) GodotSingleRoot spec
GRIFFEL_bim_data Vendor Full Root, Node GriffelBimData, GriffelBimDataNode spec
MSFT_lod Vendor Full Node, Material MsftLod spec
MSFT_packing_normalRoughnessMetallic Vendor Full Material MsftPackingNormalRoughnessMetallic spec
MSFT_packing_occlusionRoughnessMetallic Vendor Full Material MsftPackingOcclusionRoughnessMetallic spec
MSFT_texture_dds Vendor Full Texture MsftTextureDds spec
NV_materials_mdl Vendor Full Root, Material NvMaterialsMdl, NvMaterialsMdlMaterial spec
MPEG_accessor_timed MPEG-I Full Accessor MpegAccessorTimed spec
MPEG_animation_timing MPEG-I Full Animation MpegAnimationTiming spec
MPEG_audio_spatial MPEG-I Full Root, Scene, Node MpegAudioSpatial spec
MPEG_buffer_circular MPEG-I Full Buffer MpegBufferCircular spec
MPEG_media MPEG-I Full Root MpegMedia spec
MPEG_mesh_linking MPEG-I Full Mesh MpegMeshLinking spec
MPEG_scene_dynamic MPEG-I Full Scene MpegSceneDynamic spec
MPEG_texture_video MPEG-I Full Texture MpegTextureVideo spec
MPEG_viewport_recommended MPEG-I Full Camera MpegViewportRecommended spec

Support: Full = everything in the spec is parsed and usable; ParseOnly = parsed into typed objects, but using the payload needs an external decoder (see below); Draft = the Khronos spec is still in progress (schema may change; loading adds one INFO to the report). Registry source: https://github.com/KhronosGroup/glTF/tree/main/extensions.

At runtime: BuiltInExtensions.Names lists everything; each parser exposes its Support level (Full / ParseOnly / Draft).

Compression and decoders

Extension Built in Plug-in
EXT_meshopt_compression pure C# MeshoptDecoder (ATTRIBUTES / TRIANGLES / INDICES, OCTAHEDRAL / QUATERNION / EXPONENTIAL filters). Compressed buffer views are decoded at load; accessors read them like any other view. IMeshoptDecoder to replace it
KHR_draco_mesh_compression metadata (KhrDracoMeshCompression: buffer view, attribute ids) IDracoDecoder: with one registered, the primitive's accessors are rewired to the decoded geometry
KHR_texture_basisu KTX2 container header (Ktx2Header: size, mip levels, ETC1S/UASTC, sRGB) IKtx2Transcoder: transcode when you upload

Without a decoder the file still loads; the affected extension is listed in gltf.Report.UndecodedExtensions. If such an extension is required by the file and StrictExtensionCheck is on, GLTFUnsupportedException is thrown.

public sealed class MyDraco : IDracoDecoder
{
    public DracoDecodedPrimitive Decode(ReadOnlySpan<byte> data, KhrDracoMeshCompression ext, IReadOnlyDictionary<string, Accessor> templates)
        => /* call your native Draco wrapper, return indices + attributes keyed by glTF semantic */;
}
GLTFReader.Read("draco.glb", new GLTFReadOptions { DracoDecoder = new MyDraco() });

Custom extensions

using System.Text.Json;
using HxGLTF.Core.Extensions;

public sealed class MyTag : IGLTFExtension
{
    public const string Name = "MY_tag";
    public string ExtensionName => Name;
    public string Tag = "";
}

public sealed class MyTagParser : GLTFExtensionParser<MyTag>
{
    public override string ExtensionName => MyTag.Name;
    public override ExtensionTargets Targets => ExtensionTargets.Node | ExtensionTargets.Mesh;

    protected override MyTag Read(ExtensionParseContext ctx, JsonElement json, GLTFProperty owner, ExtensionTargets target)
    {
        var tag = GLTFJson.GetString(json, "tag");
        if (tag == null) ctx.Error("'tag' is required.", LoadCodes.MissingProperty); // lands in gltf.Report with the JSON pointer
        return new MyTag { Tag = tag ?? "" };
    }
}

ExtensionRegistry.Default.Register(new MyTagParser()); // globally
// or per load:
var registry = ExtensionRegistry.CreateDefault().Register(new MyTagParser());
var gltf = GLTFReader.Read("model.glb", new GLTFReadOptions { Extensions = registry });
string? tag = gltf.Nodes![0].Extensions.Get<MyTag>()?.Tag;

ExtensionParseContext gives you bounds-checked reference resolution (ctx.ResolveNode/Mesh/Material/Accessor/...), ctx.ReadTextureInfo(json, "myTexture") (handles KHR_texture_transform), access to already parsed root extensions (ctx.File.Extensions.Get<...>()), and ctx.ParseExtensionsAndExtras(...) for nested glTF-style objects. Parsers run after all root arrays exist (buffer/bufferView extensions run inline during buffer loading), root extensions before per-object ones. GLTFJson has tolerant getters for every JSON type.


Debugging

  • Every type has a [DebuggerDisplay]: a Material shows Material 'Glass' #2 Blend ext=[KHR_materials_transmission, KHR_materials_volume], a Node its mesh/children, an Accessor its type and count.
  • gltf.EnumerateExtendedObjects() yields (pointer, object) for every object carrying extensions.
  • HxDiagnostics.EnableAll() prints per-phase timings, IO paths and sizes (HxDiagnostics.Log = your sink).
  • Report messages carry stable codes (LoadCodes.*) and JSON pointers you can paste into a JSON viewer.

Object model

GLTFFile : GLTFProperty (Extensions, Extras, Report, ExtensionsUsed/Required)
  Asset (Version, Generator, Copyright)
  Scenes[] > Nodes[]
  Nodes[] > Children[], Parent, Mesh, Camera, Skin, Translation/Rotation/Scale | Matrix, Weights
  Meshes[] > Primitives[] > Attributes, Targets, Indices, Material, Mode
  Materials[] > PbrMetallicRoughness, NormalTexture, OcclusionTexture, EmissiveTexture, AlphaMode, DoubleSided
  Textures[] > Source (Image), Sampler, AlternateSources
  Images[] > Uri | BufferView + MiMeType
  Samplers[] > WrapS/WrapT, MinFilter/MagFilter
  Animations[] > Channels[] (Sampler, Target), Samplers[] (Input, Output, Interpolation)
  Skins[] > Joints[], InverseBindMatrices, Skeleton
  Cameras[] > Perspective | Orthographic
  Buffers[] / BufferViews[] / Accessors[] (+ Sparse)

Every object derives from GLTFProperty (Extensions, Extras); root-array objects from GLTFChildOfRoot (Name, Index).

Validation

Validation is a separate package, H073.HxGLTF.Validator: a pure C# port of the Khronos glTF-Validator with identical issue codes, messages and JSON reports.

using HxGLTF.Validator;

ValidationReport report = GLTFValidator.Validate("robot.glb"); // standalone
GLTFFile gltf = GLTFReaderValidation.ReadValidated("robot.glb", out report); // validate and load, issues merged into gltf.Report

See the HxGLTF.Validator README for options, the report format and the reference validator wrapper.

MonoGame

HxGLTF.MonoGame turns a GLTFFile into a ModelKit Scene (meshes, materials, skeletons, animations, lights).

License

MIT: Discord: sameplayer

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on H073.HxGLTF:

Package Downloads
H073.HxGLTF.MonoGame

MonoGame bridge for HxGLTF – loads glTF/GLB into ModelKit scenes.

H073.HxGLTF.MonoGame.WindowsDX

MonoGame WindowsDX bridge for HxGLTF – loads glTF/GLB into ModelKit scenes.

H073.HxGLTF.Validator

Pure C# port of the Khronos glTF-Validator: validates glTF 2.0 / GLB files with the same issue codes, messages and JSON report format as the reference validator, plus a wrapper for the official binary and integration with HxGLTF.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0 134 8/15/2026
2.0.0 187 3/16/2026
1.3.1 132 3/15/2026
1.2.2 285 5/6/2025
1.2.0 411 5/6/2025
1.0.2 322 5/5/2025
1.0.1.3 241 5/4/2025
1.0.1.1 195 5/4/2025
1.0.1 228 9/26/2024