|
#if OPENGL |
|
#define SV_POSITION POSITION |
|
#define VS_SHADERMODEL vs_3_0 |
|
#define PS_SHADERMODEL ps_3_0 |
|
#else |
|
#define VS_SHADERMODEL vs_4_0_level_9_1 |
|
#define PS_SHADERMODEL ps_4_0_level_9_1 |
|
#endif |
|
|
|
// Runs on GraphicsManager.BackbufferPostProcessChain, monitor-pixel resolution. Three effects |
|
// stacked in one shader, in this order: |
|
// |
|
// 1. Column mask: darken source samples along vertical bands aligned with the "between- |
|
// source-pixel" boundaries. Applied INSIDE the per-channel sample step, so the aberration |
|
// in step 2 naturally bleeds bright neighboring columns across the darkened boundaries |
|
// (produces the classic magenta / cyan fringing at column edges). Uniform strength - the |
|
// column mask deliberately does NOT bright-shine-through (unlike scanlines below); a real |
|
// shadow mask blocks every column equally regardless of what phosphor is behind it. |
|
// 2. Chromatic aberration: sample R shifted right by ChromaticAberrationPx, B shifted left, |
|
// G at center. Each channel's tap goes through the column mask above. |
|
// 3. Scanlines: darken pixels along horizontal bands between source-pixel rows, sinusoidal |
|
// so the darkening tapers smoothly toward each source pixel's center. Attenuated by |
|
// luminance so bright pixels shine through almost unaffected. |
|
// |
|
// Column mask (1) is inside the sample function so it happens BEFORE aberration (2); scanlines |
|
// (3) are applied last, over the aberrated result, so bloom / bright halos escape both masks. |
|
|
|
#define TWO_PI 6.283185307179586 |
|
|
|
float2 ScreenSizePx; |
|
// Size of one source (game) pixel in monitor pixels - i.e., GraphicsManager.Zoom. Isotropic |
|
// (the upscale is point-clamp square), so this drives both the horizontal column period and |
|
// the vertical scanline period. |
|
float SourcePixelSizePx; |
|
// R/B channel horizontal offset in monitor pixels. 0 disables aberration entirely. |
|
float ChromaticAberrationPx; |
|
// 0..1 darkening applied at column-boundary peaks. Uniform - not luminance-attenuated (unlike |
|
// ScanlineIntensity below), so bright columns are darkened at boundaries just as much as dark |
|
// ones. A real shadow mask blocks every column equally. |
|
float ColumnIntensity; |
|
// 0..1 maximum darkening applied at scanline peaks to a fully-black pixel. Same luminance |
|
// shine-through as the column mask. |
|
float ScanlineIntensity; |
|
|
|
Texture2D SpriteTexture; |
|
// No sampler_state override: inherit SamplerState.PointClamp from the SpriteBatch that invokes |
|
// this shader (per GraphicsManager.EndDraw's chain loop). PointClamp keeps the R/G/B taps on |
|
// exact monitor pixels so the aberration is a crisp channel shift, not a blurred smear. |
|
sampler2D SpriteTextureSampler = sampler_state |
|
{ |
|
Texture = <SpriteTexture>; |
|
}; |
|
|
|
struct VertexShaderOutput |
|
{ |
|
float4 Position : SV_POSITION; |
|
float4 Color : COLOR0; |
|
float2 TextureCoordinates : TEXCOORD0; |
|
}; |
|
|
|
// Sample the source with the vertical column mask applied. Each aberration tap goes through |
|
// here at its own shifted UV, so the R and B channels see the column darkening at THEIR |
|
// sample positions - which is exactly what causes the aberration to bleed a bright neighbor's |
|
// color across the darkened boundary in front of us. |
|
float3 SampleWithColumnMask(float2 sampleUv) |
|
{ |
|
float3 raw = tex2D(SpriteTextureSampler, sampleUv).rgb; |
|
|
|
// Column mask: peaks at monitor-pixel centers that sit on a source-pixel-column boundary, |
|
// troughs at pixel centers away from any boundary. The (monitorX - 0.5) phase shift is |
|
// critical: pixel-shader input for monitor pixel n arrives at x = n + 0.5 (pixel CENTER). |
|
// Without the shift, at Zoom == 2 those centers land at cos-wave zero-crossings uniformly |
|
// (mask == 0.5 everywhere → invisible), and at Zoom == 3 the pattern is off-center. With |
|
// the shift, each column-boundary monitor pixel gets mask == 1 as intended. |
|
float monitorX = sampleUv.x * ScreenSizePx.x; |
|
float phase = (monitorX - 0.5) / SourcePixelSizePx; |
|
float mask = 0.5 + 0.5 * cos(phase * TWO_PI); |
|
|
|
// No luminance shine-through here (unlike the scanline pass in MainPS): a shadow-mask |
|
// column blocks bright and dark pixels alike. |
|
return raw * (1.0 - ColumnIntensity * mask); |
|
} |
|
|
|
float4 MainPS(VertexShaderOutput input) : COLOR |
|
{ |
|
float2 uv = input.TextureCoordinates; |
|
|
|
// Aberration through the column mask: R shifted right, B shifted left, G stays put. Each |
|
// channel's tap samples the column-masked source at its own X. Saturating the UVs makes |
|
// near-edge shifts sample the edge pixel rather than wrap / clamp to black. |
|
float aberrUv = ChromaticAberrationPx / ScreenSizePx.x; |
|
float3 rSample = SampleWithColumnMask(saturate(uv + float2(aberrUv, 0.0))); |
|
float3 gSample = SampleWithColumnMask(uv); |
|
float3 bSample = SampleWithColumnMask(saturate(uv - float2(aberrUv, 0.0))); |
|
float alpha = tex2D(SpriteTextureSampler, uv).a; |
|
|
|
float3 color = float3(rSample.r, gSample.g, bSample.b); |
|
|
|
// Horizontal scanline mask. Same (monitorY - 0.5) phase shift as the column mask above: |
|
// pixel-shader input lands at pixel CENTERS (n + 0.5), and without the shift Zoom == 2 |
|
// samples every peak at a cos zero-crossing → the whole screen sits at mask == 0.5 and |
|
// no scanlines are visible. With the shift, the top monitor row of each source-pixel row |
|
// gets mask == 1 as intended. |
|
float monitorY = uv.y * ScreenSizePx.y; |
|
float scanPhase = (monitorY - 0.5) / SourcePixelSizePx; |
|
float scanMask = 0.5 + 0.5 * cos(scanPhase * TWO_PI); |
|
|
|
// Luminance of the aberrated color - dark pixels get the full ScanlineIntensity darkening |
|
// at scan peaks, bright pixels get essentially none. |
|
float scanLum = saturate(dot(color, float3(0.299, 0.587, 0.114))); |
|
float scanDarkening = ScanlineIntensity * scanMask * (1.0 - scanLum); |
|
|
|
color *= (1.0 - scanDarkening); |
|
|
|
return float4(color, alpha); |
|
} |
|
|
|
technique SpriteDrawing |
|
{ |
|
pass P0 |
|
{ |
|
PixelShader = compile PS_SHADERMODEL MainPS(); |
|
} |
|
}; |