Skip to content

Commit

Permalink
fix material issues and add features (#762)
Browse files Browse the repository at this point in the history
* fix material issues and add features

Signed-off-by: Clement Fuji Tsang <[email protected]>

* update .readthedocs.yml

Signed-off-by: Clement Fuji Tsang <[email protected]>

* .

Signed-off-by: Clement Fuji Tsang <[email protected]>

---------

Signed-off-by: Clement Fuji Tsang <[email protected]>
  • Loading branch information
Caenorst authored Oct 25, 2023
1 parent 3f05196 commit 1a73983
Show file tree
Hide file tree
Showing 14 changed files with 614 additions and 358 deletions.
20 changes: 12 additions & 8 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
version: 2

python:
version: 3.8
install:
- requirements: docs/readthedocs_requirements.txt
- requirements: tools/doc_requirements.txt
- method: setuptools
path: '.'
install:
- requirements: docs/readthedocs_requirements.txt
- requirements: tools/doc_requirements.txt
- method: setuptools
path: '.'

build:
os: "ubuntu-22.04"
tools:
python: "3.8"

sphinx:
configuration: docs/conf.py
fail_on_warning: true
configuration: docs/conf.py
fail_on_warning: true
288 changes: 243 additions & 45 deletions kaolin/io/materials.py

Large diffs are not rendered by default.

179 changes: 142 additions & 37 deletions tests/python/kaolin/io/test_materials.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2019, 20-21 NVIDIA CORPORATION & AFFILIATES.
# Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -24,8 +24,47 @@
from kaolin.io import usd, obj
from kaolin.utils.testing import contained_torch_equal

_misc_attributes = [
'diffuse_colorspace',
'roughness_colorspace',
'metallic_colorspace',
'clearcoat_colorspace',
'clearcoat_roughness_colorspace',
'opacity_colorspace',
'ior_colorspace',
'specular_colorspace',
'normals_colorspace',
'displacement_colorspace',
'is_specular_workflow'
]

_value_attributes = [
'diffuse_color',
'roughness_value',
'metallic_value',
'clearcoat_value',
'clearcoat_roughness_value',
'opacity_value',
'opacity_threshold',
'ior_value',
'specular_color',
'displacement_value',
]

_texture_attributes = [
'diffuse_texture',
'roughness_texture',
'metallic_texture',
'clearcoat_texture',
'clearcoat_roughness_texture',
'opacity_texture',
'ior_texture',
'specular_texture',
'displacement_texture'
]

# Seed for texture sampling
# TODO(cfujitsang): This might fix the seed for the whole pytest.
torch.random.manual_seed(0)


Expand All @@ -47,8 +86,7 @@ def material_values():
'specular_color': (1., 0., 0.),
'is_specular_workflow': True,
}
material = kal_materials.PBRMaterial(**params)
yield material
yield params


@pytest.fixture(scope='module')
Expand All @@ -59,10 +97,8 @@ def material_textures():
'metallic_texture': torch.rand((1, 256, 256)),
'normals_texture': torch.rand((1, 256, 256)),
'specular_texture': torch.rand((3, 256, 256)),
'is_specular_workflow': True,
}
material = kal_materials.PBRMaterial(**params)
yield material
yield params


@pytest.fixture(scope='module')
Expand All @@ -77,40 +113,43 @@ def mesh():
class TestPBRMaterial:
def test_separate_texture_path(self, out_dir, material_values):
file_path = os.path.join(out_dir, 'pbr_test.usda')
material_values.write_to_usd(file_path, '/World/Looks/pbr', texture_dir='texture')
mat = kal_materials.PBRMaterial(**material_values)
mat.write_to_usd(file_path, '/World/Looks/pbr', texture_dir='texture')

material_in = kal_materials.PBRMaterial().read_from_usd(file_path, '/World/Looks/pbr', texture_path='texture')

assert material_values.diffuse_color == pytest.approx(material_in.diffuse_color, 0.1)
assert material_values.roughness_value == pytest.approx(material_in.roughness_value, 0.1)
assert material_values.metallic_value == pytest.approx(material_in.metallic_value, 0.1)
assert material_values.specular_color == pytest.approx(material_in.specular_color, 0.1)
assert material_values.is_specular_workflow == material_in.is_specular_workflow
assert mat.diffuse_color == pytest.approx(material_in.diffuse_color, 0.1)
assert mat.roughness_value == pytest.approx(material_in.roughness_value, 0.1)
assert mat.metallic_value == pytest.approx(material_in.metallic_value, 0.1)
assert mat.specular_color == pytest.approx(material_in.specular_color, 0.1)
assert mat.is_specular_workflow == material_in.is_specular_workflow

def test_cycle_values(self, out_dir, material_values):
file_path = os.path.join(out_dir, 'pbr_test.usda')
material_values.write_to_usd(file_path, '/World/Looks/pbr')
mat = kal_materials.PBRMaterial(**material_values)
mat.write_to_usd(file_path, '/World/Looks/pbr')

material_in = kal_materials.PBRMaterial().read_from_usd(file_path, '/World/Looks/pbr')

assert material_values.diffuse_color == pytest.approx(material_in.diffuse_color, 0.1)
assert material_values.roughness_value == pytest.approx(material_in.roughness_value, 0.1)
assert material_values.metallic_value == pytest.approx(material_in.metallic_value, 0.1)
assert material_values.specular_color == pytest.approx(material_in.specular_color, 0.1)
assert material_values.is_specular_workflow == material_in.is_specular_workflow
assert mat.diffuse_color == pytest.approx(material_in.diffuse_color, 0.1)
assert mat.roughness_value == pytest.approx(material_in.roughness_value, 0.1)
assert mat.metallic_value == pytest.approx(material_in.metallic_value, 0.1)
assert mat.specular_color == pytest.approx(material_in.specular_color, 0.1)
assert mat.is_specular_workflow == material_in.is_specular_workflow

def test_cycle_textures(self, out_dir, material_textures):
"""Cycle test for textures. This conversion is lossy!"""
file_path = os.path.join(out_dir, 'pbr_tex_test.usda')
material_textures.write_to_usd(file_path, '/World/Looks/pbr')
mat = kal_materials.PBRMaterial(**material_textures)
mat.write_to_usd(file_path, '/World/Looks/pbr')

material_in = kal_materials.PBRMaterial().read_from_usd(file_path, '/World/Looks/pbr')
assert torch.allclose(material_textures.diffuse_texture, material_in.diffuse_texture, atol=1e-2)
assert torch.allclose(material_textures.roughness_texture, material_in.roughness_texture, atol=1e-2)
assert torch.allclose(material_textures.metallic_texture, material_in.metallic_texture, atol=1e-2)
assert torch.allclose(material_textures.normals_texture, material_in.normals_texture, atol=1e-2)
assert torch.allclose(material_textures.specular_texture, material_in.specular_texture, atol=1e-2)
assert material_textures.is_specular_workflow == material_in.is_specular_workflow
assert torch.allclose(mat.diffuse_texture, material_in.diffuse_texture, atol=1e-2)
assert torch.allclose(mat.roughness_texture, material_in.roughness_texture, atol=1e-2)
assert torch.allclose(mat.metallic_texture, material_in.metallic_texture, atol=1e-2)
assert torch.allclose(mat.normals_texture, material_in.normals_texture, atol=1e-2)
assert torch.allclose(mat.specular_texture, material_in.specular_texture, atol=1e-2)
assert mat.is_specular_workflow == material_in.is_specular_workflow

def test_material_values(self, out_dir):
out_path = os.path.join(out_dir, 'pbr_material_values.usda')
Expand All @@ -119,14 +158,22 @@ def test_material_values(self, out_dir):
tests = {
'Default': {},
'Diffuse': {'diffuse_color': (0., 1., 0.)},
'Roughness': {'roughness_value': 0.1},
'Metallic': {'metallic_value': 1.},
'SpecularRoughness': {
'diffuse_color': (1., 0., 0.),
'roughness_value': 0.1,
'specular_color': (0., 0., 1.),
'is_specular_workflow': True
},
'Metallic': {
'diffuse_color': (0., 1., 0.),
'metallic_value': 1.,
'is_specular_workflow': False
},
'Clearcoat': {'clearcoat_value': 1.},
'ClearcoatRougness': {'clearcoat_roughness_value': 1.},
'Opacity': {'opacity_value': 0.5},
'OpacityThreshold': {'opacity_threshold': 0.5},
'Ior': {'ior_value': 1.},
'Specular': {'specular_color': (1., 0., 0.), 'is_specular_workflow': True},
'Displacement': {'displacement_value': 0.1},
}
for test_name, params in tests.items():
Expand All @@ -141,7 +188,7 @@ def test_material_values(self, out_dir):
os.pardir, 'samples/golden/pbr_material_values.usda')
assert open(golden).read() == open(out_path).read()

def test_material_textures(self, out_dir, mesh, material_textures):
def test_material_textures(self, out_dir, mesh):
def _create_checkerboard(val1, val2):
channels = len(val1)
checkerboard = torch.ones((channels, 2, 2)) * torch.tensor(val1)[:, None, None]
Expand All @@ -160,24 +207,24 @@ def _create_checkerboard(val1, val2):
'Metallic': {'metallic_texture': _create_checkerboard((0.1,), (0.9,)), 'metallic_colorspace': 'raw'},
'Clearcoat': {'clearcoat_texture': _create_checkerboard((0.01,), (0.9,)), 'metallic_colorspace': 'raw'},
'ClearcoatRoughness': {'clearcoat_roughness_texture': _create_checkerboard((0.1,), (0.9,)), 'metallic_colorspace': 'raw'},
'Opacity': {'opacity_texture': _create_checkerboard((0.1,), (0.9,)), 'metallic_colorspace': 'raw',
'opacity_threshold': 0.5},
'Ior': {'ior_texture': _create_checkerboard((0.1,), (0.9,)), 'metallic_colorspace': 'raw'},
'Opacity': {'opacity_texture': _create_checkerboard((0.1,), (0.9,)), 'opacity_threshold': 0.5,
'opacity_colorspace': 'raw'},
'Ior': {'ior_texture': _create_checkerboard((0.1,), (0.9,)), 'ior_colorspace': 'raw'},
'Normal': {'normals_texture': _create_checkerboard((0., 0., 1.,), (0., 0.5, 0.5)),
'normals_colorspace': 'raw'},
'Specular': {'specular_texture': _create_checkerboard((1., 0., 0.), (0., 0., 1.)),
'is_specular_workflow': True, 'specular_colorspace': 'raw'},
'Displacement': {'displacement_texture': _create_checkerboard((0.1,), (0.9,)),
'specular_colorspace': 'raw'},
'displacement_colorspace': 'raw'},
}

for test_name, params in tests.items():
material_textures = kal_materials.PBRMaterial(**params)
mat = kal_materials.PBRMaterial(**params)
prim = usd.add_mesh(stage, f'/World/{test_name}', mesh.vertices, mesh.faces,
uvs=mesh.uvs,
face_uvs_idx=mesh.face_uvs_idx,
face_normals=mesh.normals[mesh.face_normals_idx].view(-1, 3))
material_textures.write_to_usd(out_path, f'/World/Looks/{test_name}', bound_prims=[prim])
mat.write_to_usd(out_path, f'/World/Looks/{test_name}', bound_prims=[prim])
stage.Save()

# Confirm exported USD matches golden file
Expand All @@ -186,7 +233,7 @@ def _create_checkerboard(val1, val2):
os.pardir, 'samples/golden/pbr_material_textures.usda')
assert open(golden).read() == open(out_path).read()

def test_colorspace(self, out_dir, mesh, material_textures):
def test_colorspace(self, out_dir, mesh):
out_path = os.path.join(out_dir, 'colorspace_auto.usda')
stage = usd.create_stage(out_path)

Expand Down Expand Up @@ -218,6 +265,64 @@ def _create_checkerboard(val1, val2):
assert material_in.metallic_colorspace == 'auto'
assert material_in.roughness_colorspace == 'raw'

@pytest.mark.parametrize('device', [None, 'cuda:0'])
@pytest.mark.parametrize('non_blocking', [False, True])
def test_cuda(self, material_values, material_textures, device, non_blocking):
mat = kal_materials.PBRMaterial(**material_values, **material_textures)
cuda_mat = mat.cuda(device=device, non_blocking=non_blocking)
for param_name in _value_attributes + _texture_attributes:
val = getattr(mat, param_name)
cuda_val = getattr(cuda_mat, param_name)
if val is None:
assert cuda_val is None
else:
assert torch.equal(cuda_val, val.cuda())
assert val.is_cpu

for param_name in _misc_attributes:
assert getattr(mat, param_name) == getattr(cuda_mat, param_name)

def test_cpu(self, material_values, material_textures):
mat = kal_materials.PBRMaterial(**material_values, **material_textures)
# see test_cuda for guarantee that this is reliable
cuda_mat = mat.cuda()
cpu_mat = cuda_mat.cpu()
for param_name in _value_attributes + _texture_attributes:
cpu_val = getattr(cpu_mat, param_name)
cuda_val = getattr(cuda_mat, param_name)
if cuda_val is None:
assert cpu_val is None
else:
assert torch.equal(cpu_val, cuda_val.cpu())
assert cuda_val.is_cuda

for param_name in _misc_attributes:
assert getattr(cpu_mat, param_name) == getattr(cuda_mat, param_name)

def test_contiguous(self, material_values, material_textures):
strided_material_textures = {
k: torch.as_strided(v, (v.shape[0], int(v.shape[1] / 2), int(v.shape[2])), (1, 2, 2))
for k, v in material_textures.items()
}
mat = kal_materials.PBRMaterial(**material_values, **strided_material_textures)
contiguous_mat = mat.contiguous()
for param_name in _texture_attributes:
val = getattr(mat, param_name)
contiguous_val = getattr(contiguous_mat, param_name)
if contiguous_val is None:
assert contiguous_val is None
else:
assert torch.equal(contiguous_val, val.contiguous())
assert not val.is_contiguous()

for param_name in _value_attributes:
if contiguous_val is None:
assert contiguous_val is None
else:
assert torch.equal(getattr(mat, param_name), getattr(contiguous_mat, param_name))

for param_name in _misc_attributes:
assert getattr(mat, param_name) == getattr(contiguous_mat, param_name)

class TestUtilities:
@pytest.mark.parametrize('any_error_handler', [obj.skip_error_handler, obj.ignore_error_handler,
Expand Down Expand Up @@ -265,4 +370,4 @@ def test_process_materials_and_assignments(self, any_error_handler, material_ass
materials, material_assignments = kal_materials.process_materials_and_assignments(
materials_dict, material_assignments_dict, any_error_handler, num_faces)
assert [m['material_name'] for m in materials] == ['bricks', 'grass', 'kitties']
assert contained_torch_equal(materials[:2], expected_materials)
assert contained_torch_equal(materials[:2], expected_materials)
13 changes: 7 additions & 6 deletions tests/python/kaolin/io/usd/test_mesh.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2019, 20-21 NVIDIA CORPORATION & AFFILIATES.
# Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -297,6 +297,9 @@ def test_import_material_subsets(self, scene_paths, out_dir, hetero_subsets_mate
mesh = usd.import_mesh(path_or_stage, scene_path='/Root',
heterogeneous_mesh_handler=utils.mesh_handler_naive_triangulate,
with_normals=True, with_materials=True)
# avoid any automatic computation of normals
mesh.unset_attributes_return_none = True

# Confirm we now have a triangulated mesh
assert mesh.faces.size(1) == 3

Expand All @@ -311,6 +314,7 @@ def test_import_material_subsets(self, scene_paths, out_dir, hetero_subsets_mate
golden_mesh = usd.import_mesh(golden_path,
heterogeneous_mesh_handler=utils.mesh_handler_naive_triangulate,
with_normals=True, with_materials=True)
golden_mesh.unset_attributes_return_none = True

# Spot check against raw USD attributes
raw_attributes = read_raw_usd_attributes(path_or_stage)[0]
Expand All @@ -334,9 +338,8 @@ def test_import_material_subsets(self, scene_paths, out_dir, hetero_subsets_mate
assert torch.allclose(golden_mesh.vertices[mesh.faces[tri_idx, :], :], expected_vertices)
assert torch.allclose(golden_mesh.face_normals[tri_idx, ...], expected_normals)
assert torch.allclose(golden_mesh.uvs[mesh.face_uvs_idx[tri_idx, :], :], expected_uvs)

# Write the homogenized mesh to file
out_path = os.path.join(out_dir, 'homogenized_materials.usda')
out_path = os.path.join(out_dir, 'rocket_homogenized_materials.usda')
if function_variant == 'export_mesh':
usd.export_mesh(out_path, '/World/Rocket', vertices=mesh.vertices, faces=mesh.faces,
face_uvs_idx=mesh.face_uvs_idx, face_normals=mesh.face_normals, uvs=mesh.uvs,
Expand All @@ -352,12 +355,10 @@ def test_import_material_subsets(self, scene_paths, out_dir, hetero_subsets_mate

# Confirm we read identical mesh after writing
reimported_mesh = usd.import_mesh(out_path, scene_path='/World/Rocket', with_materials=True, with_normals=True)
reimported_mesh.unset_attributes_return_none = True

# Since comparison of materials is not implemented, we override materials with diffuse colors first
assert len(mesh.materials) == len(reimported_mesh.materials)
for i in range(len(mesh.materials)):
mesh.materials[i] = mesh.materials[i].diffuse_color
reimported_mesh.materials[i] = reimported_mesh.materials[i].diffuse_color
assert contained_torch_equal(mesh, reimported_mesh, print_error_context='')

@pytest.mark.parametrize('input_stage', [False, True])
Expand Down
File renamed without changes
Binary file modified tests/samples/golden/metallic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/samples/golden/normals.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 1a73983

Please sign in to comment.