• Godot Help
  • Subpixel snapping in a **3D** pixel art game

Moving a project over from Unity into Godot. It uses a 3D pixel art art-style similar to the works of t3ssel8r. Currently, I've been able to get a pixelated look using the resolution settings in Project Settings > Display > Window. However, I've been having a lot of trouble getting rid of pixel creep/swimming which happens when the camera moves.

I tried multiple implementations (including trying to port multiple from Unity) but nothing was able to work. Closest I've been able to get so far was with the help of this reddit comment by denovodavid which gives some code for achieving subpixel snapping. In a reply to this comment, you can see a gif of someone who successfully implemented denovodavid's code and the result is exactly what I want.

However, I have not been so lucky. The best result I've got is this but the camera has this jittery movement which I have not been able to solve.

My code is as follow (lightly modified version of denovodavid's code. Variable names have changed, added a texturerect which is offset by the subpixel error as per denovodavid's instructions, and I have a lower resolution)

`
#Resolution is 320x180 with some padding to account for the screen texel error
const width: int = 322
const height: int = 182

const orthographic_size: float = 10
const texel_snap: float = float(height) / orthographic_size

@export var cam_proxy: Node3D
@export var cam_parent: Node3D
@export var render_texture: Control
@onready var cam :Camera3D = self.get_parent()

var last_rot
var world_space

func _ready():
INPUT_PROVIDER.camera = self.get_parent()
INPUT_PROVIDER.set_cam()
last_rot = cam_proxy.global_rotation
world_space = cam_proxy.global_transform


func _process(delta):
if cam_proxy.global_rotation != last_rot:
last_rot = cam_proxy.global_rotation
world_space = cam_proxy.global_transform


var snapped_pos = cam_proxy.global_position * world_space
var snapped_world_space_pos: Vector3 = floor(snapped_pos * texel_snap) / texel_snap
cam.global_position = world_space * snapped_world_space_pos
cam.global_rotation = cam_proxy.global_rotation

#use error to shift the final image on TextureRect/Sprite3D/etc. for extra smooth
var snap_space_error = snapped_world_space_pos - snapped_pos
var screen_texel_error = Vector2(snap_space_error.x, -snap_space_error.y) * texel_snap

#Smoothly move the render texture to the correct position
render_texture.set_position(screen_texel_error)

`

In case it matters, here's my hierachy layout

and as a final thing, here's my basic camera movement code I made for this test scene:
`extends Node3D

const BUTTON_MASK_LEFT = 1
const BUTTON_MASK_RIGHT = 2

func _process(delta):
var input_state😃ictionary = INPUT_PROVIDER.get_state()


var direction = input_state["input_vector"] if input_state.has("input_vector") else Vector3.ZERO
#Direction is just a vector3 based on what keys are pressed (i.e. W = (0,0,1), A = (-1,0,0), etc.)
if direction != Vector3.ZERO:
    # Move the camera
    var camera_transform = self.get_transform()
    camera_transform.origin -= direction * 3 * delta
    set_transform(camera_transform)`

This is attached to the CamProxy node which the camera is snapped to.

I'd really appreciate if anyone could give some input on how to achieve a pixel creep free and smooth camera movement for 3D pixel art games. This is a really big block for me right now and as far as I can tell, there is no open-source example for a smooth 3D pixel art camera for Godot and I would be nice to be able to make a test scene example of one other people can use.

Apologies for the messed up code formatting. I don't know how to fix it.

I have been able to get something sort of working:
https://github.com/BradFitz66/Godot-3D-pixel-art-camera

However, there's still a few issues I need to fix:

  • Only seems to work for 640x320. For example, 320x180 reintroduces pixel swimming even after changing everything like the subviewport size and the variables inside 3DPixelCam.gd

  • Can't seem to make the camera follow something at an offset. Currently it's hardlocked to the camera proxy and I can't, for example, have it follow a player. Trying to make the camera be a child of an object so it's always at an offset reintroduces pixel swim and pixel creep.

11 days later

Not sure how to code, but I'm here along for the ride. Keep me updated!

Any idea on how to get rid of the edge jittering of moving meshes? Aligning their position to the grid doesn't really do anything since the vertices are not grid aligned.

could you explain how the updated code works? Just so I have a better idea visually. Maybe add a few comments in the code on the github.

BTW, pretty new to godot and the engine in general, so I'll try to keep up.