|
|
@@ -1,9 +1,14 @@
|
|
|
#version 330 core
|
|
|
|
|
|
+// ============================================================================
|
|
|
+// CARTHAGINIAN HORSE ARCHER - Rich Brown Leather & Team-Colored Cloak
|
|
|
+// Unique earth-tone palette with vibrant team colors
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
in vec3 v_normal;
|
|
|
in vec2 v_texCoord;
|
|
|
in vec3 v_worldPos;
|
|
|
-in float v_armorLayer; // Armor layer from vertex shader
|
|
|
+in float v_armorLayer;
|
|
|
|
|
|
uniform sampler2D u_texture;
|
|
|
uniform vec3 u_color;
|
|
|
@@ -13,15 +18,26 @@ uniform int u_materialId;
|
|
|
|
|
|
out vec4 FragColor;
|
|
|
|
|
|
-// ---------------------
|
|
|
-// utilities & noise
|
|
|
-// ---------------------
|
|
|
+// ============================================================================
|
|
|
+// CONSTANTS & HELPERS
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
const float PI = 3.14159265359;
|
|
|
|
|
|
+// *** Core bases
|
|
|
+const vec3 LEATHER_BASE_BROWN = vec3(0.36, 0.22, 0.10);
|
|
|
+const vec3 BRONZE_BASE_COLOR = vec3(0.86, 0.66, 0.36);
|
|
|
+const vec3 ARCHER_SKIN_BASE = vec3(0.08, 0.07, 0.065);
|
|
|
+
|
|
|
float saturate(float x) { return clamp(x, 0.0, 1.0); }
|
|
|
-vec3 saturate(vec3 x) { return clamp(x, 0.0, 1.0); }
|
|
|
+vec3 saturate3(vec3 x) { return clamp(x, 0.0, 1.0); }
|
|
|
|
|
|
-float hash(vec2 p) {
|
|
|
+vec3 boostSaturation(vec3 color, float amount) {
|
|
|
+ float grey = dot(color, vec3(0.299, 0.587, 0.114));
|
|
|
+ return mix(vec3(grey), color, 1.0 + amount);
|
|
|
+}
|
|
|
+
|
|
|
+float hash2(vec2 p) {
|
|
|
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
|
p3 += dot(p3, p3.yzx + 33.33);
|
|
|
return fract((p3.x + p3.y) * p3.z);
|
|
|
@@ -31,408 +47,530 @@ float noise(vec2 p) {
|
|
|
vec2 i = floor(p);
|
|
|
vec2 f = fract(p);
|
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
|
- float a = hash(i);
|
|
|
- float b = hash(i + vec2(1.0, 0.0));
|
|
|
- float c = hash(i + vec2(0.0, 1.0));
|
|
|
- float d = hash(i + vec2(1.0, 1.0));
|
|
|
+ float a = hash2(i);
|
|
|
+ float b = hash2(i + vec2(1.0, 0.0));
|
|
|
+ float c = hash2(i + vec2(0.0, 1.0));
|
|
|
+ float d = hash2(i + vec2(1.0, 1.0));
|
|
|
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
|
}
|
|
|
|
|
|
float fbm(vec2 p) {
|
|
|
+ float v = 0.0;
|
|
|
float a = 0.5;
|
|
|
- float f = 0.0;
|
|
|
+ mat2 rot = mat2(0.87, 0.50, -0.50, 0.87);
|
|
|
for (int i = 0; i < 5; ++i) {
|
|
|
- f += a * noise(p);
|
|
|
- p *= 2.03;
|
|
|
+ v += a * noise(p);
|
|
|
+ p = rot * p * 2.0 + vec2(100.0);
|
|
|
a *= 0.5;
|
|
|
}
|
|
|
- return f;
|
|
|
+ return v;
|
|
|
}
|
|
|
|
|
|
-// anti-aliased step
|
|
|
-float aa_step(float edge, float x) {
|
|
|
- float w = fwidth(x);
|
|
|
- return smoothstep(edge - w, edge + w, x);
|
|
|
+// ============================================================================
|
|
|
+// PBR FUNCTIONS
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
+float D_GGX(float NdotH, float roughness) {
|
|
|
+ float a = roughness * roughness;
|
|
|
+ float a2 = a * a;
|
|
|
+ float denom = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
|
|
+ return a2 / (PI * denom * denom + 1e-6);
|
|
|
}
|
|
|
|
|
|
-// ---------------------
|
|
|
-// patterns
|
|
|
-// ---------------------
|
|
|
-
|
|
|
-// plate seams + rivets (AA)
|
|
|
-float armor_plates(vec2 p, float y) {
|
|
|
- float plate_y = fract(y * 6.5);
|
|
|
- float line = smoothstep(0.92, 0.98, plate_y) - smoothstep(0.98, 1.0, plate_y);
|
|
|
- // anti-aliased line thickness
|
|
|
- line = smoothstep(0.0, fwidth(plate_y) * 2.0, line) * 0.12;
|
|
|
-
|
|
|
- // rivets on top seams
|
|
|
- float rivet_x = fract(p.x * 18.0);
|
|
|
- float rivet =
|
|
|
- smoothstep(0.48, 0.50, rivet_x) * smoothstep(0.52, 0.50, rivet_x);
|
|
|
- rivet *= step(0.92, plate_y);
|
|
|
- return line + rivet * 0.25;
|
|
|
+float G_SchlickGGX(float NdotX, float roughness) {
|
|
|
+ float r = roughness + 1.0;
|
|
|
+ float k = (r * r) / 8.0;
|
|
|
+ return NdotX / (NdotX * (1.0 - k) + k + 1e-6);
|
|
|
}
|
|
|
|
|
|
-// linked ring suggestion (AA)
|
|
|
-float chainmail_rings(vec2 p) {
|
|
|
- vec2 uv = p * 35.0;
|
|
|
+float G_Smith(float NdotV, float NdotL, float roughness) {
|
|
|
+ return G_SchlickGGX(NdotV, roughness) * G_SchlickGGX(NdotL, roughness);
|
|
|
+}
|
|
|
|
|
|
- vec2 g0 = fract(uv) - 0.5;
|
|
|
- float r0 = length(g0);
|
|
|
- float fw0 = fwidth(r0) * 1.2;
|
|
|
- float ring0 = smoothstep(0.30 + fw0, 0.30 - fw0, r0) -
|
|
|
- smoothstep(0.20 + fw0, 0.20 - fw0, r0);
|
|
|
+vec3 F_Schlick(float cosTheta, vec3 F0) {
|
|
|
+ float t = 1.0 - cosTheta;
|
|
|
+ float t5 = t * t * t * t * t;
|
|
|
+ return F0 + (1.0 - F0) * t5;
|
|
|
+}
|
|
|
|
|
|
- vec2 g1 = fract(uv + vec2(0.5, 0.0)) - 0.5;
|
|
|
- float r1 = length(g1);
|
|
|
- float fw1 = fwidth(r1) * 1.2;
|
|
|
- float ring1 = smoothstep(0.30 + fw1, 0.30 - fw1, r1) -
|
|
|
- smoothstep(0.20 + fw1, 0.20 - fw1, r1);
|
|
|
+// ============================================================================
|
|
|
+// LEATHER TEXTURE - RICH BROWN PALETTE
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
+vec3 brownLeatherPalette(float variation) {
|
|
|
+ vec3 darkBrown = vec3(0.28, 0.18, 0.10);
|
|
|
+ vec3 redBrown = vec3(0.45, 0.25, 0.15);
|
|
|
+ vec3 warmBrown = vec3(0.55, 0.38, 0.22);
|
|
|
+ vec3 lightTan = vec3(0.68, 0.52, 0.35);
|
|
|
+
|
|
|
+ if (variation < 0.33) {
|
|
|
+ return mix(darkBrown, redBrown, variation * 3.0);
|
|
|
+ } else if (variation < 0.66) {
|
|
|
+ return mix(redBrown, warmBrown, (variation - 0.33) * 3.0);
|
|
|
+ } else {
|
|
|
+ return mix(warmBrown, lightTan, (variation - 0.66) * 3.0);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- return (ring0 + ring1) * 0.15;
|
|
|
+float leatherGrain(vec2 uv, float scale) {
|
|
|
+ float coarse = fbm(uv * scale);
|
|
|
+ float medium = fbm(uv * scale * 2.5 + 7.3);
|
|
|
+ float fine = noise(uv * scale * 6.0);
|
|
|
+ float pores = smoothstep(0.55, 0.65, noise(uv * scale * 4.0));
|
|
|
+ return coarse * 0.4 + medium * 0.35 + fine * 0.15 + pores * 0.1;
|
|
|
}
|
|
|
|
|
|
-float horse_hide_pattern(vec2 p) {
|
|
|
- float grain = fbm(p * 80.0) * 0.10;
|
|
|
- float ripple = sin(p.x * 22.0) * cos(p.y * 28.0) * 0.035;
|
|
|
- float mottling = smoothstep(0.55, 0.65, fbm(p * 6.0)) * 0.07;
|
|
|
- return grain + ripple + mottling;
|
|
|
+float stitchPattern(vec2 uv, float spacing) {
|
|
|
+ float stitch = fract(uv.y * spacing);
|
|
|
+ stitch = smoothstep(0.4, 0.5, stitch) * smoothstep(0.6, 0.5, stitch);
|
|
|
+ float seamLine = smoothstep(0.48, 0.50, fract(uv.x * 4.0)) *
|
|
|
+ smoothstep(0.52, 0.50, fract(uv.x * 4.0));
|
|
|
+ return stitch * seamLine;
|
|
|
}
|
|
|
|
|
|
-// ---------------------
|
|
|
-// microfacet shading
|
|
|
-// ---------------------
|
|
|
-vec3 fresnel_schlick(float cos_theta, vec3 F0) {
|
|
|
- return F0 + (1.0 - F0) * pow(1.0 - cos_theta, 5.0);
|
|
|
+float battleWear(vec3 pos) {
|
|
|
+ float scratch1 = smoothstep(0.72, 0.77, noise(pos.xy * 22.0 + pos.z * 4.0));
|
|
|
+ float scratch2 = smoothstep(0.74, 0.79, noise(pos.zy * 18.0 - 2.5));
|
|
|
+ float scuff = fbm(pos.xz * 7.0) * fbm(pos.xy * 10.0);
|
|
|
+ scuff = smoothstep(0.35, 0.55, scuff);
|
|
|
+ return (scratch1 + scratch2) * 0.25 + scuff * 0.35;
|
|
|
}
|
|
|
|
|
|
-float D_GGX(float NdotH, float rough) {
|
|
|
- float a = max(0.001, rough);
|
|
|
- float a2 = a * a;
|
|
|
- float d = (NdotH * NdotH) * (a2 - 1.0) + 1.0;
|
|
|
- return a2 / max(1e-6, (PI * d * d));
|
|
|
+float oilSheen(vec3 pos, vec3 N, vec3 V) {
|
|
|
+ float facing = 1.0 - abs(dot(N, V));
|
|
|
+ float variation = fbm(pos.xz * 12.0) * 0.5 + 0.5;
|
|
|
+ return facing * facing * variation;
|
|
|
}
|
|
|
|
|
|
-float G_Smith(float NdotV, float NdotL, float rough) {
|
|
|
- float r = rough + 1.0;
|
|
|
- float k = (r * r) / 8.0;
|
|
|
- float g_v = NdotV / (NdotV * (1.0 - k) + k);
|
|
|
- float g_l = NdotL / (NdotL * (1.0 - k) + k);
|
|
|
- return g_v * g_l;
|
|
|
+// ============================================================================
|
|
|
+// CLOAK & FABRIC
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
+float cloakFolds(vec3 pos) {
|
|
|
+ float folds = sin(pos.x * 8.0 + pos.y * 3.0) * 0.5 + 0.5;
|
|
|
+ folds += sin(pos.z * 6.0 - pos.y * 2.0) * 0.3;
|
|
|
+ float wind = fbm(pos.xz * 4.0 + pos.y * 2.0);
|
|
|
+ return folds * 0.6 + wind * 0.4;
|
|
|
}
|
|
|
|
|
|
-// screen-space bump from a height field h(uv) in world XZ
|
|
|
-vec3 perturb_normal_ws(vec3 N, vec3 world_pos, float h, float scale) {
|
|
|
- vec3 dpdx = dFdx(world_pos);
|
|
|
- vec3 dpdy = dFdy(world_pos);
|
|
|
- vec3 T = normalize(dpdx);
|
|
|
- vec3 B = normalize(cross(N, T));
|
|
|
- float hx = dFdx(h);
|
|
|
- float hy = dFdy(h);
|
|
|
- vec3 Np = normalize(N - scale * (hx * B + hy * T));
|
|
|
- return Np;
|
|
|
+float fabricWeave(vec2 uv) {
|
|
|
+ float warpX = sin(uv.x * 100.0) * 0.5 + 0.5;
|
|
|
+ float weftY = sin(uv.y * 100.0) * 0.5 + 0.5;
|
|
|
+ return warpX * weftY;
|
|
|
}
|
|
|
|
|
|
-// hemisphere ambient (sky/ground)
|
|
|
-vec3 hemilight(vec3 N) {
|
|
|
- vec3 sky = vec3(0.46, 0.70, 0.82);
|
|
|
- vec3 ground = vec3(0.22, 0.18, 0.14);
|
|
|
- float t = saturate(N.y * 0.5 + 0.5);
|
|
|
- return mix(ground, sky, t) * 0.29;
|
|
|
+// ============================================================================
|
|
|
+// HORSE PATTERNS
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
+float horseCoatPattern(vec2 uv) {
|
|
|
+ float coarse = fbm(uv * 3.0) * 0.15;
|
|
|
+ float fine = noise(uv * 25.0) * 0.08;
|
|
|
+ float dapple = smoothstep(0.4, 0.6, fbm(uv * 5.0)) * 0.1;
|
|
|
+ return coarse + fine + dapple;
|
|
|
}
|
|
|
|
|
|
-// ---------------------
|
|
|
-// main
|
|
|
-// ---------------------
|
|
|
-void main() {
|
|
|
- vec3 base_color = u_color;
|
|
|
- if (u_useTexture)
|
|
|
- base_color *= texture(u_texture, v_texCoord).rgb;
|
|
|
+float furStrand(vec2 uv, float density) {
|
|
|
+ float strand = sin(uv.x * density) * cos(uv.y * density * 0.7);
|
|
|
+ strand = strand * 0.5 + 0.5;
|
|
|
+ float variation = noise(uv * density * 0.5);
|
|
|
+ return strand * 0.3 + variation * 0.2;
|
|
|
+}
|
|
|
|
|
|
- vec3 N = normalize(v_normal);
|
|
|
- vec2 uv = v_worldPos.xz * 5.0;
|
|
|
-
|
|
|
- float avg = (base_color.r + base_color.g + base_color.b) * (1.0 / 3.0);
|
|
|
- float hue_span = max(max(base_color.r, base_color.g), base_color.b) -
|
|
|
- min(min(base_color.r, base_color.g), base_color.b);
|
|
|
-
|
|
|
- // Material ID: 0=rider skin, 1=armor, 2=helmet, 3=weapon, 4=shield,
|
|
|
- // 5=rider clothing, 6=horse hide, 7=horse mane, 8=horse hoof,
|
|
|
- // 9=saddle leather, 10=bridle, 11=saddle blanket,
|
|
|
- // 12=cloak drape, 13=cloak shoulder
|
|
|
- bool is_rider_skin = (u_materialId == 0);
|
|
|
- bool is_armor = (u_materialId == 1);
|
|
|
- bool is_helmet = (u_materialId == 2);
|
|
|
- bool is_weapon = (u_materialId == 3);
|
|
|
- bool is_shield = (u_materialId == 4);
|
|
|
- bool is_rider_clothing = (u_materialId == 5);
|
|
|
- bool is_cloak = (u_materialId == 12 || u_materialId == 13);
|
|
|
- bool is_horse_hide = (u_materialId == 6);
|
|
|
- bool is_horse_mane = (u_materialId == 7);
|
|
|
- bool is_horse_hoof = (u_materialId == 8);
|
|
|
- bool is_saddle_leather = (u_materialId == 9);
|
|
|
- bool is_bridle = (u_materialId == 10);
|
|
|
- bool is_saddle_blanket = (u_materialId == 11);
|
|
|
+// ============================================================================
|
|
|
+// BOW WOOD
|
|
|
+// ============================================================================
|
|
|
|
|
|
- if (is_rider_skin) {
|
|
|
- // Carthage horse archer: dark complexion.
|
|
|
- vec3 target = vec3(0.32, 0.24, 0.18);
|
|
|
- float tone_noise = fbm(v_worldPos.xz * 3.1) - 0.5;
|
|
|
- base_color = clamp(target + vec3(tone_noise) * 0.05, 0.0, 1.0);
|
|
|
- if (u_useTexture) {
|
|
|
- vec3 tex = texture(u_texture, v_texCoord).rgb;
|
|
|
- float eye_mask = step(0.25, 1.0 - dot(tex, vec3(0.299, 0.587, 0.114)));
|
|
|
- base_color = mix(base_color, vec3(0.02), eye_mask); // black eyes
|
|
|
- }
|
|
|
- }
|
|
|
+float woodGrainBow(vec3 pos) {
|
|
|
+ float grain = sin(pos.y * 40.0 + fbm(pos.xy * 8.0) * 3.0);
|
|
|
+ grain = grain * 0.5 + 0.5;
|
|
|
+ float rings = fbm(vec2(pos.x * 20.0, pos.z * 20.0));
|
|
|
+ return grain * 0.7 + rings * 0.3;
|
|
|
+}
|
|
|
|
|
|
- // Material-based detection only (no fallbacks)
|
|
|
- bool is_brass = is_helmet;
|
|
|
- bool is_steel = false;
|
|
|
- bool is_chain = false;
|
|
|
- bool is_fabric = is_rider_clothing || is_saddle_blanket || is_cloak;
|
|
|
- bool is_leather = is_saddle_leather || is_bridle;
|
|
|
+// ============================================================================
|
|
|
+// MAIN
|
|
|
+// ============================================================================
|
|
|
|
|
|
- // Team-tint cloaks while preserving base styling.
|
|
|
- if (is_cloak) {
|
|
|
- base_color = mix(base_color, saturate(u_color), 0.75);
|
|
|
+void main() {
|
|
|
+ vec3 baseColor = clamp(u_color, 0.0, 1.0);
|
|
|
+ if (u_useTexture) {
|
|
|
+ baseColor *= texture(u_texture, v_texCoord).rgb;
|
|
|
}
|
|
|
-
|
|
|
- // lighting frame
|
|
|
- vec3 L = normalize(vec3(1.0, 1.2, 1.0));
|
|
|
- vec3 V = normalize(
|
|
|
- vec3(0.0, 1.0, 0.5)); // stable view proxy (keeps interface unchanged)
|
|
|
+
|
|
|
+ // Material IDs
|
|
|
+ bool is_rider_skin = (u_materialId == 0);
|
|
|
+ bool is_armor = (u_materialId == 1);
|
|
|
+ bool is_helmet = (u_materialId == 2);
|
|
|
+ bool is_weapon = (u_materialId == 3);
|
|
|
+ bool is_shield = (u_materialId == 4);
|
|
|
+ bool is_rider_clothing = (u_materialId == 5);
|
|
|
+ bool is_horse_hide = (u_materialId == 6);
|
|
|
+ bool is_horse_mane = (u_materialId == 7);
|
|
|
+ bool is_horse_hoof = (u_materialId == 8);
|
|
|
+ bool is_saddle_leather = (u_materialId == 9);
|
|
|
+ bool is_bridle = (u_materialId == 10);
|
|
|
+ bool is_saddle_blanket = (u_materialId == 11);
|
|
|
+ bool is_cloak = (u_materialId == 12 || u_materialId == 13);
|
|
|
+
|
|
|
+ vec3 N = normalize(v_normal);
|
|
|
+ vec3 V = normalize(vec3(0.0, 0.5, 1.0));
|
|
|
+ vec3 L = normalize(vec3(0.5, 1.0, 0.4));
|
|
|
vec3 H = normalize(L + V);
|
|
|
-
|
|
|
- float NdotL = saturate(dot(N, L));
|
|
|
- float NdotV = saturate(dot(N, V));
|
|
|
- float NdotH = saturate(dot(N, H));
|
|
|
- float VdotH = saturate(dot(V, H));
|
|
|
-
|
|
|
- // wrap diffuse like original (softens lambert)
|
|
|
- float wrap_amount = (avg > 0.50) ? 0.08 : 0.30;
|
|
|
- float NdotL_wrap = max(NdotL * (1.0 - wrap_amount) + wrap_amount, 0.12);
|
|
|
-
|
|
|
- // base material params
|
|
|
+
|
|
|
+ vec3 albedo = baseColor;
|
|
|
+ float metallic = 0.0;
|
|
|
float roughness = 0.5;
|
|
|
- vec3 F0 = vec3(0.04); // dielectric default
|
|
|
- float metalness = 0.0;
|
|
|
- vec3 albedo = base_color;
|
|
|
-
|
|
|
- // micro details / masks (re-used)
|
|
|
- float n_small = fbm(uv * 6.0);
|
|
|
- float n_large = fbm(uv * 2.0);
|
|
|
- float cavity = 1.0 - (n_large * 0.25 + n_small * 0.15);
|
|
|
-
|
|
|
- // ---------------------
|
|
|
- // MATERIAL BRANCHES
|
|
|
- // ---------------------
|
|
|
- vec3 col = vec3(0.0);
|
|
|
- vec3 ambient = hemilight(N) * (0.85 + 0.15 * cavity);
|
|
|
-
|
|
|
- if (is_armor) {
|
|
|
- // Leather-first mix, consistent with infantry light armor
|
|
|
- float leather_grain = fbm(uv * 12.0) * 0.12;
|
|
|
- float linen_weave = fbm(uv * 6.0) * 0.08;
|
|
|
- float scale_hint = armor_plates(v_worldPos.xz, v_worldPos.y) * 0.35;
|
|
|
-
|
|
|
- float h = fbm(vec2(v_worldPos.y * 22.0, v_worldPos.z * 7.0));
|
|
|
- N = perturb_normal_ws(N, v_worldPos, h, 0.30);
|
|
|
-
|
|
|
- vec3 leather_tint = vec3(0.44, 0.30, 0.19);
|
|
|
- vec3 linen_tint = vec3(0.86, 0.80, 0.72);
|
|
|
- vec3 bronze_tint = vec3(0.62, 0.46, 0.20);
|
|
|
- vec3 chain_tint = vec3(0.78, 0.80, 0.82);
|
|
|
-
|
|
|
- // Treat the entire armor mesh as torso to avoid clipping by height bands.
|
|
|
- float torsoBand = 1.0;
|
|
|
- float skirtBand = 0.0;
|
|
|
- float linenBlend = skirtBand * 0.40;
|
|
|
- float bronzeBlend = torsoBand * 0.45;
|
|
|
- float chainBlend = torsoBand * 0.20;
|
|
|
- float leatherOverlay = skirtBand * 0.90 + torsoBand * 0.30;
|
|
|
- float edge = 1.0 - clamp(dot(N, vec3(0.0, 1.0, 0.0)), 0.0, 1.0);
|
|
|
- vec3 highlight = vec3(0.10, 0.08, 0.05) * smoothstep(0.3, 0.9, edge);
|
|
|
-
|
|
|
- albedo = leather_tint;
|
|
|
- albedo = mix(albedo, linen_tint, linenBlend);
|
|
|
- albedo = mix(albedo, bronze_tint, bronzeBlend);
|
|
|
- albedo = mix(albedo, chain_tint, chainBlend);
|
|
|
- albedo = mix(albedo, leather_tint + highlight, leatherOverlay);
|
|
|
-
|
|
|
- roughness = 0.42 - leather_grain * 0.12 + linen_weave * 0.08;
|
|
|
- roughness = clamp(roughness, 0.26, 0.62);
|
|
|
- F0 = mix(vec3(0.06), bronze_tint, 0.22);
|
|
|
-
|
|
|
- float D = D_GGX(saturate(dot(N, H)), roughness);
|
|
|
- float G = G_Smith(saturate(dot(N, V)), saturate(dot(N, L)), roughness);
|
|
|
- vec3 F = fresnel_schlick(VdotH, F0);
|
|
|
- vec3 spec = (D * G) * F / max(1e-5, 4.0 * NdotV * NdotL);
|
|
|
-
|
|
|
- col += ambient * albedo * 0.70;
|
|
|
- col += NdotL_wrap * (albedo * (1.0 - F) + spec * 0.65);
|
|
|
- col += vec3(scale_hint * 0.4 + leather_grain * 0.2 + linen_weave * 0.15);
|
|
|
-
|
|
|
+ float sheen = 0.0;
|
|
|
+
|
|
|
+ // ========== MATERIAL SETUP ==========
|
|
|
+
|
|
|
+ if (is_rider_skin) {
|
|
|
+ vec3 skinBase = ARCHER_SKIN_BASE;
|
|
|
+ vec3 teamTint = mix(skinBase, baseColor, 0.2);
|
|
|
+ float toneNoise = fbm(v_worldPos.xz * 3.1) - 0.5;
|
|
|
+ albedo = clamp(teamTint + vec3(toneNoise) * 0.04, 0.0, 1.0);
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = 0.58;
|
|
|
+
|
|
|
+ } else if (is_armor) {
|
|
|
+ // ====== RICH BROWN LEATHER ARMOR (UV-based, clearly rough leather) ======
|
|
|
+ vec2 leatherUV = v_texCoord * 6.0; // *** UV-driven
|
|
|
+ float leatherVar = fbm(leatherUV * 2.0);
|
|
|
+ vec3 leatherPalette = brownLeatherPalette(leatherVar);
|
|
|
+
|
|
|
+ // *** Core brown leather base, lightly tinted by team color
|
|
|
+ vec3 tint = mix(vec3(1.0), baseColor, 0.25);
|
|
|
+ vec3 leatherBase = mix(LEATHER_BASE_BROWN, leatherPalette, 0.5);
|
|
|
+ albedo = leatherBase * tint;
|
|
|
+ albedo = boostSaturation(albedo, 0.25);
|
|
|
+
|
|
|
+ float grain = leatherGrain(leatherUV, 8.0); // *** stronger grain
|
|
|
+ float wear = battleWear(v_worldPos);
|
|
|
+ float oil = oilSheen(v_worldPos, N, V);
|
|
|
+ float stitches = stitchPattern(leatherUV, 18.0); // *** visible seams
|
|
|
+
|
|
|
+ // Strong grain contrast
|
|
|
+ float grainStrength = 0.55;
|
|
|
+ albedo = mix(albedo * 0.7, albedo * 1.3, grain * grainStrength);
|
|
|
+
|
|
|
+ // Darker creases / lighter raised areas
|
|
|
+ float depth = fbm(leatherUV * 3.0);
|
|
|
+ albedo = mix(albedo * 0.7, albedo * 1.15, depth);
|
|
|
+
|
|
|
+ // Worn edges lighter & slightly desaturated
|
|
|
+ vec3 wornColor = albedo * 1.3 + vec3(0.1, 0.08, 0.05);
|
|
|
+ wornColor = mix(wornColor, vec3(dot(wornColor, vec3(0.3, 0.59, 0.11))), 0.15);
|
|
|
+ albedo = mix(albedo, wornColor, wear * 0.6);
|
|
|
+
|
|
|
+ // Stitch darkening
|
|
|
+ albedo = mix(albedo, albedo * 0.55, stitches * 0.9);
|
|
|
+
|
|
|
+ // Leather straps (keep existing pattern)
|
|
|
+ float straps = smoothstep(0.45, 0.48, fract(v_worldPos.x * 6.0)) *
|
|
|
+ smoothstep(0.55, 0.52, fract(v_worldPos.x * 6.0));
|
|
|
+ vec3 strapColor = brownLeatherPalette(0.15);
|
|
|
+ albedo = mix(albedo, strapColor, straps * 0.6);
|
|
|
+
|
|
|
+ // *** Fully leather-like roughness
|
|
|
+ metallic = 0.0;
|
|
|
+ float baseRough = 0.85;
|
|
|
+ roughness = baseRough - oil * 0.25 + grain * 0.05;
|
|
|
+ roughness = clamp(roughness, 0.65, 0.9);
|
|
|
+ sheen = oil * 0.6;
|
|
|
+
|
|
|
+ } else if (is_helmet) {
|
|
|
+ // ====== BRONZE-HEAVY HELMET WITH SMALL LEATHER UNDER-CAP ======
|
|
|
+
|
|
|
+ // Leather under-cap near bottom/inside
|
|
|
+ float leatherZone = smoothstep(0.2, 0.0, v_worldPos.y);
|
|
|
+ leatherZone *= smoothstep(0.25, 0.15, abs(v_worldPos.x));
|
|
|
+
|
|
|
+ float helmLeatherVar = fbm(v_worldPos.xz * 6.0);
|
|
|
+ vec3 helmLeather = brownLeatherPalette(helmLeatherVar * 0.6 + 0.2);
|
|
|
+ float helmGrain = leatherGrain(v_worldPos.xz, 12.0);
|
|
|
+ helmLeather *= 0.9 + helmGrain * 0.2;
|
|
|
+
|
|
|
+ // *** Hammered bronze shell
|
|
|
+ vec2 bronzeUV = v_texCoord * 10.0;
|
|
|
+ float bronzeNoise = fbm(bronzeUV * 2.5);
|
|
|
+ float hammer = noise(bronzeUV * 20.0);
|
|
|
+
|
|
|
+ vec3 bronzeColor = BRONZE_BASE_COLOR;
|
|
|
+ vec3 patina = vec3(0.78, 0.82, 0.70);
|
|
|
+ bronzeColor = mix(bronzeColor, patina, bronzeNoise * 0.25);
|
|
|
+ bronzeColor *= 0.9 + hammer * 0.25;
|
|
|
+ bronzeColor = boostSaturation(bronzeColor, 0.2);
|
|
|
+
|
|
|
+ // Extra bright bronze trim on cheek guards and ridge
|
|
|
+ float trimMask = 0.0;
|
|
|
+ trimMask += smoothstep(0.65, 0.8, abs(v_worldPos.x) * 1.8); // sides
|
|
|
+ trimMask += smoothstep(0.8, 0.9, v_worldPos.y); // top
|
|
|
+ trimMask = saturate(trimMask);
|
|
|
+
|
|
|
+ vec3 trimBronze = bronzeColor * 1.25 + vec3(0.06, 0.04, 0.02);
|
|
|
+
|
|
|
+ // Combine bronze + trim, with small leather zone
|
|
|
+ vec3 helmBase = mix(bronzeColor, trimBronze, trimMask * 0.7);
|
|
|
+ albedo = mix(helmLeather, helmBase, 1.0 - leatherZone * 0.7);
|
|
|
+
|
|
|
+ // Strongly metallic bronze, non-metallic leather
|
|
|
+ float bronzeMetal = 0.95;
|
|
|
+ float leatherMetal = 0.0;
|
|
|
+ metallic = mix(bronzeMetal, leatherMetal, leatherZone);
|
|
|
+
|
|
|
+ // Bronze relatively smooth, leather rough
|
|
|
+ float bronzeRough = 0.32 + hammer * 0.05;
|
|
|
+ float leatherRough = 0.7;
|
|
|
+ roughness = mix(bronzeRough, leatherRough, leatherZone);
|
|
|
+
|
|
|
+ sheen = (1.0 - leatherZone) * 0.35;
|
|
|
+
|
|
|
+ } else if (is_weapon) {
|
|
|
+ // ====== BOW ======
|
|
|
+ float h = v_worldPos.y;
|
|
|
+ float grip = smoothstep(0.38, 0.45, h) * smoothstep(0.62, 0.55, h);
|
|
|
+ float limbs = 1.0 - grip;
|
|
|
+ float stringArea = smoothstep(0.92, 1.0, h) + smoothstep(0.08, 0.0, h);
|
|
|
+
|
|
|
+ float woodGrain = woodGrainBow(v_worldPos);
|
|
|
+ vec3 woodLight = vec3(0.72, 0.55, 0.35);
|
|
|
+ vec3 woodDark = vec3(0.48, 0.32, 0.18);
|
|
|
+ vec3 woodColor = mix(woodDark, woodLight, woodGrain);
|
|
|
+ woodColor = boostSaturation(woodColor, 0.2);
|
|
|
+
|
|
|
+ float woodPolish = pow(max(dot(reflect(-V, N), L), 0.0), 24.0);
|
|
|
+
|
|
|
+ float gripGrain = leatherGrain(v_worldPos.xy, 25.0);
|
|
|
+ vec3 gripLeather = brownLeatherPalette(0.25);
|
|
|
+ gripLeather *= 0.9 + gripGrain * 0.2;
|
|
|
+
|
|
|
+ float wrapPattern = sin(v_worldPos.y * 50.0 + v_worldPos.x * 20.0);
|
|
|
+ wrapPattern = smoothstep(-0.2, 0.2, wrapPattern);
|
|
|
+ gripLeather *= 0.9 + wrapPattern * 0.15;
|
|
|
+
|
|
|
+ vec3 stringColor = vec3(0.85, 0.80, 0.72);
|
|
|
+ float stringShine = pow(max(dot(reflect(-V, N), L), 0.0), 48.0);
|
|
|
+
|
|
|
+ albedo = woodColor;
|
|
|
+ albedo = mix(albedo, gripLeather, grip);
|
|
|
+ albedo = mix(albedo, stringColor, stringArea * 0.8);
|
|
|
+
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = mix(0.38, 0.48, grip);
|
|
|
+ roughness = mix(roughness, 0.32, stringArea);
|
|
|
+ sheen = woodPolish * limbs * 0.4 + grip * 0.3 + stringShine * stringArea * 0.3;
|
|
|
+
|
|
|
+ } else if (is_shield) {
|
|
|
+ float leatherVar = fbm(v_worldPos.xz * 5.0);
|
|
|
+ vec3 leatherColor = brownLeatherPalette(leatherVar * 0.7 + 0.15);
|
|
|
+
|
|
|
+ float grain = leatherGrain(v_worldPos.xz, 10.0);
|
|
|
+ leatherColor *= 0.88 + grain * 0.24;
|
|
|
+
|
|
|
+ float dist = length(v_worldPos.xz);
|
|
|
+ float boss = smoothstep(0.15, 0.0, dist);
|
|
|
+ float edgeRim = smoothstep(0.85, 0.95, dist);
|
|
|
+
|
|
|
+ vec3 bronzeColor = vec3(0.85, 0.65, 0.35);
|
|
|
+ bronzeColor = boostSaturation(bronzeColor, 0.25);
|
|
|
+
|
|
|
+ albedo = leatherColor;
|
|
|
+ albedo = mix(albedo, bronzeColor, boss + edgeRim * 0.7);
|
|
|
+
|
|
|
+ metallic = mix(0.0, 0.9, boss + edgeRim * 0.6);
|
|
|
+ roughness = mix(0.48, 0.22, boss);
|
|
|
+ sheen = (1.0 - boss) * (1.0 - edgeRim) * 0.35;
|
|
|
+
|
|
|
+ } else if (is_rider_clothing) {
|
|
|
+ // Linen tunic
|
|
|
+ float weave = fabricWeave(v_worldPos.xy);
|
|
|
+ float threadVar = noise(v_worldPos.xz * 40.0);
|
|
|
+
|
|
|
+ albedo = boostSaturation(baseColor, 0.2);
|
|
|
+ albedo *= 0.92 + weave * 0.08 + threadVar * 0.06;
|
|
|
+
|
|
|
+ float folds = fbm(v_worldPos.xy * 6.0);
|
|
|
+ albedo *= 0.88 + folds * 0.18;
|
|
|
+
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = 0.72;
|
|
|
+ sheen = pow(1.0 - max(dot(N, V), 0.0), 4.0) * 0.12;
|
|
|
+
|
|
|
+ } else if (is_cloak) {
|
|
|
+ // ====== TEAM-COLORED CLOAK ======
|
|
|
+ vec3 teamColor = boostSaturation(baseColor, 0.5);
|
|
|
+ teamColor *= 1.2;
|
|
|
+
|
|
|
+ float folds = cloakFolds(v_worldPos);
|
|
|
+ float weave = fabricWeave(v_worldPos.xy);
|
|
|
+
|
|
|
+ albedo = teamColor;
|
|
|
+ albedo *= 0.75 + folds * 0.5;
|
|
|
+ albedo *= 0.95 + weave * 0.08;
|
|
|
+
|
|
|
+ vec3 shadowTint = mix(teamColor, teamColor * vec3(0.85, 0.9, 1.0), 0.3);
|
|
|
+ albedo = mix(shadowTint, albedo, folds);
|
|
|
+
|
|
|
+ float edgeFray = noise(v_worldPos.xy * 40.0);
|
|
|
+ albedo *= 0.97 + edgeFray * 0.06;
|
|
|
+
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = 0.72;
|
|
|
+ sheen = pow(1.0 - max(dot(N, V), 0.0), 4.0) * 0.25;
|
|
|
+
|
|
|
} else if (is_horse_hide) {
|
|
|
- vec3 coat = vec3(0.36, 0.32, 0.28);
|
|
|
- float hide_tex = horse_hide_pattern(v_worldPos.xz);
|
|
|
- float grain = fbm(v_worldPos.xz * 18.0 + v_worldPos.y * 2.5);
|
|
|
- albedo = coat * (1.0 + hide_tex * 0.06 - grain * 0.04);
|
|
|
-
|
|
|
- roughness = 0.75;
|
|
|
- F0 = vec3(0.02);
|
|
|
- metalness = 0.0;
|
|
|
-
|
|
|
- float h = fbm(v_worldPos.xz * 22.0);
|
|
|
- N = perturb_normal_ws(N, v_worldPos, h, 0.18);
|
|
|
-
|
|
|
- col += ambient * albedo;
|
|
|
- col += NdotL_wrap * albedo * 0.9;
|
|
|
-
|
|
|
+ // ====== HORSE COAT ======
|
|
|
+ float coat = horseCoatPattern(v_worldPos.xz);
|
|
|
+ float fur = furStrand(v_worldPos.xz, 60.0);
|
|
|
+
|
|
|
+ albedo = boostSaturation(baseColor, 0.18);
|
|
|
+ albedo *= 0.9 + coat * 0.15 + fur * 0.1;
|
|
|
+
|
|
|
+ float muscle = fbm(v_worldPos.xy * 4.0);
|
|
|
+ albedo *= 0.95 + muscle * 0.1;
|
|
|
+
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = 0.65;
|
|
|
+ sheen = 0.22;
|
|
|
+
|
|
|
} else if (is_horse_mane) {
|
|
|
- float strand = sin(uv.x * 140.0) * 0.2 + noise(uv * 120.0) * 0.15;
|
|
|
- float clump = noise(uv * 15.0) * 0.4;
|
|
|
- float frizz = fbm(uv * 40.0) * 0.15;
|
|
|
-
|
|
|
- float h = fbm(v_worldPos.xz * 25.0);
|
|
|
- N = perturb_normal_ws(N, v_worldPos, h, 0.18);
|
|
|
-
|
|
|
- albedo = vec3(0.08, 0.07, 0.07) *
|
|
|
- (1.0 + strand * 0.02 + clump * 0.02 + frizz * 0.02);
|
|
|
-
|
|
|
- roughness = 0.70;
|
|
|
- F0 = vec3(0.02);
|
|
|
- metalness = 0.0;
|
|
|
-
|
|
|
- col += ambient * albedo;
|
|
|
- col += NdotL_wrap * albedo * 0.85;
|
|
|
-
|
|
|
- } else if (is_steel) {
|
|
|
- float brushed =
|
|
|
- abs(sin(v_worldPos.y * 95.0)) * 0.02 + noise(uv * 35.0) * 0.015;
|
|
|
- float dents = noise(uv * 8.0) * 0.03;
|
|
|
- float plates = armor_plates(v_worldPos.xz, v_worldPos.y);
|
|
|
-
|
|
|
- // bump from brushing
|
|
|
- float h = fbm(vec2(v_worldPos.y * 25.0, v_worldPos.z * 6.0));
|
|
|
- N = perturb_normal_ws(N, v_worldPos, h, 0.35);
|
|
|
-
|
|
|
- // steel-like params
|
|
|
- metalness = 1.0;
|
|
|
- F0 = vec3(0.92);
|
|
|
- roughness = 0.28 + brushed * 2.0 + dents * 0.6;
|
|
|
- roughness = clamp(roughness, 0.15, 0.55);
|
|
|
-
|
|
|
- // base tint & sky reflection lift
|
|
|
- albedo = mix(vec3(0.60), base_color, 0.25);
|
|
|
- float sky_refl = (N.y * 0.5 + 0.5) * 0.10;
|
|
|
-
|
|
|
- // microfacet spec only for metals
|
|
|
- float D = D_GGX(saturate(dot(N, H)), roughness);
|
|
|
- float G = G_Smith(saturate(dot(N, V)), saturate(dot(N, L)), roughness);
|
|
|
- vec3 F = fresnel_schlick(VdotH, F0 * albedo); // slight tint
|
|
|
- vec3 spec = (D * G) * F / max(1e-5, 4.0 * NdotV * NdotL);
|
|
|
-
|
|
|
- col += ambient * 0.3; // metals rely more on spec
|
|
|
- col += NdotL_wrap * spec * 1.5;
|
|
|
- col += vec3(plates) + vec3(sky_refl) - vec3(dents * 0.25) + vec3(brushed);
|
|
|
-
|
|
|
- } else if (is_brass) {
|
|
|
- float brass_noise = noise(uv * 22.0) * 0.02;
|
|
|
- float patina = fbm(uv * 4.0) * 0.12; // larger-scale patina
|
|
|
-
|
|
|
- // bump from subtle hammering
|
|
|
- float h = fbm(uv * 18.0);
|
|
|
- N = perturb_normal_ws(N, v_worldPos, h, 0.30);
|
|
|
-
|
|
|
- metalness = 1.0;
|
|
|
- vec3 brass_tint = vec3(0.94, 0.78, 0.45);
|
|
|
- F0 = mix(brass_tint, base_color, 0.5);
|
|
|
- roughness = clamp(0.32 + patina * 0.45, 0.18, 0.75);
|
|
|
-
|
|
|
- float D = D_GGX(saturate(dot(N, H)), roughness);
|
|
|
- float G = G_Smith(saturate(dot(N, V)), saturate(dot(N, L)), roughness);
|
|
|
- vec3 F = fresnel_schlick(VdotH, F0);
|
|
|
- vec3 spec = (D * G) * F / max(1e-5, 4.0 * NdotV * NdotL);
|
|
|
-
|
|
|
- col += ambient * 0.25;
|
|
|
- col += NdotL_wrap * spec * 1.35;
|
|
|
- col += vec3(brass_noise) - vec3(patina * 0.35);
|
|
|
-
|
|
|
- } else if (is_chain) {
|
|
|
- float rings = chainmail_rings(v_worldPos.xz);
|
|
|
- float ring_hi = noise(uv * 30.0) * 0.10;
|
|
|
-
|
|
|
- // small pitted bump
|
|
|
- float h = fbm(uv * 35.0);
|
|
|
- N = perturb_normal_ws(N, v_worldPos, h, 0.25);
|
|
|
-
|
|
|
- metalness = 1.0;
|
|
|
- F0 = vec3(0.86);
|
|
|
- roughness = 0.35;
|
|
|
-
|
|
|
- float D = D_GGX(saturate(dot(N, H)), roughness);
|
|
|
- float G = G_Smith(saturate(dot(N, V)), saturate(dot(N, L)), roughness);
|
|
|
- vec3 F = fresnel_schlick(VdotH, F0);
|
|
|
- vec3 spec = (D * G) * F / max(1e-5, 4.0 * NdotV * NdotL);
|
|
|
-
|
|
|
- col += ambient * 0.25;
|
|
|
- col += NdotL_wrap * (spec * (1.2 + rings)) + vec3(ring_hi);
|
|
|
- // slight diffuse damping to keep chainmail darker in cavities
|
|
|
- col *= (0.95 - 0.10 * (1.0 - cavity));
|
|
|
-
|
|
|
- } else if (is_fabric) {
|
|
|
- float weave_x = sin(v_worldPos.x * 70.0);
|
|
|
- float weave_z = sin(v_worldPos.z * 70.0);
|
|
|
- float weave = weave_x * weave_z * 0.04;
|
|
|
- float embroidery = fbm(uv * 6.0) * 0.08;
|
|
|
-
|
|
|
- float h = fbm(uv * 22.0) * 0.7 + weave * 0.6;
|
|
|
- N = perturb_normal_ws(N, v_worldPos, h, 0.35);
|
|
|
-
|
|
|
- roughness = 0.78;
|
|
|
- F0 = vec3(0.035);
|
|
|
- metalness = 0.0;
|
|
|
-
|
|
|
- vec3 F = fresnel_schlick(VdotH, F0);
|
|
|
- float D = D_GGX(saturate(dot(N, H)), roughness);
|
|
|
- float G = G_Smith(saturate(dot(N, V)), saturate(dot(N, L)), roughness);
|
|
|
- vec3 spec = (D * G) * F / max(1e-5, 4.0 * NdotV * NdotL);
|
|
|
-
|
|
|
- float sheen = pow(1.0 - NdotV, 6.0) * 0.10;
|
|
|
- albedo *= 1.0 + fbm(uv * 5.0) * 0.10 - 0.05;
|
|
|
-
|
|
|
- col += ambient * albedo;
|
|
|
- col += NdotL_wrap * (albedo * (1.0 - F) + spec * 0.3) +
|
|
|
- vec3(weave + embroidery + sheen);
|
|
|
-
|
|
|
- } else { // leather
|
|
|
- float grain = fbm(uv * 10.0) * 0.15;
|
|
|
- float wear = fbm(uv * 3.0) * 0.12;
|
|
|
-
|
|
|
- float h = fbm(uv * 18.0);
|
|
|
- N = perturb_normal_ws(N, v_worldPos, h, 0.28);
|
|
|
-
|
|
|
- roughness = 0.58 - wear * 0.15;
|
|
|
- F0 = vec3(0.038);
|
|
|
- metalness = 0.0;
|
|
|
-
|
|
|
- vec3 F = fresnel_schlick(VdotH, F0);
|
|
|
- float D = D_GGX(saturate(dot(N, H)), roughness);
|
|
|
- float G = G_Smith(saturate(dot(N, V)), saturate(dot(N, L)), roughness);
|
|
|
- vec3 spec = (D * G) * F / max(1e-5, 4.0 * NdotV * NdotL);
|
|
|
-
|
|
|
- float sheen = pow(1.0 - NdotV, 4.0) * 0.06;
|
|
|
-
|
|
|
- albedo *= (1.0 + grain - 0.06 + wear * 0.05);
|
|
|
- col += ambient * albedo;
|
|
|
- col += NdotL_wrap * (albedo * (1.0 - F)) + spec * 0.4 + vec3(sheen);
|
|
|
+ float strand = furStrand(v_worldPos.xy, 120.0);
|
|
|
+ float clump = fbm(v_worldPos.xy * 8.0);
|
|
|
+
|
|
|
+ albedo = baseColor * 0.35;
|
|
|
+ albedo = boostSaturation(albedo, 0.15);
|
|
|
+ albedo *= 0.85 + strand * 0.2 + clump * 0.1;
|
|
|
+
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = 0.75;
|
|
|
+ sheen = 0.18;
|
|
|
+
|
|
|
+ } else if (is_horse_hoof) {
|
|
|
+ float grain = fbm(v_worldPos.xy * 20.0);
|
|
|
+
|
|
|
+ albedo = vec3(0.18, 0.15, 0.12);
|
|
|
+ albedo = boostSaturation(albedo, 0.1);
|
|
|
+ albedo *= 0.9 + grain * 0.15;
|
|
|
+
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = 0.45;
|
|
|
+ sheen = 0.28;
|
|
|
+
|
|
|
+ } else if (is_saddle_leather || is_bridle) {
|
|
|
+ // ====== TACK - Rich brown leather ======
|
|
|
+ float leatherVar = fbm(v_worldPos.xy * 5.0);
|
|
|
+ vec3 tackLeather = brownLeatherPalette(leatherVar * 0.5 + 0.3);
|
|
|
+
|
|
|
+ float grain = leatherGrain(v_worldPos.xy, 18.0);
|
|
|
+ float wear = battleWear(v_worldPos) * 0.5;
|
|
|
+ float oil = oilSheen(v_worldPos, N, V);
|
|
|
+
|
|
|
+ albedo = tackLeather;
|
|
|
+ albedo = boostSaturation(albedo, 0.28);
|
|
|
+ albedo *= 0.88 + grain * 0.22;
|
|
|
+
|
|
|
+ vec3 wornColor = albedo * 1.2 + vec3(0.06, 0.04, 0.02);
|
|
|
+ albedo = mix(albedo, wornColor, wear * 0.4);
|
|
|
+
|
|
|
+ if (is_bridle) {
|
|
|
+ float buckle = smoothstep(0.78, 0.82, noise(v_worldPos.xz * 15.0));
|
|
|
+ vec3 brassColor = vec3(0.82, 0.62, 0.32);
|
|
|
+ albedo = mix(albedo, brassColor, buckle * 0.85);
|
|
|
+ metallic = mix(0.0, 0.85, buckle);
|
|
|
+ roughness = mix(0.42, 0.28, buckle);
|
|
|
+ } else {
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = 0.42 - oil * 0.12 + grain * 0.08;
|
|
|
+ }
|
|
|
+
|
|
|
+ sheen = oil * 0.45;
|
|
|
+
|
|
|
+ } else if (is_saddle_blanket) {
|
|
|
+ // Woven wool - can have team color accent
|
|
|
+ float weaveX = sin(v_worldPos.x * 50.0);
|
|
|
+ float weaveZ = sin(v_worldPos.z * 50.0);
|
|
|
+ float weave = weaveX * weaveZ * 0.5 + 0.5;
|
|
|
+ float fuzz = noise(v_worldPos.xz * 80.0);
|
|
|
+
|
|
|
+ vec3 woolBase = vec3(0.45, 0.35, 0.25);
|
|
|
+ vec3 teamStripe = boostSaturation(baseColor, 0.4);
|
|
|
+
|
|
|
+ float stripe = smoothstep(0.35, 0.45, fract(v_worldPos.x * 3.0));
|
|
|
+ stripe *= smoothstep(0.65, 0.55, fract(v_worldPos.x * 3.0));
|
|
|
+
|
|
|
+ albedo = mix(woolBase, teamStripe, stripe * 0.7);
|
|
|
+ albedo = boostSaturation(albedo, 0.2);
|
|
|
+ albedo *= 0.88 + weave * 0.12 + fuzz * 0.06;
|
|
|
+
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = 0.82;
|
|
|
+ sheen = 0.08;
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // Default - leather-tinted
|
|
|
+ float leatherVar = fbm(v_worldPos.xy * 6.0);
|
|
|
+ albedo = mix(brownLeatherPalette(leatherVar), baseColor, 0.4);
|
|
|
+ albedo = boostSaturation(albedo, 0.15);
|
|
|
+ metallic = 0.0;
|
|
|
+ roughness = 0.55;
|
|
|
}
|
|
|
-
|
|
|
- // Apply Carthage-specific tint - more teal/turquoise for visibility
|
|
|
- col = mix(col, vec3(0.20, 0.55, 0.60),
|
|
|
- saturate((base_color.g + base_color.b) * 0.5) * 0.20);
|
|
|
- col = saturate(col);
|
|
|
- FragColor = vec4(col, u_alpha);
|
|
|
+
|
|
|
+ // ========== PBR LIGHTING ==========
|
|
|
+
|
|
|
+ float NdotL = max(dot(N, L), 0.0);
|
|
|
+ float NdotV = max(dot(N, V), 0.001);
|
|
|
+ float NdotH = max(dot(N, H), 0.0);
|
|
|
+ float VdotH = max(dot(V, H), 0.0);
|
|
|
+
|
|
|
+ vec3 F0 = mix(vec3(0.04), albedo, metallic);
|
|
|
+
|
|
|
+ float D = D_GGX(NdotH, max(roughness, 0.01));
|
|
|
+ float G = G_Smith(NdotV, NdotL, roughness);
|
|
|
+ vec3 F = F_Schlick(VdotH, F0);
|
|
|
+ vec3 specular = (D * G * F) / (4.0 * NdotV * NdotL + 0.001);
|
|
|
+
|
|
|
+ vec3 kD = (vec3(1.0) - F) * (1.0 - metallic);
|
|
|
+ vec3 diffuse = kD * albedo / PI;
|
|
|
+
|
|
|
+ vec3 color = (diffuse + specular * 1.6) * NdotL * 2.0;
|
|
|
+
|
|
|
+ // ====== SHEEN EFFECTS ======
|
|
|
+ if (sheen > 0.01) {
|
|
|
+ vec3 R = reflect(-V, N);
|
|
|
+ float sheenSpec = pow(max(dot(R, L), 0.0), 14.0);
|
|
|
+ color += albedo * sheenSpec * sheen * 1.4;
|
|
|
+
|
|
|
+ float edgeSheen = pow(1.0 - NdotV, 3.0);
|
|
|
+ color += vec3(0.92, 0.88, 0.78) * edgeSheen * sheen * 0.35;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ====== METALLIC SHINE (bronze) ======
|
|
|
+ if (metallic > 0.5) {
|
|
|
+ vec3 R = reflect(-V, N);
|
|
|
+ float specPower = 128.0 / (roughness * roughness + 0.001);
|
|
|
+ specPower = min(specPower, 512.0);
|
|
|
+ float mirrorSpec = pow(max(dot(R, L), 0.0), specPower);
|
|
|
+ color += albedo * mirrorSpec * 1.1;
|
|
|
+
|
|
|
+ float hotspot = pow(NdotH, 256.0);
|
|
|
+ color += vec3(1.0) * hotspot * (1.0 - roughness);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ====== WARM AMBIENT ======
|
|
|
+ vec3 ambient = albedo * 0.36;
|
|
|
+
|
|
|
+ float sss = pow(saturate(dot(-N, L)), 2.5) * 0.12;
|
|
|
+ ambient += albedo * vec3(1.1, 0.92, 0.75) * sss;
|
|
|
+
|
|
|
+ float rim = pow(1.0 - NdotV, 3.5);
|
|
|
+ ambient += vec3(0.32, 0.30, 0.26) * rim * 0.22;
|
|
|
+
|
|
|
+ color += ambient;
|
|
|
+
|
|
|
+ // Tone mapping
|
|
|
+ color = color / (color + vec3(0.55));
|
|
|
+ color = pow(color, vec3(0.94));
|
|
|
+
|
|
|
+ FragColor = vec4(saturate3(color), u_alpha);
|
|
|
}
|