|
|
|
|
|
|
|
|
|
|
|
|
|
import unittest |
|
|
|
import numpy as np |
|
import torch |
|
from pytorch3d.renderer.lighting import AmbientLights, DirectionalLights, PointLights |
|
from pytorch3d.transforms import RotateAxisAngle |
|
|
|
from .common_testing import TestCaseMixin |
|
|
|
|
|
class TestLights(TestCaseMixin, unittest.TestCase): |
|
def test_init_lights(self): |
|
""" |
|
Initialize Lights class with the default values. |
|
""" |
|
device = torch.device("cuda:0") |
|
light = DirectionalLights(device=device) |
|
keys = ["ambient_color", "diffuse_color", "specular_color", "direction"] |
|
for k in keys: |
|
prop = getattr(light, k) |
|
self.assertTrue(torch.is_tensor(prop)) |
|
self.assertTrue(prop.device == device) |
|
self.assertTrue(prop.shape == (1, 3)) |
|
|
|
light = PointLights(device=device) |
|
keys = ["ambient_color", "diffuse_color", "specular_color", "location"] |
|
for k in keys: |
|
prop = getattr(light, k) |
|
self.assertTrue(torch.is_tensor(prop)) |
|
self.assertTrue(prop.device == device) |
|
self.assertTrue(prop.shape == (1, 3)) |
|
|
|
def test_lights_clone_to(self): |
|
device = torch.device("cuda:0") |
|
cpu = torch.device("cpu") |
|
light = DirectionalLights() |
|
new_light = light.clone().to(device) |
|
keys = ["ambient_color", "diffuse_color", "specular_color", "direction"] |
|
for k in keys: |
|
prop = getattr(light, k) |
|
new_prop = getattr(new_light, k) |
|
self.assertTrue(prop.device == cpu) |
|
self.assertTrue(new_prop.device == device) |
|
self.assertSeparate(new_prop, prop) |
|
|
|
light = PointLights() |
|
new_light = light.clone().to(device) |
|
keys = ["ambient_color", "diffuse_color", "specular_color", "location"] |
|
for k in keys: |
|
prop = getattr(light, k) |
|
new_prop = getattr(new_light, k) |
|
self.assertTrue(prop.device == cpu) |
|
self.assertTrue(new_prop.device == device) |
|
self.assertSeparate(new_prop, prop) |
|
|
|
def test_lights_accessor(self): |
|
d_light = DirectionalLights(ambient_color=((0.0, 0.0, 0.0), (1.0, 1.0, 1.0))) |
|
p_light = PointLights(ambient_color=((0.0, 0.0, 0.0), (1.0, 1.0, 1.0))) |
|
for light in [d_light, p_light]: |
|
|
|
color = (0.5, 0.5, 0.5) |
|
light[1].ambient_color = color |
|
self.assertClose(light.ambient_color[1], torch.tensor(color)) |
|
|
|
l0 = light[0] |
|
self.assertClose(l0.ambient_color, torch.tensor((0.0, 0.0, 0.0))) |
|
|
|
def test_initialize_lights_broadcast(self): |
|
light = DirectionalLights( |
|
ambient_color=torch.randn(10, 3), |
|
diffuse_color=torch.randn(1, 3), |
|
specular_color=torch.randn(1, 3), |
|
) |
|
keys = ["ambient_color", "diffuse_color", "specular_color", "direction"] |
|
for k in keys: |
|
prop = getattr(light, k) |
|
self.assertTrue(prop.shape == (10, 3)) |
|
|
|
light = PointLights( |
|
ambient_color=torch.randn(10, 3), |
|
diffuse_color=torch.randn(1, 3), |
|
specular_color=torch.randn(1, 3), |
|
) |
|
keys = ["ambient_color", "diffuse_color", "specular_color", "location"] |
|
for k in keys: |
|
prop = getattr(light, k) |
|
self.assertTrue(prop.shape == (10, 3)) |
|
|
|
def test_initialize_lights_broadcast_fail(self): |
|
""" |
|
Batch dims have to be the same or 1. |
|
""" |
|
with self.assertRaises(ValueError): |
|
DirectionalLights( |
|
ambient_color=torch.randn(10, 3), diffuse_color=torch.randn(15, 3) |
|
) |
|
|
|
with self.assertRaises(ValueError): |
|
PointLights( |
|
ambient_color=torch.randn(10, 3), diffuse_color=torch.randn(15, 3) |
|
) |
|
|
|
def test_initialize_lights_dimensions_fail(self): |
|
""" |
|
Color should have shape (N, 3) or (1, 3) |
|
""" |
|
with self.assertRaises(ValueError): |
|
DirectionalLights(ambient_color=torch.randn(10, 4)) |
|
|
|
with self.assertRaises(ValueError): |
|
DirectionalLights(direction=torch.randn(10, 4)) |
|
|
|
with self.assertRaises(ValueError): |
|
PointLights(ambient_color=torch.randn(10, 4)) |
|
|
|
with self.assertRaises(ValueError): |
|
PointLights(location=torch.randn(10, 4)) |
|
|
|
def test_initialize_ambient(self): |
|
N = 13 |
|
color = 0.8 * torch.ones((N, 3)) |
|
lights = AmbientLights(ambient_color=color) |
|
self.assertEqual(len(lights), N) |
|
self.assertClose(lights.ambient_color, color) |
|
|
|
lights = AmbientLights(ambient_color=color[:1]) |
|
self.assertEqual(len(lights), 1) |
|
self.assertClose(lights.ambient_color, color[:1]) |
|
|
|
|
|
class TestDiffuseLighting(TestCaseMixin, unittest.TestCase): |
|
def test_diffuse_directional_lights(self): |
|
""" |
|
Test with a single point where: |
|
1) the normal and light direction are 45 degrees apart. |
|
2) the normal and light direction are 90 degrees apart. The output |
|
should be zero for this case |
|
""" |
|
color = torch.tensor([1, 1, 1], dtype=torch.float32) |
|
direction = torch.tensor( |
|
[0, 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32 |
|
) |
|
normals = torch.tensor([0, 0, 1], dtype=torch.float32) |
|
normals = normals[None, None, :] |
|
expected_output = torch.tensor( |
|
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32 |
|
) |
|
expected_output = expected_output.view(1, 1, 3).repeat(3, 1, 1) |
|
light = DirectionalLights(diffuse_color=color, direction=direction) |
|
output_light = light.diffuse(normals=normals) |
|
self.assertClose(output_light, expected_output) |
|
|
|
|
|
direction = torch.tensor([0, 1, 0], dtype=torch.float32) |
|
light.direction = direction |
|
expected_output = torch.zeros_like(expected_output) |
|
output_light = light.diffuse(normals=normals) |
|
self.assertClose(output_light, expected_output) |
|
|
|
def test_diffuse_point_lights(self): |
|
""" |
|
Test with a single point at the origin. Test two cases: |
|
1) the point light is at (1, 0, 1) hence the light direction is 45 |
|
degrees apart from the normal direction |
|
1) the point light is at (0, 1, 0) hence the light direction is 90 |
|
degrees apart from the normal direction. The output |
|
should be zero for this case |
|
""" |
|
color = torch.tensor([1, 1, 1], dtype=torch.float32) |
|
location = torch.tensor( |
|
[0, 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32 |
|
) |
|
points = torch.tensor([0, 0, 0], dtype=torch.float32) |
|
normals = torch.tensor([0, 0, 1], dtype=torch.float32) |
|
expected_output = torch.tensor( |
|
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32 |
|
) |
|
expected_output = expected_output.view(-1, 1, 3) |
|
light = PointLights(diffuse_color=color[None, :], location=location[None, :]) |
|
output_light = light.diffuse( |
|
points=points[None, None, :], normals=normals[None, None, :] |
|
) |
|
self.assertClose(output_light, expected_output) |
|
|
|
|
|
location = torch.tensor([0, 1, 0], dtype=torch.float32) |
|
expected_output = torch.zeros_like(expected_output) |
|
light = PointLights(diffuse_color=color[None, :], location=location[None, :]) |
|
output_light = light.diffuse( |
|
points=points[None, None, :], normals=normals[None, None, :] |
|
) |
|
self.assertClose(output_light, expected_output) |
|
|
|
def test_diffuse_batched(self): |
|
""" |
|
Test with a batch where each batch element has one point |
|
where the normal and light direction are 45 degrees apart. |
|
""" |
|
batch_size = 10 |
|
color = torch.tensor([1, 1, 1], dtype=torch.float32) |
|
direction = torch.tensor( |
|
[0, 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32 |
|
) |
|
normals = torch.tensor([0, 0, 1], dtype=torch.float32) |
|
expected_out = torch.tensor( |
|
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32 |
|
) |
|
|
|
|
|
direction = direction.view(-1, 3).expand(batch_size, -1) |
|
normals = normals.view(-1, 1, 3).expand(batch_size, -1, -1) |
|
color = color.view(-1, 3).expand(batch_size, -1) |
|
expected_out = expected_out.view(-1, 1, 3).expand(batch_size, 1, 3) |
|
|
|
lights = DirectionalLights(diffuse_color=color, direction=direction) |
|
output_light = lights.diffuse(normals=normals) |
|
self.assertClose(output_light, expected_out) |
|
|
|
def test_diffuse_batched_broadcast_inputs(self): |
|
""" |
|
Test with a batch where each batch element has one point |
|
where the normal and light direction are 45 degrees apart. |
|
The color and direction are the same for each batch element. |
|
""" |
|
batch_size = 10 |
|
color = torch.tensor([1, 1, 1], dtype=torch.float32) |
|
direction = torch.tensor( |
|
[0, 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32 |
|
) |
|
normals = torch.tensor([0, 0, 1], dtype=torch.float32) |
|
expected_out = torch.tensor( |
|
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32 |
|
) |
|
|
|
|
|
normals = normals.view(-1, 1, 3).expand(batch_size, -1, -1) |
|
expected_out = expected_out.view(-1, 1, 3).expand(batch_size, 1, 3) |
|
|
|
|
|
|
|
direction = direction.view(1, 3) |
|
color = color.view(1, 3) |
|
|
|
lights = DirectionalLights(diffuse_color=color, direction=direction) |
|
output_light = lights.diffuse(normals=normals) |
|
self.assertClose(output_light, expected_out) |
|
|
|
def test_diffuse_batched_arbitrary_input_dims(self): |
|
""" |
|
Test with a batch of inputs where shape of the input is mimicking the |
|
shape in a shading function i.e. an interpolated normal per pixel for |
|
top K faces per pixel. |
|
""" |
|
N, H, W, K = 16, 256, 256, 100 |
|
device = torch.device("cuda:0") |
|
color = torch.tensor([1, 1, 1], dtype=torch.float32, device=device) |
|
direction = torch.tensor( |
|
[0, 1 / np.sqrt(2), 1 / np.sqrt(2)], dtype=torch.float32, device=device |
|
) |
|
normals = torch.tensor([0, 0, 1], dtype=torch.float32, device=device) |
|
normals = normals.view(1, 1, 1, 1, 3).expand(N, H, W, K, -1) |
|
direction = direction.view(1, 3) |
|
color = color.view(1, 3) |
|
expected_output = torch.tensor( |
|
[1 / np.sqrt(2), 1 / np.sqrt(2), 1 / np.sqrt(2)], |
|
dtype=torch.float32, |
|
device=device, |
|
) |
|
expected_output = expected_output.view(1, 1, 1, 1, 3) |
|
expected_output = expected_output.expand(N, H, W, K, -1) |
|
|
|
lights = DirectionalLights(diffuse_color=color, direction=direction) |
|
output_light = lights.diffuse(normals=normals) |
|
self.assertClose(output_light, expected_output) |
|
|
|
def test_diffuse_batched_packed(self): |
|
""" |
|
Test with a batch of 2 meshes each of which has faces on a single plane. |
|
The normal and light direction are 45 degrees apart for the first mesh |
|
and 90 degrees apart for the second mesh. |
|
|
|
The points and normals are in the packed format i.e. no batch dimension. |
|
""" |
|
verts_packed = torch.rand((10, 3)) |
|
faces_per_mesh = [6, 4] |
|
mesh_to_vert_idx = [0] * faces_per_mesh[0] + [1] * faces_per_mesh[1] |
|
mesh_to_vert_idx = torch.tensor(mesh_to_vert_idx, dtype=torch.int64) |
|
color = torch.tensor([[1, 1, 1], [1, 1, 1]], dtype=torch.float32) |
|
direction = torch.tensor( |
|
[ |
|
[0, 1 / np.sqrt(2), 1 / np.sqrt(2)], |
|
[0, 1, 0], |
|
], |
|
dtype=torch.float32, |
|
) |
|
normals = torch.tensor([[0, 0, 1], [0, 0, 1]], dtype=torch.float32) |
|
expected_output = torch.zeros_like(verts_packed, dtype=torch.float32) |
|
expected_output[:6, :] += 1 / np.sqrt(2) |
|
expected_output[6:, :] = 0.0 |
|
lights = DirectionalLights( |
|
diffuse_color=color[mesh_to_vert_idx, :], |
|
direction=direction[mesh_to_vert_idx, :], |
|
) |
|
output_light = lights.diffuse(normals=normals[mesh_to_vert_idx, :]) |
|
self.assertClose(output_light, expected_output) |
|
|
|
|
|
class TestSpecularLighting(TestCaseMixin, unittest.TestCase): |
|
def test_specular_directional_lights(self): |
|
""" |
|
Specular highlights depend on the camera position as well as the light |
|
position/direction. |
|
Test with a single point where: |
|
1) the normal and light direction are -45 degrees apart and the normal |
|
and camera position are +45 degrees apart. The reflected light ray |
|
will be perfectly aligned with the camera so the output is 1.0. |
|
2) the normal and light direction are -45 degrees apart and the |
|
camera position is behind the point. The output should be zero for |
|
this case. |
|
""" |
|
color = torch.tensor([1, 0, 1], dtype=torch.float32) |
|
direction = torch.tensor( |
|
[-1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
camera_position = torch.tensor( |
|
[+1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
points = torch.tensor([0, 0, 0], dtype=torch.float32) |
|
normals = torch.tensor([0, 1, 0], dtype=torch.float32) |
|
expected_output = torch.tensor([1.0, 0.0, 1.0], dtype=torch.float32) |
|
expected_output = expected_output.view(1, 1, 3).repeat(3, 1, 1) |
|
lights = DirectionalLights(specular_color=color, direction=direction) |
|
output_light = lights.specular( |
|
points=points[None, None, :], |
|
normals=normals[None, None, :], |
|
camera_position=camera_position[None, :], |
|
shininess=torch.tensor(10), |
|
) |
|
self.assertClose(output_light, expected_output) |
|
|
|
|
|
camera_position = torch.tensor( |
|
[+1 / np.sqrt(2), -1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
expected_output = torch.zeros_like(expected_output) |
|
output_light = lights.specular( |
|
points=points[None, None, :], |
|
normals=normals[None, None, :], |
|
camera_position=camera_position[None, :], |
|
shininess=torch.tensor(10), |
|
) |
|
self.assertClose(output_light, expected_output) |
|
|
|
def test_specular_point_lights(self): |
|
""" |
|
Replace directional lights with point lights and check the output |
|
is the same. |
|
|
|
Test an additional case where the angle between the light reflection |
|
direction and the view direction is 30 degrees. |
|
""" |
|
color = torch.tensor([1, 0, 1], dtype=torch.float32) |
|
location = torch.tensor([-1, 1, 0], dtype=torch.float32) |
|
camera_position = torch.tensor( |
|
[+1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
points = torch.tensor([0, 0, 0], dtype=torch.float32) |
|
normals = torch.tensor([0, 1, 0], dtype=torch.float32) |
|
expected_output = torch.tensor([1.0, 0.0, 1.0], dtype=torch.float32) |
|
expected_output = expected_output.view(-1, 1, 3) |
|
lights = PointLights(specular_color=color[None, :], location=location[None, :]) |
|
output_light = lights.specular( |
|
points=points[None, None, :], |
|
normals=normals[None, None, :], |
|
camera_position=camera_position[None, :], |
|
shininess=torch.tensor(10), |
|
) |
|
self.assertClose(output_light, expected_output) |
|
|
|
|
|
camera_position = torch.tensor( |
|
[+1 / np.sqrt(2), -1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
expected_output = torch.zeros_like(expected_output) |
|
output_light = lights.specular( |
|
points=points[None, None, :], |
|
normals=normals[None, None, :], |
|
camera_position=camera_position[None, :], |
|
shininess=torch.tensor(10), |
|
) |
|
self.assertClose(output_light, expected_output) |
|
|
|
|
|
camera_position = torch.tensor( |
|
[+1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
rotate_30 = RotateAxisAngle(-30, axis="z") |
|
camera_position = rotate_30.transform_points(camera_position[None, :]) |
|
expected_output = torch.tensor( |
|
[np.cos(30.0 * np.pi / 180), 0.0, np.cos(30.0 * np.pi / 180)], |
|
dtype=torch.float32, |
|
) |
|
expected_output = expected_output.view(-1, 1, 3) |
|
output_light = lights.specular( |
|
points=points[None, None, :], |
|
normals=normals[None, None, :], |
|
camera_position=camera_position[None, :], |
|
shininess=torch.tensor(10), |
|
) |
|
self.assertClose(output_light, expected_output**10) |
|
|
|
def test_specular_batched(self): |
|
batch_size = 10 |
|
color = torch.tensor([1, 0, 1], dtype=torch.float32) |
|
direction = torch.tensor( |
|
[-1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
camera_position = torch.tensor( |
|
[+1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
points = torch.tensor([0, 0, 0], dtype=torch.float32) |
|
normals = torch.tensor([0, 1, 0], dtype=torch.float32) |
|
expected_out = torch.tensor([1.0, 0.0, 1.0], dtype=torch.float32) |
|
|
|
|
|
direction = direction.view(1, 3).expand(batch_size, -1) |
|
camera_position = camera_position.view(1, 3).expand(batch_size, -1) |
|
normals = normals.view(1, 1, 3).expand(batch_size, -1, -1) |
|
points = points.view(1, 1, 3).expand(batch_size, -1, -1) |
|
color = color.view(1, 3).expand(batch_size, -1) |
|
expected_out = expected_out.view(1, 1, 3).expand(batch_size, 1, 3) |
|
|
|
lights = DirectionalLights(specular_color=color, direction=direction) |
|
output_light = lights.specular( |
|
points=points, |
|
normals=normals, |
|
camera_position=camera_position, |
|
shininess=torch.tensor(10), |
|
) |
|
self.assertClose(output_light, expected_out) |
|
|
|
def test_specular_batched_broadcast_inputs(self): |
|
batch_size = 10 |
|
color = torch.tensor([1, 0, 1], dtype=torch.float32) |
|
direction = torch.tensor( |
|
[-1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
camera_position = torch.tensor( |
|
[+1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
points = torch.tensor([0, 0, 0], dtype=torch.float32) |
|
normals = torch.tensor([0, 1, 0], dtype=torch.float32) |
|
expected_out = torch.tensor([1.0, 0.0, 1.0], dtype=torch.float32) |
|
|
|
|
|
normals = normals.view(1, 1, 3).expand(batch_size, -1, -1) |
|
points = points.view(1, 1, 3).expand(batch_size, -1, -1) |
|
expected_out = expected_out.view(1, 1, 3).expand(batch_size, 1, 3) |
|
|
|
|
|
|
|
direction = direction.view(1, 3) |
|
camera_position = camera_position.view(1, 3) |
|
color = color.view(1, 3) |
|
|
|
lights = DirectionalLights(specular_color=color, direction=direction) |
|
output_light = lights.specular( |
|
points=points, |
|
normals=normals, |
|
camera_position=camera_position, |
|
shininess=torch.tensor(10), |
|
) |
|
self.assertClose(output_light, expected_out) |
|
|
|
def test_specular_batched_arbitrary_input_dims(self): |
|
""" |
|
Test with a batch of inputs where shape of the input is mimicking the |
|
shape expected after rasterization i.e. a normal per pixel for |
|
top K faces per pixel. |
|
""" |
|
device = torch.device("cuda:0") |
|
N, H, W, K = 8, 128, 128, 100 |
|
color = torch.tensor([1, 0, 1], dtype=torch.float32, device=device) |
|
direction = torch.tensor( |
|
[-1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
camera_position = torch.tensor( |
|
[+1 / np.sqrt(2), 1 / np.sqrt(2), 0], dtype=torch.float32 |
|
) |
|
points = torch.tensor([0, 0, 0], dtype=torch.float32, device=device) |
|
normals = torch.tensor([0, 1, 0], dtype=torch.float32, device=device) |
|
points = points.view(1, 1, 1, 1, 3).expand(N, H, W, K, 3) |
|
normals = normals.view(1, 1, 1, 1, 3).expand(N, H, W, K, 3) |
|
|
|
direction = direction.view(1, 3) |
|
color = color.view(1, 3) |
|
camera_position = camera_position.view(1, 3) |
|
|
|
expected_output = torch.tensor( |
|
[1.0, 0.0, 1.0], dtype=torch.float32, device=device |
|
) |
|
expected_output = expected_output.view(-1, 1, 1, 1, 3) |
|
expected_output = expected_output.expand(N, H, W, K, -1) |
|
|
|
lights = DirectionalLights(specular_color=color, direction=direction) |
|
output_light = lights.specular( |
|
points=points, |
|
normals=normals, |
|
camera_position=camera_position, |
|
shininess=10.0, |
|
) |
|
self.assertClose(output_light, expected_output) |
|
|
|
def test_specular_batched_packed(self): |
|
""" |
|
Test with a batch of 2 meshes each of which has faces on a single plane. |
|
The points and normals are in the packed format i.e. no batch dimension. |
|
""" |
|
faces_per_mesh = [6, 4] |
|
mesh_to_vert_idx = [0] * faces_per_mesh[0] + [1] * faces_per_mesh[1] |
|
mesh_to_vert_idx = torch.tensor(mesh_to_vert_idx, dtype=torch.int64) |
|
color = torch.tensor([[1, 1, 1], [1, 0, 1]], dtype=torch.float32) |
|
direction = torch.tensor( |
|
[[-1 / np.sqrt(2), 1 / np.sqrt(2), 0], [-1, 1, 0]], dtype=torch.float32 |
|
) |
|
camera_position = torch.tensor( |
|
[ |
|
[+1 / np.sqrt(2), 1 / np.sqrt(2), 0], |
|
[+1 / np.sqrt(2), -1 / np.sqrt(2), 0], |
|
], |
|
dtype=torch.float32, |
|
) |
|
points = torch.tensor([[0, 0, 0]], dtype=torch.float32) |
|
normals = torch.tensor([[0, 1, 0], [0, 1, 0]], dtype=torch.float32) |
|
expected_output = torch.zeros((10, 3), dtype=torch.float32) |
|
expected_output[:6, :] += 1.0 |
|
|
|
lights = DirectionalLights( |
|
specular_color=color[mesh_to_vert_idx, :], |
|
direction=direction[mesh_to_vert_idx, :], |
|
) |
|
output_light = lights.specular( |
|
points=points.view(-1, 3).expand(10, -1), |
|
normals=normals.view(-1, 3)[mesh_to_vert_idx, :], |
|
camera_position=camera_position[mesh_to_vert_idx, :], |
|
shininess=10.0, |
|
) |
|
self.assertClose(output_light, expected_output) |
|
|