| | import torch |
| | import numpy as np |
| |
|
| | from .scene import Light, Scene, Camera, dot_product, normalize, generate_normalized_random_direction, gamma_encode |
| |
|
| |
|
| | class Renderer: |
| | def __init__(self, return_params=False): |
| | self.use_augmentation = False |
| | self.return_params = return_params |
| |
|
| | def xi(self, x): |
| | return (x > 0.0) * torch.ones_like(x) |
| |
|
| | def compute_microfacet_distribution(self, roughness, NH): |
| | alpha = roughness**2 |
| | alpha_squared = alpha**2 |
| | NH_squared = NH**2 |
| | denominator_part = torch.clamp(NH_squared * (alpha_squared + (1 - NH_squared) / NH_squared), min=0.001) |
| | return (alpha_squared * self.xi(NH)) / (np.pi * denominator_part**2) |
| |
|
| | def compute_fresnel(self, F0, VH): |
| | |
| | return F0 + (1.0 - F0) * (1.0 - VH)**5 |
| |
|
| | def compute_g1(self, roughness, XH, XN): |
| | alpha = roughness**2 |
| | alpha_squared = alpha**2 |
| | XN_squared = XN**2 |
| | return 2 * self.xi(XH / XN) / (1 + torch.sqrt(1 + alpha_squared * (1.0 - XN_squared) / XN_squared)) |
| |
|
| | def compute_geometry(self, roughness, VH, LH, VN, LN): |
| | return self.compute_g1(roughness, VH, VN) * self.compute_g1(roughness, LH, LN) |
| |
|
| | def compute_specular_term(self, wi, wo, albedo, normals, roughness, metalness): |
| | F0 = 0.04 * (1. - metalness) + metalness * albedo |
| |
|
| | |
| | H = normalize((wi + wo) / 2.0) |
| |
|
| | |
| | NH = torch.clamp(dot_product(normals, H), min=0.001) |
| | VH = torch.clamp(dot_product(wo, H), min=0.001) |
| | LH = torch.clamp(dot_product(wi, H), min=0.001) |
| | VN = torch.clamp(dot_product(wo, normals), min=0.001) |
| | LN = torch.clamp(dot_product(wi, normals), min=0.001) |
| |
|
| | F = self.compute_fresnel(F0, VH) |
| | G = self.compute_geometry(roughness, VH, LH, VN, LN) |
| | D = self.compute_microfacet_distribution(roughness, NH) |
| |
|
| | return F * G * D / (4.0 * VN * LN) |
| |
|
| | def compute_diffuse_term(self, albedo, metalness): |
| | return albedo * (1. - metalness) / np.pi |
| |
|
| | def evaluate_brdf(self, wi, wo, normals, albedo, roughness, metalness): |
| | diffuse_term = self.compute_diffuse_term(albedo, metalness) |
| | specular_term = self.compute_specular_term(wi, wo, albedo, normals, roughness, metalness) |
| | return diffuse_term, specular_term |
| |
|
| | def render(self, scene, svbrdf): |
| | |
| | normals, albedo, roughness = svbrdf |
| | device = albedo.device |
| |
|
| | |
| | |
| | |
| | xcoords_row = torch.linspace(-1, 1, albedo.shape[-1], device=device) |
| | xcoords = xcoords_row.unsqueeze(0).expand(albedo.shape[-2], albedo.shape[-1]).unsqueeze(0) |
| | ycoords = -1 * torch.transpose(xcoords, dim0=1, dim1=2) |
| | coords = torch.cat((xcoords, ycoords, torch.zeros_like(xcoords)), dim=0) |
| |
|
| | |
| | camera_pos = scene.camera.pos.unsqueeze(-1).unsqueeze(-1).to(device) |
| | relative_camera_pos = camera_pos - coords |
| | wo = normalize(relative_camera_pos) |
| |
|
| | |
| | roughness = torch.clamp(roughness, min=0.001) |
| |
|
| | light_pos = scene.light.pos.unsqueeze(-1).unsqueeze(-1).to(device) |
| | relative_light_pos = light_pos - coords |
| | wi = normalize(relative_light_pos) |
| |
|
| | fdiffuse, fspecular = self.evaluate_brdf(wi, wo, normals, albedo, roughness, metalness=0) |
| | f = fdiffuse + fspecular |
| |
|
| | color = scene.light.color if torch.is_tensor(scene.light.color) else torch.tensor(scene.light.color) |
| | light_color = color.unsqueeze(-1).unsqueeze(-1).unsqueeze(0).to(device) |
| | falloff = 1.0 / torch.sqrt(dot_product(relative_light_pos, relative_light_pos))**2 |
| | LN = torch.clamp(dot_product(wi, normals), min=0.0) |
| | radiance = torch.mul(torch.mul(f, light_color * falloff), LN) |
| |
|
| | return radiance |
| |
|
| | def _get_input_params(self, n_samples, light, pose): |
| | min_eps = 0.001 |
| | max_eps = 0.02 |
| | light_distance = 2.197 |
| | view_distance = 2.75 |
| |
|
| | |
| | |
| | |
| | view_dist = torch.ones(n_samples-1) * view_distance |
| | if pose is None: |
| | view_poses = torch.cat([torch.Tensor(2).uniform_(-0.25, 0.25), torch.ones(1) * view_distance], dim=-1).unsqueeze(0) |
| | if n_samples > 1: |
| | hemi_views = generate_normalized_random_direction(n_samples - 1, min_eps=min_eps, max_eps=max_eps) * view_distance |
| | view_poses = torch.cat([view_poses, hemi_views]) |
| | else: |
| | assert torch.is_tensor(pose) |
| | view_poses = pose.cpu() |
| |
|
| | if light is None: |
| | light_poses = torch.cat([torch.Tensor(2).uniform_(-0.75, 0.75), torch.ones(1) * light_distance], dim=-1).unsqueeze(0) |
| | if n_samples > 1: |
| | hemi_lights = generate_normalized_random_direction(n_samples - 1, min_eps=min_eps, max_eps=max_eps) * light_distance |
| | light_poses = torch.cat([light_poses, hemi_lights]) |
| | else: |
| | assert torch.is_tensor(light) |
| | light_poses = light.cpu() |
| |
|
| | light_colors = torch.Tensor([10.0]).unsqueeze(-1).expand(n_samples, 3) |
| |
|
| | return view_poses, light_poses, light_colors |
| |
|
| | def __call__(self, svbrdf, n_samples=1, lights=None, poses=None): |
| | view_poses, light_poses, light_colors = self._get_input_params(n_samples, lights, poses) |
| |
|
| | renderings = [] |
| | for wo, wi, c in zip(view_poses, light_poses, light_colors): |
| | scene = Scene(Camera(wo), Light(wi, c)) |
| | rendering = self.render(scene, svbrdf) |
| |
|
| | |
| | std_deviation_noise = torch.exp(torch.Tensor(1).normal_(mean = np.log(0.005), std=0.3)).numpy()[0] |
| | noise = torch.zeros_like(rendering).normal_(mean=0.0, std=std_deviation_noise) |
| |
|
| | |
| | post_noise = torch.clamp(rendering + noise, min=0.0, max=1.0) |
| |
|
| | |
| | post_gamma = gamma_encode(post_noise) |
| |
|
| | renderings.append(post_gamma) |
| |
|
| | renderings = torch.cat(renderings, dim=0) |
| |
|
| | if self.return_params: |
| | return renderings, (view_poses, light_poses, light_colors) |
| | return renderings |