def test_simple_sphere_batched(self): """ Test a mesh with vertex textures can be extended to form a batch, and is rendered correctly with Phong, Gouraud and Flat Shaders. """ batch_size = 20 device = torch.device("cuda:0") # Init mesh with vertex textures. sphere_meshes = ico_sphere(5, device).extend(batch_size) verts_padded = sphere_meshes.verts_padded() faces_padded = sphere_meshes.faces_padded() textures = Textures(verts_rgb=torch.ones_like(verts_padded)) sphere_meshes = Meshes(verts=verts_padded, faces=faces_padded, textures=textures) # Init rasterizer settings dist = torch.tensor([2.7]).repeat(batch_size).to(device) elev = torch.zeros_like(dist) azim = torch.zeros_like(dist) R, T = look_at_view_transform(dist, elev, azim) cameras = OpenGLPerspectiveCameras(device=device, R=R, T=T) raster_settings = RasterizationSettings(image_size=512, blur_radius=0.0, faces_per_pixel=1) # Init shader settings materials = Materials(device=device) lights = PointLights(device=device) lights.location = torch.tensor([0.0, 0.0, +2.0], device=device)[None] blend_params = BlendParams(1e-4, 1e-4, (0, 0, 0)) # Init renderer rasterizer = MeshRasterizer(cameras=cameras, raster_settings=raster_settings) shaders = { "phong": HardPhongShader, "gouraud": HardGouraudShader, "flat": HardFlatShader, } for (name, shader_init) in shaders.items(): shader = shader_init( lights=lights, cameras=cameras, materials=materials, blend_params=blend_params, ) renderer = MeshRenderer(rasterizer=rasterizer, shader=shader) images = renderer(sphere_meshes) image_ref = load_rgb_image( "test_simple_sphere_light_%s.png" % name, DATA_DIR) for i in range(batch_size): rgb = images[i, ..., :3].squeeze().cpu() self.assertClose(rgb, image_ref, atol=0.05)
def test_simple_sphere_batched(self): """ Test output of phong shading matches a reference image using the default values for the light sources. """ batch_size = 5 device = torch.device("cuda:0") # Init mesh sphere_meshes = ico_sphere(5, device).extend(batch_size) verts_padded = sphere_meshes.verts_padded() faces_padded = sphere_meshes.faces_padded() textures = Textures(verts_rgb=torch.ones_like(verts_padded)) sphere_meshes = Meshes( verts=verts_padded, faces=faces_padded, textures=textures ) # Init rasterizer settings dist = torch.tensor([2.7]).repeat(batch_size).to(device) elev = torch.zeros_like(dist) azim = torch.zeros_like(dist) R, T = look_at_view_transform(dist, elev, azim) cameras = OpenGLPerspectiveCameras(device=device, R=R, T=T) raster_settings = RasterizationSettings( image_size=512, blur_radius=0.0, faces_per_pixel=1, bin_size=0 ) # Init shader settings materials = Materials(device=device) lights = PointLights(device=device) lights.location = torch.tensor([0.0, 0.0, +2.0], device=device)[None] # Init renderer renderer = MeshRenderer( rasterizer=MeshRasterizer( cameras=cameras, raster_settings=raster_settings ), shader=HardPhongShader( lights=lights, cameras=cameras, materials=materials ), ) images = renderer(sphere_meshes) # Load ref image image_ref = load_rgb_image("test_simple_sphere_light.png") for i in range(batch_size): rgb = images[i, ..., :3].squeeze().cpu() if DEBUG: Image.fromarray((rgb.numpy() * 255).astype(np.uint8)).save( DATA_DIR / f"DEBUG_simple_sphere_{i}.png" ) self.assertTrue(torch.allclose(rgb, image_ref, atol=0.05))
def test_simple_sphere(self, elevated_camera=False): """ Test output of phong and gouraud shading matches a reference image using the default values for the light sources. Args: elevated_camera: Defines whether the camera observing the scene should have an elevation of 45 degrees. """ device = torch.device("cuda:0") # Init mesh sphere_mesh = ico_sphere(5, device) verts_padded = sphere_mesh.verts_padded() faces_padded = sphere_mesh.faces_padded() textures = Textures(verts_rgb=torch.ones_like(verts_padded)) sphere_mesh = Meshes(verts=verts_padded, faces=faces_padded, textures=textures) # Init rasterizer settings if elevated_camera: # Elevated and rotated camera R, T = look_at_view_transform(dist=2.7, elev=45.0, azim=45.0) postfix = "_elevated_camera" # If y axis is up, the spot of light should # be on the bottom left of the sphere. else: # No elevation or azimuth rotation R, T = look_at_view_transform(2.7, 0.0, 0.0) postfix = "" cameras = OpenGLPerspectiveCameras(device=device, R=R, T=T) # Init shader settings materials = Materials(device=device) lights = PointLights(device=device) lights.location = torch.tensor([0.0, 0.0, +2.0], device=device)[None] raster_settings = RasterizationSettings(image_size=512, blur_radius=0.0, faces_per_pixel=1, bin_size=0) rasterizer = MeshRasterizer(cameras=cameras, raster_settings=raster_settings) # Test several shaders shaders = { "phong": HardPhongShader, "gouraud": HardGouraudShader, "flat": HardFlatShader, } for (name, shader_init) in shaders.items(): shader = shader_init(lights=lights, cameras=cameras, materials=materials) renderer = MeshRenderer(rasterizer=rasterizer, shader=shader) images = renderer(sphere_mesh) filename = "simple_sphere_light_%s%s.png" % (name, postfix) image_ref = load_rgb_image("test_%s" % filename) rgb = images[0, ..., :3].squeeze().cpu() if DEBUG: filename = "DEBUG_" % filename Image.fromarray((rgb.numpy() * 255).astype(np.uint8)).save( DATA_DIR / filename) self.assertTrue(torch.allclose(rgb, image_ref, atol=0.05)) ######################################################## # Move the light to the +z axis in world space so it is # behind the sphere. Note that +Z is in, +Y up, # +X left for both world and camera space. ######################################################## lights.location[..., 2] = -2.0 phong_shader = HardPhongShader(lights=lights, cameras=cameras, materials=materials) phong_renderer = MeshRenderer(rasterizer=rasterizer, shader=phong_shader) images = phong_renderer(sphere_mesh, lights=lights) rgb = images[0, ..., :3].squeeze().cpu() if DEBUG: filename = "DEBUG_simple_sphere_dark%s.png" % postfix Image.fromarray( (rgb.numpy() * 255).astype(np.uint8)).save(DATA_DIR / filename) # Load reference image image_ref_phong_dark = load_rgb_image("test_simple_sphere_dark%s.png" % postfix) self.assertTrue(torch.allclose(rgb, image_ref_phong_dark, atol=0.05))
def test_joined_spheres(self): """ Test a list of Meshes can be joined as a single mesh and the single mesh is rendered correctly with Phong, Gouraud and Flat Shaders. """ device = torch.device("cuda:0") # Init mesh with vertex textures. # Initialize a list containing two ico spheres of different sizes. sphere_list = [ico_sphere(3, device), ico_sphere(4, device)] # [(42 verts, 80 faces), (162 verts, 320 faces)] # The scale the vertices need to be set at to resize the spheres scales = [0.25, 1] # The distance the spheres ought to be offset horizontally to prevent overlap. offsets = [1.2, -0.3] # Initialize a list containing the adjusted sphere meshes. sphere_mesh_list = [] for i in range(len(sphere_list)): verts = sphere_list[i].verts_padded() * scales[i] verts[0, :, 0] += offsets[i] sphere_mesh_list.append( Meshes(verts=verts, faces=sphere_list[i].faces_padded())) joined_sphere_mesh = join_mesh(sphere_mesh_list) joined_sphere_mesh.textures = Textures( verts_rgb=torch.ones_like(joined_sphere_mesh.verts_padded())) # Init rasterizer settings R, T = look_at_view_transform(2.7, 0.0, 0.0) cameras = OpenGLPerspectiveCameras(device=device, R=R, T=T) raster_settings = RasterizationSettings(image_size=512, blur_radius=0.0, faces_per_pixel=1) # Init shader settings materials = Materials(device=device) lights = PointLights(device=device) lights.location = torch.tensor([0.0, 0.0, +2.0], device=device)[None] blend_params = BlendParams(1e-4, 1e-4, (0, 0, 0)) # Init renderer rasterizer = MeshRasterizer(cameras=cameras, raster_settings=raster_settings) shaders = { "phong": HardPhongShader, "gouraud": HardGouraudShader, "flat": HardFlatShader, } for (name, shader_init) in shaders.items(): shader = shader_init( lights=lights, cameras=cameras, materials=materials, blend_params=blend_params, ) renderer = MeshRenderer(rasterizer=rasterizer, shader=shader) image = renderer(joined_sphere_mesh) rgb = image[..., :3].squeeze().cpu() if DEBUG: file_name = "DEBUG_joined_spheres_%s.png" % name Image.fromarray((rgb.numpy() * 255).astype(np.uint8)).save( DATA_DIR / file_name) image_ref = load_rgb_image("test_joined_spheres_%s.png" % name, DATA_DIR) self.assertClose(rgb, image_ref, atol=0.05)
def test_simple_sphere(self, elevated_camera=False): """ Test output of phong and gouraud shading matches a reference image using the default values for the light sources. Args: elevated_camera: Defines whether the camera observing the scene should have an elevation of 45 degrees. """ device = torch.device("cuda:0") # Init mesh sphere_mesh = ico_sphere(5, device) verts_padded = sphere_mesh.verts_padded() faces_padded = sphere_mesh.faces_padded() textures = Textures(verts_rgb=torch.ones_like(verts_padded)) sphere_mesh = Meshes(verts=verts_padded, faces=faces_padded, textures=textures) # Init rasterizer settings if elevated_camera: R, T = look_at_view_transform(2.7, 45.0, 0.0) postfix = "_elevated_camera" else: R, T = look_at_view_transform(2.7, 0.0, 0.0) postfix = "" cameras = OpenGLPerspectiveCameras(device=device, R=R, T=T) raster_settings = RasterizationSettings(image_size=512, blur_radius=0.0, faces_per_pixel=1, bin_size=0) # Init shader settings materials = Materials(device=device) lights = PointLights(device=device) lights.location = torch.tensor([0.0, 0.0, -2.0], device=device)[None] # Init renderer rasterizer = MeshRasterizer(cameras=cameras, raster_settings=raster_settings) renderer = MeshRenderer( rasterizer=rasterizer, shader=HardPhongShader(lights=lights, cameras=cameras, materials=materials), ) images = renderer(sphere_mesh) rgb = images[0, ..., :3].squeeze().cpu() if DEBUG: Image.fromarray((rgb.numpy() * 255).astype(np.uint8)).save( DATA_DIR / "DEBUG_simple_sphere_light%s.png" % postfix) # Load reference image image_ref_phong = load_rgb_image( "test_simple_sphere_illuminated%s.png" % postfix) self.assertTrue(torch.allclose(rgb, image_ref_phong, atol=0.05)) ################################### # Move the light behind the object ################################### # Check the image is dark lights.location[..., 2] = +2.0 images = renderer(sphere_mesh, lights=lights) rgb = images[0, ..., :3].squeeze().cpu() if DEBUG: Image.fromarray((rgb.numpy() * 255).astype(np.uint8)).save( DATA_DIR / "DEBUG_simple_sphere_dark%s.png" % postfix) # Load reference image image_ref_phong_dark = load_rgb_image("test_simple_sphere_dark%s.png" % postfix) self.assertTrue(torch.allclose(rgb, image_ref_phong_dark, atol=0.05)) ###################################### # Change the shader to a GouraudShader ###################################### lights.location = torch.tensor([0.0, 0.0, -2.0], device=device)[None] renderer = MeshRenderer( rasterizer=rasterizer, shader=HardGouraudShader(lights=lights, cameras=cameras, materials=materials), ) images = renderer(sphere_mesh) rgb = images[0, ..., :3].squeeze().cpu() if DEBUG: Image.fromarray((rgb.numpy() * 255).astype(np.uint8)).save( DATA_DIR / "DEBUG_simple_sphere_light_gouraud%s.png" % postfix) # Load reference image image_ref_gouraud = load_rgb_image( "test_simple_sphere_light_gouraud%s.png" % postfix) self.assertTrue(torch.allclose(rgb, image_ref_gouraud, atol=0.005)) self.assertFalse(torch.allclose(rgb, image_ref_phong, atol=0.005))
def test_texture_map(self): """ Test a mesh with a texture map is loaded and rendered correctly """ device = torch.device("cuda:0") DATA_DIR = ( Path(__file__).resolve().parent.parent / "docs/tutorials/data" ) obj_filename = DATA_DIR / "cow_mesh/cow.obj" # Load mesh + texture verts, faces, aux = load_obj(obj_filename) faces_idx = faces.verts_idx.to(device) verts = verts.to(device) texture_uvs = aux.verts_uvs materials = aux.material_colors tex_maps = aux.texture_images # tex_maps is a dictionary of material names as keys and texture images # as values. Only need the images for this example. textures = Textures( maps=list(tex_maps.values()), faces_uvs=faces.textures_idx.to(torch.int64).to(device)[None, :], verts_uvs=texture_uvs.to(torch.float32).to(device)[None, :], ) mesh = Meshes(verts=[verts], faces=[faces_idx], textures=textures) # Init rasterizer settings R, T = look_at_view_transform(2.7, 10, 20) cameras = OpenGLPerspectiveCameras(device=device, R=R, T=T) raster_settings = RasterizationSettings( image_size=512, blur_radius=0.0, faces_per_pixel=1, bin_size=0 ) # Init shader settings materials = Materials(device=device) lights = PointLights(device=device) lights.location = torch.tensor([0.0, 0.0, -2.0], device=device)[None] raster_settings = RasterizationSettings( image_size=512, blur_radius=0.0, faces_per_pixel=1, bin_size=0 ) # Init renderer renderer = MeshRenderer( rasterizer=MeshRasterizer( cameras=cameras, raster_settings=raster_settings ), shader=TexturedPhongShader( lights=lights, cameras=cameras, materials=materials ), ) images = renderer(mesh) rgb = images[0, ..., :3].squeeze().cpu() # Load reference image image_ref = load_rgb_image("test_texture_map.png") if DEBUG: Image.fromarray((rgb.numpy() * 255).astype(np.uint8)).save( DATA_DIR / "DEBUG_texture_map.png" ) # There's a calculation instability on the corner of the ear of the cow. # We ignore that pixel. image_ref[137, 166] = 0 rgb[137, 166] = 0 self.assertTrue(torch.allclose(rgb, image_ref, atol=0.05)) # Check grad exists verts = verts.clone() verts.requires_grad = True mesh = Meshes(verts=[verts], faces=[faces_idx], textures=textures) images = renderer(mesh) images[0, ...].sum().backward() self.assertIsNotNone(verts.grad) # Helpful comments below.# Helpful comments below.# Helpful comments below.# Helpful comments below.