154 lines
5.3 KiB
Python
154 lines
5.3 KiB
Python
import torch
|
|
import numpy as np
|
|
import torch
|
|
import torch.nn.functional as F
|
|
from .relighting.tonemapper import TonemapHDR
|
|
|
|
def create_envmap_grid(size: int):
|
|
"""
|
|
BLENDER CONVENSION
|
|
Create the grid of environment map that contain the position in sperical coordinate
|
|
Top left is (0,0) and bottom right is (pi/2, 2pi)
|
|
"""
|
|
theta = torch.linspace(0, np.pi * 2, size * 2)
|
|
phi = torch.linspace(0, np.pi, size)
|
|
|
|
#use indexing 'xy' torch match vision's homework 3
|
|
theta, phi = torch.meshgrid(theta, phi ,indexing='xy')
|
|
|
|
theta_phi = torch.cat([theta[..., None], phi[..., None]], dim=-1)
|
|
theta_phi = theta_phi.numpy()
|
|
return theta_phi
|
|
|
|
def get_normal_vector(incoming_vector: np.ndarray, reflect_vector: np.ndarray):
|
|
"""
|
|
BLENDER CONVENSION
|
|
incoming_vector: the vector from the point to the camera
|
|
reflect_vector: the vector from the point to the light source
|
|
"""
|
|
#N = 2(R ⋅ I)R - I
|
|
N = (incoming_vector + reflect_vector) / np.linalg.norm(incoming_vector + reflect_vector, axis=-1, keepdims=True)
|
|
return N
|
|
|
|
def get_cartesian_from_spherical(theta: np.array, phi: np.array, r = 1.0):
|
|
"""
|
|
BLENDER CONVENSION
|
|
theta: vertical angle
|
|
phi: horizontal angle
|
|
r: radius
|
|
"""
|
|
x = r * np.sin(theta) * np.cos(phi)
|
|
y = r * np.sin(theta) * np.sin(phi)
|
|
z = r * np.cos(theta)
|
|
return np.concatenate([x[...,None],y[...,None],z[...,None]], axis=-1)
|
|
|
|
class chrome_ball_to_envmap:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"ball_images": ("IMAGE", ),
|
|
"envmap_height": ("INT", {"default": 256, "min": 1, "max": 2048, "step": 1}, ),
|
|
"scale": ("INT", {"default": 4, "min": 1, "max": 30, "step": 1}, ),
|
|
},
|
|
}
|
|
|
|
CATEGORY = "DiffusionLight"
|
|
RETURN_TYPES = ("IMAGE",)
|
|
RETURN_NAMES = ("image", )
|
|
|
|
FUNCTION = "process"
|
|
|
|
|
|
def process(self, ball_images, envmap_height, scale):
|
|
I = np.array([1, 0, 0])
|
|
|
|
# compute normal map that create from reflect vector
|
|
env_grid = create_envmap_grid(envmap_height * scale)
|
|
reflect_vec = get_cartesian_from_spherical(env_grid[...,1], env_grid[...,0])
|
|
normal = get_normal_vector(I[None,None], reflect_vec)
|
|
|
|
# turn from normal map to position to lookup [Range: 0,1]
|
|
pos = (normal + 1.0) / 2
|
|
pos = 1.0 - pos
|
|
pos = pos[...,1:]
|
|
|
|
env_map = None
|
|
# convert position to pytorch grid look up
|
|
grid = torch.from_numpy(pos)[None].float()
|
|
grid = grid * 2 - 1 # convert to range [-1,1]
|
|
print(grid.shape)
|
|
ball_images = ball_images.permute(0,3,1,2) # [1,3,H,W]
|
|
|
|
env_maps_list = []
|
|
for ball in ball_images:
|
|
env_map = F.grid_sample(ball.unsqueeze(0), grid, mode='bilinear', padding_mode='border', align_corners=True)
|
|
env_map_default = F.interpolate(env_map, size=(envmap_height, envmap_height*2), mode='bilinear', align_corners=True)
|
|
env_map_default = env_map_default.permute(0,2,3,1).cpu().to(torch.float32)
|
|
env_maps_list.append(env_map_default)
|
|
env_maps_out = torch.cat(env_maps_list, dim=0)
|
|
|
|
|
|
|
|
return env_maps_out,
|
|
|
|
class exposure_to_hdr:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"images": ("IMAGE", ),
|
|
#"EV": ("FLOAT", {"default": 0, "min": 1, "max": 30, "step": 1}, ),
|
|
"gamma": ("FLOAT", {"default": 2.4, "min": 1, "max": 30, "step": 0.01}, ),
|
|
},
|
|
}
|
|
|
|
CATEGORY = "DiffusionLight"
|
|
RETURN_TYPES = ("IMAGE", "IMAGE",)
|
|
RETURN_NAMES = ("hrd_image", "ldr_image", )
|
|
|
|
FUNCTION = "exposuretohdr"
|
|
|
|
def exposuretohdr(self, images, gamma):
|
|
first_image = torch.pow(images[0], gamma)
|
|
evs = [0.0, -2.5, -5.0]
|
|
hdr2ldr = TonemapHDR(gamma=gamma, percentile=99, max_mapping=0.9)
|
|
scaler = torch.tensor([0.212671, 0.715160, 0.072169])
|
|
|
|
# read luminace for every image
|
|
luminances = []
|
|
for i in range(len(evs)):
|
|
linear_img = torch.pow(images[i], gamma)
|
|
linear_img = linear_img * 1 / (2** evs[i])
|
|
# compute luminace
|
|
lumi = linear_img @ scaler
|
|
luminances.append(lumi)
|
|
|
|
# start from darkest image
|
|
out_luminace = luminances[len(evs) - 1]
|
|
for i in range(len(evs) - 1, 0, -1):
|
|
# compute mask
|
|
maxval = 1 / (2 ** evs[i-1])
|
|
p1 = torch.clip((luminances[i-1] - 0.9 * maxval) / (0.1 * maxval), 0, 1)
|
|
p2 = out_luminace > luminances[i-1]
|
|
mask = (p1 * p2)
|
|
out_luminace = luminances[i-1] * (1-mask) + out_luminace * mask
|
|
|
|
hdr_rgb = first_image * (out_luminace / (luminances[0] + 1e-10)).unsqueeze(-1)
|
|
ldr_rgb, _, _ = hdr2ldr(hdr_rgb)
|
|
|
|
hrd_rgb = hdr_rgb.unsqueeze(0).cpu().to(torch.float32)
|
|
ldr_rgb = ldr_rgb.unsqueeze(0).cpu().to(torch.float32)
|
|
|
|
return (hrd_rgb, ldr_rgb,)
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"chrome_ball_to_envmap": chrome_ball_to_envmap,
|
|
"exposure_to_hdr": exposure_to_hdr,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"chrome_ball_to_envmap": "Chrome Ball to Envmap",
|
|
"exposure_to_hdr": "Exposure to HDR",
|
|
}
|