Please show off what crt shaders can do!

lol no worries, it’s a small/simple change. I’ll take it, though :smiley:

2 Likes

Aw I wasn’t able to make it work, and I think I know why: to make a short story shorter… could you make it glsl, @hunterk?

1 Like

sure, here you go:

/*
   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

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

// compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

void main()
{
   gl_Position = MVPMatrix * VertexCoord;
   TEX0.xy = TexCoord.xy;
}

#elif defined(FRAGMENT)

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out COMPAT_PRECISION vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float gamma_boost_r, gamma_boost_g, gamma_boost_b, sat, lum, cntrst, blr, blg, blb, r, g, b, rg, rb, gr, gb, br, bg;
#else
#define gamma_boost_r 0.0
#define gamma_boost_g 0.0
#define gamma_boost_b 0.0
#define sat 1.0
#define lum 1.0
#define cntrst 1.0
#define blr 0.0
#define blg 0.0
#define blb 0.0
#define r 1.0
#define g 1.0
#define b 1.0
#define rg 0.0
#define rb 0.0
#define gr 0.0
#define gb 0.0
#define br 0.0
#define bg 0.0
#endif

void main()
{
   vec4 screen = pow(COMPAT_TEXTURE(Source, vTexCoord), vec4(2.2)).rgba;
   vec4 avglum = vec4(0.5);
   screen = mix(screen, avglum, (1.0 - cntrst));

                   //  r    g    b  alpha ; alpha does nothing for our purposes
   mat4 color = mat4(  r,  rg,  rb, 0.0,  //red tint
                      gr,   g,  gb, 0.0,  //green tint
                      br,  bg,   b, 0.0,  //blue tint
                     blr, blg, blb, 0.0); //black tint

   mat4 adjust = mat4((1.0 - sat) * 0.2126 + sat, (1.0 - sat) * 0.2126, (1.0 - sat) * 0.2126, 1.0,
                      (1.0 - sat) * 0.7152, (1.0 - sat) * 0.7152 + sat, (1.0 - sat) * 0.7152, 1.0,
                      (1.0 - sat) * 0.0722, (1.0 - sat) * 0.0722, (1.0 - sat) * 0.0722 + sat, 1.0,
                      0.0, 0.0, 0.0, 1.0);
   color *= adjust;
   screen = clamp(screen * lum, 0.0, 1.0);
   screen = color * screen;
	vec3 out_gamma = vec3(1.) / (vec3(2.2) - vec3(gamma_boost_r, gamma_boost_g, gamma_boost_b));
	FragColor = pow(screen, vec4(out_gamma, 1.0));
}
#endif
2 Likes

Could you possibly do a version of image-adjustment with color-mangler’s rgb setup the mat4 color thing, instead of image-adjustment’s rgb?

Hunter made the shader so color shifting is done very carefully (0.5% increases per button press), which makes sense. But I need faster changes within retroarch, so I modified the shader a bit. If anyone else needs that speed (5% increases) just paste these over the original lines.

#pragma parameter r "Red" 1.0 0.0 2.0 0.05
#pragma parameter g "Green" 1.0 0.0 2.0 0.05
#pragma parameter b "Blue" 1.0 0.0 2.0 0.05
#pragma parameter rg "Red-Green Tint" 0.0 0.0 1.0 0.05
#pragma parameter rb "Red-Blue Tint" 0.0 0.0 1.0 0.05
#pragma parameter gr "Green-Red Tint" 0.0 0.0 1.0 0.05
#pragma parameter gb "Green-Blue Tint" 0.0 0.0 1.0 0.05
#pragma parameter br "Blue-Red Tint" 0.0 0.0 1.0 0.05
#pragma parameter bg "Blue-Green Tint" 0.0 0.0 1.0 0.05
#pragma parameter blr "Black-Red Tint" 0.0 0.0 1.0 0.05
#pragma parameter blg "Black-Green Tint" 0.0 0.0 1.0 0.05
#pragma parameter blb "Black-Blue Tint" 0.0 0.0 1.0 0.05

Also @hunterk , the color sliders are not working, at least in the glsl version. Gamma, sat, lum, cntrst do, but not color. edit: yeah they work now

1 Like

If you really want a cool temperature. You’d really need a device to measure and calibrate the white point of your display. Or a device to measure and create a LUT to the desired temperature.

Most displays are pretty far off out of the box.

I don’t recall any of my CRTs ever having cool temp. My current CRT is closer to 6500 than 9300. ( Haven’t bothered to try using my calibration devices on this CRT) (This also has a color temp option to and i’ve got it set to warm because i’m used to calibrated 6500 devices and SRGB) But really with CRTs I dobut any developers had all calibrated TVs, the colors were made to look right to each individual artist’s eye based on whatever they were using back then to create their art. And I bet a tiny fraction of the populace had calibrated consumer TVs.

You are just better off just leaving it as is or just making it look however you think is correct. I don’t think a “Correct” look with colors, old video games and CRTs. Films, maybe. But games , no.

Far off from “Correct” when I played ATLIII I wanted to increase the render resolution but try to retain some of that 240p’ish look for the 2D elements. What I ended up with looked pretty good to my eyes.

2 Likes

oops, shit. I wiped out a line accidentally. I edited the post to correct it. Let me know if that doesn’t fix it.

pretty much this, but apparently NTSC-J used a temp of 9300K instead of 6500K. I have to say, it looked nice on my CRT monitor after my eyes adjusted. I just haven’t been able to get a cool temp to look good on my LCD, even by shifting green to blue as suggested. I think it might be because I’m using some shader settings that significantly darken the screen, so shifting green to blue just makes everything look dim and washed out.

I’d like to see some close ups of that CRT showing the mask/phosphor structure :smiley:

Before going through the images, let me explain what you will be looking at:

  1. A crop of the image that @BONKERS posted, from his own crt, set to ‘warm’ (if it wasn’t, the differences would be even more obvious!).

  2. Same crop with corrected white balance in order to get rid of any casts produced by his camera. And no, I didn’t do it to my personal taste or anything random like that. I used this method, which is extremely accurate. Whenever I must make sure that my white balance is on spot, that’s what I do.

  3. Genesis Plus’ default output (which is the same as a real Mega Drive’s RGB, afaik).

  4. An attempt to replicate the crt colours with shaders. It took a while but I think I almost nailed it. Not only useful to further prove points and test ideas, but also I like that crt, it looks great and I think it’s a good reference : ) And I guess I don’t need to state how different it looks from the original emu capture.

Now some observations:

  1. I’m glad you chose the Mega Drive because its very limited palette (even for its time) makes these things very easy to spot, even for the untrained eye. Also Green Hill is a great place for an exercise like this, for it contains intense basic tones: reds (rocks), greens (vegetation, grass), blues (skies and water) and whites (clouds).

  2. The camera’s white balance settings added quite a bit of red to the image. No big deal, but I had to fix it to be sure, since I’m not a cyborg (and it was actually a pretty good capture to start with, I must say).

  3. RED: crt rocks are darker than emu. Emu rocks not only seem brighter but also definitely contain more yellow than they should (that’s why they look so orange). CRT wins. Needed some color+gamma correction.

  4. GREEN: crt grass is way cooler than emu grass, which looks very yellow and oversaturated (especially against the white balanced shot). I find the former much more natural and pleasing. CRT wins. Needed a lot of color correction + some gamma.

  5. BLUE: crt water, sky and Sonic himself are much darker in emu. CRT wins. Needed a lot of gamma correction.

  6. WHITE: crt clouds look white, as they should. Emu clouds have this dirty warm cast. Also the detail in them is barely visible, probably because they are digitally clipping. CRT wins yet again, and those clouds were in fact hard to fix without screwing everything else. Needed some color+gamma correction.

Phew I guess that will be enough for now. If anyone is interested I can of course tell you what I learnt from this, and post settings.

3 Likes

I’d be interested knowing what you found out, plus those settings would be nice.

Hi, your crt-guest.r-dr.Venom is marvelous thanks. Is their a way to disable the curvature ? I don’t see any setting about this in parameters. thanks

1 Like

if it helps, I took a bunch of pics awhile back of my NEC XM29+ showing a calibration card next to a piece of white copy paper:

2 Likes

Hey there, glad you like the shader. :relaxed: Please make sure you using the standard version from the repo. Updating your GLSL shaders should fix this. Otherwise it’s “fixed” by setting the parameters:

CurvatureX
CurvatureY

to 0.0.

2 Likes

Cool thanks. will try with the repo version thanks

so I test the official making an update of shaders throw retroarch but the shader doesn’t apply. weird. only work with your shaders available on your previous link. is the official of the online updater is broken ? regards

Shouldn’t be, but if you can get a log of the error and post it here, I can take a look.

Ok thanks I will try tomorow to post it after work

How would you describe the method, here? Just shift green to blue one step at a time until it looks right?

I made a shader that should be pretty close to an actual white point adjustment, whereas the function in crt-guest is essentially just shifting the image toward blue (that is, it uses a RGB-to-XYZ colorspace conversion matrix, which makes the image blue and then blends that with the normal image). My shader is slang only for now but if it’s worth making a GLSL version I will.

#version 450

// white point adjustment
// by hunterk
// based on blog post by Tanner Helland
// http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
   float temperature;
   float luma_preserve;
} params;

#pragma parameter temperature "White Point" 6500.0 0.0 12000.0 100.0
#pragma parameter luma_preserve "Preserve Luminance" 1.0 0.0 1.0 1.0

layout(std140, set = 0, binding = 0) uniform UBO
{
	mat4 MVP;
} global;

#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;

// white point adjustment
// based on blog post by Tanner Helland
// http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
vec3 wp_adjust(vec3 color){
   float temp = params.temperature / 100.0;
   
   // all calculations assume a scale of 255. We'll normalize this at the end
   vec3 wp = vec3(255.);
   
   // calculate RED
   wp.r = (temp <= 66.) ? 255. : 329.698727446 * pow((temp - 60.), -0.1332047592);
   
   // calculate GREEN
   wp.g = (temp <= 66.) ? 99.4708025861 * log(temp) - 161.1195681661 : 288.1221695283 * pow((temp - 60.), -0.0755148492);
   
   // calculate BLUE
   wp.b = (temp >= 66.) ? 255. : (temp <= 19.) ? 0. : 138.5177312231 * log(temp - 10.) - 305.0447927307;
   
   // clamp and normalize
   wp.rgb = clamp(wp.rgb, vec3(0.), vec3(255.)) / vec3(255.);
   
   return (color * wp);
}

float luma(vec3 col){
   return dot(col, vec3(0.2126, 0.7152, 0.0722));
}

    vec3 RGBtoYIQ(vec3 RGB)
  {
     const mat3x3 m = mat3x3(
     0.2989, 0.5870, 0.1140,
     0.5959, -0.2744, -0.3216,
     0.2115, -0.5229, 0.3114);
     return RGB * m;
  }

vec3 YIQtoRGB(vec3 YIQ)
  {
     const mat3x3 m = mat3x3(
     1.0, 0.956, 0.6210,
     1.0, -0.2720, -0.6474,
     1.0, -1.1060, 1.7046);
   return YIQ * m;
}

void main()
{
   vec3 original = texture(Source, vTexCoord).rgb;
   vec3 adjusted = wp_adjust(original);
   vec3 base_luma = RGBtoYIQ(original);
   vec3 adjusted_luma = RGBtoYIQ(adjusted);
   adjusted = (params.luma_preserve > 0.5) ? adjusted_luma + (vec3(base_luma.r,0.,0.) - vec3(adjusted_luma.r,0.,0.)) : adjusted_luma;
   FragColor = vec4(YIQtoRGB(adjusted), 1.0);
}

Just multiplying the original color by the new white point significantly darkens the image the further you get from the base 6500K, so I included an option (default on) to boost the adjusted luminance to match the original, raw luminance of each pixel. This, of course, throws off the perceptual color balance, but I think it looks a lot better with than without (i.e., lesser of two evils). (EDIT: make some quick edits to no longer need the colorspace-tools)

EDIT: okay, here’s a GLSL version:

// white point adjustment
// by hunterk
// based on blog post by Tanner Helland
// http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/

#pragma parameter temperature "White Point" 6500.0 0.0 12000.0 100.0
#pragma parameter luma_preserve "Preserve Luminance" 1.0 0.0 1.0 1.0

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

vec4 _oPosition1; 
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

// compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    TEX0.xy = TexCoord.xy;
}

#elif defined(FRAGMENT)

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out COMPAT_PRECISION vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;
// in variables go here as COMPAT_VARYING whatever

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float temperature, luma_preserve;
#else
#define temperature 6500.0
#define luma_preserve 1.0
#endif

// white point adjustment
// based on blog post by Tanner Helland
// http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
vec3 wp_adjust(vec3 color){
   float temp = temperature / 100.0;
   
   // all calculations assume a scale of 255. We'll normalize this at the end
   vec3 wp = vec3(255.);
   
   // calculate RED
   wp.r = (temp <= 66.) ? 255. : 329.698727446 * pow((temp - 60.), -0.1332047592);
   
   // calculate GREEN
   wp.g = (temp <= 66.) ? 99.4708025861 * log(temp) - 161.1195681661 : 288.1221695283 * pow((temp - 60.), -0.0755148492);
   
   // calculate BLUE
   wp.b = (temp >= 66.) ? 255. : (temp <= 19.) ? 0. : 138.5177312231 * log(temp - 10.) - 305.0447927307;
   
   // clamp and normalize
   wp.rgb = clamp(wp.rgb, vec3(0.), vec3(255.)) / vec3(255.);
   
   return (color * wp);
}

vec3 RGBtoYIQ(vec3 RGB){
   const mat3x3 m = mat3x3(
   0.2989, 0.5870, 0.1140,
   0.5959, -0.2744, -0.3216,
   0.2115, -0.5229, 0.3114);
   return RGB * m;
}

vec3 YIQtoRGB(vec3 YIQ){
   const mat3x3 m = mat3x3(
   1.0, 0.956, 0.6210,
   1.0, -0.2720, -0.6474,
   1.0, -1.1060, 1.7046);
   return YIQ * m;
}

void main()
{
   vec3 original = COMPAT_TEXTURE(Source, vTexCoord).rgb;
   vec3 adjusted = wp_adjust(original);
   vec3 base_luma = RGBtoYIQ(original);
   vec3 adjusted_luma = RGBtoYIQ(adjusted);
   adjusted = (luma_preserve > 0.5) ? adjusted_luma + (vec3(base_luma.r,0.,0.) - vec3(adjusted_luma.r,0.,0.)) : adjusted_luma;
   FragColor = vec4(YIQtoRGB(adjusted), 1.0);
} 
#endif
6 Likes

@guest.r, could you add the ntsc-256 and ntsc-320 in your shader? I like your shader for Arcade (the best) but for console a miss the composite effect.

:slightly_smiling_face:

Thanks!

1 Like