Полностью корректный кольцевой буффер

This commit is contained in:
2026-07-31 23:56:47 +04:00
parent 5e3f985025
commit eb497f257a
10 changed files with 519 additions and 247 deletions
+2
View File
@@ -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
+1
View File
@@ -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": "",
+2
View File
@@ -0,0 +1,2 @@
accessWidener v2 named
accessible method net/minecraft/world/chunk/PalettedContainer get (I)Ljava/lang/Object;