GranTurismoTonemapper

3.2k words

简介

Gran Turismo色调映射是一种颜色变换技术,通过改变HSV色彩空间的色调H值并采样色调映射纹理来产生平滑的颜色变化,将颜色从HDR转为LDR

实现步骤

  1. Shader中将输入的RGB颜色空间转换到HSV颜色空间
  2. 改变HSV空间的色调H值,可以通过时间或其他参数进行变化。色调H值的变化范围是0到1
  3. 根据变化的色调H值采样色调映射纹理,得到新的RGB颜色
  4. 将原始RGB颜色和新的RGB颜色进行线性插值,得到最终输出颜色。插值因子由外部设定,控制颜色变换的强度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
float4 Frag(Varyings input) : SV_Target
{
// Lut space
// We use Alexa LogC (El 1000) to store the LUT as it provides a good enough range
// (~58.85666) and is good enough to be stored in fp16 without losing precision in the
// darks
float3 colorLutSpace = GetLutStripValue(input.uv, _Lut_Params);

// Color grade & tonemap
float3 gradedColor = ColorGrade(colorLutSpace);
gradedColor = Tonemap(gradedColor);

return float4(gradedColor, 1.0);
}

_CurveHueVsSat/_CurveSatVsSat/_CurveLumVsSat 参数由 Color Adjustments 提供

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// HSV operations
float satMult;
float3 hsv = RgbToHsv(colorLinear);
{
// Hue Vs Sat
satMult = EvaluateCurve(_CurveHueVsSat, hsv.x) * 2.0;

// Sat Vs Sat
satMult *= EvaluateCurve(_CurveSatVsSat, hsv.y) * 2.0;

// Lum Vs Sat
satMult *= EvaluateCurve(_CurveLumVsSat, Luminance(colorLinear)) * 2.0;

// Hue Shift & Hue Vs Hue
float hue = hsv.x + _HueSatCon.x;
float offset = EvaluateCurve(_CurveHueVsHue, hue) - 0.5;
hue += offset;
hsv.x = RotateHue(hue, 0.0, 1.0);
}
colorLinear = HsvToRgb(hsv);

参考

GT Tonemap

效果

Tonemapping 为 None
Image text

Tonemapping 为 Neutral
Image text

Tonemapping 为 Aces
Image text

Tonemapping 为 GranTurismo
Image text

用的曲线公式最大值不超过1,所以 Bloom 效果无法生效,原先大于1的颜色值被映射到小于1的值,可以调整相关参数

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#ifndef GRAN_TURISMO_LIB
//https://www.desmos.com/calculator/gslcdxvipg?lang=es

#define E 2.718281828459

half W(half x,half e0,half e1){
if(x<=e0){
return 0;
}
if(x>=e1){
return 1;
}
half temp=(x-e0)/(e1-e0);
return temp*temp*(3-2*temp);
}

half H(half x,half e0,half e1){
if(x<=e0){
return 0;
}
if(x>=e1){
return 1;
}
return (x-e0)/(e1-e0);
}

half GranTurismoColor(half x){
half P=1;
half a=1;
half m=0.22;
half l=0.4;
half c=1.33;
half b=0;
half l0=0.312;
half L0=0;
half L1=1;
half Lx=m+a*(x-m);
half Tx=m*(pow(x/m,c))+b;
half S0=0.532;
half S1=0.532;
half C2=2.13675213675;
half Sx=P-(P-S1)*pow(E,-(C2*(x-S0)/P));
half W0x=1-W(x,0,m);
half W2x=H(x,m+l0,m+l0);
half W1x=1-W0x-W2x;
return Tx*W0x+Lx*W1x+Sx*W2x;
}

static const float e = 2.71828;

float W_f(float x,float e0,float e1) {
if (x <= e0)
return 0;
if (x >= e1)
return 1;
float a = (x - e0) / (e1 - e0);
return a * a*(3 - 2 * a);
}
float H_f(float x, float e0, float e1) {
if (x <= e0)
return 0;
if (x >= e1)
return 1;
return (x - e0) / (e1 - e0);
}

float GranTurismoTonemapper(float x) {
float P = 1;
float a = 1;
float m = 0.22;
float l = 0.4;
float c = 1.33;
float b = 0;
float l0 = (P - m)*l / a;
float L0 = m - m / a;
float L1 = m + (1 - m) / a;
float L_x = m + a * (x - m);
float T_x = m * pow(x / m, c) + b;
float S0 = m + l0;
float S1 = m + a * l0;
float C2 = a * P / (P - S1);
float S_x = P - (P - S1)*pow(e,-(C2*(x-S0)/P));
float w0_x = 1 - W_f(x, 0, m);
float w2_x = H_f(x, m + l0, m + l0);
float w1_x = 1 - w0_x - w2_x;
float f_x = T_x * w0_x + L_x * w1_x + S_x * w2_x;
return f_x;
}

half3 GranTurismoTonemap(half3 color){

// half r=GranTurismoTonemapper(color.r);
// half g=GranTurismoTonemapper(color.g);
// half b=GranTurismoTonemapper(color.b);

half r=GranTurismoColor(color.r);
half g=GranTurismoColor(color.g);
half b=GranTurismoColor(color.b);
return half3(r,g,b);
}

#endif