aboutsummaryrefslogtreecommitdiff
path: root/src/shaders/hueShift.ts
blob: 1cfe10510c211681a1160119990f96da33a2e1b4 (plain)
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
export const hueShiftVertexShader = `
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;

void main() {
  vUv = uv;
  vNormal = normalize(normalMatrix * normal);
  vPosition = position;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;

export const hueShiftFragmentShader = `
uniform sampler2D map;
uniform bool hasMap;
uniform vec3 baseColor;
uniform float hueShiftRed;
uniform float hueShiftGreen;
uniform float hueShiftBlue;

varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;

vec3 rgb2hsl(vec3 color) {
  float maxC = max(max(color.r, color.g), color.b);
  float minC = min(min(color.r, color.g), color.b);
  float l = (maxC + minC) / 2.0;

  if (maxC == minC) {
    return vec3(0.0, 0.0, l);
  }

  float d = maxC - minC;
  float s = l > 0.5 ? d / (2.0 - maxC - minC) : d / (maxC + minC);
  float h;

  if (maxC == color.r) {
    h = (color.g - color.b) / d + (color.g < color.b ? 6.0 : 0.0);
  } else if (maxC == color.g) {
    h = (color.b - color.r) / d + 2.0;
  } else {
    h = (color.r - color.g) / d + 4.0;
  }

  h /= 6.0;
  return vec3(h, s, l);
}

float hue2rgb(float p, float q, float t) {
  if (t < 0.0) t += 1.0;
  if (t > 1.0) t -= 1.0;
  if (t < 1.0/6.0) return p + (q - p) * 6.0 * t;
  if (t < 1.0/2.0) return q;
  if (t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0;
  return p;
}

vec3 hsl2rgb(vec3 hsl) {
  if (hsl.y == 0.0) {
    return vec3(hsl.z);
  }

  float q = hsl.z < 0.5 ? hsl.z * (1.0 + hsl.y) : hsl.z + hsl.y - hsl.z * hsl.y;
  float p = 2.0 * hsl.z - q;

  return vec3(
    hue2rgb(p, q, hsl.x + 1.0/3.0),
    hue2rgb(p, q, hsl.x),
    hue2rgb(p, q, hsl.x - 1.0/3.0)
  );
}

vec3 applyHueShift(vec3 color) {
  // Determine dominant color channel
  float maxChannel = max(max(color.r, color.g), color.b);
  float threshold = 0.1;

  vec3 hsl = rgb2hsl(color);
  float shift = 0.0;

  // Apply shift based on dominant color
  if (color.r > color.g + threshold && color.r > color.b + threshold) {
    // Red dominant
    shift = hueShiftRed;
  } else if (color.g > color.r + threshold && color.g > color.b + threshold) {
    // Green dominant
    shift = hueShiftGreen;
  } else if (color.b > color.r + threshold && color.b > color.g + threshold) {
    // Blue dominant
    shift = hueShiftBlue;
  } else {
    // Mixed - apply weighted average
    float total = color.r + color.g + color.b;
    if (total > 0.0) {
      shift = (color.r * hueShiftRed + color.g * hueShiftGreen + color.b * hueShiftBlue) / total;
    }
  }

  hsl.x = mod(hsl.x + shift, 1.0);
  return hsl2rgb(hsl);
}

void main() {
  vec3 color;

  if (hasMap) {
    color = texture2D(map, vUv).rgb;
  } else {
    color = baseColor;
  }

  // Apply hue shift
  color = applyHueShift(color);

  // Bright lighting from multiple directions
  vec3 lightDir1 = normalize(vec3(1.0, 1.0, 1.0));
  vec3 lightDir2 = normalize(vec3(-1.0, 0.5, -0.5));
  vec3 lightDir3 = normalize(vec3(0.0, -1.0, 0.0));

  float diff1 = max(dot(vNormal, lightDir1), 0.0);
  float diff2 = max(dot(vNormal, lightDir2), 0.0) * 0.5;
  float diff3 = max(dot(vNormal, lightDir3), 0.0) * 0.3;

  float ambient = 0.7;
  float diffuse = diff1 + diff2 + diff3;

  vec3 finalColor = color * (ambient + diffuse * 0.5);

  gl_FragColor = vec4(finalColor, 1.0);
}
`;