From 76370f71e2b6309f70091201c757cd117c67e5b4 Mon Sep 17 00:00:00 2001 From: DIvan2000 Date: Tue, 4 Aug 2026 20:15:35 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=81=D0=B2=D0=B5=D1=89=D1=91=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B2=D0=BE=D0=BA=D1=81=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9=20=D1=83=D1=87=D0=B8=D1=82=D1=8B=D0=B2=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/render/PostProcessingManager.java | 2 +- .../client/render/fog/FogLightProvider.java | 22 +-- .../client/render/fog/FogLightVolume.java | 139 ++++++----------- .../assets/veila/shaders/fog_raymarching.frag | 145 ++++++++++++------ 4 files changed, 157 insertions(+), 151 deletions(-) diff --git a/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java b/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java index 001aa04..5090caa 100644 --- a/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java +++ b/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java @@ -276,7 +276,7 @@ public class PostProcessingManager { GL20.glUniform1f( skyBrightnessLocation, - client.world.getSkyBrightness(tickDelta)*0.75f+0.25f + client.world.getSkyBrightness(tickDelta) ); FullscreenQuad.draw(); diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogLightProvider.java b/src/client/java/su/divan2000/veila/client/render/fog/FogLightProvider.java index 7e4c391..d252ad6 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogLightProvider.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogLightProvider.java @@ -10,7 +10,8 @@ import net.minecraft.world.chunk.light.LightingProvider; public final class FogLightProvider { - public static final int SECTION_DATA_SIZE = 4096; + // Размер массива для одной секции: 16×16×16×2 = 8192 (×2 для двух каналов) + public static final int SECTION_DATA_SIZE = 16 * 16 * 16 * 2; private FogLightProvider() { } @@ -21,16 +22,13 @@ public final class FogLightProvider { LightingProvider provider = client.world.getLightingProvider(); - // Прямой доступ к провайдерам через AccessWidener ChunkLightProvider blockProvider = provider.blockLightProvider; ChunkLightProvider skyProvider = provider.skyLightProvider; - // Проверка на null (поля @Nullable) if (blockProvider == null || skyProvider == null) { return null; } - // Используем ПУБЛИЧНЫЙ метод ChunkLightProvider.getLightSection() ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ); ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos); @@ -43,29 +41,19 @@ public final class FogLightProvider { byte[] result = new byte[SECTION_DATA_SIZE]; int index = 0; - // Порядок заполнения: Z → Y → X (как в FogChunkProvider) for (int localZ = 0; localZ < 16; localZ++) { for (int localY = 0; localY < 16; localY++) { for (int localX = 0; localX < 16; localX++) { int blockLight = (blockArray != null) ? blockArray.get(localX, localY, localZ) : 0; int skyLight = (skyArray != null) ? skyArray.get(localX, localY, localZ) : 0; - result[index++] = FogLightVolume.packLight(blockLight, skyLight); + // Упаковка в два байта: R=blockLight, G=skyLight + FogLightVolume.packLight(blockLight, skyLight, result, index); + index += 2; } } } return result; } - - public static byte computeLightValue(BlockPos pos) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.world == null) return FogLightVolume.DEFAULT_LIGHT; - - LightingProvider provider = client.world.getLightingProvider(); - int blockLight = provider.get(LightType.BLOCK).getLightLevel(pos); - int skyLight = provider.get(LightType.SKY).getLightLevel(pos); - - return FogLightVolume.packLight(blockLight, skyLight); - } } \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogLightVolume.java b/src/client/java/su/divan2000/veila/client/render/fog/FogLightVolume.java index 13e1b78..8aa4d66 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogLightVolume.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogLightVolume.java @@ -9,8 +9,6 @@ import org.lwjgl.opengl.GL31; import java.nio.ByteBuffer; -import static su.divan2000.veila.client.render.fog.FogLightStreamer.SECTION_SIZE; - public final class FogLightVolume { public static final int SIZE_X = FogWorldVolume.SIZE_X; @@ -22,38 +20,40 @@ public final class FogLightVolume { private static int texture = -1; - // Упаковка: bits 0-3 = BlockLight, bits 4-7 = SkyLight - public static final byte DEFAULT_LIGHT = packLight(0, 15); // Темно, полное небо + // Дефолтное освещение: blockLight=0, skyLight=15 (ночь, открытое небо) + // Теперь это vec2, упакованный в RG8 + public static final byte DEFAULT_BLOCK_LIGHT = 0; + public static final byte DEFAULT_SKY_LIGHT = 15; - private static final byte[] ZEROS_ARRAY; + private static final byte[] DEFAULT_ARRAY; static { - ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE]; - java.util.Arrays.fill(ZEROS_ARRAY, DEFAULT_LIGHT); + DEFAULT_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE * 2]; // ×2 для двух каналов + for (int i = 0; i < CHUNK_SIZE * SIZE_Y * CHUNK_SIZE; i++) { + DEFAULT_ARRAY[i * 2] = DEFAULT_BLOCK_LIGHT; // R = blockLight + DEFAULT_ARRAY[i * 2 + 1] = DEFAULT_SKY_LIGHT; // G = skyLight + } } private static final ByteBuffer CHUNK_BUFFER = - BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE); + BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE * 2); private static final ByteBuffer SINGLE_VOXEL_BUFFER = - BufferUtils.createByteBuffer(1); + BufferUtils.createByteBuffer(2); - // PBO для быстрой очистки (как в FogWorldVolume) + // PBO для быстрой очистки private static int clearPBO = -1; - private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z; + private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z * 2; // ×2 для двух каналов private FogLightVolume() { } - public static byte packLight(int blockLight, int skyLight) { - return (byte) (((skyLight & 0xF) << 4) | (blockLight & 0xF)); - } - - public static int unpackBlockLight(byte packed) { - return packed & 0xF; - } - - public static int unpackSkyLight(byte packed) { - return (packed >> 4) & 0xF; + /** + * Упаковывает blockLight и skyLight в два байта для RG8 текстуры. + * blockLight → R канал, skyLight → G канал. + */ + public static void packLight(int blockLight, int skyLight, byte[] out, int offset) { + out[offset] = (byte) (blockLight * 17); + out[offset + 1] = (byte) (skyLight * 17); } private static void initClearPBO() { @@ -66,8 +66,9 @@ public final class FogLightVolume { ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY); if (mapped != null) { - for (int i = 0; i < MAX_CLEAR_SIZE; i++) { - mapped.put(DEFAULT_LIGHT); + for (int i = 0; i < SIZE_X * SIZE_Y * SIZE_Z; i++) { + mapped.put(DEFAULT_BLOCK_LIGHT); + mapped.put(DEFAULT_SKY_LIGHT); } GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); } @@ -84,21 +85,23 @@ public final class FogLightVolume { GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); + // ИСПРАВЛЕНО: GL_RG8 вместо GL_R8UI, два канала для двух типов света GL30.glTexImage3D( GL12.GL_TEXTURE_3D, 0, - GL30.GL_R8UI, + GL30.GL_RG8, SIZE_X, SIZE_Y, SIZE_Z, 0, - GL30.GL_RED_INTEGER, + GL30.GL_RG, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null ); - GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); - GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + // GL_LINEAR для плавной интерполяции + GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_S, GL12.GL_REPEAT); GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE); @@ -111,7 +114,7 @@ public final class FogLightVolume { public static void clear() { CHUNK_BUFFER.clear(); - CHUNK_BUFFER.put(ZEROS_ARRAY); + CHUNK_BUFFER.put(DEFAULT_ARRAY); CHUNK_BUFFER.flip(); GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); @@ -143,7 +146,7 @@ public final class FogLightVolume { CHUNK_SIZE, SIZE_Y, CHUNK_SIZE, - GL30.GL_RED_INTEGER, + GL30.GL_RG, // ИСПРАВЛЕНО: GL_RG вместо GL_RED_INTEGER GL11.GL_UNSIGNED_BYTE, data ); @@ -151,19 +154,17 @@ public final class FogLightVolume { GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); } - public static void updateSingleVoxel( - int textureChunkX, - int textureChunkZ, - int localX, - int localY, - int localZ, - byte value + public static void uploadSection( + int textureSectionX, + int textureSectionY, + int textureSectionZ, + byte[] data ) { - GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); + ByteBuffer sectionBuffer = BufferUtils.createByteBuffer(data.length); + sectionBuffer.put(data); + sectionBuffer.flip(); - SINGLE_VOXEL_BUFFER.clear(); - SINGLE_VOXEL_BUFFER.put(value); - SINGLE_VOXEL_BUFFER.flip(); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0); @@ -173,13 +174,15 @@ public final class FogLightVolume { GL30.glTexSubImage3D( GL12.GL_TEXTURE_3D, 0, - textureChunkX * CHUNK_SIZE + localX, - localY, - textureChunkZ * CHUNK_SIZE + localZ, - 1, 1, 1, - GL30.GL_RED_INTEGER, + textureSectionX * CHUNK_SIZE, + textureSectionY * 16, + textureSectionZ * CHUNK_SIZE, + 16, + 16, + 16, + GL30.GL_RG, GL11.GL_UNSIGNED_BYTE, - SINGLE_VOXEL_BUFFER + sectionBuffer ); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); @@ -230,7 +233,7 @@ public final class FogLightVolume { 0, physZ[i] * CHUNK_SIZE, w, h, d, - GL30.GL_RED_INTEGER, + GL30.GL_RG, GL11.GL_UNSIGNED_BYTE, 0L ); @@ -239,46 +242,4 @@ public final class FogLightVolume { GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); } - - // Статический буфер для одной секции (4096 байт) - private static final ByteBuffer SECTION_BUFFER = - BufferUtils.createByteBuffer(SECTION_SIZE * SECTION_SIZE * SECTION_SIZE); - - /** - * Загружает одну секцию (16×16×16) в указанную позицию текстуры. - * textureSectionX/Z - в чанках (0-15), textureSectionY - индекс секции по Y (0-23). - */ - public static void uploadSection( - int textureSectionX, - int textureSectionY, - int textureSectionZ, - byte[] data - ) { - SECTION_BUFFER.clear(); - SECTION_BUFFER.put(data); - SECTION_BUFFER.flip(); - - GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); - - GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); - GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0); - GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0); - GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0); - - GL30.glTexSubImage3D( - GL12.GL_TEXTURE_3D, - 0, - textureSectionX * CHUNK_SIZE, - textureSectionY * SECTION_SIZE, - textureSectionZ * CHUNK_SIZE, - SECTION_SIZE, - SECTION_SIZE, - SECTION_SIZE, - GL30.GL_RED_INTEGER, - GL11.GL_UNSIGNED_BYTE, - SECTION_BUFFER - ); - - GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); - } } \ No newline at end of file diff --git a/src/client/resources/assets/veila/shaders/fog_raymarching.frag b/src/client/resources/assets/veila/shaders/fog_raymarching.frag index 19068ca..39e9021 100644 --- a/src/client/resources/assets/veila/shaders/fog_raymarching.frag +++ b/src/client/resources/assets/veila/shaders/fog_raymarching.frag @@ -3,6 +3,7 @@ uniform sampler2D DiffuseSampler; uniform sampler2D DepthSampler; uniform sampler3D FogVolume; +uniform sampler3D LightVolume; uniform mat4 InverseProjection; uniform mat4 InverseView; @@ -28,22 +29,33 @@ 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 = false; +// Физические коэффициенты volumetric rendering +// EXTINCTION: насколько сильно туман поглощает/рассеивает свет (0.1-2.0) +// Больше значение = туман сильнее затеняет далёкие объекты +const float EXTINCTION_COEFF = 1.0; + +// SCATTERING: насколько сильно туман сам светится от источников +// Обычно равен EXTINCTION (консервативная среда), но можно варьировать +const float SCATTERING_COEFF = 1.0; + +// Ambient — базовое освещение среды (воздух, микрочастицы). +// Не зависит от времени суток и от skyLight/blockLight. +// Даёт минимальную видимость тумана даже в полной темноте. +const float AMBIENT_INTENSITY = 0.10; + +// Порог transmittance для early termination (оптимизация) +// Если transmittance упал ниже этого значения, дальнейшие шаги ничего не внесут +const float MIN_TRANSMITTANCE = 0.01; + //------------------------------------------------------------ vec3 reconstructViewPosition(vec2 uv) @@ -57,25 +69,20 @@ vec3 reconstructViewPosition(vec2 uv) ); 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 worldToUV(vec3 worldPos) { - vec3 uv; - float localX = worldPos.x - float(FogOrigin.x); float localZ = worldPos.z - float(FogOrigin.y); @@ -85,25 +92,43 @@ float density(vec3 worldPos) worldPos.y < WORLD_BOTTOM || worldPos.y >= WORLD_BOTTOM + WORLD_HEIGHT ){ - return 0.0; + return vec3(-1.0); } - float physicalX = - localX + float(RingBlockOffset.x); - - float physicalZ = - localZ + float(RingBlockOffset.y); + float physicalX = localX + float(RingBlockOffset.x); + float physicalZ = localZ + float(RingBlockOffset.y); + vec3 uv; uv.x = physicalX / FOG_SIZE_X; uv.z = physicalZ / FOG_SIZE_Z; uv.y = (worldPos.y - WORLD_BOTTOM) / WORLD_HEIGHT; + return uv; +} + +float density(vec3 worldPos) +{ + vec3 uv = worldToUV(worldPos); + if (uv.x < 0.0) return 0.0; return texture(FogVolume, uv).r; } +vec2 light(vec3 worldPos) +{ + vec3 uv = worldToUV(worldPos); + if (uv.x < 0.0) return vec2(0.0); + + vec2 rg = texture(LightVolume, uv).rg; + float blockNorm = rg.r; + float skyNorm = rg.g; + + float skyContribution = skyNorm * SkyBrightness; + + return vec2(skyContribution, blockNorm); +} + //------------------------------------------------------------ -// Псевдослучайное значение для jittering (детерминированное по UV) float random(vec2 st) { vec3 p3 = fract(vec3(st.xyx) * 0.1031); @@ -111,8 +136,6 @@ float random(vec2 st) return fract((p3.x + p3.y) * p3.z); } -// Функция нелинейного маппинга параметра t [0,1] в расстояние -// Использует степенную функцию для распределения шагов float mapParameterToDistance(float t) { return MAX_DISTANCE * pow(t, GAMMA); @@ -125,22 +148,26 @@ 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 + // Transmittance: сколько света от сцены дошло до текущего шага + // Начинается с 1.0 (всё доходит) и экспоненциально падает + float transmittance = 1.0; + + // Накопленный in-scattered свет (взвешенный по transmittance) + float skyScattered = 0.0; + float blockScattered = 0.0; + + float ambientScattered = 0.0; + float jitter = 0.0; if (USE_JITTERING) { - // Вычисляем размер первого шага для корректного jittering float firstStep = mapParameterToDistance(1.0 / float(MAX_STEPS)); jitter = random(texCoord) * firstStep; currentDistance = jitter; @@ -148,44 +175,74 @@ void main() } 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; + float d = density(samplePos); + + // Оптическая толщина этого шага + float opticalDepth = d * stepSize * EXTINCTION_COEFF; + + if (d > 0.0) { + // Получаем освещение в текущей точке + vec2 lightValues = light(samplePos); + float totalLightAtPoint = lightValues.x + lightValues.y; + + // Базовая формула in-scattering для каждого источника + float baseScattering = d * stepSize * SCATTERING_COEFF; + + // In-scattering: свет, рассеянный в камерy из этого шага. + // Умножается на transmittance до этого шага (дальний туман затенён ближним). + // Умножается на плотность (больше частиц = больше рассеяния). + float stepScattering = totalLightAtPoint * d * stepSize * SCATTERING_COEFF; + + // Вклад ослабляется текущим transmittance + skyScattered += lightValues.x * baseScattering * transmittance; + blockScattered += lightValues.y * baseScattering * transmittance; + ambientScattered += AMBIENT_INTENSITY * baseScattering * transmittance; + + // Обновляем transmittance: Beer-Lambert law + transmittance *= exp(-opticalDepth); + } currentDistance = nextDistance; - // Прерываем, если достигли конца луча + // Early termination: если transmittance упал почти до нуля, + // дальние шаги ничего не внесут в итоговый цвет + if (transmittance < MIN_TRANSMITTANCE) { + transmittance = 0.0; + break; + } + if (currentDistance >= rayLength) { break; } } - fog = clamp(fog, 0.0, 0.95); + // Суммарное освещение, рассеянное в камеру + float totalScattered = skyScattered + blockScattered + ambientScattered; - FragColor = vec4( - mix(sceneColor.rgb, FogColor*SkyBrightness, fog), - 1.0 - ); + // Финальный цвет: + // - sceneColor × transmittance: сцена, ослабленная туманом (Beer-Lambert) + // - FogColor × totalScattered: свет, который рассеялся в тумане и попал в камеру + // + // Эта формула физически корректна: + // - Когда тумана нет (transmittance=1, scattered=0): result = sceneColor ✓ + // - Когда туман очень плотный (transmittance=0): result = FogColor × scattered ✓ + // - Фонарь в толще тумана: ближние слои дают scattered, дальние уже не видны + vec3 finalColor = sceneColor.rgb * transmittance + FogColor * totalScattered; + + FragColor = vec4(finalColor, 1.0); } \ No newline at end of file