diff --git a/shaders/star_frag.glsl b/shaders/star_frag.glsl index db6331eb88..9dfeddd845 100644 --- a/shaders/star_frag.glsl +++ b/shaders/star_frag.glsl @@ -1,4 +1,4 @@ -const float degree_per_px = 0.05; +const float degree_per_px = 0.01; const float br_limit = 1.0 / (255.0 * 12.92); // empirical constants const float a = 0.123; @@ -7,53 +7,40 @@ const float k = 0.0016; varying vec3 v_color; varying float max_theta; varying float pointSize; -varying float br; -// py: def PSF_Bounded(theta: float, max_theta: float, br_center: float): -// max_theta is common for all pixels so it's set via `varying` in the vertex shader -float psf_bounded(float theta, float br_center) +float psf_central(float offset) { - // Human eye's point source function from the research by Greg Spencer et al., optimized to fit a square. - // Lower limit on brightness and angular size: 1 Vega and 0.05 degrees per pixel. No upper limits. + // Human eye's point source function from the research by Greg Spencer et al. + // Optimized for the central part of the PSF. The flow from the four neighboring pixels is constant. + // Designed for degree_per_px == 0.01. + if (offset < 1.6667) + { + return 1.0 + 1.136 * offset * (0.3 * offset - 1.0); + } + return 0.0; // function starts to grow again +} - // py: if theta == 0: +float psf_outer(float offset) +{ + // Human eye's point source function from the research by Greg Spencer et al. + // Optimized for the outer part of the PSF. Designed with bounds by arctangent in mind. + // Causes star blinking with degree_per_px > 0.01, large grid misses the center peak of brightness. + float theta = offset * degree_per_px; if (theta == 0) - // py: return br_center - return br_center; // the center is always overexposed (zero division error) - - // py: elif theta < max_theta: + return 100.; // the center is always overexposed (zero division error) if (theta < max_theta) { - // py: brackets = max_theta / theta - 1 float brackets = max_theta / theta - 1.0; - // py: return k * brackets * brackets return k * brackets * brackets; } - - // py: return 0. # after max_theta function starts to grow again return 0.0; // after max_theta function starts to grow again } void main(void) { - if (max_theta == -1.0) - { - // just a one pixel star - // py: arr[center[1], center[0]] += scaled_color - gl_FragColor = vec4(v_color, 1.0); - } - else - { - // Option 2: glare square render - // py: theta = np.sqrt(xx*xx + yy*yy) * degree_per_px # array of distances to the center - // in fragment shader all points have virtual dimension 1x1, so gl_PointCoord has a value from [0; 1] - vec2 offset = (gl_PointCoord.xy - vec2(0.5)) * pointSize; - float theta = length(offset) * degree_per_px; - // py: glow_bw = PSF_Bounded(theta, max_theta, br0) # in the [0, 1] range, like in Celestia - float glow_bw = psf_bounded(theta, br); - // py: glow_colored = color * np.repeat(np.expand_dims(glow_bw, axis=2), 3, axis=2) # scaling - vec3 glow_colored = v_color * glow_bw; - // py: arr[center[1]+y_min:center[1]+y_max, center[0]+x_min:center[0]+x_max] += glow_colored - gl_FragColor = vec4(glow_colored, 1.0); - } + // in fragment shader all points have virtual dimension 1x1, so gl_PointCoord has a value from [0; 1] + float offset = length((gl_PointCoord.xy - vec2(0.5)) * pointSize); + float glow_bw = (max_theta == -1.0) ? psf_central(offset) : psf_outer(offset); + vec3 glow_colored = v_color * glow_bw; // color and brightness scaling + gl_FragColor = vec4(glow_colored, 1.0)+ vec4(0.1, 0.0, 0.0, 0.0); } diff --git a/shaders/star_vert.glsl b/shaders/star_vert.glsl index 59e64ffb72..ea1f1890a4 100644 --- a/shaders/star_vert.glsl +++ b/shaders/star_vert.glsl @@ -1,5 +1,4 @@ - -const float degree_per_px = 0.05; +const float degree_per_px = 0.01; // empirical constants const float a = 0.123; const float k = 0.0016; @@ -19,69 +18,48 @@ attribute vec4 in_Position; attribute vec3 in_Color; attribute float in_PointSize; -const float color_saturation_limit = 0.1; // The ratio of the minimum color component to the maximum +const float color_saturation_limit = 0.1; // the ratio of the minimum color component to the maximum //! Normalizes the color by its green value and corrects extreme saturation -// py: def green_normalization(color: np.ndarray): vec3 green_normalization(vec3 color) { - // py: color /= color.max() // color /= max(color.r, max(color.g, color.b)); // we do this in XYZRGBConverter::convertUnnormalized() - - // py: delta = color_saturation_limit - color.min() float delta = color_saturation_limit - min(color.r, min(color.g, color.b)); - // py: if delta > 0: if (delta > 0) { - // py: color += delta * (1-color)**2 # desaturating to the saturation limit vec3 diff = vec3(1.0) - color; - color += delta * (diff * diff); // desaturating to the saturation limit + color += diff * diff * delta; // desaturating to the saturation limit } - // py: return color / color[1] return color / color.g; } void main(void) { // py: linear_br = 10**(-0.4 * star_mag) * exposure # scaled brightness measured in Vegas - // here i use +0.4 because `in_PointSize` is not the actual magnitude but `faintest` - `actual` + // +0.4 because `in_PointSize` is not the actual magnitude but `faintest` - `actual` float br0 = pow(10.0, 0.4 * in_PointSize) * exposure; - - // py: color = auxiliary.green_normalization(color0) vec3 color = green_normalization(in_Color); - - // py: scaled_color = color * br0 vec3 scaled_color = color * br0; // py: if np.all(scaled_color < 1): - if (all(lessThan(scaled_color, vec3(1.0)))) + if (all(lessThan(scaled_color, vec3(1.0)))) // not works! never "true" + //if (max(scaled_color.r, max(scaled_color.g, scaled_color.b)) < 1.0) + //if (true) { - // we set color in the fragment shader (using v_color) so here we just set point size to 1px - pointSize = 1.0; - // use max_theta == -1 as as signal that the point size is 1px + // Option 1: Weak light source, no glow + // use max_theta == -1 as an indicator max_theta = -1.0; - + pointSize = 3.0; v_color = scaled_color; } else { - // py: br = np.arctan(br0 / max_br) * max_br # dimmed brightness + // Option 2: Strong light source, glow br = atan(br0 / max_br) * max_br; // dimmed brightness - // py: max_theta = a * np.sqrt(br) # glow radius max_theta = a * sqrt(br); // glow radius - // py: half_sq = floor(max_theta / degree_per_px) float half_sq = max_theta / degree_per_px; - // py: x_min = -min(half_sq, center[0]) - // py: x_max = min(half_sq+1, width-center[0]) - // py: y_min = -min(half_sq, center[1]) - // py: y_max = min(half_sq+1, hight-center[1]) - // py: x = np.arange(x_min, x_max) - // py: y = np.arange(y_min, y_max) - // py: xx, yy = np.meshgrid(x, y) - // we just set a point size. all iteration over every px is done in the fragment shader pointSize = 2.0 * half_sq - 1.0; - v_color = color; }