stereokit-rust 0.4.0-alpha.22

High-Level Rust bindings around the StereoKitC library for XR
Documentation
#include "spherical_harmonics.h"
#include "sk_math.h"
#include "asset_types/texture_.h"
#include "libraries/ferr_halffloat.h"

namespace sk {

void sh_add      (spherical_harmonics_t &to, vec3 light_dir, vec3 light_color);
void sh_windowing(spherical_harmonics_t &harmonics, float window_width);

///////////////////////////////////////////

vec3 to_color_128(uint8_t *color) { return *(vec3 *)color; }
vec3 to_color_32 (uint8_t *color) { return vec3{color[0]/255.f,color[1]/255.f,color[2]/255.f}; }
vec3 to_color_32_linear(uint8_t* color) { return vec3{ powf(color[0] / 255.f,2.2f),powf(color[1] / 255.f,2.2f),powf(color[2] / 255.f,2.2f) }; }
vec3 to_color_64(uint8_t* color) { const uint16_t* c = (uint16_t*)color; vec4 r; fhf_f16_to_f32_x4(c, &r.x); return vec3{ r.x, r.y, r.z }; }
vec3 to_color_11_11_10f(uint8_t* color) { vec3 r; fhf_r11g11b10f_to_f32_x3(*(uint32_t*)color, &r.x); return vec3{ r.x, r.y, r.z }; }

///////////////////////////////////////////

// Reference here:
// https://github.com/kayru/Probulator/blob/master/Source/Probulator/SphericalHarmonics.h#L317
void sh_windowing(spherical_harmonics_t &harmonics, float window_width) {
	int i = 0;
	for(int band = 0; band <= 2; band++) {
		float s = 1.0f / (1.0f + window_width * band * band * (band + 1.0f) * (band + 1.0f));
		for(int m = -band; m <= band; m++) {
			harmonics.coefficients[i++] *= s;
		}
	}
}

///////////////////////////////////////////

spherical_harmonics_t sh_create(const sh_light_t* lights, int32_t light_count) {
	spherical_harmonics_t result = {};
	for (int32_t i = 0; i < light_count; i++) {
		sh_add(result, vec3_normalize(lights[i].dir_to), { lights[i].color.r, lights[i].color.g, lights[i].color.b });
	}
	for (int32_t i = 0; i < 9; i++) {
		result.coefficients[i] /= (float)light_count;
	}

	// Apply windowing to prevent overshooting
	sh_windowing(result, .01f);

	return result;
}

///////////////////////////////////////////

void sh_brightness(spherical_harmonics_t &harmonics, float scale) {
	for (int32_t i = 0; i < 9; i++)
		harmonics.coefficients[i] *= scale;
}

///////////////////////////////////////////

void sh_add(spherical_harmonics_t &to, vec3 light_dir, vec3 light_color) {
	light_dir = { -light_dir.x, -light_dir.y, light_dir.z };

	// From DirectXMath's XMSHEvalDirectionalLight. See:
	// https://github.com/microsoft/DirectXMath/blob/master/SHMath/DirectXSH.cpp#L4476

	// CosWtInt
	const float fCW0 = 0.25f;
	const float fCW1 = 0.5f;
	const float fRet = MATH_PI / (fCW0 + fCW1);
	light_color = light_color * fRet;

	// The rest is a mix of XMSHEvalDirectionalLight, XMSHEvalDirection, and
	// sh_eval_basis_2
	const float z2 = light_dir.z*light_dir.z;
	const float s1 = light_dir.y;
	const float c1 = light_dir.x;
	const float s2 = light_dir.x*s1 + light_dir.y*c1;
	const float c2 = light_dir.x*c1 - light_dir.y*s1;

	to.coefficients[0] += light_color * 0.282094791773878140f;
	to.coefficients[2] += light_color * 0.488602511902919920f*light_dir.z;
	to.coefficients[6] += light_color * (0.946174695757560080f*z2 + -0.315391565252520050f);
	to.coefficients[1] += light_color * -0.488602511902919920f*s1;
	to.coefficients[3] += light_color * -0.488602511902919920f*c1;
	const float p_2_1 = -1.092548430592079200f*light_dir.z;
	to.coefficients[5] += light_color * p_2_1*s1;
	to.coefficients[7] += light_color * p_2_1*c1;
	to.coefficients[4] += light_color * 0.546274215296039590f*s2;
	to.coefficients[8] += light_color * 0.546274215296039590f*c2;
}

///////////////////////////////////////////

spherical_harmonics_t sh_calculate(void **env_map_data, tex_format_ format, int32_t face_size) {
	spherical_harmonics_t result   = {};
	size_t                col_size = 0;
	vec3     (*convert)(uint8_t *) = nullptr;
	switch (format) {
	case tex_format_rgba128:       convert = to_color_128;       col_size = sizeof(float) * 4; break;
	case tex_format_rgba64f:       convert = to_color_64;        col_size = sizeof(uint16_t) * 4; break;
	case tex_format_rg11b10:       convert = to_color_11_11_10f; col_size = sizeof(uint32_t); break;
	case tex_format_rgba32:        convert = to_color_32_linear; col_size = sizeof(color32); break;
	case tex_format_rgba32_linear: convert = to_color_32;        col_size = sizeof(color32); break;
	default: return {};
	}

	float half_px = 0.5f / face_size;
	for (int32_t i = 0; i < 6; i++) {
		uint8_t *data = (uint8_t*)env_map_data[i];
		vec3 p1 = math_cubemap_corner(i * 4);
		vec3 p2 = math_cubemap_corner(i * 4+1);
		vec3 p3 = math_cubemap_corner(i * 4+2);
		vec3 p4 = math_cubemap_corner(i * 4+3); 

		for (int32_t y = 0; y < face_size; y++) {
			float py = 1 - (y / (float)face_size + half_px);

			// Top face is flipped on both axes
			if (i == 2) {
				py = 1 - py;
			}
			for (int32_t x = 0; x < face_size; x++) {
				float px = x / (float)face_size + half_px;

				// Top face is flipped on both axes
				if (i == 2) {
					px = 1 - px;
				}

				vec3 pl = vec3_lerp(p1, p4, py);
				vec3 pr = vec3_lerp(p2, p3, py);
				vec3 pt = vec3_lerp(pl, pr, px);
				pt = vec3_normalize(pt);

				vec3 color = convert(&data[(x + y * face_size) * col_size]);
				sh_add(result, pt, color);
			}
		}
	}

	float count = face_size * face_size * 6.f;
	for (int32_t i = 0; i < 9; i++)
		result.coefficients[i] /= count;

	// Apply windowing to prevent overshooting
	sh_windowing(result, .01f);

	return result;
}

///////////////////////////////////////////

color128 sh_lookup(const spherical_harmonics_t &harmonics, vec3 normal) {
	vec3 result = {};

	static const float Pi = 3.141592654f;
	static const float CosineA0 = Pi;
	static const float CosineA1 = (2.0f * Pi) / 3.0f;
	static const float CosineA2 = Pi * 0.25f;

	// Band 0
	result += harmonics.coefficients[0] * (0.282095f * CosineA0);

	// Band 1
	result += harmonics.coefficients[1] * (0.488603f * normal.y * CosineA1);
	result += harmonics.coefficients[2] * (0.488603f * normal.z * CosineA1);
	result += harmonics.coefficients[3] * (0.488603f * normal.x * CosineA1);

	// Band 2
	result += harmonics.coefficients[4] * (1.092548f * normal.x * normal.y * CosineA2);
	result += harmonics.coefficients[5] * (1.092548f * normal.y * normal.z * CosineA2);
	result += harmonics.coefficients[6] * (0.315392f * (3.0f * normal.z * normal.z - 1.0f) * CosineA2);
	result += harmonics.coefficients[7] * (1.092548f * normal.x * normal.z * CosineA2);
	result += harmonics.coefficients[8] * (0.546274f * (normal.x * normal.x - normal.y * normal.y) * CosineA2);

	return { result.x, result.y, result.z, 1 };
}

///////////////////////////////////////////

vec3 sh_dominant_dir(const sk_ref(spherical_harmonics_t) harmonics) {
	// Reference from here:
	// https://seblagarde.wordpress.com/2011/10/09/dive-in-sh-buffer-idea/
	vec3 dir = vec3_normalize({
		harmonics.coefficients[3].x * 0.3f + harmonics.coefficients[3].y * 0.59f + harmonics.coefficients[3].z,
		harmonics.coefficients[1].x * 0.3f + harmonics.coefficients[1].y * 0.59f + harmonics.coefficients[1].z,
		harmonics.coefficients[2].x * 0.3f + harmonics.coefficients[2].y * 0.59f + harmonics.coefficients[2].z });
	return -dir;
}

///////////////////////////////////////////

inline vec4 to_vec4(const vec3& vec) { return { vec.x, vec.y, vec.z, 0 }; }

void sh_to_fast(const spherical_harmonics_t& lookup, vec4* fast_9) {
	static const float CosineA0 = 3.141592654f;
	static const float CosineA1 = (2.0f * CosineA0) / 3.0f;
	static const float CosineA2 = CosineA0 * 0.25f;

	fast_9[0] = to_vec4(lookup.coefficients[0] * (0.282095f * CosineA0));

	fast_9[1] = to_vec4(lookup.coefficients[1] * (0.488603f * CosineA1));
	fast_9[2] = to_vec4(lookup.coefficients[2] * (0.488603f * CosineA1));
	fast_9[3] = to_vec4(lookup.coefficients[3] * (0.488603f * CosineA1));

	fast_9[4] = to_vec4(lookup.coefficients[4] * (1.092548f * CosineA2));
	fast_9[5] = to_vec4(lookup.coefficients[5] * (1.092548f * CosineA2));
	fast_9[6] = to_vec4(lookup.coefficients[6] * (0.315392f * CosineA2));
	fast_9[7] = to_vec4(lookup.coefficients[7] * (1.092548f * CosineA2));
	fast_9[8] = to_vec4(lookup.coefficients[8] * (0.546274f * CosineA2));
}

}