How to fix inverted normals in code?

Godot Version

4.6.2

Question

So I am currently working on making a parser for Roblox’s 1.0 mesh format in GDScript. It mostly works except that normals are inverted:

While yes this can easily be fixed by changing to front face culling, is there a way in order to reorder the vertex data in code to prevent having to change the material?

Heres the code for the special mesh (roblox 1.0 mesh file) creator:

class_name CreateSpecialMesh
extends Node

enum SPECIAL_MESH_VERSIONS {
	VERSION_1,
	VERSION_1_01
}

# Local function for formatting assert
static func formatAssert(function:String, error:String, params:Dictionary) -> String:
	return "CreateSpecialMesh::" + function + "() " + error.format(params)
	

static func flipNormals(ogNormals : Vector3) -> Vector3:
	return Vector3(ogNormals.x, ogNormals.y, ogNormals.z)

static func createSurfaceData(fileName: String) -> Part_SurfaceData:
	var funcName = "createSurfaceData"
	
	var surfaceData = Part_SurfaceData.new();
	var meshFile = FileAccess.open(fileName, FileAccess.READ)
	assert(meshFile != null, formatAssert(funcName, "Mesh file {fileName} not found!", {"fileName" : fileName}))
	
	var meshData := meshFile.get_as_text()
	meshData = meshData.replace("\n", "")
	meshData = meshData.replace("\r", "")
	meshData = meshData.replace("\t", "")
	
	assert(meshData.begins_with("version 1.00") or meshData.begins_with("version 1.01"), 
	formatAssert(funcName, "{fileName} is not a valid 1.0 mesh file!", {"fileName" : fileName}))
	
	var version:SPECIAL_MESH_VERSIONS = SPECIAL_MESH_VERSIONS.VERSION_1 
	if meshData.substr(0, 12).contains("1.01"): 
		version = SPECIAL_MESH_VERSIONS.VERSION_1_01  
	
	meshData = meshData.erase(0, 12)
	
	var firstBracket:int = meshData.find("[")
	var faceCount:int = int(meshData.substr(0, firstBracket))
	assert(faceCount != null, formatAssert(funcName, "{fileName}'s face count is invalid!", {"fileName" : fileName}))
	
	meshData = meshData.erase(0, firstBracket)
	
	for i in faceCount * 3:
		var pos:int = meshData.find("[")
		var vertPosition:Vector3;
		var vertNormals:Vector3;
		var vertTexCoords:Vector2;

		meshData = meshData.erase(0, pos+1)
		
		# X Position
		pos = meshData.find(",")
		vertPosition.x = float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		
		# Y Position
		pos = meshData.find(",")
		vertPosition.y = float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		
		# Z Position
		pos = meshData.find(",")
		vertPosition.z = float(meshData.substr(0, pos))
		
		pos = meshData.find("[")
		
		meshData = meshData.erase(0, pos+1)
		# X Normal
		pos = meshData.find(",")
		vertNormals.x = float(meshData.substr(0, pos))
		print(vertNormals.x)
		meshData = meshData.erase(0, pos+1)
		
		# Y Normal
		pos = meshData.find(",")
		vertNormals.y = float(meshData.substr(0, pos))
		print(vertNormals.y)
		meshData = meshData.erase(0, pos+1)
		
		# Z Normal
		pos = meshData.find(",")
		vertNormals.z = float(meshData.substr(0, pos))
		print(vertNormals.z)
		
		pos = meshData.find("[")
		meshData = meshData.erase(0, pos+1)
		
		# X Tex
		pos = meshData.find(",")
		vertTexCoords.x = float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		
		# Y Tex
		pos = meshData.find(",")
		
		vertTexCoords.y = 1 - float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		pos = meshData.find("]")

		meshData = meshData.erase(0, pos+1)
		
		if version == SPECIAL_MESH_VERSIONS.VERSION_1:
			vertPosition *= 0.5
		surfaceData.appendVertexData(vertPosition, flipNormals(vertNormals), vertTexCoords)
	
	
	return surfaceData;

As well as the surfaceData class:

class_name Part_SurfaceData
extends Node

var vertices : PackedVector3Array;
var indices : PackedInt32Array;
var normals : PackedVector3Array;
var texCoords : PackedVector2Array;
var usesIndices : bool;

func _init(useIndices:bool = false):
print(“Creating Part Surface Data…”)
vertices = PackedVector3Array()
indices = PackedInt32Array();
normals = PackedVector3Array();
texCoords = PackedVector2Array();
usesIndices = useIndices

func appendVertexData(vertexPos:Vector3, vertexNormal:Vector3, vertexTexCoord:Vector2):
vertices.append(vertexPos)
normals.append(vertexNormal)
texCoords.append(vertexTexCoord)

func createArrayMesh() → ArrayMesh:
var surface_array = 

var array_mesh : ArrayMesh = ArrayMesh.new()

surface_array.resize(Mesh.ARRAY_MAX)
surface_array[Mesh.ARRAY_VERTEX] = vertices
surface_array[Mesh.ARRAY_NORMAL] = normals
surface_array[Mesh.ARRAY_TEX_UV] = texCoords
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)
return array_mesh
#surface_array[Mesh.ARRAY_INDEX] = indices

And here is a document which contains the file formats specifications if you need it: Roblox FileMesh Format Specification - Community Resources - Developer Forum | Roblox

The normal is defined by the order of the vertices of a face. To flip the normal you have to swap two vertices of the face. They have to be drawn clockwise to get them as the front face.
I’m not aware of a command in Godot to flip the normals in a mesh.

1 Like

I managed to fix it by looping through each face, storing the vertices inside a temp array and then switching them when they get added to the array mesh like so:

for i in faceCount:
	var vertsPositions:PackedVector3Array
	var vertsNormals:PackedVector3Array
	var vertsTexcoords:PackedVector2Array
	for j in 3:
		var pos:int = meshData.find("[")
		var vertPosition:Vector3;
		var vertNormals:Vector3;
		var vertTexCoords:Vector2;

		meshData = meshData.erase(0, pos+1)
		
		# X Position
		pos = meshData.find(",")
		vertPosition.x = float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		
		# Y Position
		pos = meshData.find(",")
		vertPosition.y = float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		
		# Z Position
		pos = meshData.find(",")
		vertPosition.z = float(meshData.substr(0, pos))
		
		pos = meshData.find("[")
		
		meshData = meshData.erase(0, pos+1)
		# X Normal
		pos = meshData.find(",")
		vertNormals.x = float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		
		# Y Normal
		pos = meshData.find(",")
		vertNormals.y = float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		
		# Z Normal
		pos = meshData.find(",")
		vertNormals.z = float(meshData.substr(0, pos))
		
		pos = meshData.find("[")
		meshData = meshData.erase(0, pos+1)
		
		# X Tex
		pos = meshData.find(",")
		vertTexCoords.x = float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		
		# Y Tex
		pos = meshData.find(",")
		
		vertTexCoords.y = 1 - float(meshData.substr(0, pos))
		meshData = meshData.erase(0, pos+1)
		pos = meshData.find("]")

		meshData = meshData.erase(0, pos+1)
		
		if version == SPECIAL_MESH_VERSIONS.VERSION_1:
			vertPosition *= 0.5
		vertsPositions.append(vertPosition)
		vertsNormals.append(vertNormals)
		vertsTexcoords.append(vertTexCoords)
	surfaceData.appendVertexData(vertsPositions[0], vertsNormals[0], vertsTexcoords[0])
	surfaceData.appendVertexData(vertsPositions[2], vertsNormals[2], vertsTexcoords[2])
	surfaceData.appendVertexData(vertsPositions[1], vertsNormals[1], vertsTexcoords[1])
	vertsPositions.clear()
	vertsNormals.clear()
	vertsTexcoords.clear()

Using linear algebra, you can also flip then by multiplying the normal by the -1 scalar, e.g your tuplet of coords is 2,3 or 4d vector. Just multiply each normal by -1.

That’ll flip the lot.

Cheers, keep on space gamedevin’! :wink:

Do you mean doing something like

vertNormals = vertNormals * -1

above the appendVertexData call in the og script?

if so that doesn’t work

Weird…. its basic linear algebra, to revert a vector along it’s direction, you multiply it by -1.

It isn’t really. Normals and polygon winding are two separate things. You can flip normals without touching the winding and vice versa.

1 Like

How would you explain this?
AFAIK the winding defines the front and the back face, the OP asked how to flip front and back.
How would you flip front- / backface?

Yes, but front/back doesn’t define the normal.

Look at the basic vertex data. The normal is explicitly stated in there along with the vertex position. It won’t change if you swap the order of vertices that form a triangle.

1 Like

ok, the term normal may be wrong here, as it’s defined in the vertex. But the face has a resulting face normal, that points away from the front face, right? (flat shaded)

Maybe the little bit misleading term Normal here comes from Blender where you can flip the Normal of a face to define front and back face.

No. You may conclude so when working with some 3d modelers that automatically (or on-demand) recalculate normals to align them with the “front” direction defined by winding. But conceptualy, they are separate things. In realtime graphics, once the vertex data reaches the gpu, they won’t affect each other in any way. The normal is used for lighting while the winding is used for culling. If a triangle is not rendered by the GPU like in OP’s screenshot, it’s only due to culling/winding. It has nothing to do with the normal.

Yep

so, how would you flip front and back face without change the order of the vertices in Godot? And without setting it in the material to front face culling?

There’s only one way to flip front/back in any program - by changing the order of vertices. And in Godot you can’t do it without manually rearranging the vertex data via script.

Note that winding convention may differ between rendering apis and modelers, so what’s “front” in one may be “back” in another. If the mesh enters Godot with the “wrong” winding, you can display it properly by swapping the culling mode. However that won’t flip the normal direction. Neither will rearranging the order in the vertex data. The normal is an explicit part of vertex data.

Your favorite 3d modeler might conflate the winding change and the normal flip, but purely from the vertex data standpoint they are separate things. As I already said, you can flip the normal without changing the winding and vice versa because those two things are separate pieces of information in the vertex data.

1 Like

I think we are discussing technical details here. You are technically correct.
But the OP has asked:

So my answer was the correct solution, but maybe the term face normal here (used by the OP, too) is not the right term. But it was clear what was asked (to flip back and front face in the mesh).

Your solution is correct, just that your explanation/interpretation of it isn’t. Both of you presume that normals and front/back facing are tied together, probably because you conceptualize the things based on your experience with modelling apps, rather than what actually happens with vertex data. Modeling apps add a layer of abstraction and tend to keep those two tings “in sync” as this is what is typically desired in most use cases. However, a game engine will be a bit lower level and won’t care to sync winding and normal. It’ll just use raw vertex data it got from the modeling app.

3 Likes

Oh yeah that actually makes a lot of sense. That does make me wonder if OpenGL itself syncs winding and normals or by default reads normals with counterclockwise winding since I didn’t need to adjust the vertex order when making a parser of the mesh format for standard opengl 3 in c++

OpenGL uses winding order, vertex data is only data to OpenGL it has no concept of normals. You can change which face is culled in OpenGL and Godot materials

Godot standard material can assign Cull Mode, Godot shaders can set a render mode like so

render_mode cull_back;
1 Like

yeah I know about face culling modes, my og issue was with the vertex for faces data in the roblox .mesh files not aligning with how godot handles front faces and back faces, causing the mesh to look as if it front face culled when back face culling is enabled and viseversa

now I’m kinda rewriting the whole mesh system to be a custom mesh type that inherits ArrayMesh in c++ (mainly because of preformance since this can be called a decent amount and also so I can use scanf_s) via extensions

1 Like

OpenGL defaults to interpreting counterclockwise winding as front faces, while Godot default to clockwise winding. If you’ve initially built your meshes for default OpenGL display, Godot’s default will cull what you’ve set to be front faces, which is precisely what happened on your screenshots. The easiest fix is to just set culling to front instead to back faces in material properties or shader render mode. That’s admittedly kinda annoying so if you want it “clean” - reverse the triangle vertex order when creating your meshes.

2 Likes