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:
- Vertex snapping in projection space to create the classic jitter.
- Affine texture mapping to produce texture warping on angled polygons.
- A low native viewport (455x256) with nearest-neighbor scaling for crisp pixels.
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
- Physical dragging uses Jolt joints instead of kinematic attachments so objects collide and feel heavy.
- The hotbar includes a lantern (dynamic shadows), a revolver (projectiles + recoil), and a camera (snapshots + inventory review).
- Movement includes stamina-backed sprinting, procedural camera bob, and contextual footstep sounds.
tech stack
- Godot 4.6 (Forward Plus)
- JoltPhysics3D for rigid body dynamics
- GDScript for gameplay logic
- Custom spatial shaders for retro and portal effects
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.