From f08dee2677d74c112d4f642f5b6daa42fe41018c Mon Sep 17 00:00:00 2001 From: DIvan2000 Date: Sun, 9 Aug 2026 18:47:25 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A3=D1=87=D1=91=D1=82=20=D0=B2=D1=80=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B8,=20=D0=BF=D0=BE=D0=B3=D0=BE=D0=B4?= =?UTF-8?q?=D1=8B=20=D0=B8=20=D0=B1=D0=B8=D0=BE=D0=BC=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/render/fog/FogBiomeParams.java | 368 ++++++++++++++++++ .../client/render/fog/FogBiomeStreamer.java | 245 ++++++++++++ .../client/render/fog/FogBiomeTexture.java | 175 +++++++++ .../render/fog/FogEnvironmentContext.java | 116 ++++++ .../render/fog/FogSimulationManager.java | 2 +- .../render/fog/FogSimulationShader.java | 27 ++ .../veila/client/render/fog/FogSystem.java | 21 +- .../client/render/fog/SimpleNoiseSampler.java | 65 ++++ .../veila/mixin/client/GameRendererMixin.java | 2 +- .../assets/veila/shaders/fog_raymarching.frag | 4 +- .../resources/assets/veila/shaders/sim.frag | 166 ++++---- src/main/resources/veila.accesswidener | 5 + 12 files changed, 1094 insertions(+), 102 deletions(-) create mode 100644 src/client/java/su/divan2000/veila/client/render/fog/FogBiomeParams.java create mode 100644 src/client/java/su/divan2000/veila/client/render/fog/FogBiomeStreamer.java create mode 100644 src/client/java/su/divan2000/veila/client/render/fog/FogBiomeTexture.java create mode 100644 src/client/java/su/divan2000/veila/client/render/fog/FogEnvironmentContext.java create mode 100644 src/client/java/su/divan2000/veila/client/render/fog/SimpleNoiseSampler.java diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogBiomeParams.java b/src/client/java/su/divan2000/veila/client/render/fog/FogBiomeParams.java new file mode 100644 index 0000000..64d21ab --- /dev/null +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogBiomeParams.java @@ -0,0 +1,368 @@ +package su.divan2000.veila.client.render.fog; + +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.world.biome.Biome; + +public final class FogBiomeParams { + + public float emissionMultiplier; + public float targetDensityBase; + public float humidity; + public float timeSensitivity; + + public FogBiomeParams(float emission, float targetDensityBase, + float humidity, float timeSensitivity) { + this.emissionMultiplier = emission; + this.targetDensityBase = targetDensityBase; + this.humidity = humidity; + this.timeSensitivity = timeSensitivity; + } + + public static FogBiomeParams forBiome(RegistryEntry biomeEntry) { + Biome biome = biomeEntry.value(); + + // Полный идентификатор: "minecraft:plains", "biomesoplenty:cherry_blossom_grove" + String biomeId = biomeEntry.getKey() + .map(key -> key.getValue().toString()) + .orElse("minecraft:unknown"); + + float emission = 1.0f; + float targetDensity = 0.03f; + float humidity = 0.5f; + float timeSensitivity = 1.0f; + + switch (biomeId) { + // === Ванильные пустыни (никогда нет дымки) === + case "minecraft:desert": + emission = 0.1f; + targetDensity = 0.0f; + humidity = 0.0f; + timeSensitivity = 1.0f; + break; + + // === Ванильные бесплодные земли === + case "minecraft:badlands": + case "minecraft:wooded_badlands": + case "minecraft:eroded_badlands": + emission = 0.15f; + targetDensity = 0.0f; + humidity = 0.05f; + timeSensitivity = 1.0f; + break; + + // === Ванильные джунгли === + case "minecraft:jungle": + case "minecraft:bamboo_jungle": + emission = 1.0f; + targetDensity = 0.045f; + humidity = 0.9f; + timeSensitivity = 0.3f; + break; + + case "minecraft:sparse_jungle": + emission = 0.8f; + targetDensity = 0.035f; + humidity = 0.75f; + timeSensitivity = 0.5f; + break; + + // === Ванильные болота === + case "minecraft:swamp": + emission = 1.5f; + targetDensity = 0.09f; + humidity = 0.9f; + timeSensitivity = 0.4f; + break; + + case "minecraft:mangrove_swamp": + emission = 1.4f; + targetDensity = 0.075f; + humidity = 0.85f; + timeSensitivity = 0.5f; + break; + + // === Ванильные равнины === + case "minecraft:plains": + case "minecraft:sunflower_plains": + emission = 1.0f; + targetDensity = 0.03f; + humidity = 0.5f; + timeSensitivity = 1.0f; + break; + + case "minecraft:snowy_plains": + emission = 0.85f; + targetDensity = 0.035f; + humidity = 0.55f; + timeSensitivity = 0.9f; + break; + + // === Ванильные луга и вишнёвые рощи === + case "minecraft:meadow": + case "minecraft:cherry_grove": + emission = 1.1f; + targetDensity = 0.035f; + humidity = 0.6f; + timeSensitivity = 0.9f; + break; + + // === Ванильные леса === + case "minecraft:forest": + case "minecraft:flower_forest": + case "minecraft:birch_forest": + case "minecraft:old_growth_birch_forest": + emission = 0.9f; + targetDensity = 0.036f; + humidity = 0.6f; + timeSensitivity = 0.9f; + break; + + case "minecraft:dark_forest": + emission = 1.0f; + targetDensity = 0.04f; + humidity = 0.65f; + timeSensitivity = 0.85f; + break; + + case "minecraft:mushroom_fields": + emission = 0.7f; + targetDensity = 0.025f; + humidity = 0.5f; + timeSensitivity = 0.8f; + break; + + // === Ванильная тайга === + case "minecraft:taiga": + case "minecraft:snowy_taiga": + emission = 0.85f; + targetDensity = 0.038f; + humidity = 0.6f; + timeSensitivity = 0.9f; + break; + + case "minecraft:old_growth_pine_taiga": + case "minecraft:old_growth_spruce_taiga": + emission = 0.95f; + targetDensity = 0.042f; + humidity = 0.7f; + timeSensitivity = 0.8f; + break; + + // === Ванильные саванны === + case "minecraft:savanna": + case "minecraft:savanna_plateau": + case "minecraft:windswept_savanna": + emission = 0.2f; + targetDensity = 0.005f; + humidity = 0.1f; + timeSensitivity = 1.0f; + break; + + // === Ванильные горы === + case "minecraft:windswept_hills": + case "minecraft:windswept_gravelly_hills": + case "minecraft:windswept_forest": + emission = 0.6f; + targetDensity = 0.02f; + humidity = 0.35f; + timeSensitivity = 1.0f; + break; + + case "minecraft:grove": + emission = 0.7f; + targetDensity = 0.025f; + humidity = 0.4f; + timeSensitivity = 0.9f; + break; + + case "minecraft:snowy_slopes": + emission = 0.65f; + targetDensity = 0.022f; + humidity = 0.35f; + timeSensitivity = 0.95f; + break; + + case "minecraft:jagged_peaks": + case "minecraft:frozen_peaks": + case "minecraft:stony_peaks": + emission = 0.5f; + targetDensity = 0.015f; + humidity = 0.25f; + timeSensitivity = 1.0f; + break; + + // === Ванильные побережья === + case "minecraft:beach": + case "minecraft:snowy_beach": + emission = 0.75f; + targetDensity = 0.028f; + humidity = 0.6f; + timeSensitivity = 0.85f; + break; + + case "minecraft:stony_shore": + emission = 0.6f; + targetDensity = 0.02f; + humidity = 0.45f; + timeSensitivity = 0.9f; + break; + + // === Ванильные реки === + case "minecraft:river": + emission = 0.8f; + targetDensity = 0.032f; + humidity = 0.65f; + timeSensitivity = 0.8f; + break; + + case "minecraft:frozen_river": + emission = 0.7f; + targetDensity = 0.035f; + humidity = 0.6f; + timeSensitivity = 0.85f; + break; + + // === Ванильные океаны === + case "minecraft:warm_ocean": + emission = 0.9f; + targetDensity = 0.035f; + humidity = 0.7f; + timeSensitivity = 0.7f; + break; + + case "minecraft:lukewarm_ocean": + case "minecraft:deep_lukewarm_ocean": + emission = 0.85f; + targetDensity = 0.032f; + humidity = 0.65f; + timeSensitivity = 0.75f; + break; + + case "minecraft:ocean": + case "minecraft:deep_ocean": + emission = 0.8f; + targetDensity = 0.03f; + humidity = 0.65f; + timeSensitivity = 0.8f; + break; + + case "minecraft:cold_ocean": + case "minecraft:deep_cold_ocean": + emission = 0.75f; + targetDensity = 0.035f; + humidity = 0.6f; + timeSensitivity = 0.85f; + break; + + case "minecraft:frozen_ocean": + case "minecraft:deep_frozen_ocean": + emission = 0.7f; + targetDensity = 0.038f; + humidity = 0.55f; + timeSensitivity = 0.9f; + break; + + case "minecraft:ice_spikes": + emission = 0.7f; + targetDensity = 0.04f; + humidity = 0.6f; + timeSensitivity = 0.85f; + break; + + // === Ванильный Незер === + case "minecraft:nether_wastes": + case "minecraft:soul_sand_valley": + case "minecraft:basalt_deltas": + case "minecraft:crimson_forest": + case "minecraft:warped_forest": + emission = 0.05f; + targetDensity = 0.0f; + humidity = 0.0f; + timeSensitivity = 1.0f; + break; + + // === Ванильный Край === + case "minecraft:the_end": + case "minecraft:small_end_islands": + case "minecraft:end_midlands": + case "minecraft:end_highlands": + case "minecraft:end_barrens": + emission = 0.05f; + targetDensity = 0.0f; + humidity = 0.0f; + timeSensitivity = 1.0f; + break; + + case "minecraft:the_void": + emission = 0.0f; + targetDensity = 0.0f; + humidity = 0.0f; + timeSensitivity = 1.0f; + break; + + // === Примеры модовых биомов (можно добавлять свои) === + // Biomes O' Plenty + case "biomesoplenty:cherry_blossom_grove": + emission = 1.1f; + targetDensity = 0.035f; + humidity = 0.65f; + timeSensitivity = 0.85f; + break; + + case "biomesoplenty:lavender_field": + emission = 1.0f; + targetDensity = 0.03f; + humidity = 0.55f; + timeSensitivity = 0.9f; + break; + + case "biomesoplenty:wetland": + emission = 1.3f; + targetDensity = 0.06f; + humidity = 0.8f; + timeSensitivity = 0.6f; + break; + + // Terrestria + case "terrestria:redwood_forest": + emission = 1.0f; + targetDensity = 0.04f; + humidity = 0.7f; + timeSensitivity = 0.8f; + break; + + // Traverse + case "traverse:autumnal_wooded_hills": + emission = 0.9f; + targetDensity = 0.035f; + humidity = 0.6f; + timeSensitivity = 0.9f; + break; + + // === Fallback для неизвестных биомов === + default: + float temperature = biome.getTemperature(); + float downfall = biome.weather.downfall(); + + humidity = downfall; + emission = 0.7f + downfall * 0.6f; + targetDensity = downfall * 0.06f; + timeSensitivity = 1.0f - downfall * 0.5f; + + if (temperature < 0.3f) { + emission *= 1.2f; + targetDensity *= 1.3f; + } + break; + } + + return new FogBiomeParams(emission, targetDensity, humidity, timeSensitivity); + } + + public void pack(float[] array, int offset) { + array[offset ] = emissionMultiplier; + array[offset + 1] = targetDensityBase; + array[offset + 2] = humidity; + array[offset + 3] = timeSensitivity; + } +} \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogBiomeStreamer.java b/src/client/java/su/divan2000/veila/client/render/fog/FogBiomeStreamer.java new file mode 100644 index 0000000..e4e3c81 --- /dev/null +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogBiomeStreamer.java @@ -0,0 +1,245 @@ +package su.divan2000.veila.client.render.fog; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.Heightmap; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.chunk.WorldChunk; + +import java.util.concurrent.atomic.AtomicLongArray; + +public final class FogBiomeStreamer { + + private static final int TOTAL_CHUNKS = + FogWorldVolume.CHUNKS_X * FogWorldVolume.CHUNKS_Z; + private static final int TOTAL_WORDS = (TOTAL_CHUNKS + 63) / 64; + + private static final int CHUNK_DATA_FLOATS = + FogWorldVolume.CHUNK_SIZE * FogWorldVolume.CHUNK_SIZE * 4; + + private static final int MAX_CHUNKS_PER_TICK = 8; + private static volatile int burstFrames = 0; + + private static final AtomicLongArray pendingMask = new AtomicLongArray(TOTAL_WORDS); + private static final AtomicLongArray readyMask = new AtomicLongArray(TOTAL_WORDS); + private static final Object sharedStateLock = new Object(); + + private static final float[][] preparedData = new float[TOTAL_CHUNKS][CHUNK_DATA_FLOATS]; + 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 FogBiomeStreamer() { + } + + private static int floorMod(int x, int y) { + int r = x % y; + return r < 0 ? r + y : r; + } + + private static void setBit(AtomicLongArray mask, int index) { + int wordIndex = index >> 6; + long bitMask = 1L << (index & 63); + while (true) { + long current = mask.get(wordIndex); + if ((current & bitMask) != 0) return; + if (mask.compareAndSet(wordIndex, current, current | bitMask)) return; + } + } + + private static void clearBit(AtomicLongArray mask, int index) { + int wordIndex = index >> 6; + long bitMask = 1L << (index & 63); + while (true) { + long current = mask.get(wordIndex); + if ((current & bitMask) == 0) return; + if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return; + } + } + + private static int nextSetBit(AtomicLongArray mask, int startIndex) { + for (int i = startIndex; i < TOTAL_CHUNKS; i++) { + int wordIndex = i >> 6; + long bitMask = 1L << (i & 63); + if ((mask.get(wordIndex) & bitMask) != 0) return i; + } + return -1; + } + + public static void update( + int oldOriginChunkX, int oldOriginChunkZ, + int newOriginChunkX, int newOriginChunkZ, + int ringChunkOffsetX, int ringChunkOffsetZ + ) { + synchronized (sharedStateLock) { + currentOriginChunkX = newOriginChunkX; + currentOriginChunkZ = newOriginChunkZ; + currentRingChunkOffsetX = ringChunkOffsetX; + currentRingChunkOffsetZ = ringChunkOffsetZ; + + if (firstUpdate) { + markAllPending(); + firstUpdate = false; + return; + } + + int dx = newOriginChunkX - oldOriginChunkX; + int dz = newOriginChunkZ - oldOriginChunkZ; + if (dx == 0 && dz == 0) return; + + if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X || + Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) { + markAllPending(); + return; + } + + if (dx > 0) markPendingRange(newOriginChunkX + FogWorldVolume.CHUNKS_X - dx, + newOriginChunkZ, dx, FogWorldVolume.CHUNKS_Z); + if (dx < 0) markPendingRange(newOriginChunkX, + newOriginChunkZ, -dx, FogWorldVolume.CHUNKS_Z); + if (dz > 0) markPendingRange(newOriginChunkX, + newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz, + FogWorldVolume.CHUNKS_X, dz); + if (dz < 0) markPendingRange(newOriginChunkX, + newOriginChunkZ, FogWorldVolume.CHUNKS_X, -dz); + } + } + + private static void markPendingRange(int worldX, int worldZ, int countX, int countZ) { + for (int i = 0; i < countX; i++) { + int wx = worldX + i; + for (int j = 0; j < countZ; j++) { + int wz = worldZ + j; + int physicalX = floorMod(wx, FogWorldVolume.CHUNKS_X); + int physicalZ = floorMod(wz, FogWorldVolume.CHUNKS_Z); + int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX; + clearBit(readyMask, physicalIndex); + setBit(pendingMask, physicalIndex); + } + } + } + + private static void markAllPending() { + for (int i = 0; i < TOTAL_WORDS; i++) { + pendingMask.set(i, -1L); + readyMask.set(i, 0L); + } + int extraBits = TOTAL_CHUNKS & 63; + if (extraBits != 0) { + pendingMask.set(TOTAL_WORDS - 1, (1L << extraBits) - 1); + } + } + + public static void processPending() { + synchronized (sharedStateLock) { + int originX = currentOriginChunkX; + int originZ = currentOriginChunkZ; + int ringOffsetX = currentRingChunkOffsetX; + int ringOffsetZ = currentRingChunkOffsetZ; + + int processedCount = 0; + int index = 0; + int limit = MAX_CHUNKS_PER_TICK + (burstFrames > 0 ? MAX_CHUNKS_PER_TICK : 0); + + while (processedCount < limit) { + index = nextSetBit(pendingMask, index); + if (index < 0) break; + + int physicalX = index % FogWorldVolume.CHUNKS_X; + int physicalZ = index / FogWorldVolume.CHUNKS_X; + + int worldChunkX = originX + floorMod(physicalX - ringOffsetX, FogWorldVolume.CHUNKS_X); + int worldChunkZ = originZ + floorMod(physicalZ - ringOffsetZ, FogWorldVolume.CHUNKS_Z); + + if (fillBiomesIntoArray(worldChunkX, worldChunkZ, preparedData[index])) { + preparedWorldChunkX[index] = worldChunkX; + preparedWorldChunkZ[index] = worldChunkZ; + setBit(readyMask, index); + clearBit(pendingMask, index); + processedCount++; + } + index++; + } + if (burstFrames > 0) burstFrames--; + } + } + + private static boolean fillBiomesIntoArray(int chunkX, int chunkZ, float[] array) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.world == null) return false; + + WorldChunk chunk = client.world.getChunkManager() + .getWorldChunk(chunkX, chunkZ, false); + if (chunk == null) return false; + + int topY = client.world.getTopY()-1; + + int baseWorldX = chunkX << 4; + int baseWorldZ = chunkZ << 4; + + int idx = 0; + for (int z = 0; z < FogWorldVolume.CHUNK_SIZE; z++) { + int worldZ = baseWorldZ + z; + for (int x = 0; x < FogWorldVolume.CHUNK_SIZE; x++) { + int worldX = baseWorldX + x; + + int surfaceY = chunk.getHeightmap(Heightmap.Type.WORLD_SURFACE).get(x, z); + BlockPos samplePos = new BlockPos(worldX, topY, worldZ); + RegistryEntry biomeEntry = client.world.getBiome(samplePos); + + FogBiomeParams params = FogBiomeParams.forBiome(biomeEntry); + params.pack(array, idx); + idx += 4; + } + } + return true; + } + + public static void uploadReadyData() { + synchronized (sharedStateLock) { + int originX = currentOriginChunkX; + int originZ = currentOriginChunkZ; + int ringOffsetX = currentRingChunkOffsetX; + int ringOffsetZ = currentRingChunkOffsetZ; + + int processed = 0; + int index = 0; + int limit = MAX_CHUNKS_PER_TICK + (burstFrames > 0 ? MAX_CHUNKS_PER_TICK : 0); + + while (processed < limit) { + index = nextSetBit(readyMask, index); + if (index < 0) break; + + int physicalX = index % FogWorldVolume.CHUNKS_X; + int physicalZ = index / FogWorldVolume.CHUNKS_X; + + int currentWorldX = originX + floorMod(physicalX - ringOffsetX, FogWorldVolume.CHUNKS_X); + int currentWorldZ = originZ + floorMod(physicalZ - ringOffsetZ, FogWorldVolume.CHUNKS_Z); + + if (preparedWorldChunkX[index] == currentWorldX && + preparedWorldChunkZ[index] == currentWorldZ) { + + FogBiomeTexture.uploadChunk(physicalX, physicalZ, preparedData[index]); + clearBit(readyMask, index); + processed++; + } else { + clearBit(readyMask, index); + setBit(pendingMask, index); + } + index++; + } + if (burstFrames > 0) burstFrames--; + } + } + + public static void setBurst(int frames) { + burstFrames = Math.max(burstFrames, frames); + } +} \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogBiomeTexture.java b/src/client/java/su/divan2000/veila/client/render/fog/FogBiomeTexture.java new file mode 100644 index 0000000..e9de662 --- /dev/null +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogBiomeTexture.java @@ -0,0 +1,175 @@ +package su.divan2000.veila.client.render.fog; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.*; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public final class FogBiomeTexture { + + public static final int SIZE_X = FogWorldVolume.SIZE_X; + public static final int SIZE_Z = FogWorldVolume.SIZE_Z; + + private static int texture = -1; + + private static final int[] uploadPBOs = new int[3]; + private static int uploadPBOIndex = 0; + + private static final int CHUNK_DATA_FLOATS = + FogWorldVolume.CHUNK_SIZE * FogWorldVolume.CHUNK_SIZE * 4; + private static final int CHUNK_DATA_BYTES = CHUNK_DATA_FLOATS * 4; + + // Буфер размером с ОДИН чанк + private static final FloatBuffer CHUNK_BUFFER = + BufferUtils.createFloatBuffer(CHUNK_DATA_FLOATS); + + // Данные одного чанка + private static final float[] EMPTY_CHUNK = new float[CHUNK_DATA_FLOATS]; + + // Данные одного пикселя (для clearRegions) + private static final float[] EMPTY_PIXEL = {0.0f, 0.0f, 0.0f, 0.0f}; + + private FogBiomeTexture() { + } + + public static void init() { + if (texture != -1) return; + + initUploadPBO(); + + texture = GL11.glGenTextures(); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture); + + GL30.glTexImage2D( + GL11.GL_TEXTURE_2D, + 0, + GL30.GL_RGBA16F, + SIZE_X, + SIZE_Z, + 0, + GL11.GL_RGBA, + GL11.GL_FLOAT, + (ByteBuffer) null + ); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_REPEAT); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_REPEAT); + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + + clear(); + } + + public static void clear() { + // Кладем данные ОДНОГО чанка в буфер один раз + CHUNK_BUFFER.clear(); + CHUNK_BUFFER.put(EMPTY_CHUNK); + CHUNK_BUFFER.flip(); + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture); + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + + // Для каждого чанка загружаем те же данные из буфера, делая rewind + for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) { + for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) { + GL11.glTexSubImage2D( + GL11.GL_TEXTURE_2D, 0, + x * FogWorldVolume.CHUNK_SIZE, + z * FogWorldVolume.CHUNK_SIZE, + FogWorldVolume.CHUNK_SIZE, + FogWorldVolume.CHUNK_SIZE, + GL11.GL_RGBA, GL11.GL_FLOAT, + CHUNK_BUFFER + ); + CHUNK_BUFFER.rewind(); // ← важно: возвращаем позицию к началу + } + } + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + } + + public static void uploadChunk(int textureChunkX, int textureChunkZ, float[] data) { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture); + + int pbo = uploadPBOs[uploadPBOIndex % uploadPBOs.length]; + uploadPBOIndex++; + + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, pbo); + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, CHUNK_DATA_BYTES, GL15.GL_STREAM_DRAW); + + ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY); + if (mapped != null) { + FloatBuffer floatMapped = mapped.asFloatBuffer(); + floatMapped.put(data); + GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER); + + 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); + + GL11.glTexSubImage2D( + GL11.GL_TEXTURE_2D, + 0, + textureChunkX * FogWorldVolume.CHUNK_SIZE, + textureChunkZ * FogWorldVolume.CHUNK_SIZE, + FogWorldVolume.CHUNK_SIZE, + FogWorldVolume.CHUNK_SIZE, + GL11.GL_RGBA, + GL11.GL_FLOAT, + 0L + ); + } + + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + } + + public static void clearRegions( + int[] physX, int[] physZ, + int[] widthChunks, int[] depthChunks, + int count + ) { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture); + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + + for (int i = 0; i < count; i++) { + int w = widthChunks[i] * FogWorldVolume.CHUNK_SIZE; + int d = depthChunks[i] * FogWorldVolume.CHUNK_SIZE; + int totalPixels = w * d; + + FloatBuffer clearBuf = BufferUtils.createFloatBuffer(totalPixels * 4); + // Кладем пустые пиксели (RGBA = 0,0,0,0) по одному + for (int j = 0; j < totalPixels; j++) { + clearBuf.put(EMPTY_PIXEL); + } + clearBuf.flip(); + + GL11.glTexSubImage2D( + GL11.GL_TEXTURE_2D, 0, + physX[i] * FogWorldVolume.CHUNK_SIZE, + physZ[i] * FogWorldVolume.CHUNK_SIZE, + w, d, + GL11.GL_RGBA, GL11.GL_FLOAT, + clearBuf + ); + } + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + } + + private static void initUploadPBO() { + for (int i = 0; i < uploadPBOs.length; i++) { + uploadPBOs[i] = GL15.glGenBuffers(); + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBOs[i]); + GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, CHUNK_DATA_BYTES, GL15.GL_STREAM_DRAW); + } + GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0); + } + + public static int getTexture() { + return texture; + } +} \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogEnvironmentContext.java b/src/client/java/su/divan2000/veila/client/render/fog/FogEnvironmentContext.java new file mode 100644 index 0000000..02d9418 --- /dev/null +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogEnvironmentContext.java @@ -0,0 +1,116 @@ +package su.divan2000.veila.client.render.fog; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +public final class FogEnvironmentContext { + + private static float emissionMultiplier = 1.0f; + private static float targetDensityMultiplier = 0.0f; + private static float fogDayRandom = 0.5f; // НОВЫЙ uniform + private static float rainMultiplier = 0.0f; + + private static float smoothEmission = 1.0f; + private static float smoothTargetDensity = 0.0f; + private static float smoothRain = 0.0f; + + private static SimpleNoiseSampler dayNoise; + private static Identifier lastWorldId = null; + + private FogEnvironmentContext() {} + + public static void init() { + dayNoise = new SimpleNoiseSampler(12345L); + } + + public static void update(float tickDelta) { + MinecraftClient client = MinecraftClient.getInstance(); + World world = client.world; + if (world == null) return; + + Identifier worldId = world.getRegistryKey().getValue(); + if (!worldId.equals(lastWorldId)) { + lastWorldId = worldId; + long seed = worldId.toString().hashCode(); + dayNoise = new SimpleNoiseSampler(seed); + } + + long timeOfDay = world.getTimeOfDay() % 24000L; + long totalDays = world.getTimeOfDay() / 24000L; + + // === 1. Трапеция для EMISSION === + double noiseValue = dayNoise.sample(totalDays * 0.05, 0); + float dayOffset = (float)(noiseValue * 1000); + + float p1 = 16000 + dayOffset; + float p2 = 20000; + float p3 = 23000; + float p4 = 3000 + dayOffset; + + float baseEmission = trapezoid(timeOfDay, p1, p2, p3, p4); + + float rain = world.getRainGradient(tickDelta); + float thunder = world.getThunderGradient(tickDelta); + float weatherBoost = 1.0f + rain * 2.0f + thunder * 1.0f; + + float rawEmission = (baseEmission + 0.1f) * weatherBoost; + + // === 2. Трапеция для TARGET DENSITY === + // РАСШИРЕННОЕ окно: 18000-6000 (полночь - полдень) + // Это 12 часов игрового времени = 10 минут реального + float t1 = 18000; // полночь + float t2 = 20000; // 02:00 + float t3 = 4000; // 10:00 + float t4 = 6000; // полдень + + float baseTargetDensity = trapezoid(timeOfDay, t1, t2, t3, t4); + + // В дождь дымка всегда + float rainFactor = rain + thunder * 0.5f; + float rawTargetDensity = Math.max(baseTargetDensity, rainFactor); + + // === 3. Случайное значение для "туманного дня" === + // Это ОТДЕЛЬНЫЙ uniform, НЕ умноженный на трапецию + double dayNoiseValue = dayNoise.sample(totalDays * 0.1, 100); + float rawFogDayRandom = (float)(0.5 + dayNoiseValue * 0.5); // 0..1 + + // === 4. Сглаживание === + float lerpSpeed = 0.08f; + smoothEmission = lerp(smoothEmission, rawEmission, lerpSpeed); + smoothTargetDensity = lerp(smoothTargetDensity, rawTargetDensity, lerpSpeed * 2.0f); + smoothRain = lerp(smoothRain, rainFactor, lerpSpeed * 2.0f); + // FogDayRandom НЕ сглаживаем — он постоянен в течение дня + fogDayRandom = rawFogDayRandom; + + emissionMultiplier = smoothEmission; + targetDensityMultiplier = smoothTargetDensity; + rainMultiplier = smoothRain; + } + + private static float trapezoid(float x, float p1, float p2, float p3, float p4) { + x = ((x % 24000) + 24000) % 24000; + + if (p4 < p1) { + if (x < p4) x += 24000; + if (p2 < p1) p2 += 24000; + if (p3 < p1) p3 += 24000; + p4 += 24000; + } + + if (x < p1) return 0.0f; + if (x < p2) return (x - p1) / (p2 - p1); + if (x < p3) return 1.0f; + if (x < p4) return 1.0f - (x - p3) / (p4 - p3); + return 0.0f; + } + + private static float lerp(float a, float b, float t) { + return a + (b - a) * t; + } + + public static float getEmissionMultiplier() { return emissionMultiplier; } + public static float getTargetDensityMultiplier() { return targetDensityMultiplier; } + public static float getFogDayRandom() { return fogDayRandom; } + public static float getRainMultiplier() { return rainMultiplier; } +} \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java index c89ab1e..9887580 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java @@ -200,7 +200,7 @@ public final class FogSimulationManager { } if (lastStatsPrintTime == 0 || timestamp - lastStatsPrintTime >= STAT_WINDOW_NS) { - printStepStats(); + //printStepStats(); lastStatsPrintTime = timestamp; } } diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java index 0d05e21..44018e8 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java @@ -32,6 +32,11 @@ public final class FogSimulationShader { private static int horizontalDiffusionLocation; private static int voxelSizeLocation; private static int currentZLocation; + private static int emissionMultiplierLocation; + private static int targetDensityMultiplierLocation; + private static int biomeParamsSamplerLocation; + private static int fogDayRandomLocation; + private static int rainMultiplierLocation; private FogSimulationShader() { } @@ -64,6 +69,11 @@ public final class FogSimulationShader { horizontalDiffusionLocation = program.uniform("HorizontalDiffusion"); voxelSizeLocation = program.uniform("VoxelSize"); currentZLocation = program.uniform("CurrentZ"); + biomeParamsSamplerLocation = program.uniform("BiomeParamsSampler"); + emissionMultiplierLocation = program.uniform("EmissionMultiplier"); + targetDensityMultiplierLocation = program.uniform("TargetDensityMultiplier"); + fogDayRandomLocation = program.uniform("FogDayRandom"); + rainMultiplierLocation = program.uniform("RainMultiplier"); fbo = GL30.glGenFramebuffers(); @@ -138,6 +148,10 @@ public final class FogSimulationShader { int previousTex1_2D = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); int previousTex1_3D = GL11.glGetInteger(GL_TEXTURE_BINDING_3D); + GL13.glActiveTexture(GL13.GL_TEXTURE2); + int previousTex2_2D = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); + int previousTex2_3D = GL11.glGetInteger(GL_TEXTURE_BINDING_3D); + // Blend state (на всякий случай) boolean previousBlend = GL11.glIsEnabled(GL11.GL_BLEND); boolean previousDepthTest = GL11.glIsEnabled(GL11.GL_DEPTH_TEST); @@ -179,6 +193,10 @@ public final class FogSimulationShader { GL11.glBindTexture(GL12.GL_TEXTURE_3D, fogRead); GL20.glUniform1i(fogReadSamplerLocation, 1); + GL13.glActiveTexture(GL13.GL_TEXTURE2); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, FogBiomeTexture.getTexture()); + GL20.glUniform1i(biomeParamsSamplerLocation, 2); + // Устанавливаем параметры GL20.glUniform1f(globalEmissionMultiplierLocation, globalEmissionMultiplier); GL20.glUniform1f(globalAbsorptionMultiplierLocation, globalAbsorptionMultiplier); @@ -187,6 +205,11 @@ public final class FogSimulationShader { GL20.glUniform1f(upDiffusionLocation, upDiffusion); GL20.glUniform1f(downDiffusionLocation, downDiffusion); GL20.glUniform1f(horizontalDiffusionLocation, horizontalDiffusion); + GL20.glUniform1f(emissionMultiplierLocation, FogEnvironmentContext.getEmissionMultiplier()); + GL20.glUniform1f(targetDensityMultiplierLocation, FogEnvironmentContext.getTargetDensityMultiplier()); + GL20.glUniform1f(fogDayRandomLocation, FogEnvironmentContext.getFogDayRandom()); + GL20.glUniform1f(rainMultiplierLocation, FogEnvironmentContext.getRainMultiplier()); + GL20.glUniform3f(voxelSizeLocation, 1.0f / FogWorldVolume.SIZE_X, 1.0f / FogWorldVolume.SIZE_Y, @@ -241,6 +264,10 @@ public final class FogSimulationShader { GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTex1_2D); GL11.glBindTexture(GL12.GL_TEXTURE_3D, previousTex1_3D); + GL13.glActiveTexture(GL13.GL_TEXTURE2); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTex2_2D); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, previousTex2_3D); + // Восстанавливаем активный текстурный юнит GL13.glActiveTexture(previousActiveTexture); diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java b/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java index 61fc96b..00b2b14 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java @@ -15,7 +15,7 @@ public final class FogSystem { private FogSystem() { } - public static void beginRender() { + public static void beginRender(float tickDelta) { if (!initialized) { initialize(); @@ -32,6 +32,11 @@ public final class FogSystem { // Освещение: загружаем готовые секции + инкрементальные обновления (render thread) FogLightStreamer.uploadReadySections(); + // Биомы + FogBiomeStreamer.uploadReadyData(); + + FogEnvironmentContext.update(tickDelta); + FogSimulationManager.update(); } @@ -41,6 +46,9 @@ public final class FogSystem { // Освещение: читаем данные секций (client thread, без OpenGL) FogLightStreamer.prepareAllSections(); + + // Читаем биомы в client thread + FogBiomeStreamer.processPending(); } private static void initialize() { @@ -48,6 +56,8 @@ public final class FogSystem { FogLightVolume.init(); FogLightStreamer.init(); FogSimulationManager.init(); + FogBiomeTexture.init(); + FogEnvironmentContext.init(); initialized = true; } @@ -103,9 +113,18 @@ public final class FogSystem { ringChunkOffsetX, ringChunkOffsetZ ); + + // Вызов FogBiomeStreamer.update после FogLightStreamer.update + FogBiomeStreamer.update( + oldOriginChunkX, oldOriginChunkZ, + originChunkX, originChunkZ, + ringChunkOffsetX, ringChunkOffsetZ + ); + // Boost uploads for a few frames to catch up after a window shift FogChunkStreamer.setBurst(8); FogLightStreamer.setBurst(8); + FogBiomeStreamer.setBurst(8); } public static int originChunkX() { diff --git a/src/client/java/su/divan2000/veila/client/render/fog/SimpleNoiseSampler.java b/src/client/java/su/divan2000/veila/client/render/fog/SimpleNoiseSampler.java new file mode 100644 index 0000000..c050674 --- /dev/null +++ b/src/client/java/su/divan2000/veila/client/render/fog/SimpleNoiseSampler.java @@ -0,0 +1,65 @@ +package su.divan2000.veila.client.render.fog; + +/** + * Простой Perlin noise без зависимостей от Minecraft API. + */ +public final class SimpleNoiseSampler { + + private final int[] perm = new int[512]; + + public SimpleNoiseSampler(long seed) { + java.util.Random random = new java.util.Random(seed); + int[] p = new int[256]; + for (int i = 0; i < 256; i++) { + p[i] = i; + } + // Fisher-Yates shuffle + for (int i = 255; i > 0; i--) { + int j = random.nextInt(i + 1); + int temp = p[i]; + p[i] = p[j]; + p[j] = temp; + } + for (int i = 0; i < 512; i++) { + perm[i] = p[i & 255]; + } + } + + public double sample(double x, double y) { + int X = (int)Math.floor(x) & 255; + int Y = (int)Math.floor(y) & 255; + + x -= Math.floor(x); + y -= Math.floor(y); + + double u = fade(x); + double v = fade(y); + + int A = perm[X] + Y; + int AA = perm[A]; + int AB = perm[A + 1]; + int B = perm[X + 1] + Y; + int BA = perm[B]; + int BB = perm[B + 1]; + + return lerp(v, + lerp(u, grad(perm[AA], x, y), grad(perm[BA], x - 1, y)), + lerp(u, grad(perm[AB], x, y - 1), grad(perm[BB], x - 1, y - 1)) + ); + } + + private static double fade(double t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + private static double lerp(double t, double a, double b) { + return a + t * (b - a); + } + + private static double grad(int hash, double x, double y) { + int h = hash & 7; + double u = h < 4 ? x : y; + double v = h < 4 ? y : x; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? 2.0 * v : -2.0 * v); + } +} \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/mixin/client/GameRendererMixin.java b/src/client/java/su/divan2000/veila/mixin/client/GameRendererMixin.java index 8e2acd2..e58c201 100644 --- a/src/client/java/su/divan2000/veila/mixin/client/GameRendererMixin.java +++ b/src/client/java/su/divan2000/veila/mixin/client/GameRendererMixin.java @@ -63,7 +63,7 @@ public class GameRendererMixin { ) { net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance(); if (client.world != null && client.player != null) { - FogSystem.beginRender(); + FogSystem.beginRender(tickDelta); } } } \ No newline at end of file diff --git a/src/client/resources/assets/veila/shaders/fog_raymarching.frag b/src/client/resources/assets/veila/shaders/fog_raymarching.frag index 0091bb5..fc7caba 100644 --- a/src/client/resources/assets/veila/shaders/fog_raymarching.frag +++ b/src/client/resources/assets/veila/shaders/fog_raymarching.frag @@ -43,8 +43,6 @@ const float SCATTERING_COEFF = 1.0; const float AMBIENT_INTENSITY = 0.10; const float MIN_TRANSMITTANCE = 0.01; -const float AMBIENT_FOG = 0.0025; - //------------------------------------------------------------ vec3 reconstructViewPosition(vec2 uv) @@ -99,7 +97,7 @@ float density(vec3 worldPos) { vec3 uv = worldToUV(worldPos); if (uv.x < 0.0) return 0.0; - return max(texture(FogVolume, uv).r, AMBIENT_FOG*texture(SkyLightVolume, uv).r); + return texture(FogVolume, uv).r; } vec2 light(vec3 worldPos) diff --git a/src/client/resources/assets/veila/shaders/sim.frag b/src/client/resources/assets/veila/shaders/sim.frag index f3cb629..c3ed3ba 100644 --- a/src/client/resources/assets/veila/shaders/sim.frag +++ b/src/client/resources/assets/veila/shaders/sim.frag @@ -2,6 +2,7 @@ uniform usampler3D WorldInfoSampler; uniform sampler3D FogReadSampler; +uniform sampler2D BiomeParamsSampler; uniform float GlobalEmissionMultiplier; uniform float GlobalAbsorptionMultiplier; @@ -14,16 +15,19 @@ uniform float HorizontalDiffusion; uniform vec3 VoxelSize; uniform float CurrentZ; +uniform float EmissionMultiplier; +uniform float TargetDensityMultiplier; +uniform float FogDayRandom; // НОВЫЙ: случайное значение [0, 1] +uniform float RainMultiplier; + in vec2 texCoord; out vec4 FragColor; -// Получение эмиссии из magnitude float getEmission(uint magnitude, bool isAbsorber) { if (isAbsorber || magnitude == 0u) return 0.0; return float(magnitude) / 63.0 * 0.05; } -// Получение абсорбции из magnitude float getAbsorption(uint magnitude, bool isAbsorber) { if (!isAbsorber || magnitude == 0u) return 0.0; return float(magnitude) / 63.0 * 0.05; @@ -32,160 +36,130 @@ float getAbsorption(uint magnitude, bool isAbsorber) { void main() { vec3 uv = vec3(texCoord, CurrentZ); - // Читаем упакованное значение - uint packedSelf = texture(WorldInfoSampler, uv).r; + // Параметры биома + vec4 biomeParams = texture(BiomeParamsSampler, uv.xz); + float biomeEmission = biomeParams.r; + float biomeTargetDensityBase = biomeParams.g; + float biomeHumidity = biomeParams.b; + float biomeTimeSensitivity = biomeParams.a; - // Распаковываем через арифметику (битовые операции не доступны в GLSL 1.50) + // === Emission === + float finalEmission = biomeEmission * EmissionMultiplier * GlobalEmissionMultiplier; + + // === Target Density === + // 1. Определяем, туманный ли сегодня день + float fogThreshold = 1.0 - biomeHumidity; + float hasFogToday = step(fogThreshold, FogDayRandom); + + // В дождь дымка всегда (если humidity > 0) + float hasRain = step(0.01, RainMultiplier); + hasFogToday = max(hasFogToday, hasRain * step(0.01, biomeHumidity)); + + // 2. Применяем зависимость от времени суток + // Для plains (timeSensitivity = 1.0): дымка только в окно трапеции + // Для джунглей (timeSensitivity = 0.3): дымка почти всегда + float timeFactor = mix(1.0, TargetDensityMultiplier, biomeTimeSensitivity); + + // 3. Итоговая плотность + float finalTargetDensity = biomeTargetDensityBase * hasFogToday * timeFactor; + + // Читаем воксель мира + uint packedSelf = texture(WorldInfoSampler, uv).r; bool selfSolid = (packedSelf >= 128u); uint tempSelf = packedSelf; if (selfSolid) tempSelf -= 128u; - bool selfIsAbsorber = (tempSelf >= 64u); if (selfIsAbsorber) tempSelf -= 64u; - uint selfMagnitude = tempSelf; - // Если solid, плотность всегда 0 if (selfSolid) { FragColor = vec4(0.0); return; } - // Читаем текущую плотность float fogOld = texture(FogReadSampler, uv).r; float fogNew = fogOld; - // === 1. Emission === - float emission = 0.0; + // Emission + float emission = getEmission(selfMagnitude, selfIsAbsorber) * finalEmission; - // Эмиссия от текущего вокселя (если non-solid источник) - emission += getEmission(selfMagnitude, selfIsAbsorber) * GlobalEmissionMultiplier; - - // Эмиссия от соседних solid источников - // Инициализируем массив через индексы (так надежнее в GLSL 1.50) vec3 offsets[6]; - offsets[0] = vec3(VoxelSize.x, 0.0, 0.0); // +X - offsets[1] = vec3(-VoxelSize.x, 0.0, 0.0); // -X - offsets[2] = vec3(0.0, VoxelSize.y, 0.0); // +Y (Up) - offsets[3] = vec3(0.0, -VoxelSize.y, 0.0); // -Y (Down) - offsets[4] = vec3(0.0, 0.0, VoxelSize.z); // +Z - offsets[5] = vec3(0.0, 0.0, -VoxelSize.z); // -Z + offsets[0] = vec3(VoxelSize.x, 0.0, 0.0); + offsets[1] = vec3(-VoxelSize.x, 0.0, 0.0); + offsets[2] = vec3(0.0, VoxelSize.y, 0.0); + offsets[3] = vec3(0.0, -VoxelSize.y, 0.0); + offsets[4] = vec3(0.0, 0.0, VoxelSize.z); + offsets[5] = vec3(0.0, 0.0, -VoxelSize.z); for (int i = 0; i < 6; i++) { vec3 neighborUV = uv + offsets[i]; uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r; - - // Распаковываем соседа bool neighborSolid = (packedNeighbor >= 128u); - uint tempNeighbor = packedNeighbor; - if (neighborSolid) tempNeighbor -= 128u; - - bool neighborIsAbsorber = (tempNeighbor >= 64u); - if (neighborIsAbsorber) tempNeighbor -= 64u; - - uint neighborMagnitude = tempNeighbor; - - // Solid источник влияет на non-solid соседей + uint tempN = packedNeighbor; + if (neighborSolid) tempN -= 128u; + bool neighborIsAbsorber = (tempN >= 64u); + if (neighborIsAbsorber) tempN -= 64u; + uint neighborMagnitude = tempN; if (neighborSolid) { - emission += getEmission(neighborMagnitude, neighborIsAbsorber) * GlobalEmissionMultiplier; + emission += getEmission(neighborMagnitude, neighborIsAbsorber) * finalEmission; } } - fogNew += emission; - // === 2. Absorption === - float absorption = 0.0; - - // Поглощение от текущего вокселя (если non-solid поглотитель) - absorption += fogOld * getAbsorption(selfMagnitude, selfIsAbsorber) * GlobalAbsorptionMultiplier; - - // Поглощение от соседних solid поглотителей + // Absorption + float absorption = fogOld * getAbsorption(selfMagnitude, selfIsAbsorber) * GlobalAbsorptionMultiplier; for (int i = 0; i < 6; i++) { vec3 neighborUV = uv + offsets[i]; uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r; - - // Распаковываем соседа bool neighborSolid = (packedNeighbor >= 128u); - uint tempNeighbor = packedNeighbor; - if (neighborSolid) tempNeighbor -= 128u; - - bool neighborIsAbsorber = (tempNeighbor >= 64u); - if (neighborIsAbsorber) tempNeighbor -= 64u; - - uint neighborMagnitude = tempNeighbor; - - // Solid поглотитель влияет на non-solid соседей + uint tempN = packedNeighbor; + if (neighborSolid) tempN -= 128u; + bool neighborIsAbsorber = (tempN >= 64u); + if (neighborIsAbsorber) tempN -= 64u; + uint neighborMagnitude = tempN; if (neighborSolid) { absorption += fogOld * getAbsorption(neighborMagnitude, neighborIsAbsorber) * GlobalAbsorptionMultiplier; } } - fogNew -= absorption; - // === 3. Diffusion === + // Diffusion float diffusion = 0.0; - - // +X (right) vec3 uvRight = uv + offsets[0]; uint packedRight = texture(WorldInfoSampler, uvRight).r; - bool rightSolid = (packedRight >= 128u); - if (!rightSolid) { - float fogRight = texture(FogReadSampler, uvRight).r; - diffusion += (fogRight - fogOld) * HorizontalDiffusion * DiffusionRate; + if (!(packedRight >= 128u)) { + diffusion += (texture(FogReadSampler, uvRight).r - fogOld) * HorizontalDiffusion * DiffusionRate; } - - // -X (left) vec3 uvLeft = uv + offsets[1]; uint packedLeft = texture(WorldInfoSampler, uvLeft).r; - bool leftSolid = (packedLeft >= 128u); - if (!leftSolid) { - float fogLeft = texture(FogReadSampler, uvLeft).r; - diffusion += (fogLeft - fogOld) * HorizontalDiffusion * DiffusionRate; + if (!(packedLeft >= 128u)) { + diffusion += (texture(FogReadSampler, uvLeft).r - fogOld) * HorizontalDiffusion * DiffusionRate; } - - // +Y (up) vec3 uvUp = uv + offsets[2]; uint packedUp = texture(WorldInfoSampler, uvUp).r; - bool upSolid = (packedUp >= 128u); - if (!upSolid) { - float fogUp = texture(FogReadSampler, uvUp).r; - diffusion += (fogUp - fogOld) * UpDiffusion * DiffusionRate; + if (!(packedUp >= 128u)) { + diffusion += (texture(FogReadSampler, uvUp).r - fogOld) * UpDiffusion * DiffusionRate; } - - // -Y (down) vec3 uvDown = uv + offsets[3]; uint packedDown = texture(WorldInfoSampler, uvDown).r; - bool downSolid = (packedDown >= 128u); - if (!downSolid) { - float fogDown = texture(FogReadSampler, uvDown).r; - diffusion += (fogDown - fogOld) * DownDiffusion * DiffusionRate; + if (!(packedDown >= 128u)) { + diffusion += (texture(FogReadSampler, uvDown).r - fogOld) * DownDiffusion * DiffusionRate; } - - // +Z vec3 uvFront = uv + offsets[4]; uint packedFront = texture(WorldInfoSampler, uvFront).r; - bool frontSolid = (packedFront >= 128u); - if (!frontSolid) { - float fogFront = texture(FogReadSampler, uvFront).r; - diffusion += (fogFront - fogOld) * HorizontalDiffusion * DiffusionRate; + if (!(packedFront >= 128u)) { + diffusion += (texture(FogReadSampler, uvFront).r - fogOld) * HorizontalDiffusion * DiffusionRate; } - - // -Z vec3 uvBack = uv + offsets[5]; uint packedBack = texture(WorldInfoSampler, uvBack).r; - bool backSolid = (packedBack >= 128u); - if (!backSolid) { - float fogBack = texture(FogReadSampler, uvBack).r; - diffusion += (fogBack - fogOld) * HorizontalDiffusion * DiffusionRate; + if (!(packedBack >= 128u)) { + diffusion += (texture(FogReadSampler, uvBack).r - fogOld) * HorizontalDiffusion * DiffusionRate; } - fogNew += diffusion; - // === 4. Global Decay === - fogNew -= fogOld * GlobalDecay; + // Target Decay + fogNew -= (fogOld - finalTargetDensity) * GlobalDecay; - // === 5. Clamp === fogNew = clamp(fogNew, 0.0, 1.0); - FragColor = vec4(fogNew, 0.0, 0.0, 1.0); } \ No newline at end of file diff --git a/src/main/resources/veila.accesswidener b/src/main/resources/veila.accesswidener index 3f6ae50..3106b29 100644 --- a/src/main/resources/veila.accesswidener +++ b/src/main/resources/veila.accesswidener @@ -2,3 +2,8 @@ accessWidener v2 named accessible method net/minecraft/world/chunk/PalettedContainer get (I)Ljava/lang/Object; accessible field net/minecraft/world/chunk/light/LightingProvider blockLightProvider Lnet/minecraft/world/chunk/light/ChunkLightProvider; accessible field net/minecraft/world/chunk/light/LightingProvider skyLightProvider Lnet/minecraft/world/chunk/light/ChunkLightProvider; +accessible field net/minecraft/world/biome/Biome weather Lnet/minecraft/world/biome/Biome$Weather; +accessible class net/minecraft/world/biome/Biome$Weather +accessible method net/minecraft/world/biome/Biome$Weather downfall ()F +accessible method net/minecraft/world/biome/Biome$Weather temperature ()F +accessible method net/minecraft/world/biome/Biome$Weather hasPrecipitation ()Z