Axis Systems used in Blender and Cycles

Hi,
Which axis systems blender and cycles use?
Does blender do any axis system conversion when it builds cycles scene?

Please answer.

Thanks in advance.

Conversion of a Blender scene to a Cycles one is done by the files in intern/cycles/blender in the source code, starting more or less in blender_session.cpp.

Cycles uses a left-coordinate system, while Blender uses a right-handed one. For example, here’s an excerpt of the conversion of the camera matrix in blender_camera.cpp:

static Transform blender_camera_matrix(const Transform &tfm,
                                       const CameraType type,
                                       const PanoramaType panorama_type)
{
  Transform result;
  ...
  else {
    /* note the blender camera points along the negative z-axis */
    result = tfm * transform_scale(1.0f, 1.0f, -1.0f);
  }

So while an untransformed Blender camera looks along the -Z axis the Cycles one looks along the +Z axis (when both have +X to the right and +Y upwards). During Blender -> Cycles export it’s scaled by -1 along Z.

2 Likes

Hi @PaulMelis,

Thanks for answering my question. So, Blender uses Z-Up, Right handed system and Cycles uses Z-Up Left handed system. Is that correct?

Blender indeed uses Z-up as convention, but for a renderer like Cyles “up” doesn’t really mean anything as the whole image rendering process is related to the camera orientation.

1 Like

But, it matters when baking textures I think…?

Blender passes Cycles the scene, including geometry and the camera orientation. Cycles might do the baking in world space (don’t know), but I don’t see how the up direction might be involved. Any transformation from blender to cycles can be done in the reverse direction from cycles to blender, e.g. to get bake results.

1 Like

You could experiment with the cycles standalone version to see what the orientation of the untransformed camera is. E.g. basing off one of the examples, like https://developer.blender.org/diffusion/C/browse/master/examples/scene_cube_surface.xml

1 Like

Ok, What I am doing is,

I have hosted Cycles in my application. I have implemented a Fbx Importer which reads a given FBX file and builds cycles scene. This importer is working fine. Position of objects are perfect as in FBX file.

But, I wanted to compare my scene (scene I built with my importer) with Blender generated cycles scene.
So, I have implemented a json serializer which serializes cycles scene.

So, after blender has built the cycles scene, I serialized the complete cycles scene to a text file.
When I compare these two files, I noticed that though the vertices, indices, normals etc… are exactly same in both, the transformation stuff is different.

Though I have no issue with the transformations that my importer is calculating, I want to fix it in such a way that the transformations also match with blender calculated transformations.

These are the Blender generated Objects

"objects": [{
		"name": "Wall01",
		"mesh": "Mesh",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Wall02",
		"mesh": "Mesh.001",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Wall03",
		"mesh": "Mesh.002",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Wall04",
		"mesh": "Mesh.003",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Floor",
		"mesh": "Mesh.004",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "DoorFoot",
		"mesh": "Mesh.005",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Dressing_Table_18",
		"mesh": "Mesh.006",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "PhotoFrames",
		"mesh": "Mesh.007",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "TV",
		"mesh": "Mesh.008",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Shelves",
		"mesh": "Mesh.009",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Book",
		"mesh": "Mesh.010",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Vase1_1",
		"mesh": "Mesh.011",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Vase1_2",
		"mesh": "Mesh.012",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Vase1_3",
		"mesh": "Mesh.013",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Bed",
		"mesh": "Mesh.014",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Mattress",
		"mesh": "Mesh.015",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Lamp",
		"mesh": "Mesh.016",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Ceiling",
		"mesh": "Mesh.017",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Door",
		"mesh": "Mesh.018",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Handle",
		"mesh": "Mesh.019",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "DoorFrame",
		"mesh": "Mesh.020",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Board",
		"mesh": "Mesh.021",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Table",
		"mesh": "Mesh.022",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, 0.0], [0.0, 0.01, -0.0, 0.0]],
	}, {
		"name": "Lights.001",
		"mesh": "Mesh.023",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, -0.0], [0.0, 0.01, -0.0, 0.06348]],
	}, {
		"name": "Lights1",
		"mesh": "Mesh.024",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, -0.0], [0.0, 0.01, -0.0, 0.06348]],
	}, {
		"name": "Lights2",
		"mesh": "Mesh.025",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, -0.0], [0.0, 0.01, -0.0, 0.06348]],
	}, {
		"name": "Lights3",
		"mesh": "Mesh.026",
		"tfm": [[0.01, 0.0, 0.0, 0.0], [0.0, -0.0, -0.01, -0.0], [0.0, 0.01, -0.0, 0.06348]],
	}, {
		"name": "SpotLit1",
		"mesh": "Mesh.027",
		"tfm": [[0.01, 0.0, 0.0, 0.48235], [0.0, -0.0, -0.01, -0.0], [0.0, 0.01, -0.0, 0.02053]],
	}
]

These are my importer generated Objects

"objects": [{
		"name": "Wall01_0",
		"geometry": "Wall01",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Wall02_1",
		"geometry": "Wall02",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Wall03_2",
		"geometry": "Wall03",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Wall04_3",
		"geometry": "Wall04",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Floor_4",
		"geometry": "Floor",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "DoorFoot_5",
		"geometry": "DoorFoot",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Dressing_Table_18_6",
		"geometry": "Dressing_Table_18",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "PhotoFrames_7",
		"geometry": "PhotoFrames",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "TV_8",
		"geometry": "TV",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Shelves_9",
		"geometry": "Shelves",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Book_10",
		"geometry": "Book",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Vase1_1_11",
		"geometry": "Vase1_1",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Vase1_2_12",
		"geometry": "Vase1_2",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Vase1_3_13",
		"geometry": "Vase1_3",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Bed_14",
		"geometry": "Bed",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Mattress_15",
		"geometry": "Mattress",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Lamp_16",
		"geometry": "Lamp",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Ceiling_17",
		"geometry": "Ceiling",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Door_18",
		"geometry": "Door",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Handle_19",
		"geometry": "Handle",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "DoorFrame_20",
		"geometry": "DoorFrame",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Board_21",
		"geometry": "Board",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Table_22",
		"geometry": "Table",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, -0.0]]
	}, {
		"name": "Lights_23",
		"geometry": "Lights",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, -0.0], [0.0, 0.00999, 0.0, 0.06348]]
	}, {
		"name": "Lights1_24",
		"geometry": "Lights1",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, -0.0], [0.0, 0.00999, 0.0, 0.06348]]
	}, {
		"name": "Lights2_25",
		"geometry": "Lights2",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, -0.0], [0.0, 0.00999, 0.0, 0.06348]]
	}, {
		"name": "Lights3_26",
		"geometry": "Lights3",
		"tfm": [[-0.00999, -0.0, 0.0, 0.0], [0.0, -0.0, 0.00999, -0.0], [0.0, 0.00999, 0.0, 0.06348]]
	}, {
		"name": "SpotLit1_27",
		"geometry": "SpotLit1",
		"tfm": [[-0.00999, -0.0, 0.0, -0.48235], [0.0, -0.0, 0.00999, 0.0], [0.0, 0.00999, 0.0, 0.02053]]
	}
]

But you can’t compare the two without taking the camera orientation into account, right?

You mean, blender while generating cycles scene, is applying camera orientation related matrix on top of actual transformation of the object?

Probably not, but what I meant was looking at object transformations alone in a renderer is not enough to compare. The combination of camera+object transformation determines the image output. So you could have different object transforms but the same image output if different camera orientations are used. You showed only the json dump of the object transforms, but not the camera transform.

But I suspect you already know this, so perhaps you could describe your issue with a bit more detail. Are you getting the same image output, but based on different object transforms? Or is there something else wrong?

Yes. I want to generate the same transformations that blender generates for Objects, Lights, and cameras. For that, to match my transformation with blender generated transformation, I need to understand what transformation I need to apply on top of the transformation that my importer calculates.

FBX uses Y up, doesn’t it? As far as a file format can dictate such things.

In the Fbx file, the axis system information, unit scale information are also stored.
Using FbxSDK, we can convert the entire scene to desired axis system and scale.

I am doing like this.

  FbxAxisSystem actual_axis_system = fbx_scene->GetGlobalSettings().GetAxisSystem();
  FbxAxisSystem cycles_axis_system(FbxAxisSystem::eZAxis, FbxAxisSystem::eParityOdd, FbxAxisSystem::eRightHanded);
  if (actual_axis_system != cycles_axis_system) {
    cycles_axis_system.ConvertScene(fbx_scene);
  }

  FbxGlobalSettings &global_settings = fbx_scene->GetGlobalSettings();
  if (global_settings.GetSystemUnit() != FbxSystemUnit::m) {
    const FbxSystemUnit::ConversionOptions lConversionOptions = {
        false, /* mConvertRrsNodes */
        true,  /* mConvertLimits */
        true,  /* mConvertClusters */
        true,  /* mConvertLightIntensity */
        true,  /* mConvertPhotometricLProperties */
        true   /* mConvertCameraClipPlanes */
    };

    // Convert the scene to meters using the defined options.
    FbxSystemUnit::m.ConvertScene(fbx_scene, lConversionOptions);
  }

You let the FBX SDK convert to Z-up here if the imported scene uses a different up axis (i.e Y). But I still don’t see how the up direction would be used here, other than in relating to the camera orientation. I suspect Cycles’ untransformed camera is Y-up, with +X to the right and looking down the +Z axis, i.e. left-handed. But we should ask @brecht to be sure.

1 Like

Blender does like this in blender_camera_matrix method. If not done, camera shows mirror image.

tfm = tfm * transform_scale(1.0f, 1.0f, -1.0f);

But for me, when I did this, the camera is pointing -90 degrees on x axis. So, I did something like this.

tfm = tfm * transform_scale(1.0f, 1.0f, -1.0f);
float ninety_degrees_in_radians = (90.0 * M_PI) / 180.0;
tfm = tfm * transform_rotate(ninety_degrees_in_radians, make_float3(0, 1, 0));

That’s why I am trying to generate the same transformations that blender generates so that I need not do these kind of fixes.

Note that when you add a camera to a Blender scene it usually is oriented Z-up, but it actually has a +90 degrees X rotation. If you clear the rotations to zero you will see that it is looking down the -Z axis, with +X to the right and +Y up (the latter meaning “up” as far as the image is concerned, not the scene “up”). So the untransformed Blender camera uses a right-handed coordinate system with Y up. But Blender’s scene “up” is +Z.

Transform fbx_matrix_to_cycles_transform(const FbxAMatrix &matrix)
{
    //Column-Major to Row-Major. Cycles uses Row-Major

  Transform transform;
  transform.x.x = matrix.Get(0, 0);
  transform.x.y = matrix.Get(1, 0);
  transform.x.z = matrix.Get(2, 0);
  transform.x.w = matrix.Get(3, 0);

  transform.y.x = matrix.Get(0, 1);
  transform.y.y = matrix.Get(1, 1);
  transform.y.z = matrix.Get(2, 1);
  transform.y.w = matrix.Get(3, 1);

  transform.z.x = matrix.Get(0, 2);
  transform.z.y = matrix.Get(1, 2);
  transform.z.z = matrix.Get(2, 2);
  transform.z.w = matrix.Get(3, 2);

  // I need to do some transformation here so that it matches with blender generated transformations.
  
  return transform;
}

Why would you need to alter the matrices? Doesn’t your call to ConvertScene() already handle that?

I need not alter. The ConvertScene gives correct matrix. Thats why my cycles scene is coming good without any flaws. But I want to alter it in such a way that it matches with blender generated transformations.