MCPcopy Create free account
hub / github.com/Bloom-Engine/engine / sample_brdf

Function sample_brdf

tools/bloom-reference/src/main.rs:1234–1326  ·  view source on GitHub ↗

Sample an outgoing direction from the BRDF at a surface point. Uses a simple metal/dielectric split: metals are pure specular, dielectrics pick diffuse vs specular by Fresnel-at-normal weight.

(surface: &SurfaceSample, view_world: Vec3, rng: &mut Rng)

Source from the content-addressed store, hash-verified

1232/// Uses a simple metal/dielectric split: metals are pure specular,
1233/// dielectrics pick diffuse vs specular by Fresnel-at-normal weight.
1234fn sample_brdf(surface: &SurfaceSample, view_world: Vec3, rng: &mut Rng) -> BrdfSample {
1235 let n = surface.normal;
1236 let alpha = surface.roughness * surface.roughness;
1237 let (t, bt) = build_tbn(n);
1238
1239 // View in the tangent frame (z-up).
1240 let v_tangent = Vec3::new(view_world.dot(t), view_world.dot(bt), view_world.dot(n));
1241
1242 // f0: dielectrics use 0.04; metals use the base color as f0.
1243 let f0 = Vec3::splat(0.04).lerp(surface.base_color, surface.metallic);
1244
1245 // Decide diffuse vs specular lobe. Weighting by luminance of the
1246 // Fresnel-at-normal-incidence gives a reasonable importance
1247 // distribution without a second sample. Pure metals have ~zero
1248 // diffuse so this collapses naturally.
1249 let spec_weight = (f0.x + f0.y + f0.z) / 3.0;
1250 let diff_weight = (1.0 - spec_weight) * (1.0 - surface.metallic);
1251 let total = spec_weight + diff_weight + 1e-6;
1252 let p_spec = spec_weight / total;
1253 let pick_spec = rng.next_f32() < p_spec;
1254
1255 let rand = rng.next_vec2();
1256
1257 if pick_spec {
1258 // Sample a microfacet normal via VNDF, then reflect the view
1259 // direction across it.
1260 let h_tangent = sample_ggx_vndf(v_tangent, alpha, rand);
1261 let l_tangent = reflect(-v_tangent, h_tangent);
1262 if l_tangent.z <= 0.0 {
1263 return BrdfSample {
1264 direction_world: Vec3::Z,
1265 throughput: Vec3::ZERO,
1266 terminated: true,
1267 };
1268 }
1269 let h_world = t * h_tangent.x + bt * h_tangent.y + n * h_tangent.z;
1270 let l_world = t * l_tangent.x + bt * l_tangent.y + n * l_tangent.z;
1271 let n_dot_l = l_tangent.z;
1272 let n_dot_v = v_tangent.z.max(1e-4);
1273 let n_dot_h = h_tangent.z.max(1e-4);
1274 let v_dot_h = v_tangent.dot(h_tangent).max(1e-4);
1275
1276 // VNDF sampling PDF: D * G1 * max(0, V·H) / N·V.
1277 // The full BRDF is F * D * V_smith, so the throughput reduces
1278 // to F * G2/G1 * (V·H / (N·V * N·H))... but with height-
1279 // correlated V_smith the clean form is:
1280 // throughput = F * G2_height_correlated / G1(V)
1281 // which we can write more stably as below.
1282 let f = fresnel_schlick(v_dot_h, f0);
1283 let g2 = v_smith(n_dot_v, n_dot_l, alpha) * 4.0 * n_dot_v * n_dot_l;
1284 // For the VNDF sampler the combined BRDF*cos/PDF simplifies
1285 // essentially to F * G2/G1. We approximate with the ratio of
1286 // the correlated V term to the monodir G1 — functionally
1287 // equivalent and numerically well-behaved.
1288 let g1_v = smith_g1(n_dot_v, alpha);
1289 let weight = if g1_v > 0.0 {
1290 f * g2 / (g1_v + 1e-6)
1291 } else {

Callers 1

trace_pathFunction · 0.85

Calls 10

build_tbnFunction · 0.85
sample_ggx_vndfFunction · 0.85
reflectFunction · 0.85
fresnel_schlickFunction · 0.85
v_smithFunction · 0.85
smith_g1Function · 0.85
sample_cosine_hemisphereFunction · 0.85
burley_diffuseFunction · 0.85
next_f32Method · 0.80
next_vec2Method · 0.80

Tested by

no test coverage detected