PerceptualHDR.glsl 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /*
  2. [configuration]
  3. [OptionRangeFloat]
  4. GUIName = Amplificiation
  5. OptionName = AMPLIFICATION
  6. MinValue = 1.0
  7. MaxValue = 6.0
  8. StepAmount = 0.25
  9. DefaultValue = 2.5
  10. [/configuration]
  11. */
  12. // ICtCP Colorspace as defined by Dolby here:
  13. // https://professional.dolby.com/siteassets/pdfs/ictcp_dolbywhitepaper_v071.pdf
  14. /***** Transfer Function *****/
  15. const float a = 0.17883277;
  16. const float b = 1.0 - 4.0 * a;
  17. const float c = 0.5 - a * log(4.0 * a);
  18. float HLG_f(float x)
  19. {
  20. if (x < 0.0) {
  21. return 0.0;
  22. }
  23. else if (x < 1.0 / 12.0) {
  24. return sqrt(3.0 * x);
  25. }
  26. return a * log(12.0 * x - b) + c;
  27. }
  28. float HLG_inv_f(float x)
  29. {
  30. if (x < 0.0) {
  31. return 0.0;
  32. }
  33. else if (x < 1.0 / 2.0) {
  34. return x * x / 3.0;
  35. }
  36. return (exp((x - c) / a) + b) / 12.0;
  37. }
  38. float4 HLG(float4 lms)
  39. {
  40. return float4(HLG_f(lms.x), HLG_f(lms.y), HLG_f(lms.z), lms.w);
  41. }
  42. float4 HLG_inv(float4 lms)
  43. {
  44. return float4(HLG_inv_f(lms.x), HLG_inv_f(lms.y), HLG_inv_f(lms.z), lms.w);
  45. }
  46. /***** Linear <--> ICtCp *****/
  47. const mat4 RGBtoLMS = mat4(
  48. 1688.0, 683.0, 99.0, 0.0,
  49. 2146.0, 2951.0, 309.0, 0.0,
  50. 262.0, 462.0, 3688.0, 0.0,
  51. 0.0, 0.0, 0.0, 4096.0)
  52. / 4096.0;
  53. const mat4 LMStoICtCp = mat4(
  54. +2048.0, +3625.0, +9500.0, 0.0,
  55. +2048.0, -7465.0, -9212.0, 0.0,
  56. +0.0, +3840.0, -288.0, 0.0,
  57. +0.0, +0.0, +0.0, 4096.0)
  58. / 4096.0;
  59. float4 LinearRGBToICtCP(float4 c)
  60. {
  61. return LMStoICtCp * HLG(RGBtoLMS * c);
  62. }
  63. /***** ICtCp <--> Linear *****/
  64. mat4 ICtCptoLMS = inverse(LMStoICtCp);
  65. mat4 LMStoRGB = inverse(RGBtoLMS);
  66. float4 ICtCpToLinearRGB(float4 c)
  67. {
  68. return LMStoRGB * HLG_inv(ICtCptoLMS * c);
  69. }
  70. void main()
  71. {
  72. float4 color = Sample();
  73. // Nothing to do here, we are in SDR
  74. if (!OptionEnabled(hdr_output) || !OptionEnabled(linear_space_output)) {
  75. SetOutput(color);
  76. return;
  77. }
  78. // Renormalize Color to be in [0.0 - 1.0] SDR Space. We will revert this later.
  79. const float hdr_paper_white = hdr_paper_white_nits / hdr_sdr_white_nits;
  80. color.rgb /= hdr_paper_white;
  81. // Convert Color to Perceptual Color Space. This will allow us to do perceptual
  82. // scaling while also being able to use the luminance channel.
  83. float4 ictcp_color = LinearRGBToICtCP(color);
  84. // Scale the color in perceptual space depending on the percieved luminance.
  85. //
  86. // At low luminances, ~0.0, pow(AMPLIFICATION, ~0.0) ~= 1.0, so the
  87. // color will appear to be unchanged. This is important as we don't want to
  88. // over expose dark colors which would not have otherwise been seen.
  89. //
  90. // At high luminances, ~1.0, pow(AMPLIFICATION, ~1.0) ~= AMPLIFICATION,
  91. // which is equivilant to scaling the color by AMPLIFICATION. This is
  92. // important as we want to get the most out of the display, and we want to
  93. // get bright colors to hit their target brightness.
  94. //
  95. // For more information, see this desmos demonstrating this scaling process:
  96. // https://www.desmos.com/calculator/syjyrjsj5c
  97. float exposure = length(ictcp_color.xyz);
  98. ictcp_color *= pow(HLG_f(AMPLIFICATION), exposure);
  99. // Convert back to Linear RGB and output the color to the display.
  100. // We use hdr_paper_white to renormalize the color to the comfortable
  101. // SDR viewing range.
  102. SetOutput(hdr_paper_white * ICtCpToLinearRGB(ictcp_color));
  103. }