Фикс карты освещения
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user