chroma min/max amb mostreig bilinear subpíxel
This commit is contained in:
1 parent
b81a453b32
commit
35d3b359eb
8 files changed
+8612
-6897
No files matched your search
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in new issue
Block a user