Mercurial > repos > blastem
comparison shaders/ntsc.f.glsl @ 2410:f1574b22d5d9
Update Sik's NTSC shader
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Thu, 04 Jan 2024 22:12:03 -0800 |
parents | 49bd818ec9d8 |
children | 9f3008f91bec |
comparison
equal
deleted
inserted
replaced
2409:f8ce89498e11 | 2410:f1574b22d5d9 |
---|---|
1 //****************************************************************************** | 1 //****************************************************************************** |
2 // NTSC composite simulator for BlastEm | 2 // NTSC composite simulator for BlastEm, fixed rainbow frequency edition |
3 // Shader by Sik, based on BlastEm's default shader | 3 // Shader by Sik, based on BlastEm's default shader |
4 // | 4 // |
5 // Now with gamma correction (NTSC = 2.5 gamma, sRGB = 2.2 gamma) | 5 // Now with gamma correction (NTSC = 2.5 gamma, sRGB = 2.2 gamma*) |
6 // *sorta, sRGB isn't exactly a gamma curve, but close enough | |
6 // | 7 // |
7 // It works by converting from RGB to YIQ and then encoding it into NTSC, then | 8 // It works by converting from RGB to YIQ and then encoding it into NTSC, then |
8 // trying to decode it back. The lossy nature of the encoding process results in | 9 // trying to decode it back. The lossy nature of the encoding process results in |
9 // the rainbow effect. It also accounts for the differences between H40 and H32 | 10 // the rainbow effect. It also accounts for the differences between H40 and H32 |
10 // mode as it computes the exact colorburst cycle length. | 11 // mode as it computes the exact colorburst cycle length. |
13 // pixels by sampling seven points (in 0.25 colorburst cycle intervals), that | 14 // pixels by sampling seven points (in 0.25 colorburst cycle intervals), that |
14 // seems to be enough to give decent filtering (four samples are used for | 15 // seems to be enough to give decent filtering (four samples are used for |
15 // low-pass filtering, but we need seven because decoding chroma also requires | 16 // low-pass filtering, but we need seven because decoding chroma also requires |
16 // four samples so we're filtering over overlapping samples... just see the | 17 // four samples so we're filtering over overlapping samples... just see the |
17 // comments in the I/Q code to understand). | 18 // comments in the I/Q code to understand). |
19 // | |
20 // Thanks to Tulio Adriano for helping adjust the frequency of the banding. | |
18 //****************************************************************************** | 21 //****************************************************************************** |
19 | 22 |
20 uniform mediump float width; | 23 uniform mediump float width; |
21 uniform sampler2D textures[2]; | 24 uniform sampler2D textures[2]; |
22 uniform mediump vec2 texsize; | 25 uniform mediump vec2 texsize; |
23 varying mediump vec2 texcoord; | 26 varying mediump vec2 texcoord; |
27 uniform int curfield; | |
28 uniform int scanlines; | |
24 | 29 |
25 // Converts from RGB to YIQ | 30 // Converts from RGB to YIQ |
26 mediump vec3 rgba2yiq(vec4 rgba) | 31 mediump vec3 rgba2yiq(vec4 rgba) |
27 { | 32 { |
28 return vec3( | 33 return vec3( |
49 ); | 54 ); |
50 } | 55 } |
51 | 56 |
52 void main() | 57 void main() |
53 { | 58 { |
54 // Use first pair of lines for hard line edges | 59 // The coordinate of the pixel we're supposed to access |
55 // Use second pair of lines for soft line edges | 60 // In interlaced mode, the entire screen is shifted down by half a scanline, |
56 mediump float modifiedY0 = (floor(texcoord.y * texsize.y + 0.25) + 0.5) / texsize.y; | 61 // y_offset is used to account for this. |
57 mediump float modifiedY1 = (floor(texcoord.y * texsize.y - 0.25) + 0.5) / texsize.y; | 62 mediump float y_offset = float(curfield) * -0.5 / texsize.y; |
58 //mediump float modifiedY0 = (texcoord.y * texsize.y + 0.75) / texsize.y; | 63 mediump float x = texcoord.x; |
59 //mediump float modifiedY1 = (texcoord.y * texsize.y + 0.25) / texsize.y; | 64 mediump float y = texcoord.y + y_offset; |
60 | |
61 // Used by the mixing when fetching texels, related to the way BlastEm | |
62 // handles interlaced mode (nothing to do with composite) | |
63 mediump float factorY = (sin(texcoord.y * texsize.y * 6.283185307) + 1.0) * 0.5; | |
64 | 65 |
65 // Horizontal distance of half a colorburst cycle | 66 // Horizontal distance of half a colorburst cycle |
66 mediump float factorX = (1.0 / texsize.x) / 170.667 * 0.5 * (width - 27.0); | 67 mediump float factorX = (1.0 / texsize.x) / 170.667 * 0.5 * width; |
67 | 68 |
68 // sRGB has a gamma of 2.2 while NTSC has a gamma of 2.5 | 69 // sRGB approximates a gamma ramp of 2.2 while NTSC has a gamma of 2.5 |
69 // Use this value to do gamma correction of every RGB value | 70 // Use this value to do gamma correction of every RGB value |
70 mediump float gamma = 2.5 / 2.2; | 71 mediump float gamma = 2.5 / 2.2; |
71 | 72 |
72 // Where we store the sampled pixels. | 73 // Where we store the sampled pixels. |
73 // [0] = current pixel | 74 // [0] = current pixel |
79 // [6] = 1 2/4 colorburst cycles earlier | 80 // [6] = 1 2/4 colorburst cycles earlier |
80 mediump float phase[7]; // Colorburst phase (in radians) | 81 mediump float phase[7]; // Colorburst phase (in radians) |
81 mediump float raw[7]; // Raw encoded composite signal | 82 mediump float raw[7]; // Raw encoded composite signal |
82 | 83 |
83 // Sample all the pixels we're going to use | 84 // Sample all the pixels we're going to use |
84 mediump float x = texcoord.x; | |
85 for (int n = 0; n < 7; n++, x -= factorX * 0.5) { | 85 for (int n = 0; n < 7; n++, x -= factorX * 0.5) { |
86 // Compute colorburst phase at this point | 86 // Compute colorburst phase at this point |
87 phase[n] = x / factorX * 3.1415926; | 87 phase[n] = x / factorX * 3.1415926; |
88 | 88 |
89 // Decode RGB into YIQ and then into composite | 89 // Decode RGB into YIQ and then into composite |
90 // Reading two textures is a BlastEm thing :P (the two fields in | 90 raw[n] = yiq2raw(rgba2yiq( |
91 // interlaced mode, that's taken as-is from the stock shaders) | 91 texture2D(textures[curfield], vec2(x, y)) |
92 raw[n] = yiq2raw(mix( | 92 ), phase[n]); |
93 rgba2yiq(texture2D(textures[1], vec2(x, modifiedY1))), | |
94 rgba2yiq(texture2D(textures[0], vec2(x, modifiedY0))), | |
95 factorY | |
96 ), phase[n]); | |
97 } | 93 } |
98 | 94 |
99 // Decode Y by averaging over the the whole sampled cycle (effectively | 95 // Decode Y by averaging over the last whole sampled cycle (effectively |
100 // filtering anything above the colorburst frequency) | 96 // filtering anything above the colorburst frequency) |
101 mediump float y_mix = (raw[0] + raw[1] + raw[2] + raw[3]) * 0.25; | 97 mediump float y_mix = (raw[0] + raw[1] + raw[2] + raw[3]) * 0.25; |
102 | 98 |
103 // Decode I and Q (see page below to understand what's going on) | 99 // Decode I and Q (see page below to understand what's going on) |
104 // https://codeandlife.com/2012/10/09/composite-video-decoding-theory-and-practice/ | 100 // https://codeandlife.com/2012/10/09/composite-video-decoding-theory-and-practice/ |
152 vec4(gamma, gamma, gamma, 1.0)); | 148 vec4(gamma, gamma, gamma, 1.0)); |
153 | 149 |
154 // If you're curious to see what the raw composite signal looks like, | 150 // If you're curious to see what the raw composite signal looks like, |
155 // comment out the above and uncomment the line below instead | 151 // comment out the above and uncomment the line below instead |
156 //gl_FragColor = vec4(raw[0], raw[0], raw[0], 1.0); | 152 //gl_FragColor = vec4(raw[0], raw[0], raw[0], 1.0); |
153 | |
154 // Basic scanlines effect. This is done by multiplying the color against a | |
155 // "half sine" wave so the center is brighter and a narrow outer area in | |
156 // each scanline is noticeable darker. | |
157 // The weird constant in the middle line is 1-sqrt(2)/4 and it's used to | |
158 // make sure that the average multiplied value across the whole screen is 1 | |
159 // to preserve the original brightness. | |
160 if (scanlines != 0) { | |
161 mediump float mult = sin(y * texsize.y * 3.1415926); | |
162 mult = abs(mult) * 0.5 + 0.646446609; | |
163 gl_FragColor *= vec4(mult, mult, mult, 1.0); | |
164 } | |
157 } | 165 } |