diff --git a/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java b/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java index 065ece6..641d191 100644 --- a/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java +++ b/src/client/java/su/divan2000/veila/client/render/PostProcessingManager.java @@ -22,55 +22,61 @@ import java.nio.FloatBuffer; public class PostProcessingManager { - private static GLProgram program; + private static GLProgram raymarchProgram; + private static GLProgram compositeProgram; + private static GLProgram depthDownsampleProgram; - /* - * Матрицы - */ + private static int dd_depthSamplerLocation; + private static int dd_screenSizeLocation; + + // Фреймбуфер для scaled raymarching + private static int scaledFBO; + private static int scaledTexture; + private static int scaledWidth; + private static int scaledHeight; + private static final float scale = 0.5f; private static final Matrix4f projection = new Matrix4f(); private static final Matrix4f inverseProjection = new Matrix4f(); - - private static final FloatBuffer projectionBuffer = - BufferUtils.createFloatBuffer(16); - - private static final FloatBuffer inverseProjectionBuffer = - BufferUtils.createFloatBuffer(16); + private static final FloatBuffer projectionBuffer = BufferUtils.createFloatBuffer(16); + private static final FloatBuffer inverseProjectionBuffer = BufferUtils.createFloatBuffer(16); /* - * 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_lightVolumeLocation; + private static int rm_fogOriginLocation; + private static int rm_ringOffsetLocation; + private static int rm_cameraPositionLocation; + private static int rm_screenSizeLocation; + private static int rm_viewLocation; + private static int rm_inverseViewLocation; + private static int rm_fogColorLocation; + private static int rm_skyBrightnessLocation; - private static int projectionLocation; - private static int inverseProjectionLocation; + /* + * Uniform locations - Composite Program (JBU) + */ + private static int comp_diffuseSamplerLocation; + private static int comp_fogSamplerLocation; + private static int comp_depthSamplerLocation; + private static int comp_scaledDepthSamplerLocation; + private static int comp_screenSizeLocation; + private static int comp_scaledScreenSizeLocation; + private static int comp_inverseProjectionLocation; - private static int diffuseSamplerLocation; - private static int depthSamplerLocation; - - private static int cameraPositionLocation; - private static int screenSizeLocation; + private static int scaledDepthTexture; private static final Matrix4f view = new Matrix4f(); private static final Matrix4f inverseView = new Matrix4f(); - - private static final FloatBuffer viewBuffer = - BufferUtils.createFloatBuffer(16); - - private static final FloatBuffer inverseViewBuffer = - BufferUtils.createFloatBuffer(16); - - private static int viewLocation; - private static int inverseViewLocation; - - private static int fogVolumeLocation; - private static int lightVolumeLocation; - - private static int fogOriginLocation; - private static int ringOffsetLocation; + private static final FloatBuffer viewBuffer = BufferUtils.createFloatBuffer(16); + private static final FloatBuffer inverseViewBuffer = BufferUtils.createFloatBuffer(16); private static float fogR, fogG, fogB; - private static int fogColorLocation; - private static int skyBrightnessLocation; public static void setFogColor(float r, float g, float b) { fogR = r; @@ -79,56 +85,116 @@ public class PostProcessingManager { } public static void init() { - - if (program != null) + if (raymarchProgram != null) return; setFogColor(0.75f, 0.80f, 0.90f); - GLShader vertex = new GLShader( + // Инициализация raymarch программы + GLShader rmVertex = new GLShader( GL20.GL_VERTEX_SHADER, ResourceUtil.load("shaders/fullscreen.vert") ); - - GLShader fragment = new GLShader( + GLShader rmFragment = new GLShader( GL20.GL_FRAGMENT_SHADER, ResourceUtil.load("shaders/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_lightVolumeLocation = raymarchProgram.uniform("LightVolume"); + rm_fogOriginLocation = raymarchProgram.uniform("FogOrigin"); + rm_ringOffsetLocation = raymarchProgram.uniform("RingBlockOffset"); + rm_cameraPositionLocation = raymarchProgram.uniform("CameraPosition"); + rm_screenSizeLocation = raymarchProgram.uniform("ScreenSize"); + rm_viewLocation = raymarchProgram.uniform("View"); + rm_inverseViewLocation = raymarchProgram.uniform("InverseView"); + rm_fogColorLocation = raymarchProgram.uniform("FogColor"); + rm_skyBrightnessLocation = raymarchProgram.uniform("SkyBrightness"); - projectionLocation = program.uniform("Projection"); - inverseProjectionLocation = program.uniform("InverseProjection"); + rmVertex.delete(); + rmFragment.delete(); - diffuseSamplerLocation = program.uniform("DiffuseSampler"); - depthSamplerLocation = program.uniform("DepthSampler"); + // Инициализация composite программы с JBU + GLShader compVertex = new GLShader( + GL20.GL_VERTEX_SHADER, + ResourceUtil.load("shaders/fullscreen.vert") + ); + GLShader compFragment = new GLShader( + GL20.GL_FRAGMENT_SHADER, + ResourceUtil.load("shaders/fog_composite.frag") + ); + compositeProgram = new GLProgram(compVertex, compFragment); - fogVolumeLocation = program.uniform("FogVolume"); - lightVolumeLocation = program.uniform("LightVolume"); + comp_diffuseSamplerLocation = compositeProgram.uniform("DiffuseSampler"); + comp_fogSamplerLocation = compositeProgram.uniform("FogSampler"); + comp_depthSamplerLocation = compositeProgram.uniform("DepthSampler"); + comp_scaledDepthSamplerLocation = compositeProgram.uniform("ScaledDepthSampler"); + comp_screenSizeLocation = compositeProgram.uniform("ScreenSize"); + comp_scaledScreenSizeLocation = compositeProgram.uniform("ScaledScreenSize"); + comp_inverseProjectionLocation = compositeProgram.uniform("InverseProjection"); - fogOriginLocation = program.uniform("FogOrigin"); - ringOffsetLocation = program.uniform("RingBlockOffset"); + compVertex.delete(); + compFragment.delete(); - cameraPositionLocation = program.uniform("CameraPosition"); - screenSizeLocation = program.uniform("ScreenSize"); + GLShader ddVertex = new GLShader( + GL20.GL_VERTEX_SHADER, + ResourceUtil.load("shaders/fullscreen.vert") + ); + GLShader ddFragment = new GLShader( + GL20.GL_FRAGMENT_SHADER, + ResourceUtil.load("shaders/depth_downsample.frag") + ); + depthDownsampleProgram = new GLProgram(ddVertex, ddFragment); - viewLocation = program.uniform("View"); - inverseViewLocation = program.uniform("InverseView"); + dd_depthSamplerLocation = depthDownsampleProgram.uniform("DepthSampler"); + dd_screenSizeLocation = depthDownsampleProgram.uniform("ScreenSize"); - fogColorLocation = program.uniform("FogColor"); - skyBrightnessLocation = program.uniform("SkyBrightness"); + ddVertex.delete(); + ddFragment.delete(); FullscreenQuad.init(); - vertex.delete(); - fragment.delete(); + System.out.println("VEILA shaders compiled!"); + } - System.out.println("VEILA shader compiled!"); + private static void initScaledFramebuffer(int width, int height) { + scaledWidth = (int)(width * scale); + scaledHeight = (int)(height * scale); + + scaledFBO = GL30.glGenFramebuffers(); + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, scaledFBO); + + scaledTexture = GL11.glGenTextures(); + GL13.glActiveTexture(GL13.GL_TEXTURE0); + 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); + + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0); } public static void render(float tickDelta) { - - if (program == null) + if (raymarchProgram == null) return; MinecraftClient client = MinecraftClient.getInstance(); @@ -141,174 +207,176 @@ public class PostProcessingManager { int width = framebuffer.textureWidth; int height = framebuffer.textureHeight; - GL30.glBindFramebuffer( - GL30.GL_READ_FRAMEBUFFER, - framebuffer.fbo - ); + // Инициализация scaled фреймбуфера + if (scaledFBO == 0 || scaledWidth != (int)(width * scale) || scaledHeight != (int)(height * scale)) { + if (scaledFBO != 0) { + GL30.glDeleteFramebuffers(scaledFBO); + GL11.glDeleteTextures(scaledTexture); + GL11.glDeleteTextures(scaledDepthTexture); + } + initScaledFramebuffer(width, height); + } + + GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, framebuffer.fbo); DepthCopy.init(width, height); DepthCopy.copy(width, height); - program.bind(); + renderDepthDownsample(width, height); - /* - * Diffuse - */ + // === Этап 1: Raymarching в scaled разрешении === + renderRaymarching(tickDelta, client); - GL13.glActiveTexture(GL13.GL_TEXTURE0); - GL11.glBindTexture( - GL11.GL_TEXTURE_2D, - framebuffer.getColorAttachment() - ); - GL20.glUniform1i(diffuseSamplerLocation, 0); - - /* - * Depth - */ - - GL13.glActiveTexture(GL13.GL_TEXTURE1); - GL11.glBindTexture( - GL11.GL_TEXTURE_2D, - DepthCopy.getTexture() - ); - GL20.glUniform1i(depthSamplerLocation, 1); - - GL13.glActiveTexture(GL13.GL_TEXTURE2); - - GL11.glBindTexture( - GL12.GL_TEXTURE_3D, - FogSimulationManager.getCurrentDensityTexture() - ); - - GL20.glUniform1i( - fogVolumeLocation, - 2 - ); - - GL13.glActiveTexture(GL13.GL_TEXTURE3); - GL11.glBindTexture( - GL12.GL_TEXTURE_3D, - FogLightVolume.getTexture() - ); - GL20.glUniform1i(lightVolumeLocation, 3); - - GL20.glUniform2i( - fogOriginLocation, - FogSystem.originChunkX() * FogWorldVolume.CHUNK_SIZE, - FogSystem.originChunkZ() * FogWorldVolume.CHUNK_SIZE - ); - - GL20.glUniform2i( - ringOffsetLocation, - FogSystem.ringChunkOffsetX() * FogWorldVolume.CHUNK_SIZE, - FogSystem.ringChunkOffsetZ() * FogWorldVolume.CHUNK_SIZE - ); - - /* - * Матрицы - */ - - updateProjectionMatrices(); - - GL20.glUniformMatrix4fv( - projectionLocation, - false, - projectionBuffer - ); - - GL20.glUniformMatrix4fv( - inverseProjectionLocation, - false, - inverseProjectionBuffer - ); - - GL20.glUniformMatrix4fv( - viewLocation, - false, - viewBuffer - ); - - GL20.glUniformMatrix4fv( - inverseViewLocation, - false, - inverseViewBuffer - ); - - /* - * Камера - */ - - Camera camera = client.gameRenderer.getCamera(); - Vec3d pos = camera.getPos(); - - GL20.glUniform3f( - cameraPositionLocation, - (float) pos.x, - (float) pos.y, - (float) pos.z - ); - - /* - * Размер экрана - */ - - GL20.glUniform2f( - screenSizeLocation, - width, - height - ); - - /* - * Цвет тумана - */ - - GL20.glUniform3f( - fogColorLocation, - fogR, - fogG, - fogB - ); - - /* - * Яркость тумана - */ - - GL20.glUniform1f( - skyBrightnessLocation, - client.world.getSkyBrightness(tickDelta) - ); - - FullscreenQuad.draw(); - - GL13.glActiveTexture(GL13.GL_TEXTURE3); - GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); - - GL13.glActiveTexture(GL13.GL_TEXTURE2); - GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); - - GL13.glActiveTexture(GL13.GL_TEXTURE1); - GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); - - GL13.glActiveTexture(GL13.GL_TEXTURE0); - GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); - - program.unbind(); - - GL30.glBindFramebuffer( - GL30.GL_READ_FRAMEBUFFER, - 0 - ); + // === Этап 2: Композитинг с JBU в полном разрешении === + renderComposite(framebuffer, width, height); + GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0); GL13.glActiveTexture(GL13.GL_TEXTURE0); } + private static void renderDepthDownsample(int width, int height) { + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, scaledFBO); + + // Настраиваем draw buffers для MRT + GL30.glDrawBuffer(GL30.GL_COLOR_ATTACHMENT1); + + GL11.glViewport(0, 0, scaledWidth, scaledHeight); + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); + + depthDownsampleProgram.bind(); + + GL13.glActiveTexture(GL13.GL_TEXTURE0); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, DepthCopy.getTexture()); + GL20.glUniform1i(dd_depthSamplerLocation, 0); + + GL20.glUniform2f(dd_screenSizeLocation, width, height); + + FullscreenQuad.draw(); + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + 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); + + // Light Volume + GL13.glActiveTexture(GL13.GL_TEXTURE2); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, FogLightVolume.getTexture()); + GL20.glUniform1i(rm_lightVolumeLocation, 2); + + 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); + + // Матрицы + updateProjectionMatrices(); + 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(); + GL20.glUniform3f(rm_cameraPositionLocation, (float) pos.x, (float) pos.y, (float) pos.z); + + // Размер экрана (scaled) + GL20.glUniform2f(rm_screenSizeLocation, scaledWidth, scaledHeight); + + // Цвет тумана + GL20.glUniform3f(rm_fogColorLocation, fogR, fogG, fogB); + + // Яркость неба + GL20.glUniform1f(rm_skyBrightnessLocation, client.world.getSkyBrightness(tickDelta)); + + FullscreenQuad.draw(); + + // Очистка текстур + GL13.glActiveTexture(GL13.GL_TEXTURE2); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + GL13.glActiveTexture(GL13.GL_TEXTURE1); + GL11.glBindTexture(GL12.GL_TEXTURE_3D, 0); + GL13.glActiveTexture(GL13.GL_TEXTURE0); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + + raymarchProgram.unbind(); + } + + private static void renderComposite(Framebuffer framebuffer, int width, int height) { + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer.fbo); + GL11.glViewport(0, 0, width, height); + + compositeProgram.bind(); + + // Original scene (Diffuse) + GL13.glActiveTexture(GL13.GL_TEXTURE0); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, framebuffer.getColorAttachment()); + GL20.glUniform1i(comp_diffuseSamplerLocation, 0); + + // Scaled fog results + GL13.glActiveTexture(GL13.GL_TEXTURE1); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledTexture); + GL20.glUniform1i(comp_fogSamplerLocation, 1); + + // Full-res depth для JBU + GL13.glActiveTexture(GL13.GL_TEXTURE2); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, DepthCopy.getTexture()); + GL20.glUniform1i(comp_depthSamplerLocation, 2); + + // Half-res depth для JBU + GL13.glActiveTexture(GL13.GL_TEXTURE3); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, scaledDepthTexture); + GL20.glUniform1i(comp_scaledDepthSamplerLocation, 3); + + // Матрицы для линеаризации depth + updateProjectionMatrices(); + GL20.glUniformMatrix4fv(comp_inverseProjectionLocation, false, inverseProjectionBuffer); + + // Screen size + GL20.glUniform2f(comp_screenSizeLocation, width, height); + GL20.glUniform2f(comp_scaledScreenSizeLocation, scaledWidth, scaledHeight); + + FullscreenQuad.draw(); + + // Очистка + GL13.glActiveTexture(GL13.GL_TEXTURE3); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + GL13.glActiveTexture(GL13.GL_TEXTURE2); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + GL13.glActiveTexture(GL13.GL_TEXTURE1); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + GL13.glActiveTexture(GL13.GL_TEXTURE0); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + + compositeProgram.unbind(); + } + private static void updateProjectionMatrices() { - projection.set(RenderSystem.getProjectionMatrix()); - - inverseProjection - .set(projection) - .invert(); + inverseProjection.set(projection).invert(); projectionBuffer.clear(); projection.get(projectionBuffer); @@ -318,12 +386,8 @@ public class PostProcessingManager { } public static void updateViewMatrix(Matrix4f matrix) { - view.set(matrix); - - inverseView - .set(matrix) - .invert(); + inverseView.set(matrix).invert(); viewBuffer.clear(); view.get(viewBuffer); @@ -331,5 +395,4 @@ public class PostProcessingManager { inverseViewBuffer.clear(); inverseView.get(inverseViewBuffer); } - } \ No newline at end of file diff --git a/src/client/resources/assets/veila/shaders/depth_downsample.frag b/src/client/resources/assets/veila/shaders/depth_downsample.frag new file mode 100644 index 0000000..333d7b7 --- /dev/null +++ b/src/client/resources/assets/veila/shaders/depth_downsample.frag @@ -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); +} \ No newline at end of file diff --git a/src/client/resources/assets/veila/shaders/fog_composite.frag b/src/client/resources/assets/veila/shaders/fog_composite.frag new file mode 100644 index 0000000..a388866 --- /dev/null +++ b/src/client/resources/assets/veila/shaders/fog_composite.frag @@ -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); +} \ No newline at end of file diff --git a/src/client/resources/assets/veila/shaders/fog_raymarching.frag b/src/client/resources/assets/veila/shaders/fog_raymarching.frag index 27645ec..3af8207 100644 --- a/src/client/resources/assets/veila/shaders/fog_raymarching.frag +++ b/src/client/resources/assets/veila/shaders/fog_raymarching.frag @@ -1,6 +1,5 @@ #version 150 -uniform sampler2D DiffuseSampler; uniform sampler2D DepthSampler; uniform sampler3D FogVolume; uniform sampler3D LightVolume; @@ -38,22 +37,9 @@ const float GAMMA = 2.0; const bool USE_JITTERING = false; -// Физические коэффициенты volumetric rendering -// EXTINCTION: насколько сильно туман поглощает/рассеивает свет (0.1-2.0) -// Больше значение = туман сильнее затеняет далёкие объекты const float EXTINCTION_COEFF = 1.0; - -// SCATTERING: насколько сильно туман сам светится от источников -// Обычно равен EXTINCTION (консервативная среда), но можно варьировать const float SCATTERING_COEFF = 1.0; - -// Ambient — базовое освещение среды (воздух, микрочастицы). -// Не зависит от времени суток и от skyLight/blockLight. -// Даёт минимальную видимость тумана даже в полной темноте. const float AMBIENT_INTENSITY = 0.10; - -// Порог transmittance для early termination (оптимизация) -// Если transmittance упал ниже этого значения, дальнейшие шаги ничего не внесут const float MIN_TRANSMITTANCE = 0.01; //------------------------------------------------------------ @@ -145,8 +131,6 @@ float mapParameterToDistance(float t) void main() { - vec4 sceneColor = texture(DiffuseSampler, texCoord); - vec3 worldPos = reconstructWorldPosition(texCoord); vec3 rayDir = normalize(worldPos - CameraPosition); @@ -156,14 +140,10 @@ void main() vec3 samplePos = CameraPosition; float currentDistance = 0.0; - // Transmittance: сколько света от сцены дошло до текущего шага - // Начинается с 1.0 (всё доходит) и экспоненциально падает float transmittance = 1.0; - // Накопленный in-scattered свет (взвешенный по transmittance) float skyScattered = 0.0; float blockScattered = 0.0; - float ambientScattered = 0.0; float jitter = 0.0; @@ -192,36 +172,25 @@ void main() float d = density(samplePos); - // Оптическая толщина этого шага float opticalDepth = d * stepSize * EXTINCTION_COEFF; if (d > 0.0) { - // Получаем освещение в текущей точке vec2 lightValues = light(samplePos); float totalLightAtPoint = lightValues.x + lightValues.y; - // Базовая формула in-scattering для каждого источника float baseScattering = d * stepSize * SCATTERING_COEFF; - // In-scattering: свет, рассеянный в камерy из этого шага. - // Умножается на transmittance до этого шага (дальний туман затенён ближним). - // Умножается на плотность (больше частиц = больше рассеяния). float stepScattering = totalLightAtPoint * d * stepSize * SCATTERING_COEFF; - // Вклад ослабляется текущим transmittance skyScattered += lightValues.x * baseScattering * transmittance; - //Свет от блоков не должен делать значимо ярче те воксели, которые и так подсвечены солнцем. Солнечный свет очень яркий, с ним сложно сравниться - blockScattered += lightValues.y * baseScattering * transmittance*((lightValues.y-lightValues.x)*0.9+0.1); + blockScattered += lightValues.y * baseScattering * transmittance * ((lightValues.y - lightValues.x) * 0.9 + 0.1); ambientScattered += AMBIENT_INTENSITY * baseScattering * transmittance; - // Обновляем transmittance: Beer-Lambert law transmittance *= exp(-opticalDepth); } currentDistance = nextDistance; - // Early termination: если transmittance упал почти до нуля, - // дальние шаги ничего не внесут в итоговый цвет if (transmittance < MIN_TRANSMITTANCE) { transmittance = 0.0; break; @@ -232,18 +201,10 @@ void main() } } - // Суммарное освещение, рассеянное в камеру float totalScattered = skyScattered + blockScattered + ambientScattered; - // Финальный цвет: - // - sceneColor × transmittance: сцена, ослабленная туманом (Beer-Lambert) - // - FogColor × totalScattered: свет, который рассеялся в тумане и попал в камеру - // - // Эта формула физически корректна: - // - Когда тумана нет (transmittance=1, scattered=0): result = sceneColor ✓ - // - Когда туман очень плотный (transmittance=0): result = FogColor × scattered ✓ - // - Фонарь в толще тумана: ближние слои дают scattered, дальние уже не видны - vec3 finalColor = sceneColor.rgb * transmittance + FogColor * totalScattered; + // Выводим: RGB = вложенный свет тумана, A = transmittance + vec3 fogColor = FogColor * totalScattered; - FragColor = vec4(finalColor, 1.0); + FragColor = vec4(fogColor, transmittance); } \ No newline at end of file