oarust 0.8.6

GPU-first semantic computing engine written in Rust
// GGX microfacet BRDF functions — shared by all Standard Surface shader
// variants.
//
// Follows the Filament / OpenPBR formulation for numerical stability and exact
// correspondence with the Arnold Standard Surface specification.  All functions
// operate in world space with normalised input vectors.
//
// Reference:
//   - Filament PBR white-paper (Google, 2018)
//   - Autodesk Standard Surface specification (2020)
//   - "Real Shading in Unreal Engine 4" (Epic Games, SIGGRAPH 2013)
//
// Donor: oacpp/source/cpp/lib/oa/render/shader/graphics/brdf.slang
// Adaptation: IBL split-sum left as a stub returning 0 until the prefiltered
// cube and BRDF LUT are available; all analytic terms are exact donor ports.

#ifndef OA_BRDF_SLANG
#define OA_BRDF_SLANG

// ── Constants ─────────────────────────────────────────────────────────────────

static const float PI = 3.14159265358979323846;
static const float INV_PI = 0.31830988618379067154;
static const float EPSILON = 1e-5;

// ── IOR → F0 conversion ───────────────────────────────────────────────────────

// Schlick F0 from index of refraction.
float3 f0_from_ior(float ior) {
	float f = (ior - 1.0) / (ior + 1.0);
	return float3(f * f, f * f, f * f);
}

// Per-channel metalness blend: dielectrics use IOR-derived F0; metals use
// base color as F0 directly.
float3 f0_blend(float3 base_color, float3 f0_dielectric, float metalness) {
	return lerp(f0_dielectric, base_color, metalness);
}

// ── Schlick Fresnel ───────────────────────────────────────────────────────────

// F(v,h) = F0 + (1 - F0) * (1 - v·h)^5
float3 fresnel_schlick(float3 f0, float v_dot_h) {
	float t = 1.0 - saturate(v_dot_h);
	float t2 = t * t;
	float t5 = t2 * t2 * t;
	return f0 + (1.0 - f0) * t5;
}

// ── GGX NDF (Trowbridge-Reitz) ────────────────────────────────────────────────

// α = roughness²  (perceptual → linear roughness)
// D(h) = α² / (π * ((n·h)² * (α² - 1) + 1)²)
float ggx_ndf(float n_dot_h, float alpha) {
	float a2 = alpha * alpha;
	float d  = (n_dot_h * n_dot_h) * (a2 - 1.0) + 1.0;
	return a2 / max(PI * d * d, EPSILON);
}

// ── Smith GGX Geometry (height-correlated) ────────────────────────────────────

// G1(x) = 2(n·x) / ((n·x) + sqrt(α² + (1-α²)(n·x)²))
float ggx_g1(float n_dot_x, float alpha) {
	float a2 = alpha * alpha;
	float d  = n_dot_x + sqrt(a2 + (1.0 - a2) * n_dot_x * n_dot_x);
	return (2.0 * n_dot_x) / max(d, EPSILON);
}

// G(v,l) = G1(v) * G1(l)
float ggx_geometry(float n_dot_v, float n_dot_l, float alpha) {
	return ggx_g1(n_dot_v, alpha) * ggx_g1(n_dot_l, alpha);
}

// ── Cook-Torrance specular BRDF ───────────────────────────────────────────────

// f_spec = D * G * F / (4 * n·v * n·l)
// Returns the specular radiance contribution; multiply by n·l * L_i at the
// call site.
float3 ggx_specular(
	float3 f0,
	float  n_dot_v,
	float  n_dot_l,
	float  n_dot_h,
	float  v_dot_h,
	float  alpha
) {
	float  D = ggx_ndf(n_dot_h, alpha);
	float  G = ggx_geometry(n_dot_v, n_dot_l, alpha);
	float3 F = fresnel_schlick(f0, v_dot_h);
	float  denom = max(4.0 * n_dot_v * n_dot_l, EPSILON);
	return (D * G * F) / denom;
}

// ── Energy-conserving Lambertian diffuse ──────────────────────────────────────

// Weighted by (1 - F) * (1 - metalness) so the total reflected energy ≤ 1.
// diffuse = (1 - F) * (1 - metalness) * base_color / π
float3 lambertian_diffuse(float3 base_color, float3 f0, float v_dot_h, float metalness) {
	float3 F   = fresnel_schlick(f0, v_dot_h);
	float3 kd  = (1.0 - F) * (1.0 - metalness);
	return kd * base_color * INV_PI;
}

// ── Combined direct-light BRDF evaluation ─────────────────────────────────────

// Returns direct reflected radiance for one light sample (caller provides
// n·l * L_i * light_color).  Adds specular and diffuse lobes.
float3 brdf_direct(
	float3 base_color,
	float3 f0,
	float  n_dot_v,
	float  n_dot_l,
	float  n_dot_h,
	float  v_dot_h,
	float  alpha,
	float  metalness
) {
	float3 spec = ggx_specular(f0, n_dot_v, n_dot_l, n_dot_h, v_dot_h, alpha);
	float3 diff = lambertian_diffuse(base_color, f0, v_dot_h, metalness);
	return (spec + diff) * n_dot_l;
}

// ── IBL stub ──────────────────────────────────────────────────────────────────
// Returns zero until the prefiltered cube and BRDF LUT are bound.
// Replaced in a later pass when IBL resources are available.

float3 ibl_specular(float3 r, float roughness, float3 f0, float n_dot_v) {
	// Planned: sample prefiltered_env_cube + brdf_lut
	return float3(0.0, 0.0, 0.0);
}

float3 ibl_diffuse(float3 n) {
	// Planned: sample irradiance_cube or evaluate SH9
	return float3(0.0, 0.0, 0.0);
}

#endif // OA_BRDF_SLANG