Source code for graphics.Lighting

# Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 
# 
# Soft Rasterizer (SoftRas)
# 
# Copyright (c) 2017 Hiroharu Kato
# Copyright (c) 2018 Nikos Kolotouros
# Copyright (c) 2019 Shichen Liu
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import torch
import torch.nn.functional as F


def compute_ambient_light(
        face_vertices: torch.Tensor,
        textures: torch.Tensor,
        ambient_intensity: float = 1.,
        ambient_color: torch.Tensor = torch.ones(3)):
    r"""Computes ambient lighting to a mesh, given faces and face textures.

    Args:
        face_vertices (torch.Tensor): A tensor containing a list of (per-face)
            vertices of the mesh (shape: `B` :math:`\times` `num_faces`
            :math:`\times 9`). Here, :math:`B` is the batchsize, `num_faces`
            is the number of faces in the mesh, and since each face is assumed
            to be a triangle, it has 3 vertices, and hence 9 coordinates in
            total.
        textures (torch.Tensor): TODO: Add docstring
        ambient_intensity (float): Intensity of ambient light (in the range
            :math:`\left[0, 1\right]`). If the values provided are outside
            this range, we clip them so that they fall in range.
        ambient_color (torch.Tensor): Color of the ambient light (R, G, B)
            (shape: :math:`3`)

    Returns:
        light (torch.Tensor): A light tensor, which can be elementwise
            multiplied with the textures, to obtain the mesh with lighting
            applied (shape: `B` :math:`\times` `num_faces` :math:`\times
            1 \times 1 \times 1 \times 3`)

    """

    if not torch.is_tensor(face_vertices):
        raise TypeError('Expected input face_vertices to be of type '
                        'torch.Tensor. Got {0} instead.'.format(
                            type(face_vertices)))
    if not torch.is_tensor(textures):
        raise TypeError('Expected input textures to be of type '
                        'torch.Tensor. Got {0} instead.'.format(
                            type(textures)))
    if not isinstance(ambient_intensity, float) and not isinstance(
            ambient_intensity, int):
        raise TypeError('Expected input ambient_intensity to be of '
                        'type float. Got {0} instead.'.format(
                            type(ambient_intensity)))
    if not torch.is_tensor(ambient_color):
        raise TypeError('Expected input ambient_color to be of type '
                        'torch.Tensor. Got {0} instead.'.format(
                            type(ambient_color)))

    # if face_vertices.dim() != 3 or face_vertices.shape[-1] != 9:
    #     raise ValueError('Input face_vertices must have 3 dimensions '
    #                      'and be of shape (..., ..., 9). Got {0} dimensions '
    #                      'and shape {1} instead.'.format(face_vertices.dim(),
    #                                                      face_vertices.shape))
    # TODO: check texture dims
    if ambient_color.dim() != 1 or ambient_color.shape != (3,):
        raise ValueError('Input ambient_color must have 1 dimension '
                         'and be of shape 3. Got {0} dimensions and shape {1} '
                         'instead.'.format(ambient_color.dim(), ambient_color.shape))

    # Clip ambient_intensity to be in the range [0, 1]
    if ambient_intensity < 0:
        ambient_intensity = 0.
    if ambient_intensity > 1:
        ambient_intensity = 1.

    batchsize = face_vertices.shape[0]
    num_faces = face_vertices.shape[1]
    device = face_vertices.device

    if ambient_color.dim() == 1:
        ambient_color = ambient_color[None, :].to(device)

    light = torch.zeros(batchsize, num_faces, 3).to(device)

    # If the intensity of the ambient light is 0, do not
    # bother computing lighting.
    if ambient_intensity == 0:
        return light

    # Ambient lighting is constant everywhere, and is given as
    # I = I_a * K_a
    # where,
    # I: Intensity at a vertex
    # I_a: Intensity of the ambient light
    # K_a: Ambient reflectance of the vertex (3 channels, R, G, B)
    light += ambient_intensity * ambient_color[:, None, :]

    return light[:, :, None, None, None, :]


[docs]def apply_ambient_light( face_vertices: torch.Tensor, textures: torch.Tensor, ambient_intensity: float = 1., ambient_color: torch.Tensor = torch.ones(3)): r"""Computes and applies ambient lighting to a mesh, given faces and face textures. Args: face_vertices (torch.Tensor): A tensor containing a list of (per-face) vertices of the mesh (shape: `B` :math:`\times` `num_faces` :math:`\times 9`). Here, :math:`B` is the batchsize, `num_faces` is the number of faces in the mesh, and since each face is assumed to be a triangle, it has 3 vertices, and hence 9 coordinates in total. textures (torch.Tensor): TODO: Add docstring ambient_intensity (float): Intensity of ambient light (in the range :math:`\left[0, 1\right]`). If the values provided are outside this range, we clip them so that they fall in range. ambient_color (torch.Tensor): Color of the ambient light (R, G, B) (shape: :math:`3`) Returns: textures (torch.Tensor): Updated textures, with ambient lighting applied (shape: same as input `textures`) #TODO: Update docstring """ light = compute_ambient_light(face_vertices, textures, ambient_intensity, ambient_color) return textures * light
def compute_directional_light( face_vertices: torch.Tensor, textures: torch.Tensor, directional_intensity: float = 1., directional_color: torch.Tensor = torch.ones(3), direction: torch.Tensor = torch.FloatTensor([0, 1, 0])): r"""Computes directional lighting to a mesh, given faces and face textures. Args: face_vertices (torch.Tensor): A tensor containing a list of (per-face) vertices of the mesh (shape: `B` :math:`\times` `num_faces` :math:`\times 9`). Here, :math:`B` is the batchsize, `num_faces` is the number of faces in the mesh, and since each face is assumed to be a triangle, it has 3 vertices, and hence 9 coordinates in total. textures (torch.Tensor): TODO: Add docstring directional_intensity (float): Intensity of directional light (in the range :math:`\left[0, 1\right]`). If the values provided are outside this range, we clip them so that they fall in range. directional_color (torch.Tensor): Color of the directional light (R, G, B) (shape: :math:`3`). direction (torch.Tensor): Direction of light from the light source. (default: :math:`\left( 0, 1, 0 \right)^T`) Returns: light (torch.Tensor): A light tensor, which can be elementwise multiplied with the textures, to obtain the mesh with lighting applied (shape: `B` :math:`\times` `num_faces` :math:`\times 1 \times 1 \times 1 \times 3`) """ if not torch.is_tensor(face_vertices): raise TypeError('Expected input face_vertices to be of type ' 'torch.Tensor. Got {0} instead.'.format( type(face_vertices))) if not torch.is_tensor(textures): raise TypeError('Expected input textures to be of type ' 'torch.Tensor. Got {0} instead.'.format( type(textures))) if not isinstance(directional_intensity, float) and not isinstance( directional_intensity, int): raise TypeError('Expected input directional_intensity to be of ' 'type float. Got {0} instead.'.format( type(directional_intensity))) if not torch.is_tensor(directional_color): raise TypeError('Expected input directional_color to be of type ' 'torch.Tensor. Got {0} instead.'.format( type(directional_color))) if not torch.is_tensor(direction): raise TypeError('Expected input direction to be of type ' 'torch.Tensor. Got {0} instead.'.format(type(direction))) # if face_vertices.dim() != 3 or face_vertices.shape[-1] != 9: # raise ValueError('Input face_vertices must have 3 dimensions ' # 'and be of shape (..., ..., 9). Got {0} dimensions ' # 'and shape {1} instead.'.format(face_vertices.dim(), # face_vertices.shape)) # TODO: check texture dims if directional_color.dim() != 1 or directional_color.shape != (3,): raise ValueError('Input directional_color must have 1 dimension ' 'and be of shape 3. Got {0} dimensions and shape {1} ' 'instead.'.format(directional_color.dim(), directional_color.shape)) if direction.dim() != 1 or direction.shape != (3,): raise ValueError('Input direction must have 1 dimension and be ' 'of shape 3. Got {0} dimensions and shape {1} ' 'instead.'.format(direction.dim(), direction.shape)) batchsize = face_vertices.shape[0] num_faces = face_vertices.shape[1] device = face_vertices.device if directional_color.dim() == 1: directional_color = directional_color[None, :].to(device) if direction.dim() == 1: direction = direction[None, :].to(device) # Clip directional intensity to be in the range [0, 1] if directional_intensity < 0: directional_intensity = 0. if directional_intensity > 1: directional_intensity = 1. # Initialize light to zeros light = torch.zeros(batchsize, num_faces, 3).to(device) # If the intensity of the directional light is 0, do not # bother computing lighting. if directional_intensity == 0: return light # Unwrap the batchsize dimension (for easier vectorization). faces = face_vertices.reshape(batchsize * num_faces, 3, 3) # Compute face normals. v10 = faces[:, 0] - faces[:, 1] v12 = faces[:, 2] - faces[:, 1] normals = F.normalize(torch.cross(v10, v12), eps=1e-5) # Reshape, to get back the batchsize dimension. normals = normals.reshape(batchsize, num_faces, 3) # Get direction to 3 dimensions, if not already there. if direction.dim() == 2: direction = direction[:, None, :] cos = F.relu(torch.sum(normals * direction, dim=2)) light += directional_intensity * (directional_color[:, None, :] * cos[:, :, None]) return light[:, :, None, None, None, :]
[docs]def apply_directional_light( face_vertices: torch.Tensor, textures: torch.Tensor, directional_intensity: float = 1., directional_color: torch.Tensor = torch.ones(3), direction: torch.Tensor = torch.FloatTensor([0, 1, 0])): r"""Computes and applies directional lighting to a mesh, given faces and face textures. Args: face_vertices (torch.Tensor): A tensor containing a list of (per-face) vertices of the mesh (shape: `B` :math:`\times` `num_faces` :math:`\times 9`). Here, :math:`B` is the batchsize, `num_faces` is the number of faces in the mesh, and since each face is assumed to be a triangle, it has 3 vertices, and hence 9 coordinates in total. textures (torch.Tensor): TODO: Add docstring directional_intensity (float): Intensity of directional light (in the range :math:`\left[0, 1\right]`). If the values provided are outside this range, we clip them so that they fall in range. directional_color (torch.Tensor): Color of the directional light (R, G, B) (shape: :math:`3`). direction (torch.Tensor): Direction of light from the light source. (default: :math:`\left( 0, 1, 0 \right)^T`) Returns: light (torch.Tensor): A light tensor, which can be elementwise multiplied with the textures, to obtain the mesh with lighting applied (shape: `B` :math:`\times` `num_faces` :math:`\times 1 \times 1 \times 1 \times 3`) """ light = compute_directional_light(face_vertices, textures, directional_intensity, directional_color, direction) return textures * light