Getting around the single vertex color channel limitation when importing GLTF files

Godot Version

4.6-beta3

Question

Is there a way to add another channel of vertex information to a GTLF import?
I’m building a VJ set with point clouds and and using vertex colors to color the points in a shader.
I also have a second channel of information in the point cloud I would like to import, but currently only one set of vertex colors is supported. (Github Proposal)

Seeing that the model is vertices only, I can’t abuse the UV coordinates and stuff the data in there, as there’s no faces.

Does anyone know of another way to get the data in there? It’s exported when I export the GLTF from Blender, but I can’t find a way to access it in Godot. Here’s a low res sample export: forest.glb (_SPLITS Channel)

If it’s at all possible with a hack I haven’t thought of, any suggestions would be helpful. :wink:

You can abuse additional UV channels supported by gltf. The vertex attribute data should appear as CUSTOMX in Godot’s shaders.

You could write a GLTFDocumentExtension that parses and modifies the ImporterMesh data with whatever data you need inside the ARRAY_CUSTOMn surface arrays.

Quick and dirty example. You should probably make it a plugin instead:

extends Node


func _ready() -> void:
	# Quick way to test the changes without having to re-import the file
	# You should do this in a plugin
	var extension = Extension.new()
	GLTFDocument.register_gltf_document_extension(extension)
	
	# Create and load the gltf file
	var doc = GLTFDocument.new()
	var state = GLTFState.new()
	doc.append_from_file("res://assets/forest.glb", state)
	
	# Generate the Godot scene and add it
	var node = doc.generate_scene(state)
	add_child(node)
	
	# Unregister the extension once it's not needed
	GLTFDocument.unregister_gltf_document_extension(extension)


class Extension extends GLTFDocumentExtension:


	func _import_node(state: GLTFState, _gltf_node: GLTFNode, _json: Dictionary, node: Node) -> Error:
		if node is ImporterMeshInstance3D:
			# Get the mesh and the first surface array.
			var mesh = node.mesh as ImporterMesh
			var array = mesh.get_surface_arrays(0)

			# Quick and DIRTY way to get the "_SPLITS" accessor.
			var split_accessor = int(state.json.get("meshes", [])[0].get("primitives", [])[0].get("attributes", {}).get("_SPLITS", 0))
			var accessor = state.get_accessors()[split_accessor]

			if accessor.accessor_type == GLTFAccessor.TYPE_SCALAR:
				# Get the buffer view
				var buffer_view = state.get_buffer_views()[accessor.buffer_view]
				# Create a StreamPeerBuffer to convert the buffer more easily
				var stream_buffer = StreamPeerBuffer.new()
				# Load the buffer view data from the state
				stream_buffer.data_array = buffer_view.load_buffer_view_data(state)
				# Fill the buffer
				var buffer = PackedFloat32Array()
				while stream_buffer.get_available_bytes() > 0:
					buffer.append(stream_buffer.get_float())
				# Set the buffefr to the array index ARRAY_CUSTOM0
				array[Mesh.ARRAY_CUSTOM0] = buffer

			# Get the info of the surface as we are going to clear the mesh
			var s_name = mesh.get_surface_name(0)
			var s_format = mesh.get_surface_format(0)
			var s_material = mesh.get_surface_material(0)
			var s_type = mesh.get_surface_primitive_type(0)

			# May as well change the material to our own one here
			s_material = preload("res://forest_material.material")

			# Clear the mesh
			mesh.clear()
			# And re-add the surface modifying the format flags with the our CUSTOM0 format
			mesh.add_surface(s_type, array, [], {}, s_material, s_name, s_format | (Mesh.ARRAY_CUSTOM_R_FLOAT << Mesh.ARRAY_FORMAT_CUSTOM0_SHIFT))

		return OK

Result:

I’m just setting the ALBEDO to red when the _SPLITS attribute is bigger than 0.0

1 Like

Wow, sorry I’m only now replying, got busy over the weekend. Thanks you so much for taking the time to look at this and come up with something.

I’ll dig in to it when I have a moment later in the week, writing a plugin sounds like a fun new challenge. :slight_smile:

It looks like native support might have a chance to be added in the near future as well: PR97756

Thanks!

This regression (it worked previously) was enough that I abandonded glTF for Godot. Which was a bit frustrating since I have hundreds of models that I converted to glTF expressly for Godot back in 4.2 when this worked - and they still work fine unless I reimport them.

glTF left, FBX right, they matched until I reimported both under 4.6

In fact, here’s an example where they still work even after reimport because they’re ‘local to scene’ and embeded:

Alllll of this is vertex based, nearly all my shaders are vertex shaders as that’s how players are allowed to customize, and that’s how the model masking works