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
<PackageReference Include="H073.HxGLTF" Version="3.0.0" />
<PackageVersion Include="H073.HxGLTF" Version="3.0.0" />
<PackageReference Include="H073.HxGLTF" />
paket add H073.HxGLTF --version 3.0.0
#r "nuget: H073.HxGLTF, 3.0.0"
#:package H073.HxGLTF@3.0.0
#addin nuget:?package=H073.HxGLTF&version=3.0.0
#tool nuget:?package=H073.HxGLTF&version=3.0.0
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]: aMaterialshowsMaterial 'Glass' #2 Blend ext=[KHR_materials_transmission, KHR_materials_volume], aNodeits mesh/children, anAccessorits 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 | Versions 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. |
-
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.