Skip to content

Commit

Permalink
Fix softmax_rgb_blend() when mesh is outside zfar
Browse files Browse the repository at this point in the history
Summary:
This fixes two small issues with blending.py:softmax_rgb_blend():
  1) zfar and znear attributes are propagated from the camera settings instead of just using default settings of znear=1.0 and zfar=100.0
  2) A check is added to prevent arithmetic overflow in softmax_rgb_blend()

This is a fix in response to #334
where meshes rendererd using a SoftPhongShader with faces_per_pixel=1 appear black.  This only occurs when the scale of the mesh is large (vertex values > 100, where 100 is the default value of zfar).  This fix allows the caller to increase the value of cameras.zfar to match the scale of her/his mesh.

Reviewed By: nikhilaravi

Differential Revision: D23517541

fbshipit-source-id: ab8631ce9e5f2149f140b67b13eff857771b8807
  • Loading branch information
Steve Branson authored and facebook-github-bot committed Sep 9, 2020
1 parent 6eb158e commit f8ea590
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 3 deletions.
2 changes: 1 addition & 1 deletion pytorch3d/renderer/blending.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def softmax_rgb_blend(
z_inv = (zfar - fragments.zbuf) / (zfar - znear) * mask
# pyre-fixme[16]: `Tuple` has no attribute `values`.
# pyre-fixme[6]: Expected `Tensor` for 1st param but got `float`.
z_inv_max = torch.max(z_inv, dim=-1).values[..., None]
z_inv_max = torch.max(z_inv, dim=-1).values[..., None].clamp(min=eps)
# pyre-fixme[6]: Expected `Tensor` for 1st param but got `float`.
weights_num = prob_map * torch.exp((z_inv - z_inv_max) / blend_params.gamma)

Expand Down
12 changes: 10 additions & 2 deletions pytorch3d/renderer/mesh/shader.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ def forward(self, fragments, meshes, **kwargs) -> torch.Tensor:
cameras=cameras,
materials=materials,
)
images = softmax_rgb_blend(colors, fragments, blend_params)
znear = kwargs.get("znear", getattr(cameras, "znear", 1.0))
zfar = kwargs.get("zfar", getattr(cameras, "zfar", 100.0))
images = softmax_rgb_blend(
colors, fragments, blend_params, znear=znear, zfar=zfar
)
return images


Expand Down Expand Up @@ -214,7 +218,11 @@ def forward(self, fragments, meshes, **kwargs) -> torch.Tensor:
cameras=cameras,
materials=materials,
)
images = softmax_rgb_blend(pixel_colors, fragments, self.blend_params)
znear = kwargs.get("znear", getattr(cameras, "znear", 1.0))
zfar = kwargs.get("zfar", getattr(cameras, "zfar", 100.0))
images = softmax_rgb_blend(
pixel_colors, fragments, self.blend_params, znear=znear, zfar=zfar
)
return images


Expand Down
Binary file added tests/data/test_simple_sphere_outside_zfar_100.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions tests/test_render_meshes.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
HardFlatShader,
HardGouraudShader,
HardPhongShader,
SoftPhongShader,
SoftSilhouetteShader,
TexturedSoftPhongShader,
)
Expand Down Expand Up @@ -981,3 +982,63 @@ def test_texture_map_atlas(self):
)

self.assertClose(rgb, image_ref, atol=0.05)

def test_simple_sphere_outside_zfar(self):
"""
Test output when rendering a sphere that is beyond zfar with a SoftPhongShader.
This renders a sphere of radius 500, with the camera at x=1500 for different
settings of zfar. This is intended to check 1) setting cameras.zfar propagates
to the blender and that the rendered sphere is (soft) clipped if it is beyond
zfar, 2) make sure there are no numerical precision/overflow errors associated
with larger world coordinates
"""
device = torch.device("cuda:0")

# Init mesh
sphere_mesh = ico_sphere(5, device)
verts_padded = sphere_mesh.verts_padded() * 500
faces_padded = sphere_mesh.faces_padded()
feats = torch.ones_like(verts_padded, device=device)
textures = TexturesVertex(verts_features=feats)
sphere_mesh = Meshes(verts=verts_padded, faces=faces_padded, textures=textures)

R, T = look_at_view_transform(1500, 0.0, 0.0)

# Init shader settings
materials = Materials(device=device)
lights = PointLights(device=device)
lights.location = torch.tensor([0.0, 0.0, +1000.0], device=device)[None]

raster_settings = RasterizationSettings(
image_size=256, blur_radius=0.0, faces_per_pixel=1
)
for zfar in (10000.0, 100.0):
cameras = FoVPerspectiveCameras(
device=device, R=R, T=T, aspect_ratio=1.0, fov=60.0, zfar=zfar
)
rasterizer = MeshRasterizer(
cameras=cameras, raster_settings=raster_settings
)
blend_params = BlendParams(1e-4, 1e-4, (0, 0, 1.0))

shader = SoftPhongShader(
lights=lights,
cameras=cameras,
materials=materials,
blend_params=blend_params,
)
renderer = MeshRenderer(rasterizer=rasterizer, shader=shader)
images = renderer(sphere_mesh)
rgb = images[0, ..., :3].squeeze().cpu()

filename = "test_simple_sphere_outside_zfar_%d.png" % int(zfar)

# Load reference image
image_ref = load_rgb_image(filename, DATA_DIR)

if DEBUG:
Image.fromarray((rgb.numpy() * 255).astype(np.uint8)).save(
DATA_DIR / ("DEBUG_" + filename)
)

self.assertClose(rgb, image_ref, atol=0.05)

0 comments on commit f8ea590

Please sign in to comment.