Учёт времени, погоды и биома

This commit is contained in:
2026-08-09 18:47:25 +04:00
parent 0faa8066af
commit f08dee2677
12 changed files with 1094 additions and 102 deletions
@@ -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<Biome> 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;
}
}
@@ -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<Biome> 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);
}
}
@@ -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;
}
}
@@ -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; }
}
@@ -200,7 +200,7 @@ public final class FogSimulationManager {
} }
if (lastStatsPrintTime == 0 || timestamp - lastStatsPrintTime >= STAT_WINDOW_NS) { if (lastStatsPrintTime == 0 || timestamp - lastStatsPrintTime >= STAT_WINDOW_NS) {
printStepStats(); //printStepStats();
lastStatsPrintTime = timestamp; lastStatsPrintTime = timestamp;
} }
} }
@@ -32,6 +32,11 @@ public final class FogSimulationShader {
private static int horizontalDiffusionLocation; private static int horizontalDiffusionLocation;
private static int voxelSizeLocation; private static int voxelSizeLocation;
private static int currentZLocation; 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() { private FogSimulationShader() {
} }
@@ -64,6 +69,11 @@ public final class FogSimulationShader {
horizontalDiffusionLocation = program.uniform("HorizontalDiffusion"); horizontalDiffusionLocation = program.uniform("HorizontalDiffusion");
voxelSizeLocation = program.uniform("VoxelSize"); voxelSizeLocation = program.uniform("VoxelSize");
currentZLocation = program.uniform("CurrentZ"); 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(); fbo = GL30.glGenFramebuffers();
@@ -138,6 +148,10 @@ public final class FogSimulationShader {
int previousTex1_2D = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); int previousTex1_2D = GL11.glGetInteger(GL_TEXTURE_BINDING_2D);
int previousTex1_3D = GL11.glGetInteger(GL_TEXTURE_BINDING_3D); 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 (на всякий случай) // Blend state (на всякий случай)
boolean previousBlend = GL11.glIsEnabled(GL11.GL_BLEND); boolean previousBlend = GL11.glIsEnabled(GL11.GL_BLEND);
boolean previousDepthTest = GL11.glIsEnabled(GL11.GL_DEPTH_TEST); boolean previousDepthTest = GL11.glIsEnabled(GL11.GL_DEPTH_TEST);
@@ -179,6 +193,10 @@ public final class FogSimulationShader {
GL11.glBindTexture(GL12.GL_TEXTURE_3D, fogRead); GL11.glBindTexture(GL12.GL_TEXTURE_3D, fogRead);
GL20.glUniform1i(fogReadSamplerLocation, 1); 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(globalEmissionMultiplierLocation, globalEmissionMultiplier);
GL20.glUniform1f(globalAbsorptionMultiplierLocation, globalAbsorptionMultiplier); GL20.glUniform1f(globalAbsorptionMultiplierLocation, globalAbsorptionMultiplier);
@@ -187,6 +205,11 @@ public final class FogSimulationShader {
GL20.glUniform1f(upDiffusionLocation, upDiffusion); GL20.glUniform1f(upDiffusionLocation, upDiffusion);
GL20.glUniform1f(downDiffusionLocation, downDiffusion); GL20.glUniform1f(downDiffusionLocation, downDiffusion);
GL20.glUniform1f(horizontalDiffusionLocation, horizontalDiffusion); 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, GL20.glUniform3f(voxelSizeLocation,
1.0f / FogWorldVolume.SIZE_X, 1.0f / FogWorldVolume.SIZE_X,
1.0f / FogWorldVolume.SIZE_Y, 1.0f / FogWorldVolume.SIZE_Y,
@@ -241,6 +264,10 @@ public final class FogSimulationShader {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTex1_2D); GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTex1_2D);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, previousTex1_3D); 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); GL13.glActiveTexture(previousActiveTexture);
@@ -15,7 +15,7 @@ public final class FogSystem {
private FogSystem() { private FogSystem() {
} }
public static void beginRender() { public static void beginRender(float tickDelta) {
if (!initialized) { if (!initialized) {
initialize(); initialize();
@@ -32,6 +32,11 @@ public final class FogSystem {
// Освещение: загружаем готовые секции + инкрементальные обновления (render thread) // Освещение: загружаем готовые секции + инкрементальные обновления (render thread)
FogLightStreamer.uploadReadySections(); FogLightStreamer.uploadReadySections();
// Биомы
FogBiomeStreamer.uploadReadyData();
FogEnvironmentContext.update(tickDelta);
FogSimulationManager.update(); FogSimulationManager.update();
} }
@@ -41,6 +46,9 @@ public final class FogSystem {
// Освещение: читаем данные секций (client thread, без OpenGL) // Освещение: читаем данные секций (client thread, без OpenGL)
FogLightStreamer.prepareAllSections(); FogLightStreamer.prepareAllSections();
// Читаем биомы в client thread
FogBiomeStreamer.processPending();
} }
private static void initialize() { private static void initialize() {
@@ -48,6 +56,8 @@ public final class FogSystem {
FogLightVolume.init(); FogLightVolume.init();
FogLightStreamer.init(); FogLightStreamer.init();
FogSimulationManager.init(); FogSimulationManager.init();
FogBiomeTexture.init();
FogEnvironmentContext.init();
initialized = true; initialized = true;
} }
@@ -103,9 +113,18 @@ public final class FogSystem {
ringChunkOffsetX, ringChunkOffsetX,
ringChunkOffsetZ 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 // Boost uploads for a few frames to catch up after a window shift
FogChunkStreamer.setBurst(8); FogChunkStreamer.setBurst(8);
FogLightStreamer.setBurst(8); FogLightStreamer.setBurst(8);
FogBiomeStreamer.setBurst(8);
} }
public static int originChunkX() { public static int originChunkX() {
@@ -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);
}
}
@@ -63,7 +63,7 @@ public class GameRendererMixin {
) { ) {
net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance(); net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance();
if (client.world != null && client.player != null) { if (client.world != null && client.player != null) {
FogSystem.beginRender(); FogSystem.beginRender(tickDelta);
} }
} }
} }
@@ -43,8 +43,6 @@ const float SCATTERING_COEFF = 1.0;
const float AMBIENT_INTENSITY = 0.10; const float AMBIENT_INTENSITY = 0.10;
const float MIN_TRANSMITTANCE = 0.01; const float MIN_TRANSMITTANCE = 0.01;
const float AMBIENT_FOG = 0.0025;
//------------------------------------------------------------ //------------------------------------------------------------
vec3 reconstructViewPosition(vec2 uv) vec3 reconstructViewPosition(vec2 uv)
@@ -99,7 +97,7 @@ float density(vec3 worldPos)
{ {
vec3 uv = worldToUV(worldPos); vec3 uv = worldToUV(worldPos);
if (uv.x < 0.0) return 0.0; 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) vec2 light(vec3 worldPos)
@@ -2,6 +2,7 @@
uniform usampler3D WorldInfoSampler; uniform usampler3D WorldInfoSampler;
uniform sampler3D FogReadSampler; uniform sampler3D FogReadSampler;
uniform sampler2D BiomeParamsSampler;
uniform float GlobalEmissionMultiplier; uniform float GlobalEmissionMultiplier;
uniform float GlobalAbsorptionMultiplier; uniform float GlobalAbsorptionMultiplier;
@@ -14,16 +15,19 @@ uniform float HorizontalDiffusion;
uniform vec3 VoxelSize; uniform vec3 VoxelSize;
uniform float CurrentZ; uniform float CurrentZ;
uniform float EmissionMultiplier;
uniform float TargetDensityMultiplier;
uniform float FogDayRandom; // НОВЫЙ: случайное значение [0, 1]
uniform float RainMultiplier;
in vec2 texCoord; in vec2 texCoord;
out vec4 FragColor; out vec4 FragColor;
// Получение эмиссии из magnitude
float getEmission(uint magnitude, bool isAbsorber) { float getEmission(uint magnitude, bool isAbsorber) {
if (isAbsorber || magnitude == 0u) return 0.0; if (isAbsorber || magnitude == 0u) return 0.0;
return float(magnitude) / 63.0 * 0.05; return float(magnitude) / 63.0 * 0.05;
} }
// Получение абсорбции из magnitude
float getAbsorption(uint magnitude, bool isAbsorber) { float getAbsorption(uint magnitude, bool isAbsorber) {
if (!isAbsorber || magnitude == 0u) return 0.0; if (!isAbsorber || magnitude == 0u) return 0.0;
return float(magnitude) / 63.0 * 0.05; return float(magnitude) / 63.0 * 0.05;
@@ -32,160 +36,130 @@ float getAbsorption(uint magnitude, bool isAbsorber) {
void main() { void main() {
vec3 uv = vec3(texCoord, CurrentZ); 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); bool selfSolid = (packedSelf >= 128u);
uint tempSelf = packedSelf; uint tempSelf = packedSelf;
if (selfSolid) tempSelf -= 128u; if (selfSolid) tempSelf -= 128u;
bool selfIsAbsorber = (tempSelf >= 64u); bool selfIsAbsorber = (tempSelf >= 64u);
if (selfIsAbsorber) tempSelf -= 64u; if (selfIsAbsorber) tempSelf -= 64u;
uint selfMagnitude = tempSelf; uint selfMagnitude = tempSelf;
// Если solid, плотность всегда 0
if (selfSolid) { if (selfSolid) {
FragColor = vec4(0.0); FragColor = vec4(0.0);
return; return;
} }
// Читаем текущую плотность
float fogOld = texture(FogReadSampler, uv).r; float fogOld = texture(FogReadSampler, uv).r;
float fogNew = fogOld; float fogNew = fogOld;
// === 1. Emission === // Emission
float emission = 0.0; float emission = getEmission(selfMagnitude, selfIsAbsorber) * finalEmission;
// Эмиссия от текущего вокселя (если non-solid источник)
emission += getEmission(selfMagnitude, selfIsAbsorber) * GlobalEmissionMultiplier;
// Эмиссия от соседних solid источников
// Инициализируем массив через индексы (так надежнее в GLSL 1.50)
vec3 offsets[6]; vec3 offsets[6];
offsets[0] = vec3(VoxelSize.x, 0.0, 0.0); // +X offsets[0] = vec3(VoxelSize.x, 0.0, 0.0);
offsets[1] = vec3(-VoxelSize.x, 0.0, 0.0); // -X offsets[1] = vec3(-VoxelSize.x, 0.0, 0.0);
offsets[2] = vec3(0.0, VoxelSize.y, 0.0); // +Y (Up) offsets[2] = vec3(0.0, VoxelSize.y, 0.0);
offsets[3] = vec3(0.0, -VoxelSize.y, 0.0); // -Y (Down) offsets[3] = vec3(0.0, -VoxelSize.y, 0.0);
offsets[4] = vec3(0.0, 0.0, VoxelSize.z); // +Z offsets[4] = vec3(0.0, 0.0, VoxelSize.z);
offsets[5] = vec3(0.0, 0.0, -VoxelSize.z); // -Z offsets[5] = vec3(0.0, 0.0, -VoxelSize.z);
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
vec3 neighborUV = uv + offsets[i]; vec3 neighborUV = uv + offsets[i];
uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r; uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r;
// Распаковываем соседа
bool neighborSolid = (packedNeighbor >= 128u); bool neighborSolid = (packedNeighbor >= 128u);
uint tempNeighbor = packedNeighbor; uint tempN = packedNeighbor;
if (neighborSolid) tempNeighbor -= 128u; if (neighborSolid) tempN -= 128u;
bool neighborIsAbsorber = (tempN >= 64u);
bool neighborIsAbsorber = (tempNeighbor >= 64u); if (neighborIsAbsorber) tempN -= 64u;
if (neighborIsAbsorber) tempNeighbor -= 64u; uint neighborMagnitude = tempN;
uint neighborMagnitude = tempNeighbor;
// Solid источник влияет на non-solid соседей
if (neighborSolid) { if (neighborSolid) {
emission += getEmission(neighborMagnitude, neighborIsAbsorber) * GlobalEmissionMultiplier; emission += getEmission(neighborMagnitude, neighborIsAbsorber) * finalEmission;
} }
} }
fogNew += emission; fogNew += emission;
// === 2. Absorption === // Absorption
float absorption = 0.0; float absorption = fogOld * getAbsorption(selfMagnitude, selfIsAbsorber) * GlobalAbsorptionMultiplier;
// Поглощение от текущего вокселя (если non-solid поглотитель)
absorption += fogOld * getAbsorption(selfMagnitude, selfIsAbsorber) * GlobalAbsorptionMultiplier;
// Поглощение от соседних solid поглотителей
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
vec3 neighborUV = uv + offsets[i]; vec3 neighborUV = uv + offsets[i];
uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r; uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r;
// Распаковываем соседа
bool neighborSolid = (packedNeighbor >= 128u); bool neighborSolid = (packedNeighbor >= 128u);
uint tempNeighbor = packedNeighbor; uint tempN = packedNeighbor;
if (neighborSolid) tempNeighbor -= 128u; if (neighborSolid) tempN -= 128u;
bool neighborIsAbsorber = (tempN >= 64u);
bool neighborIsAbsorber = (tempNeighbor >= 64u); if (neighborIsAbsorber) tempN -= 64u;
if (neighborIsAbsorber) tempNeighbor -= 64u; uint neighborMagnitude = tempN;
uint neighborMagnitude = tempNeighbor;
// Solid поглотитель влияет на non-solid соседей
if (neighborSolid) { if (neighborSolid) {
absorption += fogOld * getAbsorption(neighborMagnitude, neighborIsAbsorber) * GlobalAbsorptionMultiplier; absorption += fogOld * getAbsorption(neighborMagnitude, neighborIsAbsorber) * GlobalAbsorptionMultiplier;
} }
} }
fogNew -= absorption; fogNew -= absorption;
// === 3. Diffusion === // Diffusion
float diffusion = 0.0; float diffusion = 0.0;
// +X (right)
vec3 uvRight = uv + offsets[0]; vec3 uvRight = uv + offsets[0];
uint packedRight = texture(WorldInfoSampler, uvRight).r; uint packedRight = texture(WorldInfoSampler, uvRight).r;
bool rightSolid = (packedRight >= 128u); if (!(packedRight >= 128u)) {
if (!rightSolid) { diffusion += (texture(FogReadSampler, uvRight).r - fogOld) * HorizontalDiffusion * DiffusionRate;
float fogRight = texture(FogReadSampler, uvRight).r;
diffusion += (fogRight - fogOld) * HorizontalDiffusion * DiffusionRate;
} }
// -X (left)
vec3 uvLeft = uv + offsets[1]; vec3 uvLeft = uv + offsets[1];
uint packedLeft = texture(WorldInfoSampler, uvLeft).r; uint packedLeft = texture(WorldInfoSampler, uvLeft).r;
bool leftSolid = (packedLeft >= 128u); if (!(packedLeft >= 128u)) {
if (!leftSolid) { diffusion += (texture(FogReadSampler, uvLeft).r - fogOld) * HorizontalDiffusion * DiffusionRate;
float fogLeft = texture(FogReadSampler, uvLeft).r;
diffusion += (fogLeft - fogOld) * HorizontalDiffusion * DiffusionRate;
} }
// +Y (up)
vec3 uvUp = uv + offsets[2]; vec3 uvUp = uv + offsets[2];
uint packedUp = texture(WorldInfoSampler, uvUp).r; uint packedUp = texture(WorldInfoSampler, uvUp).r;
bool upSolid = (packedUp >= 128u); if (!(packedUp >= 128u)) {
if (!upSolid) { diffusion += (texture(FogReadSampler, uvUp).r - fogOld) * UpDiffusion * DiffusionRate;
float fogUp = texture(FogReadSampler, uvUp).r;
diffusion += (fogUp - fogOld) * UpDiffusion * DiffusionRate;
} }
// -Y (down)
vec3 uvDown = uv + offsets[3]; vec3 uvDown = uv + offsets[3];
uint packedDown = texture(WorldInfoSampler, uvDown).r; uint packedDown = texture(WorldInfoSampler, uvDown).r;
bool downSolid = (packedDown >= 128u); if (!(packedDown >= 128u)) {
if (!downSolid) { diffusion += (texture(FogReadSampler, uvDown).r - fogOld) * DownDiffusion * DiffusionRate;
float fogDown = texture(FogReadSampler, uvDown).r;
diffusion += (fogDown - fogOld) * DownDiffusion * DiffusionRate;
} }
// +Z
vec3 uvFront = uv + offsets[4]; vec3 uvFront = uv + offsets[4];
uint packedFront = texture(WorldInfoSampler, uvFront).r; uint packedFront = texture(WorldInfoSampler, uvFront).r;
bool frontSolid = (packedFront >= 128u); if (!(packedFront >= 128u)) {
if (!frontSolid) { diffusion += (texture(FogReadSampler, uvFront).r - fogOld) * HorizontalDiffusion * DiffusionRate;
float fogFront = texture(FogReadSampler, uvFront).r;
diffusion += (fogFront - fogOld) * HorizontalDiffusion * DiffusionRate;
} }
// -Z
vec3 uvBack = uv + offsets[5]; vec3 uvBack = uv + offsets[5];
uint packedBack = texture(WorldInfoSampler, uvBack).r; uint packedBack = texture(WorldInfoSampler, uvBack).r;
bool backSolid = (packedBack >= 128u); if (!(packedBack >= 128u)) {
if (!backSolid) { diffusion += (texture(FogReadSampler, uvBack).r - fogOld) * HorizontalDiffusion * DiffusionRate;
float fogBack = texture(FogReadSampler, uvBack).r;
diffusion += (fogBack - fogOld) * HorizontalDiffusion * DiffusionRate;
} }
fogNew += diffusion; fogNew += diffusion;
// === 4. Global Decay === // Target Decay
fogNew -= fogOld * GlobalDecay; fogNew -= (fogOld - finalTargetDensity) * GlobalDecay;
// === 5. Clamp ===
fogNew = clamp(fogNew, 0.0, 1.0); fogNew = clamp(fogNew, 0.0, 1.0);
FragColor = vec4(fogNew, 0.0, 0.0, 1.0); FragColor = vec4(fogNew, 0.0, 0.0, 1.0);
} }
+5
View File
@@ -2,3 +2,8 @@ accessWidener v2 named
accessible method net/minecraft/world/chunk/PalettedContainer get (I)Ljava/lang/Object; 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 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/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