|
|
|
|
@@ -58,27 +58,23 @@
|
|
|
|
|
/* Statics */
|
|
|
|
|
static ListBase studiolights;
|
|
|
|
|
static int last_studiolight_id = 0;
|
|
|
|
|
#define STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE 128
|
|
|
|
|
#define STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_HEIGHT 32
|
|
|
|
|
#define STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_WIDTH (STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_HEIGHT * 2)
|
|
|
|
|
#define STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE 96
|
|
|
|
|
#define STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT 32
|
|
|
|
|
#define STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH (STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT * 2)
|
|
|
|
|
|
|
|
|
|
#define STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE 0
|
|
|
|
|
#define STUDIOLIGHT_IRRADIANCE_METHOD_SPHERICAL_HARMONICS 1
|
|
|
|
|
/*
|
|
|
|
|
* The method to calculate the irradiance buffers
|
|
|
|
|
* The irradiance buffer is only shown in the background when in LookDev.
|
|
|
|
|
*
|
|
|
|
|
* STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE is very slow, but very accurate
|
|
|
|
|
* STUDIOLIGHT_IRRADIANCE_METHOD_SPHERICAL_HARMONICS is faster but has artifacts
|
|
|
|
|
* Cannot have both enabled at the same time!!!
|
|
|
|
|
*/
|
|
|
|
|
// #define STUDIOLIGHT_IRRADIANCE_METHOD STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
#define STUDIOLIGHT_IRRADIANCE_METHOD STUDIOLIGHT_IRRADIANCE_METHOD_SPHERICAL_HARMONICS
|
|
|
|
|
// #define STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
#define STUDIOLIGHT_IRRADIANCE_METHOD_SPHERICAL_HARMONICS
|
|
|
|
|
|
|
|
|
|
#if 0 /* Temporarily disabled due to the creation of textures with -nan(ind)s */
|
|
|
|
|
#if STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL == 2
|
|
|
|
|
# define STUDIOLIGHT_SPHERICAL_HARMONICS_WINDOWING
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
/* Temporarily disabled due to the creation of textures with -nan(ind)s */
|
|
|
|
|
#define STUDIOLIGHT_SH_WINDOWING 0.0f /* 0.0 is disabled */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Disable this option so caches are not loaded from disk
|
|
|
|
|
@@ -90,6 +86,34 @@ static const char *STUDIOLIGHT_CAMERA_FOLDER = "studiolights/camera/";
|
|
|
|
|
static const char *STUDIOLIGHT_WORLD_FOLDER = "studiolights/world/";
|
|
|
|
|
static const char *STUDIOLIGHT_MATCAP_FOLDER = "studiolights/matcap/";
|
|
|
|
|
|
|
|
|
|
/* ITER MACRO */
|
|
|
|
|
|
|
|
|
|
/** Iter on all pixel giving texel center position and pixel pointer.
|
|
|
|
|
* Arguments
|
|
|
|
|
* type : type of src.
|
|
|
|
|
* src : source buffer.
|
|
|
|
|
* channels : number of channels per pixel.
|
|
|
|
|
*
|
|
|
|
|
* Others
|
|
|
|
|
* x, y : normalized UV coordinate [0..1] of the current pixel center.
|
|
|
|
|
* texel_size[2] : UV size of a pixel in this texture.
|
|
|
|
|
* pixel[] : pointer to the current pixel.
|
|
|
|
|
*/
|
|
|
|
|
#define ITER_PIXELS(type, src, channels, width, height) \
|
|
|
|
|
{ \
|
|
|
|
|
float texel_size[2]; \
|
|
|
|
|
texel_size[0] = 1.0f / width; \
|
|
|
|
|
texel_size[1] = 1.0f / height; \
|
|
|
|
|
type (*pixel_)[channels] = (type (*)[channels])src; \
|
|
|
|
|
for (float y = 0.5 * texel_size[1]; y < 1.0; y += texel_size[1]) { \
|
|
|
|
|
for (float x = 0.5 * texel_size[0]; x < 1.0; x += texel_size[0], pixel_++) { \
|
|
|
|
|
type *pixel = *pixel_;
|
|
|
|
|
|
|
|
|
|
#define ITER_PIXELS_END \
|
|
|
|
|
} \
|
|
|
|
|
} \
|
|
|
|
|
} ((void)0)
|
|
|
|
|
|
|
|
|
|
/* FUNCTIONS */
|
|
|
|
|
#define IMB_SAFE_FREE(p) do { \
|
|
|
|
|
if (p) { \
|
|
|
|
|
@@ -125,13 +149,12 @@ static void studiolight_free(struct StudioLight *sl)
|
|
|
|
|
for (int index = 0; index < 6; index++) {
|
|
|
|
|
IMB_SAFE_FREE(sl->radiance_cubemap_buffers[index]);
|
|
|
|
|
}
|
|
|
|
|
GPU_TEXTURE_SAFE_FREE(sl->equirectangular_radiance_gputexture);
|
|
|
|
|
GPU_TEXTURE_SAFE_FREE(sl->equirectangular_irradiance_gputexture);
|
|
|
|
|
IMB_SAFE_FREE(sl->equirectangular_radiance_buffer);
|
|
|
|
|
IMB_SAFE_FREE(sl->equirectangular_irradiance_buffer);
|
|
|
|
|
GPU_TEXTURE_SAFE_FREE(sl->equirect_radiance_gputexture);
|
|
|
|
|
GPU_TEXTURE_SAFE_FREE(sl->equirect_irradiance_gputexture);
|
|
|
|
|
IMB_SAFE_FREE(sl->equirect_radiance_buffer);
|
|
|
|
|
IMB_SAFE_FREE(sl->equirect_irradiance_buffer);
|
|
|
|
|
MEM_SAFE_FREE(sl->path_irr_cache);
|
|
|
|
|
MEM_SAFE_FREE(sl->path_sh_cache);
|
|
|
|
|
MEM_SAFE_FREE(sl->gpu_matcap_3components);
|
|
|
|
|
MEM_SAFE_FREE(sl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -161,13 +184,13 @@ static struct StudioLight *studiolight_create(int flag)
|
|
|
|
|
return sl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void direction_to_equirectangular(float r[2], const float dir[3])
|
|
|
|
|
static void direction_to_equirect(float r[2], const float dir[3])
|
|
|
|
|
{
|
|
|
|
|
r[0] = (atan2f(dir[1], dir[0]) - M_PI) / -(M_PI * 2);
|
|
|
|
|
r[1] = (acosf(dir[2] / 1.0) - M_PI) / -M_PI;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void equirectangular_to_direction(float r[3], float u, float v)
|
|
|
|
|
static void equirect_to_direction(float r[3], float u, float v)
|
|
|
|
|
{
|
|
|
|
|
float phi = (-(M_PI * 2)) * u + M_PI;
|
|
|
|
|
float theta = -M_PI * v + M_PI;
|
|
|
|
|
@@ -177,38 +200,47 @@ static void equirectangular_to_direction(float r[3], float u, float v)
|
|
|
|
|
r[2] = cosf(theta);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_radiance(ImBuf *ibuf, float color[4], const float direction[3])
|
|
|
|
|
static void direction_to_cube_face_uv(float r_uv[2], int *r_face, const float dir[3])
|
|
|
|
|
{
|
|
|
|
|
float uv[2];
|
|
|
|
|
direction_to_equirectangular(uv, direction);
|
|
|
|
|
nearest_interpolation_color_wrap(ibuf, NULL, color, uv[0] * ibuf->x, uv[1] * ibuf->y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_radiance_buffer(
|
|
|
|
|
ImBuf *ibuf, float *colbuf,
|
|
|
|
|
const float start_x, const float add_x,
|
|
|
|
|
const float start_y, const float add_y, const float z,
|
|
|
|
|
const int index_x, const int index_y, const int index_z)
|
|
|
|
|
{
|
|
|
|
|
float direction[3];
|
|
|
|
|
float yf = start_y;
|
|
|
|
|
float xf;
|
|
|
|
|
float *color = colbuf;
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE; y++, yf += add_y) {
|
|
|
|
|
xf = start_x;
|
|
|
|
|
for (int x = 0; x < STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE; x++, xf += add_x) {
|
|
|
|
|
direction[index_x] = xf;
|
|
|
|
|
direction[index_y] = yf;
|
|
|
|
|
direction[index_z] = z;
|
|
|
|
|
normalize_v3(direction);
|
|
|
|
|
studiolight_calculate_radiance(ibuf, color, direction);
|
|
|
|
|
color += 4;
|
|
|
|
|
}
|
|
|
|
|
if (fabsf(dir[0]) > fabsf(dir[1]) && fabsf(dir[0]) > fabsf(dir[2])) {
|
|
|
|
|
bool is_pos = (dir[0] > 0.0f);
|
|
|
|
|
*r_face = is_pos ? STUDIOLIGHT_X_POS : STUDIOLIGHT_X_NEG;
|
|
|
|
|
r_uv[0] = dir[2] / fabsf(dir[0]) * (is_pos ? 1 : -1);
|
|
|
|
|
r_uv[1] = dir[1] / fabsf(dir[0]) * (is_pos ? -1 : -1);
|
|
|
|
|
}
|
|
|
|
|
else if (fabsf(dir[1]) > fabsf(dir[0]) && fabsf(dir[1]) > fabsf(dir[2])) {
|
|
|
|
|
bool is_pos = (dir[1] > 0.0f);
|
|
|
|
|
*r_face = is_pos ? STUDIOLIGHT_Y_POS : STUDIOLIGHT_Y_NEG;
|
|
|
|
|
r_uv[0] = dir[0] / fabsf(dir[1]) * (is_pos ? 1 : 1);
|
|
|
|
|
r_uv[1] = dir[2] / fabsf(dir[1]) * (is_pos ? -1 : 1);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
bool is_pos = (dir[2] > 0.0f);
|
|
|
|
|
*r_face = is_pos ? STUDIOLIGHT_Z_NEG : STUDIOLIGHT_Z_POS;
|
|
|
|
|
r_uv[0] = dir[0] / fabsf(dir[2]) * (is_pos ? -1 : 1);
|
|
|
|
|
r_uv[1] = dir[1] / fabsf(dir[2]) * (is_pos ? -1 : -1);
|
|
|
|
|
}
|
|
|
|
|
r_uv[0] = r_uv[0] * 0.5f + 0.5f;
|
|
|
|
|
r_uv[1] = r_uv[1] * 0.5f + 0.5f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_load_equirectangular_image(StudioLight *sl)
|
|
|
|
|
static void cube_face_uv_to_direction(float r_dir[3], float x, float y, int face)
|
|
|
|
|
{
|
|
|
|
|
const float conversion_matrices[6][3][3] = {
|
|
|
|
|
{{ 0.0f, 0.0f, 1.0f}, {0.0f, -1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}},
|
|
|
|
|
{{ 0.0f, 0.0f, -1.0f}, {0.0f, -1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}},
|
|
|
|
|
{{ 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}},
|
|
|
|
|
{{ 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, { 0.0f, -1.0f, 0.0f}},
|
|
|
|
|
{{ 1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}},
|
|
|
|
|
{{-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
copy_v3_fl3(r_dir, x * 2.0f - 1.0f, y * 2.0f - 1.0f, 1.0f);
|
|
|
|
|
mul_m3_v3(conversion_matrices[face], r_dir);
|
|
|
|
|
normalize_v3(r_dir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_load_equirect_image(StudioLight *sl)
|
|
|
|
|
{
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_EXTERNAL_FILE) {
|
|
|
|
|
ImBuf *ibuf = NULL;
|
|
|
|
|
@@ -219,98 +251,123 @@ static void studiolight_load_equirectangular_image(StudioLight *sl)
|
|
|
|
|
ibuf = IMB_allocFromBuffer(NULL, colbuf, 1, 1);
|
|
|
|
|
}
|
|
|
|
|
IMB_float_from_rect(ibuf);
|
|
|
|
|
sl->equirectangular_radiance_buffer = ibuf;
|
|
|
|
|
sl->equirect_radiance_buffer = ibuf;
|
|
|
|
|
}
|
|
|
|
|
sl->flag |= STUDIOLIGHT_EXTERNAL_IMAGE_LOADED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_create_equirectangular_radiance_gputexture(StudioLight *sl)
|
|
|
|
|
static void studiolight_create_equirect_radiance_gputexture(StudioLight *sl)
|
|
|
|
|
{
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_EXTERNAL_FILE) {
|
|
|
|
|
char error[256];
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EXTERNAL_IMAGE_LOADED);
|
|
|
|
|
ImBuf *ibuf = sl->equirectangular_radiance_buffer;
|
|
|
|
|
ImBuf *ibuf = sl->equirect_radiance_buffer;
|
|
|
|
|
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_ORIENTATION_VIEWNORMAL) {
|
|
|
|
|
sl->gpu_matcap_3components = MEM_callocN(sizeof(float[3]) * ibuf->x * ibuf->y, __func__);
|
|
|
|
|
float *gpu_matcap_3components = MEM_callocN(sizeof(float[3]) * ibuf->x * ibuf->y, __func__);
|
|
|
|
|
|
|
|
|
|
float *offset4 = ibuf->rect_float;
|
|
|
|
|
float *offset3 = sl->gpu_matcap_3components;
|
|
|
|
|
for (int i = 0; i < ibuf->x * ibuf->y; i++) {
|
|
|
|
|
copy_v3_v3(offset3, offset4);
|
|
|
|
|
offset3 += 3;
|
|
|
|
|
offset4 += 4;
|
|
|
|
|
float (*offset4)[4] = (float (*)[4])ibuf->rect_float;
|
|
|
|
|
float (*offset3)[3] = (float (*)[3])gpu_matcap_3components;
|
|
|
|
|
for (int i = 0; i < ibuf->x * ibuf->y; i++, offset4++, offset3++) {
|
|
|
|
|
copy_v3_v3(*offset3, *offset4);
|
|
|
|
|
}
|
|
|
|
|
sl->equirectangular_radiance_gputexture = GPU_texture_create_nD(
|
|
|
|
|
ibuf->x, ibuf->y, 0, 2, sl->gpu_matcap_3components, GPU_R11F_G11F_B10F, GPU_DATA_FLOAT, 0, false, error);
|
|
|
|
|
|
|
|
|
|
sl->equirect_radiance_gputexture = GPU_texture_create_nD(
|
|
|
|
|
ibuf->x, ibuf->y, 0, 2, gpu_matcap_3components, GPU_R11F_G11F_B10F, GPU_DATA_FLOAT, 0, false, error);
|
|
|
|
|
|
|
|
|
|
MEM_SAFE_FREE(gpu_matcap_3components);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
sl->equirectangular_radiance_gputexture = GPU_texture_create_2D(
|
|
|
|
|
sl->equirect_radiance_gputexture = GPU_texture_create_2D(
|
|
|
|
|
ibuf->x, ibuf->y, GPU_RGBA16F, ibuf->rect_float, error);
|
|
|
|
|
GPUTexture *tex = sl->equirectangular_radiance_gputexture;
|
|
|
|
|
GPUTexture *tex = sl->equirect_radiance_gputexture;
|
|
|
|
|
GPU_texture_bind(tex, 0);
|
|
|
|
|
GPU_texture_filter_mode(tex, true);
|
|
|
|
|
GPU_texture_wrap_mode(tex, true);
|
|
|
|
|
GPU_texture_unbind(tex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sl->flag |= STUDIOLIGHT_EQUIRECTANGULAR_RADIANCE_GPUTEXTURE;
|
|
|
|
|
sl->flag |= STUDIOLIGHT_EQUIRECT_RADIANCE_GPUTEXTURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_create_equirectangular_irradiance_gputexture(StudioLight *sl)
|
|
|
|
|
static void studiolight_create_equirect_irradiance_gputexture(StudioLight *sl)
|
|
|
|
|
{
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_EXTERNAL_FILE) {
|
|
|
|
|
char error[256];
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EQUIRECTANGULAR_IRRADIANCE_IMAGE_CALCULATED);
|
|
|
|
|
ImBuf *ibuf = sl->equirectangular_irradiance_buffer;
|
|
|
|
|
sl->equirectangular_irradiance_gputexture = GPU_texture_create_2D(
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EQUIRECT_IRRADIANCE_IMAGE_CALCULATED);
|
|
|
|
|
ImBuf *ibuf = sl->equirect_irradiance_buffer;
|
|
|
|
|
sl->equirect_irradiance_gputexture = GPU_texture_create_2D(
|
|
|
|
|
ibuf->x, ibuf->y, GPU_RGBA16F, ibuf->rect_float, error);
|
|
|
|
|
GPUTexture *tex = sl->equirectangular_irradiance_gputexture;
|
|
|
|
|
GPUTexture *tex = sl->equirect_irradiance_gputexture;
|
|
|
|
|
GPU_texture_bind(tex, 0);
|
|
|
|
|
GPU_texture_filter_mode(tex, true);
|
|
|
|
|
GPU_texture_wrap_mode(tex, true);
|
|
|
|
|
GPU_texture_unbind(tex);
|
|
|
|
|
}
|
|
|
|
|
sl->flag |= STUDIOLIGHT_EQUIRECTANGULAR_IRRADIANCE_GPUTEXTURE;
|
|
|
|
|
sl->flag |= STUDIOLIGHT_EQUIRECT_IRRADIANCE_GPUTEXTURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_radiance(ImBuf *ibuf, float color[4], const float direction[3])
|
|
|
|
|
{
|
|
|
|
|
float uv[2];
|
|
|
|
|
direction_to_equirect(uv, direction);
|
|
|
|
|
nearest_interpolation_color_wrap(ibuf, NULL, color, uv[0] * ibuf->x, uv[1] * ibuf->y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_radiance_buffer(
|
|
|
|
|
ImBuf *ibuf, float *colbuf,
|
|
|
|
|
const int index_x, const int index_y, const int index_z,
|
|
|
|
|
const float xsign, const float ysign, const float zsign)
|
|
|
|
|
{
|
|
|
|
|
ITER_PIXELS(float, colbuf, 4,
|
|
|
|
|
STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE,
|
|
|
|
|
STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE)
|
|
|
|
|
{
|
|
|
|
|
float direction[3];
|
|
|
|
|
direction[index_x] = xsign * (x - 0.5f);
|
|
|
|
|
direction[index_y] = ysign * (y - 0.5f);
|
|
|
|
|
direction[index_z] = zsign * 0.5f;
|
|
|
|
|
normalize_v3(direction);
|
|
|
|
|
studiolight_calculate_radiance(ibuf, pixel, direction);
|
|
|
|
|
}
|
|
|
|
|
ITER_PIXELS_END;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_radiance_cubemap_buffers(StudioLight *sl)
|
|
|
|
|
{
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_EXTERNAL_FILE) {
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EXTERNAL_IMAGE_LOADED);
|
|
|
|
|
ImBuf *ibuf = sl->equirectangular_radiance_buffer;
|
|
|
|
|
ImBuf *ibuf = sl->equirect_radiance_buffer;
|
|
|
|
|
if (ibuf) {
|
|
|
|
|
float *colbuf = MEM_mallocN(SQUARE(STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE) * sizeof(float[4]), __func__);
|
|
|
|
|
const float add = 1.0f / (STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE + 1);
|
|
|
|
|
const float start = ((1.0f / STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE) * 0.5f) - 0.5f;
|
|
|
|
|
|
|
|
|
|
/* front */
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, start, add, start, add, 0.5f, 0, 2, 1);
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, 0, 2, 1, 1, -1, 1);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Y_POS] = IMB_allocFromBuffer(
|
|
|
|
|
NULL, colbuf, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE);
|
|
|
|
|
|
|
|
|
|
/* back */
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, -start, -add, start, add, -0.5f, 0, 2, 1);
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, 0, 2, 1, 1, 1, -1);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Y_NEG] = IMB_allocFromBuffer(
|
|
|
|
|
NULL, colbuf, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE);
|
|
|
|
|
|
|
|
|
|
/* left */
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, -start, -add, start, add, 0.5f, 1, 2, 0);
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, 2, 1, 0, 1, -1, 1);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_X_POS] = IMB_allocFromBuffer(
|
|
|
|
|
NULL, colbuf, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE);
|
|
|
|
|
|
|
|
|
|
/* right */
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, start, add, start, add, -0.5f, 1, 2, 0);
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, 2, 1, 0, -1, -1, -1);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_X_NEG] = IMB_allocFromBuffer(
|
|
|
|
|
NULL, colbuf, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE);
|
|
|
|
|
|
|
|
|
|
/* top */
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, start, add, start, add, -0.5f, 0, 1, 2);
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, 0, 1, 2, -1, -1, 1);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Z_NEG] = IMB_allocFromBuffer(
|
|
|
|
|
NULL, colbuf, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE);
|
|
|
|
|
|
|
|
|
|
/* bottom */
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, start, add, -start, -add, 0.5f, 0, 1, 2);
|
|
|
|
|
studiolight_calculate_radiance_buffer(ibuf, colbuf, 0, 1, 2, 1, -1, -1);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Z_POS] = IMB_allocFromBuffer(
|
|
|
|
|
NULL, colbuf, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE);
|
|
|
|
|
|
|
|
|
|
@@ -328,243 +385,131 @@ static void studiolight_calculate_radiance_cubemap_buffers(StudioLight *sl)
|
|
|
|
|
sl->flag |= STUDIOLIGHT_RADIANCE_BUFFERS_CALCULATED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BLI_INLINE void studiolight_evaluate_radiance_buffer(
|
|
|
|
|
ImBuf *radiance_buffer, const float normal[3], float color[3], int *hits,
|
|
|
|
|
int xoffset, int yoffset, int zoffset, float zvalue)
|
|
|
|
|
{
|
|
|
|
|
if (radiance_buffer == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
float angle;
|
|
|
|
|
float *radiance_color = radiance_buffer->rect_float;
|
|
|
|
|
float direction[3];
|
|
|
|
|
for (int y = 0; y < STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE; y++) {
|
|
|
|
|
for (int x = 0; x < STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE; x++) {
|
|
|
|
|
// calculate light direction;
|
|
|
|
|
direction[zoffset] = zvalue;
|
|
|
|
|
direction[xoffset] = (x / (float)STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE) - 0.5f;
|
|
|
|
|
direction[yoffset] = (y / (float)STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE) - 0.5f;
|
|
|
|
|
normalize_v3(direction);
|
|
|
|
|
angle = fmax(0.0f, dot_v3v3(direction, normal));
|
|
|
|
|
madd_v3_v3fl(color, radiance_color, angle);
|
|
|
|
|
(*hits)++;
|
|
|
|
|
radiance_color += 4;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Spherical Harmonics
|
|
|
|
|
*/
|
|
|
|
|
BLI_INLINE float studiolight_area_element(float x, float y)
|
|
|
|
|
BLI_INLINE float area_element(float x, float y)
|
|
|
|
|
{
|
|
|
|
|
return atan2(x * y, sqrtf(x * x + y * y + 1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BLI_INLINE float studiolight_texel_solid_angle(float x, float y, float halfpix)
|
|
|
|
|
BLI_INLINE float texel_solid_angle(float x, float y, float halfpix)
|
|
|
|
|
{
|
|
|
|
|
float v1x = (x - halfpix) * 2.0f - 1.0f;
|
|
|
|
|
float v1y = (y - halfpix) * 2.0f - 1.0f;
|
|
|
|
|
float v2x = (x + halfpix) * 2.0f - 1.0f;
|
|
|
|
|
float v2y = (y + halfpix) * 2.0f - 1.0f;
|
|
|
|
|
|
|
|
|
|
return studiolight_area_element(v1x, v1y) - studiolight_area_element(v1x, v2y) - studiolight_area_element(v2x, v1y) + studiolight_area_element(v2x, v2y);
|
|
|
|
|
return area_element(v1x, v1y) - area_element(v1x, v2y) - area_element(v2x, v1y) + area_element(v2x, v2y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_cubemap_vector_weight(float normal[3], float *weight, int face, float x, float y)
|
|
|
|
|
{
|
|
|
|
|
copy_v3_fl3(normal, x * 2.0f - 1.0f, y * 2.0f - 1.0f, 1.0f);
|
|
|
|
|
const float conversion_matrices[6][3][3] = {
|
|
|
|
|
{
|
|
|
|
|
{0.0f, 0.0f, 1.0f},
|
|
|
|
|
{0.0f, -1.0f, 0.0f},
|
|
|
|
|
{1.0f, 0.0f, 0.0f},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
{0.0f, 0.0f, -1.0f},
|
|
|
|
|
{0.0f, -1.0f, 0.0f},
|
|
|
|
|
{-1.0f, 0.0f, 0.0f},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
{1.0f, 0.0f, 0.0f},
|
|
|
|
|
{0.0f, 0.0f, -1.0f},
|
|
|
|
|
{0.0f, 1.0f, 0.0f},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
{1.0f, 0.0f, 0.0f},
|
|
|
|
|
{0.0f, 0.0f, 1.0f},
|
|
|
|
|
{0.0f, -1.0f, 0.0f},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
{1.0f, 0.0f, 0.0f},
|
|
|
|
|
{0.0f, -1.0f, 0.0f},
|
|
|
|
|
{0.0f, 0.0f, -1.0f},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
{-1.0f, 0.0f, 0.0f},
|
|
|
|
|
{0.0f, -1.0f, 0.0f},
|
|
|
|
|
{0.0f, 0.0f, 1.0f},
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mul_m3_v3(conversion_matrices[face], normal);
|
|
|
|
|
normalize_v3(normal);
|
|
|
|
|
const float halfpix = 1.0f / (2.0f * STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE);
|
|
|
|
|
*weight = studiolight_texel_solid_angle(x + halfpix, y + halfpix, halfpix);
|
|
|
|
|
const float halfpix = 0.5f / STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE;
|
|
|
|
|
cube_face_uv_to_direction(normal, x, y, face);
|
|
|
|
|
*weight = texel_solid_angle(x, y, halfpix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_spherical_harmonics_coefficient(StudioLight *sl, int sh_component)
|
|
|
|
|
static void studiolight_spherical_harmonics_calculate_coefficients(StudioLight *sl, float (*sh)[3])
|
|
|
|
|
{
|
|
|
|
|
const float M_4PI = M_PI * 4.0f;
|
|
|
|
|
|
|
|
|
|
float weight_accum = 0.0f;
|
|
|
|
|
float sh[3] = {0.0f, 0.0f, 0.0f};
|
|
|
|
|
memset(sh, 0, sizeof(float) * 3 * STUDIOLIGHT_SH_COEFS_LEN);
|
|
|
|
|
|
|
|
|
|
for (int face = 0; face < 6; face++) {
|
|
|
|
|
float *color;
|
|
|
|
|
color = sl->radiance_cubemap_buffers[face]->rect_float;
|
|
|
|
|
for (int y = 0; y < STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE; y++) {
|
|
|
|
|
float yf = y / (float)STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE;
|
|
|
|
|
for (int x = 0; x < STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE; x++) {
|
|
|
|
|
float xf = x / (float)STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE;
|
|
|
|
|
float weight, coef;
|
|
|
|
|
float cubevec[3];
|
|
|
|
|
studiolight_calculate_cubemap_vector_weight(cubevec, &weight, face, xf, yf);
|
|
|
|
|
ITER_PIXELS(float, sl->radiance_cubemap_buffers[face]->rect_float, 4,
|
|
|
|
|
STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE,
|
|
|
|
|
STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE)
|
|
|
|
|
{
|
|
|
|
|
float color[3], cubevec[3], weight;
|
|
|
|
|
studiolight_calculate_cubemap_vector_weight(cubevec, &weight, face, x, y);
|
|
|
|
|
mul_v3_v3fl(color, pixel, weight);
|
|
|
|
|
weight_accum += weight;
|
|
|
|
|
|
|
|
|
|
const float nx = cubevec[0];
|
|
|
|
|
const float ny = cubevec[1];
|
|
|
|
|
const float nz = cubevec[2];
|
|
|
|
|
const float nx2 = SQUARE(nx);
|
|
|
|
|
const float ny2 = SQUARE(ny);
|
|
|
|
|
const float nz2 = SQUARE(nz);
|
|
|
|
|
const float nx4 = SQUARE(nx2);
|
|
|
|
|
const float ny4 = SQUARE(ny2);
|
|
|
|
|
const float nz4 = SQUARE(nz2);
|
|
|
|
|
|
|
|
|
|
switch (sh_component) {
|
|
|
|
|
/* L0 */
|
|
|
|
|
case 0:
|
|
|
|
|
coef = 0.2822095f;
|
|
|
|
|
break;
|
|
|
|
|
/* L1 */
|
|
|
|
|
case 1:
|
|
|
|
|
coef = -0.488603f * nz * 2.0f / 3.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
coef = 0.488603f * ny * 2.0f / 3.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
coef = -0.488603f * nx * 2.0f / 3.0f;
|
|
|
|
|
break;
|
|
|
|
|
/* L2 */
|
|
|
|
|
case 4:
|
|
|
|
|
coef = 1.092548f * nx * nz * 1.0f / 4.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 5:
|
|
|
|
|
coef = -1.092548f * nz * ny * 1.0f / 4.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 6:
|
|
|
|
|
coef = 0.315392f * (3.0f * ny2 - 1.0f) * 1.0f / 4.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 7:
|
|
|
|
|
coef = 1.092548f * nx * ny * 1.0f / 4.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 8:
|
|
|
|
|
coef = 0.546274f * (nx2 - nz2) * 1.0f / 4.0f;
|
|
|
|
|
break;
|
|
|
|
|
/* L4 */
|
|
|
|
|
case 9:
|
|
|
|
|
coef = (2.5033429417967046f * nx * nz * (nx2 - nz2)) / -24.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 10:
|
|
|
|
|
coef = (-1.7701307697799304f * nz * ny * (3.0f * nx2 - nz2)) / -24.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 11:
|
|
|
|
|
coef = (0.9461746957575601f * nz * nx * (-1.0f + 7.0f * ny2)) / -24.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 12:
|
|
|
|
|
coef = (-0.6690465435572892f * nz * ny * (-3.0f + 7.0f * ny2)) / -24.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 13:
|
|
|
|
|
coef = ((105.0f * ny4 - 90.0f * ny2 + 9.0f) / 28.359261614f) / -24.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 14:
|
|
|
|
|
coef = (-0.6690465435572892f * nx * ny * (-3.0f + 7.0f * ny2)) / -24.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 15:
|
|
|
|
|
coef = (0.9461746957575601f * (nx2 - nz2) * (-1.0f + 7.0f * ny2)) / -24.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 16:
|
|
|
|
|
coef = (-1.7701307697799304f * nx * ny * (nx2 - 3.0f * nz2)) / -24.0f;
|
|
|
|
|
break;
|
|
|
|
|
case 17:
|
|
|
|
|
coef = (0.6258357354491761f * (nx4 - 6.0f * nz2 * nx2 + nz4)) / -24.0f;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
coef = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
madd_v3_v3fl(sh, color, coef * weight);
|
|
|
|
|
weight_accum += weight;
|
|
|
|
|
color += 4;
|
|
|
|
|
}
|
|
|
|
|
int i = 0;
|
|
|
|
|
/* L0 */
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 0.2822095f);
|
|
|
|
|
#if STUDIOLIGHT_SH_BANDS > 1 /* L1 */
|
|
|
|
|
const float nx = cubevec[0];
|
|
|
|
|
const float ny = cubevec[1];
|
|
|
|
|
const float nz = cubevec[2];
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, -0.488603f * nz);
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 0.488603f * ny);
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, -0.488603f * nx);
|
|
|
|
|
#endif
|
|
|
|
|
#if STUDIOLIGHT_SH_BANDS > 2 /* L2 */
|
|
|
|
|
const float nx2 = SQUARE(nx);
|
|
|
|
|
const float ny2 = SQUARE(ny);
|
|
|
|
|
const float nz2 = SQUARE(nz);
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 1.092548f * nx * nz);
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, -1.092548f * nz * ny);
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 0.315392f * (3.0f * ny2 - 1.0f));
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 1.092548f * nx * ny);
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 0.546274f * (nx2 - nz2));
|
|
|
|
|
#endif
|
|
|
|
|
/* Bypass L3 Because final irradiance does not need it. */
|
|
|
|
|
#if STUDIOLIGHT_SH_BANDS > 4 /* L4 */
|
|
|
|
|
const float nx4 = SQUARE(nx2);
|
|
|
|
|
const float ny4 = SQUARE(ny2);
|
|
|
|
|
const float nz4 = SQUARE(nz2);
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 2.5033429417967046f * nx * nz * (nx2 - nz2));
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, -1.7701307697799304f * nz * ny * (3.0f * nx2 - nz2));
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 0.9461746957575601f * nz * nx * (-1.0f + 7.0f * ny2));
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, -0.6690465435572892f * nz * ny * (-3.0f + 7.0f * ny2));
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, (105.0f * ny4 - 90.0f * ny2 + 9.0f) / 28.359261614f);
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, -0.6690465435572892f * nx * ny * (-3.0f + 7.0f * ny2));
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 0.9461746957575601f * (nx2 - nz2) * (-1.0f + 7.0f * ny2));
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, -1.7701307697799304f * nx * ny * (nx2 - 3.0f * nz2));
|
|
|
|
|
madd_v3_v3fl(sh[i++], color, 0.6258357354491761f * (nx4 - 6.0f * nz2 * nx2 + nz4));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
ITER_PIXELS_END;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mul_v3_fl(sh, M_4PI / weight_accum);
|
|
|
|
|
copy_v3_v3(sl->spherical_harmonics_coefs[sh_component], sh);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef STUDIOLIGHT_SPHERICAL_HARMONICS_WINDOWING
|
|
|
|
|
static void studiolight_calculate_spherical_harmonics_luminance(StudioLight *sl, float luminance[STUDIOLIGHT_SPHERICAL_HARMONICS_COMPONENTS])
|
|
|
|
|
{
|
|
|
|
|
for (int index = 0; index < STUDIOLIGHT_SPHERICAL_HARMONICS_COMPONENTS; index++) {
|
|
|
|
|
luminance[index] = rgb_to_grayscale(sl->spherical_harmonics_coefs[index]);
|
|
|
|
|
/* The sum of solid angle should be equal to the solid angle of the sphere (4 PI),
|
|
|
|
|
* so normalize in order to make our weightAccum exactly match 4 PI. */
|
|
|
|
|
for (int i = 0; i < STUDIOLIGHT_SH_COEFS_LEN; ++i) {
|
|
|
|
|
mul_v3_fl(sh[i], M_PI * 4.0f / weight_accum);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_apply_spherical_harmonics_windowing(StudioLight *sl, float max_lamplacian)
|
|
|
|
|
/* Take monochrome SH as input */
|
|
|
|
|
static float studiolight_spherical_harmonics_lambda_get(float *sh, float max_laplacian)
|
|
|
|
|
{
|
|
|
|
|
/* From Peter-Pike Sloan's Stupid SH Tricks http://www.ppsloan.org/publications/StupidSH36.pdf */
|
|
|
|
|
float table_l[STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL + 1];
|
|
|
|
|
float table_b[STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL + 1];
|
|
|
|
|
float table_l[STUDIOLIGHT_SH_BANDS];
|
|
|
|
|
float table_b[STUDIOLIGHT_SH_BANDS];
|
|
|
|
|
|
|
|
|
|
float lambda = 0.0f;
|
|
|
|
|
|
|
|
|
|
table_l[0] = 0.0f;
|
|
|
|
|
table_b[0] = 0.0f;
|
|
|
|
|
|
|
|
|
|
/* convert to luminance */
|
|
|
|
|
float luminance[STUDIOLIGHT_SPHERICAL_HARMONICS_COMPONENTS];
|
|
|
|
|
studiolight_calculate_spherical_harmonics_luminance(sl, luminance);
|
|
|
|
|
|
|
|
|
|
int index = 1;
|
|
|
|
|
for (int level = 1; level <= STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL; level++) {
|
|
|
|
|
for (int level = 1; level < STUDIOLIGHT_SH_BANDS; level++) {
|
|
|
|
|
table_l[level] = (float)(SQUARE(level) * SQUARE(level + 1));
|
|
|
|
|
|
|
|
|
|
float b = 0.0f;
|
|
|
|
|
for (int m = -1; m <= level; m++) {
|
|
|
|
|
b += SQUARE(luminance[index++]);
|
|
|
|
|
b += SQUARE(sh[index++]);
|
|
|
|
|
}
|
|
|
|
|
table_b[level] = b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float squared_lamplacian = 0.0f;
|
|
|
|
|
for (int level = 1; level <= STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL; level++) {
|
|
|
|
|
for (int level = 1; level < STUDIOLIGHT_SH_BANDS; level++) {
|
|
|
|
|
squared_lamplacian += table_l[level] * table_b[level];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float target_squared_laplacian = max_lamplacian * max_lamplacian;
|
|
|
|
|
const float target_squared_laplacian = max_laplacian * max_laplacian;
|
|
|
|
|
if (squared_lamplacian <= target_squared_laplacian) {
|
|
|
|
|
return;
|
|
|
|
|
return lambda;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float lambda = 0.0f;
|
|
|
|
|
|
|
|
|
|
const int no_iterations = 10000000;
|
|
|
|
|
for (int i = 0; i < no_iterations; ++i) {
|
|
|
|
|
float f = 0.0f;
|
|
|
|
|
float fd = 0.0f;
|
|
|
|
|
|
|
|
|
|
for (int level = 1; level <= (int)STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL; ++level) {
|
|
|
|
|
for (int level = 1; level < STUDIOLIGHT_SH_BANDS; ++level) {
|
|
|
|
|
f += table_l[level] * table_b[level] / SQUARE(1.0f + lambda * table_l[level]);
|
|
|
|
|
fd += (2.0f * SQUARE(table_l[level]) * table_b[level]) / CUBE(1.0f + lambda * table_l[level]);
|
|
|
|
|
}
|
|
|
|
|
@@ -579,36 +524,53 @@ static void studiolight_apply_spherical_harmonics_windowing(StudioLight *sl, flo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return lambda;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_spherical_harmonics_apply_windowing(float (*sh)[3], float max_laplacian)
|
|
|
|
|
{
|
|
|
|
|
if (max_laplacian <= 0.0f)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
float sh_r[STUDIOLIGHT_SH_COEFS_LEN];
|
|
|
|
|
float sh_g[STUDIOLIGHT_SH_COEFS_LEN];
|
|
|
|
|
float sh_b[STUDIOLIGHT_SH_COEFS_LEN];
|
|
|
|
|
for (int i = 0; i < STUDIOLIGHT_SH_COEFS_LEN; i++) {
|
|
|
|
|
sh_r[i] = sh[i][0];
|
|
|
|
|
sh_g[i] = sh[i][1];
|
|
|
|
|
sh_b[i] = sh[i][2];
|
|
|
|
|
}
|
|
|
|
|
float lambda_r = studiolight_spherical_harmonics_lambda_get(sh_r, max_laplacian);
|
|
|
|
|
float lambda_g = studiolight_spherical_harmonics_lambda_get(sh_g, max_laplacian);
|
|
|
|
|
float lambda_b = studiolight_spherical_harmonics_lambda_get(sh_b, max_laplacian);
|
|
|
|
|
|
|
|
|
|
/* Apply windowing lambda */
|
|
|
|
|
index = 0;
|
|
|
|
|
for (int level = 0; level <= STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL; level++) {
|
|
|
|
|
float s = 1.0f / (1.0f + lambda * SQUARE(level) * SQUARE(level + 1.0f));
|
|
|
|
|
int index = 0;
|
|
|
|
|
for (int level = 0; level < STUDIOLIGHT_SH_BANDS; level++) {
|
|
|
|
|
float s[3];
|
|
|
|
|
s[0] = 1.0f / (1.0f + lambda_r * SQUARE(level) * SQUARE(level + 1.0f));
|
|
|
|
|
s[1] = 1.0f / (1.0f + lambda_g * SQUARE(level) * SQUARE(level + 1.0f));
|
|
|
|
|
s[2] = 1.0f / (1.0f + lambda_b * SQUARE(level) * SQUARE(level + 1.0f));
|
|
|
|
|
|
|
|
|
|
for (int m = -1; m <= level; m++) {
|
|
|
|
|
mul_v3_fl(sl->spherical_harmonics_coefs[index++], s);
|
|
|
|
|
mul_v3_v3(sh[index++], s);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
BLI_INLINE void studiolight_sample_spherical_harmonics(StudioLight *sl, float color[3], float normal[3])
|
|
|
|
|
BLI_INLINE void studiolight_spherical_harmonics_eval(StudioLight *sl, float color[3], float normal[3])
|
|
|
|
|
{
|
|
|
|
|
/* L0 */
|
|
|
|
|
mul_v3_v3fl(color, sl->spherical_harmonics_coefs[0], 0.282095f);
|
|
|
|
|
#if STUDIOLIGHT_SH_BANDS > 1 /* L1 */
|
|
|
|
|
const float nx = normal[0];
|
|
|
|
|
const float ny = normal[1];
|
|
|
|
|
const float nz = normal[2];
|
|
|
|
|
|
|
|
|
|
copy_v3_fl(color, 0.0f);
|
|
|
|
|
madd_v3_v3fl(color, sl->spherical_harmonics_coefs[0], 0.282095f);
|
|
|
|
|
|
|
|
|
|
#if STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL > 0
|
|
|
|
|
/* Spherical Harmonics L1 */
|
|
|
|
|
madd_v3_v3fl(color, sl->spherical_harmonics_coefs[1], -0.488603f * nz);
|
|
|
|
|
madd_v3_v3fl(color, sl->spherical_harmonics_coefs[2], 0.488603f * ny);
|
|
|
|
|
madd_v3_v3fl(color, sl->spherical_harmonics_coefs[3], -0.488603f * nx);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL > 1
|
|
|
|
|
/* Spherical Harmonics L2 */
|
|
|
|
|
#if STUDIOLIGHT_SH_BANDS > 2 /* L2 */
|
|
|
|
|
const float nx2 = SQUARE(nx);
|
|
|
|
|
const float ny2 = SQUARE(ny);
|
|
|
|
|
const float nz2 = SQUARE(nz);
|
|
|
|
|
@@ -618,9 +580,8 @@ BLI_INLINE void studiolight_sample_spherical_harmonics(StudioLight *sl, float co
|
|
|
|
|
madd_v3_v3fl(color, sl->spherical_harmonics_coefs[7], -1.092548 * nx * ny);
|
|
|
|
|
madd_v3_v3fl(color, sl->spherical_harmonics_coefs[8], 0.546274 * (nx2 - nz2));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL > 3
|
|
|
|
|
/* Spherical Harmonics L4 */
|
|
|
|
|
/* L3 coefs are 0 */
|
|
|
|
|
#if STUDIOLIGHT_SH_BANDS > 4 /* L4 */
|
|
|
|
|
const float nx4 = SQUARE(nx2);
|
|
|
|
|
const float ny4 = SQUARE(ny2);
|
|
|
|
|
const float nz4 = SQUARE(nz2);
|
|
|
|
|
@@ -634,7 +595,29 @@ BLI_INLINE void studiolight_sample_spherical_harmonics(StudioLight *sl, float co
|
|
|
|
|
madd_v3_v3fl(color, sl->spherical_harmonics_coefs[16], -1.7701307697799304f * nx * ny * (nx2 - 3.0f * nz2));
|
|
|
|
|
madd_v3_v3fl(color, sl->spherical_harmonics_coefs[17], 0.6258357354491761f * (nx4 - 6.0f * nz2 * nx2 + nz4));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This modify the radiance into irradiance. */
|
|
|
|
|
static void studiolight_spherical_harmonics_apply_band_factors(StudioLight *sl, float (*sh)[3])
|
|
|
|
|
{
|
|
|
|
|
static float sl_sh_band_factors[5] = {
|
|
|
|
|
1.0f,
|
|
|
|
|
2.0f / 3.0f,
|
|
|
|
|
1.0f / 4.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
-1.0f / 24.0f
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int index = 0, dst_idx = 0;
|
|
|
|
|
for (int band = 0; band < STUDIOLIGHT_SH_BANDS; band++) {
|
|
|
|
|
for (int m = -1; m <= band; m++) {
|
|
|
|
|
/* Skip L3 */
|
|
|
|
|
if (band != 3) {
|
|
|
|
|
mul_v3_v3fl(sl->spherical_harmonics_coefs[dst_idx++], sh[index], sl_sh_band_factors[band]);
|
|
|
|
|
}
|
|
|
|
|
index++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_diffuse_light(StudioLight *sl)
|
|
|
|
|
@@ -643,13 +626,10 @@ static void studiolight_calculate_diffuse_light(StudioLight *sl)
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_EXTERNAL_FILE) {
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_RADIANCE_BUFFERS_CALCULATED);
|
|
|
|
|
|
|
|
|
|
for (int comp = 0; comp < STUDIOLIGHT_SPHERICAL_HARMONICS_COMPONENTS; comp++) {
|
|
|
|
|
studiolight_calculate_spherical_harmonics_coefficient(sl, comp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef STUDIOLIGHT_SPHERICAL_HARMONICS_WINDOWING
|
|
|
|
|
studiolight_apply_spherical_harmonics_windowing(sl, STUDIOLIGHT_SPHERICAL_HARMONICS_WINDOWING_TARGET_LAMPLACIAN);
|
|
|
|
|
#endif
|
|
|
|
|
float sh_coefs[STUDIOLIGHT_SH_COEFS_LEN][3];
|
|
|
|
|
studiolight_spherical_harmonics_calculate_coefficients(sl, sh_coefs);
|
|
|
|
|
studiolight_spherical_harmonics_apply_windowing(sh_coefs, STUDIOLIGHT_SH_WINDOWING);
|
|
|
|
|
studiolight_spherical_harmonics_apply_band_factors(sl, sh_coefs);
|
|
|
|
|
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_USER_DEFINED) {
|
|
|
|
|
FILE *fp = BLI_fopen(sl->path_sh_cache, "wb");
|
|
|
|
|
@@ -662,81 +642,70 @@ static void studiolight_calculate_diffuse_light(StudioLight *sl)
|
|
|
|
|
sl->flag |= STUDIOLIGHT_SPHERICAL_HARMONICS_COEFFICIENTS_CALCULATED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static float texel_coord_solid_angle(float a_U, float a_V, int a_Size)
|
|
|
|
|
{
|
|
|
|
|
//scale up to [-1, 1] range (inclusive), offset by 0.5 to point to texel center.
|
|
|
|
|
float u = (2.0f * ((float)a_U + 0.5f) / (float)a_Size) - 1.0f;
|
|
|
|
|
float v = (2.0f * ((float)a_V + 0.5f) / (float)a_Size) - 1.0f;
|
|
|
|
|
|
|
|
|
|
float resolution_inv = 1.0f / a_Size;
|
|
|
|
|
|
|
|
|
|
// U and V are the -1..1 texture coordinate on the current face.
|
|
|
|
|
// Get projected area for this texel
|
|
|
|
|
float x0 = u - resolution_inv;
|
|
|
|
|
float y0 = v - resolution_inv;
|
|
|
|
|
float x1 = u + resolution_inv;
|
|
|
|
|
float y1 = v + resolution_inv;
|
|
|
|
|
return studiolight_area_element(x0, y0) - studiolight_area_element(x0, y1) - studiolight_area_element(x1, y0) + studiolight_area_element(x1, y1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BLI_INLINE void studiolight_evaluate_specular_radiance_buffer(
|
|
|
|
|
ImBuf *radiance_buffer, const float normal[3], float color[3],
|
|
|
|
|
int xoffset, int yoffset, int zoffset, float zvalue)
|
|
|
|
|
int xoffset, int yoffset, int zoffset, float zsign)
|
|
|
|
|
{
|
|
|
|
|
if (radiance_buffer == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
float angle;
|
|
|
|
|
float *radiance_color = radiance_buffer->rect_float;
|
|
|
|
|
float direction[3];
|
|
|
|
|
for (int y = 0; y < STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE; y++) {
|
|
|
|
|
for (int x = 0; x < STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE; x++) {
|
|
|
|
|
// calculate light direction;
|
|
|
|
|
float u = (x / (float)STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE) - 0.5f;
|
|
|
|
|
float v = (y / (float)STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE) - 0.5f;
|
|
|
|
|
direction[zoffset] = zvalue;
|
|
|
|
|
direction[xoffset] = u;
|
|
|
|
|
direction[yoffset] = v;
|
|
|
|
|
normalize_v3(direction);
|
|
|
|
|
angle = fmax(0.0f, dot_v3v3(direction, normal)) * texel_coord_solid_angle(x, y, STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE);
|
|
|
|
|
madd_v3_v3fl(color, radiance_color, angle);
|
|
|
|
|
radiance_color += 4;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float accum[3] = {0.0f, 0.0f, 0.0f};
|
|
|
|
|
float accum_weight = 0.00001f;
|
|
|
|
|
ITER_PIXELS(float, radiance_buffer->rect_float, 4,
|
|
|
|
|
STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE,
|
|
|
|
|
STUDIOLIGHT_RADIANCE_CUBEMAP_SIZE)
|
|
|
|
|
{
|
|
|
|
|
float direction[3];
|
|
|
|
|
direction[zoffset] = zsign * 0.5f;
|
|
|
|
|
direction[xoffset] = x - 0.5f;
|
|
|
|
|
direction[yoffset] = y - 0.5f;
|
|
|
|
|
normalize_v3(direction);
|
|
|
|
|
float weight = dot_v3v3(direction, normal) > 0.95f ? 1.0f : 0.0f;
|
|
|
|
|
// float solid_angle = texel_solid_angle(x, y, texel_size[0] * 0.5f);
|
|
|
|
|
madd_v3_v3fl(accum, pixel, weight);
|
|
|
|
|
accum_weight += weight;
|
|
|
|
|
}
|
|
|
|
|
ITER_PIXELS_END;
|
|
|
|
|
|
|
|
|
|
madd_v3_v3fl(color, accum, 1.0f / accum_weight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if STUDIOLIGHT_IRRADIANCE_METHOD == STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
static void studiolight_calculate_specular_irradiance(StudioLight *sl, float color[3], const float normal[3])
|
|
|
|
|
#ifdef STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
static void studiolight_irradiance_eval(StudioLight *sl, float color[3], const float normal[3])
|
|
|
|
|
{
|
|
|
|
|
copy_v3_fl(color, 0.0f);
|
|
|
|
|
|
|
|
|
|
/* XXX: This is madness, iterating over all cubemap pixels for each destination pixels
|
|
|
|
|
* even if their weight is 0.0f.
|
|
|
|
|
* It should use hemisphere, cosine sampling at least. */
|
|
|
|
|
|
|
|
|
|
/* back */
|
|
|
|
|
studiolight_evaluate_specular_radiance_buffer(
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Y_POS], normal, color, 0, 2, 1, 0.5);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Y_POS], normal, color, 0, 2, 1, 1);
|
|
|
|
|
/* front */
|
|
|
|
|
studiolight_evaluate_specular_radiance_buffer(
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Y_NEG], normal, color, 0, 2, 1, -0.5);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Y_NEG], normal, color, 0, 2, 1, -1);
|
|
|
|
|
|
|
|
|
|
/* left */
|
|
|
|
|
studiolight_evaluate_specular_radiance_buffer(
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_X_POS], normal, color, 1, 2, 0, 0.5);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_X_POS], normal, color, 1, 2, 0, 1);
|
|
|
|
|
/* right */
|
|
|
|
|
studiolight_evaluate_specular_radiance_buffer(
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_X_NEG], normal, color, 1, 2, 0, -0.5);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_X_NEG], normal, color, 1, 2, 0, -1);
|
|
|
|
|
|
|
|
|
|
/* top */
|
|
|
|
|
studiolight_evaluate_specular_radiance_buffer(
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Z_POS], normal, color, 0, 1, 2, 0.5);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Z_POS], normal, color, 0, 1, 2, 1);
|
|
|
|
|
/* bottom */
|
|
|
|
|
studiolight_evaluate_specular_radiance_buffer(
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Z_NEG], normal, color, 0, 1, 2, -0.5);
|
|
|
|
|
sl->radiance_cubemap_buffers[STUDIOLIGHT_Z_NEG], normal, color, 0, 1, 2, -1);
|
|
|
|
|
|
|
|
|
|
mul_v3_fl(color, 1.0 / M_PI);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
static bool studiolight_load_irradiance_equirectangular_image(StudioLight *sl)
|
|
|
|
|
static bool studiolight_load_irradiance_equirect_image(StudioLight *sl)
|
|
|
|
|
{
|
|
|
|
|
#ifdef STUDIOLIGHT_LOAD_CACHED_FILES
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_EXTERNAL_FILE) {
|
|
|
|
|
@@ -744,11 +713,13 @@ static bool studiolight_load_irradiance_equirectangular_image(StudioLight *sl)
|
|
|
|
|
ibuf = IMB_loadiffname(sl->path_irr_cache, 0, NULL);
|
|
|
|
|
if (ibuf) {
|
|
|
|
|
IMB_float_from_rect(ibuf);
|
|
|
|
|
sl->equirectangular_irradiance_buffer = ibuf;
|
|
|
|
|
sl->flag |= STUDIOLIGHT_EQUIRECTANGULAR_IRRADIANCE_IMAGE_CALCULATED;
|
|
|
|
|
sl->equirect_irradiance_buffer = ibuf;
|
|
|
|
|
sl->flag |= STUDIOLIGHT_EQUIRECT_IRRADIANCE_IMAGE_CALCULATED;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
UNUSED_VARS(sl);
|
|
|
|
|
#endif
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
@@ -767,92 +738,54 @@ static bool studiolight_load_spherical_harmonics_coefficients(StudioLight *sl)
|
|
|
|
|
fclose(fp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
UNUSED_VARS(sl);
|
|
|
|
|
#endif
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_irradiance_equirectangular_image(StudioLight *sl)
|
|
|
|
|
static void studiolight_calculate_irradiance_equirect_image(StudioLight *sl)
|
|
|
|
|
{
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_EXTERNAL_FILE) {
|
|
|
|
|
#if STUDIOLIGHT_IRRADIANCE_METHOD == STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
#ifdef STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_RADIANCE_BUFFERS_CALCULATED);
|
|
|
|
|
#endif
|
|
|
|
|
#if STUDIOLIGHT_IRRADIANCE_METHOD == STUDIOLIGHT_IRRADIANCE_METHOD_SPHERICAL_HARMONICS
|
|
|
|
|
#else
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_SPHERICAL_HARMONICS_COEFFICIENTS_CALCULATED);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
float *colbuf = MEM_mallocN(STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_WIDTH * STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_HEIGHT * sizeof(float[4]), __func__);
|
|
|
|
|
float *color = colbuf;
|
|
|
|
|
for (int y = 0; y < STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_HEIGHT; y++) {
|
|
|
|
|
float yf = y / (float)STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_HEIGHT;
|
|
|
|
|
float *colbuf = MEM_mallocN(STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH * STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT * sizeof(float[4]), __func__);
|
|
|
|
|
|
|
|
|
|
for (int x = 0; x < STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_WIDTH; x++) {
|
|
|
|
|
float xf = x / (float)STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_WIDTH;
|
|
|
|
|
float dir[3];
|
|
|
|
|
equirectangular_to_direction(dir, xf, yf);
|
|
|
|
|
|
|
|
|
|
#if STUDIOLIGHT_IRRADIANCE_METHOD == STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
studiolight_calculate_specular_irradiance(sl, color, dir);
|
|
|
|
|
ITER_PIXELS(float, colbuf, 4,
|
|
|
|
|
STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH,
|
|
|
|
|
STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT)
|
|
|
|
|
{
|
|
|
|
|
float dir[3];
|
|
|
|
|
equirect_to_direction(dir, x, y);
|
|
|
|
|
#ifdef STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
studiolight_irradiance_eval(sl, pixel, dir);
|
|
|
|
|
#else
|
|
|
|
|
studiolight_spherical_harmonics_eval(sl, pixel, dir);
|
|
|
|
|
#endif
|
|
|
|
|
#if STUDIOLIGHT_IRRADIANCE_METHOD == STUDIOLIGHT_IRRADIANCE_METHOD_SPHERICAL_HARMONICS
|
|
|
|
|
studiolight_sample_spherical_harmonics(sl, color, dir);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
color[3] = 1.0f;
|
|
|
|
|
color += 4;
|
|
|
|
|
}
|
|
|
|
|
pixel[3] = 1.0f;
|
|
|
|
|
}
|
|
|
|
|
ITER_PIXELS_END;
|
|
|
|
|
|
|
|
|
|
sl->equirectangular_irradiance_buffer = IMB_allocFromBuffer(
|
|
|
|
|
sl->equirect_irradiance_buffer = IMB_allocFromBuffer(
|
|
|
|
|
NULL, colbuf,
|
|
|
|
|
STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_WIDTH,
|
|
|
|
|
STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_HEIGHT);
|
|
|
|
|
STUDIOLIGHT_IRRADIANCE_EQUIRECT_WIDTH,
|
|
|
|
|
STUDIOLIGHT_IRRADIANCE_EQUIRECT_HEIGHT);
|
|
|
|
|
MEM_freeN(colbuf);
|
|
|
|
|
|
|
|
|
|
#if STUDIOLIGHT_IRRADIANCE_METHOD == STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
#ifdef STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
/*
|
|
|
|
|
* Only store cached files when using STUDIOLIGHT_IRRADIANCE_METHOD_RADIANCE
|
|
|
|
|
*/
|
|
|
|
|
if (sl->flag & STUDIOLIGHT_USER_DEFINED) {
|
|
|
|
|
IMB_saveiff(sl->equirectangular_irradiance_buffer, sl->path_irr_cache, IB_rectfloat);
|
|
|
|
|
IMB_saveiff(sl->equirect_irradiance_buffer, sl->path_irr_cache, IB_rectfloat);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
sl->flag |= STUDIOLIGHT_EQUIRECTANGULAR_IRRADIANCE_IMAGE_CALCULATED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_calculate_light_direction(StudioLight *sl)
|
|
|
|
|
{
|
|
|
|
|
float best_light = 0.0;
|
|
|
|
|
sl->light_direction[0] = 0.0f;
|
|
|
|
|
sl->light_direction[1] = 0.0f;
|
|
|
|
|
sl->light_direction[2] = -1.0f;
|
|
|
|
|
|
|
|
|
|
if ((sl->flag & STUDIOLIGHT_EXTERNAL_FILE) && (sl->flag & STUDIOLIGHT_ORIENTATION_WORLD)) {
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EQUIRECTANGULAR_IRRADIANCE_IMAGE_CALCULATED);
|
|
|
|
|
ImBuf *ibuf = sl->equirectangular_irradiance_buffer;
|
|
|
|
|
if (ibuf) {
|
|
|
|
|
/* go over every pixel, determine light, if higher calc direction off the light */
|
|
|
|
|
float new_light;
|
|
|
|
|
float *color = ibuf->rect_float;
|
|
|
|
|
for (int y = 0; y < STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_HEIGHT; y++) {
|
|
|
|
|
for (int x = 0; x < STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_WIDTH; x++) {
|
|
|
|
|
new_light = color[0] + color[1] + color[2];
|
|
|
|
|
if (new_light > best_light) {
|
|
|
|
|
float u = x / (float)STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_WIDTH;
|
|
|
|
|
float v = y / (float)STUDIOLIGHT_IRRADIANCE_EQUIRECTANGULAR_HEIGHT;
|
|
|
|
|
equirectangular_to_direction(sl->light_direction, u, v);
|
|
|
|
|
SWAP(float, sl->light_direction[0], sl->light_direction[1]);
|
|
|
|
|
normalize_v3(sl->light_direction);
|
|
|
|
|
negate_v3(sl->light_direction);
|
|
|
|
|
best_light = new_light;
|
|
|
|
|
}
|
|
|
|
|
color += 4;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sl->flag |= STUDIOLIGHT_LIGHT_DIRECTION_CALCULATED;
|
|
|
|
|
sl->flag |= STUDIOLIGHT_EQUIRECT_IRRADIANCE_IMAGE_CALCULATED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static StudioLight *studiolight_add_file(const char *path, int flag)
|
|
|
|
|
@@ -931,119 +864,115 @@ static uint alpha_circle_mask(float u, float v, float inner_edge, float outer_ed
|
|
|
|
|
return mask << 24;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Percentage of the icon that the preview sphere covers. */
|
|
|
|
|
#define STUDIOLIGHT_DIAMETER 0.95f
|
|
|
|
|
/* Rescale coord around (0.5, 0.5) by STUDIOLIGHT_DIAMETER. */
|
|
|
|
|
#define RESCALE_COORD(x) (x / STUDIOLIGHT_DIAMETER - (1.0f - STUDIOLIGHT_DIAMETER) / 2.0f)
|
|
|
|
|
|
|
|
|
|
/* Remaps normalized UV [0..1] to a sphere normal around (0.5, 0.5) */
|
|
|
|
|
static void sphere_normal_from_uv(float normal[3], float u, float v)
|
|
|
|
|
{
|
|
|
|
|
normal[0] = u * 2.0f - 1.0f;
|
|
|
|
|
normal[1] = v * 2.0f - 1.0f;
|
|
|
|
|
float dist = len_v2(normal);
|
|
|
|
|
normal[2] = sqrtf(1.0f - SQUARE(dist));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_radiance_preview(uint *icon_buffer, StudioLight *sl)
|
|
|
|
|
{
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EXTERNAL_IMAGE_LOADED);
|
|
|
|
|
|
|
|
|
|
float pixel_size = 1.0f / (float)STUDIOLIGHT_ICON_SIZE;
|
|
|
|
|
ITER_PIXELS(uint, icon_buffer, 1,
|
|
|
|
|
STUDIOLIGHT_ICON_SIZE,
|
|
|
|
|
STUDIOLIGHT_ICON_SIZE)
|
|
|
|
|
{
|
|
|
|
|
float dy = RESCALE_COORD(y);
|
|
|
|
|
float dx = RESCALE_COORD(x);
|
|
|
|
|
|
|
|
|
|
int offset = 0;
|
|
|
|
|
for (int y = 0; y < STUDIOLIGHT_ICON_SIZE; y++) {
|
|
|
|
|
float dy = (y + 0.5f) / (float)STUDIOLIGHT_ICON_SIZE;
|
|
|
|
|
dy = dy / STUDIOLIGHT_DIAMETER - (1.0f - STUDIOLIGHT_DIAMETER) / 2.0f;
|
|
|
|
|
for (int x = 0; x < STUDIOLIGHT_ICON_SIZE; x++) {
|
|
|
|
|
float dx = (x + 0.5f) / (float)STUDIOLIGHT_ICON_SIZE;
|
|
|
|
|
dx = dx / STUDIOLIGHT_DIAMETER - (1.0f - STUDIOLIGHT_DIAMETER) / 2.0f;
|
|
|
|
|
uint alphamask = alpha_circle_mask(dx, dy, 0.5f - texel_size[0], 0.5f);
|
|
|
|
|
if (alphamask != 0) {
|
|
|
|
|
float normal[3], direction[3], color[4];
|
|
|
|
|
float incoming[3] = {0.0f, 0.0f, -1.0f};
|
|
|
|
|
sphere_normal_from_uv(normal, dx, dy);
|
|
|
|
|
reflect_v3_v3v3(direction, incoming, normal);
|
|
|
|
|
/* We want to see horizon not poles. */
|
|
|
|
|
SWAP(float, direction[1], direction[2]);
|
|
|
|
|
direction[1] = -direction[1];
|
|
|
|
|
|
|
|
|
|
uint pixelresult = 0x0;
|
|
|
|
|
uint alphamask = alpha_circle_mask(dx, dy, 0.5f - pixel_size, 0.5f);
|
|
|
|
|
if (alphamask != 0) {
|
|
|
|
|
float incoming[3] = {0.0f, 0.0f, -1.0f};
|
|
|
|
|
studiolight_calculate_radiance(sl->equirect_radiance_buffer, color, direction);
|
|
|
|
|
|
|
|
|
|
float normal[3];
|
|
|
|
|
normal[0] = dx * 2.0f - 1.0f;
|
|
|
|
|
normal[1] = dy * 2.0f - 1.0f;
|
|
|
|
|
float dist = len_v2(normal);
|
|
|
|
|
normal[2] = sqrtf(1.0f - SQUARE(dist));
|
|
|
|
|
|
|
|
|
|
float direction[3];
|
|
|
|
|
reflect_v3_v3v3(direction, incoming, normal);
|
|
|
|
|
|
|
|
|
|
/* We want to see horizon not poles. */
|
|
|
|
|
SWAP(float, direction[1], direction[2]);
|
|
|
|
|
direction[1] = -direction[1];
|
|
|
|
|
|
|
|
|
|
float color[4];
|
|
|
|
|
studiolight_calculate_radiance(sl->equirectangular_radiance_buffer, color, direction);
|
|
|
|
|
|
|
|
|
|
pixelresult = rgb_to_cpack(
|
|
|
|
|
linearrgb_to_srgb(color[0]),
|
|
|
|
|
linearrgb_to_srgb(color[1]),
|
|
|
|
|
linearrgb_to_srgb(color[2])) | alphamask;
|
|
|
|
|
}
|
|
|
|
|
icon_buffer[offset++] = pixelresult;
|
|
|
|
|
*pixel = rgb_to_cpack(
|
|
|
|
|
linearrgb_to_srgb(color[0]),
|
|
|
|
|
linearrgb_to_srgb(color[1]),
|
|
|
|
|
linearrgb_to_srgb(color[2])) | alphamask;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
*pixel = 0x0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ITER_PIXELS_END;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_matcap_preview(uint *icon_buffer, StudioLight *sl, bool flipped)
|
|
|
|
|
{
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EXTERNAL_IMAGE_LOADED);
|
|
|
|
|
|
|
|
|
|
float color[4];
|
|
|
|
|
float fx, fy;
|
|
|
|
|
float pixel_size = 1.0f / (float)STUDIOLIGHT_ICON_SIZE;
|
|
|
|
|
int offset = 0;
|
|
|
|
|
ImBuf *ibuf = sl->equirectangular_radiance_buffer;
|
|
|
|
|
ImBuf *ibuf = sl->equirect_radiance_buffer;
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < STUDIOLIGHT_ICON_SIZE; y++) {
|
|
|
|
|
fy = (y + 0.5f) / (float)STUDIOLIGHT_ICON_SIZE;
|
|
|
|
|
fy = fy / STUDIOLIGHT_DIAMETER - (1.0f - STUDIOLIGHT_DIAMETER) / 2.0f;
|
|
|
|
|
for (int x = 0; x < STUDIOLIGHT_ICON_SIZE; x++) {
|
|
|
|
|
fx = (x + 0.5f) / (float)STUDIOLIGHT_ICON_SIZE;
|
|
|
|
|
fx = fx / STUDIOLIGHT_DIAMETER - (1.0f - STUDIOLIGHT_DIAMETER) / 2.0f;
|
|
|
|
|
if (flipped) {
|
|
|
|
|
fx = 1.0f - fx;
|
|
|
|
|
}
|
|
|
|
|
nearest_interpolation_color(ibuf, NULL, color, fx * ibuf->x - 1.0f, fy * ibuf->y - 1.0f);
|
|
|
|
|
|
|
|
|
|
uint alphamask = alpha_circle_mask(fx, fy, 0.5f - pixel_size, 0.5f);
|
|
|
|
|
|
|
|
|
|
icon_buffer[offset++] = rgb_to_cpack(
|
|
|
|
|
linearrgb_to_srgb(color[0]),
|
|
|
|
|
linearrgb_to_srgb(color[1]),
|
|
|
|
|
linearrgb_to_srgb(color[2])) | alphamask;
|
|
|
|
|
ITER_PIXELS(uint, icon_buffer, 1,
|
|
|
|
|
STUDIOLIGHT_ICON_SIZE,
|
|
|
|
|
STUDIOLIGHT_ICON_SIZE)
|
|
|
|
|
{
|
|
|
|
|
float dy = RESCALE_COORD(y);
|
|
|
|
|
float dx = RESCALE_COORD(x);
|
|
|
|
|
if (flipped) {
|
|
|
|
|
dx = 1.0f - dx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float color[4];
|
|
|
|
|
nearest_interpolation_color(ibuf, NULL, color, dx * ibuf->x - 1.0f, dy * ibuf->y - 1.0f);
|
|
|
|
|
|
|
|
|
|
uint alphamask = alpha_circle_mask(dx, dy, 0.5f - texel_size[0], 0.5f);
|
|
|
|
|
|
|
|
|
|
*pixel = rgb_to_cpack(
|
|
|
|
|
linearrgb_to_srgb(color[0]),
|
|
|
|
|
linearrgb_to_srgb(color[1]),
|
|
|
|
|
linearrgb_to_srgb(color[2])) | alphamask;
|
|
|
|
|
}
|
|
|
|
|
ITER_PIXELS_END;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void studiolight_irradiance_preview(uint *icon_buffer, StudioLight *sl)
|
|
|
|
|
{
|
|
|
|
|
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_SPHERICAL_HARMONICS_COEFFICIENTS_CALCULATED);
|
|
|
|
|
|
|
|
|
|
float pixel_size = 1.0f / (float)STUDIOLIGHT_ICON_SIZE;
|
|
|
|
|
ITER_PIXELS(uint, icon_buffer, 1,
|
|
|
|
|
STUDIOLIGHT_ICON_SIZE,
|
|
|
|
|
STUDIOLIGHT_ICON_SIZE)
|
|
|
|
|
{
|
|
|
|
|
float dy = RESCALE_COORD(y);
|
|
|
|
|
float dx = RESCALE_COORD(x);
|
|
|
|
|
|
|
|
|
|
int offset = 0;
|
|
|
|
|
for (int y = 0; y < STUDIOLIGHT_ICON_SIZE; y++) {
|
|
|
|
|
float dy = (y + 0.5f) / (float)STUDIOLIGHT_ICON_SIZE;
|
|
|
|
|
dy = dy / STUDIOLIGHT_DIAMETER - (1.0f - STUDIOLIGHT_DIAMETER) / 2.0f;
|
|
|
|
|
for (int x = 0; x < STUDIOLIGHT_ICON_SIZE; x++) {
|
|
|
|
|
float dx = (x + 0.5f) / (float)STUDIOLIGHT_ICON_SIZE;
|
|
|
|
|
dx = dx / STUDIOLIGHT_DIAMETER - (1.0f - STUDIOLIGHT_DIAMETER) / 2.0f;
|
|
|
|
|
uint alphamask = alpha_circle_mask(dx, dy, 0.5f - texel_size[0], 0.5f);
|
|
|
|
|
if (alphamask != 0) {
|
|
|
|
|
float normal[3], color[3];
|
|
|
|
|
sphere_normal_from_uv(normal, dx, dy);
|
|
|
|
|
/* We want to see horizon not poles. */
|
|
|
|
|
SWAP(float, normal[1], normal[2]);
|
|
|
|
|
normal[1] = -normal[1];
|
|
|
|
|
|
|
|
|
|
uint pixelresult = 0x0;
|
|
|
|
|
uint alphamask = alpha_circle_mask(dx, dy, 0.5f - pixel_size, 0.5f);
|
|
|
|
|
if (alphamask != 0) {
|
|
|
|
|
/* calculate normal */
|
|
|
|
|
float normal[3];
|
|
|
|
|
normal[0] = dx * 2.0f - 1.0f;
|
|
|
|
|
normal[1] = -(dy * 2.0f - 1.0f);
|
|
|
|
|
float dist = len_v2(normal);
|
|
|
|
|
normal[2] = -sqrtf(1.0f - SQUARE(dist));
|
|
|
|
|
SWAP(float, normal[1], normal[2]);
|
|
|
|
|
studiolight_spherical_harmonics_eval(sl, color, normal);
|
|
|
|
|
|
|
|
|
|
float color[3];
|
|
|
|
|
studiolight_sample_spherical_harmonics(sl, color, normal);
|
|
|
|
|
pixelresult = rgb_to_cpack(
|
|
|
|
|
linearrgb_to_srgb(color[0]),
|
|
|
|
|
linearrgb_to_srgb(color[1]),
|
|
|
|
|
linearrgb_to_srgb(color[2])) | alphamask;
|
|
|
|
|
}
|
|
|
|
|
icon_buffer[offset++] = pixelresult;
|
|
|
|
|
*pixel = rgb_to_cpack(
|
|
|
|
|
linearrgb_to_srgb(color[0]),
|
|
|
|
|
linearrgb_to_srgb(color[1]),
|
|
|
|
|
linearrgb_to_srgb(color[2])) | alphamask;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
*pixel = 0x0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ITER_PIXELS_END;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* API */
|
|
|
|
|
@@ -1057,19 +986,19 @@ void BKE_studiolight_init(void)
|
|
|
|
|
sl = studiolight_create(STUDIOLIGHT_INTERNAL | STUDIOLIGHT_SPHERICAL_HARMONICS_COEFFICIENTS_CALCULATED | STUDIOLIGHT_ORIENTATION_CAMERA);
|
|
|
|
|
BLI_strncpy(sl->name, "Default", FILE_MAXFILE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[0], 1.03271556f, 1.07163882f, 1.11193657f);
|
|
|
|
|
#if STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL > 0
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[1], -0.00480952f, 0.05290511f, 0.16394117f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[2], -0.29686999f, -0.27378261f, -0.24797194f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[3], 0.47932500f, 0.48242140f, 0.47190312f);
|
|
|
|
|
int i = 0;
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[i++], 1.03271556f, 1.07163882f, 1.11193657f);
|
|
|
|
|
#if STUDIOLIGHT_SH_BANDS > 1
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[i++], -0.00480952f, 0.05290511f, 0.16394117f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[i++], -0.29686999f, -0.27378261f, -0.24797194f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[i++], 0.47932500f, 0.48242140f, 0.47190312f);
|
|
|
|
|
#endif
|
|
|
|
|
#if STUDIOLIGHT_SPHERICAL_HARMONICS_LEVEL > 1
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[4], -0.00576984f, 0.00504886f, 0.01640534f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[5], 0.15500379f, 0.15415503f, 0.16244425f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[6], -0.02483751f, -0.02245096f, -0.00536885f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[7], 0.11155496f, 0.11005443f, 0.10839636f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[8], 0.01363425f, 0.01278363f, -0.00159006f);
|
|
|
|
|
#if STUDIOLIGHT_SH_BANDS > 2
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[i++], -0.00576984f, 0.00504886f, 0.01640534f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[i++], 0.15500379f, 0.15415503f, 0.16244425f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[i++], -0.02483751f, -0.02245096f, -0.00536885f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[i++], 0.11155496f, 0.11005443f, 0.10839636f);
|
|
|
|
|
copy_v3_fl3(sl->spherical_harmonics_coefs[i++], 0.01363425f, 0.01278363f, -0.00159006f);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
BLI_addtail(&studiolights, sl);
|
|
|
|
|
@@ -1171,7 +1100,7 @@ void BKE_studiolight_ensure_flag(StudioLight *sl, int flag)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((flag & STUDIOLIGHT_EXTERNAL_IMAGE_LOADED)) {
|
|
|
|
|
studiolight_load_equirectangular_image(sl);
|
|
|
|
|
studiolight_load_equirect_image(sl);
|
|
|
|
|
}
|
|
|
|
|
if ((flag & STUDIOLIGHT_RADIANCE_BUFFERS_CALCULATED)) {
|
|
|
|
|
studiolight_calculate_radiance_cubemap_buffers(sl);
|
|
|
|
|
@@ -1181,18 +1110,15 @@ void BKE_studiolight_ensure_flag(StudioLight *sl, int flag)
|
|
|
|
|
studiolight_calculate_diffuse_light(sl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ((flag & STUDIOLIGHT_EQUIRECTANGULAR_RADIANCE_GPUTEXTURE)) {
|
|
|
|
|
studiolight_create_equirectangular_radiance_gputexture(sl);
|
|
|
|
|
if ((flag & STUDIOLIGHT_EQUIRECT_RADIANCE_GPUTEXTURE)) {
|
|
|
|
|
studiolight_create_equirect_radiance_gputexture(sl);
|
|
|
|
|
}
|
|
|
|
|
if ((flag & STUDIOLIGHT_LIGHT_DIRECTION_CALCULATED)) {
|
|
|
|
|
studiolight_calculate_light_direction(sl);
|
|
|
|
|
if ((flag & STUDIOLIGHT_EQUIRECT_IRRADIANCE_GPUTEXTURE)) {
|
|
|
|
|
studiolight_create_equirect_irradiance_gputexture(sl);
|
|
|
|
|
}
|
|
|
|
|
if ((flag & STUDIOLIGHT_EQUIRECTANGULAR_IRRADIANCE_GPUTEXTURE)) {
|
|
|
|
|
studiolight_create_equirectangular_irradiance_gputexture(sl);
|
|
|
|
|
}
|
|
|
|
|
if ((flag & STUDIOLIGHT_EQUIRECTANGULAR_IRRADIANCE_IMAGE_CALCULATED)) {
|
|
|
|
|
if (!studiolight_load_irradiance_equirectangular_image(sl)) {
|
|
|
|
|
studiolight_calculate_irradiance_equirectangular_image(sl);
|
|
|
|
|
if ((flag & STUDIOLIGHT_EQUIRECT_IRRADIANCE_IMAGE_CALCULATED)) {
|
|
|
|
|
if (!studiolight_load_irradiance_equirect_image(sl)) {
|
|
|
|
|
studiolight_calculate_irradiance_equirect_image(sl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|