byte-engine 0.1.0

A composable Rust game engine focused on graphics, input, audio, physics, and retained UI.
SkyParameters: struct {
	inverse_view_projection: mat4f,
	camera_position: vec4f,
	sun_direction: vec4f,
	planet_center: vec4f,
	atmosphere: vec4f,
	misc: vec4f,
}

depth_texture: descriptor<Texture2D, 0, read>;
main_texture: descriptor<StorageImage<rgba16>, 1, read_write>;
parameters: descriptor<SkyParameters, 2, read>;

get_camera_position: fn () -> vec3f {
	return vec3f(parameters.camera_position.x, parameters.camera_position.y, parameters.camera_position.z);
}

get_sun_direction: fn () -> vec3f {
	return vec3f(parameters.sun_direction.x, parameters.sun_direction.y, parameters.sun_direction.z);
}

get_planet_center: fn () -> vec3f {
	return vec3f(parameters.planet_center.x, parameters.planet_center.y, parameters.planet_center.z);
}

get_ground_radius: fn () -> f32 { return parameters.atmosphere.x; }
get_atmosphere_radius: fn () -> f32 { return parameters.atmosphere.y; }
get_rayleigh_scale_height: fn () -> f32 { return parameters.atmosphere.z; }
get_mie_scale_height: fn () -> f32 { return parameters.atmosphere.w; }
get_mie_g: fn () -> f32 { return parameters.sun_direction.w; }
get_sun_intensity: fn () -> f32 { return parameters.camera_position.w; }
get_sun_angular_radius: fn () -> f32 { return parameters.planet_center.w; }
get_ozone_strength: fn () -> f32 { return parameters.misc.x; }

should_skip_below_horizon: fn () -> bool {
	return parameters.misc.y > 0.5;
}

ray_sphere_discriminant: fn(origin: vec3f, direction: vec3f, center: vec3f, radius: f32) -> f32 {
	let oc: vec3f = origin - center;
	let b: f32 = dot(oc, direction);
	let c: f32 = dot(oc, oc) - radius * radius;
	return b * b - c;
}

ray_sphere_t_min: fn(origin: vec3f, direction: vec3f, center: vec3f, radius: f32) -> f32 {
	let oc: vec3f = origin - center;
	let b: f32 = dot(oc, direction);
	let discriminant: f32 = ray_sphere_discriminant(origin, direction, center, radius);
	return (0.0 - b) - sqrt(discriminant);
}

ray_sphere_t_max: fn(origin: vec3f, direction: vec3f, center: vec3f, radius: f32) -> f32 {
	let oc: vec3f = origin - center;
	let b: f32 = dot(oc, direction);
	let discriminant: f32 = ray_sphere_discriminant(origin, direction, center, radius);
	return (0.0 - b) + sqrt(discriminant);
}

density_profile: fn(sample_position: vec3f) -> vec3f {
	let altitude: f32 = length(sample_position - get_planet_center()) - get_ground_radius();
	if (altitude < 0.0) {
		return vec3f(0.0, 0.0, 0.0);
	}
	let rayleigh: f32 = exp((0.0 - altitude) / get_rayleigh_scale_height());
	let mie: f32 = exp((0.0 - altitude) / get_mie_scale_height());
	let ozone: f32 = max(0.0, 1.0 - abs(altitude - 25000.0) / 15000.0) * get_ozone_strength();
	return vec3f(rayleigh, mie, ozone);
}

extinction_from_density: fn(density: vec3f) -> vec3f {
	let beta_rayleigh: vec3f = vec3f(0.000005802, 0.000013558, 0.000033100);
	let beta_mie: vec3f = vec3f(0.000003996, 0.000003996, 0.000003996);
	let beta_ozone: vec3f = vec3f(0.000000650, 0.000001881, 0.000000085);
	return density.x * beta_rayleigh + density.y * beta_mie + density.z * beta_ozone;
}

interleaved_gradient_noise: fn(pixel: vec2u) -> f32 {
	return fract(52.9829189 * fract(0.06711056 * f32(pixel.x) + 0.00583715 * f32(pixel.y)));
}

sample_distribution: fn(u: f32) -> f32 {
	let clamped: f32 = clamp(u, 0.0, 1.0);
	return clamped * clamped;
}

phase_rayleigh: fn(cosine_theta: f32) -> f32 {
	let pi: f32 = 3.14159265359;
	return (3.0 / (16.0 * pi)) * (1.0 + cosine_theta * cosine_theta);
}

phase_mie: fn(cosine_theta: f32, g: f32) -> f32 {
	let pi: f32 = 3.14159265359;
	let g2: f32 = g * g;
	let denominator: f32 = 1.0 + g2 - 2.0 * g * cosine_theta;
	let denominator_sqrt: f32 = sqrt(max(0.0001, denominator));
	return (3.0 / (8.0 * pi)) * ((1.0 - g2) * (1.0 + cosine_theta * cosine_theta)) / ((2.0 + g2) * max(0.0001, denominator * denominator_sqrt));
}

march_transmittance: fn(origin: vec3f, direction: vec3f) -> vec3f {
	let atmosphere_discriminant: f32 = ray_sphere_discriminant(origin, direction, get_planet_center(), get_atmosphere_radius());
	if (atmosphere_discriminant < 0.0) {
		return vec3f(1.0, 1.0, 1.0);
	}
	let t_min: f32 = max(0.0, ray_sphere_t_min(origin, direction, get_planet_center(), get_atmosphere_radius()));
	let t_max: f32 = ray_sphere_t_max(origin, direction, get_planet_center(), get_atmosphere_radius());
	let ground_discriminant: f32 = ray_sphere_discriminant(origin, direction, get_planet_center(), get_ground_radius());
	if (ground_discriminant >= 0.0) {
		let ground_t_max: f32 = ray_sphere_t_max(origin, direction, get_planet_center(), get_ground_radius());
		if (ground_t_max > 0.0) {
			return vec3f(0.0, 0.0, 0.0);
		}
	}
	let distance_through_atmosphere: f32 = t_max - t_min;
	let optical_depth: vec3f = vec3f(0.0, 0.0, 0.0);
	for (let i: u32 = 0; i < 4; i = i + 1) {
		let t0: f32 = t_min + distance_through_atmosphere * sample_distribution(f32(i) / 4.0);
		let t1: f32 = t_min + distance_through_atmosphere * sample_distribution(f32(i + 1) / 4.0);
		let t: f32 = 0.5 * (t0 + t1);
		let step_size: f32 = t1 - t0;
		let sample_position: vec3f = origin + direction * t;
		let density: vec3f = density_profile(sample_position);
		optical_depth = optical_depth + density * step_size;
	}
	return exp(vec3f(0.0, 0.0, 0.0) - extinction_from_density(optical_depth));
}

integrate_atmosphere: fn(pixel: vec2u, origin: vec3f, direction: vec3f) -> vec3f {
	let atmosphere_discriminant: f32 = ray_sphere_discriminant(origin, direction, get_planet_center(), get_atmosphere_radius());
	if (atmosphere_discriminant < 0.0) {
		return vec3f(0.0, 0.0, 0.0);
	}
	let atmosphere_t_min: f32 = max(0.0, ray_sphere_t_min(origin, direction, get_planet_center(), get_atmosphere_radius()));
	let atmosphere_t_max: f32 = ray_sphere_t_max(origin, direction, get_planet_center(), get_atmosphere_radius());
	let distance_through_atmosphere: f32 = atmosphere_t_max - atmosphere_t_min;
	let optical_depth: vec3f = vec3f(0.0, 0.0, 0.0);
	let luminance: vec3f = vec3f(0.0, 0.0, 0.0);
	let sun_direction: vec3f = get_sun_direction();
	let cosine_theta: f32 = dot(direction, sun_direction);
	let phase_r: f32 = phase_rayleigh(cosine_theta);
	let phase_m: f32 = phase_mie(cosine_theta, get_mie_g());
	let jitter: f32 = interleaved_gradient_noise(pixel);
	for (let i: u32 = 0; i < 16; i = i + 1) {
		let t0: f32 = atmosphere_t_min + distance_through_atmosphere * sample_distribution(f32(i) / 16.0);
		let t1: f32 = atmosphere_t_min + distance_through_atmosphere * sample_distribution(f32(i + 1) / 16.0);
		let t: f32 = mix(t0, t1, jitter);
		let step_size: f32 = t1 - t0;
		let sample_position: vec3f = origin + direction * t;
		let density: vec3f = density_profile(sample_position);
		optical_depth = optical_depth + density * step_size;
		let transmittance_to_camera: vec3f = exp(vec3f(0.0, 0.0, 0.0) - extinction_from_density(optical_depth));
		let transmittance_to_sun: vec3f = march_transmittance(sample_position, sun_direction);
		let beta_rayleigh: vec3f = vec3f(0.000005802, 0.000013558, 0.000033100);
		let beta_mie: vec3f = vec3f(0.000003996, 0.000003996, 0.000003996);
		let scattering: vec3f = density.x * beta_rayleigh * phase_r + density.y * beta_mie * phase_m;
		luminance = luminance + transmittance_to_camera * transmittance_to_sun * scattering * step_size;
	}
	let sun_disk: f32 = smoothstep(cos(get_sun_angular_radius() * 1.4), cos(get_sun_angular_radius()), cosine_theta);
	let sun_radiance: vec3f = vec3f(0.0, 0.0, 0.0);
	if (sun_disk > 0.0) {
		let sun_transmittance: vec3f = march_transmittance(origin, sun_direction);
		sun_radiance = sun_disk * sun_transmittance * vec3f(20.0, 18.0, 16.0);
	}
	let color: vec3f = luminance * get_sun_intensity() + sun_radiance;
	return color / (vec3f(1.0, 1.0, 1.0) + color);
}

reconstruct_view_direction: fn(pixel: vec2u, extent: vec2u) -> vec3f {
	let uv: vec2f = (vec2f(f32(pixel.x), f32(pixel.y)) + vec2f(0.5, 0.5)) / vec2f(f32(extent.x), f32(extent.y));
	let ndc: vec2f = vec2f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
	let world: vec4f = parameters.inverse_view_projection * vec4f(ndc.x, ndc.y, 0.0, 1.0);
	return normalize(vec3f(world.x / world.w, world.y / world.w, world.z / world.w) - get_camera_position());
}

is_below_horizon: fn(origin: vec3f, direction: vec3f) -> bool {
	let local_up: vec3f = normalize(origin - get_planet_center());
	return dot(direction, local_up) < 0.0;
}

main: fn () -> void {
	let pixel: vec2u = thread_id();
	guard_image_bounds(main_texture, pixel);
	let extent: vec2u = image_size(main_texture);
	let depth_uv: vec2f = (vec2f(f32(pixel.x), f32(pixel.y)) + vec2f(0.5, 0.5)) / vec2f(f32(extent.x), f32(extent.y));
	let depth: f32 = texture_lod(depth_texture, depth_uv).x;
	if (depth > 0.000001) {
		return;
	}
	let direction: vec3f = reconstruct_view_direction(pixel, extent);
	if (should_skip_below_horizon()) {
		if (is_below_horizon(get_camera_position(), direction)) {
			return;
		}
	}
	let sky: vec3f = integrate_atmosphere(pixel, get_camera_position(), direction);
	let foreground: vec4f = image_load(main_texture, pixel);
	let foreground_alpha: f32 = clamp(foreground.w, 0.0, 1.0);
	let source: vec4f = vec4f(foreground.x, foreground.y, foreground.z, foreground_alpha);
	let destination: vec4f = vec4f(sky.x, sky.y, sky.z, 1.0);
	write(main_texture, pixel, source_over(source, destination));
}