in python FBX SDK, I am trying to generate and export a cube. However, the normals in the exported model are completely off.
How I set the normals. I am setting a normal per control point (aka vertex) of the cube, so 8 normals in total. A normal for a given vertex is collinear with and has the same direction as the vector pointing from cube's center to the given vertex, so the cube normals are pointing 'outwards' with respect to cube's center. The full code I am using is below, it is fully reproduceable if the FBX library is installed. Also, this code is based on this example by FBX.
import fbx
memory_manager = fbx.FbxManager.Create()
scene = fbx.FbxScene.Create(memory_manager, '')
mesh_node = fbx.FbxNode.Create(memory_manager, 'cube')
scene.GetRootNode().AddChild(mesh_node)
# -- mesh
mesh_attribute: fbx.FbxMesh = fbx.FbxMesh.Create(memory_manager, '')
mesh_node.AddNodeAttribute(mesh_attribute)
# -- mesh -- vertices
mesh_attribute.InitControlPoints(8)
CUBE_VERTS = (
(0,0,0),
(0,0,1),
(0,1,0),
(0,1,1),
(1,0,0),
(1,0,1),
(1,1,0),
(1,1,1)
)
for i in range(0, 8):
vert = CUBE_VERTS[i]
mesh_attribute.SetControlPointAt(fbx.FbxVector4(vert[0], vert[1], vert[2], 0.0), i)
# -- mesh -- faces
CUBE_FACES = (
(0,1,3,2),
(3,2,6,7),
(4,5,7,6),
(0,1,5,4),
(1,3,7,5),
(0,2,6,4)
)
for i in range(0, len(CUBE_FACES)):
mesh_attribute.BeginPolygon()
mesh_attribute.AddPolygon(CUBE_FACES[i][0])
mesh_attribute.AddPolygon(CUBE_FACES[i][1])
mesh_attribute.AddPolygon(CUBE_FACES[i][2])
mesh_attribute.AddPolygon(CUBE_FACES[i][3])
mesh_attribute.EndPolygon()
# -- mesh -- normals
NORMALS = [
[-0.57735, -0.57735, -0.57735, 1.0],
[-0.57735, -0.57735, 0.57735, 1.0],
[-0.57735, 0.57735, -0.57735, 1.0],
[-0.57735, 0.57735, 0.57735, 1.0],
[0.57735, -0.57735, -0.57735, 1.0],
[0.57735, -0.57735, 0.57735, 1.0],
[0.57735, 0.57735, -0.57735, 1.0],
[0.57735, 0.57735, 0.57735, 1.0]
]
normal_element: fbx.FbxLayerElementNormal = mesh_attribute.CreateElementNormal()
normal_element.SetMappingMode(fbx.FbxLayerElementNormal.eByControlPoint)
normal_element.SetReferenceMode(fbx.FbxLayerElementNormal.eDirect)
for i in range(0, 8):
normal_element.GetDirectArray().Add(fbx.FbxVector4(NORMALS[i][0], NORMALS[i][1], NORMALS[i][2]))
# EXPORT
exporter = fbx.FbxExporter.Create(memory_manager, '')
exporter.Initialize('cuby', -1, memory_manager.GetIOSettings())
result = exporter.Export(scene)
if result:
print('exported succesfully')
memory_manager.Destroy()
What I expect. When I export the model, I expect to see per-face normals which are all pointing outwards with respect to the center, so a normal-looking cube with healthy normals. I expect the cube as shown below:
What actually happens. The normals are completely off, seems as if they had been generated with some random algorithm. I have examined the cube in the FBX Review software and in Blender. The results respectively as below:
In the second picture (Blender), you can see that normals are pointing in pretty much random directions: both vertex and face normals.
I am completely sure that I am missing something in the code but I can't figure out what.
EDIT. I also tried adding the normal layer element to a geometry layer, as specified here, with no result... I did this like follows:
# layer exists in my case, else create
layer = mesh_attribute.GetLayer(0)
layer.SetNormals(normal_element)
To export face normals correctly, you have to define the face (a.k.a polygon) correctly. What this means:
fbx
SDK does not allow itfbx
SDK: to define a face in fbx
SDK, you must define a sequence of points which form this face. fbx
SDK will use three of these points (a.k.a vertices) to form two vectors and calculate their cross product, which will be the final normal. Depending on the order of multiplication of these vectors, the normal will be facing one or another direction.To illustrate: suppose, you have a list of vertices verts
, and, suppose, you define a single triangle. Then, the below two options are not equivalent: the order of vertex inclusion matters.
mesh.BeginPolygon()
mesh.AddPolygon(verts[0])
mesh.AddPolygon(verts[1])
mesh.AddPolygon(verts[2])
mesh.EndPolygon()
will yield a different normal compared to
mesh.BeginPolygon()
mesh.AddPolygon(verts[2])
mesh.AddPolygon(verts[1])
mesh.AddPolygon(verts[0])
mesh.EndPolygon()
The order of definition of polygon vertices will decide upon the normal. A more thorough explanation deals with linear algebra and 3d graphics stuff, so is out of scope, but you can read here.
Also, you may wonder what kind of normals I was defining in the code of the question. These were per-vertex normals, as opposed to per-face normals. Per-vertex normals are a different thing, although also important: they define whether surface looks smooth or triangulated, i.e. defines shading.