Skip to content

Commit

Permalink
New code for dim stars, clean-up
Browse files Browse the repository at this point in the history
  • Loading branch information
Askaniy committed Jun 30, 2024
1 parent 3d78893 commit b2d23fb
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 69 deletions.
59 changes: 23 additions & 36 deletions shaders/star_frag.glsl
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
44 changes: 11 additions & 33 deletions shaders/star_vert.glsl
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}

Expand Down

0 comments on commit b2d23fb

Please sign in to comment.