#3 TIP – Proyector Avanzado en Godot + Shader MATERIAL de Película ANTIGUA
Estaba trabajando en la idea de meter un proyector que fuera dejando mensajes en la sala principal donde habitan los impíos.
Pero claro, no quería simplemente proyectar una imagen, para ello ya sabemos que los Spotlights en Godot tienen una propiedad “light_projector”, sino que quería tener todo el control posible para incluir animaciones, meter texto real, poder traducirlo o cambiarlo a mi antojo sin depender de texturas únicas para cada cosa.

Bien, para ello necesitamos en vez de proyectar imágenes/texturas , proyectar un SubViewport que apunte a un Control o GUI donde incluiremos lo que necesitemos, como si de una interfaz cualquiera se tratara.

Ahora bien, dentro del SubViewport hay que poner el tamaño del contenido. Puede ser bastante confuso hasta que des con ello.

Fíjate que Update Mode está en Always, ya que la proyección irá animada con diferentes efectos y el texto será animado también. Pero esto no es suficiente para que se actualice a tiempo real, aunque eso lo veremos luego.

Entonces, teniendo una escena separada y referenciada, ya podemos trabajar con ella de manera dinámica. En este caso tendré una lista de diferentes animaciones que iré lanzando desde código, cambiando texto, propiedades, símbolos o lo que necesite. La idea es tenerlo todo en una única escena y un AnimationPlayer, de esa manera no tendremos que preocuparnos de cargar y descargar diferentes escenas animadas.
Pero claro….
EN ESTE MOMENTO LA ESCENA NO SE ACTUALIZA O NO SE VE DURANTE EL JUEGO!
No sé si es un bug pendiente de revisión pero la manera de solventarlo (y quizás no la mejor) es reasignar la textura de ese SubViewport en el _process() del propio Spotlight

Y con esa funcionaría tanto en el Editor como en el juego. Nótese que he añadido un @export_tool_button, feature nueva en Godot 4.4 , que al hacer click, se actualiza llamando al método _update() también, ya que a veces falla en el propio editor.
SHADER DE PELÍCULA ANTIGUA DE 8mm en GODOT 4.4 :
Quería darle un toque de película antigua , por lo que pensé en simplemente, poner un TextureRect con un shader custom en modo blend_mul con los típicos efectos de película dañanda, variaciones de luz y demás.
Para ello estuve viendo varios ejemplos en www.shadertoy.com o en https://godotshaders.com/ , pero realmente no sabía muy bien como adaptarlos a lo que quería, generalmente el efecto es muy exagerado, parpadeando fuertemente en cada frame, y no quería eso en mi pantalla horas y horas distrayéndome. Gastando mucho tiempo intentando entender esos shaders y adaptarles a mi gusto, me di cuenta que lo mejor sería empezar uno de cero, un shader que me permitiera:
– Menos parpadeo, más sutil y flexible
– Menos generación procedural y costosa de daños en la película, utilizaremos una textura rgb de 1024×1024 para todo ello.
Tengo idea de subirlo a godotshaders en algún momento , cuando esté mejor refinado en un futuro, pero siéntete libre de subirlo tu siempre que des crédito a ésta página o a Impious.
Muestro por encima el cómo está hecho éste shader, pero dejaré el código completo y la textura rgb más abajo.
– Creamos unos uniforms para controlar los parámetros

Dos funciones para obtener números aleatorios (encontrado en this link)

Entonces, lo principal de este shader es que el parpadeo sea aleatorio y su frecuencia y probabilidad de salir , manejable (line_freq o line_chance por ejemplo).

Como ves, es bastante sencillo, multiplicando el tiempo por la frecuencia a la que queremos el flick, y redondeándolo, randomizando y ya es cuestión de mezclar cuánto queremos bajar de intensidad (min_flick ) de la imagen y cada cuánto.

Añadimos grano, pero manejando su intensidad con “grain_punch” , queremos que sea sutil.
Ahora, mezclaremos 3 canales contenidas en una textura RGB , de esa manera tenemos 3 máscaras en una

Sin matemáticas complejas ni mucha generación procedural, es usar el mismo sistema del parpadeo para multiplicar y mover las UVS aleatoriamente tanto del canal rojo, correspondiente a las típicas líneas verticales , como diferentes daños en el canal verde y azul.

De esta manera, aleatoriamente saldrán líneas verticales, y aleatoriamente saldrá una textura de daño, pudiendo se aleatoriamente el canal verde o el azul, y cuyas UV son aleatorias para que no se repitan. No consideré necesario añadir rotaciones de UV a la ecuación, creo que así es suficiente para el efecto deseado.
Os dejo el código y la textura por aquí 😉

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;
}
Y esto es todo! Un pequeño ejercicio para trabajar en la atmósfera del video juego, y quería escribirlo por aquí.
Así que oye, si te ha parecido interesante y quieres saber más sobre Impious , no dudes en añadir el juego a tu Wishlist de Steam!
Gracias por leer y hasta la próxima.
¡Seguimos!