PS1 Shader

This is a shader trying to emulate the look of a PS1 game. The PS1 graphics pipeline took a lot of shortcuts, for example it used exclusively integers.

Simply apply it to any mesh. Best used with a texture with disabled filtering.

Uniforms:

Jitter – The amount of polygon “jitter”, higher value means more intensive jittering.
Jitter Z Coordinate – If disabled, the Z axis (depth) of a vertex isn’t affected. Can avoid some clipping.
Jitter Depth Independent – If disabled, makes further objects jitter less, basically “scaling” the jitter with distance.
Affine Texture Mapping – If disabled, gets rid of the “weird floaty textures” effect at oblique angles. PS1 developers counteracted this by subdividing geometry, so you should too.

Shader code
shader_type spatial;
render_mode vertex_lighting, skip_vertex_transform, 
			specular_phong, diffuse_lambert_wrap;

uniform sampler2D albedo: hint_black_albedo;
uniform sampler2D specular: hint_black;
uniform sampler2D emission: hint_black;

uniform float jitter: hint_range(0.0, 1.0) = 0.5;
uniform bool jitter_z_coordinate = true;
uniform bool jitter_depth_independent = true;
uniform bool affine_texture_mapping = true;
uniform float alpha_scissor: hint_range(0.0, 1.0) = 1.0;

void vertex() {
	VERTEX = (MODELVIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
	
	float z_orig = VERTEX.z;
	float i = (1.0 - jitter) * min(VIEWPORT_SIZE.x, VIEWPORT_SIZE.y) / 2.0;
	
	if (jitter_depth_independent) {
		float w = (PROJECTION_MATRIX * vec4(VERTEX, 1.0)).w;
		VERTEX = round(VERTEX / w * i) / i * w;
	} else {
		VERTEX = round(VERTEX * i) / i;
	}
	
	if (!jitter_z_coordinate) {
		VERTEX.z = z_orig;
	}

	if (affine_texture_mapping) {
		UV *= VERTEX.z;
	}
}

void fragment() {
	vec2 uv = UV;
	
	if (affine_texture_mapping) {
		uv /= VERTEX.z;
	}
	
	ALBEDO = texture(albedo, uv).rgb;
	ALPHA = texture(albedo, uv).a;
	ALPHA_SCISSOR = alpha_scissor;
	EMISSION = texture(emission, uv).rgb;
	SPECULAR = texture(specular, uv).r;
}
Tags
ps1, retro
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 Mighty Duke

PS1 Post-processing

Mandelbrot Set

Related shaders

PS1/PSX Model

PS1 Post-processing

PS1/PSX PostProcessing

Subscribe
Notify of
guest

8 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Nekuromu
2 years ago

Do you plan to add UV scaling? Such as tiling a floor with a texture on repeat? (I just finished implementing this myself but was wondering if you had plans to make your own updates to further long this shader!)

Last edited 2 years ago by Nekuromu
Guessy
2 years ago

A quick heads up, though the vertex effect is great it does NOT transform normals, they will remain in local space.

Add:

NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);

into the vertex function to fix this

shidoengie
shidoengie
2 years ago

hey ive been trying to use this shader but its not tiling my textures so im not able to use this!

breather
1 year ago

Howdy, I used this shader extensively in my game, Synesthesia. It only appears in the desktop versions due to graphical constraints. It was vital to forming the aesthetic for the game. Thank you for sharing this shader with the community.

Here’s a trailer if anyone wants to see the shader in action: https://www.youtube.com/watch?v=v2BZei_-wUs

RyFooled
RyFooled
1 year ago

It does not work. its just black for me.

pnda044
9 months ago

If you want to control the texture warping intensity you can make these changes:

Add this line to the variables:

uniform float affine_texture_intensity: hint_range(-2.0, 0.0) = -2.0;

Change line 33 to:

UV.x *= (1.0 + (VERTEX.z * affine_texture_intensity));
UV.y *= (1.0 + (VERTEX.z * affine_texture_intensity));

Change line 41 to:

uv.x /= (1.0 + (VERTEX.z * affine_texture_intensity));
uv.y /= (1.0 + (VERTEX.z * affine_texture_intensity));

With this you will be able to control the texture warping intensity from 0 (no distortion), to -2 (full distortion).

bry
bry
7 months ago

Hey Duke, thanks for the shader. Is this supposed to work with the 4.0 releases of Godot? When trying to use it I get Invalid render mode: specular_phong. I’ve tried other modes but then there are other errors I’m not sure how to correct.

Noname
Noname
1 month ago
Reply to  bry

Not sure about the ALPHA_SCISSOR_THRESHOLD but at least this should get you going:

shader_type spatial;
render_mode vertex_lighting, skip_vertex_transform,
         diffuse_lambert_wrap;

uniform sampler2D albedo: hint_default_black;
uniform sampler2D specular: hint_default_black;
uniform sampler2D emission: hint_default_black;

uniform float jitter: hint_range(0.0, 1.0) = 0.5;
uniform bool jitter_z_coordinate = true;
uniform bool jitter_depth_independent = true;
uniform bool affine_texture_mapping = true;
uniform float alpha_scissor: hint_range(0.0, 1.0) = 1.0;

void vertex() {
   VERTEX = (MODELVIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;

   float z_orig = VERTEX.z;
   float i = (1.0 – jitter) * min(VIEWPORT_SIZE.x, VIEWPORT_SIZE.y) / 2.0;

   if (jitter_depth_independent) {
      float w = (PROJECTION_MATRIX * vec4(VERTEX, 1.0)).w;
      VERTEX = round(VERTEX / w * i) / i * w;
   } else {
      VERTEX = round(VERTEX * i) / i;
   }

   if (!jitter_z_coordinate) {
      VERTEX.z = z_orig;
   }

   if (affine_texture_mapping) {
      UV *= VERTEX.z;
   }
}

void fragment() {
   vec2 uv = UV;

   if (affine_texture_mapping) {
      uv /= VERTEX.z;
   }

   ALBEDO = texture(albedo, uv).rgb;
   ALPHA = texture(albedo, uv).a;
   ALPHA_SCISSOR_THRESHOLD = alpha_scissor;
   EMISSION = texture(emission, uv).rgb;
   SPECULAR = texture(specular, uv).r;
}

Last edited 1 month ago by Noname