Теперь строится worldinfo для будующей симуляции

This commit is contained in:
2026-08-01 18:06:41 +04:00
parent eb497f257a
commit 41238fddb0
6 changed files with 195 additions and 147 deletions
@@ -0,0 +1,49 @@
package su.divan2000.veila.client.render.fog;
public record FogBlockProperties(
boolean solid,
int sign,
int magnitude
) {
public static final FogBlockProperties DEFAULT = new FogBlockProperties(false, 0, 0);
public FogBlockProperties {
if (sign < -1 || sign > 1) {
throw new IllegalArgumentException("sign must be -1, 0, or 1");
}
if (magnitude < 0 || magnitude > 63) {
throw new IllegalArgumentException("magnitude must be 0-63");
}
}
/**
* Упаковывает свойства в один байт:
* Бит 7: solid (1 = solid)
* Бит 6: sign (0 = source/neutral, 1 = absorber)
* Биты 0-5: magnitude (0-63)
*/
public byte pack() {
int packed = 0;
if (solid) {
packed |= 0b10000000;
}
if (sign < 0) {
packed |= 0b01000000;
}
packed |= (magnitude & 0b00111111);
return (byte) packed;
}
public static FogBlockProperties unpack(byte packed) {
boolean solid = (packed & 0b10000000) != 0;
boolean isAbsorber = (packed & 0b01000000) != 0;
int magnitude = packed & 0b00111111;
int sign = isAbsorber ? -1 : (magnitude > 0 ? 1 : 0);
return new FogBlockProperties(solid, sign, magnitude);
}
}
@@ -0,0 +1,96 @@
package su.divan2000.veila.client.render.fog;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.fluid.Fluids;
import net.minecraft.registry.Registries;
import net.minecraft.registry.tag.BlockTags;
public final class FogBlockRegistry {
private static final byte[] BLOCK_CACHE;
private static final boolean[] BLOCK_CACHE_INITIALIZED;
// Предвычисленное значение для waterlogged блоков (вода)
private static final byte WATER_VALUE;
static {
int blockCount = Registries.BLOCK.size();
BLOCK_CACHE = new byte[blockCount];
BLOCK_CACHE_INITIALIZED = new boolean[blockCount];
// Waterlogged блок всегда содержит воду
WATER_VALUE = new FogBlockProperties(true, 1, 60).pack();
}
private FogBlockRegistry() {
}
public static byte getPackedProperties(BlockState state) {
// СНАЧАЛА проверяем конкретно воду (waterlogged или обычный блок воды)
if (state.getFluidState().getFluid() == Fluids.WATER) {
return WATER_VALUE;
}
Block block = state.getBlock();
int id = Registries.BLOCK.getRawId(block);
if (BLOCK_CACHE_INITIALIZED[id]) {
return BLOCK_CACHE[id];
}
byte packed = computePackedProperties(state);
BLOCK_CACHE[id] = packed;
BLOCK_CACHE_INITIALIZED[id] = true;
return packed;
}
private static byte computePackedProperties(BlockState state) {
Block block = state.getBlock();
boolean solid = computeSolid(state);
// Источники тумана (sign = +1)
// WATER уже обработан в getPackedProperties
if (block == Blocks.GRASS || block == Blocks.TALL_GRASS || block == Blocks.FERN) {
return new FogBlockProperties(solid, 1, 10).pack();
}
if (block == Blocks.VINE) {
return new FogBlockProperties(solid, 1, 5).pack();
}
// Поглотители тумана (sign = -1)
if (block == Blocks.LAVA) {
return new FogBlockProperties(solid, -1, 50).pack();
}
if (block == Blocks.MAGMA_BLOCK) {
return new FogBlockProperties(solid, -1, 40).pack();
}
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
return new FogBlockProperties(solid, -1, 30).pack();
}
if (block == Blocks.CAMPFIRE || block == Blocks.SOUL_CAMPFIRE) {
return new FogBlockProperties(solid, -1, 25).pack();
}
if (block == Blocks.TORCH || block == Blocks.WALL_TORCH ||
block == Blocks.SOUL_TORCH || block == Blocks.SOUL_WALL_TORCH) {
return new FogBlockProperties(solid, -1, 15).pack();
}
if (block == Blocks.LANTERN || block == Blocks.SOUL_LANTERN) {
return new FogBlockProperties(solid, -1, 10).pack();
}
// Нейтральные блоки (sign = 0)
return new FogBlockProperties(solid, 0, 0).pack();
}
@SuppressWarnings("deprecation")
private static boolean computeSolid(BlockState state) {
if (state.isAir()) return false;
// Waterlogged уже обработан в getPackedProperties
if (!state.getFluidState().isEmpty()) return true; // лава и другие жидкости
if (state.isIn(BlockTags.LEAVES)) return false;
return state.blocksMovement();
}
}
@@ -20,17 +20,7 @@ public final class FogChunkProvider {
return client.world.getChunkManager().getWorldChunk(chunkX, chunkZ, false) != null; return client.world.getChunkManager().getWorldChunk(chunkX, chunkZ, false) != null;
} }
public static float computeVoxelValue(BlockState state) { public static boolean fillChunkIntoArray(int chunkX, int chunkZ, byte[] array) {
if (state.isOf(Blocks.POPPY)) {
return 0.2f;
} else if (state.isOf(Blocks.GRASS)) {
return 0.4f;
} else {
return 0.0f;
}
}
public static boolean fillChunkIntoArray(int chunkX, int chunkZ, float[] array) {
MinecraftClient client = MinecraftClient.getInstance(); MinecraftClient client = MinecraftClient.getInstance();
if (!isChunkLoaded(chunkX, chunkZ)) return false; if (!isChunkLoaded(chunkX, chunkZ)) return false;
@@ -42,10 +32,9 @@ public final class FogChunkProvider {
int topYExclusive = bottomY + sections.length * 16; int topYExclusive = bottomY + sections.length * 16;
int index = 0; int index = 0;
byte airValue = FogBlockRegistry.getPackedProperties(Blocks.AIR.getDefaultState());
// Порядок циклов Z -> Y -> X критичен для glTexSubImage3D
for (int z = 0; z < FogWorldVolume.CHUNK_SIZE; z++) { for (int z = 0; z < FogWorldVolume.CHUNK_SIZE; z++) {
// Кэшируем секции и их состояние
int currentSectionIndex = -1; int currentSectionIndex = -1;
ChunkSection currentSection = null; ChunkSection currentSection = null;
PalettedContainer<BlockState> currentContainer = null; PalettedContainer<BlockState> currentContainer = null;
@@ -54,9 +43,8 @@ public final class FogChunkProvider {
for (int y = 0; y < FogWorldVolume.SIZE_Y; y++) { for (int y = 0; y < FogWorldVolume.SIZE_Y; y++) {
int worldY = FogWorldVolume.MIN_Y + y; int worldY = FogWorldVolume.MIN_Y + y;
// За пределами мира (например, ниже -64)
if (worldY < bottomY || worldY >= topYExclusive) { if (worldY < bottomY || worldY >= topYExclusive) {
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, 0.0f); Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, airValue);
index += FogWorldVolume.CHUNK_SIZE; index += FogWorldVolume.CHUNK_SIZE;
continue; continue;
} }
@@ -76,25 +64,25 @@ public final class FogChunkProvider {
int localY = worldY & 15; int localY = worldY & 15;
// ОПТИМИЗАЦИЯ: Если текущая секция полностью пустая (только воздух)
if (currentIsEmpty) { if (currentIsEmpty) {
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, 0.0f); Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, airValue);
index += FogWorldVolume.CHUNK_SIZE; index += FogWorldVolume.CHUNK_SIZE;
continue; continue;
} }
// Предварительное вычисление базового индекса для строки Y и Z
// Формула локального индекса в секции 16x16x16: (y << 8) | (z << 4) | x
int baseIndex = (localY << 8) | (z << 4); int baseIndex = (localY << 8) | (z << 4);
for (int x = 0; x < FogWorldVolume.CHUNK_SIZE; x++) { for (int x = 0; x < FogWorldVolume.CHUNK_SIZE; x++) {
BlockState currentBlock = currentContainer.get(baseIndex | x); BlockState currentBlock = currentContainer.get(baseIndex | x);
array[index++] = FogBlockRegistry.getPackedProperties(currentBlock);
array[index++] = computeVoxelValue(currentBlock);
} }
} }
} }
return true; return true;
} }
public static byte computePackedVoxelValue(BlockState state) {
return FogBlockRegistry.getPackedProperties(state);
}
} }
@@ -28,7 +28,7 @@ public final class FogChunkStreamer {
private static final ChunkStatus[] statuses = new ChunkStatus[TOTAL_CHUNKS]; private static final ChunkStatus[] statuses = new ChunkStatus[TOTAL_CHUNKS];
private static final boolean[] readyToUpload = new boolean[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 byte[][] preparedData = new byte[TOTAL_CHUNKS][CHUNK_DATA_SIZE];
private static final int[] preparedWorldChunkX = new int[TOTAL_CHUNKS]; private static final int[] preparedWorldChunkX = new int[TOTAL_CHUNKS];
private static final int[] preparedWorldChunkZ = new int[TOTAL_CHUNKS]; private static final int[] preparedWorldChunkZ = new int[TOTAL_CHUNKS];
@@ -175,7 +175,7 @@ public final class FogChunkStreamer {
} }
// Вычисляем значение для этого вокселя // Вычисляем значение для этого вокселя
float value = FogChunkProvider.computeVoxelValue(event.newState); byte value = FogChunkProvider.computePackedVoxelValue(event.newState());
// Обновляем один воксель в текстуре // Обновляем один воксель в текстуре
FogWorldVolume.updateSingleVoxel( FogWorldVolume.updateSingleVoxel(
@@ -3,18 +3,12 @@ package su.divan2000.veila.client.render.fog;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
public final class FogWorldVolume { public final class FogWorldVolume {
/*
* Размер всего объёма
*/
public static final int SIZE_X = 256; public static final int SIZE_X = 256;
public static final int SIZE_Z = 256; public static final int SIZE_Z = 256;
@@ -22,156 +16,81 @@ public final class FogWorldVolume {
public static final int MAX_Y = 320; public static final int MAX_Y = 320;
public static final int SIZE_Y = MAX_Y - MIN_Y; public static final int SIZE_Y = MAX_Y - MIN_Y;
/*
* Размер одного чанка
*/
public static final int CHUNK_SIZE = 16; public static final int CHUNK_SIZE = 16;
public static final int CHUNKS_X = SIZE_X / CHUNK_SIZE; public static final int CHUNKS_X = SIZE_X / CHUNK_SIZE;
public static final int CHUNKS_Z = SIZE_Z / CHUNK_SIZE; public static final int CHUNKS_Z = SIZE_Z / CHUNK_SIZE;
/*
* OpenGL
*/
private static int texture = -1; private static int texture = -1;
/* private static final byte[] ZEROS_ARRAY;
* Буфер для одного чанка
*
* 16 × 384 × 16
*/
private static final float[] ZEROS_ARRAY = new float[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
static { static {
java.util.Arrays.fill(ZEROS_ARRAY, 0.0f); ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
byte airValue = new FogBlockProperties(false, 0, 0).pack();
java.util.Arrays.fill(ZEROS_ARRAY, airValue);
} }
private static final FloatBuffer CHUNK_BUFFER = private static final ByteBuffer CHUNK_BUFFER =
BufferUtils.createFloatBuffer( BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE);
CHUNK_SIZE *
SIZE_Y *
CHUNK_SIZE
);
private static final ByteBuffer SINGLE_VOXEL_BUFFER =
private static final FloatBuffer SINGLE_VOXEL_BUFFER = BufferUtils.createByteBuffer(1);
BufferUtils.createFloatBuffer(1);
private FogWorldVolume() { private FogWorldVolume() {
} }
public static void init() { public static void init() {
if (texture != -1) return;
if (texture != -1)
return;
texture = GL11.glGenTextures(); texture = GL11.glGenTextures();
GL11.glBindTexture( GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
GL12.GL_TEXTURE_3D,
texture
);
GL30.glTexImage3D( GL30.glTexImage3D(
GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_3D,
0, 0,
GL30.GL_R16F, GL30.GL_R8UI,
SIZE_X, SIZE_X,
SIZE_Y, SIZE_Y,
SIZE_Z, SIZE_Z,
0, 0,
GL11.GL_RED, GL30.GL_RED_INTEGER,
GL11.GL_FLOAT, GL11.GL_UNSIGNED_BYTE,
(ByteBuffer) null (ByteBuffer) null
); );
GL11.glTexParameteri( GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GL12.GL_TEXTURE_3D, GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.GL_TEXTURE_MIN_FILTER,
GL11.GL_LINEAR
);
GL11.glTexParameteri( GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_S, GL12.GL_REPEAT);
GL12.GL_TEXTURE_3D, GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
GL11.GL_TEXTURE_MAG_FILTER, GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_R, GL12.GL_REPEAT);
GL11.GL_LINEAR
);
GL11.glTexParameteri( GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
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
);
clear(); clear();
} }
public static void clear() { public static void clear() {
CHUNK_BUFFER.clear(); CHUNK_BUFFER.clear();
CHUNK_BUFFER.put(ZEROS_ARRAY);
int size =
CHUNK_SIZE *
SIZE_Y *
CHUNK_SIZE;
for (int i = 0; i < size; i++)
CHUNK_BUFFER.put(0.0f);
CHUNK_BUFFER.flip(); CHUNK_BUFFER.flip();
GL11.glBindTexture( GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
GL12.GL_TEXTURE_3D,
texture
);
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);
uploadChunk(
chunkX,
chunkZ,
CHUNK_BUFFER
);
CHUNK_BUFFER.rewind(); CHUNK_BUFFER.rewind();
} }
} }
GL11.glBindTexture( GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GL12.GL_TEXTURE_3D,
0
);
} }
public static void uploadChunk( public static void uploadChunk(int textureChunkX, int textureChunkZ, ByteBuffer data) {
int textureChunkX,
int textureChunkZ,
FloatBuffer data
) {
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
// Сбрасываем ВСЕ pixel store параметры, которые мог оставить Minecraft
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);
@@ -180,17 +99,14 @@ public final class FogWorldVolume {
GL30.glTexSubImage3D( GL30.glTexSubImage3D(
GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_3D,
0, 0,
textureChunkX * CHUNK_SIZE, textureChunkX * CHUNK_SIZE,
0, 0,
textureChunkZ * CHUNK_SIZE, textureChunkZ * CHUNK_SIZE,
CHUNK_SIZE, CHUNK_SIZE,
SIZE_Y, SIZE_Y,
CHUNK_SIZE, CHUNK_SIZE,
GL30.GL_RED_INTEGER,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE,
GL11.GL_FLOAT,
data data
); );
@@ -203,7 +119,7 @@ public final class FogWorldVolume {
int localX, int localX,
int localY, int localY,
int localZ, int localZ,
float value byte value
) { ) {
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture); GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
@@ -223,8 +139,8 @@ public final class FogWorldVolume {
localY, localY,
textureChunkZ * CHUNK_SIZE + localZ, textureChunkZ * CHUNK_SIZE + localZ,
1, 1, 1, 1, 1, 1,
GL11.GL_RED, GL30.GL_RED_INTEGER,
GL11.GL_FLOAT, GL11.GL_UNSIGNED_BYTE,
SINGLE_VOXEL_BUFFER SINGLE_VOXEL_BUFFER
); );
@@ -232,7 +148,7 @@ public final class FogWorldVolume {
} }
public static void uploadZeros(int textureChunkX, int textureChunkZ) { public static void uploadZeros(int textureChunkX, int textureChunkZ) {
FloatBuffer buffer = chunkBuffer(); ByteBuffer buffer = chunkBuffer();
buffer.put(ZEROS_ARRAY); buffer.put(ZEROS_ARRAY);
buffer.flip(); buffer.flip();
uploadChunk(textureChunkX, textureChunkZ, buffer); uploadChunk(textureChunkX, textureChunkZ, buffer);
@@ -241,23 +157,20 @@ public final class FogWorldVolume {
public static void uploadChunkFromArray( public static void uploadChunkFromArray(
int textureChunkX, int textureChunkX,
int textureChunkZ, int textureChunkZ,
float[] data byte[] data
) { ) {
FloatBuffer buffer = chunkBuffer(); ByteBuffer buffer = chunkBuffer();
buffer.put(data); buffer.put(data);
buffer.flip(); buffer.flip();
uploadChunk(textureChunkX, textureChunkZ, buffer); uploadChunk(textureChunkX, textureChunkZ, buffer);
} }
public static FloatBuffer chunkBuffer() { public static ByteBuffer chunkBuffer() {
CHUNK_BUFFER.clear(); CHUNK_BUFFER.clear();
return CHUNK_BUFFER; return CHUNK_BUFFER;
} }
public static int getTexture() { public static int getTexture() {
return texture; return texture;
} }
} }
@@ -2,7 +2,7 @@
uniform sampler2D DiffuseSampler; uniform sampler2D DiffuseSampler;
uniform sampler2D DepthSampler; uniform sampler2D DepthSampler;
uniform sampler3D FogVolume; uniform usampler3D FogVolume;
uniform mat4 InverseProjection; uniform mat4 InverseProjection;
uniform mat4 InverseView; uniform mat4 InverseView;
@@ -23,7 +23,7 @@ out vec4 FragColor;
const float FOG_SIZE_X = 256.0; const float FOG_SIZE_X = 256.0;
const float FOG_SIZE_Z = FOG_SIZE_X; const float FOG_SIZE_Z = FOG_SIZE_X;
const int MAX_STEPS = 256; // Уменьшили количество шагов благодаря адаптивности const int MAX_STEPS = 256;
// Высота мира // Высота мира
const float WORLD_BOTTOM = -64.0; const float WORLD_BOTTOM = -64.0;
@@ -39,7 +39,7 @@ const float MAX_DISTANCE = (FOG_SIZE_X / 2.0) - 18.0;
const float GAMMA = 2.0; const float GAMMA = 2.0;
// Включение jittering для устранения banding артефактов // Включение jittering для устранения banding артефактов
const bool USE_JITTERING = true; const bool USE_JITTERING = false;
//------------------------------------------------------------ //------------------------------------------------------------
@@ -103,7 +103,9 @@ float density(vec3 worldPos)
// Псевдослучайное значение для jittering (детерминированное по UV) // Псевдослучайное значение для jittering (детерминированное по UV)
float random(vec2 st) float random(vec2 st)
{ {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); vec3 p3 = fract(vec3(st.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
} }
// Функция нелинейного маппинга параметра t [0,1] в расстояние // Функция нелинейного маппинга параметра t [0,1] в расстояние