Question regarding white point shader

I didn’t read the blog post because it was mostly over my head, but does this shader assume that your monitor is already calibrated to a particular temperature?

1 Like

It assumes 6500, yeah, but I think it’s not 100% perfect anyway. If you do 240p test suite’s white screen test, it won’t look exactly white all the time (some cores seemed to be white, some weren’t). Dunno what’s up with that.

2 Likes

Just as I thought; using the white point shader set to 9300k looks much better than the preset cool mode on my monitor. Very useful!

2 Likes

Where can I read the blog post? I searched here and in hunterk’s blog

if you open the shader file, I think there’s a link in the description

1 Like

Ah yes, I had read it before, very cool. I’m just not sure on the HSL note, it’s a bad model for detaching luminance and chromaticities, Yxy space being a better alternative.

So reading the comments section in the link I adapted the shader to use a better approach described here.

I also implemented the Yxy space which is a better mathematical approach for luminance-only operations, works better for moderate temperature changes but worse (high saturation) at extreme temperatures. In any case I also added a Strength parameter for more control.

It is the first shader I made or adapted so if you find something iffy please correct, or in case the more “accurate” space isn’t suited for the task.

https://pastebin.com/i2cc237t

Reference:

Before 9300K

Update 9300K

Before 3500K

Update 3500K

2 Likes

This seems really interesting @Dogway, I don’t know enough about either things to really weigh -in any on the matter (the code and color space), but I will check this out!

I’d love for some one that has a better understanding of these to weigh-in on this, @hunterk.

Also if it seems like I’m complaining about anything @Dogway, I’m not and I really appreciate this!

EDIT: Could you explain the settings some? I’ve looked over the code and have fairly good idea of what’s going on but I’d just like some clarification, lol.

The only new setting is Strength, it’s like an opacity for the temperature, instead of going to a cooler/warmer version. I put it there in case the saturation is too strong, or in case you like the hue (color) shift but not it’s strength, just for completion. If you think a a channel gets too saturated reduce it with the RGB Shift parameters.

Yes, having a second opinion is always nice, maybe temperature shifts in the analog medium happened more like in the HSL model, despite how inaccurate that model is.

Maybe this leads me to finally recreate the removegrain(22)median blur filter in glsl.

2 Likes

Thanks for the clarification, it helps alot!

Looks good to me! It performs better for reds at cool settings and blues at warm settings, which is nice.

The difference seems to be almost entirely a result of using Yxy instead YIQ, as it becomes just barely perceptible in A/B testing without the ‘preserve luminance’ option enabled (see test shader at the bottom of this post; TEST 0 == old method + YIQ, TEST 1 == new method + Yxy; set pres. lum. option to 0 to isolate the wp calc). That is to say, the improved curve fit only has a small impact, but whatever: it’s better, so may as well use it. OTOH, the Yxy color space is much better than using YIQ for the luma comparison and modification, so that’s a slam dunk.

As mentioned in the second blog post, we should probably cap the minimum temp to 1000K since the data is unreliable below that point (not that anyone would likely ever want to go that low anyway…).

I’m not personally a huge fan of the intensity parameter, since white point either is or it isn’t. That is, if someone thinks it’s too cool, they should lower the temp; if it’s too warm, raise it. The intensity parameter is essentially doing that anyway, just in a way that disconnects the actual temp value from its meaning.

// white point adjustment
// adapted by Dogway (based on original's hunterk shader as template)
//
// based on blog post by Neil Bartlett (inspired on Tanner Helland's work)
// http://www.zombieprototypes.com/?p=210

#pragma parameter temperature "White Point" 6500.0 1000.0 12000.0 100.0
#pragma parameter intensity "Strength" 1.0 0.0 1.0 0.05
#pragma parameter luma_preserve "Preserve Luminance" 1.0 0.0 1.0 1.0
#pragma parameter red "Red Shift" 0.0 -1.0 1.0 0.01
#pragma parameter green "Green Shift" 0.0 -1.0 1.0 0.01
#pragma parameter blue "Blue Shift" 0.0 -1.0 1.0 0.01
#pragma parameter test "TEST" 0.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;

// 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, intensity, luma_preserve, red, green, blue, test;
#else
#define temperature 6500.0
#define intensity 1.0
#define luma_preserve 1.0
#define red 0.0
#define green 0.0
#define blue 0.0
#endif

// white point adjustment
// based on blog post by Neil Bartlett (inspired by Tanner Helland's work)
// http://www.zombieprototypes.com/?p=210
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.);
if(int(test) == 0)
{
   // 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.);
}
else
{   
   // calculate RED
   wp.r = (temp <= 66.) ? 255. : 351.97690566805693 + 0.114206453784165 * (temp - 55.) - 40.25366309332127 * log(temp - 55.);
   
   // calculate GREEN
   float mg = - 155.25485562709179 - 0.44596950469579133 * (temp - 2.)  + 104.49216199393888 * log(temp - 2.);
   float pg =   325.4494125711974  + 0.07943456536662342 * (temp - 50.) - 28.0852963507957   * log(temp - 50.);
   wp.g = (temp <= 66.) ? mg : pg;
   
   // calculate BLUE
   wp.b = (temp >= 66.) ? 255. : (temp <= 19.) ? 0. : - 254.76935184120902 + 0.8274096064007395 * (temp - 10.) + 115.67994401066147 * log(temp - 10.) ;
   
   // clamp and normalize
   wp.rgb = clamp(wp.rgb, vec3(0.), vec3(255.)) / vec3(255.);
}   
   // this is dumb, but various cores don't always show white as white. Use this to make white white...
   wp.rgb += vec3(red, green, blue);
   
   return (color * wp);
}

vec3 sRGB_to_XYZ(vec3 RGB){
	const mat3 m = mat3(
    0.4124564,  0.3575761,  0.1804375,
    0.2126729,  0.7151522,  0.0721750,
    0.0193339,  0.1191920,  0.9503041);
    return RGB * m;
}

vec3 XYZtoYxy(vec3 XYZ){

    float XYZrgb = XYZ.r+XYZ.g+XYZ.b;
    float Yxyr = XYZ.g;
    float Yxyg = (XYZrgb <= 0.0) ? 0.3805 : XYZ.r / XYZrgb;
    float Yxyb = (XYZrgb <= 0.0) ? 0.3769 : XYZ.g / XYZrgb;
    return vec3(Yxyr,Yxyg,Yxyb);

}

vec3 XYZ_to_sRGB(vec3 XYZ){
	const mat3 m = mat3(
    3.2404542, -1.5371385, -0.4985314,
   -0.9692660,  1.8760108,  0.0415560,
    0.0556434, -0.2040259,  1.0572252);
    return XYZ * m;
}

vec3 YxytoXYZ(vec3 Yxy){

    float Xs = Yxy.r * (Yxy.g/Yxy.b);
    float Xsz = (Yxy.r <= 0.0) ? 0. : 1.;
    vec3 XYZ = vec3(Xsz,Xsz,Xsz) * vec3(Xs, Yxy.r, (Xs/Yxy.g)-Xs-Yxy.r);
    return XYZ;
}

vec3 RGBtoYIQ(vec3 RGB){
   const mat3 m = mat3(
   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 mat3 m = mat3(
   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);
   adjusted = mix(original, adjusted, intensity);
	vec3 base_luma, adjusted_luma;
if(int(test) == 1)
{
   base_luma = XYZtoYxy(sRGB_to_XYZ(original));
   adjusted_luma = XYZtoYxy(sRGB_to_XYZ(adjusted));
}
else
{
	base_luma = RGBtoYIQ(original);
	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 = (int(test) == 1) ? vec4(XYZ_to_sRGB(YxytoXYZ(adjusted)), 1.0) : vec4(YIQtoRGB(adjusted), 1.0);
} 
#endif
3 Likes

Yes, it’s ok to remove the Strength parameter, in any case per channel adjustments can still be made if saturation goes too high.

I removed the option, capped minimum to 1000 and some cosmetic changes, after you review it it can be uploaded to repository with an appropriate name if we are going to keep the old one. I did some range and NaN control fixes to the Yxy space conversion based on Linbloom suggestions, but haven’t tested thoroughly.

// white point adjustment
// adapted by Dogway (based on hunterk's original shader as template)
//
// based on blog post by Neil Bartlett (inspired on Tanner Helland's work)
// http://www.zombieprototypes.com/?p=210

#pragma parameter temperature "White Point" 6500.0 1000.0 12000.0 100.0
#pragma parameter luma_preserve "Preserve Luminance" 1.0 0.0 1.0 1.0
#pragma parameter red "Red Shift" 0.0 -1.0 1.0 0.01
#pragma parameter green "Green Shift" 0.0 -1.0 1.0 0.01
#pragma parameter blue "Blue Shift" 0.0 -1.0 1.0 0.01

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

// 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, red, green, blue;
#else
#define temperature 6500.0
#define luma_preserve 1.0
#define red 0.0
#define green 0.0
#define blue 0.0
#endif

// white point adjustment
// based on blog post by Neil Bartlett (inspired by Tanner Helland's work)
// http://www.zombieprototypes.com/?p=210
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. : 351.97690566805693 + 0.114206453784165 * (temp - 55.) - 40.25366309332127 * log(temp - 55.);
   
   // calculate GREEN
   float mg = - 155.25485562709179 - 0.44596950469579133 * (temp - 2.)  + 104.49216199393888 * log(temp - 2.);
   float pg =   325.4494125711974  + 0.07943456536662342 * (temp - 50.) - 28.0852963507957   * log(temp - 50.);
   wp.g = (temp <= 66.) ? mg : pg;
   
   // calculate BLUE
   wp.b = (temp >= 66.) ? 255. : (temp <= 19.) ? 0. : - 254.76935184120902 + 0.8274096064007395 * (temp - 10.) + 115.67994401066147 * log(temp - 10.) ;
   
   // clamp and normalize
   wp.rgb = clamp(wp.rgb, vec3(0.), vec3(255.)) / vec3(255.);
   
   // this is dumb, but various cores don't always show white as white. Use this to make white white...
   wp.rgb += vec3(red, green, blue);
   
   return (color * wp);
}

vec3 sRGB_to_XYZ(vec3 RGB){

    const mat3x3 m = mat3x3(
    0.4124564,  0.3575761,  0.1804375,
    0.2126729,  0.7151522,  0.0721750,
    0.0193339,  0.1191920,  0.9503041);
    return RGB * m;
}


vec3 XYZtoYxy(vec3 XYZ){

    float XYZrgb = XYZ.r+XYZ.g+XYZ.b;
    float Yxyr = XYZ.g;
    float Yxyg = (XYZrgb <= 0.0) ? 0.3805 : XYZ.r / XYZrgb;
    float Yxyb = (XYZrgb <= 0.0) ? 0.3769 : XYZ.g / XYZrgb;
    return vec3(Yxyr,Yxyg,Yxyb);
}

vec3 XYZ_to_sRGB(vec3 XYZ){

    const mat3x3 m = mat3x3(
    3.2404542, -1.5371385, -0.4985314,
   -0.9692660,  1.8760108,  0.0415560,
    0.0556434, -0.2040259,  1.0572252);
    return XYZ * m;
}


vec3 YxytoXYZ(vec3 Yxy){

    float Xs = Yxy.r * (Yxy.g/Yxy.b);
    float Xsz = (Yxy.r <= 0.0) ? 0 : 1;
    vec3 XYZ = vec3(Xsz,Xsz,Xsz) * vec3(Xs, Yxy.r, (Xs/Yxy.g)-Xs-Yxy.r);
    return XYZ;
}


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

nah, this looks good. I replaced the old one with it altogether.

3 Likes