Spaces:
Sleeping
Sleeping
| 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); | |
| } | |
| } | |