├── LICENSE ├── README.md ├── addons └── crt_script_editor │ ├── crt.gdshader │ ├── crt_overlay.tscn │ ├── crt_script_editor.gd │ └── plugin.cfg ├── icon.png ├── icon.png.import ├── project.godot └── screenshots └── screenshot_1.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Andrés Gamboa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRT Script Editor 2 | Enhance your Godot Engine script editor with the nostalgic charm of a CRT monitor effect. 3 | ![Alt text](screenshots/screenshot_1.png?raw=true "Title") 4 | -------------------------------------------------------------------------------- /addons/crt_script_editor/crt.gdshader: -------------------------------------------------------------------------------- 1 | /* 2 | Shader from Godot Shaders - the free shader library. 3 | godotshaders.com/shader/VHS-and-CRT-monitor-effect 4 | 5 | This shader is under CC0 license. Feel free to use, improve and 6 | change this shader according to your needs and consider sharing 7 | the modified result to godotshaders.com. 8 | */ 9 | 10 | shader_type canvas_item; 11 | 12 | //*** IMPORTANT! ***/ 13 | // - If you are using this shader to affect the node it is applied to set 'overlay' to false (unchecked in the instepctor). 14 | // - If you are using this shader as an overlay, and want the shader to affect the nodes below in the Scene hierarchy, 15 | // set 'overlay' to true (checked in the inspector). 16 | // On Mac there is potentially a bug causing this to not work properly. If that is the case and you want to use the shader as an overlay 17 | // change all "overlay ? SCREEN_TEXTURE : TEXTURE" to only "SCREEN_TEXTURE" on lines 129-140, and "vec2 uv = overlay ? warp(SCREEN_UV) : warp(UV);" 18 | // to "vec2 uv = warp(SCREEN_UV);" on line 98. 19 | uniform bool overlay = false; 20 | 21 | uniform float scanlines_opacity : hint_range(0.0, 1.0) = 0.4; 22 | uniform float scanlines_width : hint_range(0.0, 0.5) = 0.25; 23 | uniform float grille_opacity : hint_range(0.0, 1.0) = 0.3; 24 | uniform vec2 resolution = vec2(640.0, 480.0); // Set the number of rows and columns the texture will be divided in. Scanlines and grille will make a square based on these values 25 | uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap; 26 | uniform bool pixelate = true; // Fill each square ("pixel") with a sampled color, creating a pixel look and a more accurate representation of how a CRT monitor would work. 27 | 28 | uniform bool roll = true; 29 | uniform float roll_speed = 8.0; // Positive values are down, negative are up 30 | uniform float roll_size : hint_range(0.0, 100.0) = 15.0; 31 | uniform float roll_variation : hint_range(0.1, 5.0) = 1.8; // This valie is not an exact science. You have to play around with the value to find a look you like. How this works is explained in the code below. 32 | uniform float distort_intensity : hint_range(0.0, 0.2) = 0.05; // The distortion created by the rolling effect. 33 | 34 | uniform float noise_opacity : hint_range(0.0, 1.0) = 0.4; 35 | uniform float noise_speed = 5.0; // There is a movement in the noise pattern that can be hard to see first. This sets the speed of that movement. 36 | 37 | uniform float static_noise_intensity : hint_range(0.0, 1.0) = 0.06; 38 | 39 | uniform float aberration : hint_range(-1.0, 1.0) = 0.03; // Chromatic aberration, a distortion on each color channel. 40 | uniform float brightness = 1.4; // When adding scanline gaps and grille the image can get very dark. Brightness tries to compensate for that. 41 | uniform bool discolor = true; // Add a discolor effect simulating a VHS 42 | 43 | uniform float warp_amount :hint_range(0.0, 5.0) = 1.0; // Warp the texture edges simulating the curved glass of a CRT monitor or old TV. 44 | uniform bool clip_warp = false; 45 | 46 | uniform float vignette_intensity = 0.4; // Size of the vignette, how far towards the middle it should go. 47 | uniform float vignette_opacity : hint_range(0.0, 1.0) = 0.5; 48 | 49 | // Used by the noise functin to generate a pseudo random value between 0.0 and 1.0 50 | vec2 random(vec2 uv){ 51 | uv = vec2( dot(uv, vec2(127.1,311.7) ), 52 | dot(uv, vec2(269.5,183.3) ) ); 53 | return -1.0 + 2.0 * fract(sin(uv) * 43758.5453123); 54 | } 55 | 56 | // Generate a Perlin noise used by the distortion effects 57 | float noise(vec2 uv) { 58 | vec2 uv_index = floor(uv); 59 | vec2 uv_fract = fract(uv); 60 | 61 | vec2 blur = smoothstep(0.0, 1.0, uv_fract); 62 | 63 | return mix( mix( dot( random(uv_index + vec2(0.0,0.0) ), uv_fract - vec2(0.0,0.0) ), 64 | dot( random(uv_index + vec2(1.0,0.0) ), uv_fract - vec2(1.0,0.0) ), blur.x), 65 | mix( dot( random(uv_index + vec2(0.0,1.0) ), uv_fract - vec2(0.0,1.0) ), 66 | dot( random(uv_index + vec2(1.0,1.0) ), uv_fract - vec2(1.0,1.0) ), blur.x), blur.y) * 0.5 + 0.5; 67 | } 68 | 69 | // Takes in the UV and warps the edges, creating the spherized effect 70 | vec2 warp(vec2 uv){ 71 | vec2 delta = uv - 0.5; 72 | float delta2 = dot(delta.xy, delta.xy); 73 | float delta4 = delta2 * delta2; 74 | float delta_offset = delta4 * warp_amount; 75 | 76 | return uv + delta * delta_offset; 77 | } 78 | 79 | // Adds a black border to hide stretched pixel created by the warp effect 80 | float border (vec2 uv){ 81 | float radius = min(warp_amount, 0.08); 82 | radius = max(min(min(abs(radius * 2.0), abs(1.0)), abs(1.0)), 1e-5); 83 | vec2 abs_uv = abs(uv * 2.0 - 1.0) - vec2(1.0, 1.0) + radius; 84 | float dist = length(max(vec2(0.0), abs_uv)) / radius; 85 | float square = smoothstep(0.96, 1.0, dist); 86 | return clamp(1.0 - square, 0.0, 1.0); 87 | } 88 | 89 | // Adds a vignette shadow to the edges of the image 90 | float vignette(vec2 uv){ 91 | uv *= 1.0 - uv.xy; 92 | float vignette = uv.x * uv.y * 15.0; 93 | return pow(vignette, vignette_intensity * vignette_opacity); 94 | } 95 | 96 | void fragment() 97 | { 98 | vec2 uv = overlay ? warp(SCREEN_UV) : warp(UV); // Warp the uv. uv will be used in most cases instead of UV to keep the warping 99 | vec2 text_uv = uv; 100 | vec2 roll_uv = vec2(0.0); 101 | float time = roll ? TIME : 0.0; 102 | 103 | 104 | // Pixelate the texture based on the given resolution. 105 | if (pixelate) 106 | { 107 | text_uv = ceil(uv * resolution) / resolution; 108 | } 109 | 110 | // Create the rolling effect. We need roll_line a bit later to make the noise effect. 111 | // That is why this runs if roll is true OR noise_opacity is over 0. 112 | float roll_line = 0.0; 113 | if (roll || noise_opacity > 0.0) 114 | { 115 | // Create the areas/lines where the texture will be distorted. 116 | roll_line = smoothstep(0.3, 0.9, sin(uv.y * roll_size - (time * roll_speed) ) ); 117 | // Create more lines of a different size and apply to the first set of lines. This creates a bit of variation. 118 | roll_line *= roll_line * smoothstep(0.3, 0.9, sin(uv.y * roll_size * roll_variation - (time * roll_speed * roll_variation) ) ); 119 | // Distort the UV where where the lines are 120 | roll_uv = vec2(( roll_line * distort_intensity * (1.-UV.x)), 0.0); 121 | } 122 | 123 | vec4 text; 124 | if (roll) 125 | { 126 | // If roll is true distort the texture with roll_uv. The texture is split up into RGB to 127 | // make some chromatic aberration. We apply the aberration to the red and green channels accorging to the aberration parameter 128 | // and intensify it a bit in the roll distortion. 129 | text.r = texture(SCREEN_TEXTURE, text_uv + roll_uv * 0.8 + vec2(aberration, 0.0) * .1).r; 130 | text.g = texture(SCREEN_TEXTURE, text_uv + roll_uv * 1.2 - vec2(aberration, 0.0) * .1 ).g; 131 | text.b = texture(SCREEN_TEXTURE, text_uv + roll_uv).b; 132 | text.a = 1.0; 133 | } 134 | else 135 | { 136 | // If roll is false only apply the aberration without any distorion. The aberration values are very small so the .1 is only 137 | // to make the slider in the Inspector less sensitive. 138 | text.r = texture(SCREEN_TEXTURE, text_uv + vec2(aberration, 0.0) * .1).r; 139 | text.g = texture(SCREEN_TEXTURE, text_uv - vec2(aberration, 0.0) * .1).g; 140 | text.b = texture(SCREEN_TEXTURE, text_uv).b; 141 | text.a = 1.0; 142 | } 143 | 144 | float r = text.r; 145 | float g = text.g; 146 | float b = text.b; 147 | 148 | uv = warp(UV); 149 | 150 | // CRT monitors don't have pixels but groups of red, green and blue dots or lines, called grille. We isolate the texture's color channels 151 | // and divide it up in 3 offsetted lines to show the red, green and blue colors next to each other, with a small black gap between. 152 | if (grille_opacity > 0.0){ 153 | 154 | float g_r = smoothstep(0.85, 0.95, abs(sin(uv.x * (resolution.x * 3.14159265)))); 155 | r = mix(r, r * g_r, grille_opacity); 156 | 157 | float g_g = smoothstep(0.85, 0.95, abs(sin(1.05 + uv.x * (resolution.x * 3.14159265)))); 158 | g = mix(g, g * g_g, grille_opacity); 159 | 160 | float b_b = smoothstep(0.85, 0.95, abs(sin(2.1 + uv.x * (resolution.x * 3.14159265)))); 161 | b = mix(b, b * b_b, grille_opacity); 162 | 163 | } 164 | 165 | // Apply the grille to the texture's color channels and apply Brightness. Since the grille and the scanlines (below) make the image very dark you 166 | // can compensate by increasing the brightness. 167 | text.r = clamp(r * brightness, 0.0, 1.0); 168 | text.g = clamp(g * brightness, 0.0, 1.0); 169 | text.b = clamp(b * brightness, 0.0, 1.0); 170 | 171 | // Scanlines are the horizontal lines that make up the image on a CRT monitor. 172 | // Here we are actual setting the black gap between each line, which I guess is not the right definition of the word, but you get the idea 173 | float scanlines = 0.5; 174 | if (scanlines_opacity > 0.0) 175 | { 176 | // Same technique as above, create lines with sine and applying it to the texture. Smoothstep to allow setting the line size. 177 | scanlines = smoothstep(scanlines_width, scanlines_width + 0.5, abs(sin(uv.y * (resolution.y * 3.14159265)))); 178 | text.rgb = mix(text.rgb, text.rgb * vec3(scanlines), scanlines_opacity); 179 | } 180 | 181 | // Apply the banded noise. 182 | if (noise_opacity > 0.0) 183 | { 184 | // Generate a noise pattern that is very stretched horizontally, and animate it with noise_speed 185 | float noise = smoothstep(0.4, 0.5, noise(uv * vec2(2.0, 200.0) + vec2(10.0, (TIME * (noise_speed))) ) ); 186 | 187 | // We use roll_line (set above) to define how big the noise should be vertically (multiplying cuts off all black parts). 188 | // We also add in some basic noise with random() to break up the noise pattern above. The noise is sized according to 189 | // the resolution value set in the inspector. If you don't like this look you can 190 | // change "ceil(uv * resolution) / resolution" to only "uv" to make it less pixelated. Or multiply resolution with som value 191 | // greater than 1.0 to make them smaller. 192 | roll_line *= noise * scanlines * clamp(random((ceil(uv * resolution) / resolution) + vec2(TIME * 0.8, 0.0)).x + 0.8, 0.0, 1.0); 193 | // Add it to the texture based on noise_opacity 194 | text.rgb = clamp(mix(text.rgb, text.rgb + roll_line, noise_opacity), vec3(0.0), vec3(1.0)); 195 | } 196 | 197 | // Apply static noise by generating it over the whole screen in the same way as above 198 | if (static_noise_intensity > 0.0) 199 | { 200 | text.rgb += clamp(random((ceil(uv * resolution) / resolution) + fract(TIME)).x, 0.0, 1.0) * static_noise_intensity; 201 | } 202 | 203 | // Apply a black border to hide imperfections caused by the warping. 204 | // Also apply the vignette 205 | text.rgb *= border(uv); 206 | text.rgb *= vignette(uv); 207 | // Hides the black border and make that area transparent. Good if you want to add the the texture on top an image of a TV or monitor. 208 | if (clip_warp) 209 | { 210 | text.a = border(uv); 211 | } 212 | 213 | // Apply discoloration to get a VHS look (lower saturation and higher contrast) 214 | // You can play with the values below or expose them in the Inspector. 215 | float saturation = 0.5; 216 | float contrast = 1.2; 217 | if (discolor) 218 | { 219 | // Saturation 220 | vec3 greyscale = vec3(text.r + text.g + text.b) / 3.; 221 | text.rgb = mix(text.rgb, greyscale, saturation); 222 | 223 | // Contrast 224 | float midpoint = pow(0.5, 2.2); 225 | text.rgb = (text.rgb - vec3(midpoint)) * contrast + midpoint; 226 | } 227 | 228 | COLOR = text; 229 | } -------------------------------------------------------------------------------- /addons/crt_script_editor/crt_overlay.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://ctkvh1lh7autt"] 2 | 3 | [ext_resource type="Shader" path="res://addons/crt_script_editor/crt.gdshader" id="1_xdohb"] 4 | 5 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_6uh0e"] 6 | shader = ExtResource("1_xdohb") 7 | shader_parameter/overlay = true 8 | shader_parameter/scanlines_opacity = 0.035 9 | shader_parameter/scanlines_width = 0.14 10 | shader_parameter/grille_opacity = 0.0 11 | shader_parameter/resolution = Vector2(1280, 720) 12 | shader_parameter/pixelate = false 13 | shader_parameter/roll = true 14 | shader_parameter/roll_speed = 2.66 15 | shader_parameter/roll_size = 27.375 16 | shader_parameter/roll_variation = 2.275 17 | shader_parameter/distort_intensity = 0.0 18 | shader_parameter/noise_opacity = 0.049 19 | shader_parameter/noise_speed = 5.0 20 | shader_parameter/static_noise_intensity = 0.06 21 | shader_parameter/aberration = 0.00400005 22 | shader_parameter/brightness = 2.155 23 | shader_parameter/discolor = false 24 | shader_parameter/warp_amount = 0.25 25 | shader_parameter/clip_warp = false 26 | shader_parameter/vignette_intensity = 0.755 27 | shader_parameter/vignette_opacity = 0.4 28 | 29 | [node name="CRTOverlay" type="ColorRect"] 30 | material = SubResource("ShaderMaterial_6uh0e") 31 | anchors_preset = 15 32 | anchor_right = 1.0 33 | anchor_bottom = 1.0 34 | grow_horizontal = 2 35 | grow_vertical = 2 36 | size_flags_horizontal = 3 37 | size_flags_vertical = 3 38 | mouse_filter = 2 39 | -------------------------------------------------------------------------------- /addons/crt_script_editor/crt_script_editor.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | var editor: ScriptEditor 5 | var overlay_scene 6 | 7 | 8 | func _enter_tree(): 9 | editor = get_editor_interface().get_script_editor() 10 | overlay_scene = load("res://addons/crt_script_editor/crt_overlay.tscn") 11 | editor.editor_script_changed.connect(_on_editor_script_changed) 12 | add_overlay() 13 | 14 | 15 | func _on_editor_script_changed(script: Script): 16 | add_overlay() 17 | 18 | 19 | func add_overlay(): 20 | var current = editor.get_current_editor() 21 | if not current: return 22 | var base = current.get_base_editor() 23 | if not base.get_children().is_empty(): return 24 | var overlay = overlay_scene.instantiate() 25 | base.add_child(overlay) 26 | 27 | 28 | func _exit_tree(): 29 | editor.get_open_script_editors() 30 | for script_editor in editor.get_open_script_editors(): 31 | var overlay = script_editor.get_base_editor().get_child(0) 32 | if overlay: overlay.queue_free() 33 | -------------------------------------------------------------------------------- /addons/crt_script_editor/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="crt script editor" 4 | description="Adds a CRT monitor effect to the script editor." 5 | author="Andres Gamboa" 6 | version="1.0" 7 | script="crt_script_editor.gd" 8 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crsolver/CRTScriptEditor/b250d29b98fa4faa9616f98339fc03ea39979e66/icon.png -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bfof3sefcqxxy" 6 | path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.png" 14 | dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="CRTScriptEditor" 14 | config/features=PackedStringArray("4.2", "Forward Plus") 15 | config/icon="res://icon.svg" 16 | 17 | [editor_plugins] 18 | 19 | enabled=PackedStringArray("res://addons/crt_script_editor/plugin.cfg") 20 | -------------------------------------------------------------------------------- /screenshots/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crsolver/CRTScriptEditor/b250d29b98fa4faa9616f98339fc03ea39979e66/screenshots/screenshot_1.png --------------------------------------------------------------------------------