From efc345fa0b5de7e36cde72f37699d366a2983032 Mon Sep 17 00:00:00 2001 From: DIvan2000 Date: Sun, 2 Aug 2026 03:16:51 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B8=D0=BC=D1=83=D0=BB=D1=8F=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/render/PostProcessingManager.java | 3 +- .../client/render/fog/FogChunkStreamer.java | 2 + .../client/render/fog/FogDensityTextures.java | 121 +++++++++ .../render/fog/FogSimulationManager.java | 73 ++++++ .../render/fog/FogSimulationShader.java | 243 ++++++++++++++++++ .../veila/client/render/fog/FogSystem.java | 3 + .../veila/mixin/client/GameRendererMixin.java | 5 +- .../resources/assets/veila/shaders/red.frag | 2 +- .../resources/assets/veila/shaders/sim.frag | 191 ++++++++++++++ 9 files changed, 640 insertions(+), 3 deletions(-) create mode 100644 src/client/java/su/divan2000/veila/client/render/fog/FogDensityTextures.java create mode 100644 src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java create mode 100644 src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java create mode 100644 src/client/resources/assets/veila/shaders/sim.frag diff --git a/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java b/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java index 7838a53..605647d 100644 --- a/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java +++ b/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java @@ -13,6 +13,7 @@ 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 su.divan2000.veila.client.render.fog.FogSimulationManager; import su.divan2000.veila.client.render.fog.FogSystem; import su.divan2000.veila.client.render.fog.FogWorldVolume; @@ -154,7 +155,7 @@ public class PostProcessingManager { GL11.glBindTexture( GL12.GL_TEXTURE_3D, - FogWorldVolume.getTexture() + FogSimulationManager.getCurrentDensityTexture() ); GL20.glUniform1i( diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogChunkStreamer.java b/src/client/java/su/divan2000/veila/client/render/fog/FogChunkStreamer.java index aaac3ff..cfe68dd 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogChunkStreamer.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogChunkStreamer.java @@ -242,6 +242,8 @@ public final class FogChunkStreamer { int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX; FogWorldVolume.uploadZeros(physicalX, physicalZ); + FogDensityTextures.clearColumn(physicalX, physicalZ); + statuses[physicalIndex] = ChunkStatus.PENDING; readyToUpload[physicalIndex] = false; } diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogDensityTextures.java b/src/client/java/su/divan2000/veila/client/render/fog/FogDensityTextures.java new file mode 100644 index 0000000..ee0e37c --- /dev/null +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogDensityTextures.java @@ -0,0 +1,121 @@ +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.GL30; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public final class FogDensityTextures { + + private static int textureA = -1; + private static int textureB = -1; + private static int readIndex = 0; + + // Float буфер с нулями для одного столбца 16×384×16 + private static final FloatBuffer ZEROS_BUFFER = + BufferUtils.createFloatBuffer( + FogWorldVolume.CHUNK_SIZE * + FogWorldVolume.SIZE_Y * + FogWorldVolume.CHUNK_SIZE + ); + + static { + int size = FogWorldVolume.CHUNK_SIZE * FogWorldVolume.SIZE_Y * FogWorldVolume.CHUNK_SIZE; + for (int i = 0; i < size; i++) { + ZEROS_BUFFER.put(0.0f); + } + ZEROS_BUFFER.flip(); + } + + private FogDensityTextures() { + } + + public static void init() { + if (textureA != -1) return; + + textureA = createDensityTexture(); + textureB = createDensityTexture(); + } + + 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, + 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_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() { + readIndex = 1 - readIndex; + } + + public static int getReadTexture() { + return readIndex == 0 ? textureA : textureB; + } + + public static int getWriteTexture() { + return readIndex == 0 ? textureB : textureA; + } + + /** + * Обнуляет столбец (один чанк) в ОБОИХ density текстурах. + * Вызывается при смене окна, чтобы не было фантомного тумана. + */ + public static void clearColumn(int textureChunkX, int textureChunkZ) { + clearColumnInTexture(textureA, textureChunkX, textureChunkZ); + clearColumnInTexture(textureB, textureChunkX, textureChunkZ); + } + + private static void clearColumnInTexture(int texture, int textureChunkX, int textureChunkZ) { + 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); + + // Сбрасываем position буфера + ZEROS_BUFFER.rewind(); + + GL30.glTexSubImage3D( + GL12.GL_TEXTURE_3D, + 0, + textureChunkX * FogWorldVolume.CHUNK_SIZE, + 0, + textureChunkZ * FogWorldVolume.CHUNK_SIZE, + FogWorldVolume.CHUNK_SIZE, + FogWorldVolume.SIZE_Y, + FogWorldVolume.CHUNK_SIZE, + GL11.GL_RED, + GL11.GL_FLOAT, + ZEROS_BUFFER + ); + + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + } +} \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java new file mode 100644 index 0000000..49de4f1 --- /dev/null +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java @@ -0,0 +1,73 @@ +package su.divan2000.veila.client.render.fog; + +public final class FogSimulationManager { + + private static boolean initialized = false; + private static long lastSimulationTime = 0; + + // Симулируем каждые 100 мс (10 раз в секунду) + private static final long SIMULATION_INTERVAL_NS = 100_000_000L; + + private static float globalEmissionMultiplier = 1.0f; + private static float globalAbsorptionMultiplier = 1.0f; + private static float globalDecay = 0.01f; + private static float diffusionRate = 0.1f; + private static float upDiffusion = 0.9f; + private static float downDiffusion = 1.1f; + 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 (currentTime - lastSimulationTime >= SIMULATION_INTERVAL_NS) { + lastSimulationTime = currentTime; + simulate(); + } + } + + private static void simulate() { + FogSimulationShader.simulate( + globalEmissionMultiplier, + globalAbsorptionMultiplier, + globalDecay, + diffusionRate, + upDiffusion, + downDiffusion, + horizontalDiffusion + ); + } + + public static int getCurrentDensityTexture() { + return FogDensityTextures.getReadTexture(); + } + + public static void setGlobalEmissionMultiplier(float value) { + globalEmissionMultiplier = value; + } + + public static void setGlobalAbsorptionMultiplier(float value) { + globalAbsorptionMultiplier = value; + } + + public static void setGlobalDecay(float value) { + globalDecay = value; + } +} \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java new file mode 100644 index 0000000..0b299ab --- /dev/null +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java @@ -0,0 +1,243 @@ +package su.divan2000.veila.client.render.fog; + +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 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"); + + 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 + ) { + 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); + + // Привязки текстур для юнитов 0 и 1 (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); + + // 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); + + // Устанавливаем параметры + 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.glUniform3f(voxelSizeLocation, + 1.0f / FogWorldVolume.SIZE_X, + 1.0f / FogWorldVolume.SIZE_Y, + 1.0f / FogWorldVolume.SIZE_Z + ); + + // ======================================== + // РЕНДЕРИНГ + // ======================================== + + for (int z = 0; z < FogWorldVolume.SIZE_Z; z++) { + GL30.glFramebufferTexture3D( + GL30.GL_FRAMEBUFFER, + GL30.GL_COLOR_ATTACHMENT0, + GL12.GL_TEXTURE_3D, + fogWrite, + 0, + z + ); + + GL20.glUniform1f(currentZLocation, (float) z / 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(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); + + // Меняем текстуры местами + FogDensityTextures.swap(); + } +} \ No newline at end of file diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java b/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java index eeabc4c..18a1891 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogSystem.java @@ -28,6 +28,8 @@ public final class FogSystem { // Всегда обрабатываем изменения блоков FogChunkStreamer.processBlockChanges(); + + FogSimulationManager.update(); } public static void tick() { @@ -36,6 +38,7 @@ public final class FogSystem { private static void initialize() { FogWorldVolume.init(); + FogSimulationManager.init(); initialized = true; } diff --git a/src/client/java/su/divan2000/veila/mixin/client/GameRendererMixin.java b/src/client/java/su/divan2000/veila/mixin/client/GameRendererMixin.java index 4153ce6..15536a4 100644 --- a/src/client/java/su/divan2000/veila/mixin/client/GameRendererMixin.java +++ b/src/client/java/su/divan2000/veila/mixin/client/GameRendererMixin.java @@ -61,6 +61,9 @@ public class GameRendererMixin { boolean tick, CallbackInfo ci ) { - FogSystem.beginRender(); + net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance(); + if (client.world != null && client.player != null) { + FogSystem.beginRender(); + } } } \ No newline at end of file diff --git a/src/client/resources/assets/veila/shaders/red.frag b/src/client/resources/assets/veila/shaders/red.frag index 5923076..28ffca7 100644 --- a/src/client/resources/assets/veila/shaders/red.frag +++ b/src/client/resources/assets/veila/shaders/red.frag @@ -2,7 +2,7 @@ uniform sampler2D DiffuseSampler; uniform sampler2D DepthSampler; -uniform usampler3D FogVolume; +uniform sampler3D FogVolume; uniform mat4 InverseProjection; uniform mat4 InverseView; diff --git a/src/client/resources/assets/veila/shaders/sim.frag b/src/client/resources/assets/veila/shaders/sim.frag new file mode 100644 index 0000000..f3cb629 --- /dev/null +++ b/src/client/resources/assets/veila/shaders/sim.frag @@ -0,0 +1,191 @@ +#version 150 + +uniform usampler3D WorldInfoSampler; +uniform sampler3D FogReadSampler; + +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; + +in vec2 texCoord; +out vec4 FragColor; + +// Получение эмиссии из magnitude +float getEmission(uint magnitude, bool isAbsorber) { + if (isAbsorber || magnitude == 0u) return 0.0; + return float(magnitude) / 63.0 * 0.05; +} + +// Получение абсорбции из magnitude +float getAbsorption(uint magnitude, bool isAbsorber) { + if (!isAbsorber || magnitude == 0u) return 0.0; + return float(magnitude) / 63.0 * 0.05; +} + +void main() { + vec3 uv = vec3(texCoord, CurrentZ); + + // Читаем упакованное значение + uint packedSelf = texture(WorldInfoSampler, uv).r; + + // Распаковываем через арифметику (битовые операции не доступны в GLSL 1.50) + bool selfSolid = (packedSelf >= 128u); + uint tempSelf = packedSelf; + if (selfSolid) tempSelf -= 128u; + + bool selfIsAbsorber = (tempSelf >= 64u); + if (selfIsAbsorber) tempSelf -= 64u; + + uint selfMagnitude = tempSelf; + + // Если solid, плотность всегда 0 + if (selfSolid) { + FragColor = vec4(0.0); + return; + } + + // Читаем текущую плотность + float fogOld = texture(FogReadSampler, uv).r; + float fogNew = fogOld; + + // === 1. Emission === + float emission = 0.0; + + // Эмиссия от текущего вокселя (если non-solid источник) + emission += getEmission(selfMagnitude, selfIsAbsorber) * GlobalEmissionMultiplier; + + // Эмиссия от соседних solid источников + // Инициализируем массив через индексы (так надежнее в GLSL 1.50) + vec3 offsets[6]; + offsets[0] = vec3(VoxelSize.x, 0.0, 0.0); // +X + offsets[1] = vec3(-VoxelSize.x, 0.0, 0.0); // -X + offsets[2] = vec3(0.0, VoxelSize.y, 0.0); // +Y (Up) + offsets[3] = vec3(0.0, -VoxelSize.y, 0.0); // -Y (Down) + offsets[4] = vec3(0.0, 0.0, VoxelSize.z); // +Z + offsets[5] = vec3(0.0, 0.0, -VoxelSize.z); // -Z + + for (int i = 0; i < 6; i++) { + vec3 neighborUV = uv + offsets[i]; + uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r; + + // Распаковываем соседа + bool neighborSolid = (packedNeighbor >= 128u); + uint tempNeighbor = packedNeighbor; + if (neighborSolid) tempNeighbor -= 128u; + + bool neighborIsAbsorber = (tempNeighbor >= 64u); + if (neighborIsAbsorber) tempNeighbor -= 64u; + + uint neighborMagnitude = tempNeighbor; + + // Solid источник влияет на non-solid соседей + if (neighborSolid) { + emission += getEmission(neighborMagnitude, neighborIsAbsorber) * GlobalEmissionMultiplier; + } + } + + fogNew += emission; + + // === 2. Absorption === + float absorption = 0.0; + + // Поглощение от текущего вокселя (если non-solid поглотитель) + absorption += fogOld * getAbsorption(selfMagnitude, selfIsAbsorber) * GlobalAbsorptionMultiplier; + + // Поглощение от соседних solid поглотителей + for (int i = 0; i < 6; i++) { + vec3 neighborUV = uv + offsets[i]; + uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r; + + // Распаковываем соседа + bool neighborSolid = (packedNeighbor >= 128u); + uint tempNeighbor = packedNeighbor; + if (neighborSolid) tempNeighbor -= 128u; + + bool neighborIsAbsorber = (tempNeighbor >= 64u); + if (neighborIsAbsorber) tempNeighbor -= 64u; + + uint neighborMagnitude = tempNeighbor; + + // Solid поглотитель влияет на non-solid соседей + if (neighborSolid) { + absorption += fogOld * getAbsorption(neighborMagnitude, neighborIsAbsorber) * GlobalAbsorptionMultiplier; + } + } + + fogNew -= absorption; + + // === 3. Diffusion === + float diffusion = 0.0; + + // +X (right) + vec3 uvRight = uv + offsets[0]; + uint packedRight = texture(WorldInfoSampler, uvRight).r; + bool rightSolid = (packedRight >= 128u); + if (!rightSolid) { + float fogRight = texture(FogReadSampler, uvRight).r; + diffusion += (fogRight - fogOld) * HorizontalDiffusion * DiffusionRate; + } + + // -X (left) + vec3 uvLeft = uv + offsets[1]; + uint packedLeft = texture(WorldInfoSampler, uvLeft).r; + bool leftSolid = (packedLeft >= 128u); + if (!leftSolid) { + float fogLeft = texture(FogReadSampler, uvLeft).r; + diffusion += (fogLeft - fogOld) * HorizontalDiffusion * DiffusionRate; + } + + // +Y (up) + vec3 uvUp = uv + offsets[2]; + uint packedUp = texture(WorldInfoSampler, uvUp).r; + bool upSolid = (packedUp >= 128u); + if (!upSolid) { + float fogUp = texture(FogReadSampler, uvUp).r; + diffusion += (fogUp - fogOld) * UpDiffusion * DiffusionRate; + } + + // -Y (down) + vec3 uvDown = uv + offsets[3]; + uint packedDown = texture(WorldInfoSampler, uvDown).r; + bool downSolid = (packedDown >= 128u); + if (!downSolid) { + float fogDown = texture(FogReadSampler, uvDown).r; + diffusion += (fogDown - fogOld) * DownDiffusion * DiffusionRate; + } + + // +Z + vec3 uvFront = uv + offsets[4]; + uint packedFront = texture(WorldInfoSampler, uvFront).r; + bool frontSolid = (packedFront >= 128u); + if (!frontSolid) { + float fogFront = texture(FogReadSampler, uvFront).r; + diffusion += (fogFront - fogOld) * HorizontalDiffusion * DiffusionRate; + } + + // -Z + vec3 uvBack = uv + offsets[5]; + uint packedBack = texture(WorldInfoSampler, uvBack).r; + bool backSolid = (packedBack >= 128u); + if (!backSolid) { + float fogBack = texture(FogReadSampler, uvBack).r; + diffusion += (fogBack - fogOld) * HorizontalDiffusion * DiffusionRate; + } + + fogNew += diffusion; + + // === 4. Global Decay === + fogNew -= fogOld * GlobalDecay; + + // === 5. Clamp === + fogNew = clamp(fogNew, 0.0, 1.0); + + FragColor = vec4(fogNew, 0.0, 0.0, 1.0); +} \ No newline at end of file