diff options
Diffstat (limited to 'Scripts')
| -rwxr-xr-x | Scripts/make_dfg_lut.py | 76 |
1 files changed, 49 insertions, 27 deletions
diff --git a/Scripts/make_dfg_lut.py b/Scripts/make_dfg_lut.py index 4e16c99..8105bb1 100755 --- a/Scripts/make_dfg_lut.py +++ b/Scripts/make_dfg_lut.py @@ -73,43 +73,56 @@ def G_Cloth_L(x, a, b, c, d, e): @numba.njit(cache=True) -def Lambda_Cloth(roughness, cos_theta): +def Lambda_Cloth_Raw(roughness, cos_theta): a0, a1 = 25.3245, 21.5473 b0, b1 = 3.32435, 3.82987 c0, c1 = 0.16801, 0.19823 d0, d1 = -1.27393, -1.97760 e0, e1 = -4.85967, -4.32054 - # Matches shader: interpolator = r^2 blends toward rough (a1) column - r_sq = roughness * roughness + one_minus_r = 1.0 - roughness + interp = one_minus_r * one_minus_r + rough_weight = 1.0 - interp lambda_val = 0.0 if cos_theta < 0.5: L0 = G_Cloth_L(cos_theta, a0, b0, c0, d0, e0) L1 = G_Cloth_L(cos_theta, a1, b1, c1, d1, e1) - L = lerp(L0, L1, r_sq) + L = lerp(L0, L1, rough_weight) lambda_val = math.exp(L) else: L_05_0 = G_Cloth_L(0.5, a0, b0, c0, d0, e0) L_05_1 = G_Cloth_L(0.5, a1, b1, c1, d1, e1) - L_05 = lerp(L_05_0, L_05_1, r_sq) + L_05 = lerp(L_05_0, L_05_1, rough_weight) one_minus_cos = 1.0 - cos_theta L_c_0 = G_Cloth_L(one_minus_cos, a0, b0, c0, d0, e0) L_c_1 = G_Cloth_L(one_minus_cos, a1, b1, c1, d1, e1) - L_c = lerp(L_c_0, L_c_1, r_sq) + L_c = lerp(L_c_0, L_c_1, rough_weight) lambda_val = math.exp(2.0 * L_05 - L_c) - # Apply terminator softening (equation 4) + return lambda_val + + +@numba.njit(cache=True) +def Lambda_Cloth_Softened(roughness, cos_theta): + lambda_val = Lambda_Cloth_Raw(roughness, cos_theta) return pow(lambda_val, 1.0 + 2.0 * pow(1.0 - cos_theta, 8.0)) @numba.njit(cache=True) -def V_Cloth(roughness, NoL, NoV): +def V_Cloth_Outgoing(roughness, NoL, NoV): # Height-correlated Smith: G2 / (4 * NoL * NoV) - lambda_l = Lambda_Cloth(roughness, NoL) - lambda_v = Lambda_Cloth(roughness, NoV) + lambda_l = Lambda_Cloth_Softened(roughness, NoL) + lambda_v = Lambda_Cloth_Raw(roughness, NoV) + return 1.0 / ((1.0 + lambda_l + lambda_v) * 4.0 * NoL * NoV + 1e-6) + + +@numba.njit(cache=True) +def V_Cloth_Incoming(roughness, NoL, NoV): + lambda_l = Lambda_Cloth_Softened(roughness, NoL) + lambda_v = Lambda_Cloth_Raw(roughness, NoV) return 1.0 / ((1.0 + lambda_l + lambda_v) * 4.0 * NoL * NoV + 1e-6) @@ -119,8 +132,8 @@ def integrate_brdf_jitted(roughness, NoV, num_samples): V_y = 0.0 V_z = NoV - # R: GGX scale, G: GGX bias, B: cloth DFG - std_scale, std_bias, cloth_val = 0.0, 0.0, 0.0 + # R: GGX scale, G: GGX bias, B: cloth outgoing albedo, A: cloth incoming albedo + std_scale, std_bias, cloth_out, cloth_in = 0.0, 0.0, 0.0, 0.0 for i in range(num_samples): e1, e2 = random.random(), random.random() @@ -164,12 +177,14 @@ def integrate_brdf_jitted(roughness, NoV, num_samples): # Same GGX importance samples, reweighted for cloth D and V. if roughness >= 1e-4: D_c = D_Cloth(roughness, NoH) - V_c = V_Cloth(roughness, NoL, NoV_proxy) pdf_ggx = D_GGX(roughness, NoH) * NoH / (4.0 * VoH + 1e-6) - cloth_val += (D_c * V_c * NoL) / (pdf_ggx + 1e-6) + V_out = V_Cloth_Outgoing(roughness, NoL, NoV_proxy) + V_in = V_Cloth_Incoming(roughness, NoV_proxy, NoL) + cloth_out += (D_c * V_out * NoL) / (pdf_ggx + 1e-6) + cloth_in += (D_c * V_in * NoL) / (pdf_ggx + 1e-6) inv_n = 1.0 / num_samples - return std_scale * inv_n, std_bias * inv_n, cloth_val * inv_n + return std_scale * inv_n, std_bias * inv_n, cloth_out * inv_n, cloth_in * inv_n def calculate_pixel(coords, resolution, num_samples): @@ -178,22 +193,28 @@ def calculate_pixel(coords, resolution, num_samples): v = (y + 0.5) / resolution NoV = saturate(u) - roughness = saturate(v) - if NoV < 1e-4: return x, y, 0.0, 0.0, 0.0 + perceptual_roughness = saturate(v) + roughness = max(perceptual_roughness * perceptual_roughness, 1e-4) + if NoV < 1e-4: return x, y, 0.0, 0.0, 0.0, 0.0 - std_scale, std_bias, cloth = integrate_brdf_jitted(roughness, NoV, num_samples) + std_scale, std_bias, cloth_out, cloth_in = integrate_brdf_jitted(roughness, NoV, num_samples) - # R: GGX scale, G: GGX bias, B: cloth DFG - return x, y, std_scale, std_bias, cloth + # R: GGX scale, G: GGX bias, B: cloth outgoing albedo, A: cloth incoming albedo + return x, y, std_scale, std_bias, cloth_out, cloth_in def generate_exr(resolution, output_filename, num_samples, num_workers): - print(f"Generating {resolution}x{resolution} EXR '{output_filename}' (R=GGX scale, G=GGX bias, B=cloth) ({num_samples} samples/pixel) using {num_workers} workers.") + print(f"Generating {resolution}x{resolution} EXR '{output_filename}' (R=GGX scale, G=GGX bias, B=cloth out, A=cloth in) ({num_samples} samples/pixel) using {num_workers} workers.") header = OpenEXR.Header(resolution, resolution) pt = Imath.PixelType(Imath.PixelType.FLOAT) - header['channels'] = { 'R': Imath.Channel(pt), 'G': Imath.Channel(pt), 'B': Imath.Channel(pt) } + header['channels'] = { + 'R': Imath.Channel(pt), + 'G': Imath.Channel(pt), + 'B': Imath.Channel(pt), + 'A': Imath.Channel(pt), + } - pixel_data = np.zeros((resolution, resolution, 3), dtype=np.float32) + pixel_data = np.zeros((resolution, resolution, 4), dtype=np.float32) coords_to_process = [(x, y) for y in range(resolution) for x in range(resolution)] worker_func = partial(calculate_pixel, resolution=resolution, num_samples=num_samples) @@ -207,8 +228,8 @@ def generate_exr(resolution, output_filename, num_samples, num_workers): for future in concurrent.futures.as_completed(futures): try: - x, y, r, g, b = future.result() - pixel_data[y, x] = (r, g, b) + x, y, r, g, b, a = future.result() + pixel_data[y, x] = (r, g, b, a) except Exception as exc: coord = futures[future] print(f'\nPixel at {coord} generated an exception: {exc}') @@ -225,14 +246,15 @@ def generate_exr(resolution, output_filename, num_samples, num_workers): r_data = pixel_data[:, :, 0].ravel().tobytes() g_data = pixel_data[:, :, 1].ravel().tobytes() b_data = pixel_data[:, :, 2].ravel().tobytes() - exr_file.writePixels({'R': r_data, 'G': g_data, 'B': b_data}) + a_data = pixel_data[:, :, 3].ravel().tobytes() + exr_file.writePixels({'R': r_data, 'G': g_data, 'B': b_data, 'A': a_data}) exr_file.close() print(f"Successfully generated {output_filename}") except Exception as e: raise RuntimeError(f"Failed to write EXR file '{output_filename}': {e}") def main(): - parser = argparse.ArgumentParser(description='Generate packed DFG LUT (R=GGX scale, G=cloth, B=GGX bias).') + parser = argparse.ArgumentParser(description='Generate packed DFG LUT (R=GGX scale, G=GGX bias, B=cloth out, A=cloth in).') parser.add_argument('-r', '--resolution', type=int, default=512, help='Resolution of the square EXR image (default: 512)') parser.add_argument('-s', '--samples', type=int, default=8192, |
