Учёт времени, погоды и биома
This commit is contained in:
@@ -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) {
|
||||
printStepStats();
|
||||
//printStepStats();
|
||||
lastStatsPrintTime = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
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 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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user