def weight_measurements(spec, D, sigma, theta_i, phi_i, theta_o, phi_o, active=None): from mitsuba.core import MarginalContinuous2D0, Vector2f m_ndf = MarginalContinuous2D0(D, normalize=False) m_sigma = MarginalContinuous2D0(sigma, normalize=False) scaled = np.zeros(spec.shape) n_phi = spec.shape[-2] n_theta = spec.shape[-1] for i in range(phi_i.size): for j in range(theta_i.size): # Incient direction wi = spherical2cartesian(theta_i[j], phi_i[i]) u_wi = Vector2f(theta2u(theta_i[j]), phi2u(phi_i[i])) # Outgoing direction wo = spherical2cartesian(theta_o[i, j].flatten(), phi_o[i, j].flatten()) # Phase direction m = ek.normalize(wi + wo) theta_m, phi_m = cartesian2spherical(wo) u_m = Vector2f(theta2u(theta_m), phi2u(phi_m)) # Scale by inverse jacobian jacobian = m_ndf.eval(u_m) / (4 * m_sigma.eval(u_wi)) scaled[i, j] = spec[i, j] / np.reshape(jacobian, (n_phi, n_theta)) if not active is None: n_wavelenths = spec.shape[2] for i in range(n_wavelenths): scaled[:, :, i][~active] = 0 return scaled
def visible_ndf(D, sigma, theta_i, phi_i, isotropic): from mitsuba.core import MarginalContinuous2D0, Vector2f # Construct projected surface area interpolant data structure m_sigma = MarginalContinuous2D0(sigma, normalize=False) # Create uniform samples and warp by G2 mapping if isotropic: n_theta = n_phi = D.shape[1] else: n_phi = D.shape[0] n_theta = D.shape[1] # Check dimensions of micro-facet model Dvis = np.zeros((phi_i.size, theta_i.size, n_phi, n_theta)) theta = u2theta(np.linspace(0, 1, n_theta)) phi = u2phi(np.linspace(0, 1, n_phi)) # Calculate projected area of micro-facets for i in range(Dvis.shape[0]): # incident elevation for j in range(Dvis.shape[1]): # incident azimuth # Postion for sigma samples sample = Vector2f(theta2u(theta_i[j]), phi2u(phi_i[i])) sigma_i = m_sigma.eval(sample) # Incident direction omega_i = spherical2cartesian(theta_i[j], phi_i[i]) #print(np.degrees(theta_i[j]), np.degrees(phi_i[i])) for k in range(Dvis.shape[2]): # observation azimuth # Observation direction omega = spherical2cartesian(theta, phi[k]) sample = Vector2f(theta2u(theta), phi2u(phi[k])) # NDF at observation directions if isotropic: D_m = D[0] else: D_m = D[k] # Bidirectional NDF Dvis[i, j, k] = ek.max(0, ek.dot(omega, omega_i)) * D_m / sigma_i return Dvis
def normalize_2D(F, isotropic=True): # Normalize function so that integral = 1 from mitsuba.core import MarginalContinuous2D0, Vector2f F_norm = np.zeros(F.shape) # Construct projected surface area interpolant data structure m_F_norm = MarginalContinuous2D0(F, normalize=True) # Create uniform samples u_1 = np.linspace(0, 1, F_norm.shape[1]) u_2 = np.linspace(0, 1, F_norm.shape[0]) # Sample normalized mapping if isotropic: sample = Vector2f(u_1, u_2[0]) F_norm[0] = m_F_norm.eval(sample) F_norm[1] = F_norm[0] else: for k in range(F_norm.shape[0]): sample = Vector2f(u_1, u_2[k]) F_norm[k] = m_F_norm.eval(sample) print(sample, m_F_norm.eval(sample)) return F_norm
def eval_md(n_theta, n_phi, D_in, isotropic): from mitsuba.render import MarginalContinuous2D0, Vector2f m_D = MarginalContinuous2D0(D_in, normalize=False) u = np.meshgrid(np.linspace(0, 1, n_theta), np.linspace(0, 1, n_phi)) u_0 = u[0].flatten() u_1 = u[1].flatten() samples = Vector2f(u_0, u_1) D = m_D.eval(samples) if isotropic: D = np.vstack((D, D)) else: D = np.reshape(D, (n_phi, n_theta)) return D
def incident_elevation(n, sigma): from mitsuba.core import Float, Vector2f from mitsuba.core import MarginalContinuous2D0 # Construct projected surface area interpolant data structure sigma_sampler = ndf_intp2sample(sigma) m_sigma = MarginalContinuous2D0(sigma_sampler, normalize=False) # Warp samples by projected area samples = Vector2f(np.linspace(0, 1, n), 0.5) u_m, _ = m_sigma.sample(samples) # Map samples to sphere theta_i = u2theta(u_m[0]) #(np.pi - u_m[0] * np.pi) / 2 return theta_i
def normalize_4D(F, theta_i, phi_i): # Normalize function so that integral = 1 from mitsuba.core import MarginalContinuous2D2, Vector2f F_norm = np.zeros(F.shape) params = [phi_i.tolist(), theta_i.tolist()] # Construct projected surface area interpolant data structure m_F_norm = MarginalContinuous2D2(F, params, normalize=True) # Create uniform samples u_1 = np.linspace(0, 1, F_norm.shape[3]) u_2 = np.linspace(0, 1, F_norm.shape[2]) # Sample normalized mapping for i in range(F_norm.shape[0]): for j in range(F_norm.shape[1]): for k in range(F_norm.shape[2]): sample = Vector2f(u_1, u_2[k]) F_norm[i, j, k] = m_F_norm.eval(sample, [phi_i[i], theta_i[j]]) return F_norm
def normalize_2D2(func, theta_i, phi_i): from mitsuba.core import MarginalContinuous2D2, Vector2f params = [phi_i.tolist(), theta_i.tolist()] m_func = MarginalContinuous2D2(func, params, normalize=True) n_phi_o = func.shape[2] n_theta_o = func.shape[3] normalized = np.zeros(func.shape) # Create uniform samples u_0 = np.linspace(0, 1, n_theta_o) u_1 = np.linspace(0, 1, n_phi_o) samples = Vector2f(np.tile(u_0, n_phi_o), np.repeat(u_1, n_theta_o)) for i in range(phi_i.size): for j in range(theta_i.size): # Warp uniform samples by VNDF distribution (G1 mapping) normalized[i, j] = np.reshape( m_func.eval(samples, [phi_i[i], theta_i[j]]), (n_phi_o, n_theta_o)) return normalized
def brdf_samples(vndf, theta_i, phi_i, n_theta, n_phi): from mitsuba.core import MarginalContinuous2D2 # Construct projected surface area interpolant data structure params = [phi_i.tolist(), theta_i.tolist()] m_vndf = MarginalContinuous2D2(vndf, params, normalize=False) u_m = np.meshgrid(np.linspace(0, 1, n_theta), np.linspace(0, 1, n_phi)) u_0 = u_m[0].flatten() u_1 = u_m[1].flatten() samples = Vector2f(u_0, u_1) # Check dimensions of micro-facet model theta_o = phi_o = np.zeros((phi_i.size, theta_i.size, n_phi * n_theta)) # Warp sample grid to VNDF for i in range(vndf.shape[0]): for j in range(vndf.shape[1]): val = m_vndf.eval(samples, [theta_i[j], phi_i[i]]) m = m_vndf.sample(samples, [theta_i[j], phi_i[i]]) # Map samples to spere theta_o[i, j] = u2theta(m[0]) phi_o[i, j] = u2phi(m[1]) return theta_o, phi_o
def outgoing_direction(n_phi, n_theta, Dvis_sampler, theta_i, phi_i, isotropic, theta_max=np.pi / 2, all=False): from mitsuba.core import Vector2f, Frame3f from mitsuba.core import MarginalContinuous2D2 print("Max theta angle is %f deg." % np.degrees(theta_max)) phi_o = np.zeros((phi_i.size, theta_i.size, n_phi, n_theta)) theta_o = np.zeros((phi_i.size, theta_i.size, n_phi, n_theta)) invalid = np.ones((phi_i.size, theta_i.size, n_phi, n_theta), dtype='bool') active = np.ones((phi_i.size, theta_i.size, n_phi, n_theta), dtype='bool') # Create uniform samples u_0 = np.linspace(0, 1, n_theta) u_1 = np.linspace(0, 1, n_phi) samples = Vector2f(np.tile(u_0, n_phi), np.repeat(u_1, n_theta)) # Construct projected surface area interpolant data structure params = [phi_i.tolist(), theta_i.tolist()] m_vndf = MarginalContinuous2D2(Dvis_sampler, params, normalize=True) for i in range(phi_i.size): for j in range(theta_i.size): # Warp uniform samples by VNDF distribution (G1 mapping) u_m, ndf_pdf = m_vndf.sample(samples, [phi_i[i], theta_i[j]]) # Convert samples to radians (G2 mapping) theta_m = u2theta(u_m.x) # [0, 1] -> [0, pi] phi_m = u2phi(u_m.y) # [0, 1] -> [0, 2pi] if isotropic: phi_m += phi_i[i] # Phase vector m = spherical2cartesian(theta_m, phi_m) # Incident direction wi = spherical2cartesian(theta_i[j], phi_i[i]) # Outgoing direction (reflection over phase vector) wo = ek.fmsub(m, 2.0 * ek.dot(m, wi), wi) tmp1, tmp2 = cartesian2spherical(wo) # Remove invalid directions act = u_m.y > 0 # covered twice [-pi = pi] inv = Frame3f.cos_theta(wo) < 0 # below surface plane act &= np.invert(inv) # above surface plane act &= tmp1 <= (theta_max + EPSILON) # further angular restriction if isotropic: act &= tmp2 >= 0 if not all: tmp1[~act] = 0 tmp2[~act] = 0 else: tmp1[inv] = 0 tmp2[inv] = 0 # Fit to datashape act = np.reshape(act, (n_phi, n_theta)) inv = np.reshape(inv, (n_phi, n_theta)) tmp1 = np.reshape(tmp1, (n_phi, n_theta)) tmp2 = np.reshape(tmp2, (n_phi, n_theta)) # Append active[i, j] = act invalid[i, j] = inv theta_o[i, j] = tmp1 phi_o[i, j] = tmp2 return [theta_o, phi_o, active, invalid]
def projected_area(D, isotropic, projected=True): from mitsuba.core import MarginalContinuous2D0, Vector2f, Vector3f # Check dimensions of micro-facet model sigma = np.zeros(D.shape) # Construct projected surface area interpolant data structure m_D = MarginalContinuous2D0(D, normalize=False) # Create uniform samples and warp by G2 mapping if isotropic: n_theta = n_phi = D.shape[1] else: n_phi = D.shape[0] n_theta = D.shape[1] theta = u2theta(np.linspace(0, 1, n_theta)) phi = u2phi(np.linspace(0, 1, n_phi)) # Temporary values for surface area calculation theta_mean = np.zeros(n_theta + 1) for i in range(n_theta - 1): theta_mean[i + 1] = (theta[i + 1] - theta[i]) / 2. + theta[i] theta_mean[-1] = theta[-1] theta_mean[0] = theta[0] """ Surface area portion of unit sphere. Conditioning better for non vectorized approach. a = sphere_surface_patch(1, theta_next, Vector2f(phi[0], phi[1])) """ a = np.zeros(n_theta) for i in range(n_theta): a[i] = sphere_surface_patch(1, theta_mean[i:i + 2], phi[-3:-1]) # Calculate constants for integration for j in range(n_phi): # Interpolation points o = spherical2cartesian(theta, phi[j]) # Postion for NDF samples u0 = theta2u(theta) u1 = np.ones(n_theta) * phi2u(phi[j]) if j == 0: omega = o u_0 = u0 u_1 = u1 area = a / 2 else: omega = np.concatenate((omega, o)) u_0 = np.concatenate((u_0, u0)) u_1 = np.concatenate((u_1, u1)) if j == n_phi - 1: area = np.concatenate((area, a / 2)) else: area = np.concatenate((area, a)) sample = Vector2f(u_0, u_1) D_s = m_D.eval(sample) omega = Vector3f(omega) P = 1. # Calculate projected area of micro-facets for i in range(sigma.shape[0] - 1): for j in range(sigma.shape[1]): # Get projection factor from incident and outgoind direction if projected: # Incident direction omega_i = spherical2cartesian(theta[j], phi[i]) P = ek.max(0, ek.dot(omega, omega_i)) # Integrate over sphere F = P * D_s sigma[i, j] = np.dot(F, area) if projected: # Normalise sigma[i] = sigma[i] / sigma[i, 0] # TODO: Check for anisotropic case if isotropic: sigma[1] = sigma[0] return sigma