Освещённость вокселей учитывается
This commit is contained in:
@@ -276,7 +276,7 @@ public class PostProcessingManager {
|
||||
|
||||
GL20.glUniform1f(
|
||||
skyBrightnessLocation,
|
||||
client.world.getSkyBrightness(tickDelta)*0.75f+0.25f
|
||||
client.world.getSkyBrightness(tickDelta)
|
||||
);
|
||||
|
||||
FullscreenQuad.draw();
|
||||
|
||||
@@ -10,7 +10,8 @@ import net.minecraft.world.chunk.light.LightingProvider;
|
||||
|
||||
public final class FogLightProvider {
|
||||
|
||||
public static final int SECTION_DATA_SIZE = 4096;
|
||||
// Размер массива для одной секции: 16×16×16×2 = 8192 (×2 для двух каналов)
|
||||
public static final int SECTION_DATA_SIZE = 16 * 16 * 16 * 2;
|
||||
|
||||
private FogLightProvider() {
|
||||
}
|
||||
@@ -21,16 +22,13 @@ public final class FogLightProvider {
|
||||
|
||||
LightingProvider provider = client.world.getLightingProvider();
|
||||
|
||||
// Прямой доступ к провайдерам через AccessWidener
|
||||
ChunkLightProvider<?, ?> blockProvider = provider.blockLightProvider;
|
||||
ChunkLightProvider<?, ?> skyProvider = provider.skyLightProvider;
|
||||
|
||||
// Проверка на null (поля @Nullable)
|
||||
if (blockProvider == null || skyProvider == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Используем ПУБЛИЧНЫЙ метод ChunkLightProvider.getLightSection()
|
||||
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
|
||||
|
||||
ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos);
|
||||
@@ -43,29 +41,19 @@ public final class FogLightProvider {
|
||||
byte[] result = new byte[SECTION_DATA_SIZE];
|
||||
int index = 0;
|
||||
|
||||
// Порядок заполнения: Z → Y → X (как в FogChunkProvider)
|
||||
for (int localZ = 0; localZ < 16; localZ++) {
|
||||
for (int localY = 0; localY < 16; localY++) {
|
||||
for (int localX = 0; localX < 16; localX++) {
|
||||
int blockLight = (blockArray != null) ? blockArray.get(localX, localY, localZ) : 0;
|
||||
int skyLight = (skyArray != null) ? skyArray.get(localX, localY, localZ) : 0;
|
||||
|
||||
result[index++] = FogLightVolume.packLight(blockLight, skyLight);
|
||||
// Упаковка в два байта: R=blockLight, G=skyLight
|
||||
FogLightVolume.packLight(blockLight, skyLight, result, index);
|
||||
index += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte computeLightValue(BlockPos pos) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.world == null) return FogLightVolume.DEFAULT_LIGHT;
|
||||
|
||||
LightingProvider provider = client.world.getLightingProvider();
|
||||
int blockLight = provider.get(LightType.BLOCK).getLightLevel(pos);
|
||||
int skyLight = provider.get(LightType.SKY).getLightLevel(pos);
|
||||
|
||||
return FogLightVolume.packLight(blockLight, skyLight);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,6 @@ import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static su.divan2000.veila.client.render.fog.FogLightStreamer.SECTION_SIZE;
|
||||
|
||||
public final class FogLightVolume {
|
||||
|
||||
public static final int SIZE_X = FogWorldVolume.SIZE_X;
|
||||
@@ -22,38 +20,40 @@ public final class FogLightVolume {
|
||||
|
||||
private static int texture = -1;
|
||||
|
||||
// Упаковка: bits 0-3 = BlockLight, bits 4-7 = SkyLight
|
||||
public static final byte DEFAULT_LIGHT = packLight(0, 15); // Темно, полное небо
|
||||
// Дефолтное освещение: blockLight=0, skyLight=15 (ночь, открытое небо)
|
||||
// Теперь это vec2, упакованный в RG8
|
||||
public static final byte DEFAULT_BLOCK_LIGHT = 0;
|
||||
public static final byte DEFAULT_SKY_LIGHT = 15;
|
||||
|
||||
private static final byte[] ZEROS_ARRAY;
|
||||
private static final byte[] DEFAULT_ARRAY;
|
||||
static {
|
||||
ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
|
||||
java.util.Arrays.fill(ZEROS_ARRAY, DEFAULT_LIGHT);
|
||||
DEFAULT_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE * 2]; // ×2 для двух каналов
|
||||
for (int i = 0; i < CHUNK_SIZE * SIZE_Y * CHUNK_SIZE; i++) {
|
||||
DEFAULT_ARRAY[i * 2] = DEFAULT_BLOCK_LIGHT; // R = blockLight
|
||||
DEFAULT_ARRAY[i * 2 + 1] = DEFAULT_SKY_LIGHT; // G = skyLight
|
||||
}
|
||||
}
|
||||
|
||||
private static final ByteBuffer CHUNK_BUFFER =
|
||||
BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE);
|
||||
BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE * 2);
|
||||
|
||||
private static final ByteBuffer SINGLE_VOXEL_BUFFER =
|
||||
BufferUtils.createByteBuffer(1);
|
||||
BufferUtils.createByteBuffer(2);
|
||||
|
||||
// PBO для быстрой очистки (как в FogWorldVolume)
|
||||
// PBO для быстрой очистки
|
||||
private static int clearPBO = -1;
|
||||
private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
|
||||
private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z * 2; // ×2 для двух каналов
|
||||
|
||||
private FogLightVolume() {
|
||||
}
|
||||
|
||||
public static byte packLight(int blockLight, int skyLight) {
|
||||
return (byte) (((skyLight & 0xF) << 4) | (blockLight & 0xF));
|
||||
}
|
||||
|
||||
public static int unpackBlockLight(byte packed) {
|
||||
return packed & 0xF;
|
||||
}
|
||||
|
||||
public static int unpackSkyLight(byte packed) {
|
||||
return (packed >> 4) & 0xF;
|
||||
/**
|
||||
* Упаковывает blockLight и skyLight в два байта для RG8 текстуры.
|
||||
* blockLight → R канал, skyLight → G канал.
|
||||
*/
|
||||
public static void packLight(int blockLight, int skyLight, byte[] out, int offset) {
|
||||
out[offset] = (byte) (blockLight * 17);
|
||||
out[offset + 1] = (byte) (skyLight * 17);
|
||||
}
|
||||
|
||||
private static void initClearPBO() {
|
||||
@@ -66,8 +66,9 @@ public final class FogLightVolume {
|
||||
|
||||
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
|
||||
if (mapped != null) {
|
||||
for (int i = 0; i < MAX_CLEAR_SIZE; i++) {
|
||||
mapped.put(DEFAULT_LIGHT);
|
||||
for (int i = 0; i < SIZE_X * SIZE_Y * SIZE_Z; i++) {
|
||||
mapped.put(DEFAULT_BLOCK_LIGHT);
|
||||
mapped.put(DEFAULT_SKY_LIGHT);
|
||||
}
|
||||
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
@@ -84,21 +85,23 @@ public final class FogLightVolume {
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
|
||||
// ИСПРАВЛЕНО: GL_RG8 вместо GL_R8UI, два канала для двух типов света
|
||||
GL30.glTexImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
GL30.GL_R8UI,
|
||||
GL30.GL_RG8,
|
||||
SIZE_X,
|
||||
SIZE_Y,
|
||||
SIZE_Z,
|
||||
0,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL30.GL_RG,
|
||||
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);
|
||||
// GL_LINEAR для плавной интерполяции
|
||||
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);
|
||||
@@ -111,7 +114,7 @@ public final class FogLightVolume {
|
||||
|
||||
public static void clear() {
|
||||
CHUNK_BUFFER.clear();
|
||||
CHUNK_BUFFER.put(ZEROS_ARRAY);
|
||||
CHUNK_BUFFER.put(DEFAULT_ARRAY);
|
||||
CHUNK_BUFFER.flip();
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
@@ -143,7 +146,7 @@ public final class FogLightVolume {
|
||||
CHUNK_SIZE,
|
||||
SIZE_Y,
|
||||
CHUNK_SIZE,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL30.GL_RG, // ИСПРАВЛЕНО: GL_RG вместо GL_RED_INTEGER
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
data
|
||||
);
|
||||
@@ -151,19 +154,17 @@ public final class FogLightVolume {
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
public static void updateSingleVoxel(
|
||||
int textureChunkX,
|
||||
int textureChunkZ,
|
||||
int localX,
|
||||
int localY,
|
||||
int localZ,
|
||||
byte value
|
||||
public static void uploadSection(
|
||||
int textureSectionX,
|
||||
int textureSectionY,
|
||||
int textureSectionZ,
|
||||
byte[] data
|
||||
) {
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
ByteBuffer sectionBuffer = BufferUtils.createByteBuffer(data.length);
|
||||
sectionBuffer.put(data);
|
||||
sectionBuffer.flip();
|
||||
|
||||
SINGLE_VOXEL_BUFFER.clear();
|
||||
SINGLE_VOXEL_BUFFER.put(value);
|
||||
SINGLE_VOXEL_BUFFER.flip();
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
|
||||
@@ -173,13 +174,15 @@ public final class FogLightVolume {
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
textureChunkX * CHUNK_SIZE + localX,
|
||||
localY,
|
||||
textureChunkZ * CHUNK_SIZE + localZ,
|
||||
1, 1, 1,
|
||||
GL30.GL_RED_INTEGER,
|
||||
textureSectionX * CHUNK_SIZE,
|
||||
textureSectionY * 16,
|
||||
textureSectionZ * CHUNK_SIZE,
|
||||
16,
|
||||
16,
|
||||
16,
|
||||
GL30.GL_RG,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
SINGLE_VOXEL_BUFFER
|
||||
sectionBuffer
|
||||
);
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
@@ -230,7 +233,7 @@ public final class FogLightVolume {
|
||||
0,
|
||||
physZ[i] * CHUNK_SIZE,
|
||||
w, h, d,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL30.GL_RG,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
0L
|
||||
);
|
||||
@@ -239,46 +242,4 @@ public final class FogLightVolume {
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
// Статический буфер для одной секции (4096 байт)
|
||||
private static final ByteBuffer SECTION_BUFFER =
|
||||
BufferUtils.createByteBuffer(SECTION_SIZE * SECTION_SIZE * SECTION_SIZE);
|
||||
|
||||
/**
|
||||
* Загружает одну секцию (16×16×16) в указанную позицию текстуры.
|
||||
* textureSectionX/Z - в чанках (0-15), textureSectionY - индекс секции по Y (0-23).
|
||||
*/
|
||||
public static void uploadSection(
|
||||
int textureSectionX,
|
||||
int textureSectionY,
|
||||
int textureSectionZ,
|
||||
byte[] data
|
||||
) {
|
||||
SECTION_BUFFER.clear();
|
||||
SECTION_BUFFER.put(data);
|
||||
SECTION_BUFFER.flip();
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
|
||||
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
textureSectionX * CHUNK_SIZE,
|
||||
textureSectionY * SECTION_SIZE,
|
||||
textureSectionZ * CHUNK_SIZE,
|
||||
SECTION_SIZE,
|
||||
SECTION_SIZE,
|
||||
SECTION_SIZE,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
SECTION_BUFFER
|
||||
);
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
uniform sampler2D DiffuseSampler;
|
||||
uniform sampler2D DepthSampler;
|
||||
uniform sampler3D FogVolume;
|
||||
uniform sampler3D LightVolume;
|
||||
|
||||
uniform mat4 InverseProjection;
|
||||
uniform mat4 InverseView;
|
||||
@@ -28,22 +29,33 @@ 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;
|
||||
|
||||
// Коэффициент нелинейности распределения шагов:
|
||||
// 1.0 = равномерные шаги (как было)
|
||||
// 1.5 = мягкое увеличение (рекомендуется для начала)
|
||||
// 2.0 = линейный рост размера шага с расстоянием (хороший баланс)
|
||||
// 2.5 = более агрессивное увеличение (лучшая производительность)
|
||||
const float GAMMA = 2.0;
|
||||
|
||||
// Включение jittering для устранения banding артефактов
|
||||
const bool USE_JITTERING = false;
|
||||
|
||||
// Физические коэффициенты volumetric rendering
|
||||
// EXTINCTION: насколько сильно туман поглощает/рассеивает свет (0.1-2.0)
|
||||
// Больше значение = туман сильнее затеняет далёкие объекты
|
||||
const float EXTINCTION_COEFF = 1.0;
|
||||
|
||||
// SCATTERING: насколько сильно туман сам светится от источников
|
||||
// Обычно равен EXTINCTION (консервативная среда), но можно варьировать
|
||||
const float SCATTERING_COEFF = 1.0;
|
||||
|
||||
// Ambient — базовое освещение среды (воздух, микрочастицы).
|
||||
// Не зависит от времени суток и от skyLight/blockLight.
|
||||
// Даёт минимальную видимость тумана даже в полной темноте.
|
||||
const float AMBIENT_INTENSITY = 0.10;
|
||||
|
||||
// Порог transmittance для early termination (оптимизация)
|
||||
// Если transmittance упал ниже этого значения, дальнейшие шаги ничего не внесут
|
||||
const float MIN_TRANSMITTANCE = 0.01;
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
vec3 reconstructViewPosition(vec2 uv)
|
||||
@@ -57,25 +69,20 @@ vec3 reconstructViewPosition(vec2 uv)
|
||||
);
|
||||
|
||||
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 worldToUV(vec3 worldPos)
|
||||
{
|
||||
vec3 uv;
|
||||
|
||||
float localX = worldPos.x - float(FogOrigin.x);
|
||||
float localZ = worldPos.z - float(FogOrigin.y);
|
||||
|
||||
@@ -85,25 +92,43 @@ float density(vec3 worldPos)
|
||||
worldPos.y < WORLD_BOTTOM ||
|
||||
worldPos.y >= WORLD_BOTTOM + WORLD_HEIGHT
|
||||
){
|
||||
return 0.0;
|
||||
return vec3(-1.0);
|
||||
}
|
||||
|
||||
float physicalX =
|
||||
localX + float(RingBlockOffset.x);
|
||||
|
||||
float physicalZ =
|
||||
localZ + float(RingBlockOffset.y);
|
||||
float physicalX = localX + float(RingBlockOffset.x);
|
||||
float physicalZ = localZ + float(RingBlockOffset.y);
|
||||
|
||||
vec3 uv;
|
||||
uv.x = physicalX / FOG_SIZE_X;
|
||||
uv.z = physicalZ / FOG_SIZE_Z;
|
||||
uv.y = (worldPos.y - WORLD_BOTTOM) / WORLD_HEIGHT;
|
||||
|
||||
return uv;
|
||||
}
|
||||
|
||||
float density(vec3 worldPos)
|
||||
{
|
||||
vec3 uv = worldToUV(worldPos);
|
||||
if (uv.x < 0.0) return 0.0;
|
||||
return texture(FogVolume, uv).r;
|
||||
}
|
||||
|
||||
vec2 light(vec3 worldPos)
|
||||
{
|
||||
vec3 uv = worldToUV(worldPos);
|
||||
if (uv.x < 0.0) return vec2(0.0);
|
||||
|
||||
vec2 rg = texture(LightVolume, uv).rg;
|
||||
float blockNorm = rg.r;
|
||||
float skyNorm = rg.g;
|
||||
|
||||
float skyContribution = skyNorm * SkyBrightness;
|
||||
|
||||
return vec2(skyContribution, blockNorm);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
// Псевдослучайное значение для jittering (детерминированное по UV)
|
||||
float random(vec2 st)
|
||||
{
|
||||
vec3 p3 = fract(vec3(st.xyx) * 0.1031);
|
||||
@@ -111,8 +136,6 @@ float random(vec2 st)
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
// Функция нелинейного маппинга параметра t [0,1] в расстояние
|
||||
// Использует степенную функцию для распределения шагов
|
||||
float mapParameterToDistance(float t)
|
||||
{
|
||||
return MAX_DISTANCE * pow(t, GAMMA);
|
||||
@@ -125,22 +148,26 @@ 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;
|
||||
float currentDistance = 0.0;
|
||||
|
||||
// Jittering начальной позиции для устранения banding (ступенчатых артефактов)
|
||||
// Это стандартная техника в volumetric rendering
|
||||
// Transmittance: сколько света от сцены дошло до текущего шага
|
||||
// Начинается с 1.0 (всё доходит) и экспоненциально падает
|
||||
float transmittance = 1.0;
|
||||
|
||||
// Накопленный in-scattered свет (взвешенный по transmittance)
|
||||
float skyScattered = 0.0;
|
||||
float blockScattered = 0.0;
|
||||
|
||||
float ambientScattered = 0.0;
|
||||
|
||||
float jitter = 0.0;
|
||||
if (USE_JITTERING) {
|
||||
// Вычисляем размер первого шага для корректного jittering
|
||||
float firstStep = mapParameterToDistance(1.0 / float(MAX_STEPS));
|
||||
jitter = random(texCoord) * firstStep;
|
||||
currentDistance = jitter;
|
||||
@@ -148,44 +175,74 @@ void main()
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_STEPS; i++) {
|
||||
// Параметр t меняется от 0 до 1
|
||||
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;
|
||||
|
||||
// Интегрируем плотность (Beer-Lambert law approximation)
|
||||
// Важно умножать на stepSize для корректной физической модели
|
||||
fog += density(samplePos) * 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;
|
||||
|
||||
// Базовая формула in-scattering для каждого источника
|
||||
float baseScattering = d * stepSize * SCATTERING_COEFF;
|
||||
|
||||
// In-scattering: свет, рассеянный в камерy из этого шага.
|
||||
// Умножается на transmittance до этого шага (дальний туман затенён ближним).
|
||||
// Умножается на плотность (больше частиц = больше рассеяния).
|
||||
float stepScattering = totalLightAtPoint * d * stepSize * SCATTERING_COEFF;
|
||||
|
||||
// Вклад ослабляется текущим transmittance
|
||||
skyScattered += lightValues.x * baseScattering * transmittance;
|
||||
blockScattered += lightValues.y * baseScattering * transmittance;
|
||||
ambientScattered += AMBIENT_INTENSITY * baseScattering * transmittance;
|
||||
|
||||
// Обновляем transmittance: Beer-Lambert law
|
||||
transmittance *= exp(-opticalDepth);
|
||||
}
|
||||
|
||||
currentDistance = nextDistance;
|
||||
|
||||
// Прерываем, если достигли конца луча
|
||||
// Early termination: если transmittance упал почти до нуля,
|
||||
// дальние шаги ничего не внесут в итоговый цвет
|
||||
if (transmittance < MIN_TRANSMITTANCE) {
|
||||
transmittance = 0.0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentDistance >= rayLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fog = clamp(fog, 0.0, 0.95);
|
||||
// Суммарное освещение, рассеянное в камеру
|
||||
float totalScattered = skyScattered + blockScattered + ambientScattered;
|
||||
|
||||
FragColor = vec4(
|
||||
mix(sceneColor.rgb, FogColor*SkyBrightness, fog),
|
||||
1.0
|
||||
);
|
||||
// Финальный цвет:
|
||||
// - sceneColor × transmittance: сцена, ослабленная туманом (Beer-Lambert)
|
||||
// - FogColor × totalScattered: свет, который рассеялся в тумане и попал в камеру
|
||||
//
|
||||
// Эта формула физически корректна:
|
||||
// - Когда тумана нет (transmittance=1, scattered=0): result = sceneColor ✓
|
||||
// - Когда туман очень плотный (transmittance=0): result = FogColor × scattered ✓
|
||||
// - Фонарь в толще тумана: ближние слои дают scattered, дальние уже не видны
|
||||
vec3 finalColor = sceneColor.rgb * transmittance + FogColor * totalScattered;
|
||||
|
||||
FragColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
Reference in New Issue
Block a user