aboutsummaryrefslogtreecommitdiff
path: root/src/shaders
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2026-01-30 11:31:24 +0100
committerRuben Beltran del Rio <jj@r.bdr.sh>2026-01-30 13:05:48 +0100
commit0d4b61993cba37c180ac91e40a0fb362711de8de (patch)
treef41dd68420b51766c1ca6fc1ee2c20dc5df830d1 /src/shaders
Initial implementation
Diffstat (limited to 'src/shaders')
-rw-r--r--src/shaders/hueShift.ts133
1 files changed, 133 insertions, 0 deletions
diff --git a/src/shaders/hueShift.ts b/src/shaders/hueShift.ts
new file mode 100644
index 0000000..1cfe105
--- /dev/null
+++ b/src/shaders/hueShift.ts
@@ -0,0 +1,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);
+}
+`;