#3 Tip – Advanced Projector in Godot / OLD Film Grain Shader MATERIAL

Check this thing

I was working on the idea of adding a projector that would leave messages in the main room where impious dwell.


But of course, I didn’t just want to project an image; we all know that Spotlights in Godot have a “light_projector” property for that. Instead, I wanted to have as much control as possible to include animations, add real text, be able to translate it or change it as I please without relying on unique textures for every little thing.

So, for that, we need to project a SubViewport instead of images/textures, pointing to a Control or GUI where we’ll include whatever we need, just like any other interface.

Now, within the SubViewport, you have to set the size of the content. It can be quite confusing until you figure it out.

Make sure that Update Mode is set to Always, since the projection will be animated with different effects, and the text will be animated too. But this isn’t enough to get it to update in real-time, although we’ll see that later.

So, having a separate and referenced scene, we can now work with it dynamically. In this case, I’ll have a list of different animations that I’ll trigger from code, changing text, properties, symbols, or whatever I need. The idea is to have everything in a single scene and an AnimationPlayer, so we don’t have to worry about loading and unloading different animated scenes.

But yeah…


I don’t know if it’s a pending bug, but the way to solve it (and maybe not the best) is to reassign the texture of that SubViewport in the _process() of the Spotlight itself.

And with that, it would work both in the Editor and in the game. Note that I’ve added an @export_tool_button, a new feature in Godot 4.4, which when clicked, updates by calling the _update() method as well, since sometimes it fails in the editor itself.

OLD 8mm FILM SHADER IN GODOT 4.4 :

Custom 8mm old film shader.

I wanted to give it an old film touch, so I thought about simply putting a TextureRect with a custom shader in blend_mul mode with the typical damaged film effects, light variations, and so on.
I was looking at several examples on www.shadertoy.com o on https://godotshaders.com/ , but I didn’t really know how to adapt them to what I wanted. Usually, the effect is too exaggerated, flickering strongly every frame, and I didn’t want that on my screen for hours distracting me. After spending a lot of time trying to understand those shaders and adapt them to my liking, I realized that the best thing would be to start one from scratch, a shader that would allow me to:
– Less flickering, more subtle and flexible
– Less procedural and costly generation of film damage; we’ll use an RGB texture of 1024×1024 for all that.

I plan to upload it to Godot Shaders at some point when it’s better refined in the future, but feel free to upload it yourself as long as you give credit to this page or to Impious .
I’ll give an overview of how this shader is made, but I’ll leave the complete code and the RGB texture below.
– We create some uniforms to control the parameters

Two functions to get random numbers (found at this link)

So, the main thing about this shader is that the flickering is random, and its frequency and chance to appear are manageable (line_freq or line_chance, for example).

With this method, we’ll handle almost everything: flickering and film damage.

As you can see, it’s quite simple: multiplying time by the frequency at which we want the flicker, rounding it, randomizing, and then it’s a matter of mixing how much we want to lower the intensity (min_flick) of the image and how often.

We add grain, but controlling its intensity with "grain_punch"; we want it to be subtle.

Now, we’ll mix 3 channels contained in an RGB texture, so we have 3 masks in one.

PURPLEPRINT IMAGE EDITOR ( https://hevedy.itch.io/purpleprint-image )

Without complex math or a lot of procedural generation, it’s about using the same flicker system to multiply and randomly move the UVs of both the red channel, corresponding to the typical vertical lines, and different damages in the green and blue channels.

In this way, vertical lines will randomly appear, and a damage texture will randomly appear, which can be either the green or blue channel, and whose UVs are random so they don’t repeat. I didn’t consider it necessary to add UV rotations to the equation; I think it’s enough for the desired effect.

I’ll leave the code and the texture here 😉

shader_type canvas_item;
render_mode blend_mul;
uniform float flick_freq = 10.0;
uniform float min_flick = 0.5;
uniform float grain_punch = 0.1;
uniform sampler2D t_damaged_film:hint_default_black,repeat_enable;

uniform float line_freq = 0.1;
uniform float line_chance = 0.8;
uniform float blotch_punch = 0.5;
uniform float blotch_freq = 5.;
uniform float blotch_chance = 0.8;

float randi(vec2 co){
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
float rand(float c){
return randi(vec2(c,1.0));
}

void fragment() {
vec4 color = texture(TEXTURE,UV);
float flick = floor(TIME*flick_freq);
float rand_flick = rand(flick);

color*=mix(min_flick,1.,rand_flick);

//---------------- GRAIN
float grain = rand(dot(UV*SCREEN_PIXEL_SIZE.xy,vec2(12.9898,78.233))*TIME)*grain_punch;
color.rgb+=vec3(grain);

//------------------- LINES
float line_time = floor(TIME * line_freq);
float line_random = rand(line_time);
float line_show = step(line_chance,line_random);

float line_offset = rand(line_time)*2.0-1.0;
vec2 line_uv = UV + vec2(line_offset,0.0);
float lines = texture(t_damaged_film,line_uv).r;
color.rgb = mix(color.rgb , color.rgb * lines , line_show);

//-------------- BLOTCHES / DAMAGE
float blotch_time = floor(TIME*blotch_freq);
float blotch_random = rand(blotch_time);
float blotch_show   = step(blotch_chance,blotch_random);
float channel_selector = rand(blotch_time + 1.0);
bool use_green = channel_selector<0.5;

//---------------------- BLOTCH uv
float scale = 0.5 * rand(blotch_time+3.0)*1.5;
vec2 offset = vec2(rand(blotch_time+4.0),rand(blotch_time+5.0)*2.0-1.0);

vec2 blotch_uv = (UV-0.5) * scale +0.5+offset;
blotch_uv = fract(blotch_uv);
vec4 blotch_tex = texture(t_damaged_film,blotch_uv);
float blotch_value = use_green ? blotch_tex.g:blotch_tex.b;
color.rgb = mix(color.rgb,color.rgb*blotch_value*blotch_punch,blotch_show);

COLOR = color;
}

And that’s it! A small exercise to work on the game’s atmosphere, and I wanted to write about it here.

So hey, if you found it interesting and want to know more about Impious, don’t hesitate to add the game to your Steam Wishlist!

Thanks for reading and until next time.

Onward!

Similar Posts