File size: 6,346 Bytes
78475cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import Phaser from 'phaser';

const fragShader = `
precision mediump float;

uniform sampler2D uMainSampler;
uniform vec2 resolution;
uniform float time;

varying vec2 outTexCoord;

#define PI 3.14159265359

vec4 permute(vec4 t) {
  return mod(((t * 34.0) + 1.0) * t, 289.0);
}

float noise3d(vec3 p) {
  vec3 a = floor(p);
  vec3 d = p - a;
  d = d * d * (3.0 - 2.0 * d);

  vec4 b = a.xxyy + vec4(0.0, 1.0, 0.0, 1.0);
  vec4 k1 = permute(b.xyxy);
  vec4 k2 = permute(k1.xyxy + b.zzww);

  vec4 c = k2 + a.zzzz;
  vec4 k3 = permute(c);
  vec4 k4 = permute(c + 1.0);

  vec4 o1 = fract(k3 * (1.0 / 41.0));
  vec4 o2 = fract(k4 * (1.0 / 41.0));

  vec4 o3_interp_z = o2 * d.z + o1 * (1.0 - d.z);
  vec2 o4_interp_xy = o3_interp_z.yw * d.x + o3_interp_z.xz * (1.0 - d.x);

  return o4_interp_xy.y * d.y + o4_interp_xy.x * (1.0 - d.y);
}

void main() {
  float brightness = 2.5;
  float red_balance = 1.0;
  float green_balance = 0.85;
  float blue_balance = 1.0;

  float phosphorWidth = 2.50;
  float phosphorHeight = 4.50;
  float internalHorizontalGap = 1.0;
  float columnGap = 0.2;
  float verticalCellGap = 0.2;
  float phosphorPower = 0.9;

  float cell_noise_variation_amount = 0.025;
  float cell_noise_scale_xy = 240.0;
  float cell_noise_speed = 24.0;
  float curvature_amount = 0.0;

  vec2 fragCoord = gl_FragCoord.xy;
  vec2 uv = outTexCoord;
  vec2 centered_uv_output = uv - 0.5;
  float r = length(centered_uv_output);
  float distort_factor = 1.0 + curvature_amount * r * r;
  vec2 centered_uv_source = centered_uv_output * distort_factor;
  vec2 source_uv = centered_uv_source + 0.5;
  vec2 fragCoord_warped = source_uv * resolution;

  bool is_on_original_flat_screen = source_uv.x >= 0.0 && source_uv.x <= 1.0 &&
                                   source_uv.y >= 0.0 && source_uv.y <= 1.0;

  if (!is_on_original_flat_screen) {
    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
    return;
  }

  float fullCellWidth = 3.0 * phosphorWidth + 3.0 * internalHorizontalGap + columnGap;
  float fullRowHeight = phosphorHeight + verticalCellGap;

  float logical_cell_index_x = floor(fragCoord_warped.x / fullCellWidth);
  float shift_y_offset = 0.0;
  
  if (mod(logical_cell_index_x, 2.0) != 0.0) {
    shift_y_offset = fullRowHeight / 2.0;
  }
  
  float effective_y_warped = fragCoord_warped.y + shift_y_offset;
  float logical_row_index = floor(effective_y_warped / fullRowHeight);

  float uv_cell_x = mod(fragCoord_warped.x, fullCellWidth);
  if (uv_cell_x < 0.0) {
    uv_cell_x += fullCellWidth;
  }
  
  float uv_row_y = mod(effective_y_warped, fullRowHeight);
  if (uv_row_y < 0.0) {
    uv_row_y += fullRowHeight;
  }

  vec3 video_color = texture2D(uMainSampler, source_uv).rgb;
  video_color.r *= red_balance;
  video_color.g *= green_balance;
  video_color.b *= blue_balance;

  vec3 final_color = vec3(0.0);
  bool in_column_gap = uv_cell_x >= (3.0 * phosphorWidth + 3.0 * internalHorizontalGap);
  bool in_vertical_gap = uv_row_y >= phosphorHeight;

  if (!in_column_gap && !in_vertical_gap) {
    float uv_cell_x_within_block = uv_cell_x;
    vec3 phosphor_base_color = vec3(0.0);
    float video_component_intensity = 0.0;
    float current_phosphor_startX_in_block = -1.0;
    float current_x_tracker = 0.0;

    if (uv_cell_x_within_block >= current_x_tracker && uv_cell_x_within_block < current_x_tracker + phosphorWidth) {
      phosphor_base_color = vec3(1.0, 0.0, 0.0);
      video_component_intensity = video_color.r;
      current_phosphor_startX_in_block = current_x_tracker;
    }
    current_x_tracker += phosphorWidth + internalHorizontalGap;

    if (uv_cell_x_within_block >= current_x_tracker && uv_cell_x_within_block < current_x_tracker + phosphorWidth) {
      phosphor_base_color = vec3(0.0, 1.0, 0.0);
      video_component_intensity = video_color.g;
      current_phosphor_startX_in_block = current_x_tracker;
    }
    current_x_tracker += phosphorWidth + internalHorizontalGap;

    if (uv_cell_x_within_block >= current_x_tracker && uv_cell_x_within_block < current_x_tracker + phosphorWidth) {
      phosphor_base_color = vec3(0.0, 0.0, 1.0);
      video_component_intensity = video_color.b;
      current_phosphor_startX_in_block = current_x_tracker;
    }

    if (current_phosphor_startX_in_block >= 0.0) {
      float x_in_phosphor = (uv_cell_x_within_block - current_phosphor_startX_in_block) / phosphorWidth;
      float horizontal_intensity_factor = pow(sin(x_in_phosphor * PI), phosphorPower);
      float y_in_phosphor_band = uv_row_y / phosphorHeight;
      float vertical_intensity_factor = (phosphorHeight > 0.0) ? pow(sin(y_in_phosphor_band * PI), phosphorPower) : 1.0;
      float total_intensity_factor = horizontal_intensity_factor * vertical_intensity_factor;
      final_color = phosphor_base_color * video_component_intensity * total_intensity_factor;
    }
  }

  vec3 noise_pos = vec3(logical_cell_index_x * cell_noise_scale_xy,
                        logical_row_index * cell_noise_scale_xy,
                        time * cell_noise_speed);

  vec3 cell_noise_rgb;
  cell_noise_rgb.r = noise3d(noise_pos);
  cell_noise_rgb.g = noise3d(noise_pos + vec3(19.0, 0.0, 0.0));
  cell_noise_rgb.b = noise3d(noise_pos + vec3(0.0, 13.0, 0.0));
  cell_noise_rgb = cell_noise_rgb * 2.0 - 1.0;
  final_color += cell_noise_rgb * cell_noise_variation_amount;

  final_color *= brightness;
  float edge_darken_strength = 0.1;
  float vignette_factor = 1.0 - dot(centered_uv_output, centered_uv_output) * edge_darken_strength * 2.0;
  vignette_factor = clamp(vignette_factor, 0.0, 1.0);
  final_color *= vignette_factor;

  final_color = clamp(final_color, 0.0, 1.0);
  gl_FragColor = vec4(final_color, 1.0);
}
`;

export default class TrinitronPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline {
  constructor(game) {
    super({
      name: 'TrinitronPipeline',
      game: game,
      renderTarget: true,
      fragShader: fragShader,
      uniforms: [
        'uMainSampler',
        'resolution',
        'time'
      ]
    });
  }

  onPreRender() {
    // Use the actual canvas display size (after scaling), not the game resolution
    const canvas = this.game.canvas;
    const displayWidth = canvas.width;
    const displayHeight = canvas.height;
    this.set2f('resolution', displayWidth, displayHeight);
    this.set1f('time', this.game.loop.time / 1000);
  }
}