Fix T100028: Convert USD camera properties to mm from USD units.

Authored by Sonny Campbell.

Currently when importing a USD file, some of the camera properties are
ignored, or the units are not converted correctly from USD world units.
On import we currently set the focal length, but not the camera sensor
size (horizontal and vertical aperture), so the camera field of view
is wrong. The sensor size information is in the USD file, but is ignored
for perspective cameras.

USD uses "tenth of a world unit" scale for some physical camera properties
like focal length and aperture.
https://graphics.pixar.com/usd/release/api/class_usd_geom_camera.html#UsdGeom_CameraUnits

I have added the UsdStage's metersPerUnit parameter to the ImportSettings
so the camera can do the required conversion on import. This will convert from
the USD file's world units to millimeters for Blender's camera settings.

Reviewed by: Sybren and makowalski.

Differential Revision: https://developer.blender.org/D16019
This commit is contained in:
Michael Kowalski 2023-01-30 17:22:26 -05:00
parent 129093fbce
commit f359a39d11
Notes: blender-bot 2023-02-14 05:50:03 +01:00
Referenced by issue #100028, USD Import - camera field of view wrong
4 changed files with 53 additions and 9 deletions

View File

@ -212,6 +212,7 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
}
convert_to_z_up(stage, &data->settings);
data->settings.stage_meters_per_unit = UsdGeomGetStageMetersPerUnit(stage);
/* Set up the stage for animated data. */
if (data->params.set_frame_range) {

View File

@ -54,14 +54,19 @@ void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTim
pxr::VtValue horAp;
cam_prim.GetHorizontalApertureAttr().Get(&horAp, motionSampleTime);
bcam->lens = val.Get<float>();
/* TODO(@makowalski): support sensor size. */
#if 0
bcam->sensor_x = 0.0f;
bcam->sensor_y = 0.0f;
#endif
bcam->shiftx = verApOffset.Get<float>();
bcam->shifty = horApOffset.Get<float>();
/*
* For USD, these camera properties are in tenths of a world unit.
* https://graphics.pixar.com/usd/release/api/class_usd_geom_camera.html#UsdGeom_CameraUnits
* tenth_of_unit = stage_meters_per_unit / 10
* val_in_meters = val.Get<float>() * tenth_of_unit
* val_in_millimeters = val_in_meters * 1000
*/
const double scale_to_mm = 100.0 * settings_->stage_meters_per_unit;
bcam->lens = val.Get<float>() * scale_to_mm;
bcam->sensor_x = horAp.Get<float>() * scale_to_mm;
bcam->sensor_y = verAp.Get<float>() * scale_to_mm;
bcam->shiftx = verApOffset.Get<float>() * scale_to_mm;
bcam->shifty = horApOffset.Get<float>() * scale_to_mm;
bcam->type = (projectionVal.Get<pxr::TfToken>().GetString() == "perspective") ? CAM_PERSP :
CAM_ORTHO;

View File

@ -49,6 +49,10 @@ struct ImportSettings {
* and is mutable similar to the map above. */
mutable std::map<std::string, Material *> mat_name_to_mat;
/* We use the stage metersPerUnit to convert camera properties from USD scene units to the
* correct millimeter scale that Blender uses for camera parameters. */
double stage_meters_per_unit;
ImportSettings()
: do_convert_mat(false),
from_up(0),
@ -60,7 +64,8 @@ struct ImportSettings {
sequence_offset(0),
read_flag(0),
validate_meshes(false),
cache_file(NULL)
cache_file(NULL),
stage_meters_per_unit(1.0)
{
}
};

View File

@ -155,6 +155,39 @@ class USDImportTest(AbstractUSDTest):
coords = list(filter(lambda x: x[0] > 1.0, coords))
self.assertGreater(len(coords), 16)
def test_import_camera_properties(self):
"""Test importing camera to ensure properties set correctly."""
# This file has metersPerUnit = 1
infile = str(self.testdir / "usd_camera_test_1.usda")
res = bpy.ops.wm.usd_import(filepath=infile)
self.assertEqual({'FINISHED'}, res)
camera_object = bpy.data.objects["Test_Camera"]
test_cam = camera_object.data
self.assertAlmostEqual(43.12, test_cam.lens, 2)
self.assertAlmostEqual(24.89, test_cam.sensor_width, 2)
self.assertAlmostEqual(14.00, test_cam.sensor_height, 2)
self.assertAlmostEqual(12.34, test_cam.shift_x, 2)
self.assertAlmostEqual(56.78, test_cam.shift_y, 2)
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# This file has metersPerUnit = 0.1
infile = str(self.testdir / "usd_camera_test_2.usda")
res = bpy.ops.wm.usd_import(filepath=infile)
self.assertEqual({'FINISHED'}, res)
camera_object = bpy.data.objects["Test_Camera"]
test_cam = camera_object.data
self.assertAlmostEqual(4.312, test_cam.lens, 3)
self.assertAlmostEqual(2.489, test_cam.sensor_width, 3)
self.assertAlmostEqual(1.400, test_cam.sensor_height, 3)
self.assertAlmostEqual(1.234, test_cam.shift_x, 3)
self.assertAlmostEqual(5.678, test_cam.shift_y, 3)
def main():
global args