Освещённость вокселей учитывается

This commit is contained in:
2026-08-04 20:15:35 +04:00
parent 8c1809a556
commit 76370f71e2
4 changed files with 157 additions and 151 deletions
@@ -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);
}