Попытка учесть освещённость вокселей
This commit is contained in:
@@ -5,6 +5,7 @@ import net.minecraft.client.MinecraftClient;
|
|||||||
import net.minecraft.client.gl.Framebuffer;
|
import net.minecraft.client.gl.Framebuffer;
|
||||||
import net.minecraft.client.render.Camera;
|
import net.minecraft.client.render.Camera;
|
||||||
import net.minecraft.util.math.Vec3d;
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
import net.minecraft.world.LightType;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.lwjgl.BufferUtils;
|
import org.lwjgl.BufferUtils;
|
||||||
import org.lwjgl.opengl.*;
|
import org.lwjgl.opengl.*;
|
||||||
@@ -13,6 +14,7 @@ import su.divan2000.veila.client.gl.FullscreenQuad;
|
|||||||
import su.divan2000.veila.client.gl.GLProgram;
|
import su.divan2000.veila.client.gl.GLProgram;
|
||||||
import su.divan2000.veila.client.gl.GLShader;
|
import su.divan2000.veila.client.gl.GLShader;
|
||||||
import su.divan2000.veila.client.gl.ResourceUtil;
|
import su.divan2000.veila.client.gl.ResourceUtil;
|
||||||
|
import su.divan2000.veila.client.render.fog.FogLightVolume;
|
||||||
import su.divan2000.veila.client.render.fog.FogSimulationManager;
|
import su.divan2000.veila.client.render.fog.FogSimulationManager;
|
||||||
import su.divan2000.veila.client.render.fog.FogSystem;
|
import su.divan2000.veila.client.render.fog.FogSystem;
|
||||||
import su.divan2000.veila.client.render.fog.FogWorldVolume;
|
import su.divan2000.veila.client.render.fog.FogWorldVolume;
|
||||||
@@ -62,15 +64,28 @@ public class PostProcessingManager {
|
|||||||
private static int inverseViewLocation;
|
private static int inverseViewLocation;
|
||||||
|
|
||||||
private static int fogVolumeLocation;
|
private static int fogVolumeLocation;
|
||||||
private static int fogOriginLocation;
|
private static int lightVolumeLocation;
|
||||||
|
|
||||||
|
private static int fogOriginLocation;
|
||||||
private static int ringOffsetLocation;
|
private static int ringOffsetLocation;
|
||||||
|
|
||||||
|
private static float fogR, fogG, fogB;
|
||||||
|
private static int fogColorLocation;
|
||||||
|
private static int skyBrightnessLocation;
|
||||||
|
|
||||||
|
public static void setFogColor(float r, float g, float b) {
|
||||||
|
fogR = r;
|
||||||
|
fogG = g;
|
||||||
|
fogB = b;
|
||||||
|
}
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
|
||||||
if (program != null)
|
if (program != null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
setFogColor(0.75f, 0.80f, 0.90f);
|
||||||
|
|
||||||
GLShader vertex = new GLShader(
|
GLShader vertex = new GLShader(
|
||||||
GL20.GL_VERTEX_SHADER,
|
GL20.GL_VERTEX_SHADER,
|
||||||
ResourceUtil.load("shaders/fullscreen.vert")
|
ResourceUtil.load("shaders/fullscreen.vert")
|
||||||
@@ -78,7 +93,7 @@ public class PostProcessingManager {
|
|||||||
|
|
||||||
GLShader fragment = new GLShader(
|
GLShader fragment = new GLShader(
|
||||||
GL20.GL_FRAGMENT_SHADER,
|
GL20.GL_FRAGMENT_SHADER,
|
||||||
ResourceUtil.load("shaders/red.frag")
|
ResourceUtil.load("shaders/fog_raymarching.frag")
|
||||||
);
|
);
|
||||||
|
|
||||||
program = new GLProgram(vertex, fragment);
|
program = new GLProgram(vertex, fragment);
|
||||||
@@ -90,6 +105,8 @@ public class PostProcessingManager {
|
|||||||
depthSamplerLocation = program.uniform("DepthSampler");
|
depthSamplerLocation = program.uniform("DepthSampler");
|
||||||
|
|
||||||
fogVolumeLocation = program.uniform("FogVolume");
|
fogVolumeLocation = program.uniform("FogVolume");
|
||||||
|
lightVolumeLocation = program.uniform("LightVolume");
|
||||||
|
|
||||||
fogOriginLocation = program.uniform("FogOrigin");
|
fogOriginLocation = program.uniform("FogOrigin");
|
||||||
ringOffsetLocation = program.uniform("RingBlockOffset");
|
ringOffsetLocation = program.uniform("RingBlockOffset");
|
||||||
|
|
||||||
@@ -99,6 +116,9 @@ public class PostProcessingManager {
|
|||||||
viewLocation = program.uniform("View");
|
viewLocation = program.uniform("View");
|
||||||
inverseViewLocation = program.uniform("InverseView");
|
inverseViewLocation = program.uniform("InverseView");
|
||||||
|
|
||||||
|
fogColorLocation = program.uniform("FogColor");
|
||||||
|
skyBrightnessLocation = program.uniform("SkyBrightness");
|
||||||
|
|
||||||
FullscreenQuad.init();
|
FullscreenQuad.init();
|
||||||
|
|
||||||
vertex.delete();
|
vertex.delete();
|
||||||
@@ -107,13 +127,16 @@ public class PostProcessingManager {
|
|||||||
System.out.println("VEILA shader compiled!");
|
System.out.println("VEILA shader compiled!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void render() {
|
public static void render(float tickDelta) {
|
||||||
|
|
||||||
if (program == null)
|
if (program == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
|
||||||
|
if (client.world == null)
|
||||||
|
return;
|
||||||
|
|
||||||
Framebuffer framebuffer = client.getFramebuffer();
|
Framebuffer framebuffer = client.getFramebuffer();
|
||||||
|
|
||||||
int width = framebuffer.textureWidth;
|
int width = framebuffer.textureWidth;
|
||||||
@@ -163,6 +186,13 @@ public class PostProcessingManager {
|
|||||||
2
|
2
|
||||||
);
|
);
|
||||||
|
|
||||||
|
GL13.glActiveTexture(GL13.GL_TEXTURE3);
|
||||||
|
GL11.glBindTexture(
|
||||||
|
GL12.GL_TEXTURE_3D,
|
||||||
|
FogLightVolume.getTexture()
|
||||||
|
);
|
||||||
|
GL20.glUniform1i(lightVolumeLocation, 3);
|
||||||
|
|
||||||
GL20.glUniform2i(
|
GL20.glUniform2i(
|
||||||
fogOriginLocation,
|
fogOriginLocation,
|
||||||
FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE,
|
FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE,
|
||||||
@@ -229,8 +259,31 @@ public class PostProcessingManager {
|
|||||||
height
|
height
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Цвет тумана
|
||||||
|
*/
|
||||||
|
|
||||||
|
GL20.glUniform3f(
|
||||||
|
fogColorLocation,
|
||||||
|
fogR,
|
||||||
|
fogG,
|
||||||
|
fogB
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Яркость тумана
|
||||||
|
*/
|
||||||
|
|
||||||
|
GL20.glUniform1f(
|
||||||
|
skyBrightnessLocation,
|
||||||
|
client.world.getSkyBrightness(tickDelta)*0.75f+0.25f
|
||||||
|
);
|
||||||
|
|
||||||
FullscreenQuad.draw();
|
FullscreenQuad.draw();
|
||||||
|
|
||||||
|
GL13.glActiveTexture(GL13.GL_TEXTURE3);
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
|
||||||
GL13.glActiveTexture(GL13.GL_TEXTURE2);
|
GL13.glActiveTexture(GL13.GL_TEXTURE2);
|
||||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public final class FogBlockRegistry {
|
|||||||
BLOCK_CACHE_INITIALIZED = new boolean[blockCount];
|
BLOCK_CACHE_INITIALIZED = new boolean[blockCount];
|
||||||
|
|
||||||
// Waterlogged блок всегда содержит воду
|
// Waterlogged блок всегда содержит воду
|
||||||
WATER_VALUE = new FogBlockProperties(true, 1, 60).pack();
|
WATER_VALUE = new FogBlockProperties(true, 1, 20).pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
private FogBlockRegistry() {
|
private FogBlockRegistry() {
|
||||||
@@ -54,10 +54,10 @@ public final class FogBlockRegistry {
|
|||||||
// Источники тумана (sign = +1)
|
// Источники тумана (sign = +1)
|
||||||
// WATER уже обработан в getPackedProperties
|
// WATER уже обработан в getPackedProperties
|
||||||
if (block == Blocks.GRASS || block == Blocks.TALL_GRASS || block == Blocks.FERN) {
|
if (block == Blocks.GRASS || block == Blocks.TALL_GRASS || block == Blocks.FERN) {
|
||||||
return new FogBlockProperties(solid, 1, 10).pack();
|
return new FogBlockProperties(solid, 1, 50).pack();
|
||||||
}
|
}
|
||||||
if (block == Blocks.VINE) {
|
if (block == Blocks.VINE) {
|
||||||
return new FogBlockProperties(solid, 1, 5).pack();
|
return new FogBlockProperties(solid, 1, 45).pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поглотители тумана (sign = -1)
|
// Поглотители тумана (sign = -1)
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package su.divan2000.veila.client.render.fog;
|
||||||
|
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.ChunkSectionPos;
|
||||||
|
import net.minecraft.world.LightType;
|
||||||
|
import net.minecraft.world.chunk.ChunkNibbleArray;
|
||||||
|
import net.minecraft.world.chunk.light.ChunkLightProvider;
|
||||||
|
import net.minecraft.world.chunk.light.LightingProvider;
|
||||||
|
|
||||||
|
public final class FogLightProvider {
|
||||||
|
|
||||||
|
public static final int SECTION_DATA_SIZE = 4096;
|
||||||
|
|
||||||
|
private FogLightProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] fillSectionIntoArray(int sectionX, int sectionY, int sectionZ) {
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
if (client.world == null) return null;
|
||||||
|
|
||||||
|
LightingProvider provider = client.world.getLightingProvider();
|
||||||
|
|
||||||
|
// Прямой доступ к провайдерам через AccessWidener
|
||||||
|
ChunkLightProvider<?, ?> blockProvider = provider.blockLightProvider;
|
||||||
|
ChunkLightProvider<?, ?> skyProvider = provider.skyLightProvider;
|
||||||
|
|
||||||
|
// Проверка на null (поля @Nullable)
|
||||||
|
if (blockProvider == null || skyProvider == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Используем ПУБЛИЧНЫЙ метод ChunkLightProvider.getLightSection()
|
||||||
|
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
|
||||||
|
|
||||||
|
ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos);
|
||||||
|
ChunkNibbleArray skyArray = skyProvider.getLightSection(sectionPos);
|
||||||
|
|
||||||
|
if (blockArray == null && skyArray == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] result = new byte[SECTION_DATA_SIZE];
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
// Порядок заполнения: Z → Y → X (как в FogChunkProvider)
|
||||||
|
for (int localZ = 0; localZ < 16; localZ++) {
|
||||||
|
for (int localY = 0; localY < 16; localY++) {
|
||||||
|
for (int localX = 0; localX < 16; localX++) {
|
||||||
|
int blockLight = (blockArray != null) ? blockArray.get(localX, localY, localZ) : 0;
|
||||||
|
int skyLight = (skyArray != null) ? skyArray.get(localX, localY, localZ) : 0;
|
||||||
|
|
||||||
|
result[index++] = FogLightVolume.packLight(blockLight, skyLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte computeLightValue(BlockPos pos) {
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
if (client.world == null) return FogLightVolume.DEFAULT_LIGHT;
|
||||||
|
|
||||||
|
LightingProvider provider = client.world.getLightingProvider();
|
||||||
|
int blockLight = provider.get(LightType.BLOCK).getLightLevel(pos);
|
||||||
|
int skyLight = provider.get(LightType.SKY).getLightLevel(pos);
|
||||||
|
|
||||||
|
return FogLightVolume.packLight(blockLight, skyLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
package su.divan2000.veila.client.render.fog;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLongArray;
|
||||||
|
|
||||||
|
public final class FogLightStreamer {
|
||||||
|
|
||||||
|
public static final int SECTION_SIZE = 16;
|
||||||
|
public static final int SECTIONS_PER_CHUNK_Y = FogLightVolume.SIZE_Y / SECTION_SIZE;
|
||||||
|
|
||||||
|
// Общее количество секций в окне: 16 чанков × 16 чанков × 24 секции = 6144
|
||||||
|
private static final int TOTAL_SECTIONS =
|
||||||
|
FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z * SECTIONS_PER_CHUNK_Y;
|
||||||
|
|
||||||
|
// Битовая маска: 6144 бита = 96 long = 768 байт
|
||||||
|
private static final AtomicLongArray pendingSectionsMask =
|
||||||
|
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
|
||||||
|
|
||||||
|
private static final AtomicLongArray readySectionsMask =
|
||||||
|
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
|
||||||
|
|
||||||
|
private static final AtomicLongArray initialLoadMask =
|
||||||
|
new AtomicLongArray((TOTAL_SECTIONS + 63) / 64);
|
||||||
|
|
||||||
|
private static final int MAX_SECTIONS_PER_FRAME = 64;
|
||||||
|
private static final int MAX_INITIAL_SECTIONS_PER_TICK = 96;
|
||||||
|
|
||||||
|
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 static final int MAX_REGIONS = 16;
|
||||||
|
private static final int[] regionPhysX = new int[MAX_REGIONS];
|
||||||
|
private static final int[] regionPhysZ = new int[MAX_REGIONS];
|
||||||
|
private static final int[] regionWidth = new int[MAX_REGIONS];
|
||||||
|
private static final int[] regionDepth = new int[MAX_REGIONS];
|
||||||
|
private static int regionCount = 0;
|
||||||
|
|
||||||
|
private FogLightStreamer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конвертирует мировые координаты секции в локальный индекс в маске.
|
||||||
|
* Возвращает -1 если секция вне текущего окна.
|
||||||
|
*/
|
||||||
|
private static int sectionToLocalIndex(int sectionX, int sectionY, int sectionZ) {
|
||||||
|
int chunkX = sectionX;
|
||||||
|
int chunkZ = sectionZ;
|
||||||
|
|
||||||
|
int relativeX = chunkX - currentOriginChunkX;
|
||||||
|
int relativeZ = chunkZ - currentOriginChunkZ;
|
||||||
|
|
||||||
|
if (relativeX < 0 || relativeX >= FogLightVolume.CHUNKS_X ||
|
||||||
|
relativeZ < 0 || relativeZ >= FogLightVolume.CHUNKS_Z) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int localSectionY = sectionY - (FogWorldVolume.MIN_Y >> 4);
|
||||||
|
if (localSectionY < 0 || localSectionY >= SECTIONS_PER_CHUNK_Y) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int physicalX = Math.floorMod(relativeX + currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||||
|
int physicalZ = Math.floorMod(relativeZ + currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||||
|
|
||||||
|
return (localSectionY * FogLightVolume.CHUNKS_Z * FogLightVolume.CHUNKS_X)
|
||||||
|
+ (physicalZ * FogLightVolume.CHUNKS_X)
|
||||||
|
+ physicalX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Устанавливает бит в маске атомарно.
|
||||||
|
*/
|
||||||
|
private static boolean 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 false; // Бит уже установлен
|
||||||
|
}
|
||||||
|
if (mask.compareAndSet(wordIndex, current, current | bitMask)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// CAS не удался - повторяем
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Снимает бит из маски атомарно.
|
||||||
|
*/
|
||||||
|
private static boolean 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 false; // Бит уже снят
|
||||||
|
}
|
||||||
|
if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет и снимает бит (для итерации).
|
||||||
|
* Возвращает индекс если бит был установлен.
|
||||||
|
*/
|
||||||
|
private static int nextSetBit(AtomicLongArray mask, int startIndex) {
|
||||||
|
for (int i = startIndex; i < TOTAL_SECTIONS; i++) {
|
||||||
|
int wordIndex = i >> 6;
|
||||||
|
long bitMask = 1L << (i & 63);
|
||||||
|
|
||||||
|
if ((mask.get(wordIndex) & bitMask) != 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onSectionUpdated(long sectionPos) {
|
||||||
|
int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos);
|
||||||
|
int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos);
|
||||||
|
int sectionZ = net.minecraft.util.math.ChunkSectionPos.unpackZ(sectionPos);
|
||||||
|
|
||||||
|
int index = sectionToLocalIndex(sectionX, sectionY, sectionZ);
|
||||||
|
if (index >= 0) {
|
||||||
|
setBit(pendingSectionsMask, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void update(
|
||||||
|
int oldOriginChunkX,
|
||||||
|
int oldOriginChunkZ,
|
||||||
|
int newOriginChunkX,
|
||||||
|
int newOriginChunkZ,
|
||||||
|
int ringChunkOffsetX,
|
||||||
|
int ringChunkOffsetZ
|
||||||
|
) {
|
||||||
|
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) >= FogLightVolume.CHUNKS_X ||
|
||||||
|
Math.abs(dz) >= FogLightVolume.CHUNKS_Z) {
|
||||||
|
markAllPending();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
regionCount = 0;
|
||||||
|
|
||||||
|
if (dx > 0) {
|
||||||
|
int worldX = newOriginChunkX + FogLightVolume.CHUNKS_X - dx;
|
||||||
|
int worldZ = newOriginChunkZ;
|
||||||
|
collectRegions(worldX, worldZ, dx, FogLightVolume.CHUNKS_Z);
|
||||||
|
addInitialLoadForRange(worldX, worldZ, dx, FogLightVolume.CHUNKS_Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dx < 0) {
|
||||||
|
int worldX = newOriginChunkX;
|
||||||
|
int worldZ = newOriginChunkZ;
|
||||||
|
collectRegions(worldX, worldZ, -dx, FogLightVolume.CHUNKS_Z);
|
||||||
|
addInitialLoadForRange(worldX, worldZ, -dx, FogLightVolume.CHUNKS_Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dz > 0) {
|
||||||
|
int worldX = newOriginChunkX;
|
||||||
|
int worldZ = newOriginChunkZ + FogLightVolume.CHUNKS_Z - dz;
|
||||||
|
collectRegions(worldX, worldZ, FogLightVolume.CHUNKS_X, dz);
|
||||||
|
addInitialLoadForRange(worldX, worldZ, FogLightVolume.CHUNKS_X, dz);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dz < 0) {
|
||||||
|
int worldX = newOriginChunkX;
|
||||||
|
int worldZ = newOriginChunkZ;
|
||||||
|
collectRegions(worldX, worldZ, FogLightVolume.CHUNKS_X, -dz);
|
||||||
|
addInitialLoadForRange(worldX, worldZ, FogLightVolume.CHUNKS_X, -dz);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regionCount > 0) {
|
||||||
|
FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addInitialLoadForRange(int worldX, int worldZ, int countX, int countZ) {
|
||||||
|
for (int i = 0; i < countX; i++) {
|
||||||
|
for (int j = 0; j < countZ; j++) {
|
||||||
|
int chunkX = worldX + i;
|
||||||
|
int chunkZ = worldZ + j;
|
||||||
|
|
||||||
|
for (int sectionY = 0; sectionY < SECTIONS_PER_CHUNK_Y; sectionY++) {
|
||||||
|
int sectionPos = sectionToLocalIndex(
|
||||||
|
chunkX,
|
||||||
|
sectionY + (FogWorldVolume.MIN_Y >> 4),
|
||||||
|
chunkZ
|
||||||
|
);
|
||||||
|
if (sectionPos >= 0) {
|
||||||
|
setBit(initialLoadMask, sectionPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void markAllPending() {
|
||||||
|
regionCount = 1;
|
||||||
|
regionPhysX[0] = 0;
|
||||||
|
regionPhysZ[0] = 0;
|
||||||
|
regionWidth[0] = FogLightVolume.CHUNKS_X;
|
||||||
|
regionDepth[0] = FogLightVolume.CHUNKS_Z;
|
||||||
|
|
||||||
|
FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||||
|
|
||||||
|
// Устанавливаем все биты в initialLoadMask
|
||||||
|
for (int i = 0; i < pendingSectionsMask.length(); i++) {
|
||||||
|
initialLoadMask.set(i, -1L); // Все биты = 1
|
||||||
|
}
|
||||||
|
// Очищаем лишние биты в последнем слове
|
||||||
|
int totalWords = (TOTAL_SECTIONS + 63) / 64;
|
||||||
|
int extraBits = TOTAL_SECTIONS & 63;
|
||||||
|
if (extraBits != 0) {
|
||||||
|
initialLoadMask.set(totalWords - 1, (1L << extraBits) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collectRegions(int worldX, int worldZ, int countX, int countZ) {
|
||||||
|
int[] xStarts = new int[2];
|
||||||
|
int[] xCounts = new int[2];
|
||||||
|
int xSegCount = splitIntoSegments(worldX, countX, FogLightVolume.CHUNKS_X, xStarts, xCounts);
|
||||||
|
|
||||||
|
int[] zStarts = new int[2];
|
||||||
|
int[] zCounts = new int[2];
|
||||||
|
int zSegCount = splitIntoSegments(worldZ, countZ, FogLightVolume.CHUNKS_Z, zStarts, zCounts);
|
||||||
|
|
||||||
|
for (int i = 0; i < xSegCount; i++) {
|
||||||
|
for (int j = 0; j < zSegCount; j++) {
|
||||||
|
if (regionCount >= MAX_REGIONS) return;
|
||||||
|
regionPhysX[regionCount] = xStarts[i];
|
||||||
|
regionPhysZ[regionCount] = zStarts[j];
|
||||||
|
regionWidth[regionCount] = xCounts[i];
|
||||||
|
regionDepth[regionCount] = zCounts[j];
|
||||||
|
regionCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int splitIntoSegments(
|
||||||
|
int start, int count, int dim,
|
||||||
|
int[] outStarts, int[] outCounts
|
||||||
|
) {
|
||||||
|
if (count == 0) return 0;
|
||||||
|
|
||||||
|
int first = Math.floorMod(start, dim);
|
||||||
|
int last = Math.floorMod(start + count - 1, dim);
|
||||||
|
|
||||||
|
if (first <= last) {
|
||||||
|
outStarts[0] = first;
|
||||||
|
outCounts[0] = count;
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
int firstCount = dim - first;
|
||||||
|
outStarts[0] = first;
|
||||||
|
outCounts[0] = firstCount;
|
||||||
|
outStarts[1] = 0;
|
||||||
|
outCounts[1] = count - firstCount;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void prepareInitialLoads() {
|
||||||
|
int processed = 0;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
while (processed < MAX_INITIAL_SECTIONS_PER_TICK) {
|
||||||
|
index = nextSetBit(initialLoadMask, index);
|
||||||
|
if (index < 0) break;
|
||||||
|
|
||||||
|
// Конвертируем индекс обратно в координаты секции
|
||||||
|
int sectionX = currentOriginChunkX + (index % FogLightVolume.CHUNKS_X);
|
||||||
|
int sectionZ = currentOriginChunkZ + ((index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z);
|
||||||
|
int sectionY = (index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z)) + (FogWorldVolume.MIN_Y >> 4);
|
||||||
|
|
||||||
|
byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ);
|
||||||
|
if (sectionData != null) {
|
||||||
|
clearBit(initialLoadMask, index);
|
||||||
|
setBit(readySectionsMask, index);
|
||||||
|
processed++;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void uploadReadySections() {
|
||||||
|
int processed = 0;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
while (processed < MAX_SECTIONS_PER_FRAME) {
|
||||||
|
index = nextSetBit(readySectionsMask, index);
|
||||||
|
if (index < 0) break;
|
||||||
|
|
||||||
|
if (uploadSectionByIndex(index)) {
|
||||||
|
clearBit(readySectionsMask, index);
|
||||||
|
processed++;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void processSectionUpdates() {
|
||||||
|
int processed = 0;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
while (processed < MAX_SECTIONS_PER_FRAME) {
|
||||||
|
index = nextSetBit(pendingSectionsMask, index);
|
||||||
|
if (index < 0) break;
|
||||||
|
|
||||||
|
if (uploadSectionByIndex(index)) {
|
||||||
|
clearBit(pendingSectionsMask, index);
|
||||||
|
processed++;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean uploadSectionByIndex(int index) {
|
||||||
|
int physicalX = index % FogLightVolume.CHUNKS_X;
|
||||||
|
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
||||||
|
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
||||||
|
|
||||||
|
int sectionX = currentOriginChunkX + Math.floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||||
|
int sectionZ = currentOriginChunkZ + Math.floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||||
|
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
||||||
|
|
||||||
|
byte[] sectionData = FogLightProvider.fillSectionIntoArray(sectionX, sectionY, sectionZ);
|
||||||
|
if (sectionData == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FogLightVolume.uploadSection(physicalX, localSectionY, physicalZ, sectionData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
package su.divan2000.veila.client.render.fog;
|
||||||
|
|
||||||
|
import org.lwjgl.BufferUtils;
|
||||||
|
import org.lwjgl.opengl.GL11;
|
||||||
|
import org.lwjgl.opengl.GL12;
|
||||||
|
import org.lwjgl.opengl.GL15;
|
||||||
|
import org.lwjgl.opengl.GL30;
|
||||||
|
import org.lwjgl.opengl.GL31;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static su.divan2000.veila.client.render.fog.FogLightStreamer.SECTION_SIZE;
|
||||||
|
|
||||||
|
public final class FogLightVolume {
|
||||||
|
|
||||||
|
public static final int SIZE_X = FogWorldVolume.SIZE_X;
|
||||||
|
public static final int SIZE_Z = FogWorldVolume.SIZE_Z;
|
||||||
|
public static final int SIZE_Y = FogWorldVolume.SIZE_Y;
|
||||||
|
public static final int CHUNK_SIZE = FogWorldVolume.CHUNK_SIZE;
|
||||||
|
public static final int CHUNKS_X = FogWorldVolume.CHUNKS_X;
|
||||||
|
public static final int CHUNKS_Z = FogWorldVolume.CHUNKS_Z;
|
||||||
|
|
||||||
|
private static int texture = -1;
|
||||||
|
|
||||||
|
// Упаковка: bits 0-3 = BlockLight, bits 4-7 = SkyLight
|
||||||
|
public static final byte DEFAULT_LIGHT = packLight(0, 15); // Темно, полное небо
|
||||||
|
|
||||||
|
private static final byte[] ZEROS_ARRAY;
|
||||||
|
static {
|
||||||
|
ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
|
||||||
|
java.util.Arrays.fill(ZEROS_ARRAY, DEFAULT_LIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ByteBuffer CHUNK_BUFFER =
|
||||||
|
BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE);
|
||||||
|
|
||||||
|
private static final ByteBuffer SINGLE_VOXEL_BUFFER =
|
||||||
|
BufferUtils.createByteBuffer(1);
|
||||||
|
|
||||||
|
// PBO для быстрой очистки (как в FogWorldVolume)
|
||||||
|
private static int clearPBO = -1;
|
||||||
|
private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
|
||||||
|
|
||||||
|
private FogLightVolume() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte packLight(int blockLight, int skyLight) {
|
||||||
|
return (byte) (((skyLight & 0xF) << 4) | (blockLight & 0xF));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int unpackBlockLight(byte packed) {
|
||||||
|
return packed & 0xF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int unpackSkyLight(byte packed) {
|
||||||
|
return (packed >> 4) & 0xF;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initClearPBO() {
|
||||||
|
if (clearPBO != -1) return;
|
||||||
|
|
||||||
|
clearPBO = GL15.glGenBuffers();
|
||||||
|
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO);
|
||||||
|
|
||||||
|
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE, GL15.GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
|
||||||
|
if (mapped != null) {
|
||||||
|
for (int i = 0; i < MAX_CLEAR_SIZE; i++) {
|
||||||
|
mapped.put(DEFAULT_LIGHT);
|
||||||
|
}
|
||||||
|
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||||
|
}
|
||||||
|
|
||||||
|
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
if (texture != -1) return;
|
||||||
|
|
||||||
|
initClearPBO();
|
||||||
|
|
||||||
|
texture = GL11.glGenTextures();
|
||||||
|
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||||
|
|
||||||
|
GL30.glTexImage3D(
|
||||||
|
GL12.GL_TEXTURE_3D,
|
||||||
|
0,
|
||||||
|
GL30.GL_R8UI,
|
||||||
|
SIZE_X,
|
||||||
|
SIZE_Y,
|
||||||
|
SIZE_Z,
|
||||||
|
0,
|
||||||
|
GL30.GL_RED_INTEGER,
|
||||||
|
GL11.GL_UNSIGNED_BYTE,
|
||||||
|
(ByteBuffer) null
|
||||||
|
);
|
||||||
|
|
||||||
|
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||||
|
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||||
|
|
||||||
|
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_S, GL12.GL_REPEAT);
|
||||||
|
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
|
||||||
|
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_R, GL12.GL_REPEAT);
|
||||||
|
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
CHUNK_BUFFER.clear();
|
||||||
|
CHUNK_BUFFER.put(ZEROS_ARRAY);
|
||||||
|
CHUNK_BUFFER.flip();
|
||||||
|
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||||
|
|
||||||
|
for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) {
|
||||||
|
for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) {
|
||||||
|
uploadChunk(chunkX, chunkZ, CHUNK_BUFFER);
|
||||||
|
CHUNK_BUFFER.rewind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void uploadChunk(int textureChunkX, int textureChunkZ, ByteBuffer data) {
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||||
|
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
|
||||||
|
|
||||||
|
GL30.glTexSubImage3D(
|
||||||
|
GL12.GL_TEXTURE_3D,
|
||||||
|
0,
|
||||||
|
textureChunkX * CHUNK_SIZE,
|
||||||
|
0,
|
||||||
|
textureChunkZ * CHUNK_SIZE,
|
||||||
|
CHUNK_SIZE,
|
||||||
|
SIZE_Y,
|
||||||
|
CHUNK_SIZE,
|
||||||
|
GL30.GL_RED_INTEGER,
|
||||||
|
GL11.GL_UNSIGNED_BYTE,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateSingleVoxel(
|
||||||
|
int textureChunkX,
|
||||||
|
int textureChunkZ,
|
||||||
|
int localX,
|
||||||
|
int localY,
|
||||||
|
int localZ,
|
||||||
|
byte value
|
||||||
|
) {
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||||
|
|
||||||
|
SINGLE_VOXEL_BUFFER.clear();
|
||||||
|
SINGLE_VOXEL_BUFFER.put(value);
|
||||||
|
SINGLE_VOXEL_BUFFER.flip();
|
||||||
|
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
|
||||||
|
|
||||||
|
GL30.glTexSubImage3D(
|
||||||
|
GL12.GL_TEXTURE_3D,
|
||||||
|
0,
|
||||||
|
textureChunkX * CHUNK_SIZE + localX,
|
||||||
|
localY,
|
||||||
|
textureChunkZ * CHUNK_SIZE + localZ,
|
||||||
|
1, 1, 1,
|
||||||
|
GL30.GL_RED_INTEGER,
|
||||||
|
GL11.GL_UNSIGNED_BYTE,
|
||||||
|
SINGLE_VOXEL_BUFFER
|
||||||
|
);
|
||||||
|
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void uploadChunkFromArray(
|
||||||
|
int textureChunkX,
|
||||||
|
int textureChunkZ,
|
||||||
|
byte[] data
|
||||||
|
) {
|
||||||
|
ByteBuffer buffer = chunkBuffer();
|
||||||
|
buffer.put(data);
|
||||||
|
buffer.flip();
|
||||||
|
uploadChunk(textureChunkX, textureChunkZ, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ByteBuffer chunkBuffer() {
|
||||||
|
CHUNK_BUFFER.clear();
|
||||||
|
return CHUNK_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getTexture() {
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearRegions(
|
||||||
|
int[] physX, int[] physZ,
|
||||||
|
int[] widthChunks, int[] depthChunks,
|
||||||
|
int count
|
||||||
|
) {
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||||
|
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
int w = widthChunks[i] * CHUNK_SIZE;
|
||||||
|
int h = SIZE_Y;
|
||||||
|
int d = depthChunks[i] * CHUNK_SIZE;
|
||||||
|
|
||||||
|
GL30.glTexSubImage3D(
|
||||||
|
GL12.GL_TEXTURE_3D,
|
||||||
|
0,
|
||||||
|
physX[i] * CHUNK_SIZE,
|
||||||
|
0,
|
||||||
|
physZ[i] * CHUNK_SIZE,
|
||||||
|
w, h, d,
|
||||||
|
GL30.GL_RED_INTEGER,
|
||||||
|
GL11.GL_UNSIGNED_BYTE,
|
||||||
|
0L
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Статический буфер для одной секции (4096 байт)
|
||||||
|
private static final ByteBuffer SECTION_BUFFER =
|
||||||
|
BufferUtils.createByteBuffer(SECTION_SIZE * SECTION_SIZE * SECTION_SIZE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Загружает одну секцию (16×16×16) в указанную позицию текстуры.
|
||||||
|
* textureSectionX/Z - в чанках (0-15), textureSectionY - индекс секции по Y (0-23).
|
||||||
|
*/
|
||||||
|
public static void uploadSection(
|
||||||
|
int textureSectionX,
|
||||||
|
int textureSectionY,
|
||||||
|
int textureSectionZ,
|
||||||
|
byte[] data
|
||||||
|
) {
|
||||||
|
SECTION_BUFFER.clear();
|
||||||
|
SECTION_BUFFER.put(data);
|
||||||
|
SECTION_BUFFER.flip();
|
||||||
|
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||||
|
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
|
||||||
|
|
||||||
|
GL30.glTexSubImage3D(
|
||||||
|
GL12.GL_TEXTURE_3D,
|
||||||
|
0,
|
||||||
|
textureSectionX * CHUNK_SIZE,
|
||||||
|
textureSectionY * SECTION_SIZE,
|
||||||
|
textureSectionZ * CHUNK_SIZE,
|
||||||
|
SECTION_SIZE,
|
||||||
|
SECTION_SIZE,
|
||||||
|
SECTION_SIZE,
|
||||||
|
GL30.GL_RED_INTEGER,
|
||||||
|
GL11.GL_UNSIGNED_BYTE,
|
||||||
|
SECTION_BUFFER
|
||||||
|
);
|
||||||
|
|
||||||
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,12 @@ public final class FogSimulationManager {
|
|||||||
// Симулируем каждые 100 мс (10 раз в секунду)
|
// Симулируем каждые 100 мс (10 раз в секунду)
|
||||||
private static final long SIMULATION_INTERVAL_NS = 100_000_000L;
|
private static final long SIMULATION_INTERVAL_NS = 100_000_000L;
|
||||||
|
|
||||||
private static float globalEmissionMultiplier = 1.0f;
|
private static float globalEmissionMultiplier = 0.6f;
|
||||||
private static float globalAbsorptionMultiplier = 1.0f;
|
private static float globalAbsorptionMultiplier = 1.0f;
|
||||||
private static float globalDecay = 0.01f;
|
private static float globalDecay = 0.025f;
|
||||||
private static float diffusionRate = 0.1f;
|
private static float diffusionRate = 0.15f;
|
||||||
private static float upDiffusion = 0.9f;
|
private static float upDiffusion = 1.5f;
|
||||||
private static float downDiffusion = 1.1f;
|
private static float downDiffusion = 1.0f;
|
||||||
private static float horizontalDiffusion = 1.0f;
|
private static float horizontalDiffusion = 1.0f;
|
||||||
|
|
||||||
private FogSimulationManager() {
|
private FogSimulationManager() {
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ public final class FogSimulationShader {
|
|||||||
z
|
z
|
||||||
);
|
);
|
||||||
|
|
||||||
GL20.glUniform1f(currentZLocation, (float) z / FogWorldVolume.SIZE_Z);
|
GL20.glUniform1f(currentZLocation, (float) (z + 0.5f) / FogWorldVolume.SIZE_Z);
|
||||||
|
|
||||||
FullscreenQuad.draw();
|
FullscreenQuad.draw();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,21 +23,30 @@ public final class FogSystem {
|
|||||||
|
|
||||||
updateWindow();
|
updateWindow();
|
||||||
|
|
||||||
// Всегда копируем готовые данные в текстуру
|
// Блоки и density: загружаем готовые данные
|
||||||
FogChunkStreamer.uploadReadyData();
|
FogChunkStreamer.uploadReadyData();
|
||||||
|
|
||||||
// Всегда обрабатываем изменения блоков
|
// Блоки и density: обрабатываем изменения
|
||||||
FogChunkStreamer.processBlockChanges();
|
FogChunkStreamer.processBlockChanges();
|
||||||
|
|
||||||
|
// Освещение: загружаем готовые секции + инкрементальные обновления (render thread)
|
||||||
|
FogLightStreamer.uploadReadySections();
|
||||||
|
FogLightStreamer.processSectionUpdates();
|
||||||
|
|
||||||
FogSimulationManager.update();
|
FogSimulationManager.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void tick() {
|
public static void tick() {
|
||||||
|
// Блоки: initial load в client thread
|
||||||
FogChunkStreamer.processPending();
|
FogChunkStreamer.processPending();
|
||||||
|
|
||||||
|
// Освещение: читаем данные секций (client thread, без OpenGL)
|
||||||
|
FogLightStreamer.prepareInitialLoads();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initialize() {
|
private static void initialize() {
|
||||||
FogWorldVolume.init();
|
FogWorldVolume.init();
|
||||||
|
FogLightVolume.init();
|
||||||
FogSimulationManager.init();
|
FogSimulationManager.init();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
@@ -84,6 +93,16 @@ public final class FogSystem {
|
|||||||
ringChunkOffsetX,
|
ringChunkOffsetX,
|
||||||
ringChunkOffsetZ
|
ringChunkOffsetZ
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Освещение: помечаем новые секции для initial load
|
||||||
|
FogLightStreamer.update(
|
||||||
|
oldOriginChunkX,
|
||||||
|
oldOriginChunkZ,
|
||||||
|
originChunkX,
|
||||||
|
originChunkZ,
|
||||||
|
ringChunkOffsetX,
|
||||||
|
ringChunkOffsetZ
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int originChunkX() {
|
public static int originChunkX() {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class GameRendererMixin {
|
|||||||
CallbackInfo ci
|
CallbackInfo ci
|
||||||
) {
|
) {
|
||||||
PostProcessingManager.init();
|
PostProcessingManager.init();
|
||||||
PostProcessingManager.render();
|
PostProcessingManager.render(tickDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(
|
@Inject(
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package su.divan2000.veila.mixin.client;
|
||||||
|
|
||||||
|
import net.minecraft.util.math.ChunkSectionPos;
|
||||||
|
import net.minecraft.world.chunk.ChunkNibbleArray;
|
||||||
|
import net.minecraft.world.chunk.light.LightStorage;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import su.divan2000.veila.client.render.fog.FogLightStreamer;
|
||||||
|
|
||||||
|
@Mixin(LightStorage.class)
|
||||||
|
public class LightStorageMixin {
|
||||||
|
|
||||||
|
// Ловим инкрементальные обновления (когда блок меняет свет)
|
||||||
|
@Inject(
|
||||||
|
method = "set(JI)V",
|
||||||
|
at = @At("TAIL")
|
||||||
|
)
|
||||||
|
private void veila$onLightSet(long blockPos, int value, CallbackInfo ci) {
|
||||||
|
long sectionPos = ChunkSectionPos.fromBlockPos(blockPos);
|
||||||
|
FogLightStreamer.onSectionUpdated(sectionPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ловим загрузку секций из сохранения
|
||||||
|
@Inject(
|
||||||
|
method = "enqueueSectionData(JLnet/minecraft/world/chunk/ChunkNibbleArray;)V",
|
||||||
|
at = @At("TAIL")
|
||||||
|
)
|
||||||
|
private void veila$onEnqueueSection(long sectionPos, @Nullable ChunkNibbleArray array, CallbackInfo ci) {
|
||||||
|
// Если array == null - секция выгружается, не нужно загружать
|
||||||
|
if (array != null) {
|
||||||
|
FogLightStreamer.onSectionUpdated(sectionPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-8
@@ -7,8 +7,11 @@ uniform sampler3D FogVolume;
|
|||||||
uniform mat4 InverseProjection;
|
uniform mat4 InverseProjection;
|
||||||
uniform mat4 InverseView;
|
uniform mat4 InverseView;
|
||||||
|
|
||||||
|
uniform vec3 FogColor;
|
||||||
uniform vec3 CameraPosition;
|
uniform vec3 CameraPosition;
|
||||||
|
|
||||||
|
uniform float SkyBrightness;
|
||||||
|
|
||||||
uniform ivec2 FogOrigin;
|
uniform ivec2 FogOrigin;
|
||||||
uniform ivec2 RingBlockOffset;
|
uniform ivec2 RingBlockOffset;
|
||||||
|
|
||||||
@@ -179,16 +182,10 @@ void main()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fog = clamp(fog, 0.0, 1.0);
|
fog = clamp(fog, 0.0, 0.95);
|
||||||
|
|
||||||
vec3 fogColor = vec3(
|
|
||||||
0.75,
|
|
||||||
0.80,
|
|
||||||
0.90
|
|
||||||
);
|
|
||||||
|
|
||||||
FragColor = vec4(
|
FragColor = vec4(
|
||||||
mix(sceneColor.rgb, fogColor, fog),
|
mix(sceneColor.rgb, FogColor*SkyBrightness, fog),
|
||||||
1.0
|
1.0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
"client": [
|
"client": [
|
||||||
"GameRendererMixin",
|
"GameRendererMixin",
|
||||||
"MinecraftClientMixin",
|
"MinecraftClientMixin",
|
||||||
"WorldChunkMixin"
|
"WorldChunkMixin",
|
||||||
|
"LightStorageMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
accessWidener v2 named
|
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 skyLightProvider Lnet/minecraft/world/chunk/light/ChunkLightProvider;
|
||||||
|
|||||||
Reference in New Issue
Block a user