Compare commits
25
Commits
15f484a39b
...
e1349d207b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1349d207b | ||
|
|
2521366109 | ||
|
|
73045aec4d | ||
|
|
eedd020064 | ||
|
|
4f6944668e | ||
|
|
e7a6e587ff | ||
|
|
9fa0370ad0 | ||
|
|
3c04c6259e | ||
|
|
f08dee2677 | ||
|
|
0faa8066af | ||
|
|
cfc9bccb89 | ||
|
|
a4cdfb0c9c | ||
|
|
e39c3d716d | ||
|
|
1bcbcf6d60 | ||
|
|
fd21d9edd8 | ||
|
|
e04f5a5ac8 | ||
|
|
4a057468e3 | ||
|
|
8ac636d114 | ||
|
|
76370f71e2 | ||
|
|
8c1809a556 | ||
|
|
ad2e5adcde | ||
|
|
efc345fa0b | ||
|
|
41238fddb0 | ||
|
|
eb497f257a | ||
|
|
5e3f985025 |
@@ -20,6 +20,8 @@ loom {
|
||||
sourceSet sourceSets.client
|
||||
}
|
||||
}
|
||||
|
||||
accessWidenerPath = file("src/main/resources/veila.accesswidener")
|
||||
}
|
||||
|
||||
fabricApi {
|
||||
|
||||
@@ -39,6 +39,7 @@ public final class FullscreenQuad {
|
||||
vbo = GL15.glGenBuffers();
|
||||
|
||||
int previous = glGetInteger(GL_VERTEX_ARRAY_BINDING);
|
||||
int previousArrayBuffer = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
|
||||
|
||||
GL30.glBindVertexArray(vao);
|
||||
|
||||
@@ -56,6 +57,7 @@ public final class FullscreenQuad {
|
||||
);
|
||||
|
||||
GL30.glBindVertexArray(previous);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, previousArrayBuffer);
|
||||
}
|
||||
|
||||
public static void draw() {
|
||||
|
||||
@@ -13,249 +13,418 @@ 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 su.divan2000.veila.client.render.fog.FogSystem;
|
||||
import su.divan2000.veila.client.render.fog.FogWorldVolume;
|
||||
import su.divan2000.veila.client.simulation.FogLightVolume;
|
||||
import su.divan2000.veila.client.simulation.FogSimulationManager;
|
||||
import su.divan2000.veila.client.simulation.FogSystem;
|
||||
import su.divan2000.veila.client.simulation.FogWorldVolume;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public class PostProcessingManager {
|
||||
|
||||
private static GLProgram program;
|
||||
private static GLProgram raymarchProgram;
|
||||
private static GLProgram compositeProgram;
|
||||
private static GLProgram depthDownsampleProgram;
|
||||
|
||||
/*
|
||||
* Матрицы
|
||||
*/
|
||||
private static int dd_depthSamplerLocation;
|
||||
private static int dd_screenSizeLocation;
|
||||
|
||||
// Фреймбуфер для scaled raymarching
|
||||
private static int scaledFBO;
|
||||
private static int scaledTexture;
|
||||
private static int scaledWidth;
|
||||
private static int scaledHeight;
|
||||
private static final float scale = 0.333f;
|
||||
|
||||
private static final Matrix4f projection = new Matrix4f();
|
||||
private static final Matrix4f inverseProjection = new Matrix4f();
|
||||
private static final FloatBuffer projectionBuffer = BufferUtils.createFloatBuffer(16);
|
||||
private static final FloatBuffer inverseProjectionBuffer = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
private static final FloatBuffer projectionBuffer =
|
||||
BufferUtils.createFloatBuffer(16);
|
||||
|
||||
private static final FloatBuffer inverseProjectionBuffer =
|
||||
BufferUtils.createFloatBuffer(16);
|
||||
private static final int[] previousViewport = new int[4];
|
||||
private static final int[] previousTexture2D = new int[5];
|
||||
private static final int[] previousTexture3D = new int[5];
|
||||
|
||||
/*
|
||||
* Uniform locations
|
||||
* Uniform locations - Raymarch Program
|
||||
*/
|
||||
private static int rm_projectionLocation;
|
||||
private static int rm_inverseProjectionLocation;
|
||||
private static int rm_depthSamplerLocation;
|
||||
private static int rm_fogVolumeLocation;
|
||||
private static int rm_prevFogVolumeLocation;
|
||||
private static int rm_fogInterpolationLocation;
|
||||
private static int rm_skyLightVolumeLocation;
|
||||
private static int rm_blockLightVolumeLocation;
|
||||
private static int rm_fogOriginLocation;
|
||||
private static int rm_ringOffsetLocation;
|
||||
private static int rm_cameraPositionLocation;
|
||||
private static int rm_screenSizeLocation;
|
||||
private static int rm_viewLocation;
|
||||
private static int rm_inverseViewLocation;
|
||||
private static int rm_fogColorLocation;
|
||||
private static int rm_skyBrightnessLocation;
|
||||
|
||||
private static int projectionLocation;
|
||||
private static int inverseProjectionLocation;
|
||||
/*
|
||||
* Uniform locations - Composite Program (JBU)
|
||||
*/
|
||||
private static int comp_diffuseSamplerLocation;
|
||||
private static int comp_fogSamplerLocation;
|
||||
private static int comp_depthSamplerLocation;
|
||||
private static int comp_scaledDepthSamplerLocation;
|
||||
private static int comp_screenSizeLocation;
|
||||
private static int comp_scaledScreenSizeLocation;
|
||||
private static int comp_inverseProjectionLocation;
|
||||
|
||||
private static int diffuseSamplerLocation;
|
||||
private static int depthSamplerLocation;
|
||||
|
||||
private static int cameraPositionLocation;
|
||||
private static int screenSizeLocation;
|
||||
private static int scaledDepthTexture;
|
||||
|
||||
private static final Matrix4f view = new Matrix4f();
|
||||
private static final Matrix4f inverseView = new Matrix4f();
|
||||
private static final FloatBuffer viewBuffer = BufferUtils.createFloatBuffer(16);
|
||||
private static final FloatBuffer inverseViewBuffer = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
private static final FloatBuffer viewBuffer =
|
||||
BufferUtils.createFloatBuffer(16);
|
||||
private static float fogR, fogG, fogB;
|
||||
|
||||
private static final FloatBuffer inverseViewBuffer =
|
||||
BufferUtils.createFloatBuffer(16);
|
||||
|
||||
private static int viewLocation;
|
||||
private static int inverseViewLocation;
|
||||
|
||||
private static int fogVolumeLocation;
|
||||
private static int fogOriginLocation;
|
||||
|
||||
private static int ringOffsetLocation;
|
||||
public static void setFogColor(float r, float g, float b) {
|
||||
fogR = r;
|
||||
fogG = g;
|
||||
fogB = b;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
|
||||
if (program != null)
|
||||
if (raymarchProgram != null)
|
||||
return;
|
||||
|
||||
GLShader vertex = new GLShader(
|
||||
setFogColor(0.75f, 0.80f, 0.90f);
|
||||
|
||||
// Инициализация raymarch программы
|
||||
GLShader rmVertex = new GLShader(
|
||||
GL20.GL_VERTEX_SHADER,
|
||||
ResourceUtil.load("shaders/fullscreen.vert")
|
||||
);
|
||||
|
||||
GLShader fragment = new GLShader(
|
||||
GLShader rmFragment = new GLShader(
|
||||
GL20.GL_FRAGMENT_SHADER,
|
||||
ResourceUtil.load("shaders/red.frag")
|
||||
ResourceUtil.load("shaders/fog_raymarching.frag")
|
||||
);
|
||||
raymarchProgram = new GLProgram(rmVertex, rmFragment);
|
||||
|
||||
program = new GLProgram(vertex, fragment);
|
||||
rm_projectionLocation = raymarchProgram.uniform("Projection");
|
||||
rm_inverseProjectionLocation = raymarchProgram.uniform("InverseProjection");
|
||||
rm_depthSamplerLocation = raymarchProgram.uniform("DepthSampler");
|
||||
rm_fogVolumeLocation = raymarchProgram.uniform("FogVolume");
|
||||
rm_prevFogVolumeLocation = raymarchProgram.uniform("PrevFogVolume");
|
||||
rm_fogInterpolationLocation = raymarchProgram.uniform("FogInterpolation");
|
||||
rm_skyLightVolumeLocation = raymarchProgram.uniform("SkyLightVolume");
|
||||
rm_blockLightVolumeLocation = raymarchProgram.uniform("BlockLightVolume");
|
||||
rm_fogOriginLocation = raymarchProgram.uniform("FogOrigin");
|
||||
rm_ringOffsetLocation = raymarchProgram.uniform("RingBlockOffset");
|
||||
rm_cameraPositionLocation = raymarchProgram.uniform("CameraPosition");
|
||||
rm_screenSizeLocation = raymarchProgram.uniform("ScreenSize");
|
||||
rm_viewLocation = raymarchProgram.uniform("View");
|
||||
rm_inverseViewLocation = raymarchProgram.uniform("InverseView");
|
||||
rm_fogColorLocation = raymarchProgram.uniform("FogColor");
|
||||
rm_skyBrightnessLocation = raymarchProgram.uniform("SkyBrightness");
|
||||
|
||||
projectionLocation = program.uniform("Projection");
|
||||
inverseProjectionLocation = program.uniform("InverseProjection");
|
||||
rmVertex.delete();
|
||||
rmFragment.delete();
|
||||
|
||||
diffuseSamplerLocation = program.uniform("DiffuseSampler");
|
||||
depthSamplerLocation = program.uniform("DepthSampler");
|
||||
// Инициализация composite программы с JBU
|
||||
GLShader compVertex = new GLShader(
|
||||
GL20.GL_VERTEX_SHADER,
|
||||
ResourceUtil.load("shaders/fullscreen.vert")
|
||||
);
|
||||
GLShader compFragment = new GLShader(
|
||||
GL20.GL_FRAGMENT_SHADER,
|
||||
ResourceUtil.load("shaders/fog_composite.frag")
|
||||
);
|
||||
compositeProgram = new GLProgram(compVertex, compFragment);
|
||||
|
||||
fogVolumeLocation = program.uniform("FogVolume");
|
||||
fogOriginLocation = program.uniform("FogOrigin");
|
||||
ringOffsetLocation = program.uniform("RingBlockOffset");
|
||||
comp_diffuseSamplerLocation = compositeProgram.uniform("DiffuseSampler");
|
||||
comp_fogSamplerLocation = compositeProgram.uniform("FogSampler");
|
||||
comp_depthSamplerLocation = compositeProgram.uniform("DepthSampler");
|
||||
comp_scaledDepthSamplerLocation = compositeProgram.uniform("ScaledDepthSampler");
|
||||
comp_screenSizeLocation = compositeProgram.uniform("ScreenSize");
|
||||
comp_scaledScreenSizeLocation = compositeProgram.uniform("ScaledScreenSize");
|
||||
comp_inverseProjectionLocation = compositeProgram.uniform("InverseProjection");
|
||||
|
||||
cameraPositionLocation = program.uniform("CameraPosition");
|
||||
screenSizeLocation = program.uniform("ScreenSize");
|
||||
compVertex.delete();
|
||||
compFragment.delete();
|
||||
|
||||
viewLocation = program.uniform("View");
|
||||
inverseViewLocation = program.uniform("InverseView");
|
||||
GLShader ddVertex = new GLShader(
|
||||
GL20.GL_VERTEX_SHADER,
|
||||
ResourceUtil.load("shaders/fullscreen.vert")
|
||||
);
|
||||
GLShader ddFragment = new GLShader(
|
||||
GL20.GL_FRAGMENT_SHADER,
|
||||
ResourceUtil.load("shaders/depth_downsample.frag")
|
||||
);
|
||||
depthDownsampleProgram = new GLProgram(ddVertex, ddFragment);
|
||||
|
||||
dd_depthSamplerLocation = depthDownsampleProgram.uniform("DepthSampler");
|
||||
dd_screenSizeLocation = depthDownsampleProgram.uniform("ScreenSize");
|
||||
|
||||
ddVertex.delete();
|
||||
ddFragment.delete();
|
||||
|
||||
FullscreenQuad.init();
|
||||
|
||||
vertex.delete();
|
||||
fragment.delete();
|
||||
|
||||
System.out.println("VEILA shader compiled!");
|
||||
System.out.println("VEILA shaders compiled!");
|
||||
}
|
||||
|
||||
public static void render() {
|
||||
private static void initScaledFramebuffer(int width, int height) {
|
||||
scaledWidth = (int)(width * scale);
|
||||
scaledHeight = (int)(height * scale);
|
||||
|
||||
if (program == null)
|
||||
int previousFramebuffer = GL11.glGetInteger(GL30.GL_FRAMEBUFFER_BINDING);
|
||||
int previousActiveTexture = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
int previousTexture0 = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
|
||||
scaledFBO = GL30.glGenFramebuffers();
|
||||
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, scaledFBO);
|
||||
|
||||
scaledTexture = GL11.glGenTextures();
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledTexture);
|
||||
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_RGBA16F, scaledWidth, scaledHeight, 0, GL11.GL_RGBA, GL11.GL_FLOAT, 0);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
|
||||
|
||||
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, scaledTexture, 0);
|
||||
|
||||
// Half-res depth buffer
|
||||
scaledDepthTexture = GL11.glGenTextures();
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledDepthTexture);
|
||||
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_R32F, scaledWidth, scaledHeight, 0, GL11.GL_RED, GL11.GL_FLOAT, 0);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
|
||||
|
||||
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT1, GL11.GL_TEXTURE_2D, scaledDepthTexture, 0);
|
||||
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTexture0);
|
||||
GL13.glActiveTexture(previousActiveTexture);
|
||||
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, previousFramebuffer);
|
||||
}
|
||||
|
||||
public static void render(float tickDelta) {
|
||||
if (raymarchProgram == null)
|
||||
return;
|
||||
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
|
||||
Framebuffer framebuffer = client.getFramebuffer();
|
||||
if (client.world == null)
|
||||
return;
|
||||
|
||||
int width = framebuffer.textureWidth;
|
||||
int height = framebuffer.textureHeight;
|
||||
int previousProgram = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
int previousActiveTexture = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
|
||||
int previousDrawFramebuffer = GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING);
|
||||
int previousReadFramebuffer = GL11.glGetInteger(GL30.GL_READ_FRAMEBUFFER_BINDING);
|
||||
int previousDrawBuffer = GL11.glGetInteger(GL11.GL_DRAW_BUFFER);
|
||||
GL11.glGetIntegerv(GL11.GL_VIEWPORT, previousViewport);
|
||||
|
||||
GL30.glBindFramebuffer(
|
||||
GL30.GL_READ_FRAMEBUFFER,
|
||||
framebuffer.fbo
|
||||
);
|
||||
for (int unit = 0; unit <= 4; unit++) {
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
previousTexture2D[unit] = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
previousTexture3D[unit] = GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D);
|
||||
}
|
||||
GL13.glActiveTexture(previousActiveTexture);
|
||||
|
||||
DepthCopy.init(width, height);
|
||||
DepthCopy.copy(width, height);
|
||||
try {
|
||||
Framebuffer framebuffer = client.getFramebuffer();
|
||||
|
||||
program.bind();
|
||||
int width = framebuffer.textureWidth;
|
||||
int height = framebuffer.textureHeight;
|
||||
|
||||
/*
|
||||
* Diffuse
|
||||
*/
|
||||
// Инициализация scaled фреймбуфера
|
||||
if (scaledFBO == 0 || scaledWidth != (int)(width * scale) || scaledHeight != (int)(height * scale)) {
|
||||
if (scaledFBO != 0) {
|
||||
GL30.glDeleteFramebuffers(scaledFBO);
|
||||
GL11.glDeleteTextures(scaledTexture);
|
||||
GL11.glDeleteTextures(scaledDepthTexture);
|
||||
}
|
||||
initScaledFramebuffer(width, height);
|
||||
}
|
||||
|
||||
GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, framebuffer.fbo);
|
||||
|
||||
DepthCopy.init(width, height);
|
||||
DepthCopy.copy(width, height);
|
||||
updateProjectionMatrices();
|
||||
|
||||
renderDepthDownsample(width, height);
|
||||
|
||||
// === Этап 1: Raymarching в scaled разрешении ===
|
||||
renderRaymarching(tickDelta, client);
|
||||
|
||||
// === Этап 2: Композитинг с JBU в полном разрешении ===
|
||||
renderComposite(framebuffer, width, height);
|
||||
|
||||
GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
|
||||
} finally {
|
||||
for (int unit = 4; unit >= 0; unit--) {
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTexture2D[unit]);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, previousTexture3D[unit]);
|
||||
}
|
||||
GL13.glActiveTexture(previousActiveTexture);
|
||||
GL20.glUseProgram(previousProgram);
|
||||
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, previousDrawFramebuffer);
|
||||
GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, previousReadFramebuffer);
|
||||
GL11.glDrawBuffer(previousDrawBuffer);
|
||||
GL11.glViewport(previousViewport[0], previousViewport[1], previousViewport[2], previousViewport[3]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void renderDepthDownsample(int width, int height) {
|
||||
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, scaledFBO);
|
||||
|
||||
// Настраиваем draw buffers для MRT
|
||||
GL30.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT1);
|
||||
|
||||
GL11.glViewport(0, 0, scaledWidth, scaledHeight);
|
||||
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
depthDownsampleProgram.bind();
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GL11.glBindTexture(
|
||||
GL11.GL_TEXTURE_2D,
|
||||
framebuffer.getColorAttachment()
|
||||
);
|
||||
GL20.glUniform1i(diffuseSamplerLocation, 0);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, DepthCopy.getTexture());
|
||||
GL20.glUniform1i(dd_depthSamplerLocation, 0);
|
||||
|
||||
/*
|
||||
* Depth
|
||||
*/
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GL11.glBindTexture(
|
||||
GL11.GL_TEXTURE_2D,
|
||||
DepthCopy.getTexture()
|
||||
);
|
||||
GL20.glUniform1i(depthSamplerLocation, 1);
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
|
||||
GL11.glBindTexture(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
FogWorldVolume.getTexture()
|
||||
);
|
||||
|
||||
GL20.glUniform1i(
|
||||
fogVolumeLocation,
|
||||
2
|
||||
);
|
||||
|
||||
GL20.glUniform2i(
|
||||
fogOriginLocation,
|
||||
FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE,
|
||||
FogSystem.originChunkZ() * FogWorldVolume.CHUNK_SIZE
|
||||
);
|
||||
|
||||
GL20.glUniform2i(
|
||||
ringOffsetLocation,
|
||||
FogSystem.ringChunkOffsetX() * FogWorldVolume.CHUNK_SIZE,
|
||||
FogSystem.ringChunkOffsetZ() * FogWorldVolume.CHUNK_SIZE
|
||||
);
|
||||
|
||||
/*
|
||||
* Матрицы
|
||||
*/
|
||||
|
||||
updateProjectionMatrices();
|
||||
|
||||
GL20.glUniformMatrix4fv(
|
||||
projectionLocation,
|
||||
false,
|
||||
projectionBuffer
|
||||
);
|
||||
|
||||
GL20.glUniformMatrix4fv(
|
||||
inverseProjectionLocation,
|
||||
false,
|
||||
inverseProjectionBuffer
|
||||
);
|
||||
|
||||
GL20.glUniformMatrix4fv(
|
||||
viewLocation,
|
||||
false,
|
||||
viewBuffer
|
||||
);
|
||||
|
||||
GL20.glUniformMatrix4fv(
|
||||
inverseViewLocation,
|
||||
false,
|
||||
inverseViewBuffer
|
||||
);
|
||||
|
||||
/*
|
||||
* Камера
|
||||
*/
|
||||
|
||||
Camera camera = client.gameRenderer.getCamera();
|
||||
Vec3d pos = camera.getPos();
|
||||
|
||||
GL20.glUniform3f(
|
||||
cameraPositionLocation,
|
||||
(float) pos.x,
|
||||
(float) pos.y,
|
||||
(float) pos.z
|
||||
);
|
||||
|
||||
/*
|
||||
* Размер экрана
|
||||
*/
|
||||
|
||||
GL20.glUniform2f(
|
||||
screenSizeLocation,
|
||||
width,
|
||||
height
|
||||
);
|
||||
GL20.glUniform2f(dd_screenSizeLocation, width, height);
|
||||
|
||||
FullscreenQuad.draw();
|
||||
|
||||
depthDownsampleProgram.unbind();
|
||||
}
|
||||
|
||||
private static void renderRaymarching(float tickDelta, MinecraftClient client) {
|
||||
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, scaledFBO);
|
||||
|
||||
// Настраиваем draw buffer для raymarching
|
||||
GL30.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT0);
|
||||
|
||||
GL11.glViewport(0, 0, scaledWidth, scaledHeight);
|
||||
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
raymarchProgram.bind();
|
||||
|
||||
// Depth
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledDepthTexture);
|
||||
GL20.glUniform1i(rm_depthSamplerLocation, 0);
|
||||
|
||||
// Fog Volume
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogSimulationManager.getCurrentDensityTexture());
|
||||
GL20.glUniform1i(rm_fogVolumeLocation, 1);
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogSimulationManager.getPreviousDensityTexture());
|
||||
GL20.glUniform1i(rm_prevFogVolumeLocation, 2);
|
||||
|
||||
// Light Volume
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE3);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getSkyLightTexture());
|
||||
GL20.glUniform1i(rm_skyLightVolumeLocation, 3);
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE4);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getBlockLightTexture());
|
||||
GL20.glUniform1i(rm_blockLightVolumeLocation, 4);
|
||||
|
||||
GL20.glUniform2i(rm_fogOriginLocation,
|
||||
FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE,
|
||||
FogSystem.originChunkZ() * FogWorldVolume.CHUNK_SIZE);
|
||||
|
||||
GL20.glUniform2i(rm_ringOffsetLocation,
|
||||
FogSystem.ringChunkOffsetX() * FogWorldVolume.CHUNK_SIZE,
|
||||
FogSystem.ringChunkOffsetZ() * FogWorldVolume.CHUNK_SIZE);
|
||||
|
||||
// Матрицы
|
||||
GL20.glUniformMatrix4fv(rm_projectionLocation, false, projectionBuffer);
|
||||
GL20.glUniformMatrix4fv(rm_inverseProjectionLocation, false, inverseProjectionBuffer);
|
||||
GL20.glUniformMatrix4fv(rm_viewLocation, false, viewBuffer);
|
||||
GL20.glUniformMatrix4fv(rm_inverseViewLocation, false, inverseViewBuffer);
|
||||
|
||||
// Камера
|
||||
Camera camera = client.gameRenderer.getCamera();
|
||||
Vec3d pos = camera.getPos();
|
||||
float localCameraX = (float) (pos.x - FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE);
|
||||
float localCameraY = (float) (pos.y - FogWorldVolume.MIN_Y);
|
||||
float localCameraZ = (float) (pos.z - FogSystem.originChunkZ() * FogWorldVolume.CHUNK_SIZE);
|
||||
GL20.glUniform3f(rm_cameraPositionLocation, localCameraX, localCameraY, localCameraZ);
|
||||
|
||||
// Размер экрана (scaled)
|
||||
GL20.glUniform2f(rm_screenSizeLocation, scaledWidth, scaledHeight);
|
||||
|
||||
// Цвет тумана
|
||||
GL20.glUniform3f(rm_fogColorLocation, fogR, fogG, fogB);
|
||||
GL20.glUniform1f(rm_fogInterpolationLocation, FogSimulationManager.getFogInterpolationAlpha());
|
||||
|
||||
// Яркость неба
|
||||
GL20.glUniform1f(rm_skyBrightnessLocation, client.world.getSkyBrightness(tickDelta));
|
||||
|
||||
FullscreenQuad.draw();
|
||||
|
||||
// Очистка текстур
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE3);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
|
||||
program.unbind();
|
||||
raymarchProgram.unbind();
|
||||
}
|
||||
|
||||
GL30.glBindFramebuffer(
|
||||
GL30.GL_READ_FRAMEBUFFER,
|
||||
0
|
||||
);
|
||||
private static void renderComposite(Framebuffer framebuffer, int width, int height) {
|
||||
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer.fbo);
|
||||
GL11.glViewport(0, 0, width, height);
|
||||
|
||||
compositeProgram.bind();
|
||||
|
||||
// Original scene (Diffuse)
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, framebuffer.getColorAttachment());
|
||||
GL20.glUniform1i(comp_diffuseSamplerLocation, 0);
|
||||
|
||||
// Scaled fog results
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledTexture);
|
||||
GL20.glUniform1i(comp_fogSamplerLocation, 1);
|
||||
|
||||
// Full-res depth для JBU
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, DepthCopy.getTexture());
|
||||
GL20.glUniform1i(comp_depthSamplerLocation, 2);
|
||||
|
||||
// Half-res depth для JBU
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE3);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledDepthTexture);
|
||||
GL20.glUniform1i(comp_scaledDepthSamplerLocation, 3);
|
||||
|
||||
// Матрицы для линеаризации depth
|
||||
GL20.glUniformMatrix4fv(comp_inverseProjectionLocation, false, inverseProjectionBuffer);
|
||||
|
||||
// Screen size
|
||||
GL20.glUniform2f(comp_screenSizeLocation, width, height);
|
||||
GL20.glUniform2f(comp_scaledScreenSizeLocation, scaledWidth, scaledHeight);
|
||||
|
||||
FullscreenQuad.draw();
|
||||
|
||||
// Очистка
|
||||
// restore state once in render() finalizer
|
||||
|
||||
compositeProgram.unbind();
|
||||
}
|
||||
|
||||
private static void updateProjectionMatrices() {
|
||||
|
||||
projection.set(RenderSystem.getProjectionMatrix());
|
||||
|
||||
inverseProjection
|
||||
.set(projection)
|
||||
.invert();
|
||||
inverseProjection.set(projection).invert();
|
||||
|
||||
projectionBuffer.clear();
|
||||
projection.get(projectionBuffer);
|
||||
@@ -265,12 +434,8 @@ public class PostProcessingManager {
|
||||
}
|
||||
|
||||
public static void updateViewMatrix(Matrix4f matrix) {
|
||||
|
||||
view.set(matrix);
|
||||
|
||||
inverseView
|
||||
.set(matrix)
|
||||
.invert();
|
||||
inverseView.set(matrix).invert();
|
||||
|
||||
viewBuffer.clear();
|
||||
view.get(viewBuffer);
|
||||
@@ -278,5 +443,4 @@ public class PostProcessingManager {
|
||||
inverseViewBuffer.clear();
|
||||
inverseView.get(inverseViewBuffer);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package su.divan2000.veila.client.render.fog;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public final class FogChunkProvider {
|
||||
static void fillChunk(
|
||||
int chunkX,
|
||||
int chunkZ,
|
||||
FloatBuffer buffer
|
||||
) {
|
||||
|
||||
float density =
|
||||
hash(chunkX, chunkZ);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float hash(
|
||||
int x,
|
||||
int z
|
||||
) {
|
||||
|
||||
int h = x * 73428767;
|
||||
|
||||
h ^= z * 912931;
|
||||
|
||||
h ^= h >> 13;
|
||||
|
||||
h *= 1274126177;
|
||||
|
||||
return ((h >>> 24) & 255)
|
||||
/ 255.0f
|
||||
* 0.08f;
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package su.divan2000.veila.client.render.fog;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static su.divan2000.veila.client.render.fog.FogWorldVolume.CHUNK_SIZE;
|
||||
import static su.divan2000.veila.client.render.fog.FogWorldVolume.SIZE_Y;
|
||||
|
||||
public final class FogChunkStreamer {
|
||||
|
||||
private FogChunkStreamer() {
|
||||
}
|
||||
|
||||
public static void update(
|
||||
int oldOriginChunkX,
|
||||
int oldOriginChunkZ,
|
||||
int newOriginChunkX,
|
||||
int newOriginChunkZ
|
||||
) {
|
||||
|
||||
int dx = newOriginChunkX - oldOriginChunkX;
|
||||
int dz = newOriginChunkZ - oldOriginChunkZ;
|
||||
|
||||
/*
|
||||
* Телепорт.
|
||||
*/
|
||||
|
||||
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
|
||||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
|
||||
|
||||
refillAll(
|
||||
newOriginChunkX,
|
||||
newOriginChunkZ
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Новые колонки.
|
||||
*/
|
||||
|
||||
if (dx > 0) {
|
||||
|
||||
for (int i = 0; i < dx; i++) {
|
||||
|
||||
uploadColumn(
|
||||
newOriginChunkX +
|
||||
FogWorldVolume.CHUNKS_X - dx + i,
|
||||
newOriginChunkZ
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (dx < 0) {
|
||||
|
||||
for (int i = 0; i < -dx; i++) {
|
||||
|
||||
uploadColumn(
|
||||
newOriginChunkX + i,
|
||||
newOriginChunkZ
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Новые строки.
|
||||
*/
|
||||
|
||||
if (dz > 0) {
|
||||
|
||||
for (int i = 0; i < dz; i++) {
|
||||
|
||||
uploadRow(
|
||||
newOriginChunkX,
|
||||
newOriginChunkZ +
|
||||
FogWorldVolume.CHUNKS_Z - dz + i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (dz < 0) {
|
||||
|
||||
for (int i = 0; i < -dz; i++) {
|
||||
|
||||
uploadRow(
|
||||
newOriginChunkX,
|
||||
newOriginChunkZ + i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadColumn(
|
||||
int worldChunkX,
|
||||
int originChunkZ
|
||||
) {
|
||||
|
||||
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,
|
||||
FogWorldVolume.CHUNKS_X
|
||||
);
|
||||
|
||||
int physicalChunkZ =
|
||||
Math.floorMod(
|
||||
worldChunkZ,
|
||||
FogWorldVolume.CHUNKS_Z
|
||||
);
|
||||
|
||||
|
||||
System.out.println(
|
||||
worldChunkX + " " +
|
||||
worldChunkZ + " -> " +
|
||||
physicalChunkX + " " +
|
||||
physicalChunkZ
|
||||
);
|
||||
|
||||
|
||||
FogWorldVolume.uploadChunk(
|
||||
physicalChunkX,
|
||||
physicalChunkZ,
|
||||
buffer
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package su.divan2000.veila.client.render.fog;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public final class FogSystem {
|
||||
|
||||
private static boolean initialized;
|
||||
|
||||
private static int originChunkX;
|
||||
private static int originChunkZ;
|
||||
|
||||
private static int ringChunkOffsetX;
|
||||
private static int ringChunkOffsetZ;
|
||||
|
||||
private FogSystem() {
|
||||
}
|
||||
|
||||
/*
|
||||
* Вызывается в начале render()
|
||||
*
|
||||
* Только Render Thread.
|
||||
*/
|
||||
|
||||
public static void beginRender() {
|
||||
|
||||
if (!initialized) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
updateWindow();
|
||||
}
|
||||
|
||||
/*
|
||||
* Вызывается каждый client tick.
|
||||
*
|
||||
* Пока пусто.
|
||||
*/
|
||||
|
||||
public static void tick() {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Инициализация всей системы.
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
if (client.world == null || client.player == null)
|
||||
return;
|
||||
|
||||
int playerChunkX = client.player.getBlockX() >> 4;
|
||||
int playerChunkZ = client.player.getBlockZ() >> 4;
|
||||
|
||||
int newOriginChunkX =
|
||||
playerChunkX - FogWorldVolume.CHUNKS_X / 2;
|
||||
|
||||
int newOriginChunkZ =
|
||||
playerChunkZ - FogWorldVolume.CHUNKS_Z / 2;
|
||||
|
||||
if (newOriginChunkX == originChunkX &&
|
||||
newOriginChunkZ == originChunkZ)
|
||||
return;
|
||||
|
||||
int oldOriginChunkX = originChunkX;
|
||||
int oldOriginChunkZ = originChunkZ;
|
||||
|
||||
originChunkX = newOriginChunkX;
|
||||
originChunkZ = newOriginChunkZ;
|
||||
|
||||
FogChunkStreamer.update(
|
||||
oldOriginChunkX,
|
||||
oldOriginChunkZ,
|
||||
originChunkX,
|
||||
originChunkZ
|
||||
);
|
||||
|
||||
ringChunkOffsetX =
|
||||
Math.floorMod(originChunkX,
|
||||
FogWorldVolume.CHUNKS_X);
|
||||
|
||||
ringChunkOffsetZ =
|
||||
Math.floorMod(originChunkZ,
|
||||
FogWorldVolume.CHUNKS_Z);
|
||||
}
|
||||
|
||||
public static int originChunkX() {
|
||||
return originChunkX;
|
||||
}
|
||||
|
||||
public static int originChunkZ() {
|
||||
return originChunkZ;
|
||||
}
|
||||
|
||||
public static int ringChunkOffsetX() {
|
||||
return ringChunkOffsetX;
|
||||
}
|
||||
|
||||
public static int ringChunkOffsetZ() {
|
||||
return ringChunkOffsetZ;
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
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.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public final class FogWorldVolume {
|
||||
|
||||
/*
|
||||
* Размер всего объёма
|
||||
*/
|
||||
|
||||
public static final int SIZE_X = 256;
|
||||
public static final int SIZE_Z = 256;
|
||||
|
||||
public static final int MIN_Y = -64;
|
||||
public static final int MAX_Y = 320;
|
||||
public static final int SIZE_Y = MAX_Y - MIN_Y;
|
||||
|
||||
/*
|
||||
* Размер одного чанка
|
||||
*/
|
||||
|
||||
public static final int CHUNK_SIZE = 16;
|
||||
|
||||
public static final int CHUNKS_X = SIZE_X / CHUNK_SIZE;
|
||||
public static final int CHUNKS_Z = SIZE_Z / CHUNK_SIZE;
|
||||
|
||||
/*
|
||||
* OpenGL
|
||||
*/
|
||||
|
||||
private static int texture = -1;
|
||||
|
||||
/*
|
||||
* Буфер для одного чанка
|
||||
*
|
||||
* 16 × 384 × 16
|
||||
*/
|
||||
|
||||
private static final FloatBuffer CHUNK_BUFFER =
|
||||
BufferUtils.createFloatBuffer(
|
||||
CHUNK_SIZE *
|
||||
SIZE_Y *
|
||||
CHUNK_SIZE
|
||||
);
|
||||
|
||||
private FogWorldVolume() {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
|
||||
if (texture != -1)
|
||||
return;
|
||||
|
||||
texture = GL11.glGenTextures();
|
||||
|
||||
GL11.glBindTexture(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
texture
|
||||
);
|
||||
|
||||
GL30.glTexImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
GL30.GL_R16F,
|
||||
SIZE_X,
|
||||
SIZE_Y,
|
||||
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_CLAMP_TO_EDGE
|
||||
);
|
||||
|
||||
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_CLAMP_TO_EDGE
|
||||
);
|
||||
|
||||
GL11.glBindTexture(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0
|
||||
);
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
|
||||
CHUNK_BUFFER.clear();
|
||||
|
||||
int size =
|
||||
CHUNK_SIZE *
|
||||
SIZE_Y *
|
||||
CHUNK_SIZE;
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
CHUNK_BUFFER.put(0.0f);
|
||||
|
||||
CHUNK_BUFFER.flip();
|
||||
|
||||
GL11.glBindTexture(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
texture
|
||||
);
|
||||
|
||||
for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) {
|
||||
|
||||
for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) {
|
||||
|
||||
uploadChunk(
|
||||
chunkX,
|
||||
chunkZ,
|
||||
CHUNK_BUFFER
|
||||
);
|
||||
|
||||
CHUNK_BUFFER.rewind();
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glBindTexture(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
public static void uploadChunk(
|
||||
int textureChunkX,
|
||||
int textureChunkZ,
|
||||
FloatBuffer data
|
||||
) {
|
||||
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
System.out.println(
|
||||
data.remaining()
|
||||
);
|
||||
|
||||
|
||||
int error = GL11.glGetError();
|
||||
|
||||
if (error != GL11.GL_NO_ERROR) {
|
||||
System.out.println("GL BEFORE = " + error);
|
||||
}
|
||||
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
|
||||
textureChunkX * CHUNK_SIZE,
|
||||
0,
|
||||
textureChunkZ * CHUNK_SIZE,
|
||||
|
||||
CHUNK_SIZE,
|
||||
SIZE_Y,
|
||||
CHUNK_SIZE,
|
||||
|
||||
GL11.GL_RED,
|
||||
GL11.GL_FLOAT,
|
||||
data
|
||||
);
|
||||
|
||||
error = GL11.glGetError();
|
||||
|
||||
if (error != GL11.GL_NO_ERROR) {
|
||||
System.out.println("GL AFTER = " + error);
|
||||
}
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
public static FloatBuffer chunkBuffer() {
|
||||
|
||||
CHUNK_BUFFER.clear();
|
||||
|
||||
return CHUNK_BUFFER;
|
||||
}
|
||||
|
||||
public static int getTexture() {
|
||||
return texture;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import net.minecraft.registry.entry.RegistryEntry;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
|
||||
public final class FogBiomeParams {
|
||||
|
||||
public float emissionMultiplier;
|
||||
public float targetDensityBase;
|
||||
public float humidity;
|
||||
public float timeSensitivity;
|
||||
|
||||
public FogBiomeParams(float emission, float targetDensityBase,
|
||||
float humidity, float timeSensitivity) {
|
||||
this.emissionMultiplier = emission;
|
||||
this.targetDensityBase = targetDensityBase;
|
||||
this.humidity = humidity;
|
||||
this.timeSensitivity = timeSensitivity;
|
||||
}
|
||||
|
||||
public static FogBiomeParams forBiome(RegistryEntry<Biome> biomeEntry) {
|
||||
Biome biome = biomeEntry.value();
|
||||
|
||||
// Полный идентификатор: "minecraft:plains", "biomesoplenty:cherry_blossom_grove"
|
||||
String biomeId = biomeEntry.getKey()
|
||||
.map(key -> key.getValue().toString())
|
||||
.orElse("minecraft:unknown");
|
||||
|
||||
float emission = 1.0f;
|
||||
float targetDensity = 0.03f;
|
||||
float humidity = 0.5f;
|
||||
float timeSensitivity = 1.0f;
|
||||
|
||||
switch (biomeId) {
|
||||
// === Ванильные пустыни (никогда нет дымки) ===
|
||||
case "minecraft:desert":
|
||||
emission = 0.1f;
|
||||
targetDensity = 0.0f;
|
||||
humidity = 0.0f;
|
||||
timeSensitivity = 1.0f;
|
||||
break;
|
||||
|
||||
// === Ванильные бесплодные земли ===
|
||||
case "minecraft:badlands":
|
||||
case "minecraft:wooded_badlands":
|
||||
case "minecraft:eroded_badlands":
|
||||
emission = 0.15f;
|
||||
targetDensity = 0.0f;
|
||||
humidity = 0.05f;
|
||||
timeSensitivity = 1.0f;
|
||||
break;
|
||||
|
||||
// === Ванильные джунгли ===
|
||||
case "minecraft:jungle":
|
||||
case "minecraft:bamboo_jungle":
|
||||
emission = 1.0f;
|
||||
targetDensity = 0.045f;
|
||||
humidity = 0.9f;
|
||||
timeSensitivity = 0.3f;
|
||||
break;
|
||||
|
||||
case "minecraft:sparse_jungle":
|
||||
emission = 0.8f;
|
||||
targetDensity = 0.035f;
|
||||
humidity = 0.75f;
|
||||
timeSensitivity = 0.5f;
|
||||
break;
|
||||
|
||||
// === Ванильные болота ===
|
||||
case "minecraft:swamp":
|
||||
emission = 1.5f;
|
||||
targetDensity = 0.09f;
|
||||
humidity = 0.9f;
|
||||
timeSensitivity = 0.4f;
|
||||
break;
|
||||
|
||||
case "minecraft:mangrove_swamp":
|
||||
emission = 1.4f;
|
||||
targetDensity = 0.075f;
|
||||
humidity = 0.85f;
|
||||
timeSensitivity = 0.5f;
|
||||
break;
|
||||
|
||||
// === Ванильные равнины ===
|
||||
case "minecraft:plains":
|
||||
case "minecraft:sunflower_plains":
|
||||
emission = 1.0f;
|
||||
targetDensity = 0.03f;
|
||||
humidity = 0.5f;
|
||||
timeSensitivity = 1.0f;
|
||||
break;
|
||||
|
||||
case "minecraft:snowy_plains":
|
||||
emission = 0.85f;
|
||||
targetDensity = 0.035f;
|
||||
humidity = 0.55f;
|
||||
timeSensitivity = 0.9f;
|
||||
break;
|
||||
|
||||
// === Ванильные луга и вишнёвые рощи ===
|
||||
case "minecraft:meadow":
|
||||
case "minecraft:cherry_grove":
|
||||
emission = 1.1f;
|
||||
targetDensity = 0.035f;
|
||||
humidity = 0.6f;
|
||||
timeSensitivity = 0.9f;
|
||||
break;
|
||||
|
||||
// === Ванильные леса ===
|
||||
case "minecraft:forest":
|
||||
case "minecraft:flower_forest":
|
||||
case "minecraft:birch_forest":
|
||||
case "minecraft:old_growth_birch_forest":
|
||||
emission = 0.9f;
|
||||
targetDensity = 0.036f;
|
||||
humidity = 0.6f;
|
||||
timeSensitivity = 0.9f;
|
||||
break;
|
||||
|
||||
case "minecraft:dark_forest":
|
||||
emission = 1.0f;
|
||||
targetDensity = 0.04f;
|
||||
humidity = 0.65f;
|
||||
timeSensitivity = 0.85f;
|
||||
break;
|
||||
|
||||
case "minecraft:mushroom_fields":
|
||||
emission = 0.7f;
|
||||
targetDensity = 0.025f;
|
||||
humidity = 0.5f;
|
||||
timeSensitivity = 0.8f;
|
||||
break;
|
||||
|
||||
// === Ванильная тайга ===
|
||||
case "minecraft:taiga":
|
||||
case "minecraft:snowy_taiga":
|
||||
emission = 0.85f;
|
||||
targetDensity = 0.038f;
|
||||
humidity = 0.6f;
|
||||
timeSensitivity = 0.9f;
|
||||
break;
|
||||
|
||||
case "minecraft:old_growth_pine_taiga":
|
||||
case "minecraft:old_growth_spruce_taiga":
|
||||
emission = 0.95f;
|
||||
targetDensity = 0.042f;
|
||||
humidity = 0.7f;
|
||||
timeSensitivity = 0.8f;
|
||||
break;
|
||||
|
||||
// === Ванильные саванны ===
|
||||
case "minecraft:savanna":
|
||||
case "minecraft:savanna_plateau":
|
||||
case "minecraft:windswept_savanna":
|
||||
emission = 0.2f;
|
||||
targetDensity = 0.005f;
|
||||
humidity = 0.1f;
|
||||
timeSensitivity = 1.0f;
|
||||
break;
|
||||
|
||||
// === Ванильные горы ===
|
||||
case "minecraft:windswept_hills":
|
||||
case "minecraft:windswept_gravelly_hills":
|
||||
case "minecraft:windswept_forest":
|
||||
emission = 0.6f;
|
||||
targetDensity = 0.02f;
|
||||
humidity = 0.35f;
|
||||
timeSensitivity = 1.0f;
|
||||
break;
|
||||
|
||||
case "minecraft:grove":
|
||||
emission = 0.7f;
|
||||
targetDensity = 0.025f;
|
||||
humidity = 0.4f;
|
||||
timeSensitivity = 0.9f;
|
||||
break;
|
||||
|
||||
case "minecraft:snowy_slopes":
|
||||
emission = 0.65f;
|
||||
targetDensity = 0.022f;
|
||||
humidity = 0.35f;
|
||||
timeSensitivity = 0.95f;
|
||||
break;
|
||||
|
||||
case "minecraft:jagged_peaks":
|
||||
case "minecraft:frozen_peaks":
|
||||
case "minecraft:stony_peaks":
|
||||
emission = 0.5f;
|
||||
targetDensity = 0.015f;
|
||||
humidity = 0.25f;
|
||||
timeSensitivity = 1.0f;
|
||||
break;
|
||||
|
||||
// === Ванильные побережья ===
|
||||
case "minecraft:beach":
|
||||
case "minecraft:snowy_beach":
|
||||
emission = 0.75f;
|
||||
targetDensity = 0.028f;
|
||||
humidity = 0.6f;
|
||||
timeSensitivity = 0.85f;
|
||||
break;
|
||||
|
||||
case "minecraft:stony_shore":
|
||||
emission = 0.6f;
|
||||
targetDensity = 0.02f;
|
||||
humidity = 0.45f;
|
||||
timeSensitivity = 0.9f;
|
||||
break;
|
||||
|
||||
// === Ванильные реки ===
|
||||
case "minecraft:river":
|
||||
emission = 0.8f;
|
||||
targetDensity = 0.032f;
|
||||
humidity = 0.65f;
|
||||
timeSensitivity = 0.8f;
|
||||
break;
|
||||
|
||||
case "minecraft:frozen_river":
|
||||
emission = 0.7f;
|
||||
targetDensity = 0.035f;
|
||||
humidity = 0.6f;
|
||||
timeSensitivity = 0.85f;
|
||||
break;
|
||||
|
||||
// === Ванильные океаны ===
|
||||
case "minecraft:warm_ocean":
|
||||
emission = 0.9f;
|
||||
targetDensity = 0.035f;
|
||||
humidity = 0.7f;
|
||||
timeSensitivity = 0.7f;
|
||||
break;
|
||||
|
||||
case "minecraft:lukewarm_ocean":
|
||||
case "minecraft:deep_lukewarm_ocean":
|
||||
emission = 0.85f;
|
||||
targetDensity = 0.032f;
|
||||
humidity = 0.65f;
|
||||
timeSensitivity = 0.75f;
|
||||
break;
|
||||
|
||||
case "minecraft:ocean":
|
||||
case "minecraft:deep_ocean":
|
||||
emission = 0.8f;
|
||||
targetDensity = 0.03f;
|
||||
humidity = 0.65f;
|
||||
timeSensitivity = 0.8f;
|
||||
break;
|
||||
|
||||
case "minecraft:cold_ocean":
|
||||
case "minecraft:deep_cold_ocean":
|
||||
emission = 0.75f;
|
||||
targetDensity = 0.035f;
|
||||
humidity = 0.6f;
|
||||
timeSensitivity = 0.85f;
|
||||
break;
|
||||
|
||||
case "minecraft:frozen_ocean":
|
||||
case "minecraft:deep_frozen_ocean":
|
||||
emission = 0.7f;
|
||||
targetDensity = 0.038f;
|
||||
humidity = 0.55f;
|
||||
timeSensitivity = 0.9f;
|
||||
break;
|
||||
|
||||
case "minecraft:ice_spikes":
|
||||
emission = 0.7f;
|
||||
targetDensity = 0.04f;
|
||||
humidity = 0.6f;
|
||||
timeSensitivity = 0.85f;
|
||||
break;
|
||||
|
||||
// === Ванильный Незер ===
|
||||
case "minecraft:nether_wastes":
|
||||
case "minecraft:soul_sand_valley":
|
||||
case "minecraft:basalt_deltas":
|
||||
case "minecraft:crimson_forest":
|
||||
case "minecraft:warped_forest":
|
||||
emission = 0.05f;
|
||||
targetDensity = 0.0f;
|
||||
humidity = 0.0f;
|
||||
timeSensitivity = 1.0f;
|
||||
break;
|
||||
|
||||
// === Ванильный Край ===
|
||||
case "minecraft:the_end":
|
||||
case "minecraft:small_end_islands":
|
||||
case "minecraft:end_midlands":
|
||||
case "minecraft:end_highlands":
|
||||
case "minecraft:end_barrens":
|
||||
emission = 0.05f;
|
||||
targetDensity = 0.0f;
|
||||
humidity = 0.0f;
|
||||
timeSensitivity = 1.0f;
|
||||
break;
|
||||
|
||||
case "minecraft:the_void":
|
||||
emission = 0.0f;
|
||||
targetDensity = 0.0f;
|
||||
humidity = 0.0f;
|
||||
timeSensitivity = 1.0f;
|
||||
break;
|
||||
|
||||
// === Примеры модовых биомов (можно добавлять свои) ===
|
||||
// Biomes O' Plenty
|
||||
case "biomesoplenty:cherry_blossom_grove":
|
||||
emission = 1.1f;
|
||||
targetDensity = 0.035f;
|
||||
humidity = 0.65f;
|
||||
timeSensitivity = 0.85f;
|
||||
break;
|
||||
|
||||
case "biomesoplenty:lavender_field":
|
||||
emission = 1.0f;
|
||||
targetDensity = 0.03f;
|
||||
humidity = 0.55f;
|
||||
timeSensitivity = 0.9f;
|
||||
break;
|
||||
|
||||
case "biomesoplenty:wetland":
|
||||
emission = 1.3f;
|
||||
targetDensity = 0.06f;
|
||||
humidity = 0.8f;
|
||||
timeSensitivity = 0.6f;
|
||||
break;
|
||||
|
||||
// Terrestria
|
||||
case "terrestria:redwood_forest":
|
||||
emission = 1.0f;
|
||||
targetDensity = 0.04f;
|
||||
humidity = 0.7f;
|
||||
timeSensitivity = 0.8f;
|
||||
break;
|
||||
|
||||
// Traverse
|
||||
case "traverse:autumnal_wooded_hills":
|
||||
emission = 0.9f;
|
||||
targetDensity = 0.035f;
|
||||
humidity = 0.6f;
|
||||
timeSensitivity = 0.9f;
|
||||
break;
|
||||
|
||||
// === Fallback для неизвестных биомов ===
|
||||
default:
|
||||
float temperature = biome.getTemperature();
|
||||
float downfall = biome.weather.downfall();
|
||||
|
||||
humidity = downfall;
|
||||
emission = 0.7f + downfall * 0.6f;
|
||||
targetDensity = downfall * 0.06f;
|
||||
timeSensitivity = 1.0f - downfall * 0.5f;
|
||||
|
||||
if (temperature < 0.3f) {
|
||||
emission *= 1.2f;
|
||||
targetDensity *= 1.3f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return new FogBiomeParams(emission, targetDensity, humidity, timeSensitivity);
|
||||
}
|
||||
|
||||
public void pack(float[] array, int offset) {
|
||||
array[offset ] = emissionMultiplier;
|
||||
array[offset + 1] = targetDensityBase;
|
||||
array[offset + 2] = humidity;
|
||||
array[offset + 3] = timeSensitivity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.registry.entry.RegistryEntry;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLongArray;
|
||||
|
||||
public final class FogBiomeStreamer {
|
||||
|
||||
private static final int TOTAL_CHUNKS =
|
||||
FogWorldVolume.CHUNKS_X * FogWorldVolume.CHUNKS_Z;
|
||||
private static final int TOTAL_WORDS = (TOTAL_CHUNKS + 63) / 64;
|
||||
|
||||
// ИЗМЕНЕНО: 4×4 пикселя на чанк × 4 канала = 64 float
|
||||
private static final int CHUNK_DATA_FLOATS =
|
||||
FogBiomeTexture.CHUNK_DATA_FLOATS;
|
||||
|
||||
private static final int MAX_CHUNKS_PER_TICK = 8;
|
||||
private static volatile int burstFrames = 0;
|
||||
|
||||
private static final AtomicLongArray pendingMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final AtomicLongArray readyMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final Object sharedStateLock = new Object();
|
||||
|
||||
private static final float[][] preparedData = new float[TOTAL_CHUNKS][CHUNK_DATA_FLOATS];
|
||||
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 FogBiomeStreamer() {
|
||||
}
|
||||
|
||||
private static int floorMod(int x, int y) {
|
||||
int r = x % y;
|
||||
return r < 0 ? r + y : r;
|
||||
}
|
||||
|
||||
private static void setBit(AtomicLongArray mask, int index) {
|
||||
int wordIndex = index >> 6;
|
||||
long bitMask = 1L << (index & 63);
|
||||
while (true) {
|
||||
long current = mask.get(wordIndex);
|
||||
if ((current & bitMask) != 0) return;
|
||||
if (mask.compareAndSet(wordIndex, current, current | bitMask)) return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearBit(AtomicLongArray mask, int index) {
|
||||
int wordIndex = index >> 6;
|
||||
long bitMask = 1L << (index & 63);
|
||||
while (true) {
|
||||
long current = mask.get(wordIndex);
|
||||
if ((current & bitMask) == 0) return;
|
||||
if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return;
|
||||
}
|
||||
}
|
||||
|
||||
private static int nextSetBit(AtomicLongArray mask, int startIndex) {
|
||||
for (int i = startIndex; i < TOTAL_CHUNKS; i++) {
|
||||
int wordIndex = i >> 6;
|
||||
long bitMask = 1L << (i & 63);
|
||||
if ((mask.get(wordIndex) & bitMask) != 0) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void update(
|
||||
int oldOriginChunkX, int oldOriginChunkZ,
|
||||
int newOriginChunkX, int newOriginChunkZ,
|
||||
int ringChunkOffsetX, int ringChunkOffsetZ
|
||||
) {
|
||||
synchronized (sharedStateLock) {
|
||||
currentOriginChunkX = newOriginChunkX;
|
||||
currentOriginChunkZ = newOriginChunkZ;
|
||||
currentRingChunkOffsetX = ringChunkOffsetX;
|
||||
currentRingChunkOffsetZ = ringChunkOffsetZ;
|
||||
|
||||
if (firstUpdate) {
|
||||
markAllPending();
|
||||
firstUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int dx = newOriginChunkX - oldOriginChunkX;
|
||||
int dz = newOriginChunkZ - oldOriginChunkZ;
|
||||
if (dx == 0 && dz == 0) return;
|
||||
|
||||
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
|
||||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
|
||||
markAllPending();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dx > 0) markPendingRange(newOriginChunkX + FogWorldVolume.CHUNKS_X - dx,
|
||||
newOriginChunkZ, dx, FogWorldVolume.CHUNKS_Z);
|
||||
if (dx < 0) markPendingRange(newOriginChunkX,
|
||||
newOriginChunkZ, -dx, FogWorldVolume.CHUNKS_Z);
|
||||
if (dz > 0) markPendingRange(newOriginChunkX,
|
||||
newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz,
|
||||
FogWorldVolume.CHUNKS_X, dz);
|
||||
if (dz < 0) markPendingRange(newOriginChunkX,
|
||||
newOriginChunkZ, FogWorldVolume.CHUNKS_X, -dz);
|
||||
}
|
||||
}
|
||||
|
||||
private static void markPendingRange(int worldX, int worldZ, int countX, int countZ) {
|
||||
for (int i = 0; i < countX; i++) {
|
||||
int wx = worldX + i;
|
||||
for (int j = 0; j < countZ; j++) {
|
||||
int wz = worldZ + j;
|
||||
int physicalX = floorMod(wx, FogWorldVolume.CHUNKS_X);
|
||||
int physicalZ = floorMod(wz, FogWorldVolume.CHUNKS_Z);
|
||||
int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX;
|
||||
clearBit(readyMask, physicalIndex);
|
||||
setBit(pendingMask, physicalIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void markAllPending() {
|
||||
for (int i = 0; i < TOTAL_WORDS; i++) {
|
||||
pendingMask.set(i, -1L);
|
||||
readyMask.set(i, 0L);
|
||||
}
|
||||
int extraBits = TOTAL_CHUNKS & 63;
|
||||
if (extraBits != 0) {
|
||||
pendingMask.set(TOTAL_WORDS - 1, (1L << extraBits) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static void processPending() {
|
||||
synchronized (sharedStateLock) {
|
||||
int originX = currentOriginChunkX;
|
||||
int originZ = currentOriginChunkZ;
|
||||
int ringOffsetX = currentRingChunkOffsetX;
|
||||
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||
|
||||
int processedCount = 0;
|
||||
int index = 0;
|
||||
int limit = MAX_CHUNKS_PER_TICK + (burstFrames > 0 ? MAX_CHUNKS_PER_TICK : 0);
|
||||
|
||||
while (processedCount < limit) {
|
||||
index = nextSetBit(pendingMask, index);
|
||||
if (index < 0) break;
|
||||
|
||||
int physicalX = index % FogWorldVolume.CHUNKS_X;
|
||||
int physicalZ = index / FogWorldVolume.CHUNKS_X;
|
||||
|
||||
int worldChunkX = originX + floorMod(physicalX - ringOffsetX, FogWorldVolume.CHUNKS_X);
|
||||
int worldChunkZ = originZ + floorMod(physicalZ - ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
||||
|
||||
if (fillBiomesIntoArray(worldChunkX, worldChunkZ, preparedData[index])) {
|
||||
preparedWorldChunkX[index] = worldChunkX;
|
||||
preparedWorldChunkZ[index] = worldChunkZ;
|
||||
setBit(readyMask, index);
|
||||
clearBit(pendingMask, index);
|
||||
processedCount++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (burstFrames > 0) burstFrames--;
|
||||
}
|
||||
}
|
||||
|
||||
// ИЗМЕНЕНО: усреднение параметров биомов
|
||||
private static boolean fillBiomesIntoArray(int chunkX, int chunkZ, float[] array) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.world == null) return false;
|
||||
|
||||
WorldChunk chunk = client.world.getChunkManager()
|
||||
.getWorldChunk(chunkX, chunkZ, false);
|
||||
if (chunk == null) return false;
|
||||
|
||||
int topY = client.world.getTopY() - 1;
|
||||
int baseWorldX = chunkX << 4;
|
||||
int baseWorldZ = chunkZ << 4;
|
||||
|
||||
int idx = 0;
|
||||
// 4×4 пикселя на чанк (каждый покрывает 4×4 блока)
|
||||
for (int pz = 0; pz < FogBiomeTexture.PIXELS_PER_CHUNK; pz++) {
|
||||
for (int px = 0; px < FogBiomeTexture.PIXELS_PER_CHUNK; px++) {
|
||||
// Суммируем параметры всех 16 блоков в этом пикселе
|
||||
float sumEmission = 0;
|
||||
float sumTargetDensity = 0;
|
||||
float sumHumidity = 0;
|
||||
float sumTimeSensitivity = 0;
|
||||
|
||||
for (int dz = 0; dz < FogBiomeTexture.PIXELS_PER_CHUNK; dz++) {
|
||||
for (int dx = 0; dx < FogBiomeTexture.PIXELS_PER_CHUNK; dx++) {
|
||||
int localX = px * FogBiomeTexture.PIXELS_PER_CHUNK + dx;
|
||||
int localZ = pz * FogBiomeTexture.PIXELS_PER_CHUNK + dz;
|
||||
int worldX = baseWorldX + localX;
|
||||
int worldZ = baseWorldZ + localZ;
|
||||
|
||||
BlockPos samplePos = new BlockPos(worldX, topY, worldZ);
|
||||
RegistryEntry<Biome> biomeEntry = client.world.getBiome(samplePos);
|
||||
FogBiomeParams params = FogBiomeParams.forBiome(biomeEntry);
|
||||
|
||||
sumEmission += params.emissionMultiplier;
|
||||
sumTargetDensity += params.targetDensityBase;
|
||||
sumHumidity += params.humidity;
|
||||
sumTimeSensitivity += params.timeSensitivity;
|
||||
}
|
||||
}
|
||||
|
||||
// Усредняем
|
||||
float invCount = 1.0f / (FogBiomeTexture.PIXELS_PER_CHUNK * FogBiomeTexture.PIXELS_PER_CHUNK);
|
||||
array[idx ] = sumEmission * invCount;
|
||||
array[idx + 1] = sumTargetDensity * invCount;
|
||||
array[idx + 2] = sumHumidity * invCount;
|
||||
array[idx + 3] = sumTimeSensitivity * invCount;
|
||||
idx += 4;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void uploadReadyData() {
|
||||
synchronized (sharedStateLock) {
|
||||
int originX = currentOriginChunkX;
|
||||
int originZ = currentOriginChunkZ;
|
||||
int ringOffsetX = currentRingChunkOffsetX;
|
||||
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||
|
||||
int processed = 0;
|
||||
int index = 0;
|
||||
int limit = MAX_CHUNKS_PER_TICK + (burstFrames > 0 ? MAX_CHUNKS_PER_TICK : 0);
|
||||
|
||||
while (processed < limit) {
|
||||
index = nextSetBit(readyMask, index);
|
||||
if (index < 0) break;
|
||||
|
||||
int physicalX = index % FogWorldVolume.CHUNKS_X;
|
||||
int physicalZ = index / FogWorldVolume.CHUNKS_X;
|
||||
|
||||
int currentWorldX = originX + floorMod(physicalX - ringOffsetX, FogWorldVolume.CHUNKS_X);
|
||||
int currentWorldZ = originZ + floorMod(physicalZ - ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
||||
|
||||
if (preparedWorldChunkX[index] == currentWorldX &&
|
||||
preparedWorldChunkZ[index] == currentWorldZ) {
|
||||
|
||||
FogBiomeTexture.uploadChunk(physicalX, physicalZ, preparedData[index]);
|
||||
clearBit(readyMask, index);
|
||||
processed++;
|
||||
} else {
|
||||
clearBit(readyMask, index);
|
||||
setBit(pendingMask, index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (burstFrames > 0) burstFrames--;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setBurst(int frames) {
|
||||
burstFrames = Math.max(burstFrames, frames);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public final class FogBiomeTexture {
|
||||
|
||||
// ИЗМЕНЕНО: 64×64 (один пиксель = 4×4 блока)
|
||||
public static final int SIZE_X = FogWorldVolume.SIZE_X / 4; // 64
|
||||
public static final int SIZE_Z = FogWorldVolume.SIZE_Z / 4; // 64
|
||||
|
||||
private static int texture = -1;
|
||||
|
||||
private static final int[] uploadPBOs = new int[3];
|
||||
private static int uploadPBOIndex = 0;
|
||||
|
||||
// ИЗМЕНЕНО: 4×4 пикселя на чанк × 4 канала = 64 float
|
||||
public static final int PIXELS_PER_CHUNK = 4;
|
||||
public static final int CHUNK_DATA_FLOATS =
|
||||
PIXELS_PER_CHUNK * PIXELS_PER_CHUNK * 4;
|
||||
public static final int CHUNK_DATA_BYTES = CHUNK_DATA_FLOATS * 4;
|
||||
|
||||
private static final FloatBuffer CHUNK_BUFFER =
|
||||
BufferUtils.createFloatBuffer(CHUNK_DATA_FLOATS);
|
||||
|
||||
private static final float[] EMPTY_CHUNK = new float[CHUNK_DATA_FLOATS];
|
||||
private static final float[] EMPTY_PIXEL = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
|
||||
private FogBiomeTexture() {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (texture != -1) return;
|
||||
|
||||
initUploadPBO();
|
||||
|
||||
texture = GL11.glGenTextures();
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
|
||||
GL30.glTexImage2D(
|
||||
GL11.GL_TEXTURE_2D,
|
||||
0,
|
||||
GL30.GL_RGBA16F,
|
||||
SIZE_X,
|
||||
SIZE_Z,
|
||||
0,
|
||||
GL11.GL_RGBA,
|
||||
GL11.GL_FLOAT,
|
||||
(ByteBuffer) null
|
||||
);
|
||||
|
||||
// ИЗМЕНЕНО: GL_LINEAR для плавной интерполяции между биомами
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_REPEAT);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_REPEAT);
|
||||
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
CHUNK_BUFFER.clear();
|
||||
CHUNK_BUFFER.put(EMPTY_CHUNK);
|
||||
CHUNK_BUFFER.flip();
|
||||
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
// 16×16 чанков (теперь каждый = 4×4 пикселя)
|
||||
for (int z = 0; z < FogWorldVolume.CHUNKS_Z; z++) {
|
||||
for (int x = 0; x < FogWorldVolume.CHUNKS_X; x++) {
|
||||
GL11.glTexSubImage2D(
|
||||
GL11.GL_TEXTURE_2D, 0,
|
||||
x * PIXELS_PER_CHUNK,
|
||||
z * PIXELS_PER_CHUNK,
|
||||
PIXELS_PER_CHUNK,
|
||||
PIXELS_PER_CHUNK,
|
||||
GL11.GL_RGBA, GL11.GL_FLOAT,
|
||||
CHUNK_BUFFER
|
||||
);
|
||||
CHUNK_BUFFER.rewind();
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
public static void uploadChunk(int textureChunkX, int textureChunkZ, float[] data) {
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
|
||||
int pbo = uploadPBOs[uploadPBOIndex % uploadPBOs.length];
|
||||
uploadPBOIndex++;
|
||||
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, pbo);
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, CHUNK_DATA_BYTES, GL15.GL_STREAM_DRAW);
|
||||
|
||||
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
|
||||
if (mapped != null) {
|
||||
FloatBuffer floatMapped = mapped.asFloatBuffer();
|
||||
floatMapped.put(data);
|
||||
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||
|
||||
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);
|
||||
|
||||
GL11.glTexSubImage2D(
|
||||
GL11.GL_TEXTURE_2D,
|
||||
0,
|
||||
textureChunkX * PIXELS_PER_CHUNK,
|
||||
textureChunkZ * PIXELS_PER_CHUNK,
|
||||
PIXELS_PER_CHUNK,
|
||||
PIXELS_PER_CHUNK,
|
||||
GL11.GL_RGBA,
|
||||
GL11.GL_FLOAT,
|
||||
0L
|
||||
);
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
public static void clearRegions(
|
||||
int[] physX, int[] physZ,
|
||||
int[] widthChunks, int[] depthChunks,
|
||||
int count
|
||||
) {
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int w = widthChunks[i] * PIXELS_PER_CHUNK;
|
||||
int d = depthChunks[i] * PIXELS_PER_CHUNK;
|
||||
int totalPixels = w * d;
|
||||
|
||||
FloatBuffer clearBuf = BufferUtils.createFloatBuffer(totalPixels * 4);
|
||||
for (int j = 0; j < totalPixels; j++) {
|
||||
clearBuf.put(EMPTY_PIXEL);
|
||||
}
|
||||
clearBuf.flip();
|
||||
|
||||
GL11.glTexSubImage2D(
|
||||
GL11.GL_TEXTURE_2D, 0,
|
||||
physX[i] * PIXELS_PER_CHUNK,
|
||||
physZ[i] * PIXELS_PER_CHUNK,
|
||||
w, d,
|
||||
GL11.GL_RGBA, GL11.GL_FLOAT,
|
||||
clearBuf
|
||||
);
|
||||
}
|
||||
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
private static void initUploadPBO() {
|
||||
for (int i = 0; i < uploadPBOs.length; i++) {
|
||||
uploadPBOs[i] = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBOs[i]);
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, CHUNK_DATA_BYTES, GL15.GL_STREAM_DRAW);
|
||||
}
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
public static int getTexture() {
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
public record FogBlockProperties(
|
||||
boolean solid,
|
||||
int sign,
|
||||
int magnitude
|
||||
) {
|
||||
public static final FogBlockProperties DEFAULT = new FogBlockProperties(false, 0, 0);
|
||||
|
||||
public FogBlockProperties {
|
||||
if (sign < -1 || sign > 1) {
|
||||
throw new IllegalArgumentException("sign must be -1, 0, or 1");
|
||||
}
|
||||
if (magnitude < 0 || magnitude > 63) {
|
||||
throw new IllegalArgumentException("magnitude must be 0-63");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Упаковывает свойства в один байт:
|
||||
* Бит 7: solid (1 = solid)
|
||||
* Бит 6: sign (0 = source/neutral, 1 = absorber)
|
||||
* Биты 0-5: magnitude (0-63)
|
||||
*/
|
||||
public byte pack() {
|
||||
int packed = 0;
|
||||
|
||||
if (solid) {
|
||||
packed |= 0b10000000;
|
||||
}
|
||||
|
||||
if (sign < 0) {
|
||||
packed |= 0b01000000;
|
||||
}
|
||||
|
||||
packed |= (magnitude & 0b00111111);
|
||||
|
||||
return (byte) packed;
|
||||
}
|
||||
|
||||
public static FogBlockProperties unpack(byte packed) {
|
||||
boolean solid = (packed & 0b10000000) != 0;
|
||||
boolean isAbsorber = (packed & 0b01000000) != 0;
|
||||
int magnitude = packed & 0b00111111;
|
||||
int sign = isAbsorber ? -1 : (magnitude > 0 ? 1 : 0);
|
||||
|
||||
return new FogBlockProperties(solid, sign, magnitude);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.fluid.Fluid;
|
||||
import net.minecraft.fluid.Fluids;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.tag.BlockTags;
|
||||
|
||||
public final class FogBlockRegistry {
|
||||
|
||||
private static final byte[] BLOCK_CACHE;
|
||||
private static final boolean[] BLOCK_CACHE_INITIALIZED;
|
||||
|
||||
// Предвычисленное значение для waterlogged блоков (вода)
|
||||
private static final byte WATER_VALUE;
|
||||
|
||||
static {
|
||||
int blockCount = Registries.BLOCK.size();
|
||||
BLOCK_CACHE = new byte[blockCount];
|
||||
BLOCK_CACHE_INITIALIZED = new boolean[blockCount];
|
||||
|
||||
// Waterlogged блок всегда содержит воду
|
||||
WATER_VALUE = new FogBlockProperties(true, 1, 20).pack();
|
||||
}
|
||||
|
||||
private FogBlockRegistry() {
|
||||
}
|
||||
|
||||
public static byte getPackedProperties(BlockState state) {
|
||||
// СНАЧАЛА проверяем конкретно воду (waterlogged или обычный блок воды)
|
||||
Fluid fluid = state.getFluidState().getFluid();
|
||||
if (fluid == Fluids.WATER || fluid == Fluids.FLOWING_WATER) {
|
||||
return WATER_VALUE;
|
||||
}
|
||||
|
||||
Block block = state.getBlock();
|
||||
int id = Registries.BLOCK.getRawId(block);
|
||||
|
||||
if (BLOCK_CACHE_INITIALIZED[id]) {
|
||||
return BLOCK_CACHE[id];
|
||||
}
|
||||
|
||||
byte packed = computePackedProperties(state);
|
||||
BLOCK_CACHE[id] = packed;
|
||||
BLOCK_CACHE_INITIALIZED[id] = true;
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
private static byte computePackedProperties(BlockState state) {
|
||||
Block block = state.getBlock();
|
||||
boolean solid = computeSolid(state);
|
||||
|
||||
// Источники тумана (sign = +1)
|
||||
// WATER уже обработан в getPackedProperties
|
||||
if (block == Blocks.GRASS || block == Blocks.TALL_GRASS || block == Blocks.FERN) {
|
||||
return new FogBlockProperties(solid, 1, 50).pack();
|
||||
}
|
||||
if (block == Blocks.VINE) {
|
||||
return new FogBlockProperties(solid, 1, 45).pack();
|
||||
}
|
||||
|
||||
if (block == Blocks.SOUL_CAMPFIRE || block == Blocks.SOUL_FIRE) {
|
||||
return new FogBlockProperties(solid, 1, 63).pack();
|
||||
}
|
||||
if (block == Blocks.SOUL_LANTERN) {
|
||||
return new FogBlockProperties(solid, 1, 31).pack();
|
||||
}
|
||||
if (block == Blocks.SOUL_TORCH || block == Blocks.SOUL_WALL_TORCH) {
|
||||
return new FogBlockProperties(solid, 1, 15).pack();
|
||||
}
|
||||
|
||||
// Поглотители тумана (sign = -1)
|
||||
if (block == Blocks.LAVA) {
|
||||
return new FogBlockProperties(solid, -1, 50).pack();
|
||||
}
|
||||
if (block == Blocks.MAGMA_BLOCK) {
|
||||
return new FogBlockProperties(solid, -1, 40).pack();
|
||||
}
|
||||
if (block == Blocks.FIRE) {
|
||||
return new FogBlockProperties(solid, -1, 30).pack();
|
||||
}
|
||||
if (block == Blocks.CAMPFIRE) {
|
||||
return new FogBlockProperties(solid, -1, 63).pack();
|
||||
}
|
||||
if (block == Blocks.TORCH || block == Blocks.WALL_TORCH) {
|
||||
return new FogBlockProperties(solid, -1, 15).pack();
|
||||
}
|
||||
if (block == Blocks.LANTERN) {
|
||||
return new FogBlockProperties(solid, -1, 25).pack();
|
||||
}
|
||||
|
||||
// Нейтральные блоки (sign = 0)
|
||||
return new FogBlockProperties(solid, 0, 0).pack();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static boolean computeSolid(BlockState state) {
|
||||
if (state.isAir()) return false;
|
||||
// Waterlogged уже обработан в getPackedProperties
|
||||
if (!state.getFluidState().isEmpty()) return true; // лава и другие жидкости
|
||||
if (state.isIn(BlockTags.LEAVES)) return false;
|
||||
if (state.getBlock() == Blocks.MANGROVE_ROOTS) return false;
|
||||
return state.blocksMovement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
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 {
|
||||
|
||||
public static boolean isChunkLoaded(int chunkX, int chunkZ) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
|
||||
if (client.world == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return client.world.getChunkManager().getWorldChunk(chunkX, chunkZ, false) != null;
|
||||
}
|
||||
|
||||
public static boolean fillChunkIntoArray(int chunkX, int chunkZ, byte[] array) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
|
||||
if (!isChunkLoaded(chunkX, chunkZ)) return false;
|
||||
|
||||
WorldChunk chunk = client.world.getChunk(chunkX, chunkZ);
|
||||
|
||||
ChunkSection[] sections = chunk.getSectionArray();
|
||||
int bottomY = client.world.getBottomY();
|
||||
int topYExclusive = bottomY + sections.length * 16;
|
||||
|
||||
int index = 0;
|
||||
byte airValue = FogBlockRegistry.getPackedProperties(Blocks.AIR.getDefaultState());
|
||||
|
||||
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;
|
||||
|
||||
if (worldY < bottomY || worldY >= topYExclusive) {
|
||||
Arrays.fill(array, index, index + FogWorldVolume.CHUNK_SIZE, airValue);
|
||||
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, airValue);
|
||||
index += FogWorldVolume.CHUNK_SIZE;
|
||||
continue;
|
||||
}
|
||||
|
||||
int baseIndex = (localY << 8) | (z << 4);
|
||||
|
||||
for (int x = 0; x < FogWorldVolume.CHUNK_SIZE; x++) {
|
||||
BlockState currentBlock = currentContainer.get(baseIndex | x);
|
||||
array[index++] = FogBlockRegistry.getPackedProperties(currentBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static byte computePackedVoxelValue(BlockState state) {
|
||||
return FogBlockRegistry.getPackedProperties(state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLongArray;
|
||||
|
||||
public final class FogChunkStreamer {
|
||||
|
||||
private static final int TOTAL_CHUNKS =
|
||||
FogWorldVolume.CHUNKS_X * FogWorldVolume.CHUNKS_Z;
|
||||
|
||||
private static final int TOTAL_WORDS = (TOTAL_CHUNKS + 63) / 64;
|
||||
|
||||
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 int MAX_READY_CHUNK_UPLOADS_PER_TICK = 4;
|
||||
private static volatile int burstFrames = 0;
|
||||
|
||||
// Битовые маски вместо массивов статусов
|
||||
private static final AtomicLongArray pendingMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final AtomicLongArray readyMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final Object sharedStateLock = new Object();
|
||||
|
||||
// Pre-allocated массивы данных
|
||||
private static final byte[][] preparedData = new byte[TOTAL_CHUNKS][CHUNK_DATA_SIZE];
|
||||
private static final int[] preparedWorldChunkX = new int[TOTAL_CHUNKS];
|
||||
private static final int[] preparedWorldChunkZ = new int[TOTAL_CHUNKS];
|
||||
|
||||
// Ring buffer для block change events (без аллокаций)
|
||||
private static final int EVENT_BUFFER_SIZE = 256;
|
||||
private static final int[] eventChunkX = new int[EVENT_BUFFER_SIZE];
|
||||
private static final int[] eventChunkZ = new int[EVENT_BUFFER_SIZE];
|
||||
private static final int[] eventBlockX = new int[EVENT_BUFFER_SIZE];
|
||||
private static final int[] eventBlockY = new int[EVENT_BUFFER_SIZE];
|
||||
private static final int[] eventBlockZ = new int[EVENT_BUFFER_SIZE];
|
||||
private static final BlockState[] eventState = new BlockState[EVENT_BUFFER_SIZE];
|
||||
private static volatile int eventWriteIndex = 0;
|
||||
private static volatile int eventReadIndex = 0;
|
||||
|
||||
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 static final int MAX_REGIONS = 16;
|
||||
private static final int[] regionPhysX = new int[MAX_REGIONS];
|
||||
private static final int[] regionPhysZ = new int[MAX_REGIONS];
|
||||
private static final int[] regionWidth = new int[MAX_REGIONS];
|
||||
private static final int[] regionDepth = new int[MAX_REGIONS];
|
||||
private static int regionCount = 0;
|
||||
|
||||
// Pre-allocated temp массивы
|
||||
private static final int[] tempXStarts = new int[2];
|
||||
private static final int[] tempXCounts = new int[2];
|
||||
private static final int[] tempZStarts = new int[2];
|
||||
private static final int[] tempZCounts = new int[2];
|
||||
|
||||
private FogChunkStreamer() {
|
||||
}
|
||||
|
||||
// ======================== Утилиты ========================
|
||||
|
||||
private static int floorMod(int x, int y) {
|
||||
int r = x % y;
|
||||
return r < 0 ? r + y : r;
|
||||
}
|
||||
|
||||
private static void setBit(AtomicLongArray mask, int index) {
|
||||
int wordIndex = index >> 6;
|
||||
long bitMask = 1L << (index & 63);
|
||||
while (true) {
|
||||
long current = mask.get(wordIndex);
|
||||
if ((current & bitMask) != 0) return;
|
||||
if (mask.compareAndSet(wordIndex, current, current | bitMask)) return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearBit(AtomicLongArray mask, int index) {
|
||||
int wordIndex = index >> 6;
|
||||
long bitMask = 1L << (index & 63);
|
||||
while (true) {
|
||||
long current = mask.get(wordIndex);
|
||||
if ((current & bitMask) == 0) return;
|
||||
if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return;
|
||||
}
|
||||
}
|
||||
|
||||
private static int nextSetBit(AtomicLongArray mask, int startIndex) {
|
||||
for (int i = startIndex; i < TOTAL_CHUNKS; i++) {
|
||||
int wordIndex = i >> 6;
|
||||
long bitMask = 1L << (i & 63);
|
||||
if ((mask.get(wordIndex) & bitMask) != 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ======================== События ========================
|
||||
|
||||
public static void onBlockChanged(
|
||||
int chunkX, int chunkZ,
|
||||
int blockX, int blockY, int blockZ,
|
||||
BlockState newState
|
||||
) {
|
||||
int nextWrite = (eventWriteIndex + 1) % EVENT_BUFFER_SIZE;
|
||||
if (nextWrite == eventReadIndex) {
|
||||
// Буфер переполнен — пропускаем событие (или можно расширить буфер)
|
||||
return;
|
||||
}
|
||||
|
||||
eventChunkX[eventWriteIndex] = chunkX;
|
||||
eventChunkZ[eventWriteIndex] = chunkZ;
|
||||
eventBlockX[eventWriteIndex] = blockX;
|
||||
eventBlockY[eventWriteIndex] = blockY;
|
||||
eventBlockZ[eventWriteIndex] = blockZ;
|
||||
eventState[eventWriteIndex] = newState;
|
||||
eventWriteIndex = nextWrite;
|
||||
}
|
||||
|
||||
// ======================== Окно ========================
|
||||
|
||||
public static void update(
|
||||
int oldOriginChunkX, int oldOriginChunkZ,
|
||||
int newOriginChunkX, int newOriginChunkZ,
|
||||
int ringChunkOffsetX, int ringChunkOffsetZ
|
||||
) {
|
||||
synchronized (sharedStateLock) {
|
||||
currentOriginChunkX = newOriginChunkX;
|
||||
currentOriginChunkZ = newOriginChunkZ;
|
||||
currentRingChunkOffsetX = ringChunkOffsetX;
|
||||
currentRingChunkOffsetZ = ringChunkOffsetZ;
|
||||
|
||||
if (firstUpdate) {
|
||||
markAllPending();
|
||||
firstUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int dx = newOriginChunkX - oldOriginChunkX;
|
||||
int dz = newOriginChunkZ - oldOriginChunkZ;
|
||||
|
||||
if (dx == 0 && dz == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.abs(dx) >= FogWorldVolume.CHUNKS_X ||
|
||||
Math.abs(dz) >= FogWorldVolume.CHUNKS_Z) {
|
||||
markAllPending();
|
||||
return;
|
||||
}
|
||||
|
||||
regionCount = 0;
|
||||
|
||||
if (dx > 0) {
|
||||
int worldX = newOriginChunkX + FogWorldVolume.CHUNKS_X - dx;
|
||||
int worldZ = newOriginChunkZ;
|
||||
collectRegions(worldX, worldZ, dx, FogWorldVolume.CHUNKS_Z);
|
||||
markPendingRange(worldX, worldZ, dx, FogWorldVolume.CHUNKS_Z);
|
||||
}
|
||||
|
||||
if (dx < 0) {
|
||||
int worldX = newOriginChunkX;
|
||||
int worldZ = newOriginChunkZ;
|
||||
collectRegions(worldX, worldZ, -dx, FogWorldVolume.CHUNKS_Z);
|
||||
markPendingRange(worldX, worldZ, -dx, FogWorldVolume.CHUNKS_Z);
|
||||
}
|
||||
|
||||
if (dz > 0) {
|
||||
int worldX = newOriginChunkX;
|
||||
int worldZ = newOriginChunkZ + FogWorldVolume.CHUNKS_Z - dz;
|
||||
collectRegions(worldX, worldZ, FogWorldVolume.CHUNKS_X, dz);
|
||||
markPendingRange(worldX, worldZ, FogWorldVolume.CHUNKS_X, dz);
|
||||
}
|
||||
|
||||
if (dz < 0) {
|
||||
int worldX = newOriginChunkX;
|
||||
int worldZ = newOriginChunkZ;
|
||||
collectRegions(worldX, worldZ, FogWorldVolume.CHUNKS_X, -dz);
|
||||
markPendingRange(worldX, worldZ, FogWorldVolume.CHUNKS_X, -dz);
|
||||
}
|
||||
|
||||
if (regionCount > 0) {
|
||||
FogWorldVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||
FogDensityTextures.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||
}
|
||||
|
||||
uploadReadyData();
|
||||
}
|
||||
}
|
||||
|
||||
private static void markPendingRange(int worldX, int worldZ, int countX, int countZ) {
|
||||
for (int i = 0; i < countX; i++) {
|
||||
int wx = worldX + i;
|
||||
for (int j = 0; j < countZ; j++) {
|
||||
int wz = worldZ + j;
|
||||
int physicalX = floorMod(wx, FogWorldVolume.CHUNKS_X);
|
||||
int physicalZ = floorMod(wz, FogWorldVolume.CHUNKS_Z);
|
||||
int physicalIndex = physicalZ * FogWorldVolume.CHUNKS_X + physicalX;
|
||||
clearBit(readyMask, physicalIndex);
|
||||
setBit(pendingMask, physicalIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void markAllPending() {
|
||||
regionCount = 1;
|
||||
regionPhysX[0] = 0;
|
||||
regionPhysZ[0] = 0;
|
||||
regionWidth[0] = FogWorldVolume.CHUNKS_X;
|
||||
regionDepth[0] = FogWorldVolume.CHUNKS_Z;
|
||||
|
||||
for (int i = 0; i < TOTAL_WORDS; i++) {
|
||||
pendingMask.set(i, -1L);
|
||||
readyMask.set(i, 0L);
|
||||
}
|
||||
|
||||
int extraBits = TOTAL_CHUNKS & 63;
|
||||
if (extraBits != 0) {
|
||||
long mask = (1L << extraBits) - 1;
|
||||
pendingMask.set(TOTAL_WORDS - 1, mask);
|
||||
}
|
||||
|
||||
FogWorldVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||
FogDensityTextures.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||
}
|
||||
|
||||
private static void collectRegions(int worldX, int worldZ, int countX, int countZ) {
|
||||
int xSegCount = splitIntoSegments(worldX, countX, FogWorldVolume.CHUNKS_X, tempXStarts, tempXCounts);
|
||||
int zSegCount = splitIntoSegments(worldZ, countZ, FogWorldVolume.CHUNKS_Z, tempZStarts, tempZCounts);
|
||||
|
||||
for (int i = 0; i < xSegCount; i++) {
|
||||
for (int j = 0; j < zSegCount; j++) {
|
||||
if (regionCount >= MAX_REGIONS) return;
|
||||
regionPhysX[regionCount] = tempXStarts[i];
|
||||
regionPhysZ[regionCount] = tempZStarts[j];
|
||||
regionWidth[regionCount] = tempXCounts[i];
|
||||
regionDepth[regionCount] = tempZCounts[j];
|
||||
regionCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) {
|
||||
if (count == 0) return 0;
|
||||
int first = floorMod(start, dim);
|
||||
int last = floorMod(start + count - 1, dim);
|
||||
if (first <= last) {
|
||||
outStarts[0] = first;
|
||||
outCounts[0] = count;
|
||||
return 1;
|
||||
} else {
|
||||
int firstCount = dim - first;
|
||||
outStarts[0] = first;
|
||||
outCounts[0] = firstCount;
|
||||
outStarts[1] = 0;
|
||||
outCounts[1] = count - firstCount;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== Обработка ========================
|
||||
|
||||
public static void processBlockChanges() {
|
||||
int originX = currentOriginChunkX;
|
||||
int originZ = currentOriginChunkZ;
|
||||
int ringOffsetX = currentRingChunkOffsetX;
|
||||
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||
|
||||
while (eventReadIndex != eventWriteIndex) {
|
||||
int chunkX = eventChunkX[eventReadIndex];
|
||||
int chunkZ = eventChunkZ[eventReadIndex];
|
||||
int blockX = eventBlockX[eventReadIndex];
|
||||
int blockY = eventBlockY[eventReadIndex];
|
||||
int blockZ = eventBlockZ[eventReadIndex];
|
||||
BlockState newState = eventState[eventReadIndex];
|
||||
|
||||
eventReadIndex = (eventReadIndex + 1) % EVENT_BUFFER_SIZE;
|
||||
|
||||
int relativeX = chunkX - originX;
|
||||
int relativeZ = chunkZ - originZ;
|
||||
|
||||
if (relativeX < 0 || relativeX >= FogWorldVolume.CHUNKS_X ||
|
||||
relativeZ < 0 || relativeZ >= FogWorldVolume.CHUNKS_Z) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int physicalX = floorMod(relativeX + ringOffsetX, FogWorldVolume.CHUNKS_X);
|
||||
int physicalZ = floorMod(relativeZ + ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
||||
|
||||
int localX = blockX & 15;
|
||||
int localZ = blockZ & 15;
|
||||
int yIndex = blockY - FogWorldVolume.MIN_Y;
|
||||
|
||||
if (yIndex < 0 || yIndex >= FogWorldVolume.SIZE_Y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte value = FogChunkProvider.computePackedVoxelValue(newState);
|
||||
FogWorldVolume.updateSingleVoxel(physicalX, physicalZ, localX, yIndex, localZ, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void processPending() {
|
||||
synchronized (sharedStateLock) {
|
||||
int originX = currentOriginChunkX;
|
||||
int originZ = currentOriginChunkZ;
|
||||
int ringOffsetX = currentRingChunkOffsetX;
|
||||
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||
|
||||
int processedCount = 0;
|
||||
int index = 0;
|
||||
|
||||
int limit = MAX_CHUNKS_PER_TICK + (burstFrames > 0 ? MAX_CHUNKS_PER_TICK : 0);
|
||||
|
||||
while (processedCount < limit) {
|
||||
index = nextSetBit(pendingMask, index);
|
||||
if (index < 0) break;
|
||||
|
||||
int physicalX = index % FogWorldVolume.CHUNKS_X;
|
||||
int physicalZ = index / FogWorldVolume.CHUNKS_X;
|
||||
|
||||
int worldChunkX = originX + floorMod(physicalX - ringOffsetX, FogWorldVolume.CHUNKS_X);
|
||||
int worldChunkZ = originZ + floorMod(physicalZ - ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
||||
|
||||
if (FogChunkProvider.fillChunkIntoArray(worldChunkX, worldChunkZ, preparedData[index])) {
|
||||
preparedWorldChunkX[index] = worldChunkX;
|
||||
preparedWorldChunkZ[index] = worldChunkZ;
|
||||
setBit(readyMask, index);
|
||||
clearBit(pendingMask, index);
|
||||
processedCount++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (burstFrames > 0) burstFrames--;
|
||||
}
|
||||
}
|
||||
|
||||
public static void uploadReadyData() {
|
||||
synchronized (sharedStateLock) {
|
||||
int originX = currentOriginChunkX;
|
||||
int originZ = currentOriginChunkZ;
|
||||
int ringOffsetX = currentRingChunkOffsetX;
|
||||
int ringOffsetZ = currentRingChunkOffsetZ;
|
||||
|
||||
int processed = 0;
|
||||
int index = 0;
|
||||
|
||||
int limit = MAX_READY_CHUNK_UPLOADS_PER_TICK + (burstFrames > 0 ? MAX_READY_CHUNK_UPLOADS_PER_TICK : 0);
|
||||
|
||||
FogWorldVolume.beginUpload();
|
||||
while (processed < limit) {
|
||||
index = nextSetBit(readyMask, index);
|
||||
if (index < 0) break;
|
||||
|
||||
int physicalX = index % FogWorldVolume.CHUNKS_X;
|
||||
int physicalZ = index / FogWorldVolume.CHUNKS_X;
|
||||
|
||||
int currentWorldX = originX + floorMod(physicalX - ringOffsetX, FogWorldVolume.CHUNKS_X);
|
||||
int currentWorldZ = originZ + floorMod(physicalZ - ringOffsetZ, FogWorldVolume.CHUNKS_Z);
|
||||
|
||||
if (preparedWorldChunkX[index] == currentWorldX &&
|
||||
preparedWorldChunkZ[index] == currentWorldZ) {
|
||||
|
||||
FogWorldVolume.uploadChunkFromArrayNoBind(physicalX, physicalZ, preparedData[index]);
|
||||
clearBit(readyMask, index);
|
||||
processed++;
|
||||
} else {
|
||||
// Координаты изменились — помечаем как pending заново
|
||||
clearBit(readyMask, index);
|
||||
setBit(pendingMask, index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
FogWorldVolume.endUpload();
|
||||
if (burstFrames > 0) burstFrames--;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setBurst(int frames) {
|
||||
burstFrames = Math.max(burstFrames, frames);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class FogDensityTextures {
|
||||
|
||||
private static int textureA = -1;
|
||||
private static int textureB = -1;
|
||||
private static int textureC = -1;
|
||||
private static int currentIndex = 0;
|
||||
private static int previousIndex = 0;
|
||||
private static int writeIndex = 1;
|
||||
private static boolean hasPreviousState = false;
|
||||
|
||||
// PBO для быстрой очистки регионов (half float нули)
|
||||
private static int clearPBO = -1;
|
||||
private static final int MAX_CLEAR_SIZE = FogWorldVolume.SIZE_X * FogWorldVolume.SIZE_Y * FogWorldVolume.SIZE_Z;
|
||||
private static final int MAX_CLEAR_SIZE_BYTES = MAX_CLEAR_SIZE * 2; // 2 байта на half float
|
||||
|
||||
private FogDensityTextures() {
|
||||
}
|
||||
|
||||
private static void initClearPBO() {
|
||||
if (clearPBO != -1) return;
|
||||
|
||||
clearPBO = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO);
|
||||
|
||||
// Выделяем буфер в VRAM
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE_BYTES, GL15.GL_STATIC_DRAW);
|
||||
|
||||
// Заполняем нулями (half float 0.0 = 2 нулевых байта: 0x0000)
|
||||
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
|
||||
if (mapped != null) {
|
||||
// Заполняем нулями - это даст half float 0.0
|
||||
for (int i = 0; i < MAX_CLEAR_SIZE_BYTES; i++) {
|
||||
mapped.put((byte) 0);
|
||||
}
|
||||
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (textureA != -1) return;
|
||||
|
||||
// Инициализируем PBO ДО создания текстур
|
||||
initClearPBO();
|
||||
|
||||
textureA = createDensityTexture();
|
||||
textureB = createDensityTexture();
|
||||
textureC = createDensityTexture();
|
||||
currentIndex = 0;
|
||||
previousIndex = 0;
|
||||
writeIndex = 1;
|
||||
hasPreviousState = false;
|
||||
}
|
||||
|
||||
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,
|
||||
GL30.GL_HALF_FLOAT, // Исправлено: GL_HALF_FLOAT для 16-bit 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() {
|
||||
previousIndex = currentIndex;
|
||||
currentIndex = writeIndex;
|
||||
|
||||
// Выбираем свободный буфер для следующей записи
|
||||
writeIndex = 0;
|
||||
while (writeIndex == currentIndex || writeIndex == previousIndex) {
|
||||
writeIndex++;
|
||||
}
|
||||
|
||||
hasPreviousState = true;
|
||||
}
|
||||
|
||||
public static int getReadTexture() {
|
||||
return indexToTexture(currentIndex);
|
||||
}
|
||||
|
||||
public static int getPreviousTexture() {
|
||||
return indexToTexture(previousIndex);
|
||||
}
|
||||
|
||||
public static int getWriteTexture() {
|
||||
return indexToTexture(writeIndex);
|
||||
}
|
||||
|
||||
public static boolean hasPreviousState() {
|
||||
return hasPreviousState;
|
||||
}
|
||||
|
||||
private static int indexToTexture(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return textureA;
|
||||
case 1:
|
||||
return textureB;
|
||||
case 2:
|
||||
return textureC;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected fog density texture index: " + index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает несколько непрерывных физических регионов в обеих density текстурах через PBO.
|
||||
*/
|
||||
public static void clearRegions(
|
||||
int[] physX, int[] physZ,
|
||||
int[] widthChunks, int[] depthChunks,
|
||||
int count
|
||||
) {
|
||||
clearRegionsInTexture(textureA, physX, physZ, widthChunks, depthChunks, count);
|
||||
clearRegionsInTexture(textureB, physX, physZ, widthChunks, depthChunks, count);
|
||||
clearRegionsInTexture(textureC, physX, physZ, widthChunks, depthChunks, count);
|
||||
}
|
||||
|
||||
private static void clearRegionsInTexture(
|
||||
int texture,
|
||||
int[] physX, int[] physZ,
|
||||
int[] widthChunks, int[] depthChunks,
|
||||
int count
|
||||
) {
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO);
|
||||
|
||||
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);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int w = widthChunks[i] * FogWorldVolume.CHUNK_SIZE;
|
||||
int h = FogWorldVolume.SIZE_Y;
|
||||
int d = depthChunks[i] * FogWorldVolume.CHUNK_SIZE;
|
||||
|
||||
// 0L = offset 0 внутри PBO (начало буфера)
|
||||
GL12.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
physX[i] * FogWorldVolume.CHUNK_SIZE,
|
||||
0,
|
||||
physZ[i] * FogWorldVolume.CHUNK_SIZE,
|
||||
w, h, d,
|
||||
GL11.GL_RED,
|
||||
GL30.GL_HALF_FLOAT,
|
||||
0L // ← long offset вместо ByteBuffer
|
||||
);
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public final class FogEnvironmentContext {
|
||||
|
||||
private static float emissionMultiplier = 1.0f;
|
||||
private static float targetDensityMultiplier = 0.0f;
|
||||
private static float fogDayRandom = 0.5f;
|
||||
private static float rainMultiplier = 0.0f;
|
||||
private static float heightFadeMax = 90.0f; // НОВОЕ
|
||||
|
||||
private static float smoothEmission = 1.0f;
|
||||
private static float smoothTargetDensity = 0.0f;
|
||||
private static float smoothRain = 0.0f;
|
||||
|
||||
private static SimpleNoiseSampler dayNoise;
|
||||
private static Identifier lastWorldId = null;
|
||||
|
||||
private FogEnvironmentContext() {}
|
||||
|
||||
public static void init() {
|
||||
dayNoise = new SimpleNoiseSampler(12345L);
|
||||
}
|
||||
|
||||
public static void update(float tickDelta) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
World world = client.world;
|
||||
if (world == null) return;
|
||||
|
||||
Identifier worldId = world.getRegistryKey().getValue();
|
||||
if (!worldId.equals(lastWorldId)) {
|
||||
lastWorldId = worldId;
|
||||
long seed = worldId.toString().hashCode();
|
||||
dayNoise = new SimpleNoiseSampler(seed);
|
||||
}
|
||||
|
||||
long timeOfDay = world.getTimeOfDay() % 24000L;
|
||||
long totalDays = world.getTimeOfDay() / 24000L;
|
||||
float timeOfDayNorm = timeOfDay/24000.0F;
|
||||
|
||||
// === 1. Трапеция для EMISSION ===
|
||||
double noiseValue = dayNoise.sample(totalDays * 0.05, 0);
|
||||
float dayOffset = (float)(noiseValue * 1000);
|
||||
|
||||
float p1 = 16000 - dayOffset;
|
||||
float p2 = 20000;
|
||||
float p3 = 23000;
|
||||
float p4 = 3000 + dayOffset;
|
||||
|
||||
float baseEmission = trapezoid(timeOfDay, p1, p2, p3, p4);
|
||||
|
||||
float rain = world.getRainGradient(tickDelta);
|
||||
float thunder = world.getThunderGradient(tickDelta);
|
||||
float weatherBoost = 1.0f + rain * 2.0f + thunder * 1.0f;
|
||||
|
||||
float rawEmission = (baseEmission + 0.1f) * weatherBoost;
|
||||
|
||||
// === 2. Трапеция для TARGET DENSITY ===
|
||||
// РАСШИРЕННОЕ окно: 18000-6000 (полночь - полдень)
|
||||
// Это 12 часов игрового времени = 10 минут реального
|
||||
float t1 = 18000; // полночь
|
||||
float t2 = 20000; // 02:00
|
||||
float t3 = 4000; // 10:00
|
||||
float t4 = 6000; // полдень
|
||||
|
||||
float baseTargetDensity = trapezoid(timeOfDay, t1, t2, t3, t4);
|
||||
float rainFactor = rain + thunder * 0.5f;
|
||||
float rawTargetDensity = Math.max(baseTargetDensity, rainFactor);
|
||||
|
||||
// === 3. Случайное значение для "туманного дня" ===
|
||||
double dayNoiseValue = dayNoise.sample(totalDays * 0.1, 100);
|
||||
float rawFogDayRandom = (float)(0.5 + dayNoiseValue * 0.5);
|
||||
|
||||
// === 4. HeightFadeMax (110 +15/-20 от дневного шума) ===
|
||||
double heightNoise = dayNoise.sample(totalDays * 0.5, 200)+dayNoise.sample(timeOfDayNorm*2, totalDays)*0.25;
|
||||
float rawHeightFadeMax = 110.0f + (float)(heightNoise * 15.0);
|
||||
rawHeightFadeMax = Math.max(90.0f, Math.min(125.0f, rawHeightFadeMax));
|
||||
|
||||
// === 5. Сглаживание ===
|
||||
float lerpSpeed = 0.08f;
|
||||
smoothEmission = lerp(smoothEmission, rawEmission, lerpSpeed);
|
||||
smoothTargetDensity = lerp(smoothTargetDensity, rawTargetDensity, lerpSpeed * 2.0f);
|
||||
smoothRain = lerp(smoothRain, rainFactor, lerpSpeed * 2.0f);
|
||||
|
||||
fogDayRandom = rawFogDayRandom;
|
||||
heightFadeMax = rawHeightFadeMax + rain*100; // Не сглаживаем, постоянен в течение дня
|
||||
|
||||
emissionMultiplier = smoothEmission;
|
||||
targetDensityMultiplier = smoothTargetDensity;
|
||||
rainMultiplier = smoothRain;
|
||||
}
|
||||
|
||||
private static float trapezoid(float x, float p1, float p2, float p3, float p4) {
|
||||
x = ((x % 24000) + 24000) % 24000;
|
||||
|
||||
if (p4 < p1) {
|
||||
if (x < p4) x += 24000;
|
||||
if (p2 < p1) p2 += 24000;
|
||||
if (p3 < p1) p3 += 24000;
|
||||
p4 += 24000;
|
||||
}
|
||||
|
||||
if (x < p1) return 0.0f;
|
||||
if (x < p2) return (x - p1) / (p2 - p1);
|
||||
if (x < p3) return 1.0f;
|
||||
if (x < p4) return 1.0f - (x - p3) / (p4 - p3);
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private static float lerp(float a, float b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
public static float getEmissionMultiplier() { return emissionMultiplier; }
|
||||
public static float getTargetDensityMultiplier() { return targetDensityMultiplier; }
|
||||
public static float getFogDayRandom() { return fogDayRandom; }
|
||||
public static float getRainMultiplier() { return rainMultiplier; }
|
||||
public static float getHeightFadeMax() { return heightFadeMax; }
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.util.math.ChunkSectionPos;
|
||||
import net.minecraft.world.chunk.ChunkNibbleArray;
|
||||
import net.minecraft.world.chunk.light.ChunkLightProvider;
|
||||
import net.minecraft.world.chunk.light.LightingProvider;
|
||||
|
||||
public final class FogLightProvider {
|
||||
|
||||
public static final int SECTION_DATA_SIZE = 16 * 16 * 16;
|
||||
|
||||
// Pre-allocated массивы для каждого индекса. Инициализируются лениво.
|
||||
private static byte[][] blockLightData;
|
||||
private static byte[][] skyLightData;
|
||||
|
||||
private FogLightProvider() {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (blockLightData != null) return;
|
||||
int total = FogLightStreamer.TOTAL_SECTIONS;
|
||||
blockLightData = new byte[total][SECTION_DATA_SIZE];
|
||||
skyLightData = new byte[total][SECTION_DATA_SIZE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает block light напрямую в pre-allocated массив.
|
||||
* Возвращает true если данные успешно прочитаны.
|
||||
*/
|
||||
public static boolean fillBlockLightIntoArray(int sectionX, int sectionY, int sectionZ, int index) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.world == null) return false;
|
||||
|
||||
LightingProvider provider = client.world.getLightingProvider();
|
||||
ChunkLightProvider<?, ?> blockProvider = provider.blockLightProvider;
|
||||
if (blockProvider == null) return false;
|
||||
|
||||
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
|
||||
ChunkNibbleArray blockArray = blockProvider.getLightSection(sectionPos);
|
||||
if (blockArray == null) return false;
|
||||
|
||||
byte[] result = blockLightData[index];
|
||||
int idx = 0;
|
||||
|
||||
for (int localZ = 0; localZ < 16; localZ++) {
|
||||
for (int localY = 0; localY < 16; localY++) {
|
||||
for (int localX = 0; localX < 16; localX++) {
|
||||
result[idx++] = (byte) (blockArray.get(localX, localY, localZ) * 17);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Читает sky light напрямую в pre-allocated массив.
|
||||
*/
|
||||
public static boolean fillSkyLightIntoArray(int sectionX, int sectionY, int sectionZ, int index) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.world == null) return false;
|
||||
|
||||
LightingProvider provider = client.world.getLightingProvider();
|
||||
ChunkLightProvider<?, ?> skyProvider = provider.skyLightProvider;
|
||||
if (skyProvider == null) return false;
|
||||
|
||||
ChunkSectionPos sectionPos = ChunkSectionPos.from(sectionX, sectionY, sectionZ);
|
||||
ChunkNibbleArray skyArray = skyProvider.getLightSection(sectionPos);
|
||||
if (skyArray == null) return false;
|
||||
|
||||
byte[] result = skyLightData[index];
|
||||
int idx = 0;
|
||||
|
||||
for (int localZ = 0; localZ < 16; localZ++) {
|
||||
for (int localY = 0; localY < 16; localY++) {
|
||||
for (int localX = 0; localX < 16; localX++) {
|
||||
result[idx++] = (byte) (skyArray.get(localX, localY, localZ) * 17);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static byte[] getBlockLightData(int index) {
|
||||
return blockLightData[index];
|
||||
}
|
||||
|
||||
public static byte[] getSkyLightData(int index) {
|
||||
return skyLightData[index];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLongArray;
|
||||
|
||||
public final class FogLightStreamer {
|
||||
|
||||
public static final int SECTION_SIZE = 16;
|
||||
public static final int SECTIONS_PER_CHUNK_Y = FogLightVolume.SIZE_Y / SECTION_SIZE;
|
||||
|
||||
static final int TOTAL_SECTIONS =
|
||||
FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z * SECTIONS_PER_CHUNK_Y;
|
||||
|
||||
private static final int TOTAL_WORDS = (TOTAL_SECTIONS + 63) / 64;
|
||||
|
||||
private static final AtomicLongArray pendingBlockMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final AtomicLongArray pendingSkyMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final AtomicLongArray readyBlockMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final AtomicLongArray readySkyMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final AtomicLongArray initialBlockMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final AtomicLongArray initialSkyMask = new AtomicLongArray(TOTAL_WORDS);
|
||||
private static final Object sharedStateLock = new Object();
|
||||
|
||||
// Flat массивы для мировых координат
|
||||
private static final int[] preparedBlockWorldX = new int[TOTAL_SECTIONS];
|
||||
private static final int[] preparedBlockWorldY = new int[TOTAL_SECTIONS];
|
||||
private static final int[] preparedBlockWorldZ = new int[TOTAL_SECTIONS];
|
||||
|
||||
private static final int[] preparedSkyWorldX = new int[TOTAL_SECTIONS];
|
||||
private static final int[] preparedSkyWorldY = new int[TOTAL_SECTIONS];
|
||||
private static final int[] preparedSkyWorldZ = new int[TOTAL_SECTIONS];
|
||||
|
||||
// Более равномерное распределение: меньше initial за раз, больше incremental
|
||||
private static final int MAX_INITIAL_SECTIONS_PER_TICK = 16;
|
||||
private static final int MAX_PENDING_SECTIONS_PER_TICK = 16;
|
||||
private static final int MAX_READY_BLOCK_SECTIONS_PER_TICK = 8;
|
||||
private static final int MAX_READY_SKY_SECTIONS_PER_TICK = 8;
|
||||
private static volatile int burstFrames = 0;
|
||||
|
||||
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 static final int MAX_REGIONS = 16;
|
||||
private static final int[] regionPhysX = new int[MAX_REGIONS];
|
||||
private static final int[] regionPhysZ = new int[MAX_REGIONS];
|
||||
private static final int[] regionWidth = new int[MAX_REGIONS];
|
||||
private static final int[] regionDepth = new int[MAX_REGIONS];
|
||||
private static int regionCount = 0;
|
||||
|
||||
// Pre-allocated temp массивы
|
||||
private static final int[] tempXStarts = new int[2];
|
||||
private static final int[] tempXCounts = new int[2];
|
||||
private static final int[] tempZStarts = new int[2];
|
||||
private static final int[] tempZCounts = new int[2];
|
||||
|
||||
private FogLightStreamer() {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
FogLightProvider.init();
|
||||
}
|
||||
|
||||
// ======================== Утилиты ========================
|
||||
|
||||
private static int floorMod(int x, int y) {
|
||||
int r = x % y;
|
||||
return r < 0 ? r + y : r;
|
||||
}
|
||||
|
||||
private static int sectionToLocalIndex(int sectionX, int sectionY, int sectionZ) {
|
||||
int relativeX = sectionX - currentOriginChunkX;
|
||||
int relativeZ = sectionZ - currentOriginChunkZ;
|
||||
|
||||
if (relativeX < 0 || relativeX >= FogLightVolume.CHUNKS_X ||
|
||||
relativeZ < 0 || relativeZ >= FogLightVolume.CHUNKS_Z) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int localSectionY = sectionY - (FogWorldVolume.MIN_Y >> 4);
|
||||
if (localSectionY < 0 || localSectionY >= SECTIONS_PER_CHUNK_Y) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int physicalX = floorMod(relativeX + currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||
int physicalZ = floorMod(relativeZ + currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||
|
||||
return (localSectionY * FogLightVolume.CHUNKS_Z * FogLightVolume.CHUNKS_X)
|
||||
+ (physicalZ * FogLightVolume.CHUNKS_X)
|
||||
+ physicalX;
|
||||
}
|
||||
|
||||
private static void setBit(AtomicLongArray mask, int index) {
|
||||
int wordIndex = index >> 6;
|
||||
long bitMask = 1L << (index & 63);
|
||||
while (true) {
|
||||
long current = mask.get(wordIndex);
|
||||
if ((current & bitMask) != 0) return;
|
||||
if (mask.compareAndSet(wordIndex, current, current | bitMask)) return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearBit(AtomicLongArray mask, int index) {
|
||||
int wordIndex = index >> 6;
|
||||
long bitMask = 1L << (index & 63);
|
||||
while (true) {
|
||||
long current = mask.get(wordIndex);
|
||||
if ((current & bitMask) == 0) return;
|
||||
if (mask.compareAndSet(wordIndex, current, current & ~bitMask)) return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Простой последовательный скан — работает быстрее на практике
|
||||
* благодаря branch prediction и JIT-оптимизациям.
|
||||
*/
|
||||
private static int nextSetBit(AtomicLongArray mask, int startIndex) {
|
||||
for (int i = startIndex; i < TOTAL_SECTIONS; i++) {
|
||||
int wordIndex = i >> 6;
|
||||
long bitMask = 1L << (i & 63);
|
||||
if ((mask.get(wordIndex) & bitMask) != 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ======================== События обновления ========================
|
||||
|
||||
public static void onBlockLightUpdated(long sectionPos) {
|
||||
int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos);
|
||||
int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos);
|
||||
int sectionZ = net.minecraft.util.math.ChunkSectionPos.unpackZ(sectionPos);
|
||||
|
||||
int index = sectionToLocalIndex(sectionX, sectionY, sectionZ);
|
||||
if (index >= 0) {
|
||||
clearBit(readyBlockMask, index);
|
||||
setBit(pendingBlockMask, index);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onSkyLightUpdated(long sectionPos) {
|
||||
int sectionX = net.minecraft.util.math.ChunkSectionPos.unpackX(sectionPos);
|
||||
int sectionY = net.minecraft.util.math.ChunkSectionPos.unpackY(sectionPos);
|
||||
int sectionZ = net.minecraft.util.math.ChunkSectionPos.unpackZ(sectionPos);
|
||||
|
||||
int index = sectionToLocalIndex(sectionX, sectionY, sectionZ);
|
||||
if (index >= 0) {
|
||||
clearBit(readySkyMask, index);
|
||||
setBit(pendingSkyMask, index);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== Окно ========================
|
||||
|
||||
public static void update(
|
||||
int oldOriginChunkX, int oldOriginChunkZ,
|
||||
int newOriginChunkX, int newOriginChunkZ,
|
||||
int ringChunkOffsetX, int ringChunkOffsetZ
|
||||
) {
|
||||
synchronized (sharedStateLock) {
|
||||
currentOriginChunkX = newOriginChunkX;
|
||||
currentOriginChunkZ = newOriginChunkZ;
|
||||
currentRingChunkOffsetX = ringChunkOffsetX;
|
||||
currentRingChunkOffsetZ = ringChunkOffsetZ;
|
||||
|
||||
if (firstUpdate) {
|
||||
markAllPending();
|
||||
firstUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int dx = newOriginChunkX - oldOriginChunkX;
|
||||
int dz = newOriginChunkZ - oldOriginChunkZ;
|
||||
|
||||
if (dx == 0 && dz == 0) return;
|
||||
|
||||
if (Math.abs(dx) >= FogLightVolume.CHUNKS_X || Math.abs(dz) >= FogLightVolume.CHUNKS_Z) {
|
||||
markAllPending();
|
||||
return;
|
||||
}
|
||||
|
||||
regionCount = 0;
|
||||
|
||||
if (dx > 0) {
|
||||
int worldX = newOriginChunkX + FogLightVolume.CHUNKS_X - dx;
|
||||
collectRegions(worldX, newOriginChunkZ, dx, FogLightVolume.CHUNKS_Z);
|
||||
addInitialLoadForRange(worldX, newOriginChunkZ, dx, FogLightVolume.CHUNKS_Z);
|
||||
}
|
||||
if (dx < 0) {
|
||||
collectRegions(newOriginChunkX, newOriginChunkZ, -dx, FogLightVolume.CHUNKS_Z);
|
||||
addInitialLoadForRange(newOriginChunkX, newOriginChunkZ, -dx, FogLightVolume.CHUNKS_Z);
|
||||
}
|
||||
if (dz > 0) {
|
||||
int worldZ = newOriginChunkZ + FogLightVolume.CHUNKS_Z - dz;
|
||||
collectRegions(newOriginChunkX, worldZ, FogLightVolume.CHUNKS_X, dz);
|
||||
addInitialLoadForRange(newOriginChunkX, worldZ, FogLightVolume.CHUNKS_X, dz);
|
||||
}
|
||||
if (dz < 0) {
|
||||
collectRegions(newOriginChunkX, newOriginChunkZ, FogLightVolume.CHUNKS_X, -dz);
|
||||
addInitialLoadForRange(newOriginChunkX, newOriginChunkZ, FogLightVolume.CHUNKS_X, -dz);
|
||||
}
|
||||
|
||||
if (regionCount > 0) {
|
||||
FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addInitialLoadForRange(int worldX, int worldZ, int countX, int countZ) {
|
||||
int minY = FogWorldVolume.MIN_Y >> 4;
|
||||
for (int i = 0; i < countX; i++) {
|
||||
int wx = worldX + i;
|
||||
for (int j = 0; j < countZ; j++) {
|
||||
int wz = worldZ + j;
|
||||
for (int sectionY = 0; sectionY < SECTIONS_PER_CHUNK_Y; sectionY++) {
|
||||
int index = sectionToLocalIndex(wx, sectionY + minY, wz);
|
||||
if (index >= 0) {
|
||||
setBit(initialBlockMask, index);
|
||||
setBit(initialSkyMask, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void markAllPending() {
|
||||
regionCount = 1;
|
||||
regionPhysX[0] = 0;
|
||||
regionPhysZ[0] = 0;
|
||||
regionWidth[0] = FogLightVolume.CHUNKS_X;
|
||||
regionDepth[0] = FogLightVolume.CHUNKS_Z;
|
||||
|
||||
FogLightVolume.clearRegions(regionPhysX, regionPhysZ, regionWidth, regionDepth, regionCount);
|
||||
|
||||
for (int i = 0; i < TOTAL_WORDS; i++) {
|
||||
pendingBlockMask.set(i, 0L);
|
||||
pendingSkyMask.set(i, 0L);
|
||||
readyBlockMask.set(i, 0L);
|
||||
readySkyMask.set(i, 0L);
|
||||
initialBlockMask.set(i, -1L);
|
||||
initialSkyMask.set(i, -1L);
|
||||
}
|
||||
|
||||
int extraBits = TOTAL_SECTIONS & 63;
|
||||
if (extraBits != 0) {
|
||||
long mask = (1L << extraBits) - 1;
|
||||
initialBlockMask.set(TOTAL_WORDS - 1, mask);
|
||||
initialSkyMask.set(TOTAL_WORDS - 1, mask);
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectRegions(int worldX, int worldZ, int countX, int countZ) {
|
||||
int xSegCount = splitIntoSegments(worldX, countX, FogLightVolume.CHUNKS_X, tempXStarts, tempXCounts);
|
||||
int zSegCount = splitIntoSegments(worldZ, countZ, FogLightVolume.CHUNKS_Z, tempZStarts, tempZCounts);
|
||||
|
||||
for (int i = 0; i < xSegCount; i++) {
|
||||
for (int j = 0; j < zSegCount; j++) {
|
||||
if (regionCount >= MAX_REGIONS) return;
|
||||
regionPhysX[regionCount] = tempXStarts[i];
|
||||
regionPhysZ[regionCount] = tempZStarts[j];
|
||||
regionWidth[regionCount] = tempXCounts[i];
|
||||
regionDepth[regionCount] = tempZCounts[j];
|
||||
regionCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int splitIntoSegments(int start, int count, int dim, int[] outStarts, int[] outCounts) {
|
||||
if (count == 0) return 0;
|
||||
int first = floorMod(start, dim);
|
||||
int last = floorMod(start + count - 1, dim);
|
||||
if (first <= last) {
|
||||
outStarts[0] = first;
|
||||
outCounts[0] = count;
|
||||
return 1;
|
||||
} else {
|
||||
int firstCount = dim - first;
|
||||
outStarts[0] = first;
|
||||
outCounts[0] = firstCount;
|
||||
outStarts[1] = 0;
|
||||
outCounts[1] = count - firstCount;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== Подготовка и загрузка ========================
|
||||
|
||||
/**
|
||||
* Более равномерное распределение нагрузки:
|
||||
* обрабатываем и initial, и pending параллельно, чтобы избежать пиков.
|
||||
*/
|
||||
public static void prepareAllSections() {
|
||||
synchronized (sharedStateLock) {
|
||||
int processedBlock = 0;
|
||||
int processedSky = 0;
|
||||
|
||||
// Чередование: немного initial block, немного pending block,
|
||||
// немного initial sky, немного pending sky
|
||||
int index = 0;
|
||||
int initialLimit = MAX_INITIAL_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_INITIAL_SECTIONS_PER_TICK : 0);
|
||||
while (processedBlock < initialLimit) {
|
||||
index = nextSetBit(initialBlockMask, index);
|
||||
if (index < 0) break;
|
||||
if (prepareBlockLight(index)) {
|
||||
clearBit(initialBlockMask, index);
|
||||
processedBlock++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
int pendingLimit = MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_PENDING_SECTIONS_PER_TICK : 0);
|
||||
while (processedBlock < pendingLimit) {
|
||||
index = nextSetBit(pendingBlockMask, index);
|
||||
if (index < 0) break;
|
||||
if (prepareBlockLight(index)) {
|
||||
clearBit(pendingBlockMask, index);
|
||||
processedBlock++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
int initialSkyLimit = MAX_INITIAL_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_INITIAL_SECTIONS_PER_TICK : 0);
|
||||
while (processedSky < initialSkyLimit) {
|
||||
index = nextSetBit(initialSkyMask, index);
|
||||
if (index < 0) break;
|
||||
if (prepareSkyLight(index)) {
|
||||
clearBit(initialSkyMask, index);
|
||||
processedSky++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
int pendingSkyLimit = MAX_INITIAL_SECTIONS_PER_TICK + MAX_PENDING_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_PENDING_SECTIONS_PER_TICK : 0);
|
||||
while (processedSky < pendingSkyLimit) {
|
||||
index = nextSetBit(pendingSkyMask, index);
|
||||
if (index < 0) break;
|
||||
if (prepareSkyLight(index)) {
|
||||
clearBit(pendingSkyMask, index);
|
||||
processedSky++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (burstFrames > 0) burstFrames--;
|
||||
}
|
||||
}
|
||||
|
||||
public static void uploadReadySections() {
|
||||
synchronized (sharedStateLock) {
|
||||
int processedBlock = 0;
|
||||
int processedSky = 0;
|
||||
int index = 0;
|
||||
FogLightVolume.beginUpload(FogLightVolume.getBlockLightTexture());
|
||||
int blockLimit = MAX_READY_BLOCK_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_READY_BLOCK_SECTIONS_PER_TICK : 0);
|
||||
while (processedBlock < blockLimit) {
|
||||
index = nextSetBit(readyBlockMask, index);
|
||||
if (index < 0) break;
|
||||
if (uploadBlockLight(index)) {
|
||||
processedBlock++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
FogLightVolume.endUpload();
|
||||
|
||||
index = 0;
|
||||
FogLightVolume.beginUpload(FogLightVolume.getSkyLightTexture());
|
||||
int skyLimit = MAX_READY_SKY_SECTIONS_PER_TICK + (burstFrames > 0 ? MAX_READY_SKY_SECTIONS_PER_TICK : 0);
|
||||
while (processedSky < skyLimit) {
|
||||
index = nextSetBit(readySkyMask, index);
|
||||
if (index < 0) break;
|
||||
if (uploadSkyLight(index)) {
|
||||
processedSky++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
FogLightVolume.endUpload();
|
||||
if (burstFrames > 0) burstFrames--;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setBurst(int frames) {
|
||||
burstFrames = Math.max(burstFrames, frames);
|
||||
}
|
||||
|
||||
private static boolean prepareBlockLight(int index) {
|
||||
int physicalX = index % FogLightVolume.CHUNKS_X;
|
||||
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
||||
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
||||
|
||||
int sectionX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||
int sectionZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
||||
|
||||
if (!FogLightProvider.fillBlockLightIntoArray(sectionX, sectionY, sectionZ, index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
preparedBlockWorldX[index] = sectionX;
|
||||
preparedBlockWorldY[index] = sectionY;
|
||||
preparedBlockWorldZ[index] = sectionZ;
|
||||
|
||||
setBit(readyBlockMask, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean prepareSkyLight(int index) {
|
||||
int physicalX = index % FogLightVolume.CHUNKS_X;
|
||||
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
||||
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
||||
|
||||
int sectionX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||
int sectionZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||
int sectionY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
||||
|
||||
if (!FogLightProvider.fillSkyLightIntoArray(sectionX, sectionY, sectionZ, index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
preparedSkyWorldX[index] = sectionX;
|
||||
preparedSkyWorldY[index] = sectionY;
|
||||
preparedSkyWorldZ[index] = sectionZ;
|
||||
|
||||
setBit(readySkyMask, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean uploadBlockLight(int index) {
|
||||
int physicalX = index % FogLightVolume.CHUNKS_X;
|
||||
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
||||
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
||||
|
||||
int currentWorldX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||
int currentWorldZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||
int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
||||
|
||||
if (preparedBlockWorldX[index] != currentWorldX ||
|
||||
preparedBlockWorldY[index] != currentWorldY ||
|
||||
preparedBlockWorldZ[index] != currentWorldZ) {
|
||||
clearBit(readyBlockMask, index);
|
||||
setBit(pendingBlockMask, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
FogLightVolume.uploadBlockLightSectionNoBind(physicalX, localSectionY, physicalZ,
|
||||
FogLightProvider.getBlockLightData(index));
|
||||
clearBit(readyBlockMask, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean uploadSkyLight(int index) {
|
||||
int physicalX = index % FogLightVolume.CHUNKS_X;
|
||||
int physicalZ = (index / FogLightVolume.CHUNKS_X) % FogLightVolume.CHUNKS_Z;
|
||||
int localSectionY = index / (FogLightVolume.CHUNKS_X * FogLightVolume.CHUNKS_Z);
|
||||
|
||||
int currentWorldX = currentOriginChunkX + floorMod(physicalX - currentRingChunkOffsetX, FogLightVolume.CHUNKS_X);
|
||||
int currentWorldZ = currentOriginChunkZ + floorMod(physicalZ - currentRingChunkOffsetZ, FogLightVolume.CHUNKS_Z);
|
||||
int currentWorldY = localSectionY + (FogWorldVolume.MIN_Y >> 4);
|
||||
|
||||
if (preparedSkyWorldX[index] != currentWorldX ||
|
||||
preparedSkyWorldY[index] != currentWorldY ||
|
||||
preparedSkyWorldZ[index] != currentWorldZ) {
|
||||
clearBit(readySkyMask, index);
|
||||
setBit(pendingSkyMask, index);
|
||||
return false;
|
||||
}
|
||||
|
||||
FogLightVolume.uploadSkyLightSectionNoBind(physicalX, localSectionY, physicalZ,
|
||||
FogLightProvider.getSkyLightData(index));
|
||||
clearBit(readySkyMask, index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
// No GL43 usage: keep compatibility with OpenGL 3.2
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class FogLightVolume {
|
||||
|
||||
public static final int SIZE_X = FogWorldVolume.SIZE_X;
|
||||
public static final int SIZE_Z = FogWorldVolume.SIZE_Z;
|
||||
public static final int SIZE_Y = FogWorldVolume.SIZE_Y;
|
||||
public static final int CHUNK_SIZE = FogWorldVolume.CHUNK_SIZE;
|
||||
public static final int CHUNKS_X = FogWorldVolume.CHUNKS_X;
|
||||
public static final int CHUNKS_Z = FogWorldVolume.CHUNKS_Z;
|
||||
|
||||
// Две отдельные одноканальные текстуры
|
||||
private static int blockLightTexture = -1;
|
||||
private static int skyLightTexture = -1;
|
||||
|
||||
private static final byte DEFAULT_BLOCK_LIGHT = (byte) 0;
|
||||
private static final byte DEFAULT_SKY_LIGHT = (byte) 255;
|
||||
|
||||
private static final byte[] DEFAULT_BLOCK_ARRAY;
|
||||
private static final byte[] DEFAULT_SKY_ARRAY;
|
||||
static {
|
||||
int sectionVoxels = CHUNK_SIZE * SIZE_Y * CHUNK_SIZE;
|
||||
DEFAULT_BLOCK_ARRAY = new byte[sectionVoxels];
|
||||
DEFAULT_SKY_ARRAY = new byte[sectionVoxels];
|
||||
java.util.Arrays.fill(DEFAULT_BLOCK_ARRAY, DEFAULT_BLOCK_LIGHT);
|
||||
java.util.Arrays.fill(DEFAULT_SKY_ARRAY, DEFAULT_SKY_LIGHT);
|
||||
}
|
||||
|
||||
private static final ByteBuffer CHUNK_BUFFER =
|
||||
BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE);
|
||||
|
||||
private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
|
||||
|
||||
// PBO для быстрой очистки
|
||||
private static int clearBlockPBO = -1;
|
||||
private static int clearSkyPBO = -1;
|
||||
// PBO для загрузки секций
|
||||
private static int uploadPBO = -1;
|
||||
|
||||
// Буферы для загрузки секций
|
||||
private static final ByteBuffer BLOCK_SECTION_UPLOAD_BUFFER =
|
||||
BufferUtils.createByteBuffer(FogLightStreamer.SECTION_SIZE
|
||||
* FogLightStreamer.SECTION_SIZE
|
||||
* FogLightStreamer.SECTION_SIZE);
|
||||
|
||||
private static final ByteBuffer SKY_SECTION_UPLOAD_BUFFER =
|
||||
BufferUtils.createByteBuffer(FogLightStreamer.SECTION_SIZE
|
||||
* FogLightStreamer.SECTION_SIZE
|
||||
* FogLightStreamer.SECTION_SIZE);
|
||||
|
||||
private FogLightVolume() {
|
||||
}
|
||||
|
||||
private static void initClearPBOs() {
|
||||
if (clearBlockPBO != -1) return;
|
||||
|
||||
// Block light PBO
|
||||
clearBlockPBO = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearBlockPBO);
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE, GL15.GL_STATIC_DRAW);
|
||||
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
|
||||
if (mapped != null) {
|
||||
for (int i = 0; i < SIZE_X * SIZE_Y * SIZE_Z; i++) {
|
||||
mapped.put(DEFAULT_BLOCK_LIGHT);
|
||||
}
|
||||
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
|
||||
// Sky light PBO
|
||||
clearSkyPBO = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearSkyPBO);
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE, GL15.GL_STATIC_DRAW);
|
||||
mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
|
||||
if (mapped != null) {
|
||||
for (int i = 0; i < SIZE_X * SIZE_Y * SIZE_Z; i++) {
|
||||
mapped.put(DEFAULT_SKY_LIGHT);
|
||||
}
|
||||
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (blockLightTexture != -1) return;
|
||||
|
||||
initClearPBOs();
|
||||
initUploadPBO();
|
||||
|
||||
// Block light texture
|
||||
blockLightTexture = GL11.glGenTextures();
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture);
|
||||
GL30.glTexImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0, GL30.GL_R8,
|
||||
SIZE_X, SIZE_Y, SIZE_Z, 0,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (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);
|
||||
|
||||
// Sky light texture
|
||||
skyLightTexture = GL11.glGenTextures();
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture);
|
||||
GL30.glTexImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0, GL30.GL_R8,
|
||||
SIZE_X, SIZE_Y, SIZE_Z, 0,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (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);
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
// Clear block light texture
|
||||
CHUNK_BUFFER.clear();
|
||||
CHUNK_BUFFER.put(DEFAULT_BLOCK_ARRAY);
|
||||
CHUNK_BUFFER.flip();
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture);
|
||||
for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) {
|
||||
for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) {
|
||||
uploadChunkInternal(blockLightTexture, chunkX, chunkZ, CHUNK_BUFFER);
|
||||
CHUNK_BUFFER.rewind();
|
||||
}
|
||||
}
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
|
||||
// Clear sky light texture
|
||||
CHUNK_BUFFER.clear();
|
||||
CHUNK_BUFFER.put(DEFAULT_SKY_ARRAY);
|
||||
CHUNK_BUFFER.flip();
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture);
|
||||
for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) {
|
||||
for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) {
|
||||
uploadChunkInternal(skyLightTexture, chunkX, chunkZ, CHUNK_BUFFER);
|
||||
CHUNK_BUFFER.rewind();
|
||||
}
|
||||
}
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
private static void uploadChunkInternal(int texture, int textureChunkX, int textureChunkZ, ByteBuffer data) {
|
||||
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, 0, textureChunkZ * CHUNK_SIZE,
|
||||
CHUNK_SIZE, SIZE_Y, CHUNK_SIZE,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, data
|
||||
);
|
||||
}
|
||||
|
||||
public static void uploadBlockLightSection(
|
||||
int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data
|
||||
) {
|
||||
BLOCK_SECTION_UPLOAD_BUFFER.clear();
|
||||
BLOCK_SECTION_UPLOAD_BUFFER.put(data);
|
||||
BLOCK_SECTION_UPLOAD_BUFFER.flip();
|
||||
|
||||
beginUpload(blockLightTexture);
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0,
|
||||
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
|
||||
16, 16, 16,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, BLOCK_SECTION_UPLOAD_BUFFER
|
||||
);
|
||||
endUpload();
|
||||
}
|
||||
|
||||
public static void uploadSkyLightSection(
|
||||
int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data
|
||||
) {
|
||||
SKY_SECTION_UPLOAD_BUFFER.clear();
|
||||
SKY_SECTION_UPLOAD_BUFFER.put(data);
|
||||
SKY_SECTION_UPLOAD_BUFFER.flip();
|
||||
|
||||
beginUpload(skyLightTexture);
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0,
|
||||
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
|
||||
16, 16, 16,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SKY_SECTION_UPLOAD_BUFFER
|
||||
);
|
||||
endUpload();
|
||||
}
|
||||
|
||||
public static void uploadBlockLightSectionNoBind(
|
||||
int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data
|
||||
) {
|
||||
// Try to upload via PBO to avoid CPU->GPU stalls
|
||||
if (uploadPBO != -1) {
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO);
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, BLOCK_SECTION_UPLOAD_BUFFER.capacity(), GL15.GL_STREAM_DRAW);
|
||||
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
|
||||
if (mapped != null) {
|
||||
mapped.put(data);
|
||||
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0,
|
||||
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
|
||||
16, 16, 16,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L
|
||||
);
|
||||
}
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
} else {
|
||||
BLOCK_SECTION_UPLOAD_BUFFER.clear();
|
||||
BLOCK_SECTION_UPLOAD_BUFFER.put(data);
|
||||
BLOCK_SECTION_UPLOAD_BUFFER.flip();
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0,
|
||||
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
|
||||
16, 16, 16,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, BLOCK_SECTION_UPLOAD_BUFFER
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void uploadSkyLightSectionNoBind(
|
||||
int textureSectionX, int textureSectionY, int textureSectionZ, byte[] data
|
||||
) {
|
||||
if (uploadPBO != -1) {
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO);
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, SKY_SECTION_UPLOAD_BUFFER.capacity(), GL15.GL_STREAM_DRAW);
|
||||
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
|
||||
if (mapped != null) {
|
||||
mapped.put(data);
|
||||
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0,
|
||||
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
|
||||
16, 16, 16,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L
|
||||
);
|
||||
}
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
} else {
|
||||
SKY_SECTION_UPLOAD_BUFFER.clear();
|
||||
SKY_SECTION_UPLOAD_BUFFER.put(data);
|
||||
SKY_SECTION_UPLOAD_BUFFER.flip();
|
||||
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0,
|
||||
textureSectionX * CHUNK_SIZE, textureSectionY * 16, textureSectionZ * CHUNK_SIZE,
|
||||
16, 16, 16,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SKY_SECTION_UPLOAD_BUFFER
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void beginUpload(int texture) {
|
||||
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);
|
||||
}
|
||||
|
||||
public static void endUpload() {
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
public static int getBlockLightTexture() {
|
||||
return blockLightTexture;
|
||||
}
|
||||
|
||||
public static int getSkyLightTexture() {
|
||||
return skyLightTexture;
|
||||
}
|
||||
|
||||
public static void clearRegions(
|
||||
int[] physX, int[] physZ,
|
||||
int[] widthChunks, int[] depthChunks,
|
||||
int count
|
||||
) {
|
||||
// Use PBO-based clears for OpenGL 3.2 compatibility
|
||||
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);
|
||||
|
||||
// Clear block light
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, blockLightTexture);
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearBlockPBO);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int w = widthChunks[i] * CHUNK_SIZE;
|
||||
int h = SIZE_Y;
|
||||
int d = depthChunks[i] * CHUNK_SIZE;
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0,
|
||||
physX[i] * CHUNK_SIZE, 0, physZ[i] * CHUNK_SIZE,
|
||||
w, h, d,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L
|
||||
);
|
||||
}
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
|
||||
// Clear sky light
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, skyLightTexture);
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearSkyPBO);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int w = widthChunks[i] * CHUNK_SIZE;
|
||||
int h = SIZE_Y;
|
||||
int d = depthChunks[i] * CHUNK_SIZE;
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D, 0,
|
||||
physX[i] * CHUNK_SIZE, 0, physZ[i] * CHUNK_SIZE,
|
||||
w, h, d,
|
||||
GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 0L
|
||||
);
|
||||
}
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
private static void initUploadPBO() {
|
||||
if (uploadPBO != -1) return;
|
||||
uploadPBO = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBO);
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, BLOCK_SECTION_UPLOAD_BUFFER.capacity(), GL15.GL_STREAM_DRAW);
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class FogSimulationManager {
|
||||
|
||||
private static boolean initialized = false;
|
||||
private static long lastSimulationTime = 0;
|
||||
private static long lastSwapTimeNs = 0;
|
||||
|
||||
// Симулируем каждые 300 мс (~3.3 раз в секунду)
|
||||
private static final long SIMULATION_INTERVAL_NS = 300_000_000L;
|
||||
private static final long TARGET_STEP_DURATION_NS = 300_000_000L;
|
||||
private static final long FRAME_TIME_WINDOW_NS = 100_000_000L;
|
||||
private static final long STAT_WINDOW_NS = 5_000_000_000L;
|
||||
private static final int MAX_LAYERS_PER_FRAME = 32;
|
||||
private static final int MIN_LAYERS_PER_FRAME = 2;
|
||||
private static final double LAYER_BUDGET_FACTOR = 0.75;
|
||||
private static final ArrayDeque<Long> recentFrameDurations = new ArrayDeque<>();
|
||||
private static final ArrayDeque<Long> recentFrameTimestamps = new ArrayDeque<>();
|
||||
private static final ArrayDeque<Long> baselineFrameDurations = new ArrayDeque<>();
|
||||
private static final ArrayDeque<Long> baselineFrameTimestamps = new ArrayDeque<>();
|
||||
private static final ArrayDeque<Long> completedStepDurations = new ArrayDeque<>();
|
||||
private static final ArrayDeque<Long> completedStepTimestamps = new ArrayDeque<>();
|
||||
private static long lastFrameTimestamp = 0;
|
||||
private static long lastStatsPrintTime = 0;
|
||||
private static boolean simulationActive = false;
|
||||
private static int currentLayer = 0;
|
||||
private static long simulationStartedAtNs = 0;
|
||||
private static long averageLayerCostNs = 1_000_000L;
|
||||
|
||||
private static float globalEmissionMultiplier = 0.6f;
|
||||
private static float globalAbsorptionMultiplier = 1.0f;
|
||||
private static float globalDecay = 0.025f;
|
||||
private static float diffusionRate = 0.15f;
|
||||
private static float upDiffusion = 1.5f;
|
||||
private static float downDiffusion = 1.0f;
|
||||
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 (lastFrameTimestamp != 0) {
|
||||
long frameDuration = currentTime - lastFrameTimestamp;
|
||||
recordFrameDuration(frameDuration, currentTime);
|
||||
if (!simulationActive) {
|
||||
recordBaselineFrameDuration(frameDuration, currentTime);
|
||||
}
|
||||
}
|
||||
lastFrameTimestamp = currentTime;
|
||||
|
||||
if (!simulationActive && currentTime - lastSimulationTime >= SIMULATION_INTERVAL_NS) {
|
||||
beginSimulation();
|
||||
lastSimulationTime = currentTime;
|
||||
}
|
||||
|
||||
if (simulationActive) {
|
||||
runSimulationStep();
|
||||
}
|
||||
}
|
||||
|
||||
private static void beginSimulation() {
|
||||
simulationActive = true;
|
||||
currentLayer = 0;
|
||||
simulationStartedAtNs = System.nanoTime();
|
||||
averageLayerCostNs = 1_000_000L;
|
||||
}
|
||||
|
||||
private static void runSimulationStep() {
|
||||
int remainingLayers = FogWorldVolume.SIZE_Z - currentLayer;
|
||||
if (remainingLayers <= 0) {
|
||||
FogDensityTextures.swap();
|
||||
simulationActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int layersThisFrame = computeLayersPerFrame(remainingLayers);
|
||||
int endLayer = Math.min(FogWorldVolume.SIZE_Z, currentLayer + layersThisFrame);
|
||||
|
||||
long workStart = System.nanoTime();
|
||||
FogSimulationShader.simulateRange(
|
||||
currentLayer,
|
||||
endLayer,
|
||||
globalEmissionMultiplier,
|
||||
globalAbsorptionMultiplier,
|
||||
globalDecay,
|
||||
diffusionRate,
|
||||
upDiffusion,
|
||||
downDiffusion,
|
||||
horizontalDiffusion
|
||||
);
|
||||
long workDuration = System.nanoTime() - workStart;
|
||||
|
||||
if (layersThisFrame > 0) {
|
||||
long observedCost = Math.max(500_000L, workDuration / layersThisFrame);
|
||||
averageLayerCostNs = Math.max(500_000L, (long) (averageLayerCostNs * 0.8 + observedCost * 0.2));
|
||||
}
|
||||
|
||||
currentLayer = endLayer;
|
||||
|
||||
if (currentLayer >= FogWorldVolume.SIZE_Z) {
|
||||
long stepDuration = System.nanoTime() - simulationStartedAtNs;
|
||||
recordCompletedStep(stepDuration);
|
||||
FogDensityTextures.swap();
|
||||
lastSwapTimeNs = System.nanoTime();
|
||||
simulationActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int computeLayersPerFrame(int remainingLayers) {
|
||||
long baselineFrameTime = getBaselineFrameTimeNs();
|
||||
if (baselineFrameTime <= 0) {
|
||||
baselineFrameTime = 16_000_000L;
|
||||
}
|
||||
|
||||
long elapsedSimulationTime = System.nanoTime() - simulationStartedAtNs;
|
||||
long remainingBudgetNs = TARGET_STEP_DURATION_NS - elapsedSimulationTime;
|
||||
if (remainingBudgetNs <= 0) {
|
||||
return Math.min(remainingLayers, MAX_LAYERS_PER_FRAME);
|
||||
}
|
||||
|
||||
long frameBudgetNs = Math.max(1_000_000L, (long) (baselineFrameTime * LAYER_BUDGET_FACTOR));
|
||||
int layersByBudget = Math.max(MIN_LAYERS_PER_FRAME, (int) Math.round((double) frameBudgetNs / averageLayerCostNs));
|
||||
|
||||
int framesLeft = Math.max(1, (int) Math.ceil((double) remainingBudgetNs / baselineFrameTime));
|
||||
int layersByProgress = Math.max(MIN_LAYERS_PER_FRAME, (int) Math.ceil((double) remainingLayers / framesLeft));
|
||||
|
||||
int layers = Math.min(MAX_LAYERS_PER_FRAME, Math.min(layersByBudget, layersByProgress));
|
||||
return Math.min(remainingLayers, Math.max(MIN_LAYERS_PER_FRAME, layers));
|
||||
}
|
||||
|
||||
private static void recordFrameDuration(long duration, long timestamp) {
|
||||
recentFrameDurations.addLast(duration);
|
||||
recentFrameTimestamps.addLast(timestamp);
|
||||
|
||||
while (!recentFrameTimestamps.isEmpty() && timestamp - recentFrameTimestamps.peekFirst() > FRAME_TIME_WINDOW_NS) {
|
||||
recentFrameTimestamps.removeFirst();
|
||||
recentFrameDurations.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
private static void recordBaselineFrameDuration(long duration, long timestamp) {
|
||||
baselineFrameDurations.addLast(duration);
|
||||
baselineFrameTimestamps.addLast(timestamp);
|
||||
|
||||
while (!baselineFrameTimestamps.isEmpty() && timestamp - baselineFrameTimestamps.peekFirst() > FRAME_TIME_WINDOW_NS) {
|
||||
baselineFrameTimestamps.removeFirst();
|
||||
baselineFrameDurations.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
private static long getAverageFrameTimeNs() {
|
||||
if (recentFrameDurations.isEmpty()) {
|
||||
return 16_000_000L;
|
||||
}
|
||||
|
||||
long sum = 0;
|
||||
for (Long duration : recentFrameDurations) {
|
||||
sum += duration;
|
||||
}
|
||||
return sum / recentFrameDurations.size();
|
||||
}
|
||||
|
||||
private static long getBaselineFrameTimeNs() {
|
||||
if (baselineFrameDurations.isEmpty()) {
|
||||
return 16_000_000L;
|
||||
}
|
||||
|
||||
long sum = 0;
|
||||
for (Long duration : baselineFrameDurations) {
|
||||
sum += duration;
|
||||
}
|
||||
return sum / baselineFrameDurations.size();
|
||||
}
|
||||
|
||||
private static void recordCompletedStep(long duration) {
|
||||
long timestamp = System.nanoTime();
|
||||
completedStepDurations.addLast(duration);
|
||||
completedStepTimestamps.addLast(timestamp);
|
||||
|
||||
while (!completedStepTimestamps.isEmpty() && timestamp - completedStepTimestamps.peekFirst() > STAT_WINDOW_NS) {
|
||||
completedStepTimestamps.removeFirst();
|
||||
completedStepDurations.removeFirst();
|
||||
}
|
||||
|
||||
if (lastStatsPrintTime == 0 || timestamp - lastStatsPrintTime >= STAT_WINDOW_NS) {
|
||||
//printStepStats();
|
||||
lastStatsPrintTime = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
private static void printStepStats() {
|
||||
if (completedStepDurations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long sum = 0;
|
||||
long min = Long.MAX_VALUE;
|
||||
long max = Long.MIN_VALUE;
|
||||
for (Long duration : completedStepDurations) {
|
||||
sum += duration;
|
||||
min = Math.min(min, duration);
|
||||
max = Math.max(max, duration);
|
||||
}
|
||||
|
||||
long average = sum / completedStepDurations.size();
|
||||
System.out.printf(Locale.ROOT,
|
||||
"[FogSim] step stats last 5s: avg=%.2f ms min=%.2f ms max=%.2f ms%n",
|
||||
average / 1_000_000.0,
|
||||
min / 1_000_000.0,
|
||||
max / 1_000_000.0);
|
||||
}
|
||||
|
||||
public static int getCurrentDensityTexture() {
|
||||
return FogDensityTextures.getReadTexture();
|
||||
}
|
||||
|
||||
public static int getPreviousDensityTexture() {
|
||||
return FogDensityTextures.getPreviousTexture();
|
||||
}
|
||||
|
||||
public static float getFogInterpolationAlpha() {
|
||||
if (!FogDensityTextures.hasPreviousState()) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
long currentTime = System.nanoTime();
|
||||
long elapsed = currentTime - lastSwapTimeNs;
|
||||
if (elapsed <= 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float alpha = (float) elapsed / (float) SIMULATION_INTERVAL_NS;
|
||||
return Math.min(alpha, 1.0f);
|
||||
}
|
||||
|
||||
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,313 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
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 static int emissionMultiplierLocation;
|
||||
private static int targetDensityMultiplierLocation;
|
||||
private static int biomeParamsSamplerLocation;
|
||||
private static int fogDayRandomLocation;
|
||||
private static int rainMultiplierLocation;
|
||||
private static int heightFadeMaxLocation;
|
||||
private static int skyLightSamplerLocation;
|
||||
|
||||
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");
|
||||
biomeParamsSamplerLocation = program.uniform("BiomeParamsSampler");
|
||||
emissionMultiplierLocation = program.uniform("EmissionMultiplier");
|
||||
targetDensityMultiplierLocation = program.uniform("TargetDensityMultiplier");
|
||||
fogDayRandomLocation = program.uniform("FogDayRandom");
|
||||
rainMultiplierLocation = program.uniform("RainMultiplier");
|
||||
heightFadeMaxLocation = program.uniform("HeightFadeMax");
|
||||
skyLightSamplerLocation = program.uniform("SkyLightSampler");
|
||||
|
||||
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
|
||||
) {
|
||||
simulateRange(
|
||||
0,
|
||||
FogWorldVolume.SIZE_Z,
|
||||
globalEmissionMultiplier,
|
||||
globalAbsorptionMultiplier,
|
||||
globalDecay,
|
||||
diffusionRate,
|
||||
upDiffusion,
|
||||
downDiffusion,
|
||||
horizontalDiffusion
|
||||
);
|
||||
FogDensityTextures.swap();
|
||||
}
|
||||
|
||||
public static void simulateRange(
|
||||
int startZ,
|
||||
int endZ,
|
||||
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);
|
||||
|
||||
// Привязки текстур для юнитов (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);
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
int previousTex2_2D = GL11.glGetInteger(GL_TEXTURE_BINDING_2D);
|
||||
int previousTex2_3D = GL11.glGetInteger(GL_TEXTURE_BINDING_3D);
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE3);
|
||||
int previousTex3_2D = GL11.glGetInteger(GL_TEXTURE_BINDING_2D);
|
||||
int previousTex3_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);
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, FogBiomeTexture.getTexture());
|
||||
GL20.glUniform1i(biomeParamsSamplerLocation, 2);
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE3);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getSkyLightTexture());
|
||||
GL20.glUniform1i(skyLightSamplerLocation, 3);
|
||||
|
||||
// Устанавливаем параметры
|
||||
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.glUniform1f(emissionMultiplierLocation, FogEnvironmentContext.getEmissionMultiplier());
|
||||
GL20.glUniform1f(targetDensityMultiplierLocation, FogEnvironmentContext.getTargetDensityMultiplier());
|
||||
GL20.glUniform1f(fogDayRandomLocation, FogEnvironmentContext.getFogDayRandom());
|
||||
GL20.glUniform1f(rainMultiplierLocation, FogEnvironmentContext.getRainMultiplier());
|
||||
GL20.glUniform1f(heightFadeMaxLocation, FogEnvironmentContext.getHeightFadeMax());
|
||||
|
||||
GL20.glUniform3f(voxelSizeLocation,
|
||||
1.0f / FogWorldVolume.SIZE_X,
|
||||
1.0f / FogWorldVolume.SIZE_Y,
|
||||
1.0f / FogWorldVolume.SIZE_Z
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// РЕНДЕРИНГ
|
||||
// ========================================
|
||||
|
||||
int clampedStartZ = Math.max(0, Math.min(FogWorldVolume.SIZE_Z, startZ));
|
||||
int clampedEndZ = Math.max(clampedStartZ, Math.min(FogWorldVolume.SIZE_Z, endZ));
|
||||
|
||||
for (int z = clampedStartZ; z < clampedEndZ; z++) {
|
||||
GL30.glFramebufferTexture3D(
|
||||
GL30.GL_FRAMEBUFFER,
|
||||
GL30.GL_COLOR_ATTACHMENT0,
|
||||
GL12.GL_TEXTURE_3D,
|
||||
fogWrite,
|
||||
0,
|
||||
z
|
||||
);
|
||||
|
||||
GL20.glUniform1f(currentZLocation, (float) (z + 0.5f) / 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(GL13.GL_TEXTURE2);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTex2_2D);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, previousTex2_3D);
|
||||
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE3);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previousTex3_2D);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, previousTex3_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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
|
||||
public final class FogSystem {
|
||||
|
||||
private static boolean initialized;
|
||||
|
||||
private static int originChunkX;
|
||||
private static int originChunkZ;
|
||||
|
||||
private static int ringChunkOffsetX;
|
||||
private static int ringChunkOffsetZ;
|
||||
|
||||
private FogSystem() {
|
||||
}
|
||||
|
||||
public static void beginRender(float tickDelta) {
|
||||
|
||||
if (!initialized) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
updateWindow();
|
||||
|
||||
// Блоки и density: загружаем готовые данные
|
||||
FogChunkStreamer.uploadReadyData();
|
||||
|
||||
// Блоки и density: обрабатываем изменения
|
||||
FogChunkStreamer.processBlockChanges();
|
||||
|
||||
// Освещение: загружаем готовые секции + инкрементальные обновления (render thread)
|
||||
FogLightStreamer.uploadReadySections();
|
||||
|
||||
// Биомы
|
||||
FogBiomeStreamer.uploadReadyData();
|
||||
|
||||
FogEnvironmentContext.update(tickDelta);
|
||||
|
||||
FogSimulationManager.update();
|
||||
}
|
||||
|
||||
public static void tick() {
|
||||
// Блоки: initial load в client thread
|
||||
FogChunkStreamer.processPending();
|
||||
|
||||
// Освещение: читаем данные секций (client thread, без OpenGL)
|
||||
FogLightStreamer.prepareAllSections();
|
||||
|
||||
// Читаем биомы в client thread
|
||||
FogBiomeStreamer.processPending();
|
||||
}
|
||||
|
||||
private static void initialize() {
|
||||
FogWorldVolume.init();
|
||||
FogLightVolume.init();
|
||||
FogLightStreamer.init();
|
||||
FogSimulationManager.init();
|
||||
FogBiomeTexture.init();
|
||||
FogEnvironmentContext.init();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private static void updateWindow() {
|
||||
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
|
||||
if (client.world == null || client.player == null)
|
||||
return;
|
||||
|
||||
int playerChunkX = client.player.getBlockX() >> 4;
|
||||
int playerChunkZ = client.player.getBlockZ() >> 4;
|
||||
|
||||
int newOriginChunkX =
|
||||
playerChunkX - FogWorldVolume.CHUNKS_X / 2;
|
||||
|
||||
int newOriginChunkZ =
|
||||
playerChunkZ - FogWorldVolume.CHUNKS_Z / 2;
|
||||
|
||||
if (newOriginChunkX == originChunkX &&
|
||||
newOriginChunkZ == originChunkZ)
|
||||
return;
|
||||
|
||||
int oldOriginChunkX = originChunkX;
|
||||
int oldOriginChunkZ = originChunkZ;
|
||||
|
||||
originChunkX = newOriginChunkX;
|
||||
originChunkZ = newOriginChunkZ;
|
||||
|
||||
ringChunkOffsetX =
|
||||
Math.floorMod(originChunkX,
|
||||
FogWorldVolume.CHUNKS_X);
|
||||
|
||||
ringChunkOffsetZ =
|
||||
Math.floorMod(originChunkZ,
|
||||
FogWorldVolume.CHUNKS_Z);
|
||||
|
||||
FogChunkStreamer.update(
|
||||
oldOriginChunkX,
|
||||
oldOriginChunkZ,
|
||||
originChunkX,
|
||||
originChunkZ,
|
||||
ringChunkOffsetX,
|
||||
ringChunkOffsetZ
|
||||
);
|
||||
|
||||
// Освещение: помечаем новые секции для initial load
|
||||
FogLightStreamer.update(
|
||||
oldOriginChunkX,
|
||||
oldOriginChunkZ,
|
||||
originChunkX,
|
||||
originChunkZ,
|
||||
ringChunkOffsetX,
|
||||
ringChunkOffsetZ
|
||||
);
|
||||
|
||||
// Вызов FogBiomeStreamer.update после FogLightStreamer.update
|
||||
FogBiomeStreamer.update(
|
||||
oldOriginChunkX, oldOriginChunkZ,
|
||||
originChunkX, originChunkZ,
|
||||
ringChunkOffsetX, ringChunkOffsetZ
|
||||
);
|
||||
|
||||
// Boost uploads for a few frames to catch up after a window shift
|
||||
FogChunkStreamer.setBurst(8);
|
||||
FogLightStreamer.setBurst(8);
|
||||
FogBiomeStreamer.setBurst(8);
|
||||
}
|
||||
|
||||
public static int originChunkX() {
|
||||
return originChunkX;
|
||||
}
|
||||
|
||||
public static int originChunkZ() {
|
||||
return originChunkZ;
|
||||
}
|
||||
|
||||
public static int ringChunkOffsetX() {
|
||||
return ringChunkOffsetX;
|
||||
}
|
||||
|
||||
public static int ringChunkOffsetZ() {
|
||||
return ringChunkOffsetZ;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
// No GL43 usage: keep compatibility with OpenGL 3.2
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class FogWorldVolume {
|
||||
|
||||
public static final int SIZE_X = 256;
|
||||
public static final int SIZE_Z = 256;
|
||||
|
||||
public static final int MIN_Y = -64;
|
||||
public static final int MAX_Y = 320;
|
||||
public static final int SIZE_Y = MAX_Y - MIN_Y;
|
||||
|
||||
public static final int CHUNK_SIZE = 16;
|
||||
|
||||
public static final int CHUNKS_X = SIZE_X / CHUNK_SIZE;
|
||||
public static final int CHUNKS_Z = SIZE_Z / CHUNK_SIZE;
|
||||
|
||||
private static int texture = -1;
|
||||
|
||||
private static final byte AIR_VALUE = new FogBlockProperties(false, 0, 0).pack();
|
||||
|
||||
private static final byte[] ZEROS_ARRAY;
|
||||
static {
|
||||
ZEROS_ARRAY = new byte[CHUNK_SIZE * SIZE_Y * CHUNK_SIZE];
|
||||
java.util.Arrays.fill(ZEROS_ARRAY, AIR_VALUE);
|
||||
}
|
||||
|
||||
private static final ByteBuffer CHUNK_BUFFER =
|
||||
BufferUtils.createByteBuffer(CHUNK_SIZE * SIZE_Y * CHUNK_SIZE);
|
||||
|
||||
private static final ByteBuffer SINGLE_VOXEL_BUFFER =
|
||||
BufferUtils.createByteBuffer(1);
|
||||
|
||||
// PBO для быстрой очистки регионов
|
||||
private static int clearPBO = -1;
|
||||
// Ring of PBOs for chunk uploads to avoid overwriting a buffer while the GPU is still using it
|
||||
private static final int[] uploadPBOs = new int[3];
|
||||
private static int uploadPBOIndex = 0;
|
||||
private static final int MAX_CLEAR_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
|
||||
|
||||
private FogWorldVolume() {
|
||||
}
|
||||
|
||||
private static void initClearPBO() {
|
||||
if (clearPBO != -1) return;
|
||||
|
||||
clearPBO = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO);
|
||||
|
||||
// Выделяем буфер в VRAM
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, MAX_CLEAR_SIZE, GL15.GL_STATIC_DRAW);
|
||||
|
||||
// Заполняем AIR_VALUE через mapping
|
||||
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(AIR_VALUE);
|
||||
}
|
||||
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (texture != -1) return;
|
||||
|
||||
// Инициализируем PBO ДО создания текстуры
|
||||
initClearPBO();
|
||||
initUploadPBO();
|
||||
|
||||
texture = GL11.glGenTextures();
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
|
||||
GL30.glTexImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
GL30.GL_R8UI,
|
||||
SIZE_X,
|
||||
SIZE_Y,
|
||||
SIZE_Z,
|
||||
0,
|
||||
GL30.GL_RED_INTEGER,
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
CHUNK_BUFFER.clear();
|
||||
CHUNK_BUFFER.put(ZEROS_ARRAY);
|
||||
CHUNK_BUFFER.flip();
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
|
||||
for (int chunkZ = 0; chunkZ < CHUNKS_Z; chunkZ++) {
|
||||
for (int chunkX = 0; chunkX < CHUNKS_X; chunkX++) {
|
||||
uploadChunk(chunkX, chunkZ, CHUNK_BUFFER);
|
||||
CHUNK_BUFFER.rewind();
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
public static void uploadChunk(int textureChunkX, int textureChunkZ, ByteBuffer data) {
|
||||
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,
|
||||
textureChunkX * CHUNK_SIZE,
|
||||
0,
|
||||
textureChunkZ * CHUNK_SIZE,
|
||||
CHUNK_SIZE,
|
||||
SIZE_Y,
|
||||
CHUNK_SIZE,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
data
|
||||
);
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
public static void updateSingleVoxel(
|
||||
int textureChunkX,
|
||||
int textureChunkZ,
|
||||
int localX,
|
||||
int localY,
|
||||
int localZ,
|
||||
byte 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,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
SINGLE_VOXEL_BUFFER
|
||||
);
|
||||
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
public static void uploadZeros(int textureChunkX, int textureChunkZ) {
|
||||
ByteBuffer buffer = chunkBuffer();
|
||||
buffer.put(ZEROS_ARRAY);
|
||||
buffer.flip();
|
||||
uploadChunk(textureChunkX, textureChunkZ, buffer);
|
||||
}
|
||||
|
||||
public static void uploadChunkFromArray(
|
||||
int textureChunkX,
|
||||
int textureChunkZ,
|
||||
byte[] data
|
||||
) {
|
||||
ByteBuffer buffer = chunkBuffer();
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
uploadChunk(textureChunkX, textureChunkZ, buffer);
|
||||
}
|
||||
|
||||
public static void beginUpload() {
|
||||
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);
|
||||
}
|
||||
|
||||
public static void uploadChunkFromArrayNoBind(
|
||||
int textureChunkX,
|
||||
int textureChunkZ,
|
||||
byte[] data
|
||||
) {
|
||||
// Use a ring of PBOs so one upload never overwrites another still pending on the GPU
|
||||
if (uploadPBOs[0] != 0) {
|
||||
int pbo = uploadPBOs[uploadPBOIndex % uploadPBOs.length];
|
||||
uploadPBOIndex++;
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, pbo);
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, CHUNK_SIZE * SIZE_Y * CHUNK_SIZE, GL15.GL_STREAM_DRAW);
|
||||
ByteBuffer mapped = GL15.glMapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, GL15.GL_WRITE_ONLY);
|
||||
if (mapped != null) {
|
||||
mapped.put(data);
|
||||
GL15.glUnmapBuffer(GL31.GL_PIXEL_UNPACK_BUFFER);
|
||||
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
textureChunkX * CHUNK_SIZE,
|
||||
0,
|
||||
textureChunkZ * CHUNK_SIZE,
|
||||
CHUNK_SIZE,
|
||||
SIZE_Y,
|
||||
CHUNK_SIZE,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
0L
|
||||
);
|
||||
}
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
} else {
|
||||
ByteBuffer buffer = chunkBuffer();
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
GL30.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
textureChunkX * CHUNK_SIZE,
|
||||
0,
|
||||
textureChunkZ * CHUNK_SIZE,
|
||||
CHUNK_SIZE,
|
||||
SIZE_Y,
|
||||
CHUNK_SIZE,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
buffer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void endUpload() {
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
|
||||
private static void initUploadPBO() {
|
||||
for (int i = 0; i < uploadPBOs.length; i++) {
|
||||
uploadPBOs[i] = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, uploadPBOs[i]);
|
||||
GL15.glBufferData(GL31.GL_PIXEL_UNPACK_BUFFER, CHUNK_SIZE * SIZE_Y * CHUNK_SIZE, GL15.GL_STREAM_DRAW);
|
||||
}
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
|
||||
public static ByteBuffer chunkBuffer() {
|
||||
CHUNK_BUFFER.clear();
|
||||
return CHUNK_BUFFER;
|
||||
}
|
||||
|
||||
public static int getTexture() {
|
||||
return texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает несколько непрерывных физических регионов через PBO (асинхронно).
|
||||
* Размеры widthChunks и depthChunks задаются в чанках (не в блоках!).
|
||||
*/
|
||||
public static void clearRegions(
|
||||
int[] physX, int[] physZ,
|
||||
int[] widthChunks, int[] depthChunks,
|
||||
int count
|
||||
) {
|
||||
// Use PBO-based clears for OpenGL 3.2 compatibility
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, texture);
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, clearPBO);
|
||||
|
||||
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);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int w = widthChunks[i] * CHUNK_SIZE;
|
||||
int h = SIZE_Y;
|
||||
int d = depthChunks[i] * CHUNK_SIZE;
|
||||
|
||||
// 0L = offset 0 внутри PBO (начало буфера)
|
||||
GL12.glTexSubImage3D(
|
||||
GL12.GL_TEXTURE_3D,
|
||||
0,
|
||||
physX[i] * CHUNK_SIZE,
|
||||
0,
|
||||
physZ[i] * CHUNK_SIZE,
|
||||
w, h, d,
|
||||
GL30.GL_RED_INTEGER,
|
||||
GL11.GL_UNSIGNED_BYTE,
|
||||
0L // ← long offset вместо ByteBuffer
|
||||
);
|
||||
}
|
||||
|
||||
GL15.glBindBuffer(GL31.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package su.divan2000.veila.client.simulation;
|
||||
|
||||
/**
|
||||
* Простой Perlin noise без зависимостей от Minecraft API.
|
||||
*/
|
||||
public final class SimpleNoiseSampler {
|
||||
|
||||
private final int[] perm = new int[512];
|
||||
|
||||
public SimpleNoiseSampler(long seed) {
|
||||
java.util.Random random = new java.util.Random(seed);
|
||||
int[] p = new int[256];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
p[i] = i;
|
||||
}
|
||||
// Fisher-Yates shuffle
|
||||
for (int i = 255; i > 0; i--) {
|
||||
int j = random.nextInt(i + 1);
|
||||
int temp = p[i];
|
||||
p[i] = p[j];
|
||||
p[j] = temp;
|
||||
}
|
||||
for (int i = 0; i < 512; i++) {
|
||||
perm[i] = p[i & 255];
|
||||
}
|
||||
}
|
||||
|
||||
public double sample(double x, double y) {
|
||||
int X = (int)Math.floor(x) & 255;
|
||||
int Y = (int)Math.floor(y) & 255;
|
||||
|
||||
x -= Math.floor(x);
|
||||
y -= Math.floor(y);
|
||||
|
||||
double u = fade(x);
|
||||
double v = fade(y);
|
||||
|
||||
int A = perm[X] + Y;
|
||||
int AA = perm[A];
|
||||
int AB = perm[A + 1];
|
||||
int B = perm[X + 1] + Y;
|
||||
int BA = perm[B];
|
||||
int BB = perm[B + 1];
|
||||
|
||||
return lerp(v,
|
||||
lerp(u, grad(perm[AA], x, y), grad(perm[BA], x - 1, y)),
|
||||
lerp(u, grad(perm[AB], x, y - 1), grad(perm[BB], x - 1, y - 1))
|
||||
);
|
||||
}
|
||||
|
||||
private static double fade(double t) {
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
private static double lerp(double t, double a, double b) {
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
private static double grad(int hash, double x, double y) {
|
||||
int h = hash & 7;
|
||||
double u = h < 4 ? x : y;
|
||||
double v = h < 4 ? y : x;
|
||||
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? 2.0 * v : -2.0 * v);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import su.divan2000.veila.client.render.PostProcessingManager;
|
||||
import su.divan2000.veila.client.render.fog.FogSystem;
|
||||
import su.divan2000.veila.client.simulation.FogSystem;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
public class GameRendererMixin {
|
||||
@@ -27,7 +27,7 @@ public class GameRendererMixin {
|
||||
CallbackInfo ci
|
||||
) {
|
||||
PostProcessingManager.init();
|
||||
PostProcessingManager.render();
|
||||
PostProcessingManager.render(tickDelta);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
@@ -61,6 +61,9 @@ public class GameRendererMixin {
|
||||
boolean tick,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
FogSystem.beginRender();
|
||||
net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance();
|
||||
if (client.world != null && client.player != null) {
|
||||
FogSystem.beginRender(tickDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package su.divan2000.veila.mixin.client;
|
||||
|
||||
import net.minecraft.util.math.ChunkSectionPos;
|
||||
import net.minecraft.world.LightType;
|
||||
import net.minecraft.world.chunk.ChunkNibbleArray;
|
||||
import net.minecraft.world.chunk.light.LightStorage;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import su.divan2000.veila.client.simulation.FogLightStreamer;
|
||||
|
||||
@Mixin(LightStorage.class)
|
||||
public class LightStorageMixin {
|
||||
|
||||
@Shadow
|
||||
private LightType lightType;
|
||||
|
||||
@Inject(method = "set(JI)V", at = @At("TAIL"))
|
||||
private void veila$onLightSet(long blockPos, int value, CallbackInfo ci) {
|
||||
long sectionPos = ChunkSectionPos.fromBlockPos(blockPos);
|
||||
if (this.lightType == LightType.BLOCK) {
|
||||
FogLightStreamer.onBlockLightUpdated(sectionPos);
|
||||
} else {
|
||||
FogLightStreamer.onSkyLightUpdated(sectionPos);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "enqueueSectionData(JLnet/minecraft/world/chunk/ChunkNibbleArray;)V", at = @At("TAIL"))
|
||||
private void veila$onEnqueueSection(long sectionPos, @Nullable ChunkNibbleArray array, CallbackInfo ci) {
|
||||
if (array != null) {
|
||||
if (this.lightType == LightType.BLOCK) {
|
||||
FogLightStreamer.onBlockLightUpdated(sectionPos);
|
||||
} else {
|
||||
FogLightStreamer.onSkyLightUpdated(sectionPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setSectionStatus(JZ)V", at = @At("TAIL"))
|
||||
private void veila$onSetSectionStatus(long sectionPos, boolean notReady, CallbackInfo ci) {
|
||||
if (!notReady) {
|
||||
if (this.lightType == LightType.BLOCK) {
|
||||
FogLightStreamer.onBlockLightUpdated(sectionPos);
|
||||
} else {
|
||||
FogLightStreamer.onSkyLightUpdated(sectionPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ 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.CallbackInfo;
|
||||
import su.divan2000.veila.client.render.fog.FogSystem;
|
||||
import su.divan2000.veila.client.simulation.FogSystem;
|
||||
|
||||
@Mixin(MinecraftClient.class)
|
||||
public class MinecraftClientMixin {
|
||||
|
||||
@@ -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.simulation.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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#version 150
|
||||
|
||||
uniform sampler2D DepthSampler;
|
||||
uniform vec2 ScreenSize;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 texelSize = 1.0 / ScreenSize;
|
||||
|
||||
// Берём минимальный depth из 2x2 блока (ближайший объект)
|
||||
float d00 = texture(DepthSampler, texCoord + vec2(-0.5, -0.5) * texelSize).r;
|
||||
float d10 = texture(DepthSampler, texCoord + vec2( 0.5, -0.5) * texelSize).r;
|
||||
float d01 = texture(DepthSampler, texCoord + vec2(-0.5, 0.5) * texelSize).r;
|
||||
float d11 = texture(DepthSampler, texCoord + vec2( 0.5, 0.5) * texelSize).r;
|
||||
|
||||
float minDepth = min(min(d00, d10), min(d01, d11));
|
||||
|
||||
FragColor = vec4(minDepth, 0.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#version 150
|
||||
|
||||
uniform sampler2D DiffuseSampler;
|
||||
uniform sampler2D FogSampler;
|
||||
uniform sampler2D DepthSampler;
|
||||
uniform sampler2D ScaledDepthSampler;
|
||||
uniform vec2 ScreenSize;
|
||||
uniform vec2 ScaledScreenSize;
|
||||
uniform mat4 InverseProjection;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Параметры JBU
|
||||
//------------------------------------------------------------
|
||||
|
||||
const int WINDOW_SIZE = 5;
|
||||
const int WINDOW_RADIUS = WINDOW_SIZE / 2;
|
||||
|
||||
const float SIGMA_SPATIAL = 2.0;
|
||||
const float SIGMA_SPECTRAL = 0.3; // Меньше = резче границы
|
||||
|
||||
const float MIN_WEIGHT = 0.0001;
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
// Предвычисленные spatial weights для окна 5x5
|
||||
const float spatialWeights[25] = float[25](
|
||||
0.003765, 0.015019, 0.023792, 0.015019, 0.003765,
|
||||
0.015019, 0.059912, 0.094907, 0.059912, 0.015019,
|
||||
0.023792, 0.094907, 0.150342, 0.094907, 0.023792,
|
||||
0.015019, 0.059912, 0.094907, 0.059912, 0.015019,
|
||||
0.003765, 0.015019, 0.023792, 0.015019, 0.003765
|
||||
);
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
float linearizeDepth(float depth)
|
||||
{
|
||||
vec4 clip = vec4(0.0, 0.0, depth * 2.0 - 1.0, 1.0);
|
||||
vec4 view = InverseProjection * clip;
|
||||
return -view.z / view.w;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 sceneColor = texture(DiffuseSampler, texCoord).rgb;
|
||||
|
||||
// Линеаризованный depth текущего full-res пикселя
|
||||
float depthFull = linearizeDepth(texture(DepthSampler, texCoord).r);
|
||||
|
||||
vec2 lowResCoord = texCoord;
|
||||
vec2 texelSize = 1.0 / ScaledScreenSize;
|
||||
|
||||
vec4 sumColor = vec4(0.0);
|
||||
float sumWeight = 0.0;
|
||||
|
||||
int sampleIndex = 0;
|
||||
|
||||
for (int y = -WINDOW_RADIUS; y <= WINDOW_RADIUS; y++) {
|
||||
for (int x = -WINDOW_RADIUS; x <= WINDOW_RADIUS; x++) {
|
||||
vec2 neighborCoord = lowResCoord + vec2(float(x), float(y)) * texelSize;
|
||||
|
||||
if (any(lessThan(neighborCoord, vec2(0.0))) || any(greaterThan(neighborCoord, vec2(1.0)))) {
|
||||
sampleIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
float wSpatial = spatialWeights[sampleIndex];
|
||||
|
||||
// Линеаризованный depth из half-res depth buffer
|
||||
float depthNeighbor = linearizeDepth(texture(ScaledDepthSampler, neighborCoord).r);
|
||||
float depthDiff = abs(depthFull - depthNeighbor);
|
||||
|
||||
// Range weight с линеаризованным depth в метрах
|
||||
float wRange = exp(-(depthDiff * depthDiff) / (2.0 * SIGMA_SPECTRAL * SIGMA_SPECTRAL));
|
||||
|
||||
float weight = wSpatial * wRange;
|
||||
|
||||
vec4 fogSample = texture(FogSampler, neighborCoord);
|
||||
|
||||
sumColor += fogSample * weight;
|
||||
sumWeight += weight;
|
||||
|
||||
sampleIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
vec4 result;
|
||||
if (sumWeight < MIN_WEIGHT) {
|
||||
result = texture(FogSampler, lowResCoord);
|
||||
} else {
|
||||
result = sumColor / sumWeight;
|
||||
}
|
||||
|
||||
vec3 scattered = result.rgb;
|
||||
float transmittance = result.a;
|
||||
|
||||
vec3 finalColor = sceneColor * transmittance + scattered;
|
||||
|
||||
FragColor = vec4(finalColor, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
#version 150
|
||||
|
||||
uniform sampler2D DepthSampler;
|
||||
uniform sampler3D FogVolume;
|
||||
uniform sampler3D PrevFogVolume;
|
||||
uniform sampler3D SkyLightVolume;
|
||||
uniform sampler3D BlockLightVolume;
|
||||
|
||||
uniform mat4 InverseProjection;
|
||||
uniform mat4 InverseView;
|
||||
|
||||
uniform vec3 FogColor;
|
||||
uniform vec3 CameraPosition;
|
||||
|
||||
uniform float FogInterpolation;
|
||||
uniform float SkyBrightness;
|
||||
|
||||
uniform ivec2 FogOrigin;
|
||||
uniform ivec2 RingBlockOffset;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Настройки
|
||||
//------------------------------------------------------------
|
||||
|
||||
const float FOG_SIZE_X = 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;
|
||||
|
||||
const float GAMMA = 2.0;
|
||||
|
||||
const bool USE_JITTERING = false;
|
||||
|
||||
const float EXTINCTION_COEFF = 1.0;
|
||||
const float SCATTERING_COEFF = 1.0;
|
||||
const float AMBIENT_INTENSITY = 0.10;
|
||||
const float MIN_TRANSMITTANCE = 0.01;
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
vec3 reconstructViewPosition(vec2 uv)
|
||||
{
|
||||
float depth = texture(DepthSampler, uv).r;
|
||||
|
||||
vec4 clip = vec4(
|
||||
uv * 2.0 - 1.0,
|
||||
depth * 2.0 - 1.0,
|
||||
1.0
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
vec3 worldToUV(vec3 worldPos)
|
||||
{
|
||||
if (
|
||||
worldPos.x < 0.0 || worldPos.x >= FOG_SIZE_X ||
|
||||
worldPos.z < 0.0 || worldPos.z >= FOG_SIZE_Z ||
|
||||
worldPos.y < 0.0 || worldPos.y >= WORLD_HEIGHT
|
||||
){
|
||||
return vec3(-1.0);
|
||||
}
|
||||
|
||||
float physicalX = mod(worldPos.x + float(RingBlockOffset.x), FOG_SIZE_X);
|
||||
float physicalZ = mod(worldPos.z + float(RingBlockOffset.y), FOG_SIZE_Z);
|
||||
|
||||
vec3 uv;
|
||||
uv.x = physicalX / FOG_SIZE_X;
|
||||
uv.z = physicalZ / FOG_SIZE_Z;
|
||||
uv.y = worldPos.y / WORLD_HEIGHT;
|
||||
|
||||
return uv;
|
||||
}
|
||||
|
||||
float density(vec3 worldPos)
|
||||
{
|
||||
vec3 uv = worldToUV(worldPos);
|
||||
if (uv.x < 0.0) return 0.0;
|
||||
float currentDensity = texture(FogVolume, uv).r;
|
||||
float prevDensity = texture(PrevFogVolume, uv).r;
|
||||
return mix(prevDensity, currentDensity, FogInterpolation);
|
||||
}
|
||||
|
||||
vec2 light(vec3 worldPos)
|
||||
{
|
||||
vec3 uv = worldToUV(worldPos);
|
||||
if (uv.x < 0.0) return vec2(0.0);
|
||||
|
||||
float blockNorm = texture(BlockLightVolume, uv).r;
|
||||
float skyNorm = texture(SkyLightVolume, uv).r;
|
||||
|
||||
float skyContribution = skyNorm * SkyBrightness;
|
||||
|
||||
return vec2(skyContribution, blockNorm);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
float random(vec2 st)
|
||||
{
|
||||
vec3 p3 = fract(vec3(st.xyx) * 0.1031);
|
||||
p3 += dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.x + p3.y) * p3.z);
|
||||
}
|
||||
|
||||
float mapParameterToDistance(float t)
|
||||
{
|
||||
return MAX_DISTANCE * pow(t, GAMMA);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 worldPos = reconstructWorldPosition(texCoord);
|
||||
vec3 rayDir = normalize(worldPos - CameraPosition);
|
||||
|
||||
float rayLength = distance(worldPos, CameraPosition);
|
||||
rayLength = min(rayLength, MAX_DISTANCE);
|
||||
|
||||
vec3 samplePos = CameraPosition;
|
||||
float currentDistance = 0.0;
|
||||
|
||||
float transmittance = 1.0;
|
||||
|
||||
float skyScattered = 0.0;
|
||||
float blockScattered = 0.0;
|
||||
float ambientScattered = 0.0;
|
||||
|
||||
float jitter = 0.0;
|
||||
if (USE_JITTERING) {
|
||||
float firstStep = mapParameterToDistance(1.0 / float(MAX_STEPS));
|
||||
jitter = random(texCoord) * firstStep;
|
||||
currentDistance = jitter;
|
||||
samplePos += rayDir * currentDistance;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_STEPS; i++) {
|
||||
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;
|
||||
|
||||
float d = density(samplePos);
|
||||
|
||||
float opticalDepth = d * stepSize * EXTINCTION_COEFF;
|
||||
|
||||
if (d > 0.0) {
|
||||
vec2 lightValues = light(samplePos);
|
||||
float totalLightAtPoint = lightValues.x + lightValues.y;
|
||||
|
||||
float baseScattering = d * stepSize * SCATTERING_COEFF;
|
||||
|
||||
float stepScattering = totalLightAtPoint * d * stepSize * SCATTERING_COEFF;
|
||||
|
||||
skyScattered += lightValues.x * baseScattering * transmittance;
|
||||
blockScattered += lightValues.y * baseScattering * transmittance * ((lightValues.y - lightValues.x) * 0.9 + 0.1);
|
||||
ambientScattered += AMBIENT_INTENSITY * baseScattering * transmittance;
|
||||
|
||||
transmittance *= exp(-opticalDepth);
|
||||
}
|
||||
|
||||
currentDistance = nextDistance;
|
||||
|
||||
if (transmittance < MIN_TRANSMITTANCE) {
|
||||
transmittance = 0.0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentDistance >= rayLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float totalScattered = skyScattered + blockScattered + ambientScattered;
|
||||
|
||||
// Выводим: RGB = вложенный свет тумана, A = transmittance
|
||||
vec3 fogColor = FogColor * totalScattered;
|
||||
|
||||
FragColor = vec4(fogColor, transmittance);
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
#version 150
|
||||
|
||||
uniform sampler2D DiffuseSampler;
|
||||
uniform sampler2D DepthSampler;
|
||||
uniform sampler3D FogVolume;
|
||||
|
||||
uniform mat4 InverseProjection;
|
||||
uniform mat4 InverseView;
|
||||
|
||||
uniform vec3 CameraPosition;
|
||||
|
||||
uniform ivec2 FogOrigin;
|
||||
uniform ivec2 RingBlockOffset;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
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 WORLD_BOTTOM = -64.0;
|
||||
const float WORLD_HEIGHT = 384.0;
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
vec3 reconstructViewPosition(vec2 uv)
|
||||
{
|
||||
float depth = texture(DepthSampler, uv).r;
|
||||
|
||||
vec4 clip = vec4(
|
||||
uv * 2.0 - 1.0,
|
||||
depth * 2.0 - 1.0,
|
||||
1.0
|
||||
);
|
||||
|
||||
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 uv;
|
||||
|
||||
float localX = worldPos.x - float(FogOrigin.x);
|
||||
float localZ = worldPos.z - float(FogOrigin.y);
|
||||
|
||||
if (
|
||||
localX < 0.0 || localX >= FOG_SIZE_X ||
|
||||
localZ < 0.0 || localZ >= FOG_SIZE_Z ||
|
||||
worldPos.y < WORLD_BOTTOM ||
|
||||
worldPos.y >= WORLD_BOTTOM + WORLD_HEIGHT
|
||||
){
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
float physicalX = mod(
|
||||
localX + float(RingBlockOffset.x),
|
||||
FOG_SIZE_X
|
||||
);
|
||||
|
||||
float physicalZ = mod(
|
||||
localZ + float(RingBlockOffset.y),
|
||||
FOG_SIZE_Z
|
||||
);
|
||||
|
||||
uv.x = physicalX / FOG_SIZE_X;
|
||||
uv.z = physicalZ / FOG_SIZE_Z;
|
||||
uv.y = (worldPos.y - WORLD_BOTTOM) / WORLD_HEIGHT;
|
||||
|
||||
return texture(FogVolume, uv).r;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
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;
|
||||
|
||||
for (int i = 0; i < MAX_STEPS; i++)
|
||||
{
|
||||
float traveled = float(i) * STEP_SIZE;
|
||||
|
||||
if (traveled >= rayLength)
|
||||
break;
|
||||
|
||||
samplePos += rayDir * STEP_SIZE;
|
||||
|
||||
fog += density(samplePos) * STEP_SIZE;
|
||||
}
|
||||
|
||||
fog = clamp(fog, 0.0, 1.0);
|
||||
|
||||
vec3 fogColor = vec3(
|
||||
0.75,
|
||||
0.80,
|
||||
0.90
|
||||
);
|
||||
|
||||
FragColor = vec4(
|
||||
mix(sceneColor.rgb, fogColor, fog),
|
||||
1.0
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
#version 150
|
||||
|
||||
uniform usampler3D WorldInfoSampler;
|
||||
uniform sampler3D FogReadSampler;
|
||||
uniform sampler3D SkyLightSampler;
|
||||
uniform sampler2D BiomeParamsSampler;
|
||||
|
||||
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;
|
||||
|
||||
uniform float EmissionMultiplier;
|
||||
uniform float TargetDensityMultiplier;
|
||||
uniform float FogDayRandom;
|
||||
uniform float RainMultiplier;
|
||||
uniform float HeightFadeMax;
|
||||
|
||||
in vec2 texCoord;
|
||||
out vec4 FragColor;
|
||||
|
||||
const float HEIGHT_FADE_MIN = 75.0;
|
||||
const float MIN_Y = -64.0;
|
||||
const float SIZE_Y = 384.0;
|
||||
|
||||
const float UNDERGROUND_TARGET_DENSITY = 0.008; // Подземная дымка (слабее поверхностной)
|
||||
const float UNDERGROUND_EMISSION = 1.0;
|
||||
|
||||
float getEmission(uint magnitude, bool isAbsorber) {
|
||||
if (isAbsorber || magnitude == 0u) return 0.0;
|
||||
return float(magnitude) / 63.0 * 0.05;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Параметры биома
|
||||
vec4 biomeParams = texture(BiomeParamsSampler, uv.xz);
|
||||
float biomeEmission = biomeParams.r;
|
||||
float biomeTargetDensityBase = biomeParams.g;
|
||||
float biomeHumidity = biomeParams.b;
|
||||
float biomeTimeSensitivity = biomeParams.a;
|
||||
|
||||
// Под землёй и в закрытых местах туман ведёт себя иначе
|
||||
float skyLight = texture(SkyLightSampler, uv).r;
|
||||
float skyLightSmooth = smoothstep(0.2, 0.8, skyLight);
|
||||
|
||||
// === Emission ===
|
||||
float emissionTimeFactor = mix(1.0, EmissionMultiplier, biomeTimeSensitivity);
|
||||
|
||||
float surfaceEmission = biomeEmission * emissionTimeFactor;
|
||||
float finalEmission = mix(UNDERGROUND_EMISSION, surfaceEmission, skyLightSmooth) * GlobalEmissionMultiplier;
|
||||
|
||||
// === Target Density ===
|
||||
// 1. Определяем, туманный ли сегодня день
|
||||
float fogThreshold = 1.0 - biomeHumidity;
|
||||
float hasFogToday = step(fogThreshold, FogDayRandom);
|
||||
|
||||
// В дождь дымка всегда (если humidity > 0)
|
||||
float hasRain = step(0.01, RainMultiplier);
|
||||
hasFogToday = max(hasFogToday, hasRain * step(0.01, biomeHumidity));
|
||||
|
||||
// 2. Применяем зависимость от времени суток
|
||||
// Для plains (timeSensitivity = 1.0): дымка только в окно трапеции
|
||||
// Для джунглей (timeSensitivity = 0.3): дымка почти всегда
|
||||
float timeFactor = mix(1.0, TargetDensityMultiplier, biomeTimeSensitivity);
|
||||
|
||||
// 3. Дымка плавно затухает к высоте 80-100
|
||||
float worldY = uv.y * SIZE_Y + MIN_Y;
|
||||
float heightFade = 1.0 - clamp((worldY - HEIGHT_FADE_MIN) / (HeightFadeMax - HEIGHT_FADE_MIN), 0.0, 1.0);
|
||||
|
||||
// 4. Итоговая плотность
|
||||
float surfaceTargetDensity = biomeTargetDensityBase * hasFogToday * timeFactor * heightFade;
|
||||
float finalTargetDensity = mix(UNDERGROUND_TARGET_DENSITY, surfaceTargetDensity, skyLightSmooth);
|
||||
|
||||
// Читаем воксель мира
|
||||
uint packedSelf = texture(WorldInfoSampler, uv).r;
|
||||
bool selfSolid = (packedSelf >= 128u);
|
||||
uint tempSelf = packedSelf;
|
||||
if (selfSolid) tempSelf -= 128u;
|
||||
bool selfIsAbsorber = (tempSelf >= 64u);
|
||||
if (selfIsAbsorber) tempSelf -= 64u;
|
||||
uint selfMagnitude = tempSelf;
|
||||
|
||||
if (selfSolid) {
|
||||
FragColor = vec4(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
float fogOld = texture(FogReadSampler, uv).r;
|
||||
float fogNew = fogOld;
|
||||
|
||||
// Emission
|
||||
float emission = getEmission(selfMagnitude, selfIsAbsorber) * finalEmission;
|
||||
|
||||
vec3 offsets[6];
|
||||
offsets[0] = vec3(VoxelSize.x, 0.0, 0.0);
|
||||
offsets[1] = vec3(-VoxelSize.x, 0.0, 0.0);
|
||||
offsets[2] = vec3(0.0, VoxelSize.y, 0.0);
|
||||
offsets[3] = vec3(0.0, -VoxelSize.y, 0.0);
|
||||
offsets[4] = vec3(0.0, 0.0, VoxelSize.z);
|
||||
offsets[5] = vec3(0.0, 0.0, -VoxelSize.z);
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
vec3 neighborUV = uv + offsets[i];
|
||||
uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r;
|
||||
bool neighborSolid = (packedNeighbor >= 128u);
|
||||
uint tempN = packedNeighbor;
|
||||
if (neighborSolid) tempN -= 128u;
|
||||
bool neighborIsAbsorber = (tempN >= 64u);
|
||||
if (neighborIsAbsorber) tempN -= 64u;
|
||||
uint neighborMagnitude = tempN;
|
||||
if (neighborSolid) {
|
||||
emission += getEmission(neighborMagnitude, neighborIsAbsorber) * finalEmission;
|
||||
}
|
||||
}
|
||||
fogNew += emission;
|
||||
|
||||
// Absorption
|
||||
float absorption = fogOld * getAbsorption(selfMagnitude, selfIsAbsorber) * GlobalAbsorptionMultiplier;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
vec3 neighborUV = uv + offsets[i];
|
||||
uint packedNeighbor = texture(WorldInfoSampler, neighborUV).r;
|
||||
bool neighborSolid = (packedNeighbor >= 128u);
|
||||
uint tempN = packedNeighbor;
|
||||
if (neighborSolid) tempN -= 128u;
|
||||
bool neighborIsAbsorber = (tempN >= 64u);
|
||||
if (neighborIsAbsorber) tempN -= 64u;
|
||||
uint neighborMagnitude = tempN;
|
||||
if (neighborSolid) {
|
||||
absorption += fogOld * getAbsorption(neighborMagnitude, neighborIsAbsorber) * GlobalAbsorptionMultiplier;
|
||||
}
|
||||
}
|
||||
fogNew -= absorption;
|
||||
|
||||
// Diffusion
|
||||
float diffusion = 0.0;
|
||||
vec3 uvRight = uv + offsets[0];
|
||||
uint packedRight = texture(WorldInfoSampler, uvRight).r;
|
||||
if (!(packedRight >= 128u)) {
|
||||
diffusion += (texture(FogReadSampler, uvRight).r - fogOld) * HorizontalDiffusion * DiffusionRate;
|
||||
}
|
||||
vec3 uvLeft = uv + offsets[1];
|
||||
uint packedLeft = texture(WorldInfoSampler, uvLeft).r;
|
||||
if (!(packedLeft >= 128u)) {
|
||||
diffusion += (texture(FogReadSampler, uvLeft).r - fogOld) * HorizontalDiffusion * DiffusionRate;
|
||||
}
|
||||
vec3 uvUp = uv + offsets[2];
|
||||
uint packedUp = texture(WorldInfoSampler, uvUp).r;
|
||||
if (!(packedUp >= 128u)) {
|
||||
diffusion += (texture(FogReadSampler, uvUp).r - fogOld) * UpDiffusion * DiffusionRate;
|
||||
}
|
||||
vec3 uvDown = uv + offsets[3];
|
||||
uint packedDown = texture(WorldInfoSampler, uvDown).r;
|
||||
if (!(packedDown >= 128u)) {
|
||||
diffusion += (texture(FogReadSampler, uvDown).r - fogOld) * DownDiffusion * DiffusionRate;
|
||||
}
|
||||
vec3 uvFront = uv + offsets[4];
|
||||
uint packedFront = texture(WorldInfoSampler, uvFront).r;
|
||||
if (!(packedFront >= 128u)) {
|
||||
diffusion += (texture(FogReadSampler, uvFront).r - fogOld) * HorizontalDiffusion * DiffusionRate;
|
||||
}
|
||||
vec3 uvBack = uv + offsets[5];
|
||||
uint packedBack = texture(WorldInfoSampler, uvBack).r;
|
||||
if (!(packedBack >= 128u)) {
|
||||
diffusion += (texture(FogReadSampler, uvBack).r - fogOld) * HorizontalDiffusion * DiffusionRate;
|
||||
}
|
||||
fogNew += diffusion;
|
||||
|
||||
// Target Decay
|
||||
fogNew -= (fogOld - finalTargetDensity) * GlobalDecay;
|
||||
|
||||
fogNew = clamp(fogNew, 0.0, 1.0);
|
||||
FragColor = vec4(fogNew, 0.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -5,7 +5,9 @@
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"client": [
|
||||
"GameRendererMixin",
|
||||
"MinecraftClientMixin"
|
||||
"MinecraftClientMixin",
|
||||
"WorldChunkMixin",
|
||||
"LightStorageMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"id": "veila",
|
||||
"version": "${version}",
|
||||
"accessWidener": "veila.accesswidener",
|
||||
|
||||
"name": "Veila",
|
||||
"description": "",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
accessWidener v2 named
|
||||
accessible method net/minecraft/world/chunk/PalettedContainer get (I)Ljava/lang/Object;
|
||||
accessible field net/minecraft/world/chunk/light/LightingProvider blockLightProvider Lnet/minecraft/world/chunk/light/ChunkLightProvider;
|
||||
accessible field net/minecraft/world/chunk/light/LightingProvider skyLightProvider Lnet/minecraft/world/chunk/light/ChunkLightProvider;
|
||||
accessible field net/minecraft/world/biome/Biome weather Lnet/minecraft/world/biome/Biome$Weather;
|
||||
accessible class net/minecraft/world/biome/Biome$Weather
|
||||
accessible method net/minecraft/world/biome/Biome$Weather downfall ()F
|
||||
accessible method net/minecraft/world/biome/Biome$Weather temperature ()F
|
||||
accessible method net/minecraft/world/biome/Biome$Weather hasPrecipitation ()Z
|
||||
Reference in New Issue
Block a user