Полностью корректный кольцевой буффер

This commit is contained in:
2026-07-31 23:56:47 +04:00
parent 5e3f985025
commit eb497f257a
10 changed files with 519 additions and 247 deletions
+2
View File
@@ -20,6 +20,8 @@ loom {
sourceSet sourceSets.client
}
}
accessWidenerPath = file("src/main/resources/veila.accesswidener")
}
fabricApi {
@@ -1,58 +1,100 @@
package su.divan2000.veila.client.render.fog;
import java.nio.FloatBuffer;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.chunk.PalettedContainer;
import java.util.Arrays;
public final class FogChunkProvider {
static void fillChunk(
int chunkX,
int chunkZ,
FloatBuffer buffer
) {
float density =
hash(chunkX, chunkZ);
public static boolean isChunkLoaded(int chunkX, int chunkZ) {
MinecraftClient client = MinecraftClient.getInstance();
for (int z = 0;
z < FogWorldVolume.CHUNK_SIZE;
z++) {
for (int y = 0;
y < FogWorldVolume.SIZE_Y;
y++) {
int worldY =
FogWorldVolume.MIN_Y + y;
float value =
worldY < 70
? density
: 0.0f;
for (int x = 0;
x < FogWorldVolume.CHUNK_SIZE;
x++) {
buffer.put(value);
if (client.world == null) {
return false;
}
return client.world.getChunkManager().getWorldChunk(chunkX, chunkZ, false) != null;
}
public static float computeVoxelValue(BlockState state) {
if (state.isOf(Blocks.POPPY)) {
return 0.2f;
} else if (state.isOf(Blocks.GRASS)) {
return 0.4f;
} else {
return 0.0f;
}
}
private static float hash(
int x,
int z
) {
public static boolean fillChunkIntoArray(int chunkX, int chunkZ, float[] array) {
MinecraftClient client = MinecraftClient.getInstance();
int h = x * 73428767;
if (!isChunkLoaded(chunkX, chunkZ)) return false;
h ^= z * 912931;
WorldChunk chunk = client.world.getChunk(chunkX, chunkZ);
h ^= h >> 13;
ChunkSection[] sections = chunk.getSectionArray();
int bottomY = client.world.getBottomY();
int topYExclusive = bottomY + sections.length * 16;
h *= 1274126177;
int index = 0;
return ((h >>> 24) & 255)
/ 255.0f
* 0.08f;
// Порядок циклов Z -> Y -> X критичен для glTexSubImage3D
for (int z = 0; z < FogWorldVolume.CHUNK_SIZE; z++) {
// Кэшируем секции и их состояние
int currentSectionIndex = -1;
ChunkSection currentSection = null;
PalettedContainer<BlockState> currentContainer = null;
boolean currentIsEmpty = false;
for (int y = 0; y < FogWorldVolume.SIZE_Y; y++) {
int worldY = FogWorldVolume.MIN_Y + y;
// За пределами мира (например, ниже -64)
if (worldY < bottomY || worldY >= topYExclusive) {
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, 0.0f);
index += FogWorldVolume.CHUNK_SIZE;
continue;
}
int newSectionIndex = (worldY - bottomY) >> 4;
if (newSectionIndex != currentSectionIndex) {
currentSectionIndex = newSectionIndex;
currentSection = sections[currentSectionIndex];
if (currentSection != null) {
currentContainer = currentSection.getBlockStateContainer();
currentIsEmpty = currentSection.isEmpty();
} else {
currentContainer = null;
currentIsEmpty = true;
}
}
int localY = worldY & 15;
// ОПТИМИЗАЦИЯ: Если текущая секция полностью пустая (только воздух)
if (currentIsEmpty) {
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, 0.0f);
index += FogWorldVolume.CHUNK_SIZE;
continue;
}
// Предварительное вычисление базового индекса для строки Y и Z
// Формула локального индекса в секции 16x16x16: (y << 8) | (z << 4) | x
int baseIndex = (localY << 8) | (z << 4);
for (int x = 0; x < FogWorldVolume.CHUNK_SIZE; x++) {
BlockState currentBlock = currentContainer.get(baseIndex | x);
array[index++] = computeVoxelValue(currentBlock);
}
}
}
return true;
}
}
@@ -1,173 +1,287 @@
package su.divan2000.veila.client.render.fog;
import java.nio.FloatBuffer;
import net.minecraft.block.BlockState;
import static su.divan2000.veila.client.render.fog.FogWorldVolume.CHUNK_SIZE;
import static su.divan2000.veila.client.render.fog.FogWorldVolume.SIZE_Y;
import java.util.concurrent.ConcurrentLinkedQueue;
public final class FogChunkStreamer {
private enum ChunkStatus {
PENDING,
LOADED
}
// Очередь изменений блоков (потокобезопасная)
private static final ConcurrentLinkedQueue<BlockChangeEvent> blockChangeQueue =
new ConcurrentLinkedQueue<>();
private static final int TOTAL_CHUNKS =
FogWorldVolume.CHUNKS_X * FogWorldVolume.CHUNKS_Z;
private static final int CHUNK_DATA_SIZE =
FogWorldVolume.CHUNK_SIZE *
FogWorldVolume.SIZE_Y *
FogWorldVolume.CHUNK_SIZE;
private static final int MAX_CHUNKS_PER_TICK = 4;
private static final ChunkStatus[] statuses = new ChunkStatus[TOTAL_CHUNKS];
private static final boolean[] readyToUpload = new boolean[TOTAL_CHUNKS];
private static final float[][] preparedData = new float[TOTAL_CHUNKS][CHUNK_DATA_SIZE];
private static final int[] preparedWorldChunkX = new int[TOTAL_CHUNKS];
private static final int[] preparedWorldChunkZ = new int[TOTAL_CHUNKS];
private static volatile int currentOriginChunkX;
private static volatile int currentOriginChunkZ;
private static volatile int currentRingChunkOffsetX;
private static volatile int currentRingChunkOffsetZ;
private static boolean firstUpdate = true;
private FogChunkStreamer() {
}
private record BlockChangeEvent(
int chunkX,
int chunkZ,
int blockX,
int blockY,
int blockZ,
BlockState newState
) {}
// Вызывается из любого потока (в том числе серверного)
public static void onBlockChanged(
int chunkX,
int chunkZ,
int blockX,
int blockY,
int blockZ,
BlockState newState
) {
blockChangeQueue.add(new BlockChangeEvent(chunkX, chunkZ, blockX, blockY, blockZ, newState));
}
public static void update(
int oldOriginChunkX,
int oldOriginChunkZ,
int newOriginChunkX,
int newOriginChunkZ
int newOriginChunkZ,
int ringChunkOffsetX,
int ringChunkOffsetZ
) {
currentOriginChunkX = newOriginChunkX;
currentOriginChunkZ = newOriginChunkZ;
currentRingChunkOffsetX = ringChunkOffsetX;
currentRingChunkOffsetZ = ringChunkOffsetZ;
if (firstUpdate) {
markAllPending(newOriginChunkX, newOriginChunkZ);
firstUpdate = false;
return;
}
int dx = newOriginChunkX - oldOriginChunkX;
int dz = newOriginChunkZ - oldOriginChunkZ;
/*
* Телепорт.
*/
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
refillAll(
newOriginChunkX,
newOriginChunkZ
);
if (dx == 0 && dz == 0) {
uploadReadyData();
return;
}
/*
* Новые колонки.
*/
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
markAllPending(newOriginChunkX, newOriginChunkZ);
uploadReadyData();
return;
}
if (dx > 0) {
for (int i = 0; i < dx; i++) {
uploadColumn(
newOriginChunkX +
FogWorldVolume.CHUNKS_X - dx + i,
newOriginChunkZ
);
int worldChunkX = newOriginChunkX + FogWorldVolume.CHUNKS_X - dx + i;
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
markSlotPending(worldChunkX, newOriginChunkZ + z);
}
}
}
if (dx < 0) {
for (int i = 0; i < -dx; i++) {
uploadColumn(
newOriginChunkX + i,
newOriginChunkZ
);
int worldChunkX = newOriginChunkX + i;
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
markSlotPending(worldChunkX, newOriginChunkZ + z);
}
}
}
/*
* Новые строки.
*/
if (dz > 0) {
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
int worldChunkX = newOriginChunkX + x;
for (int i = 0; i < dz; i++) {
uploadRow(
newOriginChunkX,
newOriginChunkZ +
FogWorldVolume.CHUNKS_Z - dz + i
);
int worldChunkZ = newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz + i;
markSlotPending(worldChunkX, worldChunkZ);
}
}
}
if (dz < 0) {
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
int worldChunkX = newOriginChunkX + x;
for (int i = 0; i < -dz; i++) {
int worldChunkZ = newOriginChunkZ + i;
markSlotPending(worldChunkX, worldChunkZ);
}
}
}
uploadRow(
newOriginChunkX,
newOriginChunkZ + i
uploadReadyData();
}
// Обрабатывает очередь изменений блоков (только в render thread)
public static void processBlockChanges() {
BlockChangeEvent event;
while ((event = blockChangeQueue.poll()) != null) {
processSingleBlockChange(event);
}
}
private static void processSingleBlockChange(BlockChangeEvent event) {
int originX = currentOriginChunkX;
int originZ = currentOriginChunkZ;
int ringOffsetX = currentRingChunkOffsetX;
int ringOffsetZ = currentRingChunkOffsetZ;
// Проверяем, находится ли чанк в нашей области
int relativeX = event.chunkX - originX;
int relativeZ = event.chunkZ - originZ;
if (relativeX < 0 || relativeX >= FogWorldVolume.CHUNKS_X ||
relativeZ < 0 || relativeZ >= FogWorldVolume.CHUNKS_Z) {
return; // Чанк вне области обработки
}
// Вычисляем физический индекс
int physicalX = Math.floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X);
int physicalZ = Math.floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z);
// Вычисляем позицию вокселя
int localX = event.blockX & 15;
int localZ = event.blockZ & 15;
int yIndex = event.blockY - FogWorldVolume.MIN_Y;
if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) {
return; // Y вне диапазона
}
// Вычисляем значение для этого вокселя
float value = FogChunkProvider.computeVoxelValue(event.newState);
// Обновляем один воксель в текстуре
FogWorldVolume.updateSingleVoxel(
physicalX,
physicalZ,
localX,
yIndex,
localZ,
value
);
}
}
public static void processPending() {
int originX = currentOriginChunkX;
int originZ = currentOriginChunkZ;
int ringOffsetX = currentRingChunkOffsetX;
int ringOffsetZ = currentRingChunkOffsetZ;
int processedCount = 0;
for (int physicalIndex = 0; physicalIndex < TOTAL_CHUNKS; physicalIndex++) {
if (statuses[physicalIndex] != ChunkStatus.PENDING) {
continue;
}
private static void refillAll(
int originChunkX,
int originChunkZ
) {
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
uploadChunk(
originChunkX + x,
originChunkZ + z
);
}
}
if (processedCount >= MAX_CHUNKS_PER_TICK) {
break;
}
private static void uploadColumn(
int worldChunkX,
int originChunkZ
) {
int physicalX = physicalIndex % FogWorldVolume.CHUNKS_X;
int physicalZ = physicalIndex / FogWorldVolume.CHUNKS_X;
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
uploadChunk(
worldChunkX,
originChunkZ + z
);
}
}
private static void uploadRow(
int originChunkX,
int worldChunkZ
) {
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
uploadChunk(
originChunkX + x,
worldChunkZ
);
}
}
private static void uploadChunk(
int worldChunkX,
int worldChunkZ
) {
FloatBuffer buffer =
FogWorldVolume.chunkBuffer();
FogChunkProvider.fillChunk(
worldChunkX,
worldChunkZ,
buffer
);
buffer.flip();
int physicalChunkX =
Math.floorMod(
worldChunkX,
int worldChunkX = originX + Math.floorMod(
physicalX - ringOffsetX,
FogWorldVolume.CHUNKS_X
);
int physicalChunkZ =
Math.floorMod(
worldChunkZ,
int worldChunkZ = originZ + Math.floorMod(
physicalZ - ringOffsetZ,
FogWorldVolume.CHUNKS_Z
);
FogWorldVolume.uploadChunk(
physicalChunkX,
physicalChunkZ,
buffer
);
if (FogChunkProvider.fillChunkIntoArray(worldChunkX, worldChunkZ, preparedData[physicalIndex])) {
preparedWorldChunkX[physicalIndex] = worldChunkX;
preparedWorldChunkZ[physicalIndex] = worldChunkZ;
readyToUpload[physicalIndex] = true;
processedCount++;
}
}
}
private static void markAllPending(int originChunkX, int originChunkZ) {
for (int physicalZ = 0; physicalZ < FogWorldVolume.CHUNKS_Z; physicalZ++) {
for (int physicalX = 0; physicalX < FogWorldVolume.CHUNKS_X; physicalX++) {
int worldChunkX = originChunkX + physicalX;
int worldChunkZ = originChunkZ + physicalZ;
markSlotPending(worldChunkX, worldChunkZ);
}
}
}
private static void markSlotPending(int worldChunkX, int worldChunkZ) {
int physicalX = Math.floorMod(worldChunkX, FogWorldVolume.CHUNKS_X);
int physicalZ = Math.floorMod(worldChunkZ, FogWorldVolume.CHUNKS_Z);
int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX;
FogWorldVolume.uploadZeros(physicalX, physicalZ);
statuses[physicalIndex] = ChunkStatus.PENDING;
readyToUpload[physicalIndex] = false;
}
public static void uploadReadyData() {
int originX = currentOriginChunkX;
int originZ = currentOriginChunkZ;
int ringOffsetX = currentRingChunkOffsetX;
int ringOffsetZ = currentRingChunkOffsetZ;
for (int physicalIndex = 0; physicalIndex < TOTAL_CHUNKS; physicalIndex++) {
if (!readyToUpload[physicalIndex]) {
continue;
}
int physicalX = physicalIndex % FogWorldVolume.CHUNKS_X;
int physicalZ = physicalIndex / FogWorldVolume.CHUNKS_X;
int currentWorldX = originX + Math.floorMod(
physicalX - ringOffsetX,
FogWorldVolume.CHUNKS_X
);
int currentWorldZ = originZ + Math.floorMod(
physicalZ - ringOffsetZ,
FogWorldVolume.CHUNKS_Z
);
if (preparedWorldChunkX[physicalIndex] == currentWorldX &&
preparedWorldChunkZ[physicalIndex] == currentWorldZ) {
FogWorldVolume.uploadChunkFromArray(
physicalX,
physicalZ,
preparedData[physicalIndex]
);
statuses[physicalIndex] = ChunkStatus.LOADED;
}
readyToUpload[physicalIndex] = false;
}
}
}
@@ -2,8 +2,6 @@ package su.divan2000.veila.client.render.fog;
import net.minecraft.client.MinecraftClient;
import java.nio.FloatBuffer;
public final class FogSystem {
private static boolean initialized;
@@ -17,12 +15,6 @@ public final class FogSystem {
private FogSystem() {
}
/*
* Вызывается в начале render()
*
* Только Render Thread.
*/
public static void beginRender() {
if (!initialized) {
@@ -30,64 +22,23 @@ public final class FogSystem {
}
updateWindow();
}
/*
* Вызывается каждый client tick.
*
* Пока пусто.
*/
// Всегда копируем готовые данные в текстуру
FogChunkStreamer.uploadReadyData();
// Всегда обрабатываем изменения блоков
FogChunkStreamer.processBlockChanges();
}
public static void tick() {
FogChunkStreamer.processPending();
}
/*
* Инициализация всей системы.
*/
private static void initialize() {
FogWorldVolume.init();
initialized = true;
}
/*
* Пока просто заполняем весь объём
* тестовыми чанками.
*/
private static void fillTestVolume() {
for (int chunkZ = 0;
chunkZ < FogWorldVolume.CHUNKS_Z;
chunkZ++) {
for (int chunkX = 0;
chunkX < FogWorldVolume.CHUNKS_X;
chunkX++) {
FloatBuffer buffer =
FogWorldVolume.chunkBuffer();
FogChunkProvider.fillChunk(
originChunkX + chunkX,
originChunkZ + chunkZ,
buffer
);
buffer.flip();
FogWorldVolume.uploadChunk(
chunkX,
chunkZ,
buffer
);
}
}
}
private static void updateWindow() {
MinecraftClient client = MinecraftClient.getInstance();
@@ -126,7 +77,9 @@ public final class FogSystem {
oldOriginChunkX,
oldOriginChunkZ,
originChunkX,
originChunkZ
originChunkZ,
ringChunkOffsetX,
ringChunkOffsetZ
);
}
@@ -43,6 +43,11 @@ public final class FogWorldVolume {
* 16 × 384 × 16
*/
private static final float[] ZEROS_ARRAY = new float[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
static {
java.util.Arrays.fill(ZEROS_ARRAY, 0.0f);
}
private static final FloatBuffer CHUNK_BUFFER =
BufferUtils.createFloatBuffer(
CHUNK_SIZE *
@@ -50,6 +55,10 @@ public final class FogWorldVolume {
CHUNK_SIZE
);
private static final FloatBuffer SINGLE_VOXEL_BUFFER =
BufferUtils.createFloatBuffer(1);
private FogWorldVolume() {
}
@@ -93,7 +102,7 @@ public final class FogWorldVolume {
GL11.glTexParameteri(
GL12.GL_TEXTURE_3D,
GL12.GL_TEXTURE_WRAP_S,
GL12.GL_CLAMP_TO_EDGE
GL12.GL_REPEAT
);
GL11.glTexParameteri(
@@ -105,7 +114,7 @@ public final class FogWorldVolume {
GL11.glTexParameteri(
GL12.GL_TEXTURE_3D,
GL12.GL_TEXTURE_WRAP_R,
GL12.GL_CLAMP_TO_EDGE
GL12.GL_REPEAT
);
GL11.glBindTexture(
@@ -188,6 +197,58 @@ public final class FogWorldVolume {
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
public static void updateSingleVoxel(
int textureChunkX,
int textureChunkZ,
int localX,
int localY,
int localZ,
float value
) {
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
SINGLE_VOXEL_BUFFER.clear();
SINGLE_VOXEL_BUFFER.put(value);
SINGLE_VOXEL_BUFFER.flip();
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,
textureChunkX * CHUNK_SIZE + localX,
localY,
textureChunkZ * CHUNK_SIZE + localZ,
1, 1, 1,
GL11.GL_RED,
GL11.GL_FLOAT,
SINGLE_VOXEL_BUFFER
);
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
public static void uploadZeros(int textureChunkX, int textureChunkZ) {
FloatBuffer buffer = chunkBuffer();
buffer.put(ZEROS_ARRAY);
buffer.flip();
uploadChunk(textureChunkX, textureChunkZ, buffer);
}
public static void uploadChunkFromArray(
int textureChunkX,
int textureChunkZ,
float[] data
) {
FloatBuffer buffer = chunkBuffer();
buffer.put(data);
buffer.flip();
uploadChunk(textureChunkX, textureChunkZ, buffer);
}
public static FloatBuffer chunkBuffer() {
CHUNK_BUFFER.clear();
@@ -0,0 +1,41 @@
package su.divan2000.veila.mixin.client;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.chunk.WorldChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import su.divan2000.veila.client.render.fog.FogChunkStreamer;
@Mixin(WorldChunk.class)
public class WorldChunkMixin {
@Inject(
method = "setBlockState",
at = @At("RETURN")
)
private void veila$onBlockChanged(
BlockPos pos,
BlockState state,
boolean moved,
CallbackInfoReturnable<BlockState> cir
) {
// Только если блок реально изменился
if (cir.getReturnValue() != null) {
WorldChunk self = (WorldChunk) (Object) this;
int chunkX = self.getPos().x;
int chunkZ = self.getPos().z;
// Уведомляем стример об изменении
FogChunkStreamer.onBlockChanged(
chunkX,
chunkZ,
pos.getX(),
pos.getY(),
pos.getZ(),
state
);
}
}
}
@@ -20,18 +20,27 @@ out vec4 FragColor;
// Настройки
//------------------------------------------------------------
const float STEP_SIZE = 0.5;
const float MAX_DISTANCE = 256.0;
const int MAX_STEPS = int(MAX_DISTANCE / STEP_SIZE);
// Размер туманного объёма
const float FOG_SIZE_X = 256.0;
const float FOG_SIZE_Z = 256.0;
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 = true;
//------------------------------------------------------------
vec3 reconstructViewPosition(vec2 uv)
@@ -76,15 +85,11 @@ float density(vec3 worldPos)
return 0.0;
}
float physicalX = mod(
localX + float(RingBlockOffset.x),
FOG_SIZE_X
);
float physicalX =
localX + float(RingBlockOffset.x);
float physicalZ = mod(
localZ + float(RingBlockOffset.y),
FOG_SIZE_Z
);
float physicalZ =
localZ + float(RingBlockOffset.y);
uv.x = physicalX / FOG_SIZE_X;
uv.z = physicalZ / FOG_SIZE_Z;
@@ -95,6 +100,21 @@ float density(vec3 worldPos)
//------------------------------------------------------------
// Псевдослучайное значение для jittering (детерминированное по UV)
float random(vec2 st)
{
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
// Функция нелинейного маппинга параметра t [0,1] в расстояние
// Использует степенную функцию для распределения шагов
float mapParameterToDistance(float t)
{
return MAX_DISTANCE * pow(t, GAMMA);
}
//------------------------------------------------------------
void main()
{
vec4 sceneColor = texture(DiffuseSampler, texCoord);
@@ -109,17 +129,52 @@ void main()
vec3 samplePos = CameraPosition;
float fog = 0.0;
float currentDistance = 0.0;
for (int i = 0; i < MAX_STEPS; i++)
{
float traveled = float(i) * STEP_SIZE;
// Jittering начальной позиции для устранения banding (ступенчатых артефактов)
// Это стандартная техника в volumetric rendering
float jitter = 0.0;
if (USE_JITTERING) {
// Вычисляем размер первого шага для корректного jittering
float firstStep = mapParameterToDistance(1.0 / float(MAX_STEPS));
jitter = random(texCoord) * firstStep;
currentDistance = jitter;
samplePos += rayDir * currentDistance;
}
if (traveled >= rayLength)
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 * STEP_SIZE;
// Продвигаемся по лучу
samplePos += rayDir * stepSize;
fog += density(samplePos) * STEP_SIZE;
// Интегрируем плотность (Beer-Lambert law approximation)
// Важно умножать на stepSize для корректной физической модели
fog += density(samplePos) * stepSize;
currentDistance = nextDistance;
// Прерываем, если достигли конца луча
if (currentDistance >= rayLength) {
break;
}
}
fog = clamp(fog, 0.0, 1.0);
@@ -5,7 +5,8 @@
"compatibilityLevel": "JAVA_17",
"client": [
"GameRendererMixin",
"MinecraftClientMixin"
"MinecraftClientMixin",
"WorldChunkMixin"
],
"injectors": {
"defaultRequire": 1
+1
View File
@@ -2,6 +2,7 @@
"schemaVersion": 1,
"id": "veila",
"version": "${version}",
"accessWidener": "veila.accesswidener",
"name": "Veila",
"description": "",
+2
View File
@@ -0,0 +1,2 @@
accessWidener v2 named
accessible method net/minecraft/world/chunk/PalettedContainer get (I)Ljava/lang/Object;