summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-03-16 19:35:38 -0700
committeryum <yum.food.vr@gmail.com>2026-03-16 19:35:38 -0700
commita0c233000c210273ca93c0444413fd45b1e6c928 (patch)
tree6928b8ded04f04dc670f049ccbdd673360c1b516
parent11e9d97c5ac694773149d6059f90c670b9c00262 (diff)
Begin work on cloth (again)
-rwxr-xr-x3ner.shader6
-rwxr-xr-xLUTS/dfg_cloth.exrbin67752 -> 0 bytes
-rwxr-xr-xLUTS/dfg_cloth.exr.meta127
-rw-r--r--LUTS/dfg_standard.exrbin2052797 -> 3026136 bytes
-rwxr-xr-xScripts/make_dfg_lut.py120
-rwxr-xr-xbrdf.cginc67
-rwxr-xr-xfeatures.cginc4
-rwxr-xr-xglobals.cginc4
-rwxr-xr-xlighting.cginc2
9 files changed, 133 insertions, 197 deletions
diff --git a/3ner.shader b/3ner.shader
index 8d9167f..569ddc5 100755
--- a/3ner.shader
+++ b/3ner.shader
@@ -1133,6 +1133,12 @@ Shader "yum_food/3ner"
_BRDF_Specular_Min_Denom("Specular minimum denominator", Float) = 0.000001
_Specular_AA_Variance("Specular AA Variance", Float) = 0.15
_Specular_AA_Threshold("Specular AA Threshold", Float) = 0.25
+ //ifex _Cloth_Enabled==0
+ [HideInInspector] m_start_Cloth("Cloth", Float) = 0
+ [ThryToggle(_CLOTH)] _Cloth_Enabled("Enable", Float) = 0
+ _Cloth_Sheen("Sheen", Color) = (0.5, 0.5, 0.5, 1)
+ [HideInInspector] m_end_Cloth("Cloth", Float) = 0
+ //endex
[HideInInspector] m_end_BRDF("BRDF", Float) = 0
[HideInInspector] m_start_blending ("Blending--{button_help:{text:Tutorial,action:{type:URL,data:https://www.poiyomi.com/rendering/blending},hover:Documentation}}", Float) = 0
diff --git a/LUTS/dfg_cloth.exr b/LUTS/dfg_cloth.exr
deleted file mode 100755
index b30a2a7..0000000
--- a/LUTS/dfg_cloth.exr
+++ /dev/null
Binary files differ
diff --git a/LUTS/dfg_cloth.exr.meta b/LUTS/dfg_cloth.exr.meta
deleted file mode 100755
index a3630b0..0000000
--- a/LUTS/dfg_cloth.exr.meta
+++ /dev/null
@@ -1,127 +0,0 @@
-fileFormatVersion: 2
-guid: 59729cfaee66a3c4d847e732c7f99272
-TextureImporter:
- internalIDToNameTable: []
- externalObjects: {}
- serializedVersion: 12
- mipmaps:
- mipMapMode: 1
- enableMipMap: 1
- sRGBTexture: 1
- linearTexture: 0
- fadeOut: 0
- borderMipMap: 0
- mipMapsPreserveCoverage: 0
- alphaTestReferenceValue: 0.5
- mipMapFadeDistanceStart: 1
- mipMapFadeDistanceEnd: 3
- bumpmap:
- convertToNormalMap: 0
- externalNormalMap: 0
- heightScale: 0.25
- normalMapFilter: 0
- flipGreenChannel: 0
- isReadable: 0
- streamingMipmaps: 1
- streamingMipmapsPriority: 0
- vTOnly: 0
- ignoreMipmapLimit: 0
- grayScaleToAlpha: 0
- generateCubemap: 6
- cubemapConvolution: 0
- seamlessCubemap: 0
- textureFormat: 1
- maxTextureSize: 2048
- textureSettings:
- serializedVersion: 2
- filterMode: 1
- aniso: 1
- mipBias: 0
- wrapU: 0
- wrapV: 0
- wrapW: 0
- nPOTScale: 1
- lightmap: 0
- compressionQuality: 50
- spriteMode: 0
- spriteExtrude: 1
- spriteMeshType: 1
- alignment: 0
- spritePivot: {x: 0.5, y: 0.5}
- spritePixelsToUnits: 100
- spriteBorder: {x: 0, y: 0, z: 0, w: 0}
- spriteGenerateFallbackPhysicsShape: 1
- alphaUsage: 1
- alphaIsTransparency: 0
- spriteTessellationDetail: -1
- textureType: 0
- textureShape: 1
- singleChannelComponent: 0
- flipbookRows: 1
- flipbookColumns: 1
- maxTextureSizeSet: 0
- compressionQualitySet: 0
- textureFormatSet: 0
- ignorePngGamma: 0
- applyGammaDecoding: 0
- swizzle: 50462976
- cookieLightType: 0
- platformSettings:
- - serializedVersion: 3
- buildTarget: DefaultTexturePlatform
- maxTextureSize: 128
- resizeAlgorithm: 0
- textureFormat: 19
- textureCompression: 1
- compressionQuality: 50
- crunchedCompression: 0
- allowsAlphaSplitting: 0
- overridden: 0
- ignorePlatformSupport: 0
- androidETC2FallbackOverride: 0
- forceMaximumCompressionQuality_BC6H_BC7: 0
- - serializedVersion: 3
- buildTarget: Standalone
- maxTextureSize: 2048
- resizeAlgorithm: 0
- textureFormat: -1
- textureCompression: 1
- compressionQuality: 50
- crunchedCompression: 0
- allowsAlphaSplitting: 0
- overridden: 0
- ignorePlatformSupport: 0
- androidETC2FallbackOverride: 0
- forceMaximumCompressionQuality_BC6H_BC7: 0
- - serializedVersion: 3
- buildTarget: Android
- maxTextureSize: 2048
- resizeAlgorithm: 0
- textureFormat: -1
- textureCompression: 1
- compressionQuality: 50
- crunchedCompression: 0
- allowsAlphaSplitting: 0
- overridden: 0
- ignorePlatformSupport: 0
- androidETC2FallbackOverride: 0
- forceMaximumCompressionQuality_BC6H_BC7: 0
- spriteSheet:
- serializedVersion: 2
- sprites: []
- outline: []
- physicsShape: []
- bones: []
- spriteID:
- internalID: 0
- vertices: []
- indices:
- edges: []
- weights: []
- secondaryTextures: []
- nameFileIdTable: {}
- mipmapLimitGroupName:
- pSDRemoveMatte: 0
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/LUTS/dfg_standard.exr b/LUTS/dfg_standard.exr
index f5209c6..812ac05 100644
--- a/LUTS/dfg_standard.exr
+++ b/LUTS/dfg_standard.exr
Binary files differ
diff --git a/Scripts/make_dfg_lut.py b/Scripts/make_dfg_lut.py
index d58f3cf..4e16c99 100755
--- a/Scripts/make_dfg_lut.py
+++ b/Scripts/make_dfg_lut.py
@@ -73,45 +73,54 @@ def G_Cloth_L(x, a, b, c, d, e):
@numba.njit(cache=True)
-def G_Cloth(roughness, LoH):
+def Lambda_Cloth(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
- one_minus_r = 1.0 - roughness
- one_minus_r_sq = one_minus_r * one_minus_r
- one_minus_LoH = 1.0 - LoH
+ # Matches shader: interpolator = r^2 blends toward rough (a1) column
+ r_sq = roughness * roughness
lambda_val = 0.0
- if LoH < 0.5:
- L0 = G_Cloth_L(LoH, a0, b0, c0, d0, e0)
- L1 = G_Cloth_L(LoH, a1, b1, c1, d1, e1)
- L = lerp(L0, L1, one_minus_r_sq)
+ 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)
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, one_minus_r_sq)
+ L_05 = lerp(L_05_0, L_05_1, r_sq)
- L_LoH_0 = G_Cloth_L(one_minus_LoH, a0, b0, c0, d0, e0)
- L_LoH_1 = G_Cloth_L(one_minus_LoH, a1, b1, c1, d1, e1)
- L_LoH = lerp(L_LoH_0, L_LoH_1, one_minus_r_sq)
+ 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)
- lambda_val = math.exp(2.0 * L_05 - L_LoH)
+ lambda_val = math.exp(2.0 * L_05 - L_c)
# Apply terminator softening (equation 4)
- return pow(lambda_val, 1.0 + 2.0 * pow(one_minus_LoH, 8.0))
+ return pow(lambda_val, 1.0 + 2.0 * pow(1.0 - cos_theta, 8.0))
@numba.njit(cache=True)
-def integrate_brdf_jitted(roughness, NoV, brdf_type, num_samples):
+def V_Cloth(roughness, NoL, NoV):
+ # Height-correlated Smith: G2 / (4 * NoL * NoV)
+ lambda_l = Lambda_Cloth(roughness, NoL)
+ lambda_v = Lambda_Cloth(roughness, NoV)
+ return 1.0 / ((1.0 + lambda_l + lambda_v) * 4.0 * NoL * NoV + 1e-6)
+
+
+@numba.njit(cache=True)
+def integrate_brdf_jitted(roughness, NoV, num_samples):
V_x = math.sqrt(1.0 - NoV * NoV)
V_y = 0.0
V_z = NoV
- A, B = 0.0, 0.0
+ # R: GGX scale, G: GGX bias, B: cloth DFG
+ std_scale, std_bias, cloth_val = 0.0, 0.0, 0.0
for i in range(num_samples):
e1, e2 = random.random(), random.random()
@@ -137,47 +146,33 @@ def integrate_brdf_jitted(roughness, NoV, brdf_type, num_samples):
NoL = saturate(L_z)
NoH = saturate(H_z)
- NoV_proxy = saturate(V_z) # NoV is V_z
+ NoV_proxy = saturate(V_z)
if NoL > 0:
- scale, bias = 0.0, 0.0
# --- Standard BRDF ---
- if brdf_type == 1:
- # Note that the D term is present in the numerator and the denominator, so it cancels out.
- #D = D_GGX(roughness, NoH)
- G = G_GGXSmith(roughness, NoL, NoV_proxy)
- Fc_term = pow(1.0 - VoH, 5.0)
-
- # PDF of GGX Importance Sampling is D * NoH / (4 * VoH).
- # The full term is (D * G * NoL) / PDF, which simplifies to:
- # G * NoL * (4 * VoH / NoH).
- # This can be unstable when NoH is close to zero, so we clamp the denominator.
- common_term = (G * NoL * 4.0 * VoH) / max(NoH, 1e-5)
-
- # We are baking the two components of the split-sum approximation for IBL:
- # reflectance = f0 * scale + bias
- scale = common_term * (1.0 - Fc_term)
- bias = common_term * Fc_term
- # --- Cloth BRDF ---
- elif brdf_type == 2:
- # We are importance sampling GGX, so must account for its PDF.
- D_c = D_Cloth(roughness, NoH)
- G_c = G_Cloth(roughness, VoH)
+ # D cancels between numerator and PDF.
+ G = G_GGXSmith(roughness, NoL, NoV_proxy)
+ Fc_term = pow(1.0 - VoH, 5.0)
- # PDF = D_GGX(r, NoH) * NoH / (4 * VoH)
- pdf_ggx = D_GGX(roughness, NoH) * NoH / (4.0 * VoH + 1e-6)
+ # PDF = D_GGX * NoH / (4 * VoH), so (D * G * NoL) / PDF simplifies to:
+ common_term = (G * NoL * 4.0 * VoH) / max(NoH, 1e-5)
- # We must divide by the PDF and multiply by our target distribution and the cosine term.
- scale = (D_c * G_c * NoL) / (pdf_ggx + 1e-6)
- bias = 0.0
+ std_scale += common_term * (1.0 - Fc_term)
+ std_bias += common_term * Fc_term
- A += scale
- B += bias
+ # --- Cloth BRDF ---
+ # 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)
- return A / num_samples, B / num_samples
+ inv_n = 1.0 / num_samples
+ return std_scale * inv_n, std_bias * inv_n, cloth_val * inv_n
-def calculate_pixel(coords, resolution, brdf_type, num_samples):
+def calculate_pixel(coords, resolution, num_samples):
x, y = coords
u = (x + 0.5) / resolution
v = (y + 0.5) / resolution
@@ -186,18 +181,14 @@ def calculate_pixel(coords, resolution, brdf_type, num_samples):
roughness = saturate(v)
if NoV < 1e-4: return x, y, 0.0, 0.0, 0.0
- r, g = 0.0, 0.0
- if brdf_type == 1: # standard
- r, g = integrate_brdf_jitted(roughness, NoV, 1, num_samples)
- elif brdf_type == 2: # cloth
- if roughness < 1e-4: return x, y, 0.0, 0.0, 0.0
- r, g = integrate_brdf_jitted(roughness, NoV, 2, num_samples)
+ std_scale, std_bias, cloth = integrate_brdf_jitted(roughness, NoV, num_samples)
- return x, y, r, g, 0.0
+ # R: GGX scale, G: GGX bias, B: cloth DFG
+ return x, y, std_scale, std_bias, cloth
-def generate_exr(resolution, output_filename, brdf_type, num_samples, num_workers):
- print(f"Generating {resolution}x{resolution} EXR '{output_filename}' ({num_samples} samples/pixel) using {num_workers} workers.")
+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.")
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) }
@@ -205,7 +196,7 @@ def generate_exr(resolution, output_filename, brdf_type, num_samples, num_worker
pixel_data = np.zeros((resolution, resolution, 3), 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, brdf_type=brdf_type, num_samples=num_samples)
+ worker_func = partial(calculate_pixel, resolution=resolution, num_samples=num_samples)
processed_count = 0
total_pixels = len(coords_to_process)
@@ -241,15 +232,13 @@ def generate_exr(resolution, output_filename, brdf_type, num_samples, num_worker
raise RuntimeError(f"Failed to write EXR file '{output_filename}': {e}")
def main():
- parser = argparse.ArgumentParser(description='Generate DFG LUT EXR images for PBR.')
- parser.add_argument('-t', '--type', choices=['standard', 'cloth'], default='standard',
- help='Type of DFG texture to generate (default: standard)')
+ parser = argparse.ArgumentParser(description='Generate packed DFG LUT (R=GGX scale, G=cloth, B=GGX bias).')
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,
help='Number of samples per pixel for integration (default: 8192)')
- parser.add_argument('-o', '--output',
- help='Output filename (default: dfg_standard.exr or dfg_cloth.exr)')
+ parser.add_argument('-o', '--output', default='dfg.exr',
+ help='Output filename (default: dfg.exr)')
parser.add_argument('-j', '--workers', type=int, default=os.cpu_count(),
help=f'Number of worker processes (default: {os.cpu_count()})')
@@ -259,11 +248,8 @@ def main():
print("Error: Resolution must be a positive integer")
return 1
- brdf_type = 1 if args.type == 'standard' else 2
- output_filename = args.output if args.output else f'dfg_{args.type}.exr'
-
try:
- generate_exr(args.resolution, output_filename, brdf_type, args.samples, args.workers)
+ generate_exr(args.resolution, args.output, args.samples, args.workers)
except Exception as e:
print(f"Error: {e}")
return 1
diff --git a/brdf.cginc b/brdf.cginc
index 94e7858..d9443ed 100755
--- a/brdf.cginc
+++ b/brdf.cginc
@@ -39,8 +39,6 @@ float3 F_Schlick(float LoH, float3 f0, float f90) {
// tan^2(theta) = sin^2(theta) / cos^2(theta)
// = (1 - cos^2(theta)) / cos^2(theta)
// = -1 + 1 / cos^2(theta)
-// The `max(1e-4, _)` and `min(4096, _)` are hand picked to minimize the issue
-// of bloom making specular highlights flash as the camera moves.
float D_GGX(float roughness, float NoH) {
float r2 = roughness * roughness;
float NoH2 = NoH * NoH;
@@ -54,6 +52,14 @@ float D_GGX(float roughness, float NoH) {
return r2 / denom;
}
+float D_Estevez(float roughness, float NoH) {
+ float r_rcp = rcp(roughness);
+ float sin_theta = sqrt(1 - NoH * NoH);
+ float D = (2 + r_rcp) * pow(sin_theta, r_rcp) / TAU;
+
+ return D;
+}
+
// Hammon "PBR Diffuse Lighting for GGX+Smith Microsurfaces"
// Slide 84. Note that we remove the (4 * NoL * NoV) from the
// denominator of the specular lobe because of some cancellations.
@@ -64,6 +70,36 @@ float G_GGXSmith(float roughness, float NoL, float NoV) {
return rcp(denom);
}
+float L_Estevez(float r, float x) {
+ // Recover constants according to Table 1.
+ float interpolator = 1 - r * r;
+ float one_minus_i = 1 - interpolator;
+ float a = interpolator * 25.3245 + one_minus_i * 21.5473;
+ float b = interpolator * 3.32435 + one_minus_i * 3.82987;
+ float c = interpolator * 0.16801 + one_minus_i * 0.19823;
+ float d = interpolator * -1.27393 + one_minus_i * -1.97760;
+ float e = interpolator * -4.85967 + one_minus_i * -4.32054;
+
+ return a / (1 + b*pow(x, c)) + d*x + e;
+}
+
+float Lambda_Estevez(float cos_theta, float roughness) {
+ // Equation 3
+ float lambda = cos_theta < 0.5
+ ? exp(L_Estevez(roughness, cos_theta))
+ : exp(2 * L_Estevez(roughness, 0.5) - L_Estevez(roughness, 1 - cos_theta));
+ // Equation 4
+ return pow(lambda, 1 + 2 * pow(1 - cos_theta, 8));
+}
+
+// Estevez & Kulla "Production Friendly Microfacet Sheen BRDF"
+// Height-correlated Smith: G2 / (4 * NoL * NoV)
+float G_Estevez(float roughness, float NoL, float NoV) {
+ float lambda_l = Lambda_Estevez(NoL, roughness);
+ float lambda_v = Lambda_Estevez(NoV, roughness);
+ return 1.0 / ((1.0 + lambda_l + lambda_v) * 4.0 * NoL * NoV);
+}
+
float4 brdf(Pbr pbr, LightData data, out BrdfData bd) {
bd = (BrdfData)0;
float3 specular = 0;
@@ -124,6 +160,23 @@ float4 brdf(Pbr pbr, LightData data, out BrdfData bd) {
remainder *= saturate(1.0f - bd.direct_f_cc * pbr.cc_strength);
#endif
+#if defined(_CLOTH)
+ float3 cloth_f0 = _Cloth_Sheen.rgb;
+ bd.direct_f = F_Schlick(data.direct.LoH, cloth_f0, f90);
+ bd.direct_d = D_Estevez(pbr.roughness, data.direct.NoH);
+ bd.direct_g = G_Estevez(pbr.roughness, data.direct.NoL, data.common.NoV);
+
+ float3 direct_specular = (bd.direct_d * bd.direct_g) * bd.direct_f;
+ direct_specular *= data.direct.color * data.direct.NoL;
+ direct_specular *= remainder;
+ specular += direct_specular;
+
+ float Fd = Fd_Lambertian(data.direct.NoL) / PI;
+ float3 direct_diffuse = Fd * pbr.albedo.xyz * data.direct.color;
+ direct_diffuse *= remainder;
+ direct_diffuse = max(0, direct_diffuse);
+ diffuse += direct_diffuse;
+#else
bd.direct_f = F_Schlick(data.direct.LoH, f0_color, f90);
bd.direct_d = D_GGX(pbr.roughness, data.direct.NoH);
bd.direct_g = G_GGXSmith(pbr.roughness, data.direct.NoL, data.common.NoV);
@@ -143,6 +196,7 @@ float4 brdf(Pbr pbr, LightData data, out BrdfData bd) {
direct_diffuse *= remainder;
direct_diffuse = max(0, direct_diffuse);
diffuse += direct_diffuse;
+#endif
}
// Indirect
@@ -157,6 +211,14 @@ float4 brdf(Pbr pbr, LightData data, out BrdfData bd) {
remainder -= cc_specular_dfg;
#endif
+#if defined(_CLOTH)
+ float3 specular_dfg = _Cloth_Sheen.rgb * bd.ibl_dfg.zzz;
+ float3 indirect_specular = data.indirect.specular * specular_dfg;
+ specular += indirect_specular * remainder;
+
+ float3 indirect_diffuse = pbr.albedo.xyz * data.indirect.diffuse;
+ diffuse += indirect_diffuse * remainder;
+#else
float3 specular_dfg = bd.ibl_dfg.xxx * f0_color + bd.ibl_dfg.yyy; // filament 5.3.4.6
float3 indirect_specular = data.indirect.specular * specular_dfg;
@@ -165,6 +227,7 @@ float4 brdf(Pbr pbr, LightData data, out BrdfData bd) {
float3 indirect_diffuse = pbr.albedo.xyz * data.indirect.diffuse * (1.0 - pbr.metallic);
diffuse += indirect_diffuse * remainder;
+#endif
}
#endif
diff --git a/features.cginc b/features.cginc
index 9a9c8fd..79c5001 100755
--- a/features.cginc
+++ b/features.cginc
@@ -223,4 +223,8 @@
#pragma shader_feature_local _RIM_LIGHTING0_MASK
//endex
+//ifex _Cloth_Enabled==0
+#pragma shader_feature_local _CLOTH
+//endex
+
#endif // __FEATURES_INC
diff --git a/globals.cginc b/globals.cginc
index ded2048..d32feeb 100755
--- a/globals.cginc
+++ b/globals.cginc
@@ -564,4 +564,8 @@ float4 _Vertex_Deformation_Slot_15_Vector_2;
float4 _Vertex_Deformation_Slot_15_Vector_3;
#endif // _VERTEX_DEFORMATION
+#if defined(_CLOTH)
+float4 _Cloth_Sheen;
+#endif // _CLOTH
+
#endif // __GLOBALS_INC
diff --git a/lighting.cginc b/lighting.cginc
index c8c3f64..c735fdb 100755
--- a/lighting.cginc
+++ b/lighting.cginc
@@ -229,7 +229,7 @@ void GetLighting(v2f i, Pbr pbr, out LightData data) {
data.direct.double_LoV = max(1e-4, 2.0f * direct_LoV * direct_LoV - 1.0f);
float4 lightColorIntensity = getDirectLightColorIntensity();
- data.direct.color = lightColorIntensity.rgb * getShadowAttenuation(i);
+ data.direct.color = lightColorIntensity.rgb * (lightColorIntensity.w * getShadowAttenuation(i));
// Indirect lighting
float3 reflect_dir = reflect(-data.common.V, pbr.normal);