Files
Ascent-of-Man/shaders/simple.frag
T

456 lines
19 KiB
GLSL

#version 400
uniform sampler2D texGrass; // Unit 0
uniform sampler2D texBorder; // Unit 1
uniform sampler2D texRock; // Unit 2 (Stone cliffs)
uniform sampler2D texSand; // Unit 3 (Soil beach)
uniform sampler2D texWater; // Unit 4 (Dynamic ocean)
uniform sampler2D texSnow; // Unit 5 (Texturelabs_Water_155XL snow/ice)
uniform sampler2D texTypeMap; // Unit 6 (Biome type map)
uniform sampler2D texSahara; // Unit 7 (Texturelabs_Soil_133L desert sand)
uniform sampler2D texTundra; // Unit 8 (Texturelabs_Soil_135XL tundra soil)
uniform sampler2D texRedSand; // Unit 9 (Texturelabs_RedSand prairie sand)
uniform sampler2D texRedRock; // Unit 10 (Texturelabs_RedRock canyon cliff)
uniform sampler2D texForest; // Unit 11 (Texturelabs_Forest_seamless woodland floor)
uniform sampler2D texFarm; // Unit 12 (images/farm.jpeg)
uniform int isFarm;
uniform float farmTextureScale;
uniform int farmMappingMode;
uniform int hasRoad;
uniform int roadMask;
uniform int roadType;
uniform float roadRadius;
uniform float roadBorderWidth;
uniform float roadTextureScale;
uniform float roadOpacity;
uniform float roadMeander;
uniform vec3 roadColor;
uniform vec3 roadBorderColor;
uniform float time;
uniform int riverMask;
smooth in vec3 vNormal;
smooth in vec3 vWorldPos;
in vec3 fragVert;
in float z_pos;
uniform vec3 cameraPosition;
uniform mat4 modelMatrix;
in vec2 tilePos;
in vec2 fragTexCoord;
out vec4 finalColor;
struct SimpleDirectionalLight
{
vec3 vColor;
vec3 vDirection;
float fAmbientIntensity;
};
uniform SimpleDirectionalLight sunLight;
vec4 GetDirectionalLightColor(SimpleDirectionalLight dirLight, vec3 norm)
{
float fDiffuseIntensity = max(dot(norm, -dirLight.vDirection), 0.0);
float fMult = dirLight.fAmbientIntensity + fDiffuseIntensity * (1.0 - dirLight.fAmbientIntensity * 0.5);
return vec4(dirLight.vColor * fMult, 1.0);
}
vec4 GetSpecularColor(vec3 worldPos, vec3 eyePos, SimpleDirectionalLight dLight, vec3 norm, float shininess, float intensity)
{
vec3 vReflectedVector = normalize(reflect(dLight.vDirection, norm));
vec3 vVertexToEyeVector = normalize(eyePos - worldPos);
float fSpecularFactor = max(dot(vVertexToEyeVector, vReflectedVector), 0.0);
if (fSpecularFactor > 0.0)
{
fSpecularFactor = pow(fSpecularFactor, shininess);
return vec4(dLight.vColor, 1.0) * intensity * fSpecularFactor;
}
return vec4(0.0);
}
float distToSegment(vec2 p, vec2 a, vec2 b) {
vec2 ba = b - a;
vec2 pa = p - a;
float l2 = dot(ba, ba);
if (l2 <= 1e-6) return length(pa);
float h = clamp(dot(pa, ba) / l2, 0.0, 1.0);
return length(pa - ba * h);
}
const vec2 EDGE_MIDPOINTS[6] = vec2[6](
vec2(-0.375, 0.21650635),
vec2( 0.0, 0.43301270),
vec2( 0.375, 0.21650635),
vec2( 0.375, -0.21650635),
vec2( 0.0, -0.43301270),
vec2(-0.375, -0.21650635)
);
float bezierCurveDist(vec2 p, vec2 p0, vec2 p2) {
vec2 s0 = p0;
vec2 s1 = 0.5625 * p0 + 0.0625 * p2;
vec2 s2 = 0.2500 * p0 + 0.2500 * p2;
vec2 s3 = 0.0625 * p0 + 0.5625 * p2;
vec2 s4 = p2;
float d0 = distToSegment(p, s0, s1);
float d1 = distToSegment(p, s1, s2);
float d2 = distToSegment(p, s2, s3);
float d3 = distToSegment(p, s3, s4);
return min(min(d0, d1), min(d2, d3));
}
float meanderingBezierDist(vec2 p, vec2 p0, vec2 p2, float amp, float s1, float s2) {
vec2 chord = p2 - p0;
float cLen = length(chord);
vec2 perp = (cLen > 1e-4) ? vec2(-chord.y / cLen, chord.x / cLen) : vec2(0.0, 1.0);
// 6-segment meandering curve from p0 (t=0) to p2 (t=1).
// At t=0 and t=1, sin(pi*t) == 0 and sin(2*pi*t) == 0, so endpoints remain locked at p0 and p2!
const float PI_VAL = 3.14159265;
vec2 prevPt = p0;
float minDist = 999.0;
for (int i = 1; i <= 6; ++i) {
float t = float(i) / 6.0;
float omt = 1.0 - t;
// Base quadratic Bezier through (0, 0)
vec2 basePt = (omt * omt) * p0 + (t * t) * p2;
// Asymmetric S-curve + bend wave perpendicular to the road chord
float wave = amp * (0.65 * s1 * sin(PI_VAL * t) + 0.85 * s2 * sin(2.0 * PI_VAL * t));
vec2 curPt = basePt + perp * wave;
minDist = min(minDist, distToSegment(p, prevPt, curPt));
prevPt = curPt;
}
return minDist;
}
float meanderingBranchDist(vec2 p, vec2 pe, vec2 centerHub, float amp, float s) {
vec2 dir = centerHub - pe;
float dLen = length(dir);
vec2 perp = (dLen > 1e-4) ? vec2(-dir.y / dLen, dir.x / dLen) : vec2(0.0, 1.0);
const float PI_VAL = 3.14159265;
vec2 prevPt = pe;
float minDist = 999.0;
for (int i = 1; i <= 4; ++i) {
float t = float(i) / 4.0;
vec2 basePt = mix(pe, centerHub, t);
float wave = amp * 0.65 * s * sin(PI_VAL * t);
vec2 curPt = basePt + perp * wave;
minDist = min(minDist, distToSegment(p, prevPt, curPt));
prevPt = curPt;
}
return minDist;
}
float getRiverDistance(vec2 p, int mask) {
if (mask == 0) return 999.0;
int edges[6];
int count = 0;
for (int e = 0; e < 6; ++e) {
if ((mask & (1 << e)) != 0) {
edges[count] = e;
count++;
}
}
if (count == 2) {
return bezierCurveDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]]);
} else {
float minDist = 999.0;
for (int i = 0; i < count; ++i) {
float d = distToSegment(p, EDGE_MIDPOINTS[edges[i]], vec2(0.0));
minDist = min(minDist, d);
}
return minDist;
}
}
float getRoadDistance(vec2 p, int mask, vec2 tileCenterWorld, float amp) {
// Deterministic per-tile random signs so each tile meanders uniquely
float h1 = fract(sin(dot(tileCenterWorld, vec2(12.9898, 78.233))) * 43758.5453);
float h2 = fract(sin(dot(tileCenterWorld, vec2(39.3468, 11.135))) * 24634.6345);
float s1 = (h1 > 0.5) ? 1.0 : -1.0;
float s2 = (h2 > 0.5) ? 1.0 : -1.0;
int edges[6];
int count = 0;
for (int e = 0; e < 6; ++e) {
if ((mask & (1 << e)) != 0) {
edges[count] = e;
count++;
}
}
if (count == 0) {
// Unconnected road on a single tile: short winding country track at tile center
return meanderingBezierDist(p, vec2(-0.16, -0.06 * s1), vec2(0.16, 0.06 * s1), amp * 0.6, s1, s2);
} else if (count == 1) {
vec2 centerHub = vec2(s1, s2) * (amp * 0.25);
return meanderingBranchDist(p, EDGE_MIDPOINTS[edges[0]], centerHub, amp, s1);
} else if (count == 2) {
return meanderingBezierDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]], amp, s1, s2);
} else {
vec2 centerHub = vec2(s1, s2) * (amp * 0.22);
float minDist = 999.0;
for (int i = 0; i < count; ++i) {
float branchSign = ((edges[i] % 2) == 0) ? s1 : -s2;
minDist = min(minDist, meanderingBranchDist(p, EDGE_MIDPOINTS[edges[i]], centerHub, amp * 0.75, branchSign));
for (int j = i + 1; j < count; ++j) {
minDist = min(minDist, meanderingBezierDist(p, EDGE_MIDPOINTS[edges[i]], EDGE_MIDPOINTS[edges[j]], amp * 0.65, s1, s2));
}
}
return minDist;
}
}
void main() {
vec3 norm = normalize(vNormal);
// 1. Biome Typemap Sampling
// World extent: [0..225.0 km, 0..108.22 km]
// Note: QImage row 0 is North (worldY = 108.22), which QOpenGLTexture maps to v = 0.0.
// Invert Y so North samples row 0 and South samples row 1499.
vec2 uvType = vec2(clamp(vWorldPos.x / 225.0, 0.0, 1.0), 1.0 - clamp(vWorldPos.y / 108.22, 0.0, 1.0));
vec4 typePixel = texture(texTypeMap, uvType);
// Compute smooth biome weights from typemap colors:
// 1. Prairie: red dominant (r - g)
float prairieWeight = clamp((typePixel.r - typePixel.g - 0.20) / 0.25, 0.0, 1.0);
// 2. Sahara: yellow (r & g high, b low)
float saharaWeight = clamp((min(typePixel.r, typePixel.g) - 0.45) / 0.25, 0.0, 1.0) * (1.0 - prairieWeight);
// 3. Forest: elevated blue with low red (b > 0.15, r < 0.25)
float forestWeight = clamp((typePixel.b - 0.15) / 0.20, 0.0, 1.0) * clamp((0.25 - typePixel.r) / 0.15, 0.0, 1.0) * (1.0 - prairieWeight) * (1.0 - saharaWeight);
// 4. Tundra: muted slate/olive (low saturation, moderate/low green)
float sat = max(typePixel.r, max(typePixel.g, typePixel.b)) - min(typePixel.r, min(typePixel.g, typePixel.b));
float tundraWeight = clamp((0.25 - sat) / 0.15, 0.0, 1.0) * clamp((0.55 - typePixel.g) / 0.15, 0.0, 1.0) * (1.0 - prairieWeight) * (1.0 - saharaWeight) * (1.0 - forestWeight);
// 5. Grassland: green dominant open plains
float grasslandWeight = clamp(1.0 - (prairieWeight + saharaWeight + forestWeight + tundraWeight), 0.0, 1.0);
// 2. Polar Latitude Calculation (World Y in [0..108.25 km])
float normY = clamp(vWorldPos.y / 108.25, 0.0, 1.0);
float distFromCenter = abs(normY - 0.5) * 2.0; // 0.0 at middle, 1.0 at North/South poles
float poleFactor = smoothstep(0.40, 0.85, distFromCenter);
// Snowline in kilometers: 2.5 (2500m) in temperate zone, dropping to 0.0 (sea level) at poles!
float snowLine = mix(2.5, 0.0, poleFactor);
// 3. Water vs Land detection (z_pos = height in kilometers, 0.0 = sea level)
float oceanWaterFac = 1.0 - smoothstep(0.005, 0.025, z_pos);
// River water detection
float riverDist = getRiverDistance(fragVert.xy, riverMask);
float riverWaterRadius = 0.024;
float riverBankRadius = 0.052;
float riverWaterFac = 1.0 - smoothstep(riverWaterRadius * 0.7, riverWaterRadius, riverDist);
float riverBankFac = (1.0 - smoothstep(riverWaterRadius, riverBankRadius, riverDist)) * (1.0 - riverWaterFac);
// Combined water factor (ocean or river)
float waterFac = max(oceanWaterFac, riverWaterFac);
// 4. Dynamic animated ocean water
vec2 uvW1 = vWorldPos.xy * 0.30 + vec2(time * 0.035, time * 0.020);
vec2 uvW2 = vWorldPos.xy * 0.38 + vec2(-time * 0.025, time * 0.030);
vec2 waveDistort1 = vec2(sin(uvW1.y * 14.0 + time * 2.2), cos(uvW1.x * 14.0 + time * 1.8)) * 0.025;
vec2 waveDistort2 = vec2(cos(uvW2.y * 18.0 - time * 1.5), sin(uvW2.x * 18.0 - time * 2.5)) * 0.020;
vec4 wSample1 = texture(texWater, uvW1 + waveDistort1);
vec4 wSample2 = texture(texWater, uvW2 + waveDistort2);
vec4 waterColor = mix(wSample1, wSample2, 0.5);
waterColor.rgb = mix(waterColor.rgb, vec3(0.05, 0.30, 0.50), 0.30);
vec3 waterNorm = normalize(vec3(
(waveDistort1.x + waveDistort2.x) * 2.5,
(waveDistort1.y + waveDistort2.y) * 2.5,
1.0
));
// 5. Triplanar mapping coordinates for Land, Cliffs & Snow
vec3 blendWeights = pow(abs(norm), vec3(4.0));
blendWeights /= (blendWeights.x + blendWeights.y + blendWeights.z);
vec2 uvX = vWorldPos.yz * 0.35;
vec2 uvY = vWorldPos.xz * 0.35;
vec2 uvZ = vWorldPos.xy * 0.35;
// Rock textures:
// Gray rock (Texturelabs_Stone_136XL_seamless)
vec4 rockGray = texture(texRock, uvX) * blendWeights.x
+ texture(texRock, uvY) * blendWeights.y
+ texture(texRock, uvZ) * blendWeights.z;
// Red rock canyon cliff (Texturelabs_RedRock_seamless)
vec4 rockRed = texture(texRedRock, uvX) * blendWeights.x
+ texture(texRedRock, uvY) * blendWeights.y
+ texture(texRedRock, uvZ) * blendWeights.z;
// Cliff rock: red sandstone in Prairie canyon, gray alpine elsewhere
vec4 cliffRockColor = mix(rockGray, rockRed, prairieWeight);
// Snow / Ice texture (Texturelabs_Water_155XL_seamless)
vec4 snowColor = texture(texSnow, uvX) * blendWeights.x
+ texture(texSnow, uvY) * blendWeights.y
+ texture(texSnow, uvZ) * blendWeights.z;
// Ground textures:
vec4 grassColor = texture(texGrass, uvZ);
vec4 forestColor = texture(texForest, uvZ);
vec4 beachColor = texture(texSand, uvZ);
vec4 saharaColor = texture(texSahara, uvZ);
vec4 tundraColor = texture(texTundra, uvZ);
vec4 redSandColor = texture(texRedSand, uvZ);
// Composite ground texture across all biomes:
vec4 biomeGround = grassColor * grasslandWeight
+ forestColor * forestWeight
+ saharaColor * saharaWeight
+ tundraColor * tundraWeight
+ redSandColor * prairieWeight;
// Farm improvement texture applied when a farm is present
if (isFarm == 1) {
float scale = max(0.01, farmTextureScale);
vec2 farmUV;
if (farmMappingMode == 1) {
// Hex-centered mapping with aspect ratio correction
vec2 centered = vec2(tilePos.x - 0.5, (tilePos.y - 0.4328799) * 1.154734411);
farmUV = centered * scale + vec2(0.5, 0.5);
} else {
// World-space coordinates: perfectly seamless and continuous across adjacent hex tiles
farmUV = vWorldPos.xy * scale;
}
vec4 farmColor = texture(texFarm, farmUV);
biomeGround = farmColor;
}
// Shoreline beach: where gentle slopes meet water (sea level up to ~110m, or along rivers)
// Steep slopes (norm.z < 0.55) remain rocky cliffs; gentle slopes (norm.z > 0.80) get a sandy beach
float gentleSlope = smoothstep(0.55, 0.85, norm.z);
float coastBeach = (1.0 - smoothstep(0.015, 0.110, z_pos)) * smoothstep(0.003, 0.015, z_pos) * gentleSlope;
float riverBeach = riverBankFac;
float beachFac = max(coastBeach, riverBeach);
// Suppress beach only at the extreme polar ice sheet edges
float polarIceCover = smoothstep(0.82, 0.96, distFromCenter);
beachFac *= (1.0 - polarIceCover);
// Apply sandy beach across all land types gently meeting water
biomeGround = mix(biomeGround, beachColor, beachFac * 0.90);
// Polar pack ice on cold polar waters
float polarSeaIce = smoothstep(0.72, 0.94, distFromCenter);
waterColor = mix(waterColor, snowColor, polarSeaIce * 0.95);
// 6. Slope & Elevation Blending Rules
// norm.z: 1.0 = flat horizontal ground, < 0.55 = steep slope / vertical cliff
float flatness = clamp((norm.z - 0.55) / 0.25, 0.0, 1.0);
// Mountain rock elevation factor: begins at 1000m (1.0 km), ramps up towards 2200m (2.2 km)
float rockLine = mix(1.0, 0.0, poleFactor);
float mountainAltitude = smoothstep(rockLine, min(snowLine, rockLine + 1.2), z_pos);
// Steep slopes are always rock; flat ground becomes rock as altitude rises above 1000m
float rockWeight = max(1.0 - flatness, mountainAltitude);
// Snow rule: starts above 2500m in temperate zone, dropping to sea level at poles
float snowHeight = smoothstep(snowLine - 0.20, snowLine + 0.30, z_pos);
float snowSlope = clamp((norm.z - 0.28) / 0.25, 0.0, 1.0);
float snowWeight = snowHeight * snowSlope;
// At polar caps, ice covers flat ground at sea level
float polarLandIce = smoothstep(0.72, 0.92, distFromCenter);
snowWeight = max(snowWeight, polarLandIce * snowSlope);
// Composite ground: biome ground -> mountain rock above 1000m & on cliffs -> snow above 2500m
vec4 nonSnowLand = mix(biomeGround, cliffRockColor, rockWeight);
vec4 landColor = mix(nonSnowLand, snowColor, snowWeight);
// 6b. Road Texture Integration (blended directly into the tile surface texture)
if (hasRoad == 1 && oceanWaterFac < 0.95) {
vec2 tileCenterWorld = vWorldPos.xy - fragVert.xy;
float amp = max(0.0, roadMeander);
// Additional subtle world-space domain warp inside the tile (fades to 0 at hex border so edges stay locked)
float borderFade = 1.0 - smoothstep(0.24, 0.42, length(fragVert.xy));
vec2 microWarp = vec2(
sin(vWorldPos.y * 13.0 + vWorldPos.x * 5.0),
cos(vWorldPos.x * 13.0 - vWorldPos.y * 5.0)
) * (amp * 0.22 * borderFade);
float rawRoadDist = getRoadDistance(fragVert.xy + microWarp, roadMask, tileCenterWorld, amp);
float rScale = max(0.5, roadTextureScale);
vec2 uvRoad = vWorldPos.xy * rScale;
vec3 soilTex = mix(texture(texSand, uvRoad).rgb, texture(texTundra, uvRoad * 1.45).rgb, 0.45);
vec3 stoneTex = texture(texRock, uvRoad * 1.35).rgb;
// Subtle organic edge roughness from soil texture grain so road borders blend naturally into terrain
float edgeGrain = (soilTex.r - 0.5) * 0.008;
float dRoad = max(0.0, rawRoadDist + edgeGrain);
float rCore = max(0.014, roadRadius);
float rOuter = rCore + max(0.006, roadBorderWidth);
float roadCoreFac = 1.0 - smoothstep(rCore * 0.50, rCore, dRoad);
float roadTotalFac = 1.0 - smoothstep(rCore * 0.65, rOuter, dRoad);
if (roadTotalFac > 0.001) {
vec3 roadSurf;
if (roadType == 1) {
// Stone / Paved road: rich stone texture modulated by configured colors
vec3 stoneCenter = mix(stoneTex, roadColor, 0.55) * (0.75 + 0.50 * stoneTex.g);
vec3 stoneEdge = mix(stoneTex, roadBorderColor, 0.65) * 0.85;
roadSurf = mix(stoneEdge, stoneCenter, roadCoreFac);
} else if (roadType == 2) {
// Railroad: gravel ballast bed with dark twin rail tracks
vec3 ballast = mix(stoneTex, roadBorderColor, 0.60);
float railLine = smoothstep(rCore * 0.35, rCore * 0.50, rawRoadDist)
* (1.0 - smoothstep(rCore * 0.55, rCore * 0.72, rawRoadDist));
roadSurf = mix(ballast, roadColor * (0.8 + 0.4 * stoneTex.r), max(roadCoreFac * 0.6, railLine));
} else {
// Dirt road: natural packed earth & wheel ruts blended with soil texture
float rutPattern = smoothstep(rCore * 0.25, rCore * 0.65, rawRoadDist);
vec3 packedEarth = mix(soilTex, roadColor, 0.52) * (0.78 + 0.45 * soilTex.r);
vec3 wornShoulder = mix(soilTex, roadBorderColor, 0.60) * (0.72 + 0.40 * soilTex.g);
vec3 innerTrack = mix(packedEarth, wornShoulder, rutPattern * 0.45);
roadSurf = mix(wornShoulder, innerTrack, roadCoreFac);
}
// If crossing a river, render a wooden/stone bridge deck and suppress river water under the bridge
if (riverWaterFac > 0.01) {
vec3 bridgeCol = (roadType >= 1)
? mix(stoneTex, vec3(0.58, 0.56, 0.53), 0.65)
: mix(soilTex, vec3(0.55, 0.38, 0.20), 0.70);
roadSurf = mix(roadSurf, bridgeCol, riverWaterFac);
waterFac *= (1.0 - roadTotalFac * 0.95);
}
float blendAlpha = roadTotalFac * clamp(roadOpacity, 0.0, 1.0) * (1.0 - oceanWaterFac);
landColor.rgb = mix(landColor.rgb, roadSurf, blendAlpha);
}
}
// 7. Combine Land and Sea
vec4 surfaceColor = mix(landColor, waterColor, waterFac);
// 8. Hexagon grid border outline
vec4 bcolor = texture(texBorder, vec2(tilePos.x, tilePos.y * 1.154734411));
float borderAlpha = bcolor.a * (1.0 - waterFac * 0.7);
// 9. Lighting & Specular glint
vec3 activeNorm = mix(norm, waterNorm, waterFac);
vec4 lightDiffuse = GetDirectionalLightColor(sunLight, activeNorm);
// Specular: crisp sparkle on snow and water
float specShininess = mix(mix(16.0, 32.0, snowWeight), 48.0, waterFac);
float specIntensity = mix(mix(0.12, 0.55, snowWeight), 0.85, waterFac);
vec4 lightSpecular = GetSpecularColor(vWorldPos, cameraPosition, sunLight, activeNorm, specShininess, specIntensity);
finalColor = (surfaceColor + bcolor * borderAlpha) * lightDiffuse + lightSpecular;
}