chroma min/max amb mostreig bilinear subpíxel

This commit is contained in:
JailDesigner committed 2026-05-17 12:24:22 +02:00
1 parent b81a453b32
commit 35d3b359eb
8 files changed
+8612 -6897

No files matched your search

+23 -6
View File
@@ -17,7 +17,7 @@ layout(set = 2, binding = 0) uniform sampler2D scene;
layout(set = 3, binding = 0) uniform PostFXUniforms {
float vignette_strength;
float chroma_strength;
float chroma_min; // intensitat mínima de l'aberració cromàtica
float scanline_strength;
float screen_height;
float mask_strength;
@@ -27,7 +27,7 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
float time; // seconds since SDL init
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz
float pad2; // padding per tancar vec4 #2
float chroma_max; // intensitat màxima; si == chroma_min → chroma estàtic
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
float scan_dark_ratio; // fracció de subfila fosca per fila lògica (1/3 ≈ 0.333)
float scan_dark_floor; // multiplicador de brillantor de la subfila fosca
@@ -35,6 +35,19 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
float pad3; // padding per tancar a 64 bytes (4 × vec4)
} u;
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
// NEAREST quan l'offset de chroma és subpíxel: sense interpolar, l'offset
// arrodonia entre 1 i 2 píxels i el drift temporal feia un parpelleig discret.
float sampleBilinearX(vec2 uv_target, int channel) {
vec2 tex_size = vec2(textureSize(scene, 0));
float px = uv_target.x * tex_size.x - 0.5;
float p_floor = floor(px);
float f = px - p_floor;
vec4 c0 = texture(scene, vec2((p_floor + 0.5) / tex_size.x, uv_target.y));
vec4 c1 = texture(scene, vec2((p_floor + 1.5) / tex_size.x, uv_target.y));
return mix(c0[channel], c1[channel], f);
}
// YCbCr helpers for NTSC bleeding
vec3 rgb_to_ycc(vec3 rgb) {
return vec3(
@@ -92,10 +105,14 @@ void main() {
colour = base;
}
// Aberración cromática (drift animado con time para efecto NTSC real)
float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
// Aberración cromática — intensitat varia entre chroma_min i chroma_max amb
// una sinusoidal (si min == max, queda estàtica). Mostreig bilinear horitzontal
// per evitar el "tic-tac" del NEAREST sampler quan l'offset és subpíxel.
if (u.chroma_min > 0.0 || u.chroma_max > 0.0) {
float ca = mix(u.chroma_min, u.chroma_max, 0.5 + 0.5 * sin(u.time * 7.3)) * 0.005;
colour.r = sampleBilinearX(uv + vec2(ca, 0.0), 0);
colour.b = sampleBilinearX(uv - vec2(ca, 0.0), 2);
}
// Corrección gamma (linealizar antes de scanlines, codificar después)
if (u.gamma_strength > 0.0) {
@@ -22,7 +22,7 @@ struct PostVOut {
struct PostFXUniforms {
float vignette_strength;
float chroma_strength;
float chroma_min;
float scanline_strength;
float screen_height;
float mask_strength;
@@ -32,7 +32,7 @@ struct PostFXUniforms {
float pixel_scale;
float time;
float flicker;
float pad2;
float chroma_max;
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
float scan_dark_ratio;
float scan_dark_floor;
@@ -40,6 +40,18 @@ struct PostFXUniforms {
float pad3;
};
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
// NEAREST quan l'offset de chroma és subpíxel.
static float sampleBilinearX(float2 uv_target, int channel, texture2d<float> scene, sampler samp) {
float2 tex_size = float2(scene.get_width(), scene.get_height());
float px = uv_target.x * tex_size.x - 0.5f;
float p_floor = floor(px);
float f = px - p_floor;
float4 c0 = scene.sample(samp, float2((p_floor + 0.5f) / tex_size.x, uv_target.y));
float4 c1 = scene.sample(samp, float2((p_floor + 1.5f) / tex_size.x, uv_target.y));
return mix(c0[channel], c1[channel], f);
}
static float3 rgb_to_ycc(float3 rgb) {
return float3(
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
@@ -94,9 +106,14 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
colour = base;
}
float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f));
colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r;
colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b;
// Chroma — varia entre chroma_min i chroma_max via sinusoidal; si min == max
// queda estàtic. Mostreig bilinear horitzontal per evitar el "tic-tac" del
// NEAREST sampler amb offsets subpíxel.
if (u.chroma_min > 0.0f || u.chroma_max > 0.0f) {
float ca = mix(u.chroma_min, u.chroma_max, 0.5f + 0.5f * sin(u.time * 7.3f)) * 0.005f;
colour.r = sampleBilinearX(uv + float2(ca, 0.0f), 0, scene, samp);
colour.b = sampleBilinearX(uv - float2(ca, 0.0f), 2, scene, samp);
}
if (u.gamma_strength > 0.0f) {
float3 lin = pow(colour, float3(2.4f));
@@ -234,7 +234,8 @@ namespace Rendering {
void SDL3GPUShader::setPostFXParams(const PostFXParams& params) {
uniforms_.vignette_strength = params.vignette;
uniforms_.scanline_strength = params.scanlines;
uniforms_.chroma_strength = params.chroma;
uniforms_.chroma_min = params.chroma_min;
uniforms_.chroma_max = params.chroma_max;
uniforms_.mask_strength = params.mask;
uniforms_.gamma_strength = params.gamma;
uniforms_.curvature = params.curvature;
@@ -15,7 +15,7 @@
struct PostFXUniforms {
// vec4 #0
float vignette_strength;
float chroma_strength;
float chroma_min;
float scanline_strength;
float screen_height;
// vec4 #1
@@ -27,7 +27,7 @@ struct PostFXUniforms {
float pixel_scale;
float time;
float flicker;
float pad2;
float chroma_max;
// vec4 #3 — paràmetres de scanlines exposats al preset YAML
float scan_dark_ratio;
float scan_dark_floor;
@@ -92,7 +92,7 @@ namespace Rendering {
PostFXUniforms uniforms_{
.vignette_strength = 0.0F,
.chroma_strength = 0.0F,
.chroma_min = 0.0F,
.scanline_strength = 0.0F,
.screen_height = 0.0F,
.mask_strength = 0.0F,
@@ -102,7 +102,7 @@ namespace Rendering {
.pixel_scale = 1.0F,
.time = 0.0F,
.flicker = 0.0F,
.pad2 = 0.0F,
.chroma_max = 0.0F,
// Defaults canònics: 2/3 brillant + 1/3 fosca, brillantor 42%, suavitzat complet (1 px físic).
.scan_dark_ratio = 0.333F,
.scan_dark_floor = 0.42F,
File diff suppressed because it is too large. Load diff
+4 -1
View File
@@ -19,7 +19,10 @@ namespace Rendering {
struct PostFXParams {
float vignette{0.0F};
float scanlines{0.0F};
float chroma{0.0F};
// Aberració cromàtica — varia entre min i max via sinusoidal; si coincideixen
// queda estàtica. Permet un "floor" perquè la imatge mai sigui lliure de chroma.
float chroma_min{0.0F};
float chroma_max{0.0F};
float mask{0.0F};
float gamma{0.0F};
float curvature{0.0F};
+20 -9
View File
@@ -248,12 +248,15 @@ namespace {
}
// Presets per defecte de PostFX, portats directament de projecte_2026.
// chroma_min == chroma_max → chroma estàtic (per defecte). Ajusta'ls al gust
// (per exemple, augmentar el rang en CRT Live perquè "pulsi" visiblement).
auto defaultPostFXPresets() -> std::vector<Options::PostFXPreset> {
return {
{.name = "CRT",
.vignette = 0.6F,
.scanlines = 0.7F,
.chroma = 0.15F,
.chroma_min = 0.15F,
.chroma_max = 0.15F,
.mask = 0.6F,
.gamma = 0.8F,
.curvature = 0.0F,
@@ -262,7 +265,8 @@ namespace {
{.name = "NTSC",
.vignette = 0.4F,
.scanlines = 0.5F,
.chroma = 0.2F,
.chroma_min = 0.2F,
.chroma_max = 0.2F,
.mask = 0.4F,
.gamma = 0.5F,
.curvature = 0.0F,
@@ -271,7 +275,8 @@ namespace {
{.name = "Curved",
.vignette = 0.5F,
.scanlines = 0.6F,
.chroma = 0.1F,
.chroma_min = 0.1F,
.chroma_max = 0.1F,
.mask = 0.5F,
.gamma = 0.7F,
.curvature = 0.8F,
@@ -280,7 +285,8 @@ namespace {
{.name = "Scanlines",
.vignette = 0.0F,
.scanlines = 0.8F,
.chroma = 0.0F,
.chroma_min = 0.0F,
.chroma_max = 0.0F,
.mask = 0.0F,
.gamma = 0.0F,
.curvature = 0.0F,
@@ -289,7 +295,8 @@ namespace {
{.name = "Subtle",
.vignette = 0.3F,
.scanlines = 0.4F,
.chroma = 0.05F,
.chroma_min = 0.05F,
.chroma_max = 0.05F,
.mask = 0.0F,
.gamma = 0.3F,
.curvature = 0.0F,
@@ -298,7 +305,8 @@ namespace {
{.name = "CRT Live",
.vignette = 0.5F,
.scanlines = 0.6F,
.chroma = 0.3F,
.chroma_min = 0.3F,
.chroma_max = 0.3F,
.mask = 0.3F,
.gamma = 0.4F,
.curvature = 0.3F,
@@ -311,7 +319,8 @@ namespace {
readField(node, "name", preset.name);
readField(node, "vignette", preset.vignette);
readField(node, "scanlines", preset.scanlines);
readField(node, "chroma", preset.chroma);
readField(node, "chroma_min", preset.chroma_min);
readField(node, "chroma_max", preset.chroma_max);
readField(node, "mask", preset.mask);
readField(node, "gamma", preset.gamma);
readField(node, "curvature", preset.curvature);
@@ -326,7 +335,8 @@ namespace {
out << " - name: \"" << preset.name << "\"\n";
out << " vignette: " << preset.vignette << "\n";
out << " scanlines: " << preset.scanlines << "\n";
out << " chroma: " << preset.chroma << "\n";
out << " chroma_min: " << preset.chroma_min << "\n";
out << " chroma_max: " << preset.chroma_max << "\n";
out << " mask: " << preset.mask << "\n";
out << " gamma: " << preset.gamma << "\n";
out << " curvature: " << preset.curvature << "\n";
@@ -876,7 +886,8 @@ namespace Options {
return {
.vignette = IT->vignette,
.scanlines = IT->scanlines,
.chroma = IT->chroma,
.chroma_min = IT->chroma_min,
.chroma_max = IT->chroma_max,
.mask = IT->mask,
.gamma = IT->gamma,
.curvature = IT->curvature,
+3 -1
View File
@@ -59,7 +59,9 @@ namespace Options {
std::string name;
float vignette{0.0F};
float scanlines{0.0F};
float chroma{0.0F};
// Aberració cromàtica entre min i max (si coincideixen, estàtica).
float chroma_min{0.0F};
float chroma_max{0.0F};
float mask{0.0F};
float gamma{0.0F};
float curvature{0.0F};