Полностью корректный кольцевой буффер
This commit is contained in:
@@ -1,58 +1,100 @@
|
||||
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 {
|
||||
static void fillChunk(
|
||||
int chunkX,
|
||||
int chunkZ,
|
||||
FloatBuffer buffer
|
||||
) {
|
||||
|
||||
float density =
|
||||
hash(chunkX, chunkZ);
|
||||
public static boolean isChunkLoaded(int chunkX, int chunkZ) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
|
||||
for (int z = 0;
|
||||
z < FogWorldVolume.CHUNK_SIZE;
|
||||
z++) {
|
||||
if (client.world == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0;
|
||||
y < FogWorldVolume.SIZE_Y;
|
||||
y++) {
|
||||
return client.world.getChunkManager().getWorldChunk(chunkX, chunkZ, false) != null;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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(
|
||||
int x,
|
||||
int z
|
||||
) {
|
||||
public static boolean fillChunkIntoArray(int chunkX, int chunkZ, float[] array) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
|
||||
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)
|
||||
/ 255.0f
|
||||
* 0.08f;
|
||||
// Порядок циклов Z -> Y -> X критичен для glTexSubImage3D
|
||||
for (int z = 0; z < FogWorldVolume.CHUNK_SIZE; z++) {
|
||||
// Кэшируем секции и их состояние
|
||||
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;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import net.minecraft.block.BlockState;
|
||||
|
||||
import static su.divan2000.veila.client.render.fog.FogWorldVolume.CHUNK_SIZE;
|
||||
import static su.divan2000.veila.client.render.fog.FogWorldVolume.SIZE_Y;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
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 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(
|
||||
int oldOriginChunkX,
|
||||
int oldOriginChunkZ,
|
||||
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 dz = newOriginChunkZ - oldOriginChunkZ;
|
||||
|
||||
/*
|
||||
* Телепорт.
|
||||
*/
|
||||
|
||||
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
|
||||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
|
||||
|
||||
refillAll(
|
||||
newOriginChunkX,
|
||||
newOriginChunkZ
|
||||
);
|
||||
|
||||
if (dx == 0 && dz == 0) {
|
||||
uploadReadyData();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Новые колонки.
|
||||
*/
|
||||
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
|
||||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
|
||||
markAllPending(newOriginChunkX, newOriginChunkZ);
|
||||
uploadReadyData();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dx > 0) {
|
||||
|
||||
for (int i = 0; i < dx; i++) {
|
||||
|
||||
uploadColumn(
|
||||
newOriginChunkX +
|
||||
FogWorldVolume.CHUNKS_X - dx + i,
|
||||
newOriginChunkZ
|
||||
);
|
||||
int worldChunkX = newOriginChunkX + FogWorldVolume.CHUNKS_X - dx + i;
|
||||
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
|
||||
markSlotPending(worldChunkX, newOriginChunkZ + z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dx < 0) {
|
||||
|
||||
for (int i = 0; i < -dx; i++) {
|
||||
|
||||
uploadColumn(
|
||||
newOriginChunkX + i,
|
||||
newOriginChunkZ
|
||||
);
|
||||
int worldChunkX = newOriginChunkX + i;
|
||||
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
|
||||
markSlotPending(worldChunkX, newOriginChunkZ + z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Новые строки.
|
||||
*/
|
||||
|
||||
if (dz > 0) {
|
||||
|
||||
for (int i = 0; i < dz; i++) {
|
||||
|
||||
uploadRow(
|
||||
newOriginChunkX,
|
||||
newOriginChunkZ +
|
||||
FogWorldVolume.CHUNKS_Z - dz + i
|
||||
);
|
||||
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
|
||||
int worldChunkX = newOriginChunkX + x;
|
||||
for (int i = 0; i < dz; i++) {
|
||||
int worldChunkZ = newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz + i;
|
||||
markSlotPending(worldChunkX, worldChunkZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dz < 0) {
|
||||
|
||||
for (int i = 0; i < -dz; i++) {
|
||||
|
||||
uploadRow(
|
||||
newOriginChunkX,
|
||||
newOriginChunkZ + i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void refillAll(
|
||||
int originChunkX,
|
||||
int originChunkZ
|
||||
) {
|
||||
|
||||
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
|
||||
|
||||
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
|
||||
int worldChunkX = newOriginChunkX + x;
|
||||
for (int i = 0; i < -dz; i++) {
|
||||
int worldChunkZ = newOriginChunkZ + i;
|
||||
markSlotPending(worldChunkX, worldChunkZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uploadChunk(
|
||||
originChunkX + x,
|
||||
originChunkZ + z
|
||||
);
|
||||
uploadReadyData();
|
||||
}
|
||||
|
||||
// Обрабатывает очередь изменений блоков (только в 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;
|
||||
}
|
||||
|
||||
if (processedCount >= MAX_CHUNKS_PER_TICK) {
|
||||
break;
|
||||
}
|
||||
|
||||
int physicalX = physicalIndex % FogWorldVolume.CHUNKS_X;
|
||||
int physicalZ = physicalIndex / FogWorldVolume.CHUNKS_X;
|
||||
|
||||
int worldChunkX = originX + Math.floorMod(
|
||||
physicalX - ringOffsetX,
|
||||
FogWorldVolume.CHUNKS_X
|
||||
);
|
||||
int worldChunkZ = originZ + Math.floorMod(
|
||||
physicalZ - ringOffsetZ,
|
||||
FogWorldVolume.CHUNKS_Z
|
||||
);
|
||||
|
||||
if (FogChunkProvider.fillChunkIntoArray(worldChunkX, worldChunkZ, preparedData[physicalIndex])) {
|
||||
preparedWorldChunkX[physicalIndex] = worldChunkX;
|
||||
preparedWorldChunkZ[physicalIndex] = worldChunkZ;
|
||||
readyToUpload[physicalIndex] = true;
|
||||
processedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadColumn(
|
||||
int worldChunkX,
|
||||
int originChunkZ
|
||||
) {
|
||||
|
||||
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
|
||||
|
||||
uploadChunk(
|
||||
worldChunkX,
|
||||
originChunkZ + z
|
||||
);
|
||||
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 uploadRow(
|
||||
int originChunkX,
|
||||
int 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;
|
||||
|
||||
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
|
||||
FogWorldVolume.uploadZeros(physicalX, physicalZ);
|
||||
statuses[physicalIndex] = ChunkStatus.PENDING;
|
||||
readyToUpload[physicalIndex] = false;
|
||||
}
|
||||
|
||||
uploadChunk(
|
||||
originChunkX + x,
|
||||
worldChunkZ
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
int physicalChunkZ =
|
||||
Math.floorMod(
|
||||
worldChunkZ,
|
||||
FogWorldVolume.CHUNKS_Z
|
||||
);
|
||||
|
||||
FogWorldVolume.uploadChunk(
|
||||
physicalChunkX,
|
||||
physicalChunkZ,
|
||||
buffer
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package su.divan2000.veila.client.render.fog;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public final class FogSystem {
|
||||
|
||||
private static boolean initialized;
|
||||
@@ -17,12 +15,6 @@ public final class FogSystem {
|
||||
private FogSystem() {
|
||||
}
|
||||
|
||||
/*
|
||||
* Вызывается в начале render()
|
||||
*
|
||||
* Только Render Thread.
|
||||
*/
|
||||
|
||||
public static void beginRender() {
|
||||
|
||||
if (!initialized) {
|
||||
@@ -30,64 +22,23 @@ public final class FogSystem {
|
||||
}
|
||||
|
||||
updateWindow();
|
||||
}
|
||||
|
||||
/*
|
||||
* Вызывается каждый client tick.
|
||||
*
|
||||
* Пока пусто.
|
||||
*/
|
||||
// Всегда копируем готовые данные в текстуру
|
||||
FogChunkStreamer.uploadReadyData();
|
||||
|
||||
// Всегда обрабатываем изменения блоков
|
||||
FogChunkStreamer.processBlockChanges();
|
||||
}
|
||||
|
||||
public static void tick() {
|
||||
|
||||
FogChunkStreamer.processPending();
|
||||
}
|
||||
|
||||
/*
|
||||
* Инициализация всей системы.
|
||||
*/
|
||||
|
||||
private static void initialize() {
|
||||
|
||||
FogWorldVolume.init();
|
||||
|
||||
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() {
|
||||
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
@@ -126,7 +77,9 @@ public final class FogSystem {
|
||||
oldOriginChunkX,
|
||||
oldOriginChunkZ,
|
||||
originChunkX,
|
||||
originChunkZ
|
||||
originChunkZ,
|
||||
ringChunkOffsetX,
|
||||
ringChunkOffsetZ
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,11 @@ public final class FogWorldVolume {
|
||||
* 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 =
|
||||
BufferUtils.createFloatBuffer(
|
||||
CHUNK_SIZE *
|
||||
@@ -50,6 +55,10 @@ public final class FogWorldVolume {
|
||||
CHUNK_SIZE
|
||||
);
|
||||
|
||||
|
||||
private static final FloatBuffer SINGLE_VOXEL_BUFFER =
|
||||
BufferUtils.createFloatBuffer(1);
|
||||
|
||||
private FogWorldVolume() {
|
||||
}
|
||||
|
||||
@@ -93,7 +102,7 @@ public final class FogWorldVolume {
|
||||
GL11.glTexParameteri(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
GL12.GL_TEXTURE_WRAP_S,
|
||||
GL12.GL_CLAMP_TO_EDGE
|
||||
GL12.GL_REPEAT
|
||||
);
|
||||
|
||||
GL11.glTexParameteri(
|
||||
@@ -105,7 +114,7 @@ public final class FogWorldVolume {
|
||||
GL11.glTexParameteri(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
GL12.GL_TEXTURE_WRAP_R,
|
||||
GL12.GL_CLAMP_TO_EDGE
|
||||
GL12.GL_REPEAT
|
||||
);
|
||||
|
||||
GL11.glBindTexture(
|
||||
@@ -188,6 +197,58 @@ public final class FogWorldVolume {
|
||||
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() {
|
||||
|
||||
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_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_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)
|
||||
@@ -68,23 +77,19 @@ float density(vec3 worldPos)
|
||||
float localZ = worldPos.z - float(FogOrigin.y);
|
||||
|
||||
if (
|
||||
localX < 0.0 || localX >= FOG_SIZE_X ||
|
||||
localZ < 0.0 || localZ >= FOG_SIZE_Z ||
|
||||
worldPos.y < WORLD_BOTTOM ||
|
||||
worldPos.y >= WORLD_BOTTOM + WORLD_HEIGHT
|
||||
localX < 0.0 || localX >= FOG_SIZE_X ||
|
||||
localZ < 0.0 || localZ >= FOG_SIZE_Z ||
|
||||
worldPos.y < WORLD_BOTTOM ||
|
||||
worldPos.y >= WORLD_BOTTOM + WORLD_HEIGHT
|
||||
){
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
float physicalX = mod(
|
||||
localX + float(RingBlockOffset.x),
|
||||
FOG_SIZE_X
|
||||
);
|
||||
float physicalX =
|
||||
localX + float(RingBlockOffset.x);
|
||||
|
||||
float physicalZ = mod(
|
||||
localZ + float(RingBlockOffset.y),
|
||||
FOG_SIZE_Z
|
||||
);
|
||||
float physicalZ =
|
||||
localZ + float(RingBlockOffset.y);
|
||||
|
||||
uv.x = physicalX / FOG_SIZE_X;
|
||||
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()
|
||||
{
|
||||
vec4 sceneColor = texture(DiffuseSampler, texCoord);
|
||||
@@ -109,17 +129,52 @@ void main()
|
||||
vec3 samplePos = CameraPosition;
|
||||
|
||||
float fog = 0.0;
|
||||
float currentDistance = 0.0;
|
||||
|
||||
for (int i = 0; i < MAX_STEPS; i++)
|
||||
{
|
||||
float traveled = float(i) * STEP_SIZE;
|
||||
// Jittering начальной позиции для устранения banding (ступенчатых артефактов)
|
||||
// Это стандартная техника в volumetric rendering
|
||||
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)
|
||||
break;
|
||||
for (int i = 0; i < MAX_STEPS; i++) {
|
||||
// Параметр t меняется от 0 до 1
|
||||
float t = float(i + 1) / float(MAX_STEPS);
|
||||
|
||||
samplePos += rayDir * STEP_SIZE;
|
||||
// Вычисляем следующее расстояние через нелинейное маппинг
|
||||
float nextDistance = mapParameterToDistance(t);
|
||||
|
||||
fog += density(samplePos) * STEP_SIZE;
|
||||
// Ограничиваем максимальной длиной луча
|
||||
if (nextDistance > rayLength) {
|
||||
nextDistance = rayLength;
|
||||
}
|
||||
|
||||
// Размер текущего шага
|
||||
float stepSize = nextDistance - currentDistance;
|
||||
|
||||
// Проверяем, не вышли ли за пределы луча
|
||||
if (stepSize <= 0.0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Продвигаемся по лучу
|
||||
samplePos += rayDir * stepSize;
|
||||
|
||||
// Интегрируем плотность (Beer-Lambert law approximation)
|
||||
// Важно умножать на stepSize для корректной физической модели
|
||||
fog += density(samplePos) * stepSize;
|
||||
|
||||
currentDistance = nextDistance;
|
||||
|
||||
// Прерываем, если достигли конца луча
|
||||
if (currentDistance >= rayLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fog = clamp(fog, 0.0, 1.0);
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"client": [
|
||||
"GameRendererMixin",
|
||||
"MinecraftClientMixin"
|
||||
"MinecraftClientMixin",
|
||||
"WorldChunkMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"id": "veila",
|
||||
"version": "${version}",
|
||||
"accessWidener": "veila.accesswidener",
|
||||
|
||||
"name": "Veila",
|
||||
"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