retro 3d psx prototype

building a retro 3d psx prototype in godot 4

This prototype started as a tight experiment in combining 90s-era rendering with modern, physics-driven interactions. Built in Godot 4.6, it mixes retro visual quirks with systems like non euclidean portals, physics-based object interaction, and an AI that responds to being photographed.

role & project details

  • Role: Solo technical designer and programmer
  • Engine: Godot 4.6
  • Focus: Rendering, portals, physics, camera interaction, and enemy behaviour

the problem

i wanted a retro look that came from the way the scene was rendered, not just from a low resolution overlay. The visual limits also needed to support a playable loop with portals, physical objects, and an enemy controlled by what the player can see.

what i built

i built a custom shader pipeline for vertex jitter, affine texture warping, and viewport scaling. I also implemented linked portal views, physics based object dragging, a camera and photo mechanic, and mannequin behaviour that reacts to visibility and active photos.

tools

  • Godot 4.6 and GDScript
  • JoltPhysics3D
  • Custom spatial shaders
  • SubViewports and SpringArm3D

project overview

Rather than just slapping a low-res filter on a modern scene, the goal was to recreate the behaviors of PS1 hardware: vertex jitter, affine texture warping, and low-resolution viewport scaling, so the visuals feel authentic, not pasted on. Underneath that is a playable template with portals, object dragging powered by JoltPhysics3D, and a creepy enemy that freezes when you look at it.

psx shader pipeline

The shader does three main things to sell the retro look:

Example snippet:

float i = (1.0 - jitter) * min(VIEWPORT_SIZE.x, VIEWPORT_SIZE.y) / 2.0;
float w = (PROJECTION_MATRIX * vec4(VERTEX, 1.0)).w;
VERTEX = round(VERTEX / w * i) / i * w;

non-euclidean portals

Portals use subviewports so each opening renders the scene from the linked portal’s perspective. The linked camera’s transform is updated every frame relative to the player, keeping the view consistent while walking through.

Key idea:

var rel = global_transform.affine_inverse() * player.camera.global_transform
linked_portal.cam.global_transform = linked_portal.global_transform * rel

Portals also trigger a short post-process effect with chromatic aberration and vignette. This gives the transition more visual feedback.

photographic ai (enemy behaviour, mannequins)

The mannequins follow a simple rule: they stay still while visible and move when you aren’t looking. The camera/photo mechanic ties into this. If you take and keep a photo of a mannequin, it stays frozen until you remove or review that photo.

If destroyed, mannequins break into rigid fragments and the physics engine handles the resulting debris.

graph TD
    A[Mannequin State] --> B{Is Player Looking?}
    B -->|Yes| C[Freeze Movement]
    B -->|No| D{Has Active Photo?}
    D -->|Yes| C
    D -->|No| E[Move Towards Player]
    E --> F{Destroyed?}
    F -->|Yes| G[Fracture into Rigid Fragments & Physics Debris]
            

gameplay systems

tech stack

why this approach

Putting retro limitations at the core of the design creates tension and helps the game feel distinct. The shaders make environments feel unstable and uncanny, which works well with portals and physics-driven enemy interactions. It’s a small, focused template that’s easy to extend into a tense, atmospheric game.

result

The result is a playable technical template that combines a custom retro rendering pipeline with portals, physics interactions, and a visibility based enemy loop.