Compare commits

..
25 Commits
Author SHA1 Message Date
DIvan2000 e1349d207b Рефактор, вычисление объёмов вынесено в отдельный от render пакет 2026-08-11 11:30:38 +04:00
DIvan2000 2521366109 Более аккуратное восстановление состояния 2026-08-11 11:10:26 +04:00
DIvan2000 73045aec4d Фикс farlands 2026-08-10 04:54:03 +04:00
DIvan2000 eedd020064 откорректированны параметры 2026-08-10 04:29:26 +04:00
DIvan2000 4f6944668e откорректированны параметры 2026-08-09 21:30:29 +04:00
DIvan2000 e7a6e587ff Сглажены биомы 2026-08-09 20:35:57 +04:00
DIvan2000 9fa0370ad0 Улучшено поведение дымки 2026-08-09 20:22:36 +04:00
DIvan2000 3c04c6259e Тройная буфферизация density volume 2026-08-09 19:10:50 +04:00
DIvan2000 f08dee2677 Учёт времени, погоды и биома 2026-08-09 18:47:25 +04:00
DIvan2000 0faa8066af Симуляция распределена во времени 2026-08-08 17:15:14 +04:00
DIvan2000 cfc9bccb89 Улучшение безопасности PBO 2026-08-08 06:12:46 +04:00
DIvan2000 a4cdfb0c9c Хорошие оптимизации PBO 2026-08-08 05:02:55 +04:00
DIvan2000 e39c3d716d Сомнительные оптимизации 2026-08-08 04:22:51 +04:00
DIvan2000 1bcbcf6d60 Фикс карты освещения 2026-08-08 03:17:19 +04:00
DIvan2000 fd21d9edd8 Фикс дефолтного освещения 2026-08-06 22:58:18 +04:00
DIvan2000 e04f5a5ac8 фикс текущей воды 2026-08-05 19:56:38 +04:00
DIvan2000 4a057468e3 Реймарчинг в уменьшеном разрешении 2026-08-05 18:54:37 +04:00
DIvan2000 8ac636d114 Сделаны бескомпромиссные оптимизации 2026-08-05 08:41:29 +04:00
DIvan2000 76370f71e2 Освещённость вокселей учитывается 2026-08-04 20:15:35 +04:00
DIvan2000 8c1809a556 Попытка учесть освещённость вокселей 2026-08-04 04:05:20 +04:00
DIvan2000 ad2e5adcde PBO ускоряет очистку чанков 2026-08-02 04:39:52 +04:00
DIvan2000 efc345fa0b Симуляция работает 2026-08-02 03:16:51 +04:00
DIvan2000 41238fddb0 Теперь строится worldinfo для будующей симуляции 2026-08-01 18:06:41 +04:00
DIvan2000 eb497f257a Полностью корректный кольцевой буффер 2026-07-31 23:56:47 +04:00
DIvan2000 5e3f985025 Теперь 3д текстура обновляется корректно 2026-07-30 20:40:41 +04:00
36 changed files with 4769 additions and 927 deletions
+2
View File
@@ -20,6 +20,8 @@ loom {
sourceSet sourceSets.client sourceSet sourceSets.client
} }
} }
accessWidenerPath = file("src/main/resources/veila.accesswidener")
} }
fabricApi { fabricApi {
@@ -39,6 +39,7 @@ public final class FullscreenQuad {
vbo = GL15.glGenBuffers(); vbo = GL15.glGenBuffers();
int previous = glGetInteger(GL_VERTEX_ARRAY_BINDING); int previous = glGetInteger(GL_VERTEX_ARRAY_BINDING);
int previousArrayBuffer = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
GL30.glBindVertexArray(vao); GL30.glBindVertexArray(vao);
@@ -56,6 +57,7 @@ public final class FullscreenQuad {
); );
GL30.glBindVertexArray(previous); GL30.glBindVertexArray(previous);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, previousArrayBuffer);
} }
public static void draw() { public static void draw() {
@@ -13,249 +13,418 @@ 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.FogSystem; import su.divan2000.veila.client.simulation.FogLightVolume;
import su.divan2000.veila.client.render.fog.FogWorldVolume; import su.divan2000.veila.client.simulation.FogSimulationManager;
import su.divan2000.veila.client.simulation.FogSystem;
import su.divan2000.veila.client.simulation.FogWorldVolume;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
public class PostProcessingManager { public class PostProcessingManager {
private static GLProgram program; private static GLProgram raymarchProgram;
private static GLProgram compositeProgram;
private static GLProgram depthDownsampleProgram;
/* private static int dd_depthSamplerLocation;
* Матрицы private static int dd_screenSizeLocation;
*/
// Фреймбуфер для scaled raymarching
private static int scaledFBO;
private static int scaledTexture;
private static int scaledWidth;
private static int scaledHeight;
private static final float scale = 0.333f;
private static final Matrix4f projection = new Matrix4f(); private static final Matrix4f projection = new Matrix4f();
private static final Matrix4f inverseProjection = new Matrix4f(); private static final Matrix4f inverseProjection = new Matrix4f();
private static final FloatBuffer projectionBuffer = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer inverseProjectionBuffer = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer projectionBuffer = private static final int[] previousViewport = new int[4];
BufferUtils.createFloatBuffer(16); private static final int[] previousTexture2D = new int[5];
private static final int[] previousTexture3D = new int[5];
private static final FloatBuffer inverseProjectionBuffer =
BufferUtils.createFloatBuffer(16);
/* /*
* Uniform locations * Uniform locations - Raymarch Program
*/ */
private static int rm_projectionLocation;
private static int rm_inverseProjectionLocation;
private static int rm_depthSamplerLocation;
private static int rm_fogVolumeLocation;
private static int rm_prevFogVolumeLocation;
private static int rm_fogInterpolationLocation;
private static int rm_skyLightVolumeLocation;
private static int rm_blockLightVolumeLocation;
private static int rm_fogOriginLocation;
private static int rm_ringOffsetLocation;
private static int rm_cameraPositionLocation;
private static int rm_screenSizeLocation;
private static int rm_viewLocation;
private static int rm_inverseViewLocation;
private static int rm_fogColorLocation;
private static int rm_skyBrightnessLocation;
private static int projectionLocation; /*
private static int inverseProjectionLocation; * Uniform locations - Composite Program (JBU)
*/
private static int comp_diffuseSamplerLocation;
private static int comp_fogSamplerLocation;
private static int comp_depthSamplerLocation;
private static int comp_scaledDepthSamplerLocation;
private static int comp_screenSizeLocation;
private static int comp_scaledScreenSizeLocation;
private static int comp_inverseProjectionLocation;
private static int diffuseSamplerLocation; private static int scaledDepthTexture;
private static int depthSamplerLocation;
private static int cameraPositionLocation;
private static int screenSizeLocation;
private static final Matrix4f view = new Matrix4f(); private static final Matrix4f view = new Matrix4f();
private static final Matrix4f inverseView = new Matrix4f(); private static final Matrix4f inverseView = new Matrix4f();
private static final FloatBuffer viewBuffer = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer inverseViewBuffer = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer viewBuffer = private static float fogR, fogG, fogB;
BufferUtils.createFloatBuffer(16);
private static final FloatBuffer inverseViewBuffer = public static void setFogColor(float r, float g, float b) {
BufferUtils.createFloatBuffer(16); fogR = r;
fogG = g;
private static int viewLocation; fogB = b;
private static int inverseViewLocation; }
private static int fogVolumeLocation;
private static int fogOriginLocation;
private static int ringOffsetLocation;
public static void init() { public static void init() {
if (raymarchProgram != null)
if (program != null)
return; return;
GLShader vertex = new GLShader( setFogColor(0.75f, 0.80f, 0.90f);
// Инициализация raymarch программы
GLShader rmVertex = new GLShader(
GL20.GL_VERTEX_SHADER, GL20.GL_VERTEX_SHADER,
ResourceUtil.load("shaders/fullscreen.vert") ResourceUtil.load("shaders/fullscreen.vert")
); );
GLShader rmFragment = 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")
); );
raymarchProgram = new GLProgram(rmVertex, rmFragment);
program = new GLProgram(vertex, fragment); rm_projectionLocation = raymarchProgram.uniform("Projection");
rm_inverseProjectionLocation = raymarchProgram.uniform("InverseProjection");
rm_depthSamplerLocation = raymarchProgram.uniform("DepthSampler");
rm_fogVolumeLocation = raymarchProgram.uniform("FogVolume");
rm_prevFogVolumeLocation = raymarchProgram.uniform("PrevFogVolume");
rm_fogInterpolationLocation = raymarchProgram.uniform("FogInterpolation");
rm_skyLightVolumeLocation = raymarchProgram.uniform("SkyLightVolume");
rm_blockLightVolumeLocation = raymarchProgram.uniform("BlockLightVolume");
rm_fogOriginLocation = raymarchProgram.uniform("FogOrigin");
rm_ringOffsetLocation = raymarchProgram.uniform("RingBlockOffset");
rm_cameraPositionLocation = raymarchProgram.uniform("CameraPosition");
rm_screenSizeLocation = raymarchProgram.uniform("ScreenSize");
rm_viewLocation = raymarchProgram.uniform("View");
rm_inverseViewLocation = raymarchProgram.uniform("InverseView");
rm_fogColorLocation = raymarchProgram.uniform("FogColor");
rm_skyBrightnessLocation = raymarchProgram.uniform("SkyBrightness");
projectionLocation = program.uniform("Projection"); rmVertex.delete();
inverseProjectionLocation = program.uniform("InverseProjection"); rmFragment.delete();
diffuseSamplerLocation = program.uniform("DiffuseSampler"); // Инициализация composite программы с JBU
depthSamplerLocation = program.uniform("DepthSampler"); GLShader compVertex = new GLShader(
GL20.GL_VERTEX_SHADER,
ResourceUtil.load("shaders/fullscreen.vert")
);
GLShader compFragment = new GLShader(
GL20.GL_FRAGMENT_SHADER,
ResourceUtil.load("shaders/fog_composite.frag")
);
compositeProgram = new GLProgram(compVertex, compFragment);
fogVolumeLocation = program.uniform("FogVolume"); comp_diffuseSamplerLocation = compositeProgram.uniform("DiffuseSampler");
fogOriginLocation = program.uniform("FogOrigin"); comp_fogSamplerLocation = compositeProgram.uniform("FogSampler");
ringOffsetLocation = program.uniform("RingBlockOffset"); comp_depthSamplerLocation = compositeProgram.uniform("DepthSampler");
comp_scaledDepthSamplerLocation = compositeProgram.uniform("ScaledDepthSampler");
comp_screenSizeLocation = compositeProgram.uniform("ScreenSize");
comp_scaledScreenSizeLocation = compositeProgram.uniform("ScaledScreenSize");
comp_inverseProjectionLocation = compositeProgram.uniform("InverseProjection");
cameraPositionLocation = program.uniform("CameraPosition"); compVertex.delete();
screenSizeLocation = program.uniform("ScreenSize"); compFragment.delete();
viewLocation = program.uniform("View"); GLShader ddVertex = new GLShader(
inverseViewLocation = program.uniform("InverseView"); GL20.GL_VERTEX_SHADER,
ResourceUtil.load("shaders/fullscreen.vert")
);
GLShader ddFragment = new GLShader(
GL20.GL_FRAGMENT_SHADER,
ResourceUtil.load("shaders/depth_downsample.frag")
);
depthDownsampleProgram = new GLProgram(ddVertex, ddFragment);
dd_depthSamplerLocation = depthDownsampleProgram.uniform("DepthSampler");
dd_screenSizeLocation = depthDownsampleProgram.uniform("ScreenSize");
ddVertex.delete();
ddFragment.delete();
FullscreenQuad.init(); FullscreenQuad.init();
vertex.delete(); System.out.println("VEILA shaders compiled!");
fragment.delete();
System.out.println("VEILA shader compiled!");
} }
public static void render() { private static void initScaledFramebuffer(int width, int height) {
scaledWidth = (int)(width * scale);
scaledHeight = (int)(height * scale);
if (program == null) int previousFramebuffer = GL11.glGetInteger(GL30.GL_FRAMEBUFFER_BINDING);
int previousActiveTexture = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
GL13.glActiveTexture(GL13.GL_TEXTURE0);
int previousTexture0 = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
scaledFBO = GL30.glGenFramebuffers();
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, scaledFBO);
scaledTexture = GL11.glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledTexture);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_RGBA16F, scaledWidth, scaledHeight, 0, GL11.GL_RGBA, GL11.GL_FLOAT, 0);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, scaledTexture, 0);
// Half-res depth buffer
scaledDepthTexture = GL11.glGenTextures();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledDepthTexture);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_R32F, scaledWidth, scaledHeight, 0, GL11.GL_RED, GL11.GL_FLOAT, 0);
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_CLAMP_TO_EDGE);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT1, GL11.GL_TEXTURE_2D, scaledDepthTexture, 0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTexture0);
GL13.glActiveTexture(previousActiveTexture);
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, previousFramebuffer);
}
public static void render(float tickDelta) {
if (raymarchProgram == null)
return; return;
MinecraftClient client = MinecraftClient.getInstance(); MinecraftClient client = MinecraftClient.getInstance();
Framebuffer framebuffer = client.getFramebuffer(); if (client.world == null)
return;
int width = framebuffer.textureWidth; int previousProgram = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
int height = framebuffer.textureHeight; int previousActiveTexture = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
int previousDrawFramebuffer = GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING);
int previousReadFramebuffer = GL11.glGetInteger(GL30.GL_READ_FRAMEBUFFER_BINDING);
int previousDrawBuffer = GL11.glGetInteger(GL11.GL_DRAW_BUFFER);
GL11.glGetIntegerv(GL11.GL_VIEWPORT, previousViewport);
GL30.glBindFramebuffer( for (int unit = 0; unit <= 4; unit++) {
GL30.GL_READ_FRAMEBUFFER, GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
framebuffer.fbo previousTexture2D[unit] = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
); previousTexture3D[unit] = GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D);
}
GL13.glActiveTexture(previousActiveTexture);
DepthCopy.init(width, height); try {
DepthCopy.copy(width, height); Framebuffer framebuffer = client.getFramebuffer();
program.bind(); int width = framebuffer.textureWidth;
int height = framebuffer.textureHeight;
/* // Инициализация scaled фреймбуфера
* Diffuse if (scaledFBO == 0 || scaledWidth != (int)(width * scale) || scaledHeight != (int)(height * scale)) {
*/ if (scaledFBO != 0) {
GL30.glDeleteFramebuffers(scaledFBO);
GL11.glDeleteTextures(scaledTexture);
GL11.glDeleteTextures(scaledDepthTexture);
}
initScaledFramebuffer(width, height);
}
GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, framebuffer.fbo);
DepthCopy.init(width, height);
DepthCopy.copy(width, height);
updateProjectionMatrices();
renderDepthDownsample(width, height);
// === Этап 1: Raymarching в scaled разрешении ===
renderRaymarching(tickDelta, client);
// === Этап 2: Композитинг с JBU в полном разрешении ===
renderComposite(framebuffer, width, height);
GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
} finally {
for (int unit = 4; unit >= 0; unit--) {
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTexture2D[unit]);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, previousTexture3D[unit]);
}
GL13.glActiveTexture(previousActiveTexture);
GL20.glUseProgram(previousProgram);
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, previousDrawFramebuffer);
GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, previousReadFramebuffer);
GL11.glDrawBuffer(previousDrawBuffer);
GL11.glViewport(previousViewport[0], previousViewport[1], previousViewport[2], previousViewport[3]);
}
}
private static void renderDepthDownsample(int width, int height) {
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, scaledFBO);
// Настраиваем draw buffers для MRT
GL30.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT1);
GL11.glViewport(0, 0, scaledWidth, scaledHeight);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
depthDownsampleProgram.bind();
GL13.glActiveTexture(GL13.GL_TEXTURE0); GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture( GL11.glBindTexture(GL11.GL_TEXTURE_2D, DepthCopy.getTexture());
GL11.GL_TEXTURE_2D, GL20.glUniform1i(dd_depthSamplerLocation, 0);
framebuffer.getColorAttachment()
);
GL20.glUniform1i(diffuseSamplerLocation, 0);
/* GL20.glUniform2f(dd_screenSizeLocation, width, height);
* Depth
*/
GL13.glActiveTexture(GL13.GL_TEXTURE1);
GL11.glBindTexture(
GL11.GL_TEXTURE_2D,
DepthCopy.getTexture()
);
GL20.glUniform1i(depthSamplerLocation, 1);
GL13.glActiveTexture(GL13.GL_TEXTURE2);
GL11.glBindTexture(
GL12.GL_TEXTURE_3D,
FogWorldVolume.getTexture()
);
GL20.glUniform1i(
fogVolumeLocation,
2
);
GL20.glUniform2i(
fogOriginLocation,
FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE,
FogSystem.originChunkZ() * FogWorldVolume.CHUNK_SIZE
);
GL20.glUniform2i(
ringOffsetLocation,
FogSystem.ringChunkOffsetX() * FogWorldVolume.CHUNK_SIZE,
FogSystem.ringChunkOffsetZ() * FogWorldVolume.CHUNK_SIZE
);
/*
* Матрицы
*/
updateProjectionMatrices();
GL20.glUniformMatrix4fv(
projectionLocation,
false,
projectionBuffer
);
GL20.glUniformMatrix4fv(
inverseProjectionLocation,
false,
inverseProjectionBuffer
);
GL20.glUniformMatrix4fv(
viewLocation,
false,
viewBuffer
);
GL20.glUniformMatrix4fv(
inverseViewLocation,
false,
inverseViewBuffer
);
/*
* Камера
*/
Camera camera = client.gameRenderer.getCamera();
Vec3d pos = camera.getPos();
GL20.glUniform3f(
cameraPositionLocation,
(float) pos.x,
(float) pos.y,
(float) pos.z
);
/*
* Размер экрана
*/
GL20.glUniform2f(
screenSizeLocation,
width,
height
);
FullscreenQuad.draw(); FullscreenQuad.draw();
depthDownsampleProgram.unbind();
}
private static void renderRaymarching(float tickDelta, MinecraftClient client) {
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, scaledFBO);
// Настраиваем draw buffer для raymarching
GL30.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT0);
GL11.glViewport(0, 0, scaledWidth, scaledHeight);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
raymarchProgram.bind();
// Depth
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledDepthTexture);
GL20.glUniform1i(rm_depthSamplerLocation, 0);
// Fog Volume
GL13.glActiveTexture(GL13.GL_TEXTURE1);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogSimulationManager.getCurrentDensityTexture());
GL20.glUniform1i(rm_fogVolumeLocation, 1);
GL13.glActiveTexture(GL13.GL_TEXTURE2);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogSimulationManager.getPreviousDensityTexture());
GL20.glUniform1i(rm_prevFogVolumeLocation, 2);
// Light Volume
GL13.glActiveTexture(GL13.GL_TEXTURE3);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getSkyLightTexture());
GL20.glUniform1i(rm_skyLightVolumeLocation, 3);
GL13.glActiveTexture(GL13.GL_TEXTURE4);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getBlockLightTexture());
GL20.glUniform1i(rm_blockLightVolumeLocation, 4);
GL20.glUniform2i(rm_fogOriginLocation,
FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE,
FogSystem.originChunkZ() * FogWorldVolume.CHUNK_SIZE);
GL20.glUniform2i(rm_ringOffsetLocation,
FogSystem.ringChunkOffsetX() * FogWorldVolume.CHUNK_SIZE,
FogSystem.ringChunkOffsetZ() * FogWorldVolume.CHUNK_SIZE);
// Матрицы
GL20.glUniformMatrix4fv(rm_projectionLocation, false, projectionBuffer);
GL20.glUniformMatrix4fv(rm_inverseProjectionLocation, false, inverseProjectionBuffer);
GL20.glUniformMatrix4fv(rm_viewLocation, false, viewBuffer);
GL20.glUniformMatrix4fv(rm_inverseViewLocation, false, inverseViewBuffer);
// Камера
Camera camera = client.gameRenderer.getCamera();
Vec3d pos = camera.getPos();
float localCameraX = (float) (pos.x - FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE);
float localCameraY = (float) (pos.y - FogWorldVolume.MIN_Y);
float localCameraZ = (float) (pos.z - FogSystem.originChunkZ() * FogWorldVolume.CHUNK_SIZE);
GL20.glUniform3f(rm_cameraPositionLocation, localCameraX, localCameraY, localCameraZ);
// Размер экрана (scaled)
GL20.glUniform2f(rm_screenSizeLocation, scaledWidth, scaledHeight);
// Цвет тумана
GL20.glUniform3f(rm_fogColorLocation, fogR, fogG, fogB);
GL20.glUniform1f(rm_fogInterpolationLocation, FogSimulationManager.getFogInterpolationAlpha());
// Яркость неба
GL20.glUniform1f(rm_skyBrightnessLocation, client.world.getSkyBrightness(tickDelta));
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);
GL13.glActiveTexture(GL13.GL_TEXTURE1); GL13.glActiveTexture(GL13.GL_TEXTURE1);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GL13.glActiveTexture(GL13.GL_TEXTURE0); GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
program.unbind(); raymarchProgram.unbind();
}
GL30.glBindFramebuffer( private static void renderComposite(Framebuffer framebuffer, int width, int height) {
GL30.GL_READ_FRAMEBUFFER, GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer.fbo);
0 GL11.glViewport(0, 0, width, height);
);
compositeProgram.bind();
// Original scene (Diffuse)
GL13.glActiveTexture(GL13.GL_TEXTURE0); GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, framebuffer.getColorAttachment());
GL20.glUniform1i(comp_diffuseSamplerLocation, 0);
// Scaled fog results
GL13.glActiveTexture(GL13.GL_TEXTURE1);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledTexture);
GL20.glUniform1i(comp_fogSamplerLocation, 1);
// Full-res depth для JBU
GL13.glActiveTexture(GL13.GL_TEXTURE2);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, DepthCopy.getTexture());
GL20.glUniform1i(comp_depthSamplerLocation, 2);
// Half-res depth для JBU
GL13.glActiveTexture(GL13.GL_TEXTURE3);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledDepthTexture);
GL20.glUniform1i(comp_scaledDepthSamplerLocation, 3);
// Матрицы для линеаризации depth
GL20.glUniformMatrix4fv(comp_inverseProjectionLocation, false, inverseProjectionBuffer);
// Screen size
GL20.glUniform2f(comp_screenSizeLocation, width, height);
GL20.glUniform2f(comp_scaledScreenSizeLocation, scaledWidth, scaledHeight);
FullscreenQuad.draw();
// Очистка
// restore state once in render() finalizer
compositeProgram.unbind();
} }
private static void updateProjectionMatrices() { private static void updateProjectionMatrices() {
projection.set(RenderSystem.getProjectionMatrix()); projection.set(RenderSystem.getProjectionMatrix());
inverseProjection.set(projection).invert();
inverseProjection
.set(projection)
.invert();
projectionBuffer.clear(); projectionBuffer.clear();
projection.get(projectionBuffer); projection.get(projectionBuffer);
@@ -265,12 +434,8 @@ public class PostProcessingManager {
} }
public static void updateViewMatrix(Matrix4f matrix) { public static void updateViewMatrix(Matrix4f matrix) {
view.set(matrix); view.set(matrix);
inverseView.set(matrix).invert();
inverseView
.set(matrix)
.invert();
viewBuffer.clear(); viewBuffer.clear();
view.get(viewBuffer); view.get(viewBuffer);
@@ -278,5 +443,4 @@ public class PostProcessingManager {
inverseViewBuffer.clear(); inverseViewBuffer.clear();
inverseView.get(inverseViewBuffer); inverseView.get(inverseViewBuffer);
} }
} }
@@ -1,58 +0,0 @@
package su.divan2000.veila.client.render.fog;
import java.nio.FloatBuffer;
public final class FogChunkProvider {
static void fillChunk(
int chunkX,
int chunkZ,
FloatBuffer buffer
) {
float density =
hash(chunkX, chunkZ);
for (int z = 0;
z < FogWorldVolume.CHUNK_SIZE;
z++) {
for (int y = 0;
y < FogWorldVolume.SIZE_Y;
y++) {
int worldY =
FogWorldVolume.MIN_Y + y;
float value =
worldY < 70
? density
: 0.0f;
for (int x = 0;
x < FogWorldVolume.CHUNK_SIZE;
x++) {
buffer.put(value);
}
}
}
}
private static float hash(
int x,
int z
) {
int h = x * 73428767;
h ^= z * 912931;
h ^= h >> 13;
h *= 1274126177;
return ((h >>> 24) & 255)
/ 255.0f
* 0.08f;
}
}
@@ -1,182 +0,0 @@
package su.divan2000.veila.client.render.fog;
import java.nio.FloatBuffer;
import static su.divan2000.veila.client.render.fog.FogWorldVolume.CHUNK_SIZE;
import static su.divan2000.veila.client.render.fog.FogWorldVolume.SIZE_Y;
public final class FogChunkStreamer {
private FogChunkStreamer() {
}
public static void update(
int oldOriginChunkX,
int oldOriginChunkZ,
int newOriginChunkX,
int newOriginChunkZ
) {
int dx = newOriginChunkX - oldOriginChunkX;
int dz = newOriginChunkZ - oldOriginChunkZ;
/*
* Телепорт.
*/
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
refillAll(
newOriginChunkX,
newOriginChunkZ
);
return;
}
/*
* Новые колонки.
*/
if (dx > 0) {
for (int i = 0; i < dx; i++) {
uploadColumn(
newOriginChunkX +
FogWorldVolume.CHUNKS_X - dx + i,
newOriginChunkZ
);
}
}
if (dx < 0) {
for (int i = 0; i < -dx; i++) {
uploadColumn(
newOriginChunkX + i,
newOriginChunkZ
);
}
}
/*
* Новые строки.
*/
if (dz > 0) {
for (int i = 0; i < dz; i++) {
uploadRow(
newOriginChunkX,
newOriginChunkZ +
FogWorldVolume.CHUNKS_Z - dz + i
);
}
}
if (dz < 0) {
for (int i = 0; i < -dz; i++) {
uploadRow(
newOriginChunkX,
newOriginChunkZ + i
);
}
}
}
private static void refillAll(
int originChunkX,
int originChunkZ
) {
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
uploadChunk(
originChunkX + x,
originChunkZ + z
);
}
}
}
private static void uploadColumn(
int worldChunkX,
int originChunkZ
) {
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
uploadChunk(
worldChunkX,
originChunkZ + z
);
}
}
private static void uploadRow(
int originChunkX,
int worldChunkZ
) {
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
uploadChunk(
originChunkX + x,
worldChunkZ
);
}
}
private static void uploadChunk(
int worldChunkX,
int worldChunkZ
) {
FloatBuffer buffer =
FogWorldVolume.chunkBuffer();
FogChunkProvider.fillChunk(
worldChunkX,
worldChunkZ,
buffer
);
buffer.flip();
int physicalChunkX =
Math.floorMod(
worldChunkX,
FogWorldVolume.CHUNKS_X
);
int physicalChunkZ =
Math.floorMod(
worldChunkZ,
FogWorldVolume.CHUNKS_Z
);
System.out.println(
worldChunkX + " " +
worldChunkZ + " -> " +
physicalChunkX + " " +
physicalChunkZ
);
FogWorldVolume.uploadChunk(
physicalChunkX,
physicalChunkZ,
buffer
);
}
}
@@ -1,148 +0,0 @@
package su.divan2000.veila.client.render.fog;
import net.minecraft.client.MinecraftClient;
import java.nio.FloatBuffer;
public final class FogSystem {
private static boolean initialized;
private static int originChunkX;
private static int originChunkZ;
private static int ringChunkOffsetX;
private static int ringChunkOffsetZ;
private FogSystem() {
}
/*
* Вызывается в начале render()
*
* Только Render Thread.
*/
public static void beginRender() {
if (!initialized) {
initialize();
}
updateWindow();
}
/*
* Вызывается каждый client tick.
*
* Пока пусто.
*/
public static void tick() {
}
/*
* Инициализация всей системы.
*/
private static void initialize() {
FogWorldVolume.init();
initialized = true;
}
/*
* Пока просто заполняем весь объём
* тестовыми чанками.
*/
private static void fillTestVolume() {
for (int chunkZ = 0;
chunkZ < FogWorldVolume.CHUNKS_Z;
chunkZ++) {
for (int chunkX = 0;
chunkX < FogWorldVolume.CHUNKS_X;
chunkX++) {
FloatBuffer buffer =
FogWorldVolume.chunkBuffer();
FogChunkProvider.fillChunk(
originChunkX + chunkX,
originChunkZ + chunkZ,
buffer
);
buffer.flip();
FogWorldVolume.uploadChunk(
chunkX,
chunkZ,
buffer
);
}
}
}
private static void updateWindow() {
MinecraftClient client = MinecraftClient.getInstance();
if (client.world == null || client.player == null)
return;
int playerChunkX = client.player.getBlockX() >> 4;
int playerChunkZ = client.player.getBlockZ() >> 4;
int newOriginChunkX =
playerChunkX - FogWorldVolume.CHUNKS_X / 2;
int newOriginChunkZ =
playerChunkZ - FogWorldVolume.CHUNKS_Z / 2;
if (newOriginChunkX == originChunkX &&
newOriginChunkZ == originChunkZ)
return;
int oldOriginChunkX = originChunkX;
int oldOriginChunkZ = originChunkZ;
originChunkX = newOriginChunkX;
originChunkZ = newOriginChunkZ;
FogChunkStreamer.update(
oldOriginChunkX,
oldOriginChunkZ,
originChunkX,
originChunkZ
);
ringChunkOffsetX =
Math.floorMod(originChunkX,
FogWorldVolume.CHUNKS_X);
ringChunkOffsetZ =
Math.floorMod(originChunkZ,
FogWorldVolume.CHUNKS_Z);
}
public static int originChunkX() {
return originChunkX;
}
public static int originChunkZ() {
return originChunkZ;
}
public static int ringChunkOffsetX() {
return ringChunkOffsetX;
}
public static int ringChunkOffsetZ() {
return ringChunkOffsetZ;
}
}
@@ -1,217 +0,0 @@
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.GL13;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
public final class FogWorldVolume {
/*
* Размер всего объёма
*/
public static final int SIZE_X = 256;
public static final int SIZE_Z = 256;
public static final int MIN_Y = -64;
public static final int MAX_Y = 320;
public static final int SIZE_Y = MAX_Y - MIN_Y;
/*
* Размер одного чанка
*/
public static final int CHUNK_SIZE = 16;
public static final int CHUNKS_X = SIZE_X / CHUNK_SIZE;
public static final int CHUNKS_Z = SIZE_Z / CHUNK_SIZE;
/*
* OpenGL
*/
private static int texture = -1;
/*
* Буфер для одного чанка
*
* 16 × 384 × 16
*/
private static final FloatBuffer CHUNK_BUFFER =
BufferUtils.createFloatBuffer(
CHUNK_SIZE *
SIZE_Y *
CHUNK_SIZE
);
private FogWorldVolume() {
}
public static void init() {
if (texture != -1)
return;
texture = GL11.glGenTextures();
GL11.glBindTexture(
GL12.GL_TEXTURE_3D,
texture
);
GL30.glTexImage3D(
GL12.GL_TEXTURE_3D,
0,
GL30.GL_R16F,
SIZE_X,
SIZE_Y,
SIZE_Z,
0,
GL11.GL_RED,
GL11.GL_FLOAT,
(ByteBuffer) null
);
GL11.glTexParameteri(
GL12.GL_TEXTURE_3D,
GL11.GL_TEXTURE_MIN_FILTER,
GL11.GL_LINEAR
);
GL11.glTexParameteri(
GL12.GL_TEXTURE_3D,
GL11.GL_TEXTURE_MAG_FILTER,
GL11.GL_LINEAR
);
GL11.glTexParameteri(
GL12.GL_TEXTURE_3D,
GL12.GL_TEXTURE_WRAP_S,
GL12.GL_CLAMP_TO_EDGE
);
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_CLAMP_TO_EDGE
);
GL11.glBindTexture(
GL12.GL_TEXTURE_3D,
0
);
clear();
}
public static void clear() {
CHUNK_BUFFER.clear();
int size =
CHUNK_SIZE *
SIZE_Y *
CHUNK_SIZE;
for (int i = 0; i < size; i++)
CHUNK_BUFFER.put(0.0f);
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,
FloatBuffer data
) {
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
System.out.println(
data.remaining()
);
int error = GL11.glGetError();
if (error != GL11.GL_NO_ERROR) {
System.out.println("GL BEFORE = " + error);
}
GL30.glTexSubImage3D(
GL12.GL_TEXTURE_3D,
0,
textureChunkX * CHUNK_SIZE,
0,
textureChunkZ * CHUNK_SIZE,
CHUNK_SIZE,
SIZE_Y,
CHUNK_SIZE,
GL11.GL_RED,
GL11.GL_FLOAT,
data
);
error = GL11.glGetError();
if (error != GL11.GL_NO_ERROR) {
System.out.println("GL AFTER = " + error);
}
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
public static FloatBuffer chunkBuffer() {
CHUNK_BUFFER.clear();
return CHUNK_BUFFER;
}
public static int getTexture() {
return texture;
}
}
@@ -0,0 +1,368 @@
package su.divan2000.veila.client.simulation;
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,267 @@
package su.divan2000.veila.client.simulation;
import net.minecraft.client.MinecraftClient;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.math.BlockPos;
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;
// ИЗМЕНЕНО: 4×4 пикселя на чанк × 4 канала = 64 float
private static final int CHUNK_DATA_FLOATS =
FogBiomeTexture.CHUNK_DATA_FLOATS;
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;
// 4×4 пикселя на чанк (каждый покрывает 4×4 блока)
for (int pz = 0; pz < FogBiomeTexture.PIXELS_PER_CHUNK; pz++) {
for (int px = 0; px < FogBiomeTexture.PIXELS_PER_CHUNK; px++) {
// Суммируем параметры всех 16 блоков в этом пикселе
float sumEmission = 0;
float sumTargetDensity = 0;
float sumHumidity = 0;
float sumTimeSensitivity = 0;
for (int dz = 0; dz < FogBiomeTexture.PIXELS_PER_CHUNK; dz++) {
for (int dx = 0; dx < FogBiomeTexture.PIXELS_PER_CHUNK; dx++) {
int localX = px * FogBiomeTexture.PIXELS_PER_CHUNK + dx;
int localZ = pz * FogBiomeTexture.PIXELS_PER_CHUNK + dz;
int worldX = baseWorldX + localX;
int worldZ = baseWorldZ + localZ;
BlockPos samplePos = new BlockPos(worldX, topY, worldZ);
RegistryEntry<Biome> biomeEntry = client.world.getBiome(samplePos);
FogBiomeParams params = FogBiomeParams.forBiome(biomeEntry);
sumEmission += params.emissionMultiplier;
sumTargetDensity += params.targetDensityBase;
sumHumidity += params.humidity;
sumTimeSensitivity += params.timeSensitivity;
}
}
// Усредняем
float invCount = 1.0f / (FogBiomeTexture.PIXELS_PER_CHUNK * FogBiomeTexture.PIXELS_PER_CHUNK);
array[idx ] = sumEmission * invCount;
array[idx + 1] = sumTargetDensity * invCount;
array[idx + 2] = sumHumidity * invCount;
array[idx + 3] = sumTimeSensitivity * invCount;
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,173 @@
package su.divan2000.veila.client.simulation;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.*;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
public final class FogBiomeTexture {
// ИЗМЕНЕНО: 64×64 (один пиксель = 4×4 блока)
public static final int SIZE_X = FogWorldVolume.SIZE_X / 4; // 64
public static final int SIZE_Z = FogWorldVolume.SIZE_Z / 4; // 64
private static int texture = -1;
private static final int[] uploadPBOs = new int[3];
private static int uploadPBOIndex = 0;
// ИЗМЕНЕНО: 4×4 пикселя на чанк × 4 канала = 64 float
public static final int PIXELS_PER_CHUNK = 4;
public static final int CHUNK_DATA_FLOATS =
PIXELS_PER_CHUNK * PIXELS_PER_CHUNK * 4;
public 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];
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
);
// ИЗМЕНЕНО: GL_LINEAR для плавной интерполяции между биомами
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
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);
// 16×16 чанков (теперь каждый = 4×4 пикселя)
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 * PIXELS_PER_CHUNK,
z * PIXELS_PER_CHUNK,
PIXELS_PER_CHUNK,
PIXELS_PER_CHUNK,
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 * PIXELS_PER_CHUNK,
textureChunkZ * PIXELS_PER_CHUNK,
PIXELS_PER_CHUNK,
PIXELS_PER_CHUNK,
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] * PIXELS_PER_CHUNK;
int d = depthChunks[i] * PIXELS_PER_CHUNK;
int totalPixels = w * d;
FloatBuffer clearBuf = BufferUtils.createFloatBuffer(totalPixels * 4);
for (int j = 0; j < totalPixels; j++) {
clearBuf.put(EMPTY_PIXEL);
}
clearBuf.flip();
GL11.glTexSubImage2D(
GL11.GL_TEXTURE_2D, 0,
physX[i] * PIXELS_PER_CHUNK,
physZ[i] * PIXELS_PER_CHUNK,
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,49 @@
package su.divan2000.veila.client.simulation;
public record FogBlockProperties(
boolean solid,
int sign,
int magnitude
) {
public static final FogBlockProperties DEFAULT = new FogBlockProperties(false, 0, 0);
public FogBlockProperties {
if (sign < -1 || sign > 1) {
throw new IllegalArgumentException("sign must be -1, 0, or 1");
}
if (magnitude < 0 || magnitude > 63) {
throw new IllegalArgumentException("magnitude must be 0-63");
}
}
/**
* Упаковывает свойства в один байт:
* Бит 7: solid (1 = solid)
* Бит 6: sign (0 = source/neutral, 1 = absorber)
* Биты 0-5: magnitude (0-63)
*/
public byte pack() {
int packed = 0;
if (solid) {
packed |= 0b10000000;
}
if (sign < 0) {
packed |= 0b01000000;
}
packed |= (magnitude & 0b00111111);
return (byte) packed;
}
public static FogBlockProperties unpack(byte packed) {
boolean solid = (packed & 0b10000000) != 0;
boolean isAbsorber = (packed & 0b01000000) != 0;
int magnitude = packed & 0b00111111;
int sign = isAbsorber ? -1 : (magnitude > 0 ? 1 : 0);
return new FogBlockProperties(solid, sign, magnitude);
}
}
@@ -0,0 +1,108 @@
package su.divan2000.veila.client.simulation;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.registry.Registries;
import net.minecraft.registry.tag.BlockTags;
public final class FogBlockRegistry {
private static final byte[] BLOCK_CACHE;
private static final boolean[] BLOCK_CACHE_INITIALIZED;
// Предвычисленное значение для waterlogged блоков (вода)
private static final byte WATER_VALUE;
static {
int blockCount = Registries.BLOCK.size();
BLOCK_CACHE = new byte[blockCount];
BLOCK_CACHE_INITIALIZED = new boolean[blockCount];
// Waterlogged блок всегда содержит воду
WATER_VALUE = new FogBlockProperties(true, 1, 20).pack();
}
private FogBlockRegistry() {
}
public static byte getPackedProperties(BlockState state) {
// СНАЧАЛА проверяем конкретно воду (waterlogged или обычный блок воды)
Fluid fluid = state.getFluidState().getFluid();
if (fluid == Fluids.WATER || fluid == Fluids.FLOWING_WATER) {
return WATER_VALUE;
}
Block block = state.getBlock();
int id = Registries.BLOCK.getRawId(block);
if (BLOCK_CACHE_INITIALIZED[id]) {
return BLOCK_CACHE[id];
}
byte packed = computePackedProperties(state);
BLOCK_CACHE[id] = packed;
BLOCK_CACHE_INITIALIZED[id] = true;
return packed;
}
private static byte computePackedProperties(BlockState state) {
Block block = state.getBlock();
boolean solid = computeSolid(state);
// Источники тумана (sign = +1)
// WATER уже обработан в getPackedProperties
if (block == Blocks.GRASS || block == Blocks.TALL_GRASS || block == Blocks.FERN) {
return new FogBlockProperties(solid, 1, 50).pack();
}
if (block == Blocks.VINE) {
return new FogBlockProperties(solid, 1, 45).pack();
}
if (block == Blocks.SOUL_CAMPFIRE || block == Blocks.SOUL_FIRE) {
return new FogBlockProperties(solid, 1, 63).pack();
}
if (block == Blocks.SOUL_LANTERN) {
return new FogBlockProperties(solid, 1, 31).pack();
}
if (block == Blocks.SOUL_TORCH || block == Blocks.SOUL_WALL_TORCH) {
return new FogBlockProperties(solid, 1, 15).pack();
}
// Поглотители тумана (sign = -1)
if (block == Blocks.LAVA) {
return new FogBlockProperties(solid, -1, 50).pack();
}
if (block == Blocks.MAGMA_BLOCK) {
return new FogBlockProperties(solid, -1, 40).pack();
}
if (block == Blocks.FIRE) {
return new FogBlockProperties(solid, -1, 30).pack();
}
if (block == Blocks.CAMPFIRE) {
return new FogBlockProperties(solid, -1, 63).pack();
}
if (block == Blocks.TORCH || block == Blocks.WALL_TORCH) {
return new FogBlockProperties(solid, -1, 15).pack();
}
if (block == Blocks.LANTERN) {
return new FogBlockProperties(solid, -1, 25).pack();
}
// Нейтральные блоки (sign = 0)
return new FogBlockProperties(solid, 0, 0).pack();
}
@SuppressWarnings("deprecation")
private static boolean computeSolid(BlockState state) {
if (state.isAir()) return false;
// Waterlogged уже обработан в getPackedProperties
if (!state.getFluidState().isEmpty()) return true; // лава и другие жидкости
if (state.isIn(BlockTags.LEAVES)) return false;
if (state.getBlock() == Blocks.MANGROVE_ROOTS) return false;
return state.blocksMovement();
}
}
@@ -0,0 +1,88 @@
package su.divan2000.veila.client.simulation;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.chunk.PalettedContainer;
import java.util.Arrays;
public final class FogChunkProvider {
public static boolean isChunkLoaded(int chunkX, int chunkZ) {
MinecraftClient client = MinecraftClient.getInstance();
if (client.world == null) {
return false;
}
return client.world.getChunkManager().getWorldChunk(chunkX, chunkZ, false) != null;
}
public static boolean fillChunkIntoArray(int chunkX, int chunkZ, byte[] array) {
MinecraftClient client = MinecraftClient.getInstance();
if (!isChunkLoaded(chunkX, chunkZ)) return false;
WorldChunk chunk = client.world.getChunk(chunkX, chunkZ);
ChunkSection[] sections = chunk.getSectionArray();
int bottomY = client.world.getBottomY();
int topYExclusive = bottomY + sections.length * 16;
int index = 0;
byte airValue = FogBlockRegistry.getPackedProperties(Blocks.AIR.getDefaultState());
for (int z = 0; z < FogWorldVolume.CHUNK_SIZE; z++) {
int currentSectionIndex = -1;
ChunkSection currentSection = null;
PalettedContainer<BlockState> currentContainer = null;
boolean currentIsEmpty = false;
for (int y = 0; y < FogWorldVolume.SIZE_Y; y++) {
int worldY = FogWorldVolume.MIN_Y + y;
if (worldY < bottomY || worldY >= topYExclusive) {
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, airValue);
index += FogWorldVolume.CHUNK_SIZE;
continue;
}
int newSectionIndex = (worldY - bottomY) >> 4;
if (newSectionIndex != currentSectionIndex) {
currentSectionIndex = newSectionIndex;
currentSection = sections[currentSectionIndex];
if (currentSection != null) {
currentContainer = currentSection.getBlockStateContainer();
currentIsEmpty = currentSection.isEmpty();
} else {
currentContainer = null;
currentIsEmpty = true;
}
}
int localY = worldY & 15;
if (currentIsEmpty) {
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, airValue);
index += FogWorldVolume.CHUNK_SIZE;
continue;
}
int baseIndex = (localY << 8) | (z << 4);
for (int x = 0; x < FogWorldVolume.CHUNK_SIZE; x++) {
BlockState currentBlock = currentContainer.get(baseIndex | x);
array[index++] = FogBlockRegistry.getPackedProperties(currentBlock);
}
}
}
return true;
}
public static byte computePackedVoxelValue(BlockState state) {
return FogBlockRegistry.getPackedProperties(state);
}
}
@@ -0,0 +1,389 @@
package su.divan2000.veila.client.simulation;
import net.minecraft.block.BlockState;
import java.util.concurrent.atomic.AtomicLongArray;
public final class FogChunkStreamer {
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_SIZE =
FogWorldVolume.CHUNK_SIZE *
FogWorldVolume.SIZE_Y *
FogWorldVolume.CHUNK_SIZE;
private static final int MAX_CHUNKS_PER_TICK = 4;
private static final int MAX_READY_CHUNK_UPLOADS_PER_TICK = 4;
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();
// Pre-allocated массивы данных
private static final byte[][] preparedData = new byte[TOTAL_CHUNKS][CHUNK_DATA_SIZE];
private static final int[] preparedWorldChunkX = new int[TOTAL_CHUNKS];
private static final int[] preparedWorldChunkZ = new int[TOTAL_CHUNKS];
// Ring buffer для block change events (без аллокаций)
private static final int EVENT_BUFFER_SIZE = 256;
private static final int[] eventChunkX = new int[EVENT_BUFFER_SIZE];
private static final int[] eventChunkZ = new int[EVENT_BUFFER_SIZE];
private static final int[] eventBlockX = new int[EVENT_BUFFER_SIZE];
private static final int[] eventBlockY = new int[EVENT_BUFFER_SIZE];
private static final int[] eventBlockZ = new int[EVENT_BUFFER_SIZE];
private static final BlockState[] eventState = new BlockState[EVENT_BUFFER_SIZE];
private static volatile int eventWriteIndex = 0;
private static volatile int eventReadIndex = 0;
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;
// Pre-allocated temp массивы
private static final int[] tempXStarts = new int[2];
private static final int[] tempXCounts = new int[2];
private static final int[] tempZStarts = new int[2];
private static final int[] tempZCounts = new int[2];
private FogChunkStreamer() {
}
// ======================== Утилиты ========================
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 onBlockChanged(
int chunkX, int chunkZ,
int blockX, int blockY, int blockZ,
BlockState newState
) {
int nextWrite = (eventWriteIndex + 1) % EVENT_BUFFER_SIZE;
if (nextWrite == eventReadIndex) {
// Буфер переполнен — пропускаем событие (или можно расширить буфер)
return;
}
eventChunkX[eventWriteIndex] = chunkX;
eventChunkZ[eventWriteIndex] = chunkZ;
eventBlockX[eventWriteIndex] = blockX;
eventBlockY[eventWriteIndex] = blockY;
eventBlockZ[eventWriteIndex] = blockZ;
eventState[eventWriteIndex] = newState;
eventWriteIndex = nextWrite;
}
// ======================== Окно ========================
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;
}
regionCount = 0;
if (dx > 0) {
int worldX = newOriginChunkX + FogWorldVolume.CHUNKS_X - dx;
int worldZ = newOriginChunkZ;
collectRegions(worldX, worldZ, dx, FogWorldVolume.CHUNKS_Z);
markPendingRange(worldX, worldZ, dx, FogWorldVolume.CHUNKS_Z);
}
if (dx < 0) {
int worldX = newOriginChunkX;
int worldZ = newOriginChunkZ;
collectRegions(worldX, worldZ, -dx, FogWorldVolume.CHUNKS_Z);
markPendingRange(worldX, worldZ, -dx, FogWorldVolume.CHUNKS_Z);
}
if (dz > 0) {
int worldX = newOriginChunkX;
int worldZ = newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz;
collectRegions(worldX, worldZ, FogWorldVolume.CHUNKS_X, dz);
markPendingRange(worldX, worldZ, FogWorldVolume.CHUNKS_X, dz);
}
if (dz < 0) {
int worldX = newOriginChunkX;
int worldZ = newOriginChunkZ;
collectRegions(worldX, worldZ, FogWorldVolume.CHUNKS_X, -dz);
markPendingRange(worldX, worldZ, FogWorldVolume.CHUNKS_X, -dz);
}
if (regionCount > 0) {
FogWorldVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
FogDensityTextures.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
}
uploadReadyData();
}
}
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() {
regionCount = 1;
regionPhysX[0] = 0;
regionPhysZ[0] = 0;
regionWidth[0] = FogWorldVolume.CHUNKS_X;
regionDepth[0] = FogWorldVolume.CHUNKS_Z;
for (int i = 0; i < TOTAL_WORDS; i++) {
pendingMask.set(i, -1L);
readyMask.set(i, 0L);
}
int extraBits = TOTAL_CHUNKS & 63;
if (extraBits != 0) {
long mask = (1L << extraBits) - 1;
pendingMask.set(TOTAL_WORDS - 1, mask);
}
FogWorldVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
FogDensityTextures.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
}
private static void collectRegions(int worldX, int worldZ, int countX, int countZ) {
int xSegCount = splitIntoSegments(worldX, countX, FogWorldVolume.CHUNKS_X, tempXStarts, tempXCounts);
int zSegCount = splitIntoSegments(worldZ, countZ, FogWorldVolume.CHUNKS_Z, tempZStarts, tempZCounts);
for (int i = 0; i < xSegCount; i++) {
for (int j = 0; j < zSegCount; j++) {
if (regionCount >= MAX_REGIONS) return;
regionPhysX[regionCount] = tempXStarts[i];
regionPhysZ[regionCount] = tempZStarts[j];
regionWidth[regionCount] = tempXCounts[i];
regionDepth[regionCount] = tempZCounts[j];
regionCount++;
}
}
}
private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) {
if (count == 0) return 0;
int first = floorMod(start, dim);
int last = 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 processBlockChanges() {
int originX = currentOriginChunkX;
int originZ = currentOriginChunkZ;
int ringOffsetX = currentRingChunkOffsetX;
int ringOffsetZ = currentRingChunkOffsetZ;
while (eventReadIndex != eventWriteIndex) {
int chunkX = eventChunkX[eventReadIndex];
int chunkZ = eventChunkZ[eventReadIndex];
int blockX = eventBlockX[eventReadIndex];
int blockY = eventBlockY[eventReadIndex];
int blockZ = eventBlockZ[eventReadIndex];
BlockState newState = eventState[eventReadIndex];
eventReadIndex = (eventReadIndex + 1) % EVENT_BUFFER_SIZE;
int relativeX = chunkX - originX;
int relativeZ = chunkZ - originZ;
if (relativeX < 0 || relativeX >= FogWorldVolume.CHUNKS_X ||
relativeZ < 0 || relativeZ >= FogWorldVolume.CHUNKS_Z) {
continue;
}
int physicalX = floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X);
int physicalZ = floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z);
int localX = blockX & 15;
int localZ = blockZ & 15;
int yIndex = blockY - FogWorldVolume.MIN_Y;
if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) {
continue;
}
byte value = FogChunkProvider.computePackedVoxelValue(newState);
FogWorldVolume.updateSingleVoxel(physicalX, physicalZ, localX, yIndex, localZ, value);
}
}
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 (FogChunkProvider.fillChunkIntoArray(worldChunkX, worldChunkZ, preparedData[index])) {
preparedWorldChunkX[index] = worldChunkX;
preparedWorldChunkZ[index] = worldChunkZ;
setBit(readyMask, index);
clearBit(pendingMask, index);
processedCount++;
}
index++;
}
if (burstFrames > 0) burstFrames--;
}
}
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_READY_CHUNK_UPLOADS_PER_TICK + (burstFrames > 0 ? MAX_READY_CHUNK_UPLOADS_PER_TICK : 0);
FogWorldVolume.beginUpload();
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) {
FogWorldVolume.uploadChunkFromArrayNoBind(physicalX, physicalZ, preparedData[index]);
clearBit(readyMask, index);
processed++;
} else {
// Координаты изменились — помечаем как pending заново
clearBit(readyMask, index);
setBit(pendingMask, index);
}
index++;
}
FogWorldVolume.endUpload();
if (burstFrames > 0) burstFrames--;
}
}
public static void setBurst(int frames) {
burstFrames = Math.max(burstFrames, frames);
}
}
@@ -0,0 +1,187 @@
package su.divan2000.veila.client.simulation;
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;
public final class FogDensityTextures {
private static int textureA = -1;
private static int textureB = -1;
private static int textureC = -1;
private static int currentIndex = 0;
private static int previousIndex = 0;
private static int writeIndex = 1;
private static boolean hasPreviousState = false;
// PBO для быстрой очистки регионов (half float нули)
private static int clearPBO = -1;
private static final int MAX_CLEAR_SIZE = FogWorldVolume.SIZE_X * FogWorldVolume.SIZE_Y * FogWorldVolume.SIZE_Z;
private static final int MAX_CLEAR_SIZE_BYTES = MAX_CLEAR_SIZE * 2; // 2 байта на half float
private FogDensityTextures() {
}
private static void initClearPBO() {
if (clearPBO != -1) return;
clearPBO = GL15.glGenBuffers();
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO);
// Выделяем буфер в VRAM
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE_BYTES, GL15.GL_STATIC_DRAW);
// Заполняем нулями (half float 0.0 = 2 нулевых байта: 0x0000)
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
if (mapped != null) {
// Заполняем нулями - это даст half float 0.0
for (int i = 0; i < MAX_CLEAR_SIZE_BYTES; i++) {
mapped.put((byte) 0);
}
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
}
public static void init() {
if (textureA != -1) return;
// Инициализируем PBO ДО создания текстур
initClearPBO();
textureA = createDensityTexture();
textureB = createDensityTexture();
textureC = createDensityTexture();
currentIndex = 0;
previousIndex = 0;
writeIndex = 1;
hasPreviousState = false;
}
private static int createDensityTexture() {
int texture = GL11.glGenTextures();
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
GL30.glTexImage3D(
GL12.GL_TEXTURE_3D,
0,
GL30.GL_R16F,
FogWorldVolume.SIZE_X,
FogWorldVolume.SIZE_Y,
FogWorldVolume.SIZE_Z,
0,
GL11.GL_RED,
GL30.GL_HALF_FLOAT, // Исправлено: GL_HALF_FLOAT для 16-bit float
(ByteBuffer) null
);
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
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);
return texture;
}
public static void swap() {
previousIndex = currentIndex;
currentIndex = writeIndex;
// Выбираем свободный буфер для следующей записи
writeIndex = 0;
while (writeIndex == currentIndex || writeIndex == previousIndex) {
writeIndex++;
}
hasPreviousState = true;
}
public static int getReadTexture() {
return indexToTexture(currentIndex);
}
public static int getPreviousTexture() {
return indexToTexture(previousIndex);
}
public static int getWriteTexture() {
return indexToTexture(writeIndex);
}
public static boolean hasPreviousState() {
return hasPreviousState;
}
private static int indexToTexture(int index) {
switch (index) {
case 0:
return textureA;
case 1:
return textureB;
case 2:
return textureC;
default:
throw new IllegalStateException("Unexpected fog density texture index: " + index);
}
}
/**
* Очищает несколько непрерывных физических регионов в обеих density текстурах через PBO.
*/
public static void clearRegions(
int[] physX, int[] physZ,
int[] widthChunks, int[] depthChunks,
int count
) {
clearRegionsInTexture(textureA, physX, physZ, widthChunks, depthChunks, count);
clearRegionsInTexture(textureB, physX, physZ, widthChunks, depthChunks, count);
clearRegionsInTexture(textureC, physX, physZ, widthChunks, depthChunks, count);
}
private static void clearRegionsInTexture(
int texture,
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] * FogWorldVolume.CHUNK_SIZE;
int h = FogWorldVolume.SIZE_Y;
int d = depthChunks[i] * FogWorldVolume.CHUNK_SIZE;
// 0L = offset 0 внутри PBO (начало буфера)
GL12.glTexSubImage3D(
GL12.GL_TEXTURE_3D,
0,
physX[i] * FogWorldVolume.CHUNK_SIZE,
0,
physZ[i] * FogWorldVolume.CHUNK_SIZE,
w, h, d,
GL11.GL_RED,
GL30.GL_HALF_FLOAT,
0L // ← long offset вместо ByteBuffer
);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
}
@@ -0,0 +1,122 @@
package su.divan2000.veila.client.simulation;
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;
private static float rainMultiplier = 0.0f;
private static float heightFadeMax = 90.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;
float timeOfDayNorm = timeOfDay/24000.0F;
// === 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. Случайное значение для "туманного дня" ===
double dayNoiseValue = dayNoise.sample(totalDays * 0.1, 100);
float rawFogDayRandom = (float)(0.5 + dayNoiseValue * 0.5);
// === 4. HeightFadeMax (110 +15/-20 от дневного шума) ===
double heightNoise = dayNoise.sample(totalDays * 0.5, 200)+dayNoise.sample(timeOfDayNorm*2, totalDays)*0.25;
float rawHeightFadeMax = 110.0f + (float)(heightNoise * 15.0);
rawHeightFadeMax = Math.max(90.0f, Math.min(125.0f, rawHeightFadeMax));
// === 5. Сглаживание ===
float lerpSpeed = 0.08f;
smoothEmission = lerp(smoothEmission, rawEmission, lerpSpeed);
smoothTargetDensity = lerp(smoothTargetDensity, rawTargetDensity, lerpSpeed * 2.0f);
smoothRain = lerp(smoothRain, rainFactor, lerpSpeed * 2.0f);
fogDayRandom = rawFogDayRandom;
heightFadeMax = rawHeightFadeMax + rain*100; // Не сглаживаем, постоянен в течение дня
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; }
public static float getHeightFadeMax() { return heightFadeMax; }
}
@@ -0,0 +1,91 @@
package su.divan2000.veila.client.simulation;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.math.ChunkSectionPos;
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 = 16 * 16 * 16;
// Pre-allocated массивы для каждого индекса. Инициализируются лениво.
private static byte[][] blockLightData;
private static byte[][] skyLightData;
private FogLightProvider() {
}
public static void init() {
if (blockLightData != null) return;
int total = FogLightStreamer.TOTAL_SECTIONS;
blockLightData = new byte[total][SECTION_DATA_SIZE];
skyLightData = new byte[total][SECTION_DATA_SIZE];
}
/**
* Читает block light напрямую в pre-allocated массив.
* Возвращает true если данные успешно прочитаны.
*/
public static boolean fillBlockLightIntoArray(int sectionX, int sectionY, int sectionZ, int index) {
MinecraftClient client = MinecraftClient.getInstance();
if (client.world == null) return false;
LightingProvider provider = client.world.getLightingProvider();
ChunkLightProvider<?, ?> blockProvider = provider.blockLightProvider;
if (blockProvider == null) return false;
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos);
if (blockArray == null) return false;
byte[] result = blockLightData[index];
int idx = 0;
for (int localZ = 0; localZ < 16; localZ++) {
for (int localY = 0; localY < 16; localY++) {
for (int localX = 0; localX < 16; localX++) {
result[idx++] = (byte) (blockArray.get(localX, localY, localZ) * 17);
}
}
}
return true;
}
/**
* Читает sky light напрямую в pre-allocated массив.
*/
public static boolean fillSkyLightIntoArray(int sectionX, int sectionY, int sectionZ, int index) {
MinecraftClient client = MinecraftClient.getInstance();
if (client.world == null) return false;
LightingProvider provider = client.world.getLightingProvider();
ChunkLightProvider<?, ?> skyProvider = provider.skyLightProvider;
if (skyProvider == null) return false;
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
ChunkNibbleArray skyArray = skyProvider.getLightSection(sectionPos);
if (skyArray == null) return false;
byte[] result = skyLightData[index];
int idx = 0;
for (int localZ = 0; localZ < 16; localZ++) {
for (int localY = 0; localY < 16; localY++) {
for (int localX = 0; localX < 16; localX++) {
result[idx++] = (byte) (skyArray.get(localX, localY, localZ) * 17);
}
}
}
return true;
}
public static byte[] getBlockLightData(int index) {
return blockLightData[index];
}
public static byte[] getSkyLightData(int index) {
return skyLightData[index];
}
}
@@ -0,0 +1,477 @@
package su.divan2000.veila.client.simulation;
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;
static final int TOTAL_SECTIONS =
FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z * SECTIONS_PER_CHUNK_Y;
private static final int TOTAL_WORDS = (TOTAL_SECTIONS + 63) / 64;
private static final AtomicLongArray pendingBlockMask = new AtomicLongArray(TOTAL_WORDS);
private static final AtomicLongArray pendingSkyMask = new AtomicLongArray(TOTAL_WORDS);
private static final AtomicLongArray readyBlockMask = new AtomicLongArray(TOTAL_WORDS);
private static final AtomicLongArray readySkyMask = new AtomicLongArray(TOTAL_WORDS);
private static final AtomicLongArray initialBlockMask = new AtomicLongArray(TOTAL_WORDS);
private static final AtomicLongArray initialSkyMask = new AtomicLongArray(TOTAL_WORDS);
private static final Object sharedStateLock = new Object();
// Flat массивы для мировых координат
private static final int[] preparedBlockWorldX = new int[TOTAL_SECTIONS];
private static final int[] preparedBlockWorldY = new int[TOTAL_SECTIONS];
private static final int[] preparedBlockWorldZ = new int[TOTAL_SECTIONS];
private static final int[] preparedSkyWorldX = new int[TOTAL_SECTIONS];
private static final int[] preparedSkyWorldY = new int[TOTAL_SECTIONS];
private static final int[] preparedSkyWorldZ = new int[TOTAL_SECTIONS];
// Более равномерное распределение: меньше initial за раз, больше incremental
private static final int MAX_INITIAL_SECTIONS_PER_TICK = 16;
private static final int MAX_PENDING_SECTIONS_PER_TICK = 16;
private static final int MAX_READY_BLOCK_SECTIONS_PER_TICK = 8;
private static final int MAX_READY_SKY_SECTIONS_PER_TICK = 8;
private static volatile int burstFrames = 0;
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;
// Pre-allocated temp массивы
private static final int[] tempXStarts = new int[2];
private static final int[] tempXCounts = new int[2];
private static final int[] tempZStarts = new int[2];
private static final int[] tempZCounts = new int[2];
private FogLightStreamer() {
}
public static void init() {
FogLightProvider.init();
}
// ======================== Утилиты ========================
private static int floorMod(int x, int y) {
int r = x % y;
return r < 0 ? r + y : r;
}
private static int sectionToLocalIndex(int sectionX, int sectionY, int sectionZ) {
int relativeX = sectionX - currentOriginChunkX;
int relativeZ = sectionZ - 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 = floorMod(relativeX + currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
int physicalZ = floorMod(relativeZ + currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
return (localSectionY * FogLightVolume.CHUNKS_Z * FogLightVolume.CHUNKS_X)
+ (physicalZ * FogLightVolume.CHUNKS_X)
+ physicalX;
}
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;
}
}
/**
* Простой последовательный скан — работает быстрее на практике
* благодаря branch prediction и JIT-оптимизациям.
*/
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 onBlockLightUpdated(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) {
clearBit(readyBlockMask, index);
setBit(pendingBlockMask, index);
}
}
public static void onSkyLightUpdated(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) {
clearBit(readySkyMask, index);
setBit(pendingSkyMask, index);
}
}
// ======================== Окно ========================
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) >= FogLightVolume.CHUNKS_X || Math.abs(dz) >= FogLightVolume.CHUNKS_Z) {
markAllPending();
return;
}
regionCount = 0;
if (dx > 0) {
int worldX = newOriginChunkX + FogLightVolume.CHUNKS_X - dx;
collectRegions(worldX, newOriginChunkZ, dx, FogLightVolume.CHUNKS_Z);
addInitialLoadForRange(worldX, newOriginChunkZ, dx, FogLightVolume.CHUNKS_Z);
}
if (dx < 0) {
collectRegions(newOriginChunkX, newOriginChunkZ, -dx, FogLightVolume.CHUNKS_Z);
addInitialLoadForRange(newOriginChunkX, newOriginChunkZ, -dx, FogLightVolume.CHUNKS_Z);
}
if (dz > 0) {
int worldZ = newOriginChunkZ + FogLightVolume.CHUNKS_Z - dz;
collectRegions(newOriginChunkX, worldZ, FogLightVolume.CHUNKS_X, dz);
addInitialLoadForRange(newOriginChunkX, worldZ, FogLightVolume.CHUNKS_X, dz);
}
if (dz < 0) {
collectRegions(newOriginChunkX, newOriginChunkZ, FogLightVolume.CHUNKS_X, -dz);
addInitialLoadForRange(newOriginChunkX, newOriginChunkZ, 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) {
int minY = FogWorldVolume.MIN_Y >> 4;
for (int i = 0; i < countX; i++) {
int wx = worldX + i;
for (int j = 0; j < countZ; j++) {
int wz = worldZ + j;
for (int sectionY = 0; sectionY < SECTIONS_PER_CHUNK_Y; sectionY++) {
int index = sectionToLocalIndex(wx, sectionY + minY, wz);
if (index >= 0) {
setBit(initialBlockMask, index);
setBit(initialSkyMask, index);
}
}
}
}
}
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);
for (int i = 0; i < TOTAL_WORDS; i++) {
pendingBlockMask.set(i, 0L);
pendingSkyMask.set(i, 0L);
readyBlockMask.set(i, 0L);
readySkyMask.set(i, 0L);
initialBlockMask.set(i, -1L);
initialSkyMask.set(i, -1L);
}
int extraBits = TOTAL_SECTIONS & 63;
if (extraBits != 0) {
long mask = (1L << extraBits) - 1;
initialBlockMask.set(TOTAL_WORDS - 1, mask);
initialSkyMask.set(TOTAL_WORDS - 1, mask);
}
}
private static void collectRegions(int worldX, int worldZ, int countX, int countZ) {
int xSegCount = splitIntoSegments(worldX, countX, FogLightVolume.CHUNKS_X, tempXStarts, tempXCounts);
int zSegCount = splitIntoSegments(worldZ, countZ, FogLightVolume.CHUNKS_Z, tempZStarts, tempZCounts);
for (int i = 0; i < xSegCount; i++) {
for (int j = 0; j < zSegCount; j++) {
if (regionCount >= MAX_REGIONS) return;
regionPhysX[regionCount] = tempXStarts[i];
regionPhysZ[regionCount] = tempZStarts[j];
regionWidth[regionCount] = tempXCounts[i];
regionDepth[regionCount] = tempZCounts[j];
regionCount++;
}
}
}
private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) {
if (count == 0) return 0;
int first = floorMod(start, dim);
int last = 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;
}
}
// ======================== Подготовка и загрузка ========================
/**
* Более равномерное распределение нагрузки:
* обрабатываем и initial, и pending параллельно, чтобы избежать пиков.
*/
public static void prepareAllSections() {
synchronized (sharedStateLock) {
int processedBlock = 0;
int processedSky = 0;
// Чередование: немного initial block, немного pending block,
// немного initial sky, немного pending sky
int index = 0;
int initialLimit = MAX_INITIAL_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_INITIAL_SECTIONS_PER_TICK : 0);
while (processedBlock < initialLimit) {
index = nextSetBit(initialBlockMask, index);
if (index < 0) break;
if (prepareBlockLight(index)) {
clearBit(initialBlockMask, index);
processedBlock++;
}
index++;
}
index = 0;
int pendingLimit = MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_PENDING_SECTIONS_PER_TICK : 0);
while (processedBlock < pendingLimit) {
index = nextSetBit(pendingBlockMask, index);
if (index < 0) break;
if (prepareBlockLight(index)) {
clearBit(pendingBlockMask, index);
processedBlock++;
}
index++;
}
index = 0;
int initialSkyLimit = MAX_INITIAL_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_INITIAL_SECTIONS_PER_TICK : 0);
while (processedSky < initialSkyLimit) {
index = nextSetBit(initialSkyMask, index);
if (index < 0) break;
if (prepareSkyLight(index)) {
clearBit(initialSkyMask, index);
processedSky++;
}
index++;
}
index = 0;
int pendingSkyLimit = MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_PENDING_SECTIONS_PER_TICK : 0);
while (processedSky < pendingSkyLimit) {
index = nextSetBit(pendingSkyMask, index);
if (index < 0) break;
if (prepareSkyLight(index)) {
clearBit(pendingSkyMask, index);
processedSky++;
}
index++;
}
if (burstFrames > 0) burstFrames--;
}
}
public static void uploadReadySections() {
synchronized (sharedStateLock) {
int processedBlock = 0;
int processedSky = 0;
int index = 0;
FogLightVolume.beginUpload(FogLightVolume.getBlockLightTexture());
int blockLimit = MAX_READY_BLOCK_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_READY_BLOCK_SECTIONS_PER_TICK : 0);
while (processedBlock < blockLimit) {
index = nextSetBit(readyBlockMask, index);
if (index < 0) break;
if (uploadBlockLight(index)) {
processedBlock++;
}
index++;
}
FogLightVolume.endUpload();
index = 0;
FogLightVolume.beginUpload(FogLightVolume.getSkyLightTexture());
int skyLimit = MAX_READY_SKY_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_READY_SKY_SECTIONS_PER_TICK : 0);
while (processedSky < skyLimit) {
index = nextSetBit(readySkyMask, index);
if (index < 0) break;
if (uploadSkyLight(index)) {
processedSky++;
}
index++;
}
FogLightVolume.endUpload();
if (burstFrames > 0) burstFrames--;
}
}
public static void setBurst(int frames) {
burstFrames = Math.max(burstFrames, frames);
}
private static boolean prepareBlockLight(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 + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
int sectionZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
if (!FogLightProvider.fillBlockLightIntoArray(sectionX, sectionY, sectionZ, index)) {
return false;
}
preparedBlockWorldX[index] = sectionX;
preparedBlockWorldY[index] = sectionY;
preparedBlockWorldZ[index] = sectionZ;
setBit(readyBlockMask, index);
return true;
}
private static boolean prepareSkyLight(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 + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
int sectionZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
if (!FogLightProvider.fillSkyLightIntoArray(sectionX, sectionY, sectionZ, index)) {
return false;
}
preparedSkyWorldX[index] = sectionX;
preparedSkyWorldY[index] = sectionY;
preparedSkyWorldZ[index] = sectionZ;
setBit(readySkyMask, index);
return true;
}
private static boolean uploadBlockLight(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 currentWorldX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
int currentWorldZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
if (preparedBlockWorldX[index] != currentWorldX ||
preparedBlockWorldY[index] != currentWorldY ||
preparedBlockWorldZ[index] != currentWorldZ) {
clearBit(readyBlockMask, index);
setBit(pendingBlockMask, index);
return false;
}
FogLightVolume.uploadBlockLightSectionNoBind(physicalX, localSectionY, physicalZ,
FogLightProvider.getBlockLightData(index));
clearBit(readyBlockMask, index);
return true;
}
private static boolean uploadSkyLight(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 currentWorldX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
int currentWorldZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
if (preparedSkyWorldX[index] != currentWorldX ||
preparedSkyWorldY[index] != currentWorldY ||
preparedSkyWorldZ[index] != currentWorldZ) {
clearBit(readySkyMask, index);
setBit(pendingSkyMask, index);
return false;
}
FogLightVolume.uploadSkyLightSectionNoBind(physicalX, localSectionY, physicalZ,
FogLightProvider.getSkyLightData(index));
clearBit(readySkyMask, index);
return true;
}
}
@@ -0,0 +1,350 @@
package su.divan2000.veila.client.simulation;
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;
// No GL43 usage: keep compatibility with OpenGL 3.2
import java.nio.ByteBuffer;
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 blockLightTexture = -1;
private static int skyLightTexture = -1;
private static final byte DEFAULT_BLOCK_LIGHT = (byte) 0;
private static final byte DEFAULT_SKY_LIGHT = (byte) 255;
private static final byte[] DEFAULT_BLOCK_ARRAY;
private static final byte[] DEFAULT_SKY_ARRAY;
static {
int sectionVoxels = CHUNK_SIZE * SIZE_Y * CHUNK_SIZE;
DEFAULT_BLOCK_ARRAY = new byte[sectionVoxels];
DEFAULT_SKY_ARRAY = new byte[sectionVoxels];
java.util.Arrays.fill(DEFAULT_BLOCK_ARRAY, DEFAULT_BLOCK_LIGHT);
java.util.Arrays.fill(DEFAULT_SKY_ARRAY, DEFAULT_SKY_LIGHT);
}
private static final ByteBuffer CHUNK_BUFFER =
BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE);
private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
// PBO для быстрой очистки
private static int clearBlockPBO = -1;
private static int clearSkyPBO = -1;
// PBO для загрузки секций
private static int uploadPBO = -1;
// Буферы для загрузки секций
private static final ByteBuffer BLOCK_SECTION_UPLOAD_BUFFER =
BufferUtils.createByteBuffer(FogLightStreamer.SECTION_SIZE
* FogLightStreamer.SECTION_SIZE
* FogLightStreamer.SECTION_SIZE);
private static final ByteBuffer SKY_SECTION_UPLOAD_BUFFER =
BufferUtils.createByteBuffer(FogLightStreamer.SECTION_SIZE
* FogLightStreamer.SECTION_SIZE
* FogLightStreamer.SECTION_SIZE);
private FogLightVolume() {
}
private static void initClearPBOs() {
if (clearBlockPBO != -1) return;
// Block light PBO
clearBlockPBO = GL15.glGenBuffers();
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearBlockPBO);
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 < SIZE_X * SIZE_Y * SIZE_Z; i++) {
mapped.put(DEFAULT_BLOCK_LIGHT);
}
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
}
// Sky light PBO
clearSkyPBO = GL15.glGenBuffers();
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearSkyPBO);
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE, GL15.GL_STATIC_DRAW);
mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
if (mapped != null) {
for (int i = 0; i < SIZE_X * SIZE_Y * SIZE_Z; i++) {
mapped.put(DEFAULT_SKY_LIGHT);
}
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
}
public static void init() {
if (blockLightTexture != -1) return;
initClearPBOs();
initUploadPBO();
// Block light texture
blockLightTexture = GL11.glGenTextures();
GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture);
GL30.glTexImage3D(
GL12.GL_TEXTURE_3D, 0, GL30.GL_R8,
SIZE_X, SIZE_Y, SIZE_Z, 0,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null
);
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
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);
// Sky light texture
skyLightTexture = GL11.glGenTextures();
GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture);
GL30.glTexImage3D(
GL12.GL_TEXTURE_3D, 0, GL30.GL_R8,
SIZE_X, SIZE_Y, SIZE_Z, 0,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null
);
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
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() {
// Clear block light texture
CHUNK_BUFFER.clear();
CHUNK_BUFFER.put(DEFAULT_BLOCK_ARRAY);
CHUNK_BUFFER.flip();
GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture);
for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) {
for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) {
uploadChunkInternal(blockLightTexture, chunkX, chunkZ, CHUNK_BUFFER);
CHUNK_BUFFER.rewind();
}
}
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
// Clear sky light texture
CHUNK_BUFFER.clear();
CHUNK_BUFFER.put(DEFAULT_SKY_ARRAY);
CHUNK_BUFFER.flip();
GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture);
for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) {
for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) {
uploadChunkInternal(skyLightTexture, chunkX, chunkZ, CHUNK_BUFFER);
CHUNK_BUFFER.rewind();
}
}
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
private static void uploadChunkInternal(int texture, int textureChunkX, int textureChunkZ, ByteBuffer data) {
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,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, data
);
}
public static void uploadBlockLightSection(
int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data
) {
BLOCK_SECTION_UPLOAD_BUFFER.clear();
BLOCK_SECTION_UPLOAD_BUFFER.put(data);
BLOCK_SECTION_UPLOAD_BUFFER.flip();
beginUpload(blockLightTexture);
GL30.glTexSubImage3D(
GL12.GL_TEXTURE_3D, 0,
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
16, 16, 16,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, BLOCK_SECTION_UPLOAD_BUFFER
);
endUpload();
}
public static void uploadSkyLightSection(
int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data
) {
SKY_SECTION_UPLOAD_BUFFER.clear();
SKY_SECTION_UPLOAD_BUFFER.put(data);
SKY_SECTION_UPLOAD_BUFFER.flip();
beginUpload(skyLightTexture);
GL30.glTexSubImage3D(
GL12.GL_TEXTURE_3D, 0,
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
16, 16, 16,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SKY_SECTION_UPLOAD_BUFFER
);
endUpload();
}
public static void uploadBlockLightSectionNoBind(
int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data
) {
// Try to upload via PBO to avoid CPU->GPU stalls
if (uploadPBO != -1) {
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO);
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, BLOCK_SECTION_UPLOAD_BUFFER.capacity(), GL15.GL_STREAM_DRAW);
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
if (mapped != null) {
mapped.put(data);
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
GL30.glTexSubImage3D(
GL12.GL_TEXTURE_3D, 0,
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
16, 16, 16,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L
);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
} else {
BLOCK_SECTION_UPLOAD_BUFFER.clear();
BLOCK_SECTION_UPLOAD_BUFFER.put(data);
BLOCK_SECTION_UPLOAD_BUFFER.flip();
GL30.glTexSubImage3D(
GL12.GL_TEXTURE_3D, 0,
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
16, 16, 16,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, BLOCK_SECTION_UPLOAD_BUFFER
);
}
}
public static void uploadSkyLightSectionNoBind(
int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data
) {
if (uploadPBO != -1) {
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO);
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, SKY_SECTION_UPLOAD_BUFFER.capacity(), GL15.GL_STREAM_DRAW);
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
if (mapped != null) {
mapped.put(data);
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
GL30.glTexSubImage3D(
GL12.GL_TEXTURE_3D, 0,
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
16, 16, 16,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L
);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
} else {
SKY_SECTION_UPLOAD_BUFFER.clear();
SKY_SECTION_UPLOAD_BUFFER.put(data);
SKY_SECTION_UPLOAD_BUFFER.flip();
GL30.glTexSubImage3D(
GL12.GL_TEXTURE_3D, 0,
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
16, 16, 16,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SKY_SECTION_UPLOAD_BUFFER
);
}
}
public static void beginUpload(int texture) {
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);
}
public static void endUpload() {
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
public static int getBlockLightTexture() {
return blockLightTexture;
}
public static int getSkyLightTexture() {
return skyLightTexture;
}
public static void clearRegions(
int[] physX, int[] physZ,
int[] widthChunks, int[] depthChunks,
int count
) {
// Use PBO-based clears for OpenGL 3.2 compatibility
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);
// Clear block light
GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture);
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearBlockPBO);
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,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L
);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
// Clear sky light
GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture);
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearSkyPBO);
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,
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L
);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
private static void initUploadPBO() {
if (uploadPBO != -1) return;
uploadPBO = GL15.glGenBuffers();
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO);
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, BLOCK_SECTION_UPLOAD_BUFFER.capacity(), GL15.GL_STREAM_DRAW);
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
}
}
@@ -0,0 +1,266 @@
package su.divan2000.veila.client.simulation;
import java.util.ArrayDeque;
import java.util.Locale;
public final class FogSimulationManager {
private static boolean initialized = false;
private static long lastSimulationTime = 0;
private static long lastSwapTimeNs = 0;
// Симулируем каждые 300 мс (~3.3 раз в секунду)
private static final long SIMULATION_INTERVAL_NS = 300_000_000L;
private static final long TARGET_STEP_DURATION_NS = 300_000_000L;
private static final long FRAME_TIME_WINDOW_NS = 100_000_000L;
private static final long STAT_WINDOW_NS = 5_000_000_000L;
private static final int MAX_LAYERS_PER_FRAME = 32;
private static final int MIN_LAYERS_PER_FRAME = 2;
private static final double LAYER_BUDGET_FACTOR = 0.75;
private static final ArrayDeque<Long> recentFrameDurations = new ArrayDeque<>();
private static final ArrayDeque<Long> recentFrameTimestamps = new ArrayDeque<>();
private static final ArrayDeque<Long> baselineFrameDurations = new ArrayDeque<>();
private static final ArrayDeque<Long> baselineFrameTimestamps = new ArrayDeque<>();
private static final ArrayDeque<Long> completedStepDurations = new ArrayDeque<>();
private static final ArrayDeque<Long> completedStepTimestamps = new ArrayDeque<>();
private static long lastFrameTimestamp = 0;
private static long lastStatsPrintTime = 0;
private static boolean simulationActive = false;
private static int currentLayer = 0;
private static long simulationStartedAtNs = 0;
private static long averageLayerCostNs = 1_000_000L;
private static float globalEmissionMultiplier = 0.6f;
private static float globalAbsorptionMultiplier = 1.0f;
private static float globalDecay = 0.025f;
private static float diffusionRate = 0.15f;
private static float upDiffusion = 1.5f;
private static float downDiffusion = 1.0f;
private static float horizontalDiffusion = 1.0f;
private FogSimulationManager() {
}
public static void init() {
if (initialized) return;
FogDensityTextures.init();
FogSimulationShader.init();
initialized = true;
lastSimulationTime = System.nanoTime();
}
/**
* Вызывается из RENDER THREAD в beginRender()
*/
public static void update() {
if (!initialized) return;
long currentTime = System.nanoTime();
if (lastFrameTimestamp != 0) {
long frameDuration = currentTime - lastFrameTimestamp;
recordFrameDuration(frameDuration, currentTime);
if (!simulationActive) {
recordBaselineFrameDuration(frameDuration, currentTime);
}
}
lastFrameTimestamp = currentTime;
if (!simulationActive && currentTime - lastSimulationTime >= SIMULATION_INTERVAL_NS) {
beginSimulation();
lastSimulationTime = currentTime;
}
if (simulationActive) {
runSimulationStep();
}
}
private static void beginSimulation() {
simulationActive = true;
currentLayer = 0;
simulationStartedAtNs = System.nanoTime();
averageLayerCostNs = 1_000_000L;
}
private static void runSimulationStep() {
int remainingLayers = FogWorldVolume.SIZE_Z - currentLayer;
if (remainingLayers <= 0) {
FogDensityTextures.swap();
simulationActive = false;
return;
}
int layersThisFrame = computeLayersPerFrame(remainingLayers);
int endLayer = Math.min(FogWorldVolume.SIZE_Z, currentLayer + layersThisFrame);
long workStart = System.nanoTime();
FogSimulationShader.simulateRange(
currentLayer,
endLayer,
globalEmissionMultiplier,
globalAbsorptionMultiplier,
globalDecay,
diffusionRate,
upDiffusion,
downDiffusion,
horizontalDiffusion
);
long workDuration = System.nanoTime() - workStart;
if (layersThisFrame > 0) {
long observedCost = Math.max(500_000L, workDuration / layersThisFrame);
averageLayerCostNs = Math.max(500_000L, (long) (averageLayerCostNs * 0.8 + observedCost * 0.2));
}
currentLayer = endLayer;
if (currentLayer >= FogWorldVolume.SIZE_Z) {
long stepDuration = System.nanoTime() - simulationStartedAtNs;
recordCompletedStep(stepDuration);
FogDensityTextures.swap();
lastSwapTimeNs = System.nanoTime();
simulationActive = false;
}
}
private static int computeLayersPerFrame(int remainingLayers) {
long baselineFrameTime = getBaselineFrameTimeNs();
if (baselineFrameTime <= 0) {
baselineFrameTime = 16_000_000L;
}
long elapsedSimulationTime = System.nanoTime() - simulationStartedAtNs;
long remainingBudgetNs = TARGET_STEP_DURATION_NS - elapsedSimulationTime;
if (remainingBudgetNs <= 0) {
return Math.min(remainingLayers, MAX_LAYERS_PER_FRAME);
}
long frameBudgetNs = Math.max(1_000_000L, (long) (baselineFrameTime * LAYER_BUDGET_FACTOR));
int layersByBudget = Math.max(MIN_LAYERS_PER_FRAME, (int) Math.round((double) frameBudgetNs / averageLayerCostNs));
int framesLeft = Math.max(1, (int) Math.ceil((double) remainingBudgetNs / baselineFrameTime));
int layersByProgress = Math.max(MIN_LAYERS_PER_FRAME, (int) Math.ceil((double) remainingLayers / framesLeft));
int layers = Math.min(MAX_LAYERS_PER_FRAME, Math.min(layersByBudget, layersByProgress));
return Math.min(remainingLayers, Math.max(MIN_LAYERS_PER_FRAME, layers));
}
private static void recordFrameDuration(long duration, long timestamp) {
recentFrameDurations.addLast(duration);
recentFrameTimestamps.addLast(timestamp);
while (!recentFrameTimestamps.isEmpty() && timestamp - recentFrameTimestamps.peekFirst() > FRAME_TIME_WINDOW_NS) {
recentFrameTimestamps.removeFirst();
recentFrameDurations.removeFirst();
}
}
private static void recordBaselineFrameDuration(long duration, long timestamp) {
baselineFrameDurations.addLast(duration);
baselineFrameTimestamps.addLast(timestamp);
while (!baselineFrameTimestamps.isEmpty() && timestamp - baselineFrameTimestamps.peekFirst() > FRAME_TIME_WINDOW_NS) {
baselineFrameTimestamps.removeFirst();
baselineFrameDurations.removeFirst();
}
}
private static long getAverageFrameTimeNs() {
if (recentFrameDurations.isEmpty()) {
return 16_000_000L;
}
long sum = 0;
for (Long duration : recentFrameDurations) {
sum += duration;
}
return sum / recentFrameDurations.size();
}
private static long getBaselineFrameTimeNs() {
if (baselineFrameDurations.isEmpty()) {
return 16_000_000L;
}
long sum = 0;
for (Long duration : baselineFrameDurations) {
sum += duration;
}
return sum / baselineFrameDurations.size();
}
private static void recordCompletedStep(long duration) {
long timestamp = System.nanoTime();
completedStepDurations.addLast(duration);
completedStepTimestamps.addLast(timestamp);
while (!completedStepTimestamps.isEmpty() && timestamp - completedStepTimestamps.peekFirst() > STAT_WINDOW_NS) {
completedStepTimestamps.removeFirst();
completedStepDurations.removeFirst();
}
if (lastStatsPrintTime == 0 || timestamp - lastStatsPrintTime >= STAT_WINDOW_NS) {
//printStepStats();
lastStatsPrintTime = timestamp;
}
}
private static void printStepStats() {
if (completedStepDurations.isEmpty()) {
return;
}
long sum = 0;
long min = Long.MAX_VALUE;
long max = Long.MIN_VALUE;
for (Long duration : completedStepDurations) {
sum += duration;
min = Math.min(min, duration);
max = Math.max(max, duration);
}
long average = sum / completedStepDurations.size();
System.out.printf(Locale.ROOT,
"[FogSim] step stats last 5s: avg=%.2f ms min=%.2f ms max=%.2f ms%n",
average / 1_000_000.0,
min / 1_000_000.0,
max / 1_000_000.0);
}
public static int getCurrentDensityTexture() {
return FogDensityTextures.getReadTexture();
}
public static int getPreviousDensityTexture() {
return FogDensityTextures.getPreviousTexture();
}
public static float getFogInterpolationAlpha() {
if (!FogDensityTextures.hasPreviousState()) {
return 1.0f;
}
long currentTime = System.nanoTime();
long elapsed = currentTime - lastSwapTimeNs;
if (elapsed <= 0) {
return 0.0f;
}
float alpha = (float) elapsed / (float) SIMULATION_INTERVAL_NS;
return Math.min(alpha, 1.0f);
}
public static void setGlobalEmissionMultiplier(float value) {
globalEmissionMultiplier = value;
}
public static void setGlobalAbsorptionMultiplier(float value) {
globalAbsorptionMultiplier = value;
}
public static void setGlobalDecay(float value) {
globalDecay = value;
}
}
@@ -0,0 +1,313 @@
package su.divan2000.veila.client.simulation;
import su.divan2000.veila.client.gl.FullscreenQuad;
import su.divan2000.veila.client.gl.GLProgram;
import su.divan2000.veila.client.gl.GLShader;
import su.divan2000.veila.client.gl.ResourceUtil;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_BINDING_2D;
import static org.lwjgl.opengl.GL12.GL_TEXTURE_BINDING_3D;
public final class FogSimulationShader {
private static GLProgram program;
private static int fbo = -1;
private static int worldInfoSamplerLocation;
private static int fogReadSamplerLocation;
private static int globalEmissionMultiplierLocation;
private static int globalAbsorptionMultiplierLocation;
private static int globalDecayLocation;
private static int diffusionRateLocation;
private static int upDiffusionLocation;
private static int downDiffusionLocation;
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 static int heightFadeMaxLocation;
private static int skyLightSamplerLocation;
private FogSimulationShader() {
}
public static void init() {
if (program != null) return;
FullscreenQuad.init();
GLShader vertex = new GLShader(
GL20.GL_VERTEX_SHADER,
ResourceUtil.load("shaders/fullscreen.vert")
);
GLShader fragment = new GLShader(
GL20.GL_FRAGMENT_SHADER,
ResourceUtil.load("shaders/sim.frag")
);
program = new GLProgram(vertex, fragment);
worldInfoSamplerLocation = program.uniform("WorldInfoSampler");
fogReadSamplerLocation = program.uniform("FogReadSampler");
globalEmissionMultiplierLocation = program.uniform("GlobalEmissionMultiplier");
globalAbsorptionMultiplierLocation = program.uniform("GlobalAbsorptionMultiplier");
globalDecayLocation = program.uniform("GlobalDecay");
diffusionRateLocation = program.uniform("DiffusionRate");
upDiffusionLocation = program.uniform("UpDiffusion");
downDiffusionLocation = program.uniform("DownDiffusion");
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");
heightFadeMaxLocation = program.uniform("HeightFadeMax");
skyLightSamplerLocation = program.uniform("SkyLightSampler");
fbo = GL30.glGenFramebuffers();
vertex.delete();
fragment.delete();
}
public static void simulate(
float globalEmissionMultiplier,
float globalAbsorptionMultiplier,
float globalDecay,
float diffusionRate,
float upDiffusion,
float downDiffusion,
float horizontalDiffusion
) {
simulateRange(
0,
FogWorldVolume.SIZE_Z,
globalEmissionMultiplier,
globalAbsorptionMultiplier,
globalDecay,
diffusionRate,
upDiffusion,
downDiffusion,
horizontalDiffusion
);
FogDensityTextures.swap();
}
public static void simulateRange(
int startZ,
int endZ,
float globalEmissionMultiplier,
float globalAbsorptionMultiplier,
float globalDecay,
float diffusionRate,
float upDiffusion,
float downDiffusion,
float horizontalDiffusion
) {
if (program == null) return;
int fogRead = FogDensityTextures.getReadTexture();
int fogWrite = FogDensityTextures.getWriteTexture();
int worldInfo = FogWorldVolume.getTexture();
// ========================================
// ПОЛНОЕ СОХРАНЕНИЕ СОСТОЯНИЯ OPENGL
// ========================================
// FBO и viewport
int previousFBO = GL11.glGetInteger(GL30.GL_FRAMEBUFFER_BINDING);
int[] previousViewport = new int[4];
GL11.glGetIntegerv(GL11.GL_VIEWPORT, previousViewport);
// Program
int previousProgram = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
// VAO
int previousVAO = GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING);
// Активный текстурный юнит
int previousActiveTexture = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
// Привязки текстур для юнитов (2D и 3D)
GL13.glActiveTexture(GL13.GL_TEXTURE0);
int previousTex0_2D = GL11.glGetInteger(GL_TEXTURE_BINDING_2D);
int previousTex0_3D = GL11.glGetInteger(GL_TEXTURE_BINDING_3D);
GL13.glActiveTexture(GL13.GL_TEXTURE1);
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);
GL13.glActiveTexture(GL13.GL_TEXTURE3);
int previousTex3_2D = GL11.glGetInteger(GL_TEXTURE_BINDING_2D);
int previousTex3_3D = GL11.glGetInteger(GL_TEXTURE_BINDING_3D);
// Blend state (на всякий случай)
boolean previousBlend = GL11.glIsEnabled(GL11.GL_BLEND);
boolean previousDepthTest = GL11.glIsEnabled(GL11.GL_DEPTH_TEST);
boolean previousCullFace = GL11.glIsEnabled(GL11.GL_CULL_FACE);
// Color mask
ByteBuffer previousColorMask = org.lwjgl.BufferUtils.createByteBuffer(4);
GL11.glGetBooleanv(GL11.GL_COLOR_WRITEMASK, previousColorMask);
// Depth mask
boolean previousDepthMask = GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK);
// ========================================
// НАСТРОЙКА СОСТОЯНИЯ ДЛЯ СИМУЛЯЦИИ
// ========================================
// Отключаем всё лишнее
GL11.glDisable(GL11.GL_BLEND);
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glDisable(GL11.GL_CULL_FACE);
GL11.glColorMask(true, true, true, true);
GL11.glDepthMask(false);
// Привязываем framebuffer
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
// Настраиваем viewport
GL11.glViewport(0, 0, FogWorldVolume.SIZE_X, FogWorldVolume.SIZE_Y);
// Привязываем программу
program.bind();
// Привязываем текстуры
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, worldInfo);
GL20.glUniform1i(worldInfoSamplerLocation, 0);
GL13.glActiveTexture(GL13.GL_TEXTURE1);
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);
GL13.glActiveTexture(GL13.GL_TEXTURE3);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getSkyLightTexture());
GL20.glUniform1i(skyLightSamplerLocation, 3);
// Устанавливаем параметры
GL20.glUniform1f(globalEmissionMultiplierLocation, globalEmissionMultiplier);
GL20.glUniform1f(globalAbsorptionMultiplierLocation, globalAbsorptionMultiplier);
GL20.glUniform1f(globalDecayLocation, globalDecay);
GL20.glUniform1f(diffusionRateLocation, diffusionRate);
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.glUniform1f(heightFadeMaxLocation, FogEnvironmentContext.getHeightFadeMax());
GL20.glUniform3f(voxelSizeLocation,
1.0f / FogWorldVolume.SIZE_X,
1.0f / FogWorldVolume.SIZE_Y,
1.0f / FogWorldVolume.SIZE_Z
);
// ========================================
// РЕНДЕРИНГ
// ========================================
int clampedStartZ = Math.max(0, Math.min(FogWorldVolume.SIZE_Z, startZ));
int clampedEndZ = Math.max(clampedStartZ, Math.min(FogWorldVolume.SIZE_Z, endZ));
for (int z = clampedStartZ; z < clampedEndZ; z++) {
GL30.glFramebufferTexture3D(
GL30.GL_FRAMEBUFFER,
GL30.GL_COLOR_ATTACHMENT0,
GL12.GL_TEXTURE_3D,
fogWrite,
0,
z
);
GL20.glUniform1f(currentZLocation, (float) (z + 0.5f) / FogWorldVolume.SIZE_Z);
FullscreenQuad.draw();
}
// ========================================
// ПОЛНОЕ ВОССТАНОВЛЕНИЕ СОСТОЯНИЯ
// ========================================
// Отвязываем программу
GL20.glUseProgram(previousProgram);
// Отвязываем FBO
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, previousFBO);
// Восстанавливаем viewport
GL11.glViewport(previousViewport[0], previousViewport[1],
previousViewport[2], previousViewport[3]);
// Восстанавливаем VAO
GL30.glBindVertexArray(previousVAO);
// Восстанавливаем текстуры
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTex0_2D);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, previousTex0_3D);
GL13.glActiveTexture(GL13.GL_TEXTURE1);
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(GL13.GL_TEXTURE3);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTex3_2D);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, previousTex3_3D);
// Восстанавливаем активный текстурный юнит
GL13.glActiveTexture(previousActiveTexture);
// Восстанавливаем blend/depth/cull
if (previousBlend) GL11.glEnable(GL11.GL_BLEND);
else GL11.glDisable(GL11.GL_BLEND);
if (previousDepthTest) GL11.glEnable(GL11.GL_DEPTH_TEST);
else GL11.glDisable(GL11.GL_DEPTH_TEST);
if (previousCullFace) GL11.glEnable(GL11.GL_CULL_FACE);
else GL11.glDisable(GL11.GL_CULL_FACE);
// Восстанавливаем color mask
GL11.glColorMask(
previousColorMask.get(0) != 0,
previousColorMask.get(1) != 0,
previousColorMask.get(2) != 0,
previousColorMask.get(3) != 0
);
// Восстанавливаем depth mask
GL11.glDepthMask(previousDepthMask);
}
}
@@ -0,0 +1,145 @@
package su.divan2000.veila.client.simulation;
import net.minecraft.client.MinecraftClient;
public final class FogSystem {
private static boolean initialized;
private static int originChunkX;
private static int originChunkZ;
private static int ringChunkOffsetX;
private static int ringChunkOffsetZ;
private FogSystem() {
}
public static void beginRender(float tickDelta) {
if (!initialized) {
initialize();
}
updateWindow();
// Блоки и density: загружаем готовые данные
FogChunkStreamer.uploadReadyData();
// Блоки и density: обрабатываем изменения
FogChunkStreamer.processBlockChanges();
// Освещение: загружаем готовые секции + инкрементальные обновления (render thread)
FogLightStreamer.uploadReadySections();
// Биомы
FogBiomeStreamer.uploadReadyData();
FogEnvironmentContext.update(tickDelta);
FogSimulationManager.update();
}
public static void tick() {
// Блоки: initial load в client thread
FogChunkStreamer.processPending();
// Освещение: читаем данные секций (client thread, без OpenGL)
FogLightStreamer.prepareAllSections();
// Читаем биомы в client thread
FogBiomeStreamer.processPending();
}
private static void initialize() {
FogWorldVolume.init();
FogLightVolume.init();
FogLightStreamer.init();
FogSimulationManager.init();
FogBiomeTexture.init();
FogEnvironmentContext.init();
initialized = true;
}
private static void updateWindow() {
MinecraftClient client = MinecraftClient.getInstance();
if (client.world == null || client.player == null)
return;
int playerChunkX = client.player.getBlockX() >> 4;
int playerChunkZ = client.player.getBlockZ() >> 4;
int newOriginChunkX =
playerChunkX - FogWorldVolume.CHUNKS_X / 2;
int newOriginChunkZ =
playerChunkZ - FogWorldVolume.CHUNKS_Z / 2;
if (newOriginChunkX == originChunkX &&
newOriginChunkZ == originChunkZ)
return;
int oldOriginChunkX = originChunkX;
int oldOriginChunkZ = originChunkZ;
originChunkX = newOriginChunkX;
originChunkZ = newOriginChunkZ;
ringChunkOffsetX =
Math.floorMod(originChunkX,
FogWorldVolume.CHUNKS_X);
ringChunkOffsetZ =
Math.floorMod(originChunkZ,
FogWorldVolume.CHUNKS_Z);
FogChunkStreamer.update(
oldOriginChunkX,
oldOriginChunkZ,
originChunkX,
originChunkZ,
ringChunkOffsetX,
ringChunkOffsetZ
);
// Освещение: помечаем новые секции для initial load
FogLightStreamer.update(
oldOriginChunkX,
oldOriginChunkZ,
originChunkX,
originChunkZ,
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() {
return originChunkX;
}
public static int originChunkZ() {
return originChunkZ;
}
public static int ringChunkOffsetX() {
return ringChunkOffsetX;
}
public static int ringChunkOffsetZ() {
return ringChunkOffsetZ;
}
}
@@ -0,0 +1,325 @@
package su.divan2000.veila.client.simulation;
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;
// No GL43 usage: keep compatibility with OpenGL 3.2
import java.nio.ByteBuffer;
public final class FogWorldVolume {
public static final int SIZE_X = 256;
public static final int SIZE_Z = 256;
public static final int MIN_Y = -64;
public static final int MAX_Y = 320;
public static final int SIZE_Y = MAX_Y - MIN_Y;
public static final int CHUNK_SIZE = 16;
public static final int CHUNKS_X = SIZE_X / CHUNK_SIZE;
public static final int CHUNKS_Z = SIZE_Z / CHUNK_SIZE;
private static int texture = -1;
private static final byte AIR_VALUE = new FogBlockProperties(false, 0, 0).pack();
private static final byte[] ZEROS_ARRAY;
static {
ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
java.util.Arrays.fill(ZEROS_ARRAY, AIR_VALUE);
}
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 для быстрой очистки регионов
private static int clearPBO = -1;
// Ring of PBOs for chunk uploads to avoid overwriting a buffer while the GPU is still using it
private static final int[] uploadPBOs = new int[3];
private static int uploadPBOIndex = 0;
private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
private FogWorldVolume() {
}
private static void initClearPBO() {
if (clearPBO != -1) return;
clearPBO = GL15.glGenBuffers();
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO);
// Выделяем буфер в VRAM
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE, GL15.GL_STATIC_DRAW);
// Заполняем AIR_VALUE через mapping
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(AIR_VALUE);
}
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
}
public static void init() {
if (texture != -1) return;
// Инициализируем PBO ДО создания текстуры
initClearPBO();
initUploadPBO();
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 uploadZeros(int textureChunkX, int textureChunkZ) {
ByteBuffer buffer = chunkBuffer();
buffer.put(ZEROS_ARRAY);
buffer.flip();
uploadChunk(textureChunkX, textureChunkZ, buffer);
}
public static void uploadChunkFromArray(
int textureChunkX,
int textureChunkZ,
byte[] data
) {
ByteBuffer buffer = chunkBuffer();
buffer.put(data);
buffer.flip();
uploadChunk(textureChunkX, textureChunkZ, buffer);
}
public static void beginUpload() {
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);
}
public static void uploadChunkFromArrayNoBind(
int textureChunkX,
int textureChunkZ,
byte[] data
) {
// Use a ring of PBOs so one upload never overwrites another still pending on the GPU
if (uploadPBOs[0] != 0) {
int pbo = uploadPBOs[uploadPBOIndex % uploadPBOs.length];
uploadPBOIndex++;
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, pbo);
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, CHUNK_SIZE * SIZE_Y * CHUNK_SIZE, GL15.GL_STREAM_DRAW);
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
if (mapped != null) {
mapped.put(data);
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
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,
0L
);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
} else {
ByteBuffer buffer = chunkBuffer();
buffer.put(data);
buffer.flip();
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,
buffer
);
}
}
public static void endUpload() {
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 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_SIZE * SIZE_Y * CHUNK_SIZE, GL15.GL_STREAM_DRAW);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
}
public static ByteBuffer chunkBuffer() {
CHUNK_BUFFER.clear();
return CHUNK_BUFFER;
}
public static int getTexture() {
return texture;
}
/**
* Очищает несколько непрерывных физических регионов через PBO (асинхронно).
* Размеры widthChunks и depthChunks задаются в чанках (не в блоках!).
*/
public static void clearRegions(
int[] physX, int[] physZ,
int[] widthChunks, int[] depthChunks,
int count
) {
// Use PBO-based clears for OpenGL 3.2 compatibility
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;
// 0L = offset 0 внутри PBO (начало буфера)
GL12.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 // ← long offset вместо ByteBuffer
);
}
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
}
@@ -0,0 +1,65 @@
package su.divan2000.veila.client.simulation;
/**
* Простой 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);
}
}
@@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import su.divan2000.veila.client.render.PostProcessingManager; import su.divan2000.veila.client.render.PostProcessingManager;
import su.divan2000.veila.client.render.fog.FogSystem; import su.divan2000.veila.client.simulation.FogSystem;
@Mixin(GameRenderer.class) @Mixin(GameRenderer.class)
public class GameRendererMixin { public class GameRendererMixin {
@@ -27,7 +27,7 @@ public class GameRendererMixin {
CallbackInfo ci CallbackInfo ci
) { ) {
PostProcessingManager.init(); PostProcessingManager.init();
PostProcessingManager.render(); PostProcessingManager.render(tickDelta);
} }
@Inject( @Inject(
@@ -61,6 +61,9 @@ public class GameRendererMixin {
boolean tick, boolean tick,
CallbackInfo ci CallbackInfo ci
) { ) {
FogSystem.beginRender(); net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance();
if (client.world != null && client.player != null) {
FogSystem.beginRender(tickDelta);
}
} }
} }
@@ -0,0 +1,52 @@
package su.divan2000.veila.mixin.client;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.world.LightType;
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.Shadow;
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.simulation.FogLightStreamer;
@Mixin(LightStorage.class)
public class LightStorageMixin {
@Shadow
private LightType lightType;
@Inject(method = "set(JI)V", at = @At("TAIL"))
private void veila$onLightSet(long blockPos, int value, CallbackInfo ci) {
long sectionPos = ChunkSectionPos.fromBlockPos(blockPos);
if (this.lightType == LightType.BLOCK) {
FogLightStreamer.onBlockLightUpdated(sectionPos);
} else {
FogLightStreamer.onSkyLightUpdated(sectionPos);
}
}
@Inject(method = "enqueueSectionData(JLnet/minecraft/world/chunk/ChunkNibbleArray;)V", at = @At("TAIL"))
private void veila$onEnqueueSection(long sectionPos, @Nullable ChunkNibbleArray array, CallbackInfo ci) {
if (array != null) {
if (this.lightType == LightType.BLOCK) {
FogLightStreamer.onBlockLightUpdated(sectionPos);
} else {
FogLightStreamer.onSkyLightUpdated(sectionPos);
}
}
}
@Inject(method = "setSectionStatus(JZ)V", at = @At("TAIL"))
private void veila$onSetSectionStatus(long sectionPos, boolean notReady, CallbackInfo ci) {
if (!notReady) {
if (this.lightType == LightType.BLOCK) {
FogLightStreamer.onBlockLightUpdated(sectionPos);
} else {
FogLightStreamer.onSkyLightUpdated(sectionPos);
}
}
}
}
@@ -5,7 +5,7 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import su.divan2000.veila.client.render.fog.FogSystem; import su.divan2000.veila.client.simulation.FogSystem;
@Mixin(MinecraftClient.class) @Mixin(MinecraftClient.class)
public class MinecraftClientMixin { public class MinecraftClientMixin {
@@ -0,0 +1,41 @@
package su.divan2000.veila.mixin.client;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.chunk.WorldChunk;
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.CallbackInfoReturnable;
import su.divan2000.veila.client.simulation.FogChunkStreamer;
@Mixin(WorldChunk.class)
public class WorldChunkMixin {
@Inject(
method = "setBlockState",
at = @At("RETURN")
)
private void veila$onBlockChanged(
BlockPos pos,
BlockState state,
boolean moved,
CallbackInfoReturnable<BlockState> cir
) {
// Только если блок реально изменился
if (cir.getReturnValue() != null) {
WorldChunk self = (WorldChunk) (Object) this;
int chunkX = self.getPos().x;
int chunkZ = self.getPos().z;
// Уведомляем стример об изменении
FogChunkStreamer.onBlockChanged(
chunkX,
chunkZ,
pos.getX(),
pos.getY(),
pos.getZ(),
state
);
}
}
}
@@ -0,0 +1,23 @@
#version 150
uniform sampler2D DepthSampler;
uniform vec2 ScreenSize;
in vec2 texCoord;
out vec4 FragColor;
void main()
{
vec2 texelSize = 1.0 / ScreenSize;
// Берём минимальный depth из 2x2 блока (ближайший объект)
float d00 = texture(DepthSampler, texCoord + vec2(-0.5, -0.5) * texelSize).r;
float d10 = texture(DepthSampler, texCoord + vec2( 0.5, -0.5) * texelSize).r;
float d01 = texture(DepthSampler, texCoord + vec2(-0.5, 0.5) * texelSize).r;
float d11 = texture(DepthSampler, texCoord + vec2( 0.5, 0.5) * texelSize).r;
float minDepth = min(min(d00, d10), min(d01, d11));
FragColor = vec4(minDepth, 0.0, 0.0, 1.0);
}
@@ -0,0 +1,106 @@
#version 150
uniform sampler2D DiffuseSampler;
uniform sampler2D FogSampler;
uniform sampler2D DepthSampler;
uniform sampler2D ScaledDepthSampler;
uniform vec2 ScreenSize;
uniform vec2 ScaledScreenSize;
uniform mat4 InverseProjection;
in vec2 texCoord;
out vec4 FragColor;
//------------------------------------------------------------
// Параметры JBU
//------------------------------------------------------------
const int WINDOW_SIZE = 5;
const int WINDOW_RADIUS = WINDOW_SIZE / 2;
const float SIGMA_SPATIAL = 2.0;
const float SIGMA_SPECTRAL = 0.3; // Меньше = резче границы
const float MIN_WEIGHT = 0.0001;
//------------------------------------------------------------
// Предвычисленные spatial weights для окна 5x5
const float spatialWeights[25] = float[25](
0.003765, 0.015019, 0.023792, 0.015019, 0.003765,
0.015019, 0.059912, 0.094907, 0.059912, 0.015019,
0.023792, 0.094907, 0.150342, 0.094907, 0.023792,
0.015019, 0.059912, 0.094907, 0.059912, 0.015019,
0.003765, 0.015019, 0.023792, 0.015019, 0.003765
);
//------------------------------------------------------------
float linearizeDepth(float depth)
{
vec4 clip = vec4(0.0, 0.0, depth * 2.0 - 1.0, 1.0);
vec4 view = InverseProjection * clip;
return -view.z / view.w;
}
//------------------------------------------------------------
void main()
{
vec3 sceneColor = texture(DiffuseSampler, texCoord).rgb;
// Линеаризованный depth текущего full-res пикселя
float depthFull = linearizeDepth(texture(DepthSampler, texCoord).r);
vec2 lowResCoord = texCoord;
vec2 texelSize = 1.0 / ScaledScreenSize;
vec4 sumColor = vec4(0.0);
float sumWeight = 0.0;
int sampleIndex = 0;
for (int y = -WINDOW_RADIUS; y <= WINDOW_RADIUS; y++) {
for (int x = -WINDOW_RADIUS; x <= WINDOW_RADIUS; x++) {
vec2 neighborCoord = lowResCoord + vec2(float(x), float(y)) * texelSize;
if (any(lessThan(neighborCoord, vec2(0.0))) || any(greaterThan(neighborCoord, vec2(1.0)))) {
sampleIndex++;
continue;
}
float wSpatial = spatialWeights[sampleIndex];
// Линеаризованный depth из half-res depth buffer
float depthNeighbor = linearizeDepth(texture(ScaledDepthSampler, neighborCoord).r);
float depthDiff = abs(depthFull - depthNeighbor);
// Range weight с линеаризованным depth в метрах
float wRange = exp(-(depthDiff * depthDiff) / (2.0 * SIGMA_SPECTRAL * SIGMA_SPECTRAL));
float weight = wSpatial * wRange;
vec4 fogSample = texture(FogSampler, neighborCoord);
sumColor += fogSample * weight;
sumWeight += weight;
sampleIndex++;
}
}
vec4 result;
if (sumWeight < MIN_WEIGHT) {
result = texture(FogSampler, lowResCoord);
} else {
result = sumColor / sumWeight;
}
vec3 scattered = result.rgb;
float transmittance = result.a;
vec3 finalColor = sceneColor * transmittance + scattered;
FragColor = vec4(finalColor, 1.0);
}
@@ -0,0 +1,210 @@
#version 150
uniform sampler2D DepthSampler;
uniform sampler3D FogVolume;
uniform sampler3D PrevFogVolume;
uniform sampler3D SkyLightVolume;
uniform sampler3D BlockLightVolume;
uniform mat4 InverseProjection;
uniform mat4 InverseView;
uniform vec3 FogColor;
uniform vec3 CameraPosition;
uniform float FogInterpolation;
uniform float SkyBrightness;
uniform ivec2 FogOrigin;
uniform ivec2 RingBlockOffset;
in vec2 texCoord;
out vec4 FragColor;
//------------------------------------------------------------
// Настройки
//------------------------------------------------------------
const float FOG_SIZE_X = 256.0;
const float FOG_SIZE_Z = FOG_SIZE_X;
const int MAX_STEPS = 256;
const float WORLD_BOTTOM = -64.0;
const float WORLD_HEIGHT = 384.0;
const float MAX_DISTANCE = (FOG_SIZE_X / 2.0) - 18.0;
const float GAMMA = 2.0;
const bool USE_JITTERING = false;
const float EXTINCTION_COEFF = 1.0;
const float SCATTERING_COEFF = 1.0;
const float AMBIENT_INTENSITY = 0.10;
const float MIN_TRANSMITTANCE = 0.01;
//------------------------------------------------------------
vec3 reconstructViewPosition(vec2 uv)
{
float depth = texture(DepthSampler, uv).r;
vec4 clip = vec4(
uv * 2.0 - 1.0,
depth * 2.0 - 1.0,
1.0
);
vec4 view = InverseProjection * clip;
return view.xyz / view.w;
}
vec3 reconstructWorldPosition(vec2 uv)
{
vec3 viewPos = reconstructViewPosition(uv);
vec4 world = InverseView * vec4(viewPos, 1.0);
return world.xyz + CameraPosition;
}
//------------------------------------------------------------
vec3 worldToUV(vec3 worldPos)
{
if (
worldPos.x < 0.0 || worldPos.x >= FOG_SIZE_X ||
worldPos.z < 0.0 || worldPos.z >= FOG_SIZE_Z ||
worldPos.y < 0.0 || worldPos.y >= WORLD_HEIGHT
){
return vec3(-1.0);
}
float physicalX = mod(worldPos.x + float(RingBlockOffset.x), FOG_SIZE_X);
float physicalZ = mod(worldPos.z + float(RingBlockOffset.y), FOG_SIZE_Z);
vec3 uv;
uv.x = physicalX / FOG_SIZE_X;
uv.z = physicalZ / FOG_SIZE_Z;
uv.y = worldPos.y / WORLD_HEIGHT;
return uv;
}
float density(vec3 worldPos)
{
vec3 uv = worldToUV(worldPos);
if (uv.x < 0.0) return 0.0;
float currentDensity = texture(FogVolume, uv).r;
float prevDensity = texture(PrevFogVolume, uv).r;
return mix(prevDensity, currentDensity, FogInterpolation);
}
vec2 light(vec3 worldPos)
{
vec3 uv = worldToUV(worldPos);
if (uv.x < 0.0) return vec2(0.0);
float blockNorm = texture(BlockLightVolume, uv).r;
float skyNorm = texture(SkyLightVolume, uv).r;
float skyContribution = skyNorm * SkyBrightness;
return vec2(skyContribution, blockNorm);
}
//------------------------------------------------------------
float random(vec2 st)
{
vec3 p3 = fract(vec3(st.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
float mapParameterToDistance(float t)
{
return MAX_DISTANCE * pow(t, GAMMA);
}
//------------------------------------------------------------
void main()
{
vec3 worldPos = reconstructWorldPosition(texCoord);
vec3 rayDir = normalize(worldPos - CameraPosition);
float rayLength = distance(worldPos, CameraPosition);
rayLength = min(rayLength, MAX_DISTANCE);
vec3 samplePos = CameraPosition;
float currentDistance = 0.0;
float transmittance = 1.0;
float skyScattered = 0.0;
float blockScattered = 0.0;
float ambientScattered = 0.0;
float jitter = 0.0;
if (USE_JITTERING) {
float firstStep = mapParameterToDistance(1.0 / float(MAX_STEPS));
jitter = random(texCoord) * firstStep;
currentDistance = jitter;
samplePos += rayDir * currentDistance;
}
for (int i = 0; i < MAX_STEPS; i++) {
float t = float(i + 1) / float(MAX_STEPS);
float nextDistance = mapParameterToDistance(t);
if (nextDistance > rayLength) {
nextDistance = rayLength;
}
float stepSize = nextDistance - currentDistance;
if (stepSize <= 0.0) {
break;
}
samplePos += rayDir * stepSize;
float d = density(samplePos);
float opticalDepth = d * stepSize * EXTINCTION_COEFF;
if (d > 0.0) {
vec2 lightValues = light(samplePos);
float totalLightAtPoint = lightValues.x + lightValues.y;
float baseScattering = d * stepSize * SCATTERING_COEFF;
float stepScattering = totalLightAtPoint * d * stepSize * SCATTERING_COEFF;
skyScattered += lightValues.x * baseScattering * transmittance;
blockScattered += lightValues.y * baseScattering * transmittance * ((lightValues.y - lightValues.x) * 0.9 + 0.1);
ambientScattered += AMBIENT_INTENSITY * baseScattering * transmittance;
transmittance *= exp(-opticalDepth);
}
currentDistance = nextDistance;
if (transmittance < MIN_TRANSMITTANCE) {
transmittance = 0.0;
break;
}
if (currentDistance >= rayLength) {
break;
}
}
float totalScattered = skyScattered + blockScattered + ambientScattered;
// Выводим: RGB = вложенный свет тумана, A = transmittance
vec3 fogColor = FogColor * totalScattered;
FragColor = vec4(fogColor, transmittance);
}
@@ -1,137 +0,0 @@
#version 150
uniform sampler2D DiffuseSampler;
uniform sampler2D DepthSampler;
uniform sampler3D FogVolume;
uniform mat4 InverseProjection;
uniform mat4 InverseView;
uniform vec3 CameraPosition;
uniform ivec2 FogOrigin;
uniform ivec2 RingBlockOffset;
in vec2 texCoord;
out vec4 FragColor;
//------------------------------------------------------------
// Настройки
//------------------------------------------------------------
const float STEP_SIZE = 0.5;
const float MAX_DISTANCE = 256.0;
const int MAX_STEPS = int(MAX_DISTANCE / STEP_SIZE);
// Размер туманного объёма
const float FOG_SIZE_X = 256.0;
const float FOG_SIZE_Z = 256.0;
// Высота мира
const float WORLD_BOTTOM = -64.0;
const float WORLD_HEIGHT = 384.0;
//------------------------------------------------------------
vec3 reconstructViewPosition(vec2 uv)
{
float depth = texture(DepthSampler, uv).r;
vec4 clip = vec4(
uv * 2.0 - 1.0,
depth * 2.0 - 1.0,
1.0
);
vec4 view = InverseProjection * clip;
return view.xyz / view.w;
}
vec3 reconstructWorldPosition(vec2 uv)
{
vec3 viewPos = reconstructViewPosition(uv);
vec4 world = InverseView * vec4(viewPos, 1.0);
return world.xyz + CameraPosition;
}
//------------------------------------------------------------
float density(vec3 worldPos)
{
vec3 uv;
float localX = worldPos.x - float(FogOrigin.x);
float localZ = worldPos.z - float(FogOrigin.y);
if (
localX < 0.0 || localX >= FOG_SIZE_X ||
localZ < 0.0 || localZ >= FOG_SIZE_Z ||
worldPos.y < WORLD_BOTTOM ||
worldPos.y >= WORLD_BOTTOM + WORLD_HEIGHT
){
return 0.0;
}
float physicalX = mod(
localX + float(RingBlockOffset.x),
FOG_SIZE_X
);
float physicalZ = mod(
localZ + float(RingBlockOffset.y),
FOG_SIZE_Z
);
uv.x = physicalX / FOG_SIZE_X;
uv.z = physicalZ / FOG_SIZE_Z;
uv.y = (worldPos.y - WORLD_BOTTOM) / WORLD_HEIGHT;
return texture(FogVolume, uv).r;
}
//------------------------------------------------------------
void main()
{
vec4 sceneColor = texture(DiffuseSampler, texCoord);
vec3 worldPos = reconstructWorldPosition(texCoord);
vec3 rayDir = normalize(worldPos - CameraPosition);
float rayLength = distance(worldPos, CameraPosition);
rayLength = min(rayLength, MAX_DISTANCE);
vec3 samplePos = CameraPosition;
float fog = 0.0;
for (int i = 0; i < MAX_STEPS; i++)
{
float traveled = float(i) * STEP_SIZE;
if (traveled >= rayLength)
break;
samplePos += rayDir * STEP_SIZE;
fog += density(samplePos) * STEP_SIZE;
}
fog = clamp(fog, 0.0, 1.0);
vec3 fogColor = vec3(
0.75,
0.80,
0.90
);
FragColor = vec4(
mix(sceneColor.rgb, fogColor, fog),
1.0
);
}
@@ -0,0 +1,186 @@
#version 150
uniform usampler3D WorldInfoSampler;
uniform sampler3D FogReadSampler;
uniform sampler3D SkyLightSampler;
uniform sampler2D BiomeParamsSampler;
uniform float GlobalEmissionMultiplier;
uniform float GlobalAbsorptionMultiplier;
uniform float GlobalDecay;
uniform float DiffusionRate;
uniform float UpDiffusion;
uniform float DownDiffusion;
uniform float HorizontalDiffusion;
uniform vec3 VoxelSize;
uniform float CurrentZ;
uniform float EmissionMultiplier;
uniform float TargetDensityMultiplier;
uniform float FogDayRandom;
uniform float RainMultiplier;
uniform float HeightFadeMax;
in vec2 texCoord;
out vec4 FragColor;
const float HEIGHT_FADE_MIN = 75.0;
const float MIN_Y = -64.0;
const float SIZE_Y = 384.0;
const float UNDERGROUND_TARGET_DENSITY = 0.008; // Подземная дымка (слабее поверхностной)
const float UNDERGROUND_EMISSION = 1.0;
float getEmission(uint magnitude, bool isAbsorber) {
if (isAbsorber || magnitude == 0u) return 0.0;
return float(magnitude) / 63.0 * 0.05;
}
float getAbsorption(uint magnitude, bool isAbsorber) {
if (!isAbsorber || magnitude == 0u) return 0.0;
return float(magnitude) / 63.0 * 0.05;
}
void main() {
vec3 uv = vec3(texCoord, CurrentZ);
// Параметры биома
vec4 biomeParams = texture(BiomeParamsSampler, uv.xz);
float biomeEmission = biomeParams.r;
float biomeTargetDensityBase = biomeParams.g;
float biomeHumidity = biomeParams.b;
float biomeTimeSensitivity = biomeParams.a;
// Под землёй и в закрытых местах туман ведёт себя иначе
float skyLight = texture(SkyLightSampler, uv).r;
float skyLightSmooth = smoothstep(0.2, 0.8, skyLight);
// === Emission ===
float emissionTimeFactor = mix(1.0, EmissionMultiplier, biomeTimeSensitivity);
float surfaceEmission = biomeEmission * emissionTimeFactor;
float finalEmission = mix(UNDERGROUND_EMISSION, surfaceEmission, skyLightSmooth) * 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. Дымка плавно затухает к высоте 80-100
float worldY = uv.y * SIZE_Y + MIN_Y;
float heightFade = 1.0 - clamp((worldY - HEIGHT_FADE_MIN) / (HeightFadeMax - HEIGHT_FADE_MIN), 0.0, 1.0);
// 4. Итоговая плотность
float surfaceTargetDensity = biomeTargetDensityBase * hasFogToday * timeFactor * heightFade;
float finalTargetDensity = mix(UNDERGROUND_TARGET_DENSITY, surfaceTargetDensity, skyLightSmooth);
// Читаем воксель мира
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;
if (selfSolid) {
FragColor = vec4(0.0);
return;
}
float fogOld = texture(FogReadSampler, uv).r;
float fogNew = fogOld;
// Emission
float emission = getEmission(selfMagnitude, selfIsAbsorber) * finalEmission;
vec3 offsets[6];
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 tempN = packedNeighbor;
if (neighborSolid) tempN -= 128u;
bool neighborIsAbsorber = (tempN >= 64u);
if (neighborIsAbsorber) tempN -= 64u;
uint neighborMagnitude = tempN;
if (neighborSolid) {
emission += getEmission(neighborMagnitude, neighborIsAbsorber) * finalEmission;
}
}
fogNew += emission;
// 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 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;
// Diffusion
float diffusion = 0.0;
vec3 uvRight = uv + offsets[0];
uint packedRight = texture(WorldInfoSampler, uvRight).r;
if (!(packedRight >= 128u)) {
diffusion += (texture(FogReadSampler, uvRight).r - fogOld) * HorizontalDiffusion * DiffusionRate;
}
vec3 uvLeft = uv + offsets[1];
uint packedLeft = texture(WorldInfoSampler, uvLeft).r;
if (!(packedLeft >= 128u)) {
diffusion += (texture(FogReadSampler, uvLeft).r - fogOld) * HorizontalDiffusion * DiffusionRate;
}
vec3 uvUp = uv + offsets[2];
uint packedUp = texture(WorldInfoSampler, uvUp).r;
if (!(packedUp >= 128u)) {
diffusion += (texture(FogReadSampler, uvUp).r - fogOld) * UpDiffusion * DiffusionRate;
}
vec3 uvDown = uv + offsets[3];
uint packedDown = texture(WorldInfoSampler, uvDown).r;
if (!(packedDown >= 128u)) {
diffusion += (texture(FogReadSampler, uvDown).r - fogOld) * DownDiffusion * DiffusionRate;
}
vec3 uvFront = uv + offsets[4];
uint packedFront = texture(WorldInfoSampler, uvFront).r;
if (!(packedFront >= 128u)) {
diffusion += (texture(FogReadSampler, uvFront).r - fogOld) * HorizontalDiffusion * DiffusionRate;
}
vec3 uvBack = uv + offsets[5];
uint packedBack = texture(WorldInfoSampler, uvBack).r;
if (!(packedBack >= 128u)) {
diffusion += (texture(FogReadSampler, uvBack).r - fogOld) * HorizontalDiffusion * DiffusionRate;
}
fogNew += diffusion;
// Target Decay
fogNew -= (fogOld - finalTargetDensity) * GlobalDecay;
fogNew = clamp(fogNew, 0.0, 1.0);
FragColor = vec4(fogNew, 0.0, 0.0, 1.0);
}
@@ -5,7 +5,9 @@
"compatibilityLevel": "JAVA_17", "compatibilityLevel": "JAVA_17",
"client": [ "client": [
"GameRendererMixin", "GameRendererMixin",
"MinecraftClientMixin" "MinecraftClientMixin",
"WorldChunkMixin",
"LightStorageMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
+1
View File
@@ -2,6 +2,7 @@
"schemaVersion": 1, "schemaVersion": 1,
"id": "veila", "id": "veila",
"version": "${version}", "version": "${version}",
"accessWidener": "veila.accesswidener",
"name": "Veila", "name": "Veila",
"description": "", "description": "",
+9
View File
@@ -0,0 +1,9 @@
accessWidener v2 named
accessible method net/minecraft/world/chunk/PalettedContainer get (I)Ljava/lang/Object;
accessible field net/minecraft/world/chunk/light/LightingProvider blockLightProvider Lnet/minecraft/world/chunk/light/ChunkLightProvider;
accessible field net/minecraft/world/chunk/light/LightingProvider skyLightProvider Lnet/minecraft/world/chunk/light/ChunkLightProvider;
accessible field net/minecraft/world/biome/Biome weather Lnet/minecraft/world/biome/Biome$Weather;
accessible class net/minecraft/world/biome/Biome$Weather
accessible method net/minecraft/world/biome/Biome$Weather downfall ()F
accessible method net/minecraft/world/biome/Biome$Weather temperature ()F
accessible method net/minecraft/world/biome/Biome$Weather hasPrecipitation ()Z