From 1bcbcf6d60b8536f4f58c303b31236bc9ce1f785 Mon Sep 17 00:00:00 2001 From: DIvan2000 Date: Sat, 8 Aug 2026 03:17:19 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D0=BA=D0=B0=D1=80?= =?UTF-8?q?=D1=82=D1=8B=20=D0=BE=D1=81=D0=B2=D0=B5=D1=89=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/render/PostProcessingManager.java | 16 +- .../client/render/fog/FogLightProvider.java | 74 ++-- .../client/render/fog/FogLightStreamer.java | 333 ++++++++++-------- .../client/render/fog/FogLightVolume.java | 314 +++++++++-------- .../veila/mixin/client/LightStorageMixin.java | 42 ++- .../assets/veila/shaders/fog_raymarching.frag | 12 +- 6 files changed, 441 insertions(+), 350 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 641d191..1dd88ea 100644 --- a/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java +++ b/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java @@ -48,7 +48,8 @@ public class PostProcessingManager { private static int rm_inverseProjectionLocation; private static int rm_depthSamplerLocation; private static int rm_fogVolumeLocation; - private static int rm_lightVolumeLocation; + private static int rm_skyLightVolumeLocation; + private static int rm_blockLightVolumeLocation; private static int rm_fogOriginLocation; private static int rm_ringOffsetLocation; private static int rm_cameraPositionLocation; @@ -105,7 +106,8 @@ public class PostProcessingManager { rm_inverseProjectionLocation = raymarchProgram.uniform("InverseProjection"); rm_depthSamplerLocation = raymarchProgram.uniform("DepthSampler"); rm_fogVolumeLocation = raymarchProgram.uniform("FogVolume"); - rm_lightVolumeLocation = raymarchProgram.uniform("LightVolume"); + rm_skyLightVolumeLocation = raymarchProgram.uniform("SkyLightVolume"); + rm_blockLightVolumeLocation = raymarchProgram.uniform("BlockLightVolume"); rm_fogOriginLocation = raymarchProgram.uniform("FogOrigin"); rm_ringOffsetLocation = raymarchProgram.uniform("RingBlockOffset"); rm_cameraPositionLocation = raymarchProgram.uniform("CameraPosition"); @@ -280,8 +282,12 @@ public class PostProcessingManager { // Light Volume GL13.glActiveTexture(GL13.GL_TEXTURE2); - GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getTexture()); - GL20.glUniform1i(rm_lightVolumeLocation, 2); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getSkyLightTexture()); + GL20.glUniform1i(rm_skyLightVolumeLocation, 2); + + GL13.glActiveTexture(GL13.GL_TEXTURE3); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getBlockLightTexture()); + GL20.glUniform1i(rm_blockLightVolumeLocation, 3); GL20.glUniform2i(rm_fogOriginLocation, FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE, @@ -315,6 +321,8 @@ public class PostProcessingManager { FullscreenQuad.draw(); // Очистка текстур + GL13.glActiveTexture(GL13.GL_TEXTURE3); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); GL13.glActiveTexture(GL13.GL_TEXTURE2); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); GL13.glActiveTexture(GL13.GL_TEXTURE1); 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 93f5995..81f8d6a 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 @@ -8,72 +8,62 @@ import net.minecraft.world.chunk.light.LightingProvider; public final class FogLightProvider { - // Размер массива для одной секции: 16×16×16×2 = 8192 (×2 для двух каналов) - public static final int SECTION_DATA_SIZE = 16 * 16 * 16 * 2; - - // Pre-allocated buffer для чтения одной секции из LightingProvider - // используется в fillSectionIntoArray для избежания аллокаций - private static final byte[] SECTION_BUFFER = new byte[SECTION_DATA_SIZE]; - - // Флаг для отслеживания используется ли буфер сейчас (защита от race condition) - // Так как fillSectionIntoArray может вызываться из разных потоков, - // нужен второй буфер для параллельной работы - private static final byte[] SECTION_BUFFER_ALT = new byte[SECTION_DATA_SIZE]; - private static volatile boolean bufferInUse = false; + public static final int SECTION_DATA_SIZE = 16 * 16 * 16; private FogLightProvider() { } - public static byte[] fillSectionIntoArray(int sectionX, int sectionY, int sectionZ) { + public static byte[] fillBlockLightIntoArray(int sectionX, int sectionY, int sectionZ) { MinecraftClient client = MinecraftClient.getInstance(); if (client.world == null) return null; LightingProvider provider = client.world.getLightingProvider(); - ChunkLightProvider blockProvider = provider.blockLightProvider; - ChunkLightProvider skyProvider = provider.skyLightProvider; - - if (blockProvider == null || skyProvider == null) { - return null; - } + if (blockProvider == null) return null; ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ); - ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos); - ChunkNibbleArray skyArray = skyProvider.getLightSection(sectionPos); - if (blockArray == null && skyArray == null) { - return null; - } - - // Используем один из двух pre-allocated буферов (double buffering) - byte[] result; - if (!bufferInUse) { - bufferInUse = true; - result = SECTION_BUFFER; - } else { - result = SECTION_BUFFER_ALT; - } + if (blockArray == null) return null; + // СОЗДАЁМ НОВЫЙ массив, а не используем ThreadLocal + byte[] result = new byte[SECTION_DATA_SIZE]; int index = 0; 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; - - FogLightVolume.packLight(blockLight, skyLight, result, index); - index += 2; + result[index++] = (byte) (blockArray.get(localX, localY, localZ) * 17); } } } + return result; + } - // Освобождаем буфер - if (result == SECTION_BUFFER) { - bufferInUse = false; + public static byte[] fillSkyLightIntoArray(int sectionX, int sectionY, int sectionZ) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.world == null) return null; + + LightingProvider provider = client.world.getLightingProvider(); + ChunkLightProvider skyProvider = provider.skyLightProvider; + if (skyProvider == null) return null; + + ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ); + ChunkNibbleArray skyArray = skyProvider.getLightSection(sectionPos); + + if (skyArray == null) return null; + + // СОЗДАЁМ НОВЫЙ массив, а не используем ThreadLocal + byte[] result = new byte[SECTION_DATA_SIZE]; + int index = 0; + + for (int localZ = 0; localZ < 16; localZ++) { + for (int localY = 0; localY < 16; localY++) { + for (int localX = 0; localX < 16; localX++) { + result[index++] = (byte) (skyArray.get(localX, localY, localZ) * 17); + } + } } - return result; } } \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogLightStreamer.java b/src/client/java/su/divan2000/veila/client/render/fog/FogLightStreamer.java index 7215073..78a3b80 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogLightStreamer.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogLightStreamer.java @@ -1,5 +1,6 @@ package su.divan2000.veila.client.render.fog; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLongArray; public final class FogLightStreamer { @@ -7,19 +8,27 @@ public final class FogLightStreamer { public static final int SECTION_SIZE = 16; public static final int SECTIONS_PER_CHUNK_Y = FogLightVolume.SIZE_Y / SECTION_SIZE; - // Общее количество секций в окне: 16 чанков × 16 чанков × 24 секции = 6144 private static final int TOTAL_SECTIONS = FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z * SECTIONS_PER_CHUNK_Y; - // Битовая маска: 6144 бита = 96 long = 768 байт - private static final AtomicLongArray pendingSectionsMask = + private static final AtomicLongArray pendingBlockMask = + new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); + private static final AtomicLongArray pendingSkyMask = new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); - private static final AtomicLongArray readySectionsMask = + private static final AtomicLongArray readyBlockMask = + new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); + private static final AtomicLongArray readySkyMask = new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); - private static final AtomicLongArray initialLoadMask = + private static final AtomicLongArray initialBlockMask = new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); + private static final AtomicLongArray initialSkyMask = + new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); + + // Храним данные вместе с мировыми координатами секции + private static final ConcurrentHashMap preparedBlockData = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap preparedSkyData = new ConcurrentHashMap<>(); private static final int MAX_SECTIONS_PER_FRAME = 64; private static final int MAX_INITIAL_SECTIONS_PER_TICK = 96; @@ -38,19 +47,26 @@ public final class FogLightStreamer { private static final int[] regionDepth = new int[MAX_REGIONS]; private static int regionCount = 0; + private static class PreparedSection { + final byte[] data; + final int worldSectionX; + final int worldSectionY; + final int worldSectionZ; + + PreparedSection(byte[] data, int worldSectionX, int worldSectionY, int worldSectionZ) { + this.data = data; + this.worldSectionX = worldSectionX; + this.worldSectionY = worldSectionY; + this.worldSectionZ = worldSectionZ; + } + } + private FogLightStreamer() { } - /** - * Конвертирует мировые координаты секции в локальный индекс в маске. - * Возвращает -1 если секция вне текущего окна. - */ private static int sectionToLocalIndex(int sectionX, int sectionY, int sectionZ) { - int chunkX = sectionX; - int chunkZ = sectionZ; - - int relativeX = chunkX - currentOriginChunkX; - int relativeZ = chunkZ - currentOriginChunkZ; + int relativeX = sectionX - currentOriginChunkX; + int relativeZ = sectionZ - currentOriginChunkZ; if (relativeX < 0 || relativeX >= FogLightVolume.CHUNKS_X || relativeZ < 0 || relativeZ >= FogLightVolume.CHUNKS_Z) { @@ -70,77 +86,65 @@ public final class FogLightStreamer { + physicalX; } - /** - * Устанавливает бит в маске атомарно. - */ private static boolean setBit(AtomicLongArray mask, int index) { int wordIndex = index >> 6; long bitMask = 1L << (index & 63); while (true) { long current = mask.get(wordIndex); - if ((current & bitMask) != 0) { - return false; // Бит уже установлен - } - if (mask.compareAndSet(wordIndex, current, current | bitMask)) { - return true; - } - // CAS не удался - повторяем + if ((current & bitMask) != 0) return false; + if (mask.compareAndSet(wordIndex, current, current | bitMask)) return true; } } - /** - * Снимает бит из маски атомарно. - */ private static boolean clearBit(AtomicLongArray mask, int index) { int wordIndex = index >> 6; long bitMask = 1L << (index & 63); while (true) { long current = mask.get(wordIndex); - if ((current & bitMask) == 0) { - return false; // Бит уже снят - } - if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) { - return true; - } + if ((current & bitMask) == 0) return false; + if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return true; } } - /** - * Проверяет и снимает бит (для итерации). - * Возвращает индекс если бит был установлен. - */ private static int nextSetBit(AtomicLongArray mask, int startIndex) { for (int i = startIndex; i < TOTAL_SECTIONS; i++) { int wordIndex = i >> 6; long bitMask = 1L << (i & 63); - - if ((mask.get(wordIndex) & bitMask) != 0) { - return i; - } + if ((mask.get(wordIndex) & bitMask) != 0) return i; } return -1; } - public static void onSectionUpdated(long sectionPos) { + public static void onBlockLightUpdated(long sectionPos) { int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos); int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos); int sectionZ = net.minecraft.util.math.ChunkSectionPos.unpackZ(sectionPos); int index = sectionToLocalIndex(sectionX, sectionY, sectionZ); if (index >= 0) { - setBit(pendingSectionsMask, index); + preparedBlockData.remove(index); + setBit(pendingBlockMask, index); + } + } + + public static void onSkyLightUpdated(long sectionPos) { + int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos); + int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos); + int sectionZ = net.minecraft.util.math.ChunkSectionPos.unpackZ(sectionPos); + + int index = sectionToLocalIndex(sectionX, sectionY, sectionZ); + if (index >= 0) { + preparedSkyData.remove(index); + setBit(pendingSkyMask, index); } } public static void update( - int oldOriginChunkX, - int oldOriginChunkZ, - int newOriginChunkX, - int newOriginChunkZ, - int ringChunkOffsetX, - int ringChunkOffsetZ + int oldOriginChunkX, int oldOriginChunkZ, + int newOriginChunkX, int newOriginChunkZ, + int ringChunkOffsetX, int ringChunkOffsetZ ) { currentOriginChunkX = newOriginChunkX; currentOriginChunkZ = newOriginChunkZ; @@ -156,12 +160,9 @@ public final class FogLightStreamer { int dx = newOriginChunkX - oldOriginChunkX; int dz = newOriginChunkZ - oldOriginChunkZ; - if (dx == 0 && dz == 0) { - return; - } + if (dx == 0 && dz == 0) return; - if (Math.abs(dx) >= FogLightVolume.CHUNKS_X || - Math.abs(dz) >= FogLightVolume.CHUNKS_Z) { + if (Math.abs(dx) >= FogLightVolume.CHUNKS_X || Math.abs(dz) >= FogLightVolume.CHUNKS_Z) { markAllPending(); return; } @@ -170,30 +171,21 @@ public final class FogLightStreamer { if (dx > 0) { int worldX = newOriginChunkX + FogLightVolume.CHUNKS_X - dx; - int worldZ = newOriginChunkZ; - collectRegions(worldX, worldZ, dx, FogLightVolume.CHUNKS_Z); - addInitialLoadForRange(worldX, worldZ, dx, FogLightVolume.CHUNKS_Z); + collectRegions(worldX, newOriginChunkZ, dx, FogLightVolume.CHUNKS_Z); + addInitialLoadForRange(worldX, newOriginChunkZ, dx, FogLightVolume.CHUNKS_Z); } - if (dx < 0) { - int worldX = newOriginChunkX; - int worldZ = newOriginChunkZ; - collectRegions(worldX, worldZ, -dx, FogLightVolume.CHUNKS_Z); - addInitialLoadForRange(worldX, worldZ, -dx, FogLightVolume.CHUNKS_Z); + collectRegions(newOriginChunkX, newOriginChunkZ, -dx, FogLightVolume.CHUNKS_Z); + addInitialLoadForRange(newOriginChunkX, newOriginChunkZ, -dx, FogLightVolume.CHUNKS_Z); } - if (dz > 0) { - int worldX = newOriginChunkX; int worldZ = newOriginChunkZ + FogLightVolume.CHUNKS_Z - dz; - collectRegions(worldX, worldZ, FogLightVolume.CHUNKS_X, dz); - addInitialLoadForRange(worldX, worldZ, FogLightVolume.CHUNKS_X, dz); + collectRegions(newOriginChunkX, worldZ, FogLightVolume.CHUNKS_X, dz); + addInitialLoadForRange(newOriginChunkX, worldZ, FogLightVolume.CHUNKS_X, dz); } - if (dz < 0) { - int worldX = newOriginChunkX; - int worldZ = newOriginChunkZ; - collectRegions(worldX, worldZ, FogLightVolume.CHUNKS_X, -dz); - addInitialLoadForRange(worldX, worldZ, FogLightVolume.CHUNKS_X, -dz); + collectRegions(newOriginChunkX, newOriginChunkZ, FogLightVolume.CHUNKS_X, -dz); + addInitialLoadForRange(newOriginChunkX, newOriginChunkZ, FogLightVolume.CHUNKS_X, -dz); } if (regionCount > 0) { @@ -204,17 +196,11 @@ public final class FogLightStreamer { private static void addInitialLoadForRange(int worldX, int worldZ, int countX, int countZ) { for (int i = 0; i < countX; i++) { for (int j = 0; j < countZ; j++) { - int chunkX = worldX + i; - int chunkZ = worldZ + j; - for (int sectionY = 0; sectionY < SECTIONS_PER_CHUNK_Y; sectionY++) { - int sectionPos = sectionToLocalIndex( - chunkX, - sectionY + (FogWorldVolume.MIN_Y >> 4), - chunkZ - ); - if (sectionPos >= 0) { - setBit(initialLoadMask, sectionPos); + int index = sectionToLocalIndex(worldX + i, sectionY + (FogWorldVolume.MIN_Y >> 4), worldZ + j); + if (index >= 0) { + setBit(initialBlockMask, index); + setBit(initialSkyMask, index); } } } @@ -230,15 +216,19 @@ public final class FogLightStreamer { FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount); - // Устанавливаем все биты в initialLoadMask - for (int i = 0; i < pendingSectionsMask.length(); i++) { - initialLoadMask.set(i, -1L); // Все биты = 1 + preparedBlockData.clear(); + preparedSkyData.clear(); + + for (int i = 0; i < pendingBlockMask.length(); i++) { + initialBlockMask.set(i, -1L); + initialSkyMask.set(i, -1L); } - // Очищаем лишние биты в последнем слове int totalWords = (TOTAL_SECTIONS + 63) / 64; int extraBits = TOTAL_SECTIONS & 63; if (extraBits != 0) { - initialLoadMask.set(totalWords - 1, (1L << extraBits) - 1); + long mask = (1L << extraBits) - 1; + initialBlockMask.set(totalWords - 1, mask); + initialSkyMask.set(totalWords - 1, mask); } } @@ -263,15 +253,10 @@ public final class FogLightStreamer { } } - private static int splitIntoSegments( - int start, int count, int dim, - int[] outStarts, int[] outCounts - ) { + private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) { if (count == 0) return 0; - int first = Math.floorMod(start, dim); int last = Math.floorMod(start + count - 1, dim); - if (first <= last) { outStarts[0] = first; outCounts[0] = count; @@ -286,86 +271,105 @@ public final class FogLightStreamer { } } - /** - * Подготавливает все секции для загрузки (client thread). - * Обрабатывает и initialLoadMask, и pendingSectionsMask. - * Вызывается из FogSystem.tick(). - */ public static void prepareAllSections() { int processed = 0; - // Обрабатываем initial load + // Initial load - block light int index = 0; while (processed < MAX_INITIAL_SECTIONS_PER_TICK) { - index = nextSetBit(initialLoadMask, index); + index = nextSetBit(initialBlockMask, index); if (index < 0) break; - - if (prepareSectionByIndex(index)) { - clearBit(initialLoadMask, index); + if (prepareBlockLight(index)) { + clearBit(initialBlockMask, index); processed++; } index++; } - // Обрабатываем инкрементальные обновления + // Initial load - sky light index = 0; - while (processed < MAX_INITIAL_SECTIONS_PER_TICK + MAX_SECTIONS_PER_FRAME) { - index = nextSetBit(pendingSectionsMask, index); + while (processed < MAX_INITIAL_SECTIONS_PER_TICK * 2) { + index = nextSetBit(initialSkyMask, index); if (index < 0) break; + if (prepareSkyLight(index)) { + clearBit(initialSkyMask, index); + processed++; + } + index++; + } - if (prepareSectionByIndex(index)) { - clearBit(pendingSectionsMask, index); + // Incremental - block light + index = 0; + while (processed < MAX_INITIAL_SECTIONS_PER_TICK * 2 + MAX_SECTIONS_PER_FRAME) { + index = nextSetBit(pendingBlockMask, index); + if (index < 0) break; + if (prepareBlockLight(index)) { + clearBit(pendingBlockMask, index); + processed++; + } + index++; + } + + // Incremental - sky light + index = 0; + while (processed < MAX_INITIAL_SECTIONS_PER_TICK * 2 + MAX_SECTIONS_PER_FRAME * 2) { + index = nextSetBit(pendingSkyMask, index); + if (index < 0) break; + if (prepareSkyLight(index)) { + clearBit(pendingSkyMask, index); processed++; } index++; } } - /** - * Загружает готовые секции в текстуру (render thread). - * Только OpenGL вызовы, без чтения из LightingProvider. - * Вызывается из FogSystem.beginRender(). - */ public static void uploadReadySections() { int processed = 0; int index = 0; while (processed < MAX_SECTIONS_PER_FRAME + MAX_INITIAL_SECTIONS_PER_TICK) { - index = nextSetBit(readySectionsMask, index); + index = nextSetBit(readyBlockMask, index); if (index < 0) break; + if (uploadBlockLight(index)) { + clearBit(readyBlockMask, index); + processed++; + } + index++; + } - if (uploadSectionByIndex(index)) { - clearBit(readySectionsMask, index); + index = 0; + while (processed < (MAX_SECTIONS_PER_FRAME + MAX_INITIAL_SECTIONS_PER_TICK) * 2) { + index = nextSetBit(readySkyMask, index); + if (index < 0) break; + if (uploadSkyLight(index)) { + clearBit(readySkyMask, index); processed++; } index++; } } - /** - * Читает данные секции из LightingProvider и помечает как готовую к загрузке. - * Вызывается из prepareAllSections() в client thread. - */ - private static boolean prepareSectionByIndex(int index) { - int sectionX = currentOriginChunkX + (index % FogLightVolume.CHUNKS_X); - int sectionZ = currentOriginChunkZ + ((index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z); - int sectionY = (index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z)) + (FogWorldVolume.MIN_Y >> 4); + private static boolean prepareBlockLight(int index) { + // Извлекаем физические координаты + int physicalX = index % FogLightVolume.CHUNKS_X; + int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z; + int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z); - byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ); - if (sectionData == null) { - return false; // Секция не готова, пробуем позже - } + // Конвертируем в мировые координаты + int sectionX = currentOriginChunkX + Math.floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X); + int sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); + int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4); - // Помечаем как готовую к загрузке в render thread - setBit(readySectionsMask, index); + byte[] data = FogLightProvider.fillBlockLightIntoArray(sectionX, sectionY, sectionZ); + if (data == null) return false; + + // Сохраняем данные вместе с мировыми координатами + preparedBlockData.put(index, new PreparedSection(data, sectionX, sectionY, sectionZ)); + setBit(readyBlockMask, index); return true; } - /** - * Загружает готовую секцию в текстуру через OpenGL. - * Вызывается из uploadReadySections() в render thread. - */ - private static boolean uploadSectionByIndex(int index) { + private static boolean prepareSkyLight(int index) { int physicalX = index % FogLightVolume.CHUNKS_X; int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z; int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z); @@ -374,13 +378,66 @@ public final class FogLightStreamer { int sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4); - // Читаем данные повторно (они должны быть доступны быстро, так как уже в кэше) - byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ); - if (sectionData == null) { + byte[] data = FogLightProvider.fillSkyLightIntoArray(sectionX, sectionY, sectionZ); + if (data == null) return false; + + preparedSkyData.put(index, new PreparedSection(data, sectionX, sectionY, sectionZ)); + setBit(readySkyMask, index); + return true; + } + + private static boolean uploadBlockLight(int index) { + int physicalX = index % FogLightVolume.CHUNKS_X; + int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z; + int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z); + + PreparedSection prepared = preparedBlockData.remove(index); + if (prepared == null) { + setBit(pendingBlockMask, index); return false; } - FogLightVolume.uploadSection(physicalX, localSectionY, physicalZ, sectionData); + // Вычисляем текущие мировые координаты для этой физической позиции + int currentWorldX = currentOriginChunkX + Math.floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X); + int currentWorldZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); + int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4); + + // Проверяем, что мировые координаты не изменились + if (prepared.worldSectionX != currentWorldX || + prepared.worldSectionY != currentWorldY || + prepared.worldSectionZ != currentWorldZ) { + // Координаты изменились — отбрасываем данные и помечаем как pending + setBit(pendingBlockMask, index); + return false; + } + + FogLightVolume.uploadBlockLightSection(physicalX, localSectionY, physicalZ, prepared.data); + return true; + } + + private static boolean uploadSkyLight(int index) { + int physicalX = index % FogLightVolume.CHUNKS_X; + int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z; + int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z); + + PreparedSection prepared = preparedSkyData.remove(index); + if (prepared == null) { + setBit(pendingSkyMask, index); + return false; + } + + int currentWorldX = currentOriginChunkX + Math.floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X); + int currentWorldZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z); + int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4); + + if (prepared.worldSectionX != currentWorldX || + prepared.worldSectionY != currentWorldY || + prepared.worldSectionZ != currentWorldZ) { + setBit(pendingSkyMask, index); + return false; + } + + FogLightVolume.uploadSkyLightSection(physicalX, localSectionY, physicalZ, prepared.data); return true; } } \ 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 8213755..86cc085 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 @@ -18,63 +18,68 @@ public final class FogLightVolume { public static final int CHUNKS_X = FogWorldVolume.CHUNKS_X; public static final int CHUNKS_Z = FogWorldVolume.CHUNKS_Z; - private static int texture = -1; + // Две отдельные одноканальные текстуры + private static int blockLightTexture = -1; + private static int skyLightTexture = -1; - // Дефолтное освещение: blockLight=0, skyLight=15 (ночь, открытое небо) - // Теперь это vec2, упакованный в RG8 - public static final byte DEFAULT_BLOCK_LIGHT = (byte) 0; - public static final byte DEFAULT_SKY_LIGHT = (byte) 255; + private static final byte DEFAULT_BLOCK_LIGHT = (byte) 0; + private static final byte DEFAULT_SKY_LIGHT = (byte) 255; - private static final byte[] DEFAULT_ARRAY; + private static final byte[] DEFAULT_BLOCK_ARRAY; + private static final byte[] DEFAULT_SKY_ARRAY; static { - 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 - } + int sectionVoxels = CHUNK_SIZE * SIZE_Y * CHUNK_SIZE; + DEFAULT_BLOCK_ARRAY = new byte[sectionVoxels]; + DEFAULT_SKY_ARRAY = new byte[sectionVoxels]; + java.util.Arrays.fill(DEFAULT_BLOCK_ARRAY, DEFAULT_BLOCK_LIGHT); + java.util.Arrays.fill(DEFAULT_SKY_ARRAY, DEFAULT_SKY_LIGHT); } private static final ByteBuffer CHUNK_BUFFER = - BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE * 2); + BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE); - private static final ByteBuffer SINGLE_VOXEL_BUFFER = - BufferUtils.createByteBuffer(2); + private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z; // PBO для быстрой очистки - private static int clearPBO = -1; - private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z * 2; // ×2 для двух каналов + private static int clearBlockPBO = -1; + private static int clearSkyPBO = -1; - // Pre-allocated buffer для одной секции (4096 вокселей × 2 байта = 8192 байта) -// Используется для загрузки секций через uploadSection - private static final ByteBuffer SECTION_UPLOAD_BUFFER = + // Буферы для загрузки секций + private static final ByteBuffer BLOCK_SECTION_UPLOAD_BUFFER = BufferUtils.createByteBuffer(FogLightStreamer.SECTION_SIZE * FogLightStreamer.SECTION_SIZE - * FogLightStreamer.SECTION_SIZE * 2); + * FogLightStreamer.SECTION_SIZE); + + private static final ByteBuffer SKY_SECTION_UPLOAD_BUFFER = + BufferUtils.createByteBuffer(FogLightStreamer.SECTION_SIZE + * FogLightStreamer.SECTION_SIZE + * FogLightStreamer.SECTION_SIZE); private FogLightVolume() { } - /** - * Упаковывает 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() { - if (clearPBO != -1) return; - - clearPBO = GL15.glGenBuffers(); - GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO); + private static void initClearPBOs() { + if (clearBlockPBO != -1) return; + // Block light PBO + clearBlockPBO = GL15.glGenBuffers(); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearBlockPBO); GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE, GL15.GL_STATIC_DRAW); - ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY); if (mapped != null) { for (int i = 0; i < SIZE_X * SIZE_Y * SIZE_Z; i++) { mapped.put(DEFAULT_BLOCK_LIGHT); + } + GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); + } + + // Sky light PBO + clearSkyPBO = GL15.glGenBuffers(); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearSkyPBO); + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE, GL15.GL_STATIC_DRAW); + mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY); + if (mapped != null) { + for (int i = 0; i < SIZE_X * SIZE_Y * SIZE_Z; i++) { mapped.put(DEFAULT_SKY_LIGHT); } GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); @@ -84,136 +89,139 @@ public final class FogLightVolume { } public static void init() { - if (texture != -1) return; + if (blockLightTexture != -1) return; - initClearPBO(); + initClearPBOs(); - texture = GL11.glGenTextures(); - - GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); - - // ИСПРАВЛЕНО: GL_RG8 вместо GL_R8UI, два канала для двух типов света + // Block light texture + blockLightTexture = GL11.glGenTextures(); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture); GL30.glTexImage3D( - GL12.GL_TEXTURE_3D, - 0, - GL30.GL_RG8, - SIZE_X, - SIZE_Y, - SIZE_Z, - 0, - GL30.GL_RG, - GL11.GL_UNSIGNED_BYTE, - (ByteBuffer) null + GL12.GL_TEXTURE_3D, 0, GL30.GL_R8, + SIZE_X, SIZE_Y, SIZE_Z, 0, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null ); - - // 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); GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_R, GL12.GL_REPEAT); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + // Sky light texture + skyLightTexture = GL11.glGenTextures(); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture); + GL30.glTexImage3D( + GL12.GL_TEXTURE_3D, 0, GL30.GL_R8, + SIZE_X, SIZE_Y, SIZE_Z, 0, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null + ); + 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); + GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_R, GL12.GL_REPEAT); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); clear(); } public static void clear() { + // Clear block light texture CHUNK_BUFFER.clear(); - CHUNK_BUFFER.put(DEFAULT_ARRAY); + CHUNK_BUFFER.put(DEFAULT_BLOCK_ARRAY); CHUNK_BUFFER.flip(); - GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); - + GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture); for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) { for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) { - uploadChunk(chunkX, chunkZ, CHUNK_BUFFER); + uploadChunkInternal(blockLightTexture, chunkX, chunkZ, CHUNK_BUFFER); CHUNK_BUFFER.rewind(); } } - GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); - } - public static void uploadChunk(int textureChunkX, int textureChunkZ, ByteBuffer data) { - 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, - textureChunkX * CHUNK_SIZE, - 0, - textureChunkZ * CHUNK_SIZE, - CHUNK_SIZE, - SIZE_Y, - CHUNK_SIZE, - GL30.GL_RG, // ИСПРАВЛЕНО: GL_RG вместо GL_RED_INTEGER - GL11.GL_UNSIGNED_BYTE, - data - ); - - GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); - } - - public static void uploadSection( - int textureSectionX, - int textureSectionY, - int textureSectionZ, - byte[] data - ) { - // Переиспользуем статический буфер вместо создания нового - SECTION_UPLOAD_BUFFER.clear(); - SECTION_UPLOAD_BUFFER.put(data); - SECTION_UPLOAD_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 * 16, - textureSectionZ * CHUNK_SIZE, - 16, - 16, - 16, - GL30.GL_RG, - GL11.GL_UNSIGNED_BYTE, - SECTION_UPLOAD_BUFFER - ); - - GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); - } - - public static void uploadChunkFromArray( - int textureChunkX, - int textureChunkZ, - byte[] data - ) { - ByteBuffer buffer = chunkBuffer(); - buffer.put(data); - buffer.flip(); - uploadChunk(textureChunkX, textureChunkZ, buffer); - } - - public static ByteBuffer chunkBuffer() { + // Clear sky light texture CHUNK_BUFFER.clear(); - return CHUNK_BUFFER; + CHUNK_BUFFER.put(DEFAULT_SKY_ARRAY); + CHUNK_BUFFER.flip(); + + GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture); + for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) { + for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) { + uploadChunkInternal(skyLightTexture, chunkX, chunkZ, CHUNK_BUFFER); + CHUNK_BUFFER.rewind(); + } + } + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); } - public static int getTexture() { - return texture; + private static void uploadChunkInternal(int texture, int textureChunkX, int textureChunkZ, ByteBuffer data) { + 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, + textureChunkX * CHUNK_SIZE, 0, textureChunkZ * CHUNK_SIZE, + CHUNK_SIZE, SIZE_Y, CHUNK_SIZE, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, data + ); + } + + public static void uploadBlockLightSection( + int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data + ) { + BLOCK_SECTION_UPLOAD_BUFFER.clear(); + BLOCK_SECTION_UPLOAD_BUFFER.put(data); + BLOCK_SECTION_UPLOAD_BUFFER.flip(); + + GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture); + 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 * 16, textureSectionZ * CHUNK_SIZE, + 16, 16, 16, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, BLOCK_SECTION_UPLOAD_BUFFER + ); + + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + } + + public static void uploadSkyLightSection( + int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data + ) { + SKY_SECTION_UPLOAD_BUFFER.clear(); + SKY_SECTION_UPLOAD_BUFFER.put(data); + SKY_SECTION_UPLOAD_BUFFER.flip(); + + GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture); + 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 * 16, textureSectionZ * CHUNK_SIZE, + 16, 16, 16, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SKY_SECTION_UPLOAD_BUFFER + ); + + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + } + + public static int getBlockLightTexture() { + return blockLightTexture; + } + + public static int getSkyLightTexture() { + return skyLightTexture; } public static void clearRegions( @@ -221,32 +229,42 @@ public final class FogLightVolume { int[] widthChunks, int[] depthChunks, int count ) { - GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); - GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO); - 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); + // Clear block light + GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearBlockPBO); for (int i = 0; i < count; i++) { int w = widthChunks[i] * CHUNK_SIZE; int h = SIZE_Y; int d = depthChunks[i] * CHUNK_SIZE; - GL30.glTexSubImage3D( - GL12.GL_TEXTURE_3D, - 0, - physX[i] * CHUNK_SIZE, - 0, - physZ[i] * CHUNK_SIZE, + GL12.GL_TEXTURE_3D, 0, + physX[i] * CHUNK_SIZE, 0, physZ[i] * CHUNK_SIZE, w, h, d, - GL30.GL_RG, - GL11.GL_UNSIGNED_BYTE, - 0L + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L ); } + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + // Clear sky light + GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearSkyPBO); + for (int i = 0; i < count; i++) { + int w = widthChunks[i] * CHUNK_SIZE; + int h = SIZE_Y; + int d = depthChunks[i] * CHUNK_SIZE; + GL30.glTexSubImage3D( + GL12.GL_TEXTURE_3D, 0, + physX[i] * CHUNK_SIZE, 0, physZ[i] * CHUNK_SIZE, + w, h, d, + GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L + ); + } GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); } diff --git a/src/client/java/su/divan2000/veila/mixin/client/LightStorageMixin.java b/src/client/java/su/divan2000/veila/mixin/client/LightStorageMixin.java index 3ffe507..b38f664 100644 --- a/src/client/java/su/divan2000/veila/mixin/client/LightStorageMixin.java +++ b/src/client/java/su/divan2000/veila/mixin/client/LightStorageMixin.java @@ -1,10 +1,13 @@ package su.divan2000.veila.mixin.client; import net.minecraft.util.math.ChunkSectionPos; +import net.minecraft.world.LightType; import net.minecraft.world.chunk.ChunkNibbleArray; +import net.minecraft.world.chunk.light.ChunkLightProvider; import net.minecraft.world.chunk.light.LightStorage; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -13,25 +16,38 @@ import su.divan2000.veila.client.render.fog.FogLightStreamer; @Mixin(LightStorage.class) public class LightStorageMixin { - // Ловим инкрементальные обновления (когда блок меняет свет) - @Inject( - method = "set(JI)V", - at = @At("TAIL") - ) + @Shadow + private LightType lightType; + + @Inject(method = "set(JI)V", at = @At("TAIL")) private void veila$onLightSet(long blockPos, int value, CallbackInfo ci) { long sectionPos = ChunkSectionPos.fromBlockPos(blockPos); - FogLightStreamer.onSectionUpdated(sectionPos); + if (this.lightType == LightType.BLOCK) { + FogLightStreamer.onBlockLightUpdated(sectionPos); + } else { + FogLightStreamer.onSkyLightUpdated(sectionPos); + } } - // Ловим загрузку секций из сохранения - @Inject( - method = "enqueueSectionData(JLnet/minecraft/world/chunk/ChunkNibbleArray;)V", - at = @At("TAIL") - ) + @Inject(method = "enqueueSectionData(JLnet/minecraft/world/chunk/ChunkNibbleArray;)V", at = @At("TAIL")) private void veila$onEnqueueSection(long sectionPos, @Nullable ChunkNibbleArray array, CallbackInfo ci) { - // Если array == null - секция выгружается, не нужно загружать if (array != null) { - FogLightStreamer.onSectionUpdated(sectionPos); + if (this.lightType == LightType.BLOCK) { + FogLightStreamer.onBlockLightUpdated(sectionPos); + } else { + FogLightStreamer.onSkyLightUpdated(sectionPos); + } + } + } + + @Inject(method = "setSectionStatus(JZ)V", at = @At("TAIL")) + private void veila$onSetSectionStatus(long sectionPos, boolean notReady, CallbackInfo ci) { + if (!notReady) { + if (this.lightType == LightType.BLOCK) { + FogLightStreamer.onBlockLightUpdated(sectionPos); + } else { + FogLightStreamer.onSkyLightUpdated(sectionPos); + } } } } \ 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 3af8207..f491d9d 100644 --- a/src/client/resources/assets/veila/shaders/fog_raymarching.frag +++ b/src/client/resources/assets/veila/shaders/fog_raymarching.frag @@ -2,7 +2,8 @@ uniform sampler2D DepthSampler; uniform sampler3D FogVolume; -uniform sampler3D LightVolume; +uniform sampler3D SkyLightVolume; +uniform sampler3D BlockLightVolume; uniform mat4 InverseProjection; uniform mat4 InverseView; @@ -42,6 +43,8 @@ const float SCATTERING_COEFF = 1.0; const float AMBIENT_INTENSITY = 0.10; const float MIN_TRANSMITTANCE = 0.01; +const float AMBIENT_FOG = 0.01; + //------------------------------------------------------------ vec3 reconstructViewPosition(vec2 uv) @@ -96,7 +99,7 @@ float density(vec3 worldPos) { vec3 uv = worldToUV(worldPos); if (uv.x < 0.0) return 0.0; - return texture(FogVolume, uv).r; + return max(texture(FogVolume, uv).r, AMBIENT_FOG); } vec2 light(vec3 worldPos) @@ -104,9 +107,8 @@ 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 blockNorm = texture(BlockLightVolume, uv).r; + float skyNorm = texture(SkyLightVolume, uv).r; float skyContribution = skyNorm * SkyBrightness;