427 lines
17 KiB
GLSL
427 lines
17 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) {
|
|
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);
|
|
|
|
// sin^2(pi*t) has BOTH zero value AND zero derivative at t=0 and t=1,
|
|
// guaranteeing C1-continuous, kink-free transitions across hex borders!
|
|
const float PI_VAL = 3.14159265;
|
|
vec2 prevPt = p0;
|
|
float minDist = 999.0;
|
|
for (int i = 1; i <= 8; ++i) {
|
|
float t = float(i) / 8.0;
|
|
float omt = 1.0 - t;
|
|
vec2 basePt = (omt * omt) * p0 + (t * t) * p2;
|
|
float st = sin(PI_VAL * t);
|
|
float wave = amp * s1 * (st * st);
|
|
vec2 curPt = basePt + perp * wave;
|
|
minDist = min(minDist, distToSegment(p, prevPt, curPt));
|
|
prevPt = curPt;
|
|
}
|
|
return minDist;
|
|
}
|
|
|
|
float meanderingBranchDist(vec2 p, vec2 pe, float amp, float s1) {
|
|
float dLen = length(pe);
|
|
vec2 perp = (dLen > 1e-4) ? vec2(pe.y / dLen, -pe.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 <= 6; ++i) {
|
|
float t = float(i) / 6.0;
|
|
vec2 basePt = pe * (1.0 - t);
|
|
float st = sin(PI_VAL * t);
|
|
float wave = amp * 0.65 * s1 * (st * st);
|
|
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, int ix, int iy, float amp) {
|
|
// Exact integer parity per hex tile: completely immune to floating-point interpolation jitter across scanlines!
|
|
int parity = (ix * 3 + iy * 5) & 1;
|
|
float s1 = (parity == 0) ? 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) {
|
|
return meanderingBezierDist(p, vec2(-0.18, 0.0), vec2(0.18, 0.0), amp * 0.7, s1);
|
|
} else if (count == 1) {
|
|
return meanderingBranchDist(p, EDGE_MIDPOINTS[edges[0]], amp, s1);
|
|
} else if (count == 2) {
|
|
return meanderingBezierDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]], amp, s1);
|
|
} else {
|
|
float minDist = 999.0;
|
|
for (int i = 0; i < count; ++i) {
|
|
minDist = min(minDist, meanderingBranchDist(p, EDGE_MIDPOINTS[edges[i]], amp * 0.5, s1));
|
|
for (int j = i + 1; j < count; ++j) {
|
|
minDist = min(minDist, bezierCurveDist(p, EDGE_MIDPOINTS[edges[i]], EDGE_MIDPOINTS[edges[j]]));
|
|
}
|
|
}
|
|
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) {
|
|
// Recover exact integer grid indices of the tile center (spacing is 0.75 in X and 0.43287992 in Y)
|
|
vec2 tileCenterWorld = vWorldPos.xy - fragVert.xy;
|
|
int ix = int(floor(tileCenterWorld.x / 0.75 + 0.5));
|
|
int iy = int(floor(tileCenterWorld.y / 0.43287992 + 0.5));
|
|
float amp = max(0.0, roadMeander);
|
|
|
|
float rScale = max(1.0, roadTextureScale);
|
|
vec2 uvRoad = vWorldPos.xy * rScale;
|
|
|
|
// Sample textures in uniform control flow (before any fragment-dependent branch) to avoid mipmap derivative glitches
|
|
vec3 soilTex = texture(texSand, uvRoad).rgb;
|
|
vec3 saharaTex = texture(texSahara, uvRoad * 0.9).rgb;
|
|
float fineGrain = (mix(soilTex.r, saharaTex.r, 0.5) - 0.5) * 0.18;
|
|
|
|
float rawRoadDist = getRoadDistance(fragVert.xy, roadMask, ix, iy, amp);
|
|
|
|
float rCore = max(0.012, roadRadius);
|
|
float rOuter = rCore + max(0.005, roadBorderWidth);
|
|
|
|
float roadCoreFac = 1.0 - smoothstep(rCore * 0.65, rCore, rawRoadDist);
|
|
float roadTotalFac = (1.0 - smoothstep(rCore, rOuter, rawRoadDist)) * (1.0 - oceanWaterFac);
|
|
|
|
if (roadTotalFac > 0.001) {
|
|
vec3 centerTone = clamp(roadColor * (1.0 + fineGrain), 0.0, 1.0);
|
|
vec3 borderTone = clamp(roadBorderColor * (1.0 + fineGrain * 0.7), 0.0, 1.0);
|
|
vec3 roadSurf = mix(borderTone, centerTone, roadCoreFac);
|
|
|
|
// If crossing a river, render a bridge deck and suppress river water under the bridge
|
|
if (riverWaterFac > 0.01) {
|
|
vec3 bridgeCol = (roadType >= 1)
|
|
? vec3(0.72, 0.70, 0.66) * (1.0 + fineGrain)
|
|
: vec3(0.68, 0.48, 0.28) * (1.0 + fineGrain);
|
|
roadSurf = mix(roadSurf, bridgeCol, riverWaterFac);
|
|
waterFac *= (1.0 - roadTotalFac * 0.98);
|
|
}
|
|
|
|
float blendAlpha = roadTotalFac * clamp(roadOpacity, 0.0, 1.0);
|
|
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;
|
|
}
|