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

@@ -417,6 +417,9 @@ void Engine::calculateDeltaTime() {
} }
void Engine::update() { void Engine::update() {
// Accumulate time for PostFX uniforms
postfx_uniforms_.time += delta_time_;
// Actualizar visibilidad del cursor (auto-ocultar tras inactividad) // Actualizar visibilidad del cursor (auto-ocultar tras inactividad)
Mouse::updateCursorVisibility(); Mouse::updateCursorVisibility();
@@ -824,6 +827,7 @@ void Engine::render() {
SDL_BindGPUGraphicsPipeline(pass2, gpu_pipeline_->postfxPipeline()); SDL_BindGPUGraphicsPipeline(pass2, gpu_pipeline_->postfxPipeline());
SDL_GPUTextureSamplerBinding scene_tsb = {offscreen_tex_->texture(), offscreen_tex_->sampler()}; SDL_GPUTextureSamplerBinding scene_tsb = {offscreen_tex_->texture(), offscreen_tex_->sampler()};
SDL_BindGPUFragmentSamplers(pass2, 0, &scene_tsb, 1); SDL_BindGPUFragmentSamplers(pass2, 0, &scene_tsb, 1);
SDL_PushGPUFragmentUniformData(cmd, 0, &postfx_uniforms_, sizeof(PostFXUniforms));
SDL_DrawGPUPrimitives(pass2, 3, 1, 0, 0); SDL_DrawGPUPrimitives(pass2, 3, 1, 0, 0);
// UI overlay (alpha-blended, uses sprite pipeline) // UI overlay (alpha-blended, uses sprite pipeline)

View File

@@ -163,6 +163,9 @@ class Engine {
Uint64 last_frame_time_ = 0; Uint64 last_frame_time_ = 0;
float delta_time_ = 0.0f; float delta_time_ = 0.0f;
// PostFX uniforms (passed to GPU each frame)
PostFXUniforms postfx_uniforms_ = {1.5f, 0.0f, 0.0f, 0.0f};
// Sistema de zoom dinámico // Sistema de zoom dinámico
int current_window_zoom_ = DEFAULT_WINDOW_ZOOM; int current_window_zoom_ = DEFAULT_WINDOW_ZOOM;

View File

@@ -85,7 +85,12 @@ vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// PostFX fragment shader // 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"( static const char* kPostFXFragMSL = R"(
#include <metal_stdlib> #include <metal_stdlib>
@@ -96,14 +101,32 @@ struct PostVOut {
float2 uv; float2 uv;
}; };
fragment float4 postfx_fs(PostVOut in [[stage_in]], struct PostFXUniforms {
texture2d<float> scene [[texture(0)]], float vignette_strength;
sampler samp [[sampler(0)]]) { float chroma_strength;
float4 color = scene.sample(samp, in.uv); 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); 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); color.rgb *= clamp(vignette, 0.0, 1.0);
return color; return color;
@@ -215,7 +238,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs", SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs",
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs", 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) { if (!postfx_vert || !postfx_frag) {
SDL_Log("GpuPipeline: failed to create postfx shaders"); SDL_Log("GpuPipeline: failed to create postfx shaders");
if (postfx_vert) SDL_ReleaseGPUShader(device, postfx_vert); if (postfx_vert) SDL_ReleaseGPUShader(device, postfx_vert);

View File

@@ -2,6 +2,18 @@
#include <SDL3/SDL_gpu.h> #include <SDL3/SDL_gpu.h>
// ============================================================================
// PostFXUniforms — pushed to the fragment stage each frame via
// SDL_PushGPUFragmentUniformData(pass, 0, &uniforms, sizeof(PostFXUniforms))
// MSL binding: constant PostFXUniforms& u [[buffer(0)]]
// ============================================================================
struct PostFXUniforms {
float vignette_strength; // 0 = none, 1.5 = default subtle
float chroma_strength; // 0 = off, 1 = full chromatic aberration
float scanline_strength; // 0 = off, 1 = full scanlines
float time; // accumulated seconds (for future animations)
};
// ============================================================================ // ============================================================================
// GpuPipeline — Creates and owns the graphics pipelines used by the engine. // GpuPipeline — Creates and owns the graphics pipelines used by the engine.
// //
@@ -9,6 +21,7 @@
// Vertex layout: GpuVertex (pos float2, uv float2, col float4). // Vertex layout: GpuVertex (pos float2, uv float2, col float4).
// postfx_pipeline_ : full-screen triangle, no vertex buffer, no blend. // postfx_pipeline_ : full-screen triangle, no vertex buffer, no blend.
// Reads offscreen texture, writes to swapchain. // Reads offscreen texture, writes to swapchain.
// Accepts PostFXUniforms via fragment uniform buffer slot 0.
// ============================================================================ // ============================================================================
class GpuPipeline { class GpuPipeline {
public: public: