Interlaced Video

This shader simulates interlaced video combing.

For the set-up you will need:

– A ColorRect, set to full rect, with the Script below.

– A SubViewportContainer set to full rect and the property ‘Stretch‘ set to True. 

– A SubViewport, as a child of the container.

– A second Camera3D, as a child of the subviewport

– A Timer, set to ‘Autostart‘. 

If your camera is static that’s all you need, otherwise you will ned a RemoteTransform3D as a child of your player’s camera, with the target set to the Interlacing Camera.

Your node tree should look something like this.


– I recommend setting the interlacing timer to 0.01-0.025s for a 1-2 frame delay at 60fps. Longer times yield slightly better performance but look really bad.

– For better performance, set the “Stretch Shrink” property in your SubViewportContainer to 2. This will halve the combed picture’s resolution, It’s not too discernable and it’s significantly less expensive. (IMPORTANT: If you do this, set the ‘Modulate’ on the SubViewportContainer to transparent, otherwise it will halve the resolution of the entire screen.

UPDATE: Removed the conditional branching from the shader code, which improved performance a little bit.

extends ColorRect

@export var interlace_viewport : SubViewport
@onready var interlacing_material : ShaderMaterial = self.material
@export var interlacing_timer : Timer

####### Connect the interlacing timer's timeout signal here #########
func _on_interlacer_timer_timeout() -> void:
	var viewport_img : ImageTexture = ImageTexture.create_from_image(

	interlacing_material.set_shader_parameter("delayed_screen", viewport_img)

Shader code
shader_type canvas_item;
uniform float line_thickness : hint_range(1.0, 100.0) = 10.0;
uniform sampler2D delayed_screen : source_color;

void fragment() {
    float screen_y = FRAGCOORD.y;
    int line_number = int(screen_y / line_thickness);
    float alpha = mod(float(line_number), 2.0);
    COLOR = texture(delayed_screen, SCREEN_UV);
    COLOR.a *= 1.0 - alpha;
digicam, interlaced, interlacing, retro, tape, VHS
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from cyanone

VHS with wiggle

Analog Monochrome Monitor

Bodycam footage

Related shaders

Rimworld style tilemap shader (with tutorial video)

Notify of

Newest Most Voted
Inline Feedbacks
View all comments
4 months ago

Looking at both shaders you’ve uploaded to this page, I’m curious to see what project you’re working on.

4 months ago

hmm, i’m having issues with the shader, it is somewhat working but the subviewport is in the topcorner

3 months ago

Hello, thanks for making this!
Seems I couldn’t get the timer part to work quite right, my output was a live feed, basically. No matter what I set the timer to. I had to attach this func since it wouldnt start on its own

func _process(delta): if interlacing_timer.time_left <= 0: _on_interlacer_timer_timeout() interlacing_timer.start()

this made it work just fine so I don’t need help getting it to work, but I’m still curious if I misunderstood a step? cheers!

3 months ago
Reply to  Mapache

Select the Interlacing Timer
Open the Node Tab (between the inspector and history)
Double click on timeout()
Select the ColorRect
Click on Pick
Select _on_interlacer_timer_timeout()

Last edited 3 months ago by xyzBrandon
9 days ago

Hi, im kinda new to godot, I just wanna ask where do I put the ColorRect code in? because I thought that I had to make a script in the ColorRect and paste the code there, but when I saw the photo of what it should look like, I didn’t see that the ColorRect had a script to it.