Симуляция работает

This commit is contained in:
2026-08-02 03:16:51 +04:00
parent 41238fddb0
commit efc345fa0b
9 changed files with 640 additions and 3 deletions
@@ -13,6 +13,7 @@ import su.divan2000.veila.client.gl.FullscreenQuad;
import su.divan2000.veila.client.gl.GLProgram; import su.divan2000.veila.client.gl.GLProgram;
import su.divan2000.veila.client.gl.GLShader; import su.divan2000.veila.client.gl.GLShader;
import su.divan2000.veila.client.gl.ResourceUtil; import su.divan2000.veila.client.gl.ResourceUtil;
import su.divan2000.veila.client.render.fog.FogSimulationManager;
import su.divan2000.veila.client.render.fog.FogSystem; import su.divan2000.veila.client.render.fog.FogSystem;
import su.divan2000.veila.client.render.fog.FogWorldVolume; import su.divan2000.veila.client.render.fog.FogWorldVolume;
@@ -154,7 +155,7 @@ public class PostProcessingManager {
GL11.glBindTexture( GL11.glBindTexture(
GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_3D,
FogWorldVolume.getTexture() FogSimulationManager.getCurrentDensityTexture()
); );
GL20.glUniform1i( GL20.glUniform1i(
@@ -242,6 +242,8 @@ public final class FogChunkStreamer {
int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX; int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX;
FogWorldVolume.uploadZeros(physicalX, physicalZ); FogWorldVolume.uploadZeros(physicalX, physicalZ);
FogDensityTextures.clearColumn(physicalX, physicalZ);
statuses[physicalIndex] = ChunkStatus.PENDING; statuses[physicalIndex] = ChunkStatus.PENDING;
readyToUpload[physicalIndex] = false; readyToUpload[physicalIndex] = false;
} }
@@ -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);
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -28,6 +28,8 @@ public final class FogSystem {
// Всегда обрабатываем изменения блоков // Всегда обрабатываем изменения блоков
FogChunkStreamer.processBlockChanges(); FogChunkStreamer.processBlockChanges();
FogSimulationManager.update();
} }
public static void tick() { public static void tick() {
@@ -36,6 +38,7 @@ public final class FogSystem {
private static void initialize() { private static void initialize() {
FogWorldVolume.init(); FogWorldVolume.init();
FogSimulationManager.init();
initialized = true; initialized = true;
} }
@@ -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();
}
} }
} }
@@ -2,7 +2,7 @@
uniform sampler2D DiffuseSampler; uniform sampler2D DiffuseSampler;
uniform sampler2D DepthSampler; uniform sampler2D DepthSampler;
uniform usampler3D FogVolume; uniform sampler3D FogVolume;
uniform mat4 InverseProjection; uniform mat4 InverseProjection;
uniform mat4 InverseView; uniform mat4 InverseView;
@@ -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);
}