#version 150 uniform sampler2D DiffuseSampler; uniform sampler2D DepthSampler; uniform sampler3D FogVolume; uniform mat4 InverseProjection; uniform mat4 InverseView; uniform vec3 CameraPosition; uniform ivec2 FogOrigin; uniform ivec2 RingBlockOffset; in vec2 texCoord; out vec4 FragColor; //------------------------------------------------------------ // Настройки //------------------------------------------------------------ const float FOG_SIZE_X = 256.0; const float FOG_SIZE_Z = FOG_SIZE_X; const int MAX_STEPS = 256; // Уменьшили количество шагов благодаря адаптивности // Высота мира const float WORLD_BOTTOM = -64.0; const float WORLD_HEIGHT = 384.0; const float MAX_DISTANCE = (FOG_SIZE_X / 2.0) - 18.0; // Коэффициент нелинейности распределения шагов: // 1.0 = равномерные шаги (как было) // 1.5 = мягкое увеличение (рекомендуется для начала) // 2.0 = линейный рост размера шага с расстоянием (хороший баланс) // 2.5 = более агрессивное увеличение (лучшая производительность) const float GAMMA = 2.0; // Включение jittering для устранения banding артефактов const bool USE_JITTERING = true; //------------------------------------------------------------ vec3 reconstructViewPosition(vec2 uv) { float depth = texture(DepthSampler, uv).r; vec4 clip = vec4( uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0 ); vec4 view = InverseProjection * clip; return view.xyz / view.w; } vec3 reconstructWorldPosition(vec2 uv) { vec3 viewPos = reconstructViewPosition(uv); vec4 world = InverseView * vec4(viewPos, 1.0); return world.xyz + CameraPosition; } //------------------------------------------------------------ float density(vec3 worldPos) { vec3 uv; float localX = worldPos.x - float(FogOrigin.x); float localZ = worldPos.z - float(FogOrigin.y); if ( localX < 0.0 || localX >= FOG_SIZE_X || localZ < 0.0 || localZ >= FOG_SIZE_Z || worldPos.y < WORLD_BOTTOM || worldPos.y >= WORLD_BOTTOM + WORLD_HEIGHT ){ return 0.0; } float physicalX = localX + float(RingBlockOffset.x); float physicalZ = localZ + float(RingBlockOffset.y); uv.x = physicalX / FOG_SIZE_X; uv.z = physicalZ / FOG_SIZE_Z; uv.y = (worldPos.y - WORLD_BOTTOM) / WORLD_HEIGHT; return texture(FogVolume, uv).r; } //------------------------------------------------------------ // Псевдослучайное значение для jittering (детерминированное по UV) float random(vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); } // Функция нелинейного маппинга параметра t [0,1] в расстояние // Использует степенную функцию для распределения шагов float mapParameterToDistance(float t) { return MAX_DISTANCE * pow(t, GAMMA); } //------------------------------------------------------------ void main() { vec4 sceneColor = texture(DiffuseSampler, texCoord); vec3 worldPos = reconstructWorldPosition(texCoord); vec3 rayDir = normalize(worldPos - CameraPosition); float rayLength = distance(worldPos, CameraPosition); rayLength = min(rayLength, MAX_DISTANCE); vec3 samplePos = CameraPosition; float fog = 0.0; float currentDistance = 0.0; // Jittering начальной позиции для устранения banding (ступенчатых артефактов) // Это стандартная техника в volumetric rendering float jitter = 0.0; if (USE_JITTERING) { // Вычисляем размер первого шага для корректного jittering float firstStep = mapParameterToDistance(1.0 / float(MAX_STEPS)); jitter = random(texCoord) * firstStep; currentDistance = jitter; samplePos += rayDir * currentDistance; } for (int i = 0; i < MAX_STEPS; i++) { // Параметр t меняется от 0 до 1 float t = float(i + 1) / float(MAX_STEPS); // Вычисляем следующее расстояние через нелинейное маппинг float nextDistance = mapParameterToDistance(t); // Ограничиваем максимальной длиной луча if (nextDistance > rayLength) { nextDistance = rayLength; } // Размер текущего шага float stepSize = nextDistance - currentDistance; // Проверяем, не вышли ли за пределы луча if (stepSize <= 0.0) { break; } // Продвигаемся по лучу samplePos += rayDir * stepSize; // Интегрируем плотность (Beer-Lambert law approximation) // Важно умножать на stepSize для корректной физической модели fog += density(samplePos) * stepSize; currentDistance = nextDistance; // Прерываем, если достигли конца луча if (currentDistance >= rayLength) { break; } } fog = clamp(fog, 0.0, 1.0); vec3 fogColor = vec3( 0.75, 0.80, 0.90 ); FragColor = vec4( mix(sceneColor.rgb, fogColor, fog), 1.0 ); }