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