glTF (model format)

glTF model from Sketchfab
glTF model
glTF model from Sketchfab

1. Introduction

glTF is an efficient, modern format for animated 3D and 2D models. Developed by Khronos.

We advise using it in CGE as much as you can. It is supported by a lot of tools. We focus on supporting this format perfectly in Castle Game Engine, with all the features and efficiency.

3. Supported Features

  • Meshes (polygons, lines), transformation hierarchy.

  • Materials (with physically-based or unlit shading, internally implemented using X3D 4 materials, designed for glTF compatibility), alpha mode, double-sidedness, per-vertex colors.

  • Texturing (for base color, normal maps, emissive, material-roughness).

  • Animating transformations (position, rotation, scale) and using skin ("armature" in Blender). Animations can be played using TCastleScene.AutoAnimation or TCastleScene.PlayAnimation. You can also play multiple animations from one model simultaneously using TTimeSensorNode.Start/Stop.

  • Cameras (perspective and orthogonal).

  • Camera transformations can be animated too. The advised approach to do this is to place camera as a child of animated bone, exposed using TCastleScene.ExposeTransforms. See Expose transforms manual. See also Viewports, cameras and navigation in Castle Game Engine movie for a demonstration.

  • Punctual lights (point, spot, directional lights).

  • Both .glb and .gltf extensions are supported. Textures can be provided in separate files or embedded inside the GLTF stream.

  • It is integrated in our engine as X3D nodes graph. This means that you can include a glTF model inside larger X3D file using the Inline node, you can modify the glTF scene graph at runtime (e.g. modify material color, show/hide something etc.) and you can serialize the scene graph to an X3D file.

  • Collisions automatically work (as with all other scene formats), you only have to initialize Scene.PreciseCollisions (see the manual). By default, static meshes have precise collisions (treating them like a set of triangles), while skinned-animated meshes collide as simple bounding boxes (so they don’t cause a slowdown when animating). This can be even customized per-shape by adjusting Shape.collision property.

  • We use linear color space (gamma correction) automatically on PBR materials. You can request to apply it on all materials (including unlit) to follow glTF spec in 100% easily.

  • We read glTF "extras" data that can be defined by your authoring tool, for example in Blender this is defined by "Custom properties". This allows to pass any custom information from Blender to the engine, for use in your own applications, or to influence the import — see Custom properties in Blender.

  • We use PasGLTF, a great open-source library for reading glTF by Benjamin "Bero" Rosseaux.

  • We support common glTF extensions:

TODO: Main missing glTF feature is morph targets. It is coming!

4. Attaching objects to bones

This is available using TCastleScene.ExposeTransforms. You can "expose" a bone transformation as TCastleTransform child and attach there a scene. See expose transforms manual.

5. Collisions when your glTF mesh uses skinned animation

For speed, the shapes animated using skinned animation in glTF uses bounding box for collisions. That’s because the triangles would change every frame and updating the octree would have a significant cost for FPS.

If you need to have better collision detection:

  1. You can use X3D file that uses Inline to include 2 glTF files. One of them would be your animated model, but not collidable. The other would be a static model, invisible, used only for collisions.

    This means that your model keeps working fast (as opposed to solution 2 below). And the collisions are resolved with respect to precise triangles. However, the triangles remain static, unaffected by animation.

    To do this you would create a file like mycreature.x3dv with content:

     #X3D V3.2 utf8
     PROFILE Interchange
    
     Collision {
       proxy Inline { url "mycreature-collidable-invisible-notanimated.gltf" }
       children Inline { url "mycreature-animated-visible-notcollidable.gltf" }
     }

    And then in game, you open castle-data:/mycreature.x3dv instead of opening any glTF file directly. Playing all animations on mycreature.x3dv should work exactly as in mycreature-animated-visible-notcollidable.gltf, it exposes the same animations.

  2. If you desperately need precise collisions, and the collision structure has to be updated at runtime, and you can tolerate some performance loss (it may be acceptable for smaller models) then you can find TShapeNode occurrences in the model, and change the TShapeNode.Collision from scBox to scDefault.

    Like this:

     procedure TMyView.Load;
     var
       Model: TX3DRootNode;
     begin
       Model := LoadNode('castle-data:/example.gltf');
       Model.EnumarateNodes(TShapeNode, @HandleNode, false);
       Scene.Load(Model, true);
     end;
    
     procedure TMyView.HandleNode(Node: TX3DNode);
     begin
       (Node as TShapeNode).Collision := scDefault;
     end;

6. Switching to Phong lighting model (for performance or just different look)

glTF models use PhysicalMaterial or UnlitMaterial for their materials.

The PhysicalMaterial node performs physically-based rendering which is very pretty but also comes with some performance cost. It also requires Phong shading (not faster Gouraud shading) to make sense.

If you need maximum speed, you can set global GltfForcePhongMaterials to true. This automatically converts (during load) all PhysicalMaterial nodes into Material nodes (using Phong lighting model, and Gouraud shading by default). Note that it will change the look of your models significantly. So if you want to go this way, you should probably prepare your models from the start testing it.

Of course, remember that you can also use unlit materials in glTF. These always have the best performance :) Blender can export glTF unlit materials.

7. Gamma Correction

The PhysicalMaterial, used by most glTF models, is calculated in the linear space (correct calculation; alternatively you can say "gamma correction is on") by default. This means that the global ColorSpace variable is by default set to csLinearWhenPhysicalMaterial.

  • If you need maximum speed, consider disabling gamma correction, by ColorSpace := csSRGB.

  • If you need maximum glTF compatibility, consider enabling gamma correction for all materials (PhysicalMaterial, UnlitMaterial, Material), by ColorSpace := csLinear.

Note that enabling or disabling gamma correction will change the look of your game. So you should make a decision about it early on — and test your models look with the desired setting.

8. Interoperability with X3D

Under the hood, glTF models are loaded as a graph of X3D nodes. This allows to use all X3D nodes functionality with glTF models.

For an introduction to X3D, Physically-Based Rendering (PBR) and glTF see this presentation:

8.1. Process glTF models (e.g. change material color) using Pascal

You can process (modify after loading, e.g. to remove/change some nodes) loaded glTF models using CGE API to handle X3D nodes.

For example glTF material corresponds to the X3D node TAppearanceNode (that in turn has TPhysicalMaterialNode as a child). To modify color of an object loaded from glTF, you can find the TAppearanceNode within the scene, get the TPhysicalMaterialNode child, and modify the properties like TPhysicalMaterialNode.BaseColor as you wish. Like this:

var
  Appearance: TAppearanceNode;
  Material: TPhysicalMaterialNode;
begin
  { We assume that model has "MyMaterialName" material in Blender and glTF. }
  Appearance := MyScene.Node('MyMaterialName') as TAppearanceNode;
  { We assume that material type is PBR, so we can cast to TPhysicalMaterialNode. }
  Material := Appearance.Material as TPhysicalMaterialNode;
  { Set color to yellow. }
  Material.BaseColor := Vector3(1, 1, 0);
end;

See https://github.com/michaliskambi/x3d-tests/wiki/Converting-glTF-to-X3D for details how glTF concepts map to X3D.

8.2. Use Inline to import glTF model in an X3D file

You can use Inline X3D node to include glTF model (or part of it) inside a larger X3D model, as many times as you want. This is as simple as just writing

Inline {
  url "some_model.gltf"
}

inside X3D file (with classic X3D encoding).

Moreover, you can use use X3D IMPORT mechanism to access particular parts (like materials or animations or objects) of the glTF model. This way you can control glTF animations from X3D, or reuse glTF materials/objects etc. in X3D.

8.3. Use Inline and IMPORT to access glTF animations in an X3D file

For example of this technique see https://github.com/castle-engine/demo-models/tree/master/blender/skinned_animation , in particular file skinned_anim_run_animations_from_x3d.x3dv there. The X3D file has this code:

DEF InlinedAnimations Inline {
  url "skinned_anim.glb"
}
IMPORT InlinedAnimations.jump AS jump
IMPORT InlinedAnimations.walk AS walk

and then it can start animations jump, walk. They are just TimeSensor nodes in X3D. We can ROUTE events to them.

8.4. Use Inline and IMPORT to access glTF materials, meshes, transforms in an X3D file

Demo of this technique is in https://github.com/michaliskambi/x3d-tests/tree/master/gltf/avocado_and_exports . Open the models there with Castle Model Viewer or just load them into TCastleScene instance in CGE editor.

It looks like this:

InlinedAvocado Inline {
  url "glTF/Avocado.gltf"
}
IMPORT InlinedAvocado.CastleEncoded_2256_Avocado_d AS AvocadoAppearance

Shape {
  appearance USE AvocadoAppearance
  geometry IndexedFaceSet { ... }
}

You can also hide the inlined glTF model (if you only want to extract subset of it). To do this, place Inline in X3D inside Switch (Switch by default hides the model; you could make it visible by switching Switch.whichChoice). Like this:

Switch {
  children DEF InlinedAvocado Inline {
    url "glTF/Avocado.gltf"
  }
}
IMPORT InlinedAvocado.CastleEncoded_2256_Avocado_d AS AvocadoAppearance

Shape {
  appearance USE AvocadoAppearance
  geometry IndexedFaceSet { ... }
}

8.5. glTF files may have non-unique names, but we advise to generate them to be unique; eventually we’ll force them unique at loading

glTF format allows non-unique names.

Even things of the same type (like "glTF nodes", which are "Transform nodes in X3D terminology") may have the same name. Moreover, things of different types (like "glTF node" and "glTF material") may have the same name.

This can easily occur in practice, as Blender allows to have non-unique names across things of different types. It is common in Blender to have "Blender mesh" and "Blender object" with the same name, and then export to glTF.

When importing to Castle Game Engine, we make all the names unique, by adding suffixes like _2, _3 to X3D node instances that would otherwise have the same name. We do this for all X3D nodes, which include grouping and transforming nodes, appearances, materials, textures, lights, cameras, animations, and so on.

We do this because:

  • X3D standard requires unique names, and we want our converters (like castle-model-converter) to generate valid X3D files from valid input.

    This makes total sense for X3D. All the node names live in the same namespace, and can be referred to using e.g. EXPORT xxx, ROUTE xxx.yyy…​, USE xxx.

  • Unique names behave more intuitively when searching among nodes from Pascal too. E.g. searching using Pascal API like Scene.Node('MyName').

    While we always encourage to qualify search by node type, like Scene.Node(TGroupingNode, 'MyName'), it was still confusing when MyName could resolve to different nodes depending on type. And Scene.Node(TX3DNode, 'MyName') was still undefined.

    So to have reliable search for node names, you need to have unique names anyway.

Please treat the order in which we add suffixes like _2, _3 undefined. It may change from release to release. If you rely on some name xxx being available, make sure it is unique in your input.

Note
See What to do with node names when importing e.g. glTF? document for more discussion and details.

To improve this documentation just edit this page and create a pull request to cge-www repository.