Compare commits
25
Commits
| 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
|
sourceSet sourceSets.client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessWidenerPath = file("src/main/resources/veila.accesswidener")
|
||||||
}
|
}
|
||||||
|
|
||||||
fabricApi {
|
fabricApi {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ public final class FullscreenQuad {
|
|||||||
vbo = GL15.glGenBuffers();
|
vbo = GL15.glGenBuffers();
|
||||||
|
|
||||||
int previous = glGetInteger(GL_VERTEX_ARRAY_BINDING);
|
int previous = glGetInteger(GL_VERTEX_ARRAY_BINDING);
|
||||||
|
int previousArrayBuffer = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
|
||||||
|
|
||||||
GL30.glBindVertexArray(vao);
|
GL30.glBindVertexArray(vao);
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ public final class FullscreenQuad {
|
|||||||
);
|
);
|
||||||
|
|
||||||
GL30.glBindVertexArray(previous);
|
GL30.glBindVertexArray(previous);
|
||||||
|
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, previousArrayBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void draw() {
|
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.GLProgram;
|
||||||
import su.divan2000.veila.client.gl.GLShader;
|
import su.divan2000.veila.client.gl.GLShader;
|
||||||
import su.divan2000.veila.client.gl.ResourceUtil;
|
import su.divan2000.veila.client.gl.ResourceUtil;
|
||||||
import su.divan2000.veila.client.render.fog.FogSystem;
|
import su.divan2000.veila.client.simulation.FogLightVolume;
|
||||||
import su.divan2000.veila.client.render.fog.FogWorldVolume;
|
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;
|
import java.nio.FloatBuffer;
|
||||||
|
|
||||||
public class PostProcessingManager {
|
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 projection = new Matrix4f();
|
||||||
private static final Matrix4f inverseProjection = 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 =
|
private static final int[] previousViewport = new int[4];
|
||||||
BufferUtils.createFloatBuffer(16);
|
private static final int[] previousTexture2D = new int[5];
|
||||||
|
private static final int[] previousTexture3D = new int[5];
|
||||||
private static final FloatBuffer inverseProjectionBuffer =
|
|
||||||
BufferUtils.createFloatBuffer(16);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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 scaledDepthTexture;
|
||||||
private static int depthSamplerLocation;
|
|
||||||
|
|
||||||
private static int cameraPositionLocation;
|
|
||||||
private static int screenSizeLocation;
|
|
||||||
|
|
||||||
private static final Matrix4f view = new Matrix4f();
|
private static final Matrix4f view = new Matrix4f();
|
||||||
private static final Matrix4f inverseView = 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 =
|
private static float fogR, fogG, fogB;
|
||||||
BufferUtils.createFloatBuffer(16);
|
|
||||||
|
|
||||||
private static final FloatBuffer inverseViewBuffer =
|
public static void setFogColor(float r, float g, float b) {
|
||||||
BufferUtils.createFloatBuffer(16);
|
fogR = r;
|
||||||
|
fogG = g;
|
||||||
private static int viewLocation;
|
fogB = b;
|
||||||
private static int inverseViewLocation;
|
}
|
||||||
|
|
||||||
private static int fogVolumeLocation;
|
|
||||||
private static int fogOriginLocation;
|
|
||||||
|
|
||||||
private static int ringOffsetLocation;
|
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
if (raymarchProgram != null)
|
||||||
if (program != null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
GLShader vertex = new GLShader(
|
setFogColor(0.75f, 0.80f, 0.90f);
|
||||||
|
|
||||||
|
// Инициализация raymarch программы
|
||||||
|
GLShader rmVertex = new GLShader(
|
||||||
GL20.GL_VERTEX_SHADER,
|
GL20.GL_VERTEX_SHADER,
|
||||||
ResourceUtil.load("shaders/fullscreen.vert")
|
ResourceUtil.load("shaders/fullscreen.vert")
|
||||||
);
|
);
|
||||||
|
GLShader rmFragment = new GLShader(
|
||||||
GLShader fragment = new GLShader(
|
|
||||||
GL20.GL_FRAGMENT_SHADER,
|
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");
|
rmVertex.delete();
|
||||||
inverseProjectionLocation = program.uniform("InverseProjection");
|
rmFragment.delete();
|
||||||
|
|
||||||
diffuseSamplerLocation = program.uniform("DiffuseSampler");
|
// Инициализация composite программы с JBU
|
||||||
depthSamplerLocation = program.uniform("DepthSampler");
|
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");
|
comp_diffuseSamplerLocation = compositeProgram.uniform("DiffuseSampler");
|
||||||
fogOriginLocation = program.uniform("FogOrigin");
|
comp_fogSamplerLocation = compositeProgram.uniform("FogSampler");
|
||||||
ringOffsetLocation = program.uniform("RingBlockOffset");
|
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");
|
compVertex.delete();
|
||||||
screenSizeLocation = program.uniform("ScreenSize");
|
compFragment.delete();
|
||||||
|
|
||||||
viewLocation = program.uniform("View");
|
GLShader ddVertex = new GLShader(
|
||||||
inverseViewLocation = program.uniform("InverseView");
|
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();
|
FullscreenQuad.init();
|
||||||
|
|
||||||
vertex.delete();
|
System.out.println("VEILA shaders compiled!");
|
||||||
fragment.delete();
|
|
||||||
|
|
||||||
System.out.println("VEILA shader 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;
|
return;
|
||||||
|
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
|
||||||
Framebuffer framebuffer = client.getFramebuffer();
|
if (client.world == null)
|
||||||
|
return;
|
||||||
|
|
||||||
int width = framebuffer.textureWidth;
|
int previousProgram = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||||
int height = framebuffer.textureHeight;
|
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(
|
for (int unit = 0; unit <= 4; unit++) {
|
||||||
GL30.GL_READ_FRAMEBUFFER,
|
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||||
framebuffer.fbo
|
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);
|
try {
|
||||||
DepthCopy.copy(width, height);
|
Framebuffer framebuffer = client.getFramebuffer();
|
||||||
|
|
||||||
program.bind();
|
int width = framebuffer.textureWidth;
|
||||||
|
int height = framebuffer.textureHeight;
|
||||||
|
|
||||||
/*
|
// Инициализация scaled фреймбуфера
|
||||||
* Diffuse
|
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);
|
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||||
GL11.glBindTexture(
|
GL11.glBindTexture(GL11.GL_TEXTURE_2D, DepthCopy.getTexture());
|
||||||
GL11.GL_TEXTURE_2D,
|
GL20.glUniform1i(dd_depthSamplerLocation, 0);
|
||||||
framebuffer.getColorAttachment()
|
|
||||||
);
|
|
||||||
GL20.glUniform1i(diffuseSamplerLocation, 0);
|
|
||||||
|
|
||||||
/*
|
GL20.glUniform2f(dd_screenSizeLocation, width, height);
|
||||||
* 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
|
|
||||||
);
|
|
||||||
|
|
||||||
FullscreenQuad.draw();
|
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);
|
GL13.glActiveTexture(GL13.GL_TEXTURE2);
|
||||||
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
|
||||||
GL13.glActiveTexture(GL13.GL_TEXTURE1);
|
GL13.glActiveTexture(GL13.GL_TEXTURE1);
|
||||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||||
|
|
||||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
||||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
program.unbind();
|
raymarchProgram.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
GL30.glBindFramebuffer(
|
private static void renderComposite(Framebuffer framebuffer, int width, int height) {
|
||||||
GL30.GL_READ_FRAMEBUFFER,
|
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer.fbo);
|
||||||
0
|
GL11.glViewport(0, 0, width, height);
|
||||||
);
|
|
||||||
|
|
||||||
|
compositeProgram.bind();
|
||||||
|
|
||||||
|
// Original scene (Diffuse)
|
||||||
GL13.glActiveTexture(GL13.GL_TEXTURE0);
|
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() {
|
private static void updateProjectionMatrices() {
|
||||||
|
|
||||||
projection.set(RenderSystem.getProjectionMatrix());
|
projection.set(RenderSystem.getProjectionMatrix());
|
||||||
|
inverseProjection.set(projection).invert();
|
||||||
inverseProjection
|
|
||||||
.set(projection)
|
|
||||||
.invert();
|
|
||||||
|
|
||||||
projectionBuffer.clear();
|
projectionBuffer.clear();
|
||||||
projection.get(projectionBuffer);
|
projection.get(projectionBuffer);
|
||||||
@@ -265,12 +434,8 @@ public class PostProcessingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void updateViewMatrix(Matrix4f matrix) {
|
public static void updateViewMatrix(Matrix4f matrix) {
|
||||||
|
|
||||||
view.set(matrix);
|
view.set(matrix);
|
||||||
|
inverseView.set(matrix).invert();
|
||||||
inverseView
|
|
||||||
.set(matrix)
|
|
||||||
.invert();
|
|
||||||
|
|
||||||
viewBuffer.clear();
|
viewBuffer.clear();
|
||||||
view.get(viewBuffer);
|
view.get(viewBuffer);
|
||||||
@@ -278,5 +443,4 @@ public class PostProcessingManager {
|
|||||||
inverseViewBuffer.clear();
|
inverseViewBuffer.clear();
|
||||||
inverseView.get(inverseViewBuffer);
|
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.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
import su.divan2000.veila.client.render.PostProcessingManager;
|
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)
|
@Mixin(GameRenderer.class)
|
||||||
public class GameRendererMixin {
|
public class GameRendererMixin {
|
||||||
@@ -27,7 +27,7 @@ public class GameRendererMixin {
|
|||||||
CallbackInfo ci
|
CallbackInfo ci
|
||||||
) {
|
) {
|
||||||
PostProcessingManager.init();
|
PostProcessingManager.init();
|
||||||
PostProcessingManager.render();
|
PostProcessingManager.render(tickDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(
|
@Inject(
|
||||||
@@ -61,6 +61,9 @@ public class GameRendererMixin {
|
|||||||
boolean tick,
|
boolean tick,
|
||||||
CallbackInfo ci
|
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.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
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)
|
@Mixin(MinecraftClient.class)
|
||||||
public class MinecraftClientMixin {
|
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",
|
"compatibilityLevel": "JAVA_17",
|
||||||
"client": [
|
"client": [
|
||||||
"GameRendererMixin",
|
"GameRendererMixin",
|
||||||
"MinecraftClientMixin"
|
"MinecraftClientMixin",
|
||||||
|
"WorldChunkMixin",
|
||||||
|
"LightStorageMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "veila",
|
"id": "veila",
|
||||||
"version": "${version}",
|
"version": "${version}",
|
||||||
|
"accessWidener": "veila.accesswidener",
|
||||||
|
|
||||||
"name": "Veila",
|
"name": "Veila",
|
||||||
"description": "",
|
"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