oarust 0.8.6

GPU-first semantic computing engine written in Rust
// Punctual light types and evaluation loop — shared by Standard Surface.
//
// Supports up to MAX_DIRECTIONAL_LIGHTS directional lights and
// MAX_POINT_LIGHTS point/spot lights stored in a uniform buffer bound at
// descriptor set 0, binding 0.
//
// Donor: oacpp/source/cpp/lib/oa/render/shader/graphics/lights.slang
// Adaptation: spot angle support stubbed (cone_angle = 0 → omnidirectional);
// shadow maps not yet connected (always unshadowed).

#ifndef OA_LIGHTS_SLANG
#define OA_LIGHTS_SLANG

static const int MAX_DIRECTIONAL_LIGHTS = 8;
static const int MAX_POINT_LIGHTS       = 4;

// ── Light structures (std140 layout) ─────────────────────────────────────────

struct DirectionalLight {
	float3 direction;     // world-space, unit vector pointing FROM surface TOWARD source
	float  intensity;     // linear, lux-equivalent
	float3 color;         // linear RGB
	float  _pad;
};

struct PointLight {
	float3 position;      // world-space
	float  intensity;     // linear, lumens
	float3 color;         // linear RGB
	float  radius;        // smooth falloff window; 0 = no falloff
};

struct SceneLights {
	DirectionalLight directional[MAX_DIRECTIONAL_LIGHTS];
	PointLight       point[MAX_POINT_LIGHTS];
	int              directional_count;
	int              point_count;
	float            exposure;       // linear exposure multiplier (1.0 = neutral)
	float            ambient_scale;  // flat ambient intensity when IBL is off
	float3           ambient_color;  // flat ambient color
	int              use_ibl;        // 1 = use IBL, 0 = use flat ambient
};

// ── Distance attenuation ──────────────────────────────────────────────────────

// Smooth window falloff matching Unreal / Filament:
//   atten = (1 - (d/r)^4)^2 / (d^2 + 1)
// Falls to 0 at d == r; 1 at d == 0.
float point_attenuation(float dist_sq, float radius) {
	if (radius <= 0.0) { return 1.0 / (dist_sq + 1.0); }
	float r2 = radius * radius;
	float t  = dist_sq / (r2 * r2);       // (d/r)^4
	float w  = saturate(1.0 - t);
	return (w * w) / (dist_sq + 1.0);
}

// ── Directional light contribution ───────────────────────────────────────────

float3 evaluate_directional(
	DirectionalLight light,
	float3           normal,           // world normal (unit)
	float3           view_dir,         // unit vector from surface toward camera
	float3           base_color,
	float3           f0,
	float            alpha,
	float            metalness
) {
	float3 l = normalize(light.direction);
	float  n_dot_l = saturate(dot(normal, l));
	if (n_dot_l <= 0.0) return float3(0.0, 0.0, 0.0);

	float3 h       = normalize(view_dir + l);
	float  n_dot_v = saturate(dot(normal, view_dir));
	float  n_dot_h = saturate(dot(normal, h));
	float  v_dot_h = saturate(dot(view_dir, h));

	// brdf_direct already multiplies by n_dot_l
	float3 brdf = brdf_direct(base_color, f0, n_dot_v, n_dot_l, n_dot_h, v_dot_h, alpha, metalness);
	return brdf * light.color * light.intensity;
}

// ── Point light contribution ──────────────────────────────────────────────────

float3 evaluate_point(
	PointLight light,
	float3     surface_pos,
	float3     normal,
	float3     view_dir,
	float3     base_color,
	float3     f0,
	float      alpha,
	float      metalness
) {
	float3 delta  = light.position - surface_pos;
	float  dist_sq = dot(delta, delta);
	float3 l       = delta / sqrt(max(dist_sq, 1e-8));
	float  n_dot_l = saturate(dot(normal, l));
	if (n_dot_l <= 0.0) return float3(0.0, 0.0, 0.0);

	float  atten   = point_attenuation(dist_sq, light.radius);
	float3 h       = normalize(view_dir + l);
	float  n_dot_v = saturate(dot(normal, view_dir));
	float  n_dot_h = saturate(dot(normal, h));
	float  v_dot_h = saturate(dot(view_dir, h));

	float3 brdf = brdf_direct(base_color, f0, n_dot_v, n_dot_l, n_dot_h, v_dot_h, alpha, metalness);
	return brdf * light.color * light.intensity * atten;
}

// ── Full scene light accumulation loop ───────────────────────────────────────

float3 evaluate_all_lights(
	SceneLights lights,
	float3      surface_pos,
	float3      normal,
	float3      view_dir,
	float3      base_color,
	float3      f0,
	float       alpha,
	float       metalness
) {
	float3 radiance = float3(0.0, 0.0, 0.0);

	int num_dir = clamp(lights.directional_count, 0, MAX_DIRECTIONAL_LIGHTS);
	for (int i = 0; i < num_dir; ++i)	{
		radiance += evaluate_directional(
			lights.directional[i], normal, view_dir,
			base_color, f0, alpha, metalness
		);
	}

	int num_pt = clamp(lights.point_count, 0, MAX_POINT_LIGHTS);
	for (int j = 0; j < num_pt; ++j) {
		radiance += evaluate_point(
			lights.point[j], surface_pos, normal, view_dir,
			base_color, f0, alpha, metalness
		);
	}

	return radiance * lights.exposure;
}

#endif // OA_LIGHTS_SLANG