feat(postfx): afegir push constants i efectes chromatic aberration + scanlines

- PostFXUniforms struct (vignette_strength, chroma_strength, scanline_strength, time)
- Shader MSL actualitzat: aberració cromàtica RGB + scanlines sin-wave + vinyeta paramètrica
- Pipeline postfx declara num_uniform_buffers=1 (buffer(0) en MSL)
- Engine acumula temps i fa SDL_PushGPUFragmentUniformData cada frame
- Valors per defecte: vignette=1.5, chroma=0, scanlines=0 (comportament idèntic a l'anterior)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 22:11:05 +01:00
parent 00a5875c92
commit af0276255e
4 changed files with 51 additions and 8 deletions

View File

@@ -85,7 +85,12 @@ vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
// ---------------------------------------------------------------------------
// PostFX fragment shader
// Samples the offscreen scene texture and applies a subtle vignette.
// Effects driven by PostFXUniforms (uniform buffer slot 0):
// - Chromatic aberration: RGB channel UV offset
// - Scanlines: sin-wave intensity modulation
// - Vignette: radial edge darkening
// MSL binding for fragment uniform buffer 0 with 1 sampler, 0 storage:
// constant PostFXUniforms& u [[buffer(0)]]
// ---------------------------------------------------------------------------
static const char* kPostFXFragMSL = R"(
#include <metal_stdlib>
@@ -96,14 +101,32 @@ struct PostVOut {
float2 uv;
};
fragment float4 postfx_fs(PostVOut in [[stage_in]],
texture2d<float> scene [[texture(0)]],
sampler samp [[sampler(0)]]) {
float4 color = scene.sample(samp, in.uv);
struct PostFXUniforms {
float vignette_strength;
float chroma_strength;
float scanline_strength;
float time;
};
// Subtle vignette: darkens edges proportionally to distance from centre
fragment float4 postfx_fs(PostVOut in [[stage_in]],
texture2d<float> scene [[texture(0)]],
sampler samp [[sampler(0)]],
constant PostFXUniforms& u [[buffer(0)]]) {
// Chromatic aberration: offset R and B channels horizontally
float ca = u.chroma_strength * 0.005;
float4 color;
color.r = scene.sample(samp, in.uv + float2( ca, 0.0)).r;
color.g = scene.sample(samp, in.uv ).g;
color.b = scene.sample(samp, in.uv - float2( ca, 0.0)).b;
color.a = scene.sample(samp, in.uv ).a;
// Scanlines: horizontal sine-wave at ~360 lines (one dark band per 2 px at 720p)
float scan = 0.85 + 0.15 * sin(in.uv.y * 3.14159265 * 720.0);
color.rgb *= mix(1.0, scan, u.scanline_strength);
// Vignette: radial edge darkening
float2 d = in.uv - float2(0.5, 0.5);
float vignette = 1.0 - dot(d, d) * 1.5;
float vignette = 1.0 - dot(d, d) * u.vignette_strength;
color.rgb *= clamp(vignette, 0.0, 1.0);
return color;
@@ -215,7 +238,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs",
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs",
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
if (!postfx_vert || !postfx_frag) {
SDL_Log("GpuPipeline: failed to create postfx shaders");
if (postfx_vert) SDL_ReleaseGPUShader(device, postfx_vert);