Полностью корректный кольцевой буффер
This commit is contained in:
@@ -20,6 +20,8 @@ loom {
|
|||||||
sourceSet sourceSets.client
|
sourceSet sourceSets.client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessWidenerPath = file("src/main/resources/veila.accesswidener")
|
||||||
}
|
}
|
||||||
|
|
||||||
fabricApi {
|
fabricApi {
|
||||||
|
|||||||
@@ -1,58 +1,100 @@
|
|||||||
package su.divan2000.veila.client.render.fog;
|
package su.divan2000.veila.client.render.fog;
|
||||||
|
|
||||||
import java.nio.FloatBuffer;
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.world.chunk.ChunkSection;
|
||||||
|
import net.minecraft.world.chunk.WorldChunk;
|
||||||
|
import net.minecraft.world.chunk.PalettedContainer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public final class FogChunkProvider {
|
public final class FogChunkProvider {
|
||||||
static void fillChunk(
|
|
||||||
int chunkX,
|
|
||||||
int chunkZ,
|
|
||||||
FloatBuffer buffer
|
|
||||||
) {
|
|
||||||
|
|
||||||
float density =
|
public static boolean isChunkLoaded(int chunkX, int chunkZ) {
|
||||||
hash(chunkX, chunkZ);
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
|
||||||
for (int z = 0;
|
if (client.world == null) {
|
||||||
z < FogWorldVolume.CHUNK_SIZE;
|
return false;
|
||||||
z++) {
|
|
||||||
|
|
||||||
for (int y = 0;
|
|
||||||
y < FogWorldVolume.SIZE_Y;
|
|
||||||
y++) {
|
|
||||||
|
|
||||||
int worldY =
|
|
||||||
FogWorldVolume.MIN_Y + y;
|
|
||||||
|
|
||||||
float value =
|
|
||||||
worldY < 70
|
|
||||||
? density
|
|
||||||
: 0.0f;
|
|
||||||
|
|
||||||
for (int x = 0;
|
|
||||||
x < FogWorldVolume.CHUNK_SIZE;
|
|
||||||
x++) {
|
|
||||||
|
|
||||||
buffer.put(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return client.world.getChunkManager().getWorldChunk(chunkX, chunkZ, false) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static float computeVoxelValue(BlockState state) {
|
||||||
|
if (state.isOf(Blocks.POPPY)) {
|
||||||
|
return 0.2f;
|
||||||
|
} else if (state.isOf(Blocks.GRASS)) {
|
||||||
|
return 0.4f;
|
||||||
|
} else {
|
||||||
|
return 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float hash(
|
public static boolean fillChunkIntoArray(int chunkX, int chunkZ, float[] array) {
|
||||||
int x,
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
int z
|
|
||||||
) {
|
|
||||||
|
|
||||||
int h = x * 73428767;
|
if (!isChunkLoaded(chunkX, chunkZ)) return false;
|
||||||
|
|
||||||
h ^= z * 912931;
|
WorldChunk chunk = client.world.getChunk(chunkX, chunkZ);
|
||||||
|
|
||||||
h ^= h >> 13;
|
ChunkSection[] sections = chunk.getSectionArray();
|
||||||
|
int bottomY = client.world.getBottomY();
|
||||||
|
int topYExclusive = bottomY + sections.length * 16;
|
||||||
|
|
||||||
h *= 1274126177;
|
int index = 0;
|
||||||
|
|
||||||
return ((h >>> 24) & 255)
|
// Порядок циклов Z -> Y -> X критичен для glTexSubImage3D
|
||||||
/ 255.0f
|
for (int z = 0; z < FogWorldVolume.CHUNK_SIZE; z++) {
|
||||||
* 0.08f;
|
// Кэшируем секции и их состояние
|
||||||
|
int currentSectionIndex = -1;
|
||||||
|
ChunkSection currentSection = null;
|
||||||
|
PalettedContainer<BlockState> currentContainer = null;
|
||||||
|
boolean currentIsEmpty = false;
|
||||||
|
|
||||||
|
for (int y = 0; y < FogWorldVolume.SIZE_Y; y++) {
|
||||||
|
int worldY = FogWorldVolume.MIN_Y + y;
|
||||||
|
|
||||||
|
// За пределами мира (например, ниже -64)
|
||||||
|
if (worldY < bottomY || worldY >= topYExclusive) {
|
||||||
|
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, 0.0f);
|
||||||
|
index += FogWorldVolume.CHUNK_SIZE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newSectionIndex = (worldY - bottomY) >> 4;
|
||||||
|
if (newSectionIndex != currentSectionIndex) {
|
||||||
|
currentSectionIndex = newSectionIndex;
|
||||||
|
currentSection = sections[currentSectionIndex];
|
||||||
|
if (currentSection != null) {
|
||||||
|
currentContainer = currentSection.getBlockStateContainer();
|
||||||
|
currentIsEmpty = currentSection.isEmpty();
|
||||||
|
} else {
|
||||||
|
currentContainer = null;
|
||||||
|
currentIsEmpty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int localY = worldY & 15;
|
||||||
|
|
||||||
|
// ОПТИМИЗАЦИЯ: Если текущая секция полностью пустая (только воздух)
|
||||||
|
if (currentIsEmpty) {
|
||||||
|
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, 0.0f);
|
||||||
|
index += FogWorldVolume.CHUNK_SIZE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Предварительное вычисление базового индекса для строки Y и Z
|
||||||
|
// Формула локального индекса в секции 16x16x16: (y << 8) | (z << 4) | x
|
||||||
|
int baseIndex = (localY << 8) | (z << 4);
|
||||||
|
|
||||||
|
for (int x = 0; x < FogWorldVolume.CHUNK_SIZE; x++) {
|
||||||
|
BlockState currentBlock = currentContainer.get(baseIndex | x);
|
||||||
|
|
||||||
|
array[index++] = computeVoxelValue(currentBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,173 +1,287 @@
|
|||||||
package su.divan2000.veila.client.render.fog;
|
package su.divan2000.veila.client.render.fog;
|
||||||
|
|
||||||
import java.nio.FloatBuffer;
|
import net.minecraft.block.BlockState;
|
||||||
|
|
||||||
import static su.divan2000.veila.client.render.fog.FogWorldVolume.CHUNK_SIZE;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import static su.divan2000.veila.client.render.fog.FogWorldVolume.SIZE_Y;
|
|
||||||
|
|
||||||
public final class FogChunkStreamer {
|
public final class FogChunkStreamer {
|
||||||
|
|
||||||
|
private enum ChunkStatus {
|
||||||
|
PENDING,
|
||||||
|
LOADED
|
||||||
|
}
|
||||||
|
|
||||||
|
// Очередь изменений блоков (потокобезопасная)
|
||||||
|
private static final ConcurrentLinkedQueue<BlockChangeEvent> blockChangeQueue =
|
||||||
|
new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
private static final int TOTAL_CHUNKS =
|
||||||
|
FogWorldVolume.CHUNKS_X * FogWorldVolume.CHUNKS_Z;
|
||||||
|
|
||||||
|
private static final int CHUNK_DATA_SIZE =
|
||||||
|
FogWorldVolume.CHUNK_SIZE *
|
||||||
|
FogWorldVolume.SIZE_Y *
|
||||||
|
FogWorldVolume.CHUNK_SIZE;
|
||||||
|
|
||||||
|
private static final int MAX_CHUNKS_PER_TICK = 4;
|
||||||
|
|
||||||
|
private static final ChunkStatus[] statuses = new ChunkStatus[TOTAL_CHUNKS];
|
||||||
|
private static final boolean[] readyToUpload = new boolean[TOTAL_CHUNKS];
|
||||||
|
|
||||||
|
private static final float[][] preparedData = new float[TOTAL_CHUNKS][CHUNK_DATA_SIZE];
|
||||||
|
private static final int[] preparedWorldChunkX = new int[TOTAL_CHUNKS];
|
||||||
|
private static final int[] preparedWorldChunkZ = new int[TOTAL_CHUNKS];
|
||||||
|
|
||||||
|
private static volatile int currentOriginChunkX;
|
||||||
|
private static volatile int currentOriginChunkZ;
|
||||||
|
private static volatile int currentRingChunkOffsetX;
|
||||||
|
private static volatile int currentRingChunkOffsetZ;
|
||||||
|
|
||||||
|
private static boolean firstUpdate = true;
|
||||||
|
|
||||||
private FogChunkStreamer() {
|
private FogChunkStreamer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record BlockChangeEvent(
|
||||||
|
int chunkX,
|
||||||
|
int chunkZ,
|
||||||
|
int blockX,
|
||||||
|
int blockY,
|
||||||
|
int blockZ,
|
||||||
|
BlockState newState
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// Вызывается из любого потока (в том числе серверного)
|
||||||
|
public static void onBlockChanged(
|
||||||
|
int chunkX,
|
||||||
|
int chunkZ,
|
||||||
|
int blockX,
|
||||||
|
int blockY,
|
||||||
|
int blockZ,
|
||||||
|
BlockState newState
|
||||||
|
) {
|
||||||
|
blockChangeQueue.add(new BlockChangeEvent(chunkX, chunkZ, blockX, blockY, blockZ, newState));
|
||||||
|
}
|
||||||
|
|
||||||
public static void update(
|
public static void update(
|
||||||
int oldOriginChunkX,
|
int oldOriginChunkX,
|
||||||
int oldOriginChunkZ,
|
int oldOriginChunkZ,
|
||||||
int newOriginChunkX,
|
int newOriginChunkX,
|
||||||
int newOriginChunkZ
|
int newOriginChunkZ,
|
||||||
|
int ringChunkOffsetX,
|
||||||
|
int ringChunkOffsetZ
|
||||||
) {
|
) {
|
||||||
|
currentOriginChunkX = newOriginChunkX;
|
||||||
|
currentOriginChunkZ = newOriginChunkZ;
|
||||||
|
currentRingChunkOffsetX = ringChunkOffsetX;
|
||||||
|
currentRingChunkOffsetZ = ringChunkOffsetZ;
|
||||||
|
|
||||||
|
if (firstUpdate) {
|
||||||
|
markAllPending(newOriginChunkX, newOriginChunkZ);
|
||||||
|
firstUpdate = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int dx = newOriginChunkX - oldOriginChunkX;
|
int dx = newOriginChunkX - oldOriginChunkX;
|
||||||
int dz = newOriginChunkZ - oldOriginChunkZ;
|
int dz = newOriginChunkZ - oldOriginChunkZ;
|
||||||
|
|
||||||
/*
|
if (dx == 0 && dz == 0) {
|
||||||
* Телепорт.
|
uploadReadyData();
|
||||||
*/
|
|
||||||
|
|
||||||
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
|
|
||||||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
|
|
||||||
|
|
||||||
refillAll(
|
|
||||||
newOriginChunkX,
|
|
||||||
newOriginChunkZ
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
|
||||||
* Новые колонки.
|
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
|
||||||
*/
|
markAllPending(newOriginChunkX, newOriginChunkZ);
|
||||||
|
uploadReadyData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (dx > 0) {
|
if (dx > 0) {
|
||||||
|
|
||||||
for (int i = 0; i < dx; i++) {
|
for (int i = 0; i < dx; i++) {
|
||||||
|
int worldChunkX = newOriginChunkX + FogWorldVolume.CHUNKS_X - dx + i;
|
||||||
uploadColumn(
|
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
|
||||||
newOriginChunkX +
|
markSlotPending(worldChunkX, newOriginChunkZ + z);
|
||||||
FogWorldVolume.CHUNKS_X - dx + i,
|
}
|
||||||
newOriginChunkZ
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dx < 0) {
|
if (dx < 0) {
|
||||||
|
|
||||||
for (int i = 0; i < -dx; i++) {
|
for (int i = 0; i < -dx; i++) {
|
||||||
|
int worldChunkX = newOriginChunkX + i;
|
||||||
uploadColumn(
|
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
|
||||||
newOriginChunkX + i,
|
markSlotPending(worldChunkX, newOriginChunkZ + z);
|
||||||
newOriginChunkZ
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Новые строки.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (dz > 0) {
|
if (dz > 0) {
|
||||||
|
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
|
||||||
|
int worldChunkX = newOriginChunkX + x;
|
||||||
for (int i = 0; i < dz; i++) {
|
for (int i = 0; i < dz; i++) {
|
||||||
|
int worldChunkZ = newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz + i;
|
||||||
uploadRow(
|
markSlotPending(worldChunkX, worldChunkZ);
|
||||||
newOriginChunkX,
|
}
|
||||||
newOriginChunkZ +
|
|
||||||
FogWorldVolume.CHUNKS_Z - dz + i
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dz < 0) {
|
if (dz < 0) {
|
||||||
|
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
|
||||||
|
int worldChunkX = newOriginChunkX + x;
|
||||||
for (int i = 0; i < -dz; i++) {
|
for (int i = 0; i < -dz; i++) {
|
||||||
|
int worldChunkZ = newOriginChunkZ + i;
|
||||||
|
markSlotPending(worldChunkX, worldChunkZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uploadRow(
|
uploadReadyData();
|
||||||
newOriginChunkX,
|
}
|
||||||
newOriginChunkZ + i
|
|
||||||
|
// Обрабатывает очередь изменений блоков (только в render thread)
|
||||||
|
public static void processBlockChanges() {
|
||||||
|
BlockChangeEvent event;
|
||||||
|
while ((event = blockChangeQueue.poll()) != null) {
|
||||||
|
processSingleBlockChange(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processSingleBlockChange(BlockChangeEvent event) {
|
||||||
|
int originX = currentOriginChunkX;
|
||||||
|
int originZ = currentOriginChunkZ;
|
||||||
|
int ringOffsetX = currentRingChunkOffsetX;
|
||||||
|
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||||
|
|
||||||
|
// Проверяем, находится ли чанк в нашей области
|
||||||
|
int relativeX = event.chunkX - originX;
|
||||||
|
int relativeZ = event.chunkZ - originZ;
|
||||||
|
|
||||||
|
if (relativeX < 0 || relativeX >= FogWorldVolume.CHUNKS_X ||
|
||||||
|
relativeZ < 0 || relativeZ >= FogWorldVolume.CHUNKS_Z) {
|
||||||
|
return; // Чанк вне области обработки
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычисляем физический индекс
|
||||||
|
int physicalX = Math.floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X);
|
||||||
|
int physicalZ = Math.floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
||||||
|
|
||||||
|
// Вычисляем позицию вокселя
|
||||||
|
int localX = event.blockX & 15;
|
||||||
|
int localZ = event.blockZ & 15;
|
||||||
|
int yIndex = event.blockY - FogWorldVolume.MIN_Y;
|
||||||
|
|
||||||
|
if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) {
|
||||||
|
return; // Y вне диапазона
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычисляем значение для этого вокселя
|
||||||
|
float value = FogChunkProvider.computeVoxelValue(event.newState);
|
||||||
|
|
||||||
|
// Обновляем один воксель в текстуре
|
||||||
|
FogWorldVolume.updateSingleVoxel(
|
||||||
|
physicalX,
|
||||||
|
physicalZ,
|
||||||
|
localX,
|
||||||
|
yIndex,
|
||||||
|
localZ,
|
||||||
|
value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static void processPending() {
|
||||||
|
int originX = currentOriginChunkX;
|
||||||
|
int originZ = currentOriginChunkZ;
|
||||||
|
int ringOffsetX = currentRingChunkOffsetX;
|
||||||
|
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||||
|
|
||||||
|
int processedCount = 0;
|
||||||
|
|
||||||
|
for (int physicalIndex = 0; physicalIndex < TOTAL_CHUNKS; physicalIndex++) {
|
||||||
|
if (statuses[physicalIndex] != ChunkStatus.PENDING) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void refillAll(
|
if (processedCount >= MAX_CHUNKS_PER_TICK) {
|
||||||
int originChunkX,
|
break;
|
||||||
int originChunkZ
|
|
||||||
) {
|
|
||||||
|
|
||||||
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
|
|
||||||
|
|
||||||
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
|
|
||||||
|
|
||||||
uploadChunk(
|
|
||||||
originChunkX + x,
|
|
||||||
originChunkZ + z
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void uploadColumn(
|
int physicalX = physicalIndex % FogWorldVolume.CHUNKS_X;
|
||||||
int worldChunkX,
|
int physicalZ = physicalIndex / FogWorldVolume.CHUNKS_X;
|
||||||
int originChunkZ
|
|
||||||
) {
|
|
||||||
|
|
||||||
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
|
int worldChunkX = originX + Math.floorMod(
|
||||||
|
physicalX - ringOffsetX,
|
||||||
uploadChunk(
|
|
||||||
worldChunkX,
|
|
||||||
originChunkZ + z
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void uploadRow(
|
|
||||||
int originChunkX,
|
|
||||||
int worldChunkZ
|
|
||||||
) {
|
|
||||||
|
|
||||||
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
|
|
||||||
|
|
||||||
uploadChunk(
|
|
||||||
originChunkX + x,
|
|
||||||
worldChunkZ
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void uploadChunk(
|
|
||||||
int worldChunkX,
|
|
||||||
int worldChunkZ
|
|
||||||
) {
|
|
||||||
|
|
||||||
FloatBuffer buffer =
|
|
||||||
FogWorldVolume.chunkBuffer();
|
|
||||||
|
|
||||||
FogChunkProvider.fillChunk(
|
|
||||||
worldChunkX,
|
|
||||||
worldChunkZ,
|
|
||||||
buffer
|
|
||||||
);
|
|
||||||
|
|
||||||
buffer.flip();
|
|
||||||
|
|
||||||
int physicalChunkX =
|
|
||||||
Math.floorMod(
|
|
||||||
worldChunkX,
|
|
||||||
FogWorldVolume.CHUNKS_X
|
FogWorldVolume.CHUNKS_X
|
||||||
);
|
);
|
||||||
|
int worldChunkZ = originZ + Math.floorMod(
|
||||||
int physicalChunkZ =
|
physicalZ - ringOffsetZ,
|
||||||
Math.floorMod(
|
|
||||||
worldChunkZ,
|
|
||||||
FogWorldVolume.CHUNKS_Z
|
FogWorldVolume.CHUNKS_Z
|
||||||
);
|
);
|
||||||
|
|
||||||
FogWorldVolume.uploadChunk(
|
if (FogChunkProvider.fillChunkIntoArray(worldChunkX, worldChunkZ, preparedData[physicalIndex])) {
|
||||||
physicalChunkX,
|
preparedWorldChunkX[physicalIndex] = worldChunkX;
|
||||||
physicalChunkZ,
|
preparedWorldChunkZ[physicalIndex] = worldChunkZ;
|
||||||
buffer
|
readyToUpload[physicalIndex] = true;
|
||||||
);
|
processedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void markAllPending(int originChunkX, int originChunkZ) {
|
||||||
|
for (int physicalZ = 0; physicalZ < FogWorldVolume.CHUNKS_Z; physicalZ++) {
|
||||||
|
for (int physicalX = 0; physicalX < FogWorldVolume.CHUNKS_X; physicalX++) {
|
||||||
|
int worldChunkX = originChunkX + physicalX;
|
||||||
|
int worldChunkZ = originChunkZ + physicalZ;
|
||||||
|
markSlotPending(worldChunkX, worldChunkZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void markSlotPending(int worldChunkX, int worldChunkZ) {
|
||||||
|
int physicalX = Math.floorMod(worldChunkX, FogWorldVolume.CHUNKS_X);
|
||||||
|
int physicalZ = Math.floorMod(worldChunkZ, FogWorldVolume.CHUNKS_Z);
|
||||||
|
int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX;
|
||||||
|
|
||||||
|
FogWorldVolume.uploadZeros(physicalX, physicalZ);
|
||||||
|
statuses[physicalIndex] = ChunkStatus.PENDING;
|
||||||
|
readyToUpload[physicalIndex] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void uploadReadyData() {
|
||||||
|
int originX = currentOriginChunkX;
|
||||||
|
int originZ = currentOriginChunkZ;
|
||||||
|
int ringOffsetX = currentRingChunkOffsetX;
|
||||||
|
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||||
|
|
||||||
|
for (int physicalIndex = 0; physicalIndex < TOTAL_CHUNKS; physicalIndex++) {
|
||||||
|
if (!readyToUpload[physicalIndex]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int physicalX = physicalIndex % FogWorldVolume.CHUNKS_X;
|
||||||
|
int physicalZ = physicalIndex / FogWorldVolume.CHUNKS_X;
|
||||||
|
|
||||||
|
int currentWorldX = originX + Math.floorMod(
|
||||||
|
physicalX - ringOffsetX,
|
||||||
|
FogWorldVolume.CHUNKS_X
|
||||||
|
);
|
||||||
|
int currentWorldZ = originZ + Math.floorMod(
|
||||||
|
physicalZ - ringOffsetZ,
|
||||||
|
FogWorldVolume.CHUNKS_Z
|
||||||
|
);
|
||||||
|
|
||||||
|
if (preparedWorldChunkX[physicalIndex] == currentWorldX &&
|
||||||
|
preparedWorldChunkZ[physicalIndex] == currentWorldZ) {
|
||||||
|
|
||||||
|
FogWorldVolume.uploadChunkFromArray(
|
||||||
|
physicalX,
|
||||||
|
physicalZ,
|
||||||
|
preparedData[physicalIndex]
|
||||||
|
);
|
||||||
|
|
||||||
|
statuses[physicalIndex] = ChunkStatus.LOADED;
|
||||||
|
}
|
||||||
|
|
||||||
|
readyToUpload[physicalIndex] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,6 @@ package su.divan2000.veila.client.render.fog;
|
|||||||
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
|
||||||
import java.nio.FloatBuffer;
|
|
||||||
|
|
||||||
public final class FogSystem {
|
public final class FogSystem {
|
||||||
|
|
||||||
private static boolean initialized;
|
private static boolean initialized;
|
||||||
@@ -17,12 +15,6 @@ public final class FogSystem {
|
|||||||
private FogSystem() {
|
private FogSystem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Вызывается в начале render()
|
|
||||||
*
|
|
||||||
* Только Render Thread.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static void beginRender() {
|
public static void beginRender() {
|
||||||
|
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
@@ -30,64 +22,23 @@ public final class FogSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateWindow();
|
updateWindow();
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
// Всегда копируем готовые данные в текстуру
|
||||||
* Вызывается каждый client tick.
|
FogChunkStreamer.uploadReadyData();
|
||||||
*
|
|
||||||
* Пока пусто.
|
// Всегда обрабатываем изменения блоков
|
||||||
*/
|
FogChunkStreamer.processBlockChanges();
|
||||||
|
}
|
||||||
|
|
||||||
public static void tick() {
|
public static void tick() {
|
||||||
|
FogChunkStreamer.processPending();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Инициализация всей системы.
|
|
||||||
*/
|
|
||||||
|
|
||||||
private static void initialize() {
|
private static void initialize() {
|
||||||
|
|
||||||
FogWorldVolume.init();
|
FogWorldVolume.init();
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Пока просто заполняем весь объём
|
|
||||||
* тестовыми чанками.
|
|
||||||
*/
|
|
||||||
|
|
||||||
private static void fillTestVolume() {
|
|
||||||
|
|
||||||
for (int chunkZ = 0;
|
|
||||||
chunkZ < FogWorldVolume.CHUNKS_Z;
|
|
||||||
chunkZ++) {
|
|
||||||
|
|
||||||
for (int chunkX = 0;
|
|
||||||
chunkX < FogWorldVolume.CHUNKS_X;
|
|
||||||
chunkX++) {
|
|
||||||
|
|
||||||
FloatBuffer buffer =
|
|
||||||
FogWorldVolume.chunkBuffer();
|
|
||||||
|
|
||||||
FogChunkProvider.fillChunk(
|
|
||||||
originChunkX + chunkX,
|
|
||||||
originChunkZ + chunkZ,
|
|
||||||
buffer
|
|
||||||
);
|
|
||||||
|
|
||||||
buffer.flip();
|
|
||||||
|
|
||||||
FogWorldVolume.uploadChunk(
|
|
||||||
chunkX,
|
|
||||||
chunkZ,
|
|
||||||
buffer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void updateWindow() {
|
private static void updateWindow() {
|
||||||
|
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
@@ -126,7 +77,9 @@ public final class FogSystem {
|
|||||||
oldOriginChunkX,
|
oldOriginChunkX,
|
||||||
oldOriginChunkZ,
|
oldOriginChunkZ,
|
||||||
originChunkX,
|
originChunkX,
|
||||||
originChunkZ
|
originChunkZ,
|
||||||
|
ringChunkOffsetX,
|
||||||
|
ringChunkOffsetZ
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ public final class FogWorldVolume {
|
|||||||
* 16 × 384 × 16
|
* 16 × 384 × 16
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
private static final float[] ZEROS_ARRAY = new float[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
|
||||||
|
static {
|
||||||
|
java.util.Arrays.fill(ZEROS_ARRAY, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
private static final FloatBuffer CHUNK_BUFFER =
|
private static final FloatBuffer CHUNK_BUFFER =
|
||||||
BufferUtils.createFloatBuffer(
|
BufferUtils.createFloatBuffer(
|
||||||
CHUNK_SIZE *
|
CHUNK_SIZE *
|
||||||
@@ -50,6 +55,10 @@ public final class FogWorldVolume {
|
|||||||
CHUNK_SIZE
|
CHUNK_SIZE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
private static final FloatBuffer SINGLE_VOXEL_BUFFER =
|
||||||
|
BufferUtils.createFloatBuffer(1);
|
||||||
|
|
||||||
private FogWorldVolume() {
|
private FogWorldVolume() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +102,7 @@ public final class FogWorldVolume {
|
|||||||
GL11.glTexParameteri(
|
GL11.glTexParameteri(
|
||||||
GL12.GL_TEXTURE_3D,
|
GL12.GL_TEXTURE_3D,
|
||||||
GL12.GL_TEXTURE_WRAP_S,
|
GL12.GL_TEXTURE_WRAP_S,
|
||||||
GL12.GL_CLAMP_TO_EDGE
|
GL12.GL_REPEAT
|
||||||
);
|
);
|
||||||
|
|
||||||
GL11.glTexParameteri(
|
GL11.glTexParameteri(
|
||||||
@@ -105,7 +114,7 @@ public final class FogWorldVolume {
|
|||||||
GL11.glTexParameteri(
|
GL11.glTexParameteri(
|
||||||
GL12.GL_TEXTURE_3D,
|
GL12.GL_TEXTURE_3D,
|
||||||
GL12.GL_TEXTURE_WRAP_R,
|
GL12.GL_TEXTURE_WRAP_R,
|
||||||
GL12.GL_CLAMP_TO_EDGE
|
GL12.GL_REPEAT
|
||||||
);
|
);
|
||||||
|
|
||||||
GL11.glBindTexture(
|
GL11.glBindTexture(
|
||||||
@@ -188,6 +197,58 @@ public final class FogWorldVolume {
|
|||||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void updateSingleVoxel(
|
||||||
|
int textureChunkX,
|
||||||
|
int textureChunkZ,
|
||||||
|
int localX,
|
||||||
|
int localY,
|
||||||
|
int localZ,
|
||||||
|
float value
|
||||||
|
) {
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||||
|
|
||||||
|
SINGLE_VOXEL_BUFFER.clear();
|
||||||
|
SINGLE_VOXEL_BUFFER.put(value);
|
||||||
|
SINGLE_VOXEL_BUFFER.flip();
|
||||||
|
|
||||||
|
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 + localX,
|
||||||
|
localY,
|
||||||
|
textureChunkZ * CHUNK_SIZE + localZ,
|
||||||
|
1, 1, 1,
|
||||||
|
GL11.GL_RED,
|
||||||
|
GL11.GL_FLOAT,
|
||||||
|
SINGLE_VOXEL_BUFFER
|
||||||
|
);
|
||||||
|
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void uploadZeros(int textureChunkX, int textureChunkZ) {
|
||||||
|
FloatBuffer buffer = chunkBuffer();
|
||||||
|
buffer.put(ZEROS_ARRAY);
|
||||||
|
buffer.flip();
|
||||||
|
uploadChunk(textureChunkX, textureChunkZ, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void uploadChunkFromArray(
|
||||||
|
int textureChunkX,
|
||||||
|
int textureChunkZ,
|
||||||
|
float[] data
|
||||||
|
) {
|
||||||
|
FloatBuffer buffer = chunkBuffer();
|
||||||
|
buffer.put(data);
|
||||||
|
buffer.flip();
|
||||||
|
uploadChunk(textureChunkX, textureChunkZ, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
public static FloatBuffer chunkBuffer() {
|
public static FloatBuffer chunkBuffer() {
|
||||||
|
|
||||||
CHUNK_BUFFER.clear();
|
CHUNK_BUFFER.clear();
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package su.divan2000.veila.mixin.client;
|
||||||
|
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.chunk.WorldChunk;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
import su.divan2000.veila.client.render.fog.FogChunkStreamer;
|
||||||
|
|
||||||
|
@Mixin(WorldChunk.class)
|
||||||
|
public class WorldChunkMixin {
|
||||||
|
@Inject(
|
||||||
|
method = "setBlockState",
|
||||||
|
at = @At("RETURN")
|
||||||
|
)
|
||||||
|
private void veila$onBlockChanged(
|
||||||
|
BlockPos pos,
|
||||||
|
BlockState state,
|
||||||
|
boolean moved,
|
||||||
|
CallbackInfoReturnable<BlockState> cir
|
||||||
|
) {
|
||||||
|
// Только если блок реально изменился
|
||||||
|
if (cir.getReturnValue() != null) {
|
||||||
|
WorldChunk self = (WorldChunk) (Object) this;
|
||||||
|
int chunkX = self.getPos().x;
|
||||||
|
int chunkZ = self.getPos().z;
|
||||||
|
|
||||||
|
// Уведомляем стример об изменении
|
||||||
|
FogChunkStreamer.onBlockChanged(
|
||||||
|
chunkX,
|
||||||
|
chunkZ,
|
||||||
|
pos.getX(),
|
||||||
|
pos.getY(),
|
||||||
|
pos.getZ(),
|
||||||
|
state
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,18 +20,27 @@ out vec4 FragColor;
|
|||||||
// Настройки
|
// Настройки
|
||||||
//------------------------------------------------------------
|
//------------------------------------------------------------
|
||||||
|
|
||||||
const float STEP_SIZE = 0.5;
|
|
||||||
const float MAX_DISTANCE = 256.0;
|
|
||||||
const int MAX_STEPS = int(MAX_DISTANCE / STEP_SIZE);
|
|
||||||
|
|
||||||
// Размер туманного объёма
|
|
||||||
const float FOG_SIZE_X = 256.0;
|
const float FOG_SIZE_X = 256.0;
|
||||||
const float FOG_SIZE_Z = 256.0;
|
const float FOG_SIZE_Z = FOG_SIZE_X;
|
||||||
|
|
||||||
|
const int MAX_STEPS = 256; // Уменьшили количество шагов благодаря адаптивности
|
||||||
|
|
||||||
// Высота мира
|
// Высота мира
|
||||||
const float WORLD_BOTTOM = -64.0;
|
const float WORLD_BOTTOM = -64.0;
|
||||||
const float WORLD_HEIGHT = 384.0;
|
const float WORLD_HEIGHT = 384.0;
|
||||||
|
|
||||||
|
const float MAX_DISTANCE = (FOG_SIZE_X / 2.0) - 18.0;
|
||||||
|
|
||||||
|
// Коэффициент нелинейности распределения шагов:
|
||||||
|
// 1.0 = равномерные шаги (как было)
|
||||||
|
// 1.5 = мягкое увеличение (рекомендуется для начала)
|
||||||
|
// 2.0 = линейный рост размера шага с расстоянием (хороший баланс)
|
||||||
|
// 2.5 = более агрессивное увеличение (лучшая производительность)
|
||||||
|
const float GAMMA = 2.0;
|
||||||
|
|
||||||
|
// Включение jittering для устранения banding артефактов
|
||||||
|
const bool USE_JITTERING = true;
|
||||||
|
|
||||||
//------------------------------------------------------------
|
//------------------------------------------------------------
|
||||||
|
|
||||||
vec3 reconstructViewPosition(vec2 uv)
|
vec3 reconstructViewPosition(vec2 uv)
|
||||||
@@ -76,15 +85,11 @@ float density(vec3 worldPos)
|
|||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
float physicalX = mod(
|
float physicalX =
|
||||||
localX + float(RingBlockOffset.x),
|
localX + float(RingBlockOffset.x);
|
||||||
FOG_SIZE_X
|
|
||||||
);
|
|
||||||
|
|
||||||
float physicalZ = mod(
|
float physicalZ =
|
||||||
localZ + float(RingBlockOffset.y),
|
localZ + float(RingBlockOffset.y);
|
||||||
FOG_SIZE_Z
|
|
||||||
);
|
|
||||||
|
|
||||||
uv.x = physicalX / FOG_SIZE_X;
|
uv.x = physicalX / FOG_SIZE_X;
|
||||||
uv.z = physicalZ / FOG_SIZE_Z;
|
uv.z = physicalZ / FOG_SIZE_Z;
|
||||||
@@ -95,6 +100,21 @@ float density(vec3 worldPos)
|
|||||||
|
|
||||||
//------------------------------------------------------------
|
//------------------------------------------------------------
|
||||||
|
|
||||||
|
// Псевдослучайное значение для jittering (детерминированное по UV)
|
||||||
|
float random(vec2 st)
|
||||||
|
{
|
||||||
|
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция нелинейного маппинга параметра t [0,1] в расстояние
|
||||||
|
// Использует степенную функцию для распределения шагов
|
||||||
|
float mapParameterToDistance(float t)
|
||||||
|
{
|
||||||
|
return MAX_DISTANCE * pow(t, GAMMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec4 sceneColor = texture(DiffuseSampler, texCoord);
|
vec4 sceneColor = texture(DiffuseSampler, texCoord);
|
||||||
@@ -109,17 +129,52 @@ void main()
|
|||||||
vec3 samplePos = CameraPosition;
|
vec3 samplePos = CameraPosition;
|
||||||
|
|
||||||
float fog = 0.0;
|
float fog = 0.0;
|
||||||
|
float currentDistance = 0.0;
|
||||||
|
|
||||||
for (int i = 0; i < MAX_STEPS; i++)
|
// Jittering начальной позиции для устранения banding (ступенчатых артефактов)
|
||||||
{
|
// Это стандартная техника в volumetric rendering
|
||||||
float traveled = float(i) * STEP_SIZE;
|
float jitter = 0.0;
|
||||||
|
if (USE_JITTERING) {
|
||||||
|
// Вычисляем размер первого шага для корректного jittering
|
||||||
|
float firstStep = mapParameterToDistance(1.0 / float(MAX_STEPS));
|
||||||
|
jitter = random(texCoord) * firstStep;
|
||||||
|
currentDistance = jitter;
|
||||||
|
samplePos += rayDir * currentDistance;
|
||||||
|
}
|
||||||
|
|
||||||
if (traveled >= rayLength)
|
for (int i = 0; i < MAX_STEPS; i++) {
|
||||||
|
// Параметр t меняется от 0 до 1
|
||||||
|
float t = float(i + 1) / float(MAX_STEPS);
|
||||||
|
|
||||||
|
// Вычисляем следующее расстояние через нелинейное маппинг
|
||||||
|
float nextDistance = mapParameterToDistance(t);
|
||||||
|
|
||||||
|
// Ограничиваем максимальной длиной луча
|
||||||
|
if (nextDistance > rayLength) {
|
||||||
|
nextDistance = rayLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Размер текущего шага
|
||||||
|
float stepSize = nextDistance - currentDistance;
|
||||||
|
|
||||||
|
// Проверяем, не вышли ли за пределы луча
|
||||||
|
if (stepSize <= 0.0) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
samplePos += rayDir * STEP_SIZE;
|
// Продвигаемся по лучу
|
||||||
|
samplePos += rayDir * stepSize;
|
||||||
|
|
||||||
fog += density(samplePos) * STEP_SIZE;
|
// Интегрируем плотность (Beer-Lambert law approximation)
|
||||||
|
// Важно умножать на stepSize для корректной физической модели
|
||||||
|
fog += density(samplePos) * stepSize;
|
||||||
|
|
||||||
|
currentDistance = nextDistance;
|
||||||
|
|
||||||
|
// Прерываем, если достигли конца луча
|
||||||
|
if (currentDistance >= rayLength) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fog = clamp(fog, 0.0, 1.0);
|
fog = clamp(fog, 0.0, 1.0);
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
"compatibilityLevel": "JAVA_17",
|
"compatibilityLevel": "JAVA_17",
|
||||||
"client": [
|
"client": [
|
||||||
"GameRendererMixin",
|
"GameRendererMixin",
|
||||||
"MinecraftClientMixin"
|
"MinecraftClientMixin",
|
||||||
|
"WorldChunkMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "veila",
|
"id": "veila",
|
||||||
"version": "${version}",
|
"version": "${version}",
|
||||||
|
"accessWidener": "veila.accesswidener",
|
||||||
|
|
||||||
"name": "Veila",
|
"name": "Veila",
|
||||||
"description": "",
|
"description": "",
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
accessWidener v2 named
|
||||||
|
accessible method net/minecraft/world/chunk/PalettedContainer get (I)Ljava/lang/Object;
|
||||||
Reference in New Issue
Block a user