#version 460 layout(location = 0) out vec4 fragColor; layout(location = 0) in vec2 texCoords; layout(location = 1) flat in vec3[4] directions; uniform sampler2D color; uniform sampler2D depthTexture; uniform float zNear; uniform float zFar; uniform vec2 tanXY; layout(binding = 5) uniform sampler2D bloomColor; struct Fog { vec3 color; float density; float fogLower; float fogHigher; }; uniform Fog fog; uniform ivec3 playerPositionInteger; uniform vec3 playerPositionFraction; float zFromDepth(float depthBufferValue) { return zNear*zFar/(depthBufferValue*(zNear - zFar) + zFar); } float densityIntegral(float dist, float zStart, float zDist, float fogLower, float fogHigher) { // The density is constant until fogLower, then gets smaller linearly until reaching fogHigher, past which there is no fog. if(zDist < 0) { zStart += zDist; zDist = -zDist; } if(abs(zDist) < 0.001) { zDist = 0.001; } float beginLower = min(fogLower, zStart); float endLower = min(fogLower, zStart + zDist); float beginMid = max(fogLower, min(fogHigher, zStart)); float endMid = max(fogLower, min(fogHigher, zStart + zDist)); float midIntegral = -0.5*(endMid - fogHigher)*(endMid - fogHigher)/(fogHigher - fogLower) - -0.5*(beginMid - fogHigher)*(beginMid - fogHigher)/(fogHigher - fogLower); if(fogHigher == fogLower) midIntegral = 0; return (endLower - beginLower + midIntegral)/zDist*dist; } float calculateFogDistance(float dist, float densityAdjustment, float zStart, float zScale, float fogDensity, float fogLower, float fogHigher) { float distCameraTerrain = densityIntegral(dist*densityAdjustment, zStart, zScale*dist*densityAdjustment, fogLower, fogHigher)*fogDensity; float distFromCamera = 0; float distFromTerrain = distFromCamera - distCameraTerrain; if(distCameraTerrain < 10) { // Resolution range is sufficient. return distFromTerrain; } else { // Here we have a few options to deal with this. We could for example weaken the fog effect to fit the entire range. // I decided to keep the fog strength close to the camera and far away, with a fog-free region in between. // I decided to this because I want far away fog to work (e.g. a distant ocean) as well as close fog(e.g. the top surface of the water when the player is under it) if(distFromTerrain > -5 && dist != 0) { return distFromTerrain; } else if(distFromCamera < 5) { return distFromCamera - 10; } else { return -5; } } } vec3 applyFrontfaceFog(float fogDistance, vec3 fogColor, vec3 inColor) { float fogFactor = exp(fogDistance); inColor *= fogFactor; inColor += fogColor; inColor -= fogColor*fogFactor; return inColor; } void main() { fragColor = texture(color, texCoords); fragColor += texture(bloomColor, texCoords); vec2 clampedTexCoords = (floor(texCoords*vec2(textureSize(color, 0))) + 0.5)/vec2(textureSize(color, 0)); vec3 direction = clampedTexCoords.x*( clampedTexCoords.y*directions[0] + (1 - clampedTexCoords.y)*directions[1] ) + (1 - clampedTexCoords.x)*( clampedTexCoords.y*directions[2] + (1 - clampedTexCoords.y)*directions[3] ); float densityAdjustment = sqrt(dot(tanXY*(clampedTexCoords*2 - 1), tanXY*(clampedTexCoords*2 - 1)) + 1); float dist = zFromDepth(texture(depthTexture, texCoords).r); float fogDistance = calculateFogDistance(dist, densityAdjustment, playerPositionFraction.z, normalize(direction).z, fog.density, fog.fogLower - playerPositionInteger.z, fog.fogHigher - playerPositionInteger.z); fragColor.rgb = applyFrontfaceFog(fogDistance, fog.color, fragColor.rgb); float maxColor = max(1.0, max(fragColor.r, max(fragColor.g, fragColor.b))); fragColor.rgb = fragColor.rgb/maxColor; }