Jump to content

Ultra Engine glTF extensions


Josh

3,823 views

 Share

As I have explained before, I plan for Ultra Engine to use glTF for our main 3D model file format, so that your final game models can be easily loaded back into a modeling program for editing whenever you need. glTF supports a lot of useful features and is widely supported, but there are a few missing pieces of information I need to add into it. Fortunately, this JSON-based file format has a mechanism for extensions that add new features and data to the format. In this article I will describe the custom extensions I am adding for Ultra Engine.

ULTRA_triangle_quads

All glTF models are triangle meshes, but we want to support quad meshes primarily because its better for tessellation. This extension gets added to the primitives block. If the "quads" value is set to true, this indicates that the triangle indices are stored in a manner such that the first four indices of every six indices form a quad:

"extensions": {
	"ULTRA_triangle_quads": {
		"quads": true
	}
}

There is no other glTF extension for quads, and so there is no way to export a glTF quad mesh from any modeling program. To get quad meshes into Ultra Engine you can load an OBJ file and then resave it as glTF. Here is a glTF file using quads that was created this way. You can see the tessellation creates an even distribution of polygons:

image.thumb.jpeg.acf42a08c98f5f6756cbdbcdb9ae241d.jpeg

For comparison, here is the same mesh saved as triangles and tessellated. The long thin triangles result in a very uneven distribution of polygons. Not good!

image.thumb.jpeg.1d7faa75c8904d7dc3d0fe07b5d7c980.jpeg

The mesh still stores triangle data so the file can be loaded back into a 3D modeling program without any issues.

Here is another comparison that shows how triangle (on the left) and quads (on the right) tessellate:

image.thumb.jpeg.329f645da6ffbff01d7f46d12725c64f.jpeg

ULTRA_material_displacement

This extension adds displacement maps to glTF materials, in a manner that is consistent with how other textures are stored:

"ULTRA_material_displacement": {
	"displacementTexture": {
		"index": 3,
		"offset": -0.035,
		"strength": 0.05
	}
}

The extension indicates a texture index, a maximum displacement value in meters, and a uniform offset, also in meters. This can be used to store material displacement data for tessellation or parallax mapping. Here is a model loaded straight from a glTF file with displacement info and tessellation:

image.thumb.jpeg.73a2611126ec5c4a5a02f2be80534466.jpeg

 

If the file is loaded in other programs, the displacement info will just be skipped.

ULTRA_vertex_displacement

Our game engine uses a per-vertex displacement factor to control how displacement maps affect geometry. This extension adds an extra attribute into the primitives structure to store these values:

"primitives": [
	{
		"attributes": {
			"NORMAL": 1,
			"POSITION": 0,
			"TEXCOORD_0": 2
		},
		"indices": 3,
		"material": 0,
		"mode": 4,
		"extensions": {
			"ULTRA_vertex_displacement": {
				"DISPLACEMENT": 7
			}
		}
	}
}

This can be used to prevent cracks from appearing  at texcoord seams.

image.thumb.jpeg.347d6964d77e24f8c293281cfcfc3d5b.jpeg

Here you can see the displacement value being loaded back from a glTF file it has been saved into. I'm using the vertex color to visually verify that it's working right:

image.thumb.jpeg.1cf298d343ee8ec919c91bd14c80cfd4.jpeg

ULTRA_extended_material

This extension adds other custom parameters that Ultra Engine uses. glTF handles almost everything we want to do, and there are just a few settings to add. Since the Ultra Engine material format is JSON-based, it's easy to just insert the extra parameters into the glTF like so:

"ULTRA_extended_material": {
	"shaderFamily": "PBR",
	"shadow": true,
	"tessellation": true
}

In reality I do not feel that this extension is very well-defined and do not expect it to see any adoption outside of Ultra Engine. I made the displacement parameters a separate extension because they are well-defined, and there might be an opportunity to work with other application developers using that extension.

Here we can see the per-material shadow property is disabled when it is loaded from the glTF:

image.thumb.jpeg.7817537f85a7240cb4c4df0acda86aa9.jpeg

For comparison, here is what the default setting looks like:

image.thumb.jpeg.4f28fa2932570cc0eca7c39a95a28132.jpeg

These extensions are simply meant to add special information that Ultra Engine uses into the glTF format. I do not currently have plans to try to get other developers to adopt my standards. I just want to add the extra information that our game engine needs, while also ensuring compatibility with the rest of the glTF ecosystem. If you are writing a glTF importer or exporter and would like to work together to improve the compatibility of our applications, let me know!

I used the Rock Moss Set 02 model pack from Polyhaven in this article.

 Share

7 Comments


Recommended Comments

Does Vulkan render quads or are they divided into triangles when rendering and does this triangulation take GPU time?

Link to comment
1 minute ago, Canardia said:

Does Vulkan render quads or are they divided into triangles when rendering and does this triangulation take GPU time?

Vulkan accepts quads and ngons but in wireframe they will appear like triangles.

Link to comment

This is the code I used to make the tessellation comparison screenshot:
 

#include "UltraEngine.h"
#include "ComponentSystem.h"

using namespace UltraEngine;

int main(int argc, const char* argv[])
{
    //Get the displays
    auto displays = GetDisplays();

    //Create a window
    auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR);

    //Create a world
    auto world = CreateWorld();

    //Create a framebuffer
    auto framebuffer = CreateFramebuffer(window);

    //Create a camera
    auto camera = CreateCamera(world);
    camera->SetClearColor(0.125);
    camera->SetFov(70);
    camera->SetPosition(0, 1.5, 0);
    camera->SetRotation(90, 0, 0);
    camera->SetWireframe(true);

    world->SetAmbientLight(1);

    //Create a box
    auto p1 = CreatePlane(world, 1, 1, 1, 1, MESH_TRIANGLES);
    p1->SetPosition(-0.75, 0, 0);

    auto p2 = CreatePlane(world, 1, 1, 1, 1, MESH_QUADS);
    p2->SetPosition(0.75, 0, 0);

    auto mtl = CreateMaterial();
    mtl->SetTessellation(true);
    p1->SetMaterial(mtl);
    p2->SetMaterial(mtl);
    camera->SetTessellation(20);

    //Main loop
    while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false)
    {
        if (window->KeyDown(KEY_UP)) camera->Move(0, 0, 0.01);
        if (window->KeyDown(KEY_DOWN)) camera->Move(0, 0, -0.01);

        world->Update();
        world->Render(framebuffer);
    }
    return 0;
}

 

Link to comment

A lot of the objects in glTF have an "extras" property in the spec, so for things that you have no intention of usage outside your own app, that's the place to put that information. So the extended properties extension mentioned above is axed.

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...