Please show off what crt shaders can do!


Yeah this feedback supports my idea that emulated games on lcds look quite a bit warmer than what they were meant to. I have ran more tests and am now pretty sure that it mostly resides in the temperature of the green channel (which “coincidentally”, the human eye is more sensitive to).

In the field of digital imaging, spatial resolution is mostly perceived via luminance, and the G channel contributes more than 70% to the luminance signal. Videogames are different of course but at the end of the day it all comes down to the same basic principles, we are dealing with video signals after all. So it makes sense that properly balanced greens (i.e. less yellow in this case) result in an output that is perceived as “better”.

I’m also not sure that modifying the overall white balance is the best solution for this because it will make violets, blues and purples a tad too strong, shift reds towards magenta and blue-tint whites, greys and maybe even blacks (see Shenmue and Astal shots from Dogway’s post). What I’m doing is

gr = “0.030000” gb = “0.150000”

with the color mangler shader. I guess what that does is make 15% of the green signal blue (shifting greens towards cyan), and 3% of that same green signal red. All this also depends on game, emulated system, display + calibration, etc… so those values will never be let’s say universal.

At any rate, whatever method we choose will result in gamma shifts (again, see Shenmue’s shots from Dogway). And that’s why I requested (and still do haha) per channel gamma adjustmens in your shaders, @hunterk :slight_smile:


Can’t you do the color edits and merge the changes to a converted luminance color model? Like Photoshop’s Color blending mode.

Edit: I have to note that my monitor is calibrated to D65, this will make my base white point warmer than most factory default monitors.


If I correctly understand what you mean, that’s definitely doable! Take a look at this entry on hunter’s blog


Yes, that works but it’s a bit convoluted. What I meant is to do everything within the shader. Convert both the unaltered and edited framebuffer to Yxy space, swap the color channels and convert back to RGB. It should work even if you raise blue gamma the luminance stays untouched.


I see, sounds good… maybe hunter (or someone with similar skills, if that person exists) can implement it in a shader.

Now a good, real world example. Easymode halation + color mangler (gb = “0.400000”) vs Sony PVM. Not exactly the same, I know… but pretty darn close isn’t it :smiley:


It’s close to the photo, but in my experience photos of CRTs always show more bloom than what you see in person. I bet that PVM is no different. Color-wise, looks like maybe too much blue?


I’m confused with all this color adjustment stuff. What’s the difference between adjusting your display’s color temp via the display’s controls, and using the shader’s adjustments? What effect does it have to start with a warm temp with the display’s controls and then adjust it to cool using the shader? Or should they both match?


Yeah don’t mind the bloom you picky bastard haha, you and I already dealt with that. I’m talking about color here.

The results should be the exactly the same or very similar. But doing it in the shader allows for individual adjustments per game/system that can be saved, and it’s easier to do. I wouldn’t want this stuff applied to content that was not meant to be displayed on crts.


The reason I ask is because while a cool color temp looks good on my CRT monitor, it looks pretty bad on my LCD. Just did some side by side comparisons and it’s a pretty huge difference. I’m wondering why this is. Maybe the color temp presets on this display just suck.


Well I said it before: I, like you, am not very fond of the results that applying an overall cool temp produces. LCDs being digital devices, are relatively sensitive to this kind of manipulation. They tend to clip pretty fast and pretty bad. And when they do, it looks hideous.

Have you tried my method (i.e. color mangling)? Makes more sense on paper imo, for reasons that I already explained, and it looks good in the real world too, as you can see. It does the same thing only in a more controlled and specific manner.


The math is very easy I have done this with math in compositing software, but I don’t do coding (at all). Here’s an article.

Also I wonder why in the color-mangler shader 0.3086, 0.6094, 0.0820 weights are used instead of ITU’s recommendation.

For photo comparisons I guess that if you use the same fixed white balance there shouldn’t be any problem. Where do you test your per channel gamma adjustments? edit: I realised “g” stand for green in your settings, not gamma. The thing is that adjusting only gamma will leave yellow clouds, like in some of my shots.


I believe the ITU NTSC/ATSC weights are intended for use with non-linear gamma, while the weights I used are intended for linear gamma.


Well, Changing my Arcade LCD’s color temp from warm to cool has changed the way my games look for the better.

My whites are actually white and not creme colored, blacks look better as well. The games look more like they did on arcade monitors.

Super Contra for example looks so much better.


On my LCD, whites have a really badly noticeable blue tint when I use cool. Whites looks best when I use the 6500K setting. I’m guessing that this is one of those display-dependent things and that results will vary. Like I said, a cool temp actually looks really good on my CRT.


Yeah gb means green to blue. I’m actually not doing any per channel gamma adjustments. That’s not possible with this shader at the moment although it is with others… MAME HLSL and reshade come to mind). edit: thanks to @hunterk, it is now possible)

What I’m doing is simply shift the green channel towards blue, to achieve cooler tones without touching the red and blue channels. Grey will be shifted a bit as well of course since we are dealing with an additive model and 1/3 of gray’s luminance comes from its green component, but it’s way less apparent than what you get from shifting all the channels.

The target, to sum it up again, is to remove that orangy-yellowish tint from games and achieve a cooler image, but in which greys (and when I say greys I’m talking about white and black as well) will remain as neutral as possible, reds will stay warm and vibrant, and blues/violets/purples/magentas, etc will not clip. And I found that shifting green (and green only) towards blue gives you all that :smiley:


Mine as well. Happens with most if not all LCDs I’m sure. That’s why I don’t like the overall cool white balance, and why for the nth time, I recommend you to try my method!


ITU 709 is for linear and 601 is for gamma compressed as far as I know. I never saw those values but I was reading a bit and seems that they should be used for 2.2 gamma compressed. He states that ITU 601 should be for linear but it doesn’t make much sense considering the close values.

@Squalo I did some tests and actually like the results, it’s a more natural/correct de-tint. Using color temperature looks more harsh, sometimes as harsh as I remember some CRTs, so I will have some time deciding what to do lol


@Dogway You suggest (0.2126, 0.7152, 0.0722) instead?

Here’s a mangler with per-channel gamma boost/cut and those weights for the grayscale:

#version 450

layout(push_constant) uniform Push
	float gamma_boost_r;
	float gamma_boost_g;
	float gamma_boost_b;
	float sat;
	float lum;
	float cntrst;
	float r;
	float g;
	float b;
	float rg;
	float rb;
	float gr;
	float gb;
	float br;
	float bg;
	float blr;
	float blg;
	float blb;
} params;

layout(std140, set = 0, binding = 0) uniform UBO
   mat4 MVP;
   vec4 OutputSize;
   vec4 OriginalSize;
   vec4 SourceSize;
} global;

   Color Mangler
   Author: hunterk
   License: Public domain
#pragma parameter gamma_boost_r "Gamma Mod Red Channel" 0.0 -5.0 5.0 0.1
#pragma parameter gamma_boost_g "Gamma Mod Green Channel" 0.0 -5.0 5.0 0.1
#pragma parameter gamma_boost_b "Gamma Mod Blue Channel" 0.0 -5.0 5.0 0.1
#pragma parameter sat "Saturation" 1.0 0.0 3.0 0.01
#pragma parameter lum "Luminance" 1.0 0.0 5.0 0.01
#pragma parameter cntrst "Contrast" 1.0 0.0 2.0 0.01
#pragma parameter r "Red" 1.0 0.0 2.0 0.01
#pragma parameter g "Green" 1.0 0.0 2.0 0.01
#pragma parameter b "Blue" 1.0 0.0 2.0 0.01
#pragma parameter rg "Red-Green Tint" 0.0 0.0 1.0 0.005
#pragma parameter rb "Red-Blue Tint" 0.0 0.0 1.0 0.005
#pragma parameter gr "Green-Red Tint" 0.0 0.0 1.0 0.005
#pragma parameter gb "Green-Blue Tint" 0.0 0.0 1.0 0.005
#pragma parameter br "Blue-Red Tint" 0.0 0.0 1.0 0.005
#pragma parameter bg "Blue-Green Tint" 0.0 0.0 1.0 0.005
#pragma parameter blr "Black-Red Tint" 0.0 0.0 1.0 0.005
#pragma parameter blg "Black-Green Tint" 0.0 0.0 1.0 0.005
#pragma parameter blb "Black-Blue Tint" 0.0 0.0 1.0 0.005

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord;

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;

void main()
   vec4 screen = pow(texture(Source, vTexCoord), vec4(2.2)).rgba;
   vec4 avglum = vec4(0.5);
   screen = mix(screen, avglum, (1.0 - params.cntrst));
 //				params.r   params.g    params.b
mat4 color = mat4(params.r,  params.rg,  params.rb, 0.0,  //red channel,  params.g,, 0.0,  //green channel,,  params.b,  0.0,  //blue channel
			  params.blr, params.blg, params.blb,    0.0); //alpha channel; these numbers do nothing for our purposes.
mat4 adjust = mat4((1.0 - params.sat) * 0.2126 + params.sat, (1.0 - params.sat) * 0.2126, (1.0 - params.sat) * 0.2126, 1.0,
(1.0 - params.sat) * 0.7152, (1.0 - params.sat) * 0.7152 + params.sat, (1.0 - params.sat) * 0.7152, 1.0,
(1.0 - params.sat) * 0.0722, (1.0 - params.sat) * 0.0722, (1.0 - params.sat) * 0.0722 + params.sat, 1.0,
0.0, 0.0, 0.0, 1.0);
	color *= adjust;
	screen = clamp(screen * params.lum, 0.0, 1.0);
	screen = color * screen;
	vec3 out_gamma = vec3(1.) / (vec3(2.2) - vec3(params.gamma_boost_r, params.gamma_boost_g, params.gamma_boost_b));
	FragColor = pow(screen, vec4(out_gamma, 1.0));

The grayscale change is pretty small in magnitude, but it might make a difference.


I played around with shifting green to blue using the shader but I’m still not able to get satisfactory results on this display. Just boosting the blue channel using my display’s controls seems to improve things a bit, though.


Thanks for the edits, yes looks correct since we are working in linear light.