21using namespace helios;
24constexpr float LensFlare::ghost_scales_[];
25constexpr float LensFlare::ghost_sizes_[];
29 int max_dim = std::max(resolution.
x, resolution.
y);
31 while (kernel_size_ < max_dim / 4 && kernel_size_ < 256) {
36 generateStarburstKernel();
40 if (pixel_data.empty()) {
47 if (bright_pixels.empty()) {
53 applyStarburst(pixel_data, resolution, bright_pixels);
58 applyGhosts(pixel_data, resolution, bright_pixels);
62std::vector<std::tuple<int, int, float>>
LensFlare::findBrightPixels(
const std::map<std::string, std::vector<float>> &pixel_data,
int2 resolution,
float threshold)
const {
63 std::vector<std::tuple<int, int, float>> bright_pixels;
65 if (pixel_data.empty()) {
69 int num_pixels = resolution.
x * resolution.
y;
72 const auto &first_band = pixel_data.begin()->second;
73 if (
static_cast<int>(first_band.size()) != num_pixels) {
78 std::vector<float> max_intensity(num_pixels, 0.0f);
79 for (
const auto &band_pair: pixel_data) {
80 const auto &band_data = band_pair.second;
81 for (
int i = 0; i < num_pixels; ++i) {
82 max_intensity[i] = std::max(max_intensity[i], band_data[i]);
87 for (
int j = 0; j < resolution.
y; ++j) {
88 for (
int i = 0; i < resolution.
x; ++i) {
89 int idx = j * resolution.
x + i;
90 if (max_intensity[idx] >= threshold) {
91 bright_pixels.emplace_back(i, j, max_intensity[idx]);
97 std::sort(bright_pixels.begin(), bright_pixels.end(), [](
const auto &a,
const auto &b) { return std::get<2>(a) > std::get<2>(b); });
100 constexpr size_t max_bright_pixels = 100;
101 if (bright_pixels.size() > max_bright_pixels) {
102 bright_pixels.resize(max_bright_pixels);
105 return bright_pixels;
108void LensFlare::generateStarburstKernel() {
110 std::vector<float> aperture_mask;
111 generateApertureMask(aperture_mask);
114 std::vector<std::complex<float>> fft_result;
115 fft2D(aperture_mask, fft_result, kernel_size_);
118 starburst_kernel_.resize(kernel_size_ * kernel_size_);
119 float max_magnitude = 0.0f;
120 for (
size_t i = 0; i < fft_result.size(); ++i) {
121 float mag = std::abs(fft_result[i]);
122 starburst_kernel_[i] = mag;
123 max_magnitude = std::max(max_magnitude, mag);
127 if (max_magnitude > 0.0f) {
128 for (
float &val: starburst_kernel_) {
129 val /= max_magnitude;
134 float center =
static_cast<float>(kernel_size_) / 2.0f;
135 float max_dist = center * 1.414f;
136 for (
int y = 0; y < kernel_size_; ++y) {
137 for (
int x = 0; x < kernel_size_; ++x) {
138 size_t i = y * kernel_size_ + x;
139 float dx = x - center;
140 float dy = y - center;
141 float dist = std::sqrt(dx * dx + dy * dy);
142 float normalized_dist = std::min(dist / center, 1.0f);
145 float window = (normalized_dist < 1.0f) ? (0.5f + 0.5f * std::cos(normalized_dist *
M_PI)) : 0.0f;
146 starburst_kernel_[i] *= window;
151 for (
float &val: starburst_kernel_) {
152 val = std::pow(val, 0.5f);
156void LensFlare::generateApertureMask(std::vector<float> &mask)
const {
157 mask.resize(kernel_size_ * kernel_size_, 0.0f);
160 float center =
static_cast<float>(kernel_size_) / 2.0f;
161 float radius = center * 0.8f;
164 std::vector<std::pair<float, float>> vertices(blade_count);
165 for (
int i = 0; i < blade_count; ++i) {
166 float angle = 2.0f *
static_cast<float>(
M_PI) *
static_cast<float>(i) /
static_cast<float>(blade_count);
168 angle +=
static_cast<float>(
M_PI) / 2.0f;
169 vertices[i] = {center + radius * std::cos(angle), center + radius * std::sin(angle)};
173 for (
int y = 0; y < kernel_size_; ++y) {
174 for (
int x = 0; x < kernel_size_; ++x) {
176 float px =
static_cast<float>(x) + 0.5f;
177 float py =
static_cast<float>(y) + 0.5f;
180 for (
int i = 0; i < blade_count; ++i) {
181 int j = (i + 1) % blade_count;
182 float x1 = vertices[i].first, y1 = vertices[i].second;
183 float x2 = vertices[j].first, y2 = vertices[j].second;
186 if ((y1 <= py && y2 > py) || (y2 <= py && y1 > py)) {
188 float t = (py - y1) / (y2 - y1);
189 float x_intersect = x1 + t * (x2 - x1);
190 if (px < x_intersect) {
197 if (crossings % 2 == 1) {
198 mask[y * kernel_size_ + x] = 1.0f;
207void LensFlare::applyStarburst(std::map<std::string, std::vector<float>> &pixel_data,
int2 resolution,
const std::vector<std::tuple<int, int, float>> &bright_pixels) {
208 if (starburst_kernel_.empty() || bright_pixels.empty()) {
213 int half_kernel = kernel_size_ / 2;
216 for (
const auto &bright_pixel: bright_pixels) {
217 int bx = std::get<0>(bright_pixel);
218 int by = std::get<1>(bright_pixel);
219 float pixel_intensity = std::get<2>(bright_pixel);
222 int pixel_idx = by * resolution.
x + bx;
223 std::map<std::string, float> color_ratios;
225 for (
const auto &[band_label, band_data]: pixel_data) {
226 float value = band_data[pixel_idx];
227 color_ratios[band_label] = value;
232 for (
auto &[band_label, ratio]: color_ratios) {
239 brightness_factor = std::clamp(brightness_factor, 0.0f, 1.0f);
240 float scaled_intensity = intensity_scale * brightness_factor * pixel_intensity;
243 for (
auto &[band_label, band_data]: pixel_data) {
244 float band_scale = color_ratios[band_label] * scaled_intensity;
246 for (
int ky = 0; ky < kernel_size_; ++ky) {
247 for (
int kx = 0; kx < kernel_size_; ++kx) {
249 int ix = bx + kx - half_kernel;
250 int iy = by + ky - half_kernel;
253 if (ix < 0 || ix >= resolution.
x || iy < 0 || iy >= resolution.
y) {
257 int img_idx = iy * resolution.
x + ix;
258 int kern_idx = ky * kernel_size_ + kx;
261 band_data[img_idx] += starburst_kernel_[kern_idx] * band_scale;
268void LensFlare::applyGhosts(std::map<std::string, std::vector<float>> &pixel_data,
int2 resolution,
const std::vector<std::tuple<int, int, float>> &bright_pixels) {
269 if (bright_pixels.empty()) {
273 float center_x =
static_cast<float>(resolution.
x) / 2.0f;
274 float center_y =
static_cast<float>(resolution.
y) / 2.0f;
279 float base_reflectance = coating_reflectance * coating_reflectance * props_.
ghost_intensity;
281 int num_ghosts = std::min(props_.
ghost_count,
static_cast<int>(
sizeof(ghost_scales_) /
sizeof(ghost_scales_[0])));
283 for (
const auto &bright_pixel: bright_pixels) {
284 int bx = std::get<0>(bright_pixel);
285 int by = std::get<1>(bright_pixel);
286 float pixel_intensity = std::get<2>(bright_pixel);
289 int pixel_idx = by * resolution.
x + bx;
290 std::map<std::string, float> source_color;
291 for (
const auto &[band_label, band_data]: pixel_data) {
292 source_color[band_label] = band_data[pixel_idx];
296 float dx =
static_cast<float>(bx) - center_x;
297 float dy =
static_cast<float>(by) - center_y;
300 float dist_from_center = std::sqrt(dx * dx + dy * dy);
301 float max_dist = std::sqrt(center_x * center_x + center_y * center_y);
302 float normalized_dist = dist_from_center / max_dist;
305 float cos_theta = std::sqrt(1.0f - normalized_dist * normalized_dist * 0.25f);
306 float fresnel = fresnelReflectance(cos_theta);
309 for (
int g = 0; g < num_ghosts; ++g) {
311 float ghost_x = center_x - ghost_scales_[g] * dx;
312 float ghost_y = center_y - ghost_scales_[g] * dy;
315 float image_diagonal = std::sqrt(
static_cast<float>(resolution.
x * resolution.
x + resolution.
y * resolution.
y));
316 float ghost_radius = image_diagonal * ghost_sizes_[g];
319 float ghost_base = base_reflectance * fresnel * std::exp(-ghost_scales_[g] * 0.5f);
323 float luminance = 0.0f;
324 for (
const auto &[band_label, val]: source_color) {
325 luminance = std::max(luminance, val);
329 std::map<std::string, float> chrominance;
330 for (
const auto &[band_label, val]: source_color) {
331 chrominance[band_label] = val / std::max(luminance, 1e-6f);
335 float compressed_luminance = luminance / (1.0f + luminance);
340 float ghost_luminance = compressed_luminance * ghost_base * 40.0f;
343 for (
auto &[band_label, band_data]: pixel_data) {
345 float band_intensity = chrominance[band_label] * ghost_luminance;
348 renderSoftDisc(band_data, resolution, ghost_x, ghost_y, ghost_radius, band_intensity);
354float LensFlare::fresnelReflectance(
float cos_theta,
float n1,
float n2) {
356 float r0 = ((n1 - n2) / (n1 + n2));
358 float one_minus_cos = 1.0f - cos_theta;
359 float one_minus_cos_5 = one_minus_cos * one_minus_cos * one_minus_cos * one_minus_cos * one_minus_cos;
360 return r0 + (1.0f - r0) * one_minus_cos_5;
363void LensFlare::renderSoftDisc(std::vector<float> &channel,
int2 resolution,
float center_x,
float center_y,
float radius,
float intensity)
const {
364 if (intensity <= 0.0f || radius <= 0.0f) {
369 int x_min = std::max(0,
static_cast<int>(center_x - radius - 1.0f));
370 int x_max = std::min(resolution.
x - 1,
static_cast<int>(center_x + radius + 1.0f));
371 int y_min = std::max(0,
static_cast<int>(center_y - radius - 1.0f));
372 int y_max = std::min(resolution.
y - 1,
static_cast<int>(center_y + radius + 1.0f));
374 float radius_sq = radius * radius;
377 for (
int y = y_min; y <= y_max; ++y) {
378 for (
int x = x_min; x <= x_max; ++x) {
379 float dx =
static_cast<float>(x) + 0.5f - center_x;
380 float dy =
static_cast<float>(y) + 0.5f - center_y;
381 float dist_sq = dx * dx + dy * dy;
383 if (dist_sq <= radius_sq) {
384 float dist_ratio = std::sqrt(dist_sq) / radius;
388 float falloff = 1.0f - dist_ratio * dist_ratio;
389 falloff = std::max(0.0f, falloff);
391 int idx = y * resolution.
x + x;
392 channel[idx] += intensity * falloff;
398void LensFlare::fft2D(
const std::vector<float> &input, std::vector<std::complex<float>> &output,
int size) {
400 output.resize(size * size);
401 for (
int i = 0; i < size * size; ++i) {
402 output[i] = std::complex<float>(input[i], 0.0f);
406 std::vector<std::complex<float>> row(size);
407 for (
int y = 0; y < size; ++y) {
408 for (
int x = 0; x < size; ++x) {
409 row[x] = output[y * size + x];
412 for (
int x = 0; x < size; ++x) {
413 output[y * size + x] = row[x];
418 std::vector<std::complex<float>> col(size);
419 for (
int x = 0; x < size; ++x) {
420 for (
int y = 0; y < size; ++y) {
421 col[y] = output[y * size + x];
424 for (
int y = 0; y < size; ++y) {
425 output[y * size + x] = col[y];
431 for (
int y = 0; y < half; ++y) {
432 for (
int x = 0; x < half; ++x) {
434 std::swap(output[y * size + x], output[(y + half) * size + (x + half)]);
435 std::swap(output[y * size + (x + half)], output[(y + half) * size + x]);
440void LensFlare::fft1D(std::vector<std::complex<float>> &data,
int size,
bool inverse) {
444 for (
int i = 0; i < n - 1; ++i) {
446 std::swap(data[i], data[j]);
457 for (
int len = 2; len <= n; len *= 2) {
458 float angle = 2.0f *
static_cast<float>(
M_PI) /
static_cast<float>(len);
462 std::complex<float> wn(std::cos(angle), std::sin(angle));
464 for (
int i = 0; i < n; i += len) {
465 std::complex<float> w(1.0f, 0.0f);
466 for (
int jj = 0; jj < len / 2; ++jj) {
467 std::complex<float> u = data[i + jj];
468 std::complex<float> t = w * data[i + jj + len / 2];
469 data[i + jj] = u + t;
470 data[i + jj + len / 2] = u - t;
478 for (
auto &val: data) {
479 val /=
static_cast<float>(n);