Фикс карты освещения

This commit is contained in:
2026-08-08 03:17:19 +04:00
parent fd21d9edd8
commit 1bcbcf6d60
6 changed files with 441 additions and 350 deletions
@@ -48,7 +48,8 @@ public class PostProcessingManager {
private static int rm_inverseProjectionLocation; private static int rm_inverseProjectionLocation;
private static int rm_depthSamplerLocation; private static int rm_depthSamplerLocation;
private static int rm_fogVolumeLocation; 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_fogOriginLocation;
private static int rm_ringOffsetLocation; private static int rm_ringOffsetLocation;
private static int rm_cameraPositionLocation; private static int rm_cameraPositionLocation;
@@ -105,7 +106,8 @@ public class PostProcessingManager {
rm_inverseProjectionLocation = raymarchProgram.uniform("InverseProjection"); rm_inverseProjectionLocation = raymarchProgram.uniform("InverseProjection");
rm_depthSamplerLocation = raymarchProgram.uniform("DepthSampler"); rm_depthSamplerLocation = raymarchProgram.uniform("DepthSampler");
rm_fogVolumeLocation = raymarchProgram.uniform("FogVolume"); 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_fogOriginLocation = raymarchProgram.uniform("FogOrigin");
rm_ringOffsetLocation = raymarchProgram.uniform("RingBlockOffset"); rm_ringOffsetLocation = raymarchProgram.uniform("RingBlockOffset");
rm_cameraPositionLocation = raymarchProgram.uniform("CameraPosition"); rm_cameraPositionLocation = raymarchProgram.uniform("CameraPosition");
@@ -280,8 +282,12 @@ public class PostProcessingManager {
// Light Volume // Light Volume
GL13.glActiveTexture(GL13.GL_TEXTURE2); GL13.glActiveTexture(GL13.GL_TEXTURE2);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getTexture()); GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getSkyLightTexture());
GL20.glUniform1i(rm_lightVolumeLocation, 2); 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, GL20.glUniform2i(rm_fogOriginLocation,
FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE, FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE,
@@ -315,6 +321,8 @@ public class PostProcessingManager {
FullscreenQuad.draw(); FullscreenQuad.draw();
// Очистка текстур // Очистка текстур
GL13.glActiveTexture(GL13.GL_TEXTURE3);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GL13.glActiveTexture(GL13.GL_TEXTURE2); GL13.glActiveTexture(GL13.GL_TEXTURE2);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GL13.glActiveTexture(GL13.GL_TEXTURE1); GL13.glActiveTexture(GL13.GL_TEXTURE1);
@@ -8,72 +8,62 @@ import net.minecraft.world.chunk.light.LightingProvider;
public final class FogLightProvider { public final class FogLightProvider {
// Размер массива для одной секции: 16×16×16×2 = 8192 (×2 для двух каналов) public static final int SECTION_DATA_SIZE = 16 * 16 * 16;
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;
private FogLightProvider() { 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(); MinecraftClient client = MinecraftClient.getInstance();
if (client.world == null) return null; if (client.world == null) return null;
LightingProvider provider = client.world.getLightingProvider(); LightingProvider provider = client.world.getLightingProvider();
ChunkLightProvider<?, ?> blockProvider = provider.blockLightProvider; ChunkLightProvider<?, ?> blockProvider = provider.blockLightProvider;
ChunkLightProvider<?, ?> skyProvider = provider.skyLightProvider; if (blockProvider == null) return null;
if (blockProvider == null || skyProvider == null) {
return null;
}
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ); ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos); ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos);
ChunkNibbleArray skyArray = skyProvider.getLightSection(sectionPos);
if (blockArray == null && skyArray == null) { if (blockArray == null) return null;
return null;
}
// Используем один из двух pre-allocated буферов (double buffering)
byte[] result;
if (!bufferInUse) {
bufferInUse = true;
result = SECTION_BUFFER;
} else {
result = SECTION_BUFFER_ALT;
}
// СОЗДАЁМ НОВЫЙ массив, а не используем ThreadLocal
byte[] result = new byte[SECTION_DATA_SIZE];
int index = 0; int index = 0;
for (int localZ = 0; localZ < 16; localZ++) { for (int localZ = 0; localZ < 16; localZ++) {
for (int localY = 0; localY < 16; localY++) { for (int localY = 0; localY < 16; localY++) {
for (int localX = 0; localX < 16; localX++) { for (int localX = 0; localX < 16; localX++) {
int blockLight = (blockArray != null) ? blockArray.get(localX, localY, localZ) : 0; result[index++] = (byte) (blockArray.get(localX, localY, localZ) * 17);
int skyLight = (skyArray != null) ? skyArray.get(localX, localY, localZ) : 0;
FogLightVolume.packLight(blockLight, skyLight, result, index);
index += 2;
} }
} }
} }
return result;
}
// Освобождаем буфер public static byte[] fillSkyLightIntoArray(int sectionX, int sectionY, int sectionZ) {
if (result == SECTION_BUFFER) { MinecraftClient client = MinecraftClient.getInstance();
bufferInUse = false; 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; return result;
} }
} }
@@ -1,5 +1,6 @@
package su.divan2000.veila.client.render.fog; package su.divan2000.veila.client.render.fog;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLongArray; import java.util.concurrent.atomic.AtomicLongArray;
public final class FogLightStreamer { public final class FogLightStreamer {
@@ -7,19 +8,27 @@ public final class FogLightStreamer {
public static final int SECTION_SIZE = 16; public static final int SECTION_SIZE = 16;
public static final int SECTIONS_PER_CHUNK_Y = FogLightVolume.SIZE_Y / SECTION_SIZE; public static final int SECTIONS_PER_CHUNK_Y = FogLightVolume.SIZE_Y / SECTION_SIZE;
// Общее количество секций в окне: 16 чанков × 16 чанков × 24 секции = 6144
private static final int TOTAL_SECTIONS = private static final int TOTAL_SECTIONS =
FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z * SECTIONS_PER_CHUNK_Y; FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z * SECTIONS_PER_CHUNK_Y;
// Битовая маска: 6144 бита = 96 long = 768 байт private static final AtomicLongArray pendingBlockMask =
private static final AtomicLongArray pendingSectionsMask = new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
private static final AtomicLongArray pendingSkyMask =
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); 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); new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
private static final AtomicLongArray initialLoadMask = private static final AtomicLongArray initialBlockMask =
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64); new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
private static final AtomicLongArray initialSkyMask =
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
// Храним данные вместе с мировыми координатами секции
private static final ConcurrentHashMap<Integer, PreparedSection> preparedBlockData = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Integer, PreparedSection> preparedSkyData = new ConcurrentHashMap<>();
private static final int MAX_SECTIONS_PER_FRAME = 64; private static final int MAX_SECTIONS_PER_FRAME = 64;
private static final int MAX_INITIAL_SECTIONS_PER_TICK = 96; 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 final int[] regionDepth = new int[MAX_REGIONS];
private static int regionCount = 0; 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() { private FogLightStreamer() {
} }
/**
* Конвертирует мировые координаты секции в локальный индекс в маске.
* Возвращает -1 если секция вне текущего окна.
*/
private static int sectionToLocalIndex(int sectionX, int sectionY, int sectionZ) { private static int sectionToLocalIndex(int sectionX, int sectionY, int sectionZ) {
int chunkX = sectionX; int relativeX = sectionX - currentOriginChunkX;
int chunkZ = sectionZ; int relativeZ = sectionZ - currentOriginChunkZ;
int relativeX = chunkX - currentOriginChunkX;
int relativeZ = chunkZ - currentOriginChunkZ;
if (relativeX < 0 || relativeX >= FogLightVolume.CHUNKS_X || if (relativeX < 0 || relativeX >= FogLightVolume.CHUNKS_X ||
relativeZ < 0 || relativeZ >= FogLightVolume.CHUNKS_Z) { relativeZ < 0 || relativeZ >= FogLightVolume.CHUNKS_Z) {
@@ -70,77 +86,65 @@ public final class FogLightStreamer {
+ physicalX; + physicalX;
} }
/**
* Устанавливает бит в маске атомарно.
*/
private static boolean setBit(AtomicLongArray mask, int index) { private static boolean setBit(AtomicLongArray mask, int index) {
int wordIndex = index >> 6; int wordIndex = index >> 6;
long bitMask = 1L << (index & 63); long bitMask = 1L << (index & 63);
while (true) { while (true) {
long current = mask.get(wordIndex); long current = mask.get(wordIndex);
if ((current & bitMask) != 0) { if ((current & bitMask) != 0) return false;
return false; // Бит уже установлен if (mask.compareAndSet(wordIndex, current, current | bitMask)) return true;
}
if (mask.compareAndSet(wordIndex, current, current | bitMask)) {
return true;
}
// CAS не удался - повторяем
} }
} }
/**
* Снимает бит из маски атомарно.
*/
private static boolean clearBit(AtomicLongArray mask, int index) { private static boolean clearBit(AtomicLongArray mask, int index) {
int wordIndex = index >> 6; int wordIndex = index >> 6;
long bitMask = 1L << (index & 63); long bitMask = 1L << (index & 63);
while (true) { while (true) {
long current = mask.get(wordIndex); long current = mask.get(wordIndex);
if ((current & bitMask) == 0) { if ((current & bitMask) == 0) return false;
return false; // Бит уже снят if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return true;
}
if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) {
return true;
}
} }
} }
/**
* Проверяет и снимает бит (для итерации).
* Возвращает индекс если бит был установлен.
*/
private static int nextSetBit(AtomicLongArray mask, int startIndex) { private static int nextSetBit(AtomicLongArray mask, int startIndex) {
for (int i = startIndex; i < TOTAL_SECTIONS; i++) { for (int i = startIndex; i < TOTAL_SECTIONS; i++) {
int wordIndex = i >> 6; int wordIndex = i >> 6;
long bitMask = 1L << (i & 63); long bitMask = 1L << (i & 63);
if ((mask.get(wordIndex) & bitMask) != 0) return i;
if ((mask.get(wordIndex) & bitMask) != 0) {
return i;
}
} }
return -1; return -1;
} }
public static void onSectionUpdated(long sectionPos) { public static void onBlockLightUpdated(long sectionPos) {
int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos); int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos);
int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos); int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos);
int sectionZ = net.minecraft.util.math.ChunkSectionPos.unpackZ(sectionPos); int sectionZ = net.minecraft.util.math.ChunkSectionPos.unpackZ(sectionPos);
int index = sectionToLocalIndex(sectionX, sectionY, sectionZ); int index = sectionToLocalIndex(sectionX, sectionY, sectionZ);
if (index >= 0) { 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( public static void update(
int oldOriginChunkX, int oldOriginChunkX, int oldOriginChunkZ,
int oldOriginChunkZ, int newOriginChunkX, int newOriginChunkZ,
int newOriginChunkX, int ringChunkOffsetX, int ringChunkOffsetZ
int newOriginChunkZ,
int ringChunkOffsetX,
int ringChunkOffsetZ
) { ) {
currentOriginChunkX = newOriginChunkX; currentOriginChunkX = newOriginChunkX;
currentOriginChunkZ = newOriginChunkZ; currentOriginChunkZ = newOriginChunkZ;
@@ -156,12 +160,9 @@ public final class FogLightStreamer {
int dx = newOriginChunkX - oldOriginChunkX; int dx = newOriginChunkX - oldOriginChunkX;
int dz = newOriginChunkZ - oldOriginChunkZ; int dz = newOriginChunkZ - oldOriginChunkZ;
if (dx == 0 && dz == 0) { if (dx == 0 && dz == 0) return;
return;
}
if (Math.abs(dx) >= FogLightVolume.CHUNKS_X || if (Math.abs(dx) >= FogLightVolume.CHUNKS_X || Math.abs(dz) >= FogLightVolume.CHUNKS_Z) {
Math.abs(dz) >= FogLightVolume.CHUNKS_Z) {
markAllPending(); markAllPending();
return; return;
} }
@@ -170,30 +171,21 @@ public final class FogLightStreamer {
if (dx > 0) { if (dx > 0) {
int worldX = newOriginChunkX + FogLightVolume.CHUNKS_X - dx; int worldX = newOriginChunkX + FogLightVolume.CHUNKS_X - dx;
int worldZ = newOriginChunkZ; collectRegions(worldX, newOriginChunkZ, dx, FogLightVolume.CHUNKS_Z);
collectRegions(worldX, worldZ, dx, FogLightVolume.CHUNKS_Z); addInitialLoadForRange(worldX, newOriginChunkZ, dx, FogLightVolume.CHUNKS_Z);
addInitialLoadForRange(worldX, worldZ, dx, FogLightVolume.CHUNKS_Z);
} }
if (dx < 0) { if (dx < 0) {
int worldX = newOriginChunkX; collectRegions(newOriginChunkX, newOriginChunkZ, -dx, FogLightVolume.CHUNKS_Z);
int worldZ = newOriginChunkZ; addInitialLoadForRange(newOriginChunkX, newOriginChunkZ, -dx, FogLightVolume.CHUNKS_Z);
collectRegions(worldX, worldZ, -dx, FogLightVolume.CHUNKS_Z);
addInitialLoadForRange(worldX, worldZ, -dx, FogLightVolume.CHUNKS_Z);
} }
if (dz > 0) { if (dz > 0) {
int worldX = newOriginChunkX;
int worldZ = newOriginChunkZ + FogLightVolume.CHUNKS_Z - dz; int worldZ = newOriginChunkZ + FogLightVolume.CHUNKS_Z - dz;
collectRegions(worldX, worldZ, FogLightVolume.CHUNKS_X, dz); collectRegions(newOriginChunkX, worldZ, FogLightVolume.CHUNKS_X, dz);
addInitialLoadForRange(worldX, worldZ, FogLightVolume.CHUNKS_X, dz); addInitialLoadForRange(newOriginChunkX, worldZ, FogLightVolume.CHUNKS_X, dz);
} }
if (dz < 0) { if (dz < 0) {
int worldX = newOriginChunkX; collectRegions(newOriginChunkX, newOriginChunkZ, FogLightVolume.CHUNKS_X, -dz);
int worldZ = newOriginChunkZ; addInitialLoadForRange(newOriginChunkX, newOriginChunkZ, FogLightVolume.CHUNKS_X, -dz);
collectRegions(worldX, worldZ, FogLightVolume.CHUNKS_X, -dz);
addInitialLoadForRange(worldX, worldZ, FogLightVolume.CHUNKS_X, -dz);
} }
if (regionCount > 0) { if (regionCount > 0) {
@@ -204,17 +196,11 @@ public final class FogLightStreamer {
private static void addInitialLoadForRange(int worldX, int worldZ, int countX, int countZ) { private static void addInitialLoadForRange(int worldX, int worldZ, int countX, int countZ) {
for (int i = 0; i < countX; i++) { for (int i = 0; i < countX; i++) {
for (int j = 0; j < countZ; j++) { 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++) { for (int sectionY = 0; sectionY < SECTIONS_PER_CHUNK_Y; sectionY++) {
int sectionPos = sectionToLocalIndex( int index = sectionToLocalIndex(worldX + i, sectionY + (FogWorldVolume.MIN_Y >> 4), worldZ + j);
chunkX, if (index >= 0) {
sectionY + (FogWorldVolume.MIN_Y >> 4), setBit(initialBlockMask, index);
chunkZ setBit(initialSkyMask, index);
);
if (sectionPos >= 0) {
setBit(initialLoadMask, sectionPos);
} }
} }
} }
@@ -230,15 +216,19 @@ public final class FogLightStreamer {
FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount); FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
// Устанавливаем все биты в initialLoadMask preparedBlockData.clear();
for (int i = 0; i < pendingSectionsMask.length(); i++) { preparedSkyData.clear();
initialLoadMask.set(i, -1L); // Все биты = 1
for (int i = 0; i < pendingBlockMask.length(); i++) {
initialBlockMask.set(i, -1L);
initialSkyMask.set(i, -1L);
} }
// Очищаем лишние биты в последнем слове
int totalWords = (TOTAL_SECTIONS + 63) / 64; int totalWords = (TOTAL_SECTIONS + 63) / 64;
int extraBits = TOTAL_SECTIONS & 63; int extraBits = TOTAL_SECTIONS & 63;
if (extraBits != 0) { 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( private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) {
int start, int count, int dim,
int[] outStarts, int[] outCounts
) {
if (count == 0) return 0; if (count == 0) return 0;
int first = Math.floorMod(start, dim); int first = Math.floorMod(start, dim);
int last = Math.floorMod(start + count - 1, dim); int last = Math.floorMod(start + count - 1, dim);
if (first <= last) { if (first <= last) {
outStarts[0] = first; outStarts[0] = first;
outCounts[0] = count; outCounts[0] = count;
@@ -286,86 +271,105 @@ public final class FogLightStreamer {
} }
} }
/**
* Подготавливает все секции для загрузки (client thread).
* Обрабатывает и initialLoadMask, и pendingSectionsMask.
* Вызывается из FogSystem.tick().
*/
public static void prepareAllSections() { public static void prepareAllSections() {
int processed = 0; int processed = 0;
// Обрабатываем initial load // Initial load - block light
int index = 0; int index = 0;
while (processed < MAX_INITIAL_SECTIONS_PER_TICK) { while (processed < MAX_INITIAL_SECTIONS_PER_TICK) {
index = nextSetBit(initialLoadMask, index); index = nextSetBit(initialBlockMask, index);
if (index < 0) break; if (index < 0) break;
if (prepareBlockLight(index)) {
if (prepareSectionByIndex(index)) { clearBit(initialBlockMask, index);
clearBit(initialLoadMask, index);
processed++; processed++;
} }
index++; index++;
} }
// Обрабатываем инкрементальные обновления // Initial load - sky light
index = 0; index = 0;
while (processed < MAX_INITIAL_SECTIONS_PER_TICK + MAX_SECTIONS_PER_FRAME) { while (processed < MAX_INITIAL_SECTIONS_PER_TICK * 2) {
index = nextSetBit(pendingSectionsMask, index); index = nextSetBit(initialSkyMask, index);
if (index < 0) break; if (index < 0) break;
if (prepareSkyLight(index)) {
clearBit(initialSkyMask, index);
processed++;
}
index++;
}
if (prepareSectionByIndex(index)) { // Incremental - block light
clearBit(pendingSectionsMask, index); 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++; processed++;
} }
index++; index++;
} }
} }
/**
* Загружает готовые секции в текстуру (render thread).
* Только OpenGL вызовы, без чтения из LightingProvider.
* Вызывается из FogSystem.beginRender().
*/
public static void uploadReadySections() { public static void uploadReadySections() {
int processed = 0; int processed = 0;
int index = 0; int index = 0;
while (processed < MAX_SECTIONS_PER_FRAME + MAX_INITIAL_SECTIONS_PER_TICK) { while (processed < MAX_SECTIONS_PER_FRAME + MAX_INITIAL_SECTIONS_PER_TICK) {
index = nextSetBit(readySectionsMask, index); index = nextSetBit(readyBlockMask, index);
if (index < 0) break; if (index < 0) break;
if (uploadBlockLight(index)) {
clearBit(readyBlockMask, index);
processed++;
}
index++;
}
if (uploadSectionByIndex(index)) { index = 0;
clearBit(readySectionsMask, index); 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++; processed++;
} }
index++; index++;
} }
} }
/** private static boolean prepareBlockLight(int index) {
* Читает данные секции из LightingProvider и помечает как готовую к загрузке. // Извлекаем физические координаты
* Вызывается из prepareAllSections() в client thread. int physicalX = index % FogLightVolume.CHUNKS_X;
*/ int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
private static boolean prepareSectionByIndex(int index) { int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
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);
byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ); // Конвертируем в мировые координаты
if (sectionData == null) { int sectionX = currentOriginChunkX + Math.floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
return false; // Секция не готова, пробуем позже int sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
} int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
// Помечаем как готовую к загрузке в render thread byte[] data = FogLightProvider.fillBlockLightIntoArray(sectionX, sectionY, sectionZ);
setBit(readySectionsMask, index); if (data == null) return false;
// Сохраняем данные вместе с мировыми координатами
preparedBlockData.put(index, new PreparedSection(data, sectionX, sectionY, sectionZ));
setBit(readyBlockMask, index);
return true; return true;
} }
/** private static boolean prepareSkyLight(int index) {
* Загружает готовую секцию в текстуру через OpenGL.
* Вызывается из uploadReadySections() в render thread.
*/
private static boolean uploadSectionByIndex(int index) {
int physicalX = index % FogLightVolume.CHUNKS_X; int physicalX = index % FogLightVolume.CHUNKS_X;
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z; int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
int localSectionY = 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 sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4); int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
// Читаем данные повторно (они должны быть доступны быстро, так как уже в кэше) byte[] data = FogLightProvider.fillSkyLightIntoArray(sectionX, sectionY, sectionZ);
byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ); if (data == null) return false;
if (sectionData == null) {
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; 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; return true;
} }
} }
@@ -18,63 +18,68 @@ public final class FogLightVolume {
public static final int CHUNKS_X = FogWorldVolume.CHUNKS_X; public static final int CHUNKS_X = FogWorldVolume.CHUNKS_X;
public static final int CHUNKS_Z = FogWorldVolume.CHUNKS_Z; 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 (ночь, открытое небо) private static final byte DEFAULT_BLOCK_LIGHT = (byte) 0;
// Теперь это vec2, упакованный в RG8 private static final byte DEFAULT_SKY_LIGHT = (byte) 255;
public static final byte DEFAULT_BLOCK_LIGHT = (byte) 0;
public 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 { static {
DEFAULT_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE * 2]; // ×2 для двух каналов int sectionVoxels = CHUNK_SIZE * SIZE_Y * CHUNK_SIZE;
for (int i = 0; i < CHUNK_SIZE * SIZE_Y * CHUNK_SIZE; i++) { DEFAULT_BLOCK_ARRAY = new byte[sectionVoxels];
DEFAULT_ARRAY[i * 2] = DEFAULT_BLOCK_LIGHT; // R = blockLight DEFAULT_SKY_ARRAY = new byte[sectionVoxels];
DEFAULT_ARRAY[i * 2 + 1] = DEFAULT_SKY_LIGHT; // G = skyLight 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 = 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 = private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
BufferUtils.createByteBuffer(2);
// PBO для быстрой очистки // PBO для быстрой очистки
private static int clearPBO = -1; private static int clearBlockPBO = -1;
private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z * 2; // ×2 для двух каналов private static int clearSkyPBO = -1;
// Pre-allocated buffer для одной секции (4096 вокселей × 2 байта = 8192 байта) // Буферы для загрузки секций
// Используется для загрузки секций через uploadSection private static final ByteBuffer BLOCK_SECTION_UPLOAD_BUFFER =
private static final ByteBuffer SECTION_UPLOAD_BUFFER =
BufferUtils.createByteBuffer(FogLightStreamer.SECTION_SIZE BufferUtils.createByteBuffer(FogLightStreamer.SECTION_SIZE
* 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() { private FogLightVolume() {
} }
/** private static void initClearPBOs() {
* Упаковывает blockLight и skyLight в два байта для RG8 текстуры. if (clearBlockPBO != -1) return;
* 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);
// 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); 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); ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
if (mapped != null) { if (mapped != null) {
for (int i = 0; i < SIZE_X * SIZE_Y * SIZE_Z; i++) { for (int i = 0; i < SIZE_X * SIZE_Y * SIZE_Z; i++) {
mapped.put(DEFAULT_BLOCK_LIGHT); 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); mapped.put(DEFAULT_SKY_LIGHT);
} }
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
@@ -84,136 +89,139 @@ public final class FogLightVolume {
} }
public static void init() { public static void init() {
if (texture != -1) return; if (blockLightTexture != -1) return;
initClearPBO(); initClearPBOs();
texture = GL11.glGenTextures(); // Block light texture
blockLightTexture = GL11.glGenTextures();
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture);
// ИСПРАВЛЕНО: GL_RG8 вместо GL_R8UI, два канала для двух типов света
GL30.glTexImage3D( GL30.glTexImage3D(
GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_3D, 0, GL30.GL_R8,
0, SIZE_X, SIZE_Y, SIZE_Z, 0,
GL30.GL_RG8, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null
SIZE_X,
SIZE_Y,
SIZE_Z,
0,
GL30.GL_RG,
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_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, 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_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_T, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_R, GL12.GL_REPEAT); 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); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
clear(); clear();
} }
public static void clear() { public static void clear() {
// Clear block light texture
CHUNK_BUFFER.clear(); CHUNK_BUFFER.clear();
CHUNK_BUFFER.put(DEFAULT_ARRAY); CHUNK_BUFFER.put(DEFAULT_BLOCK_ARRAY);
CHUNK_BUFFER.flip(); 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 chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) {
for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) { for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) {
uploadChunk(chunkX, chunkZ, CHUNK_BUFFER); uploadChunkInternal(blockLightTexture, chunkX, chunkZ, CHUNK_BUFFER);
CHUNK_BUFFER.rewind(); CHUNK_BUFFER.rewind();
} }
} }
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
public static void uploadChunk(int textureChunkX, int textureChunkZ, ByteBuffer data) { // Clear sky light texture
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() {
CHUNK_BUFFER.clear(); 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() { private static void uploadChunkInternal(int texture, int textureChunkX, int textureChunkZ, ByteBuffer data) {
return 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,
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( public static void clearRegions(
@@ -221,32 +229,42 @@ public final class FogLightVolume {
int[] widthChunks, int[] depthChunks, int[] widthChunks, int[] depthChunks,
int count 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_ALIGNMENT, 1);
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0); GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0); GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 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++) { for (int i = 0; i < count; i++) {
int w = widthChunks[i] * CHUNK_SIZE; int w = widthChunks[i] * CHUNK_SIZE;
int h = SIZE_Y; int h = SIZE_Y;
int d = depthChunks[i] * CHUNK_SIZE; int d = depthChunks[i] * CHUNK_SIZE;
GL30.glTexSubImage3D( GL30.glTexSubImage3D(
GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_3D, 0,
0, physX[i] * CHUNK_SIZE, 0, physZ[i] * CHUNK_SIZE,
physX[i] * CHUNK_SIZE,
0,
physZ[i] * CHUNK_SIZE,
w, h, d, w, h, d,
GL30.GL_RG, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L
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); GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
} }
@@ -1,10 +1,13 @@
package su.divan2000.veila.mixin.client; package su.divan2000.veila.mixin.client;
import net.minecraft.util.math.ChunkSectionPos; import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.world.LightType;
import net.minecraft.world.chunk.ChunkNibbleArray; import net.minecraft.world.chunk.ChunkNibbleArray;
import net.minecraft.world.chunk.light.ChunkLightProvider;
import net.minecraft.world.chunk.light.LightStorage; import net.minecraft.world.chunk.light.LightStorage;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin; 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.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -13,25 +16,38 @@ import su.divan2000.veila.client.render.fog.FogLightStreamer;
@Mixin(LightStorage.class) @Mixin(LightStorage.class)
public class LightStorageMixin { public class LightStorageMixin {
// Ловим инкрементальные обновления (когда блок меняет свет) @Shadow
@Inject( private LightType lightType;
method = "set(JI)V",
at = @At("TAIL") @Inject(method = "set(JI)V", at = @At("TAIL"))
)
private void veila$onLightSet(long blockPos, int value, CallbackInfo ci) { private void veila$onLightSet(long blockPos, int value, CallbackInfo ci) {
long sectionPos = ChunkSectionPos.fromBlockPos(blockPos); 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) { private void veila$onEnqueueSection(long sectionPos, @Nullable ChunkNibbleArray array, CallbackInfo ci) {
// Если array == null - секция выгружается, не нужно загружать
if (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);
}
} }
} }
} }
@@ -2,7 +2,8 @@
uniform sampler2D DepthSampler; uniform sampler2D DepthSampler;
uniform sampler3D FogVolume; uniform sampler3D FogVolume;
uniform sampler3D LightVolume; uniform sampler3D SkyLightVolume;
uniform sampler3D BlockLightVolume;
uniform mat4 InverseProjection; uniform mat4 InverseProjection;
uniform mat4 InverseView; uniform mat4 InverseView;
@@ -42,6 +43,8 @@ const float SCATTERING_COEFF = 1.0;
const float AMBIENT_INTENSITY = 0.10; const float AMBIENT_INTENSITY = 0.10;
const float MIN_TRANSMITTANCE = 0.01; const float MIN_TRANSMITTANCE = 0.01;
const float AMBIENT_FOG = 0.01;
//------------------------------------------------------------ //------------------------------------------------------------
vec3 reconstructViewPosition(vec2 uv) vec3 reconstructViewPosition(vec2 uv)
@@ -96,7 +99,7 @@ float density(vec3 worldPos)
{ {
vec3 uv = worldToUV(worldPos); vec3 uv = worldToUV(worldPos);
if (uv.x < 0.0) return 0.0; 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) vec2 light(vec3 worldPos)
@@ -104,9 +107,8 @@ vec2 light(vec3 worldPos)
vec3 uv = worldToUV(worldPos); vec3 uv = worldToUV(worldPos);
if (uv.x < 0.0) return vec2(0.0); if (uv.x < 0.0) return vec2(0.0);
vec2 rg = texture(LightVolume, uv).rg; float blockNorm = texture(BlockLightVolume, uv).r;
float blockNorm = rg.r; float skyNorm = texture(SkyLightVolume, uv).r;
float skyNorm = rg.g;
float skyContribution = skyNorm * SkyBrightness; float skyContribution = skyNorm * SkyBrightness;