feat(rendering): add cinematic post processing

This commit is contained in:
Josh Creek
2026-08-07 22:54:01 +01:00
parent 0a7eb0b742
commit 8fa40769a8
5 changed files with 91 additions and 3 deletions
+27 -1
View File
@@ -1,9 +1,35 @@
[gd_scene load_steps=2 format=3]
[gd_scene load_steps=5 format=3]
[ext_resource type="Script" path="res://scripts/ship_camera.gd" id="1_rig"]
[ext_resource type="Shader" path="res://shaders/post_process.gdshader" id="2_post"]
[sub_resource type="CameraAttributesPractical" id="CameraAttributes_gameplay"]
dof_blur_far_enabled = true
dof_blur_far_distance = 24.0
dof_blur_far_transition = 14.0
dof_blur_near_enabled = true
dof_blur_near_distance = 1.25
dof_blur_near_transition = 0.75
dof_blur_amount = 0.08
[sub_resource type="ShaderMaterial" id="ShaderMaterial_post"]
shader = ExtResource("2_post")
[node name="ShipCameraRig" type="Node3D"]
script = ExtResource("1_rig")
[node name="Camera3D" type="Camera3D" parent="."]
current = true
attributes = SubResource("CameraAttributes_gameplay")
[node name="PostProcess" type="CanvasLayer" parent="."]
layer = 0
[node name="PostFX" type="ColorRect" parent="PostProcess"]
material = SubResource("ShaderMaterial_post")
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
+19 -1
View File
@@ -36,6 +36,7 @@ var target: Ship:
var ball_cam_enabled := true
@onready var camera: Camera3D = $Camera3D
@onready var post_material: ShaderMaterial = $PostProcess/PostFX.material as ShaderMaterial
var _ball: Node3D
# Smoothed horizontal direction from ball to ship; the ball-cam orbits along
@@ -43,6 +44,7 @@ var _ball: Node3D
# raw position (which whips when ball and ship are close together).
var _orbit_dir := Vector3.BACK
var _turbo_blend := 0.0
var _speed_blend := 0.0
var _effective_distance := 8.0
var _shake_strength := 0.0
var _shake_noise := FastNoiseLite.new()
@@ -175,11 +177,17 @@ func _update_speed_feel(delta: float) -> void:
var feel_t := 1.0 - exp(-feel_smoothing * delta)
var turbo_target := 1.0 if target.is_turbo_active() else 0.0
_turbo_blend = lerpf(_turbo_blend, turbo_target, feel_t)
var target_fov := base_fov + target.get_speed_ratio() * speed_fov_add + _turbo_blend * turbo_fov_kick
_speed_blend = lerpf(_speed_blend, target.get_speed_ratio(), feel_t)
var target_fov := base_fov + _speed_blend * speed_fov_add + _turbo_blend * turbo_fov_kick
camera.fov = lerpf(camera.fov, target_fov, feel_t)
_effective_distance = lerpf(
_effective_distance, camera_distance + _turbo_blend * turbo_distance_kick, feel_t
)
post_material.set_shader_parameter(
"motion_blur_amount", _speed_blend * _speed_blend * 0.006 + _turbo_blend * 0.006
)
post_material.set_shader_parameter("chromatic_aberration", _turbo_blend * 0.007)
post_material.set_shader_parameter("vignette_strength", 0.22 + _turbo_blend * 0.16)
func _on_target_ball_contact(intensity: float, _world_position: Vector3) -> void:
@@ -224,7 +232,17 @@ func begin_goal_cut(goal_position: Vector3) -> void:
camera.global_position = _goal_cut_position
camera.fov = 58.0
camera.look_at(_goal_cut_look_at, Vector3.UP)
# A hard, stationary cinematic camera must not inherit the scoring frame's
# ship-speed smear or turbo chroma. Keep only a deliberate cinematic edge.
post_material.set_shader_parameter("motion_blur_amount", 0.0)
post_material.set_shader_parameter("chromatic_aberration", 0.0)
post_material.set_shader_parameter("vignette_strength", 0.3)
func end_goal_cut() -> void:
_goal_cut_active = false
_speed_blend = 0.0
_turbo_blend = 0.0
post_material.set_shader_parameter("motion_blur_amount", 0.0)
post_material.set_shader_parameter("chromatic_aberration", 0.0)
post_material.set_shader_parameter("vignette_strength", 0.22)
+43
View File
@@ -0,0 +1,43 @@
shader_type canvas_item;
render_mode unshaded;
uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;
uniform float motion_blur_amount : hint_range(0.0, 0.03) = 0.0;
uniform float chromatic_aberration : hint_range(0.0, 0.02) = 0.0;
uniform float vignette_strength : hint_range(0.0, 1.0) = 0.22;
vec4 motion_sample(vec2 uv) {
// A restrained centre-directed velocity smear. The camera script scales it
// with ship speed/turbo; mipmapped screen reads soften the outer samples.
if (motion_blur_amount < 0.0001) {
return textureLod(screen_texture, uv, 0.0);
}
vec2 step_uv = (uv - vec2(0.5)) * motion_blur_amount;
vec4 color = textureLod(screen_texture, uv, 0.0) * 0.36;
color += textureLod(screen_texture, uv - step_uv * 0.35, 0.5) * 0.25;
color += textureLod(screen_texture, uv - step_uv * 0.7, 1.0) * 0.21;
color += textureLod(screen_texture, uv - step_uv, 1.5) * 0.18;
return color;
}
void fragment() {
vec2 radial = UV - vec2(0.5);
vec2 chroma = radial * chromatic_aberration;
vec4 center = motion_sample(UV);
vec3 color = center.rgb;
// The branch is driven by one uniform for the whole draw, so non-turbo
// frames avoid eight redundant screen taps without causing warp divergence.
if (chromatic_aberration > 0.0001) {
color.r = motion_sample(UV + chroma).r;
color.b = motion_sample(UV - chroma).b;
}
// Aspect-correct vignette, always subtle and tightened slightly on turbo.
vec2 aspect_radial = radial;
aspect_radial.x *= SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x;
float vignette = smoothstep(0.38, 0.78, length(aspect_radial));
color *= 1.0 - vignette * vignette_strength;
COLOR = vec4(color, 1.0);
}
+1
View File
@@ -0,0 +1 @@
uid://cx1lpjodunjhx
+1 -1
View File
@@ -23,7 +23,7 @@ The largest gap between this and a AAA-feeling product is presentation, not code
- [x] Local lighting / dynamic GI — four local pitch lights plus SDFGI provide bounce and contact lighting for the runtime-generated arena shell; the shader's `hull_fill` stand-in is retired. (The shell is generated in `_ready`, so it cannot participate in an editor LightmapGI bake.)
- [x] Dress `arena_03` — its asteroid field now has mining stations, debris clusters and a distant planet.
- [x] Custom font + a real `Theme` resource for the HUD. Packaged Orbitron typography, shared label styling and themed procedural-instrument text replace `ThemeDB.fallback_font`.
- [ ] Post-processing beyond glow: DoF, motion blur, vignette, chromatic aberration on turbo.
- [x] Post-processing beyond glow: camera DoF plus speed-scaled motion blur, a cinematic vignette and turbo-driven chromatic aberration.
## Multiplayer (long term)