diff --git a/common/src/main/java/foundry/veil/api/client/render/VeilRenderSystem.java b/common/src/main/java/foundry/veil/api/client/render/VeilRenderSystem.java index c51556457..dd218e19c 100644 --- a/common/src/main/java/foundry/veil/api/client/render/VeilRenderSystem.java +++ b/common/src/main/java/foundry/veil/api/client/render/VeilRenderSystem.java @@ -40,6 +40,7 @@ import foundry.veil.impl.client.render.pipeline.VeilShaderBufferCache; import foundry.veil.impl.client.render.profiler.VeilRenderProfilerImpl; import foundry.veil.impl.client.render.shader.program.ShaderProgramImpl; +import foundry.veil.impl.client.render.light.VoxelShadowGrid; import foundry.veil.mixin.pipeline.accessor.PipelineBufferSourceAccessor; import foundry.veil.platform.VeilEventPlatform; import net.minecraft.Util; @@ -1241,6 +1242,7 @@ public static void close() { if (renderer != null) { renderer.free(); } + VoxelShadowGrid.close(); glDeleteVertexArrays(screenQuadVao); MemoryUtil.memFree(emptySamplers); SHADER_BUFFER_CACHE.free(); @@ -1277,6 +1279,8 @@ public static boolean drawLights(ProfilerFiller profiler, CullFrustum cullFrustu return false; } + VoxelShadowGrid.beforeRenderLights(); + VeilDebug debug = VeilDebug.get(); debug.pushDebugGroup("Veil Draw Lights"); @@ -1322,5 +1326,6 @@ public static void compositeLights(ProfilerFiller profiler) { @ApiStatus.Internal public static void clearLevel() { NecromancerRenderDispatcher.delete(); + VoxelShadowGrid.clearLevel(); } } diff --git a/common/src/main/java/foundry/veil/api/client/render/ext/VeilMultiBind.java b/common/src/main/java/foundry/veil/api/client/render/ext/VeilMultiBind.java index c5a48a09e..b9d824389 100644 --- a/common/src/main/java/foundry/veil/api/client/render/ext/VeilMultiBind.java +++ b/common/src/main/java/foundry/veil/api/client/render/ext/VeilMultiBind.java @@ -151,6 +151,11 @@ public void bindSamplers(int first, int... samplers) { .expireAfterAccess(10, TimeUnit.SECONDS) .build(); + private static final Cache TEXTURE_TARGET_CACHE = CacheBuilder.newBuilder() + .maximumSize(100) + .expireAfterAccess(10, TimeUnit.SECONDS) + .build(); + private static int getTarget(int texture) { Integer cached = TEXTURE_TARGET_CACHE.getIfPresent(texture); if (cached != null) { @@ -238,4 +243,5 @@ public static VeilMultiBind get() { } return multiBind; } + } diff --git a/common/src/main/java/foundry/veil/api/client/render/light/data/AreaLightData.java b/common/src/main/java/foundry/veil/api/client/render/light/data/AreaLightData.java index 85ecec04d..6ee7889bc 100644 --- a/common/src/main/java/foundry/veil/api/client/render/light/data/AreaLightData.java +++ b/common/src/main/java/foundry/veil/api/client/render/light/data/AreaLightData.java @@ -31,6 +31,7 @@ public class AreaLightData extends LightData implements InstancedLightData, Edit protected float angle; protected float distance; + protected boolean occluded; public AreaLightData() { this.matrix = new Matrix4d(); @@ -41,6 +42,16 @@ public AreaLightData() { this.angle = (float) Math.toRadians(45); this.distance = 1.0F; + this.occluded = false; + } + + public boolean isOccluded() { + return this.occluded; + } + + public AreaLightData setOccluded(boolean occluded) { + this.occluded = occluded; + return this; } protected void updateMatrix() { @@ -163,6 +174,7 @@ public void store(ByteBuffer buffer) { buffer.putShort((short) Mth.clamp((int) (this.angle * MAX_ANGLE_SIZE), 0, 65535)); buffer.putFloat(this.distance); + buffer.putFloat(this.occluded ? 1.0F : 0.0F); } @Override @@ -201,6 +213,7 @@ public void renderImGuiAttributes() { float[] editAngle = new float[]{this.angle}; float[] editDistance = new float[]{this.distance}; + imgui.type.ImBoolean editOccluded = new imgui.type.ImBoolean(this.occluded); if (ImGui.dragFloat2("size", editSize, 0.02F, 0.0001F)) { this.setSize(editSize[0], editSize[1]); @@ -250,5 +263,9 @@ public void renderImGuiAttributes() { if (ImGui.dragScalar("distance", editDistance, 0.02F, 0.0F)) { this.setDistance(editDistance[0]); } + + if (ImGui.checkbox("Occluded", editOccluded)) { + this.occluded = editOccluded.get(); + } } } diff --git a/common/src/main/java/foundry/veil/api/client/render/light/data/PointLightData.java b/common/src/main/java/foundry/veil/api/client/render/light/data/PointLightData.java index dfb45ac3c..f8b563c58 100644 --- a/common/src/main/java/foundry/veil/api/client/render/light/data/PointLightData.java +++ b/common/src/main/java/foundry/veil/api/client/render/light/data/PointLightData.java @@ -23,10 +23,21 @@ public class PointLightData extends LightData implements IndirectLightData, Edit protected final Vector3d position; protected float radius; + protected boolean occluded; public PointLightData() { this.position = new Vector3d(); this.radius = 1.0F; + this.occluded = false; + } + + public boolean isOccluded() { + return this.occluded; + } + + public PointLightData setOccluded(boolean occluded) { + this.occluded = occluded; + return this; } @Override @@ -97,6 +108,7 @@ public void store(ByteBuffer buffer) { buffer.putFloat(this.color.green() * this.brightness); buffer.putFloat(this.color.blue() * this.brightness); buffer.putFloat(this.radius); + buffer.putFloat(this.occluded ? 1.0F : 0.0F); } @Override @@ -118,6 +130,7 @@ public void renderImGuiAttributes() { double[] editZ = new double[]{this.position.z()}; float[] editRadius = new float[]{this.radius}; + imgui.type.ImBoolean editOccluded = new imgui.type.ImBoolean(this.occluded); float totalWidth = ImGui.calcItemWidth(); ImGui.pushItemWidth(totalWidth / 3.0F - (ImGui.getStyle().getItemInnerSpacingX() * 0.58F)); @@ -140,5 +153,9 @@ public void renderImGuiAttributes() { if (ImGui.dragScalar("radius", editRadius, 0.02F, 0.0F)) { this.setRadius(editRadius[0]); } + + if (ImGui.checkbox("Occluded", editOccluded)) { + this.occluded = editOccluded.get(); + } } } diff --git a/common/src/main/java/foundry/veil/impl/client/render/light/AreaLightRenderer.java b/common/src/main/java/foundry/veil/impl/client/render/light/AreaLightRenderer.java index 80169769c..7813338b0 100644 --- a/common/src/main/java/foundry/veil/impl/client/render/light/AreaLightRenderer.java +++ b/common/src/main/java/foundry/veil/impl/client/render/light/AreaLightRenderer.java @@ -25,7 +25,7 @@ public class AreaLightRenderer extends InstancedLightRenderer { private static final ResourceLocation RENDER_TYPE = Veil.veilPath("light/area"); public AreaLightRenderer() { - super(Float.BYTES * 22 + 2); + super(Float.BYTES * 23 + 2); } @Override @@ -45,6 +45,7 @@ protected void setupBufferState(VertexArrayBuilder builder) { builder.setVertexAttribute(6, 2, 2, VertexArrayBuilder.DataType.FLOAT, false, Float.BYTES * 19); // size builder.setVertexAttribute(7, 2, 1, VertexArrayBuilder.DataType.UNSIGNED_SHORT, true, Float.BYTES * 21); // angle builder.setVertexAttribute(8, 2, 1, VertexArrayBuilder.DataType.FLOAT, false, Float.BYTES * 21 + 2); // distance + builder.setVertexAttribute(9, 2, 1, VertexArrayBuilder.DataType.FLOAT, false, Float.BYTES * 21 + 2 + Float.BYTES); } @Override diff --git a/common/src/main/java/foundry/veil/impl/client/render/light/InstancedPointLightRenderer.java b/common/src/main/java/foundry/veil/impl/client/render/light/InstancedPointLightRenderer.java index d150d44a3..c781d945b 100644 --- a/common/src/main/java/foundry/veil/impl/client/render/light/InstancedPointLightRenderer.java +++ b/common/src/main/java/foundry/veil/impl/client/render/light/InstancedPointLightRenderer.java @@ -25,7 +25,7 @@ public class InstancedPointLightRenderer extends InstancedLightRenderer gridDimension; + private static int originX, originY, originZ; + private static ByteBuffer gridBuffer; + + private static ResourceKey buildDimension; + private static int buildOriginX, buildOriginY, buildOriginZ; + private static int buildIndex; + private static ByteBuffer buildBuffer; + + private static final Object DIRTY_LOCK = new Object(); + private static final LongArrayFIFOQueue DIRTY_QUEUE = new LongArrayFIFOQueue(); + private static final LongOpenHashSet DIRTY_SET = new LongOpenHashSet(); + private static final long[] DRAIN_SCRATCH = new long[MAX_DIRTY_UPDATES_PER_FRAME]; + private static boolean rebuildRequested; + + private VoxelShadowGrid() { + } + + public static void beforeRenderLights() { + RenderSystem.assertOnRenderThread(); + + Minecraft client = Minecraft.getInstance(); + ClientLevel level = client.level; + if (level == null) { + return; + } + + ensureTexture(); + + if (gridDimension != null && !Objects.equals(gridDimension, level.dimension())) { + clearLevel(); + } + + Vec3 cameraPos = client.gameRenderer.getMainCamera().getPosition(); + int cx = (int) Math.floor(cameraPos.x); + int cy = (int) Math.floor(cameraPos.y); + int cz = (int) Math.floor(cameraPos.z); + + if (hasOccludedLights()) { + if (rebuildRequested) { + rebuildRequested = false; + clearDirty(); + startFullBuild(level, cx, cy, cz); + } else if (buildBuffer != null) { + int maxDelta = Math.max( + Math.abs(cx - (buildOriginX + HALF)), + Math.max(Math.abs(cy - (buildOriginY + HALF)), Math.abs(cz - (buildOriginZ + HALF))) + ); + if (!Objects.equals(buildDimension, level.dimension()) || maxDelta >= HALF) { + startFullBuild(level, cx, cy, cz); + } + } else if (gridBuffer == null) { + startFullBuild(level, cx, cy, cz); + } + + if (buildBuffer != null) { + continueFullBuild(level); + } else { + shiftTowards(level, cx, cy, cz); + } + } + + if (applyDirtyUpdates(level)) { + uploadBuffer(gridBuffer); + } + + pushUniforms(level, cx, cy, cz); + } + + public static void markBlockDirty(BlockPos pos) { + long packed = pos.asLong(); + synchronized (DIRTY_LOCK) { + if (!DIRTY_SET.add(packed)) { + return; + } + DIRTY_QUEUE.enqueue(packed); + if (DIRTY_QUEUE.size() > MAX_DIRTY_BACKLOG) { + rebuildRequested = true; + clearDirty(); + } + } + } + + public static void clearLevel() { + RenderSystem.assertOnRenderThreadOrInit(); + + gridDimension = null; + if (gridBuffer != null) { + MemoryUtil.memFree(gridBuffer); + gridBuffer = null; + } + + buildDimension = null; + buildIndex = 0; + if (buildBuffer != null) { + MemoryUtil.memFree(buildBuffer); + buildBuffer = null; + } + + clearDirty(); + } + + public static void close() { + RenderSystem.assertOnRenderThreadOrInit(); + clearLevel(); + if (textureId != 0) { + glDeleteTextures(textureId); + textureId = 0; + } + } + + private static void clearDirty() { + synchronized (DIRTY_LOCK) { + DIRTY_QUEUE.clear(); + DIRTY_SET.clear(); + } + } + + private static void startFullBuild(ClientLevel level, int cx, int cy, int cz) { + buildDimension = level.dimension(); + buildOriginX = cx - HALF; + buildOriginY = cy - HALF; + buildOriginZ = cz - HALF; + buildIndex = 0; + if (buildBuffer == null) { + buildBuffer = MemoryUtil.memAlloc(GRID_VOLUME); + } + } + + private static void continueFullBuild(ClientLevel level) { + if (!Objects.equals(buildDimension, level.dimension())) { + MemoryUtil.memFree(buildBuffer); + buildBuffer = null; + buildDimension = null; + buildIndex = 0; + return; + } + + long deadline = System.nanoTime() + BUILD_BUDGET_NS; + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + while (buildIndex < GRID_VOLUME && System.nanoTime() < deadline) { + int lx = buildIndex & 63; + int ly = (buildIndex >> 6) & 63; + int lz = buildIndex >> 12; + pos.set(buildOriginX + lx, buildOriginY + ly, buildOriginZ + lz); + BlockState state = level.getBlockState(pos); + buildBuffer.put(buildIndex, voxelOccupancy(level, pos, state)); + buildIndex++; + } + + if (buildIndex < GRID_VOLUME) { + return; + } + + if (gridBuffer != null) { + MemoryUtil.memFree(gridBuffer); + } + gridBuffer = buildBuffer; + gridDimension = buildDimension; + originX = buildOriginX; + originY = buildOriginY; + originZ = buildOriginZ; + + buildBuffer = null; + buildDimension = null; + buildIndex = 0; + + uploadBuffer(gridBuffer); + } + + private static void shiftTowards(ClientLevel level, int cx, int cy, int cz) { + if (gridBuffer == null || !Objects.equals(gridDimension, level.dimension())) { + return; + } + + int dx = cx - (originX + HALF); + int dy = cy - (originY + HALF); + int dz = cz - (originZ + HALF); + + if (Math.max(Math.abs(dx), Math.max(Math.abs(dy), Math.abs(dz))) >= HALF) { + startFullBuild(level, cx, cy, cz); + return; + } + + int steps = 0; + boolean changed = false; + while (steps < MAX_SLICE_UPDATES_PER_FRAME && (dx != 0 || dy != 0 || dz != 0)) { + int ax = Math.abs(dx), ay = Math.abs(dy), az = Math.abs(dz); + + if (dx != 0 && ax >= ay && ax >= az) { + if (dx > 0) { shiftXPositive(level); dx--; } + else { shiftXNegative(level); dx++; } + } else if (dz != 0 && az >= ay) { + if (dz > 0) { shiftZPositive(level); dz--; } + else { shiftZNegative(level); dz++; } + } else if (dy != 0) { + if (dy > 0) { shiftYPositive(level); dy--; } + else { shiftYNegative(level); dy++; } + } else { + break; + } + + changed = true; + steps++; + } + + if (changed) { + uploadBuffer(gridBuffer); + } + } + + private static boolean applyDirtyUpdates(ClientLevel level) { + if (gridBuffer == null && buildBuffer == null) { + clearDirty(); + return false; + } + + int toDrain; + synchronized (DIRTY_LOCK) { + toDrain = Math.min(DIRTY_QUEUE.size(), MAX_DIRTY_UPDATES_PER_FRAME); + for (int i = 0; i < toDrain; i++) { + DRAIN_SCRATCH[i] = DIRTY_QUEUE.dequeueLong(); + DIRTY_SET.remove(DRAIN_SCRATCH[i]); + } + } + + boolean updatedGrid = false; + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + for (int i = 0; i < toDrain; i++) { + long packed = DRAIN_SCRATCH[i]; + int x = BlockPos.getX(packed); + int y = BlockPos.getY(packed); + int z = BlockPos.getZ(packed); + pos.set(x, y, z); + byte occupancy = voxelOccupancy(level, pos, level.getBlockState(pos)); + + if (buildBuffer != null && Objects.equals(buildDimension, level.dimension())) { + int bx = x - buildOriginX, by = y - buildOriginY, bz = z - buildOriginZ; + if ((bx | by | bz) >= 0 && bx < GRID_SIZE && by < GRID_SIZE && bz < GRID_SIZE) { + buildBuffer.put(bx + by * GRID_SIZE + bz * SLICE_AREA, occupancy); + } + } + + if (gridBuffer != null && Objects.equals(gridDimension, level.dimension())) { + int gx = x - originX, gy = y - originY, gz = z - originZ; + if ((gx | gy | gz) >= 0 && gx < GRID_SIZE && gy < GRID_SIZE && gz < GRID_SIZE) { + gridBuffer.put(gx + gy * GRID_SIZE + gz * SLICE_AREA, occupancy); + updatedGrid = true; + } + } + } + + return updatedGrid; + } + + private static void shiftXPositive(ClientLevel level) { + originX++; + long base = MemoryUtil.memAddress(gridBuffer); + for (int z = 0; z < GRID_SIZE; z++) { + for (int y = 0; y < GRID_SIZE; y++) { + long row = base + (long) z * SLICE_AREA + (long) y * GRID_SIZE; + MemoryUtil.memCopy(row + 1, row, GRID_SIZE - 1); + } + } + fillSliceX(level, GRID_SIZE - 1, originX + GRID_SIZE - 1, base); + } + + private static void shiftXNegative(ClientLevel level) { + originX--; + long base = MemoryUtil.memAddress(gridBuffer); + for (int z = 0; z < GRID_SIZE; z++) { + for (int y = 0; y < GRID_SIZE; y++) { + long row = base + (long) z * SLICE_AREA + (long) y * GRID_SIZE; + MemoryUtil.memCopy(row, row + 1, GRID_SIZE - 1); + } + } + fillSliceX(level, 0, originX, base); + } + + private static void fillSliceX(ClientLevel level, int writeX, int worldX, long base) { + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + for (int z = 0; z < GRID_SIZE; z++) { + for (int y = 0; y < GRID_SIZE; y++) { + pos.set(worldX, originY + y, originZ + z); + BlockState state = level.getBlockState(pos); + MemoryUtil.memPutByte(base + (long) z * SLICE_AREA + (long) y * GRID_SIZE + writeX, voxelOccupancy(level, pos, state)); + } + } + } + + private static void shiftYPositive(ClientLevel level) { + originY++; + long base = MemoryUtil.memAddress(gridBuffer); + for (int z = 0; z < GRID_SIZE; z++) { + long plane = base + (long) z * SLICE_AREA; + MemoryUtil.memCopy(plane + GRID_SIZE, plane, (long) GRID_SIZE * (GRID_SIZE - 1)); + } + fillSliceY(level, GRID_SIZE - 1, originY + GRID_SIZE - 1, base); + } + + private static void shiftYNegative(ClientLevel level) { + originY--; + long base = MemoryUtil.memAddress(gridBuffer); + for (int z = 0; z < GRID_SIZE; z++) { + long plane = base + (long) z * SLICE_AREA; + MemoryUtil.memCopy(plane, plane + GRID_SIZE, (long) GRID_SIZE * (GRID_SIZE - 1)); + } + fillSliceY(level, 0, originY, base); + } + + private static void fillSliceY(ClientLevel level, int writeY, int worldY, long base) { + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + for (int z = 0; z < GRID_SIZE; z++) { + long row = base + (long) z * SLICE_AREA + (long) writeY * GRID_SIZE; + for (int x = 0; x < GRID_SIZE; x++) { + pos.set(originX + x, worldY, originZ + z); + BlockState state = level.getBlockState(pos); + MemoryUtil.memPutByte(row + x, voxelOccupancy(level, pos, state)); + } + } + } + + private static void shiftZPositive(ClientLevel level) { + originZ++; + long base = MemoryUtil.memAddress(gridBuffer); + MemoryUtil.memCopy(base + SLICE_AREA, base, (long) SLICE_AREA * (GRID_SIZE - 1)); + fillSliceZ(level, GRID_SIZE - 1, originZ + GRID_SIZE - 1, base); + } + + private static void shiftZNegative(ClientLevel level) { + originZ--; + long base = MemoryUtil.memAddress(gridBuffer); + MemoryUtil.memCopy(base, base + SLICE_AREA, (long) SLICE_AREA * (GRID_SIZE - 1)); + fillSliceZ(level, 0, originZ, base); + } + + private static void fillSliceZ(ClientLevel level, int writeZ, int worldZ, long base) { + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + long plane = base + (long) writeZ * SLICE_AREA; + for (int y = 0; y < GRID_SIZE; y++) { + long row = plane + (long) y * GRID_SIZE; + for (int x = 0; x < GRID_SIZE; x++) { + pos.set(originX + x, originY + y, worldZ); + BlockState state = level.getBlockState(pos); + MemoryUtil.memPutByte(row + x, voxelOccupancy(level, pos, state)); + } + } + } + + private static byte voxelOccupancy(ClientLevel level, BlockPos pos, BlockState state) { + if (!state.canOcclude()) return 0; + if (!state.getFluidState().isEmpty()) return 0; + return state.isSolidRender(level, pos) ? (byte) 0xFF : 0; + } + + private static void uploadBuffer(ByteBuffer buffer) { + if (buffer == null) { + return; + } + buffer.rewind(); + glBindTexture(GL_TEXTURE_3D, textureId); + glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, GRID_SIZE, GRID_SIZE, GRID_SIZE, GL_RED, GL_UNSIGNED_BYTE, buffer); + glBindTexture(GL_TEXTURE_3D, 0); + } + + private static void ensureTexture() { + if (textureId != 0) { + return; + } + textureId = glGenTextures(); + glBindTexture(GL_TEXTURE_3D, textureId); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + ByteBuffer zeros = MemoryUtil.memCalloc(GRID_VOLUME); + glTexImage3D(GL_TEXTURE_3D, 0, GL_R8, GRID_SIZE, GRID_SIZE, GRID_SIZE, 0, GL_RED, GL_UNSIGNED_BYTE, zeros); + MemoryUtil.memFree(zeros); + glBindTexture(GL_TEXTURE_3D, 0); + } + + private static boolean hasOccludedLights() { + LightRenderer renderer = VeilRenderSystem.renderer().getLightRenderer(); + for (LightRenderHandle handle : renderer.getLights(LightTypeRegistry.POINT.get())) { + if (handle.getLightData().isOccluded()) return true; + } + for (LightRenderHandle handle : renderer.getLights(LightTypeRegistry.AREA.get())) { + if (handle.getLightData().isOccluded()) return true; + } + return false; + } + + private static void pushUniforms(ClientLevel level, int cx, int cy, int cz) { + int ox, oy, oz; + if (gridBuffer != null && Objects.equals(gridDimension, level.dimension())) { + ox = originX; + oy = originY; + oz = originZ; + } else { + ox = cx - HALF; + oy = cy - HALF; + oz = cz - HALF; + } + pushUniforms(POINT_SHADER, ox, oy, oz); + pushUniforms(AREA_SHADER, ox, oy, oz); + } + + private static void pushUniforms(ResourceLocation shader, int ox, int oy, int oz) { + ShaderProgram program = VeilRenderSystem.renderer().getShaderManager().getShader(shader); + if (program == null || !program.isValid()) { + return; + } + program.setSampler("BlockGrid", textureId); + program.getUniformSafe("GridOrigin").setVector(ox, oy, oz); + } +} diff --git a/common/src/main/java/foundry/veil/mixin/pipeline/client/PipelineLevelRendererMixin.java b/common/src/main/java/foundry/veil/mixin/pipeline/client/PipelineLevelRendererMixin.java index ccb4353e0..582e1b568 100644 --- a/common/src/main/java/foundry/veil/mixin/pipeline/client/PipelineLevelRendererMixin.java +++ b/common/src/main/java/foundry/veil/mixin/pipeline/client/PipelineLevelRendererMixin.java @@ -12,6 +12,7 @@ import foundry.veil.api.client.render.VeilLevelPerspectiveRenderer; import foundry.veil.api.client.render.VeilRenderBridge; import foundry.veil.api.client.render.VeilRenderSystem; +import foundry.veil.impl.client.render.light.VoxelShadowGrid; import foundry.veil.api.client.render.framebuffer.AdvancedFbo; import foundry.veil.api.client.render.framebuffer.FramebufferManager; import foundry.veil.api.client.render.framebuffer.FramebufferStack; @@ -33,6 +34,8 @@ import net.minecraft.client.renderer.culling.Frustum; import net.minecraft.core.BlockPos; import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; @@ -154,6 +157,11 @@ public void initTransparency(CallbackInfo ci) { framebufferManager.setFramebuffer(VeilFramebuffers.CLOUDS_TARGET, VeilRenderBridge.wrap(this.cloudsTarget)); } + @Inject(method = "blockChanged", at = @At("TAIL")) + public void veil$onBlockChanged(BlockGetter level, BlockPos pos, BlockState oldState, BlockState newState, int flags, CallbackInfo ci) { + VoxelShadowGrid.markBlockDirty(pos); + } + @Inject(method = "setLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/SectionOcclusionGraph;waitAndReset(Lnet/minecraft/client/renderer/ViewArea;)V")) public void free(ClientLevel level, CallbackInfo ci) { VeilRenderSystem.clearLevel(); diff --git a/common/src/main/resources/assets/veil/pinwheel/shaders/include/voxel_shadow.glsl b/common/src/main/resources/assets/veil/pinwheel/shaders/include/voxel_shadow.glsl new file mode 100644 index 000000000..a3fd1c816 --- /dev/null +++ b/common/src/main/resources/assets/veil/pinwheel/shaders/include/voxel_shadow.glsl @@ -0,0 +1,47 @@ +uniform sampler3D BlockGrid; +uniform vec3 GridOrigin; + +#define VOXELSHADOW_GRID_SIZE 64 +#define VOXELSHADOW_MAX_STEPS 128 + +float voxelshadowVisibility(vec3 fragPos, vec3 lightPos) { + vec3 startG = fragPos - GridOrigin; + vec3 endG = lightPos - GridOrigin; + vec3 delta = endG - startG; + float rayLen = length(delta); + if (rayLen < 0.001) return 1.0; + + vec3 rDir = delta / rayLen; + ivec3 cell = ivec3(floor(startG)); + ivec3 iStep = ivec3(sign(rDir)); + + vec3 invAbs = 1.0 / max(abs(rDir), vec3(1e-5)); + vec3 tDelta = invAbs; + + vec3 cellF = vec3(cell); + vec3 tMax; + tMax.x = (rDir.x >= 0.0) ? (cellF.x + 1.0 - startG.x) * invAbs.x : (startG.x - cellF.x) * invAbs.x; + tMax.y = (rDir.y >= 0.0) ? (cellF.y + 1.0 - startG.y) * invAbs.y : (startG.y - cellF.y) * invAbs.y; + tMax.z = (rDir.z >= 0.0) ? (cellF.z + 1.0 - startG.z) * invAbs.z : (startG.z - cellF.z) * invAbs.z; + + for (int i = 0; i < VOXELSHADOW_MAX_STEPS; i++) { + if (any(lessThan(cell, ivec3(0))) || any(greaterThanEqual(cell, ivec3(VOXELSHADOW_GRID_SIZE)))) break; + if (i > 0 && texelFetch(BlockGrid, cell, 0).r > 0.5) return 0.0; + + if (tMax.x < tMax.y && tMax.x < tMax.z) { + if (tMax.x >= rayLen) break; + tMax.x += tDelta.x; + cell.x += iStep.x; + } else if (tMax.y < tMax.z) { + if (tMax.y >= rayLen) break; + tMax.y += tDelta.y; + cell.y += iStep.y; + } else { + if (tMax.z >= rayLen) break; + tMax.z += tDelta.z; + cell.z += iStep.z; + } + } + + return 1.0; +} diff --git a/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/area.fsh b/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/area.fsh index 8af99a180..079a01398 100644 --- a/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/area.fsh +++ b/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/area.fsh @@ -2,12 +2,14 @@ #include veil:space_helper #include veil:color_utilities #include veil:light +#include veil:voxel_shadow in mat4 lightMat; in vec3 lightColor; in vec2 size; in float maxAngle; in float maxDistance; +in float occluded; uniform sampler2D AlbedoSampler; uniform sampler2D NormalSampler; @@ -69,10 +71,13 @@ void main() { float angleFalloff = clamp(angle, 0.0, maxAngle) / maxAngle; angleFalloff = smoothstep(1.0, 0.0, angleFalloff); diffuse *= angleFalloff; + if (occluded > 0.5) { + vec3 normalWS = normalize((VeilCamera.IViewMat * vec4(normalVS, 0.0)).xyz); + diffuse *= voxelshadowVisibility(pos + normalWS * 0.01, lightPos); + } float reflectivity = 0.05; vec3 diffuseColor = diffuse * lightColor; fragColor = vec4(albedoColor.rgb * diffuseColor * (1.0 - reflectivity) + diffuseColor * reflectivity, 1.0); } - diff --git a/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/area.vsh b/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/area.vsh index 536844978..e166c011a 100644 --- a/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/area.vsh +++ b/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/area.vsh @@ -6,12 +6,14 @@ layout (location = 5) in vec3 Color; layout (location = 6) in vec2 Size; layout (location = 7) in float NormalizedAngle; layout (location = 8) in float Distance; +layout (location = 9) in float Occluded; out mat4 lightMat; out vec3 lightColor; out vec2 size; out float maxAngle; out float maxDistance; +out float occluded; void main() { vec3 vertexPos = Position; @@ -34,4 +36,5 @@ void main() { size = Size; maxAngle = Angle; maxDistance = Distance; + occluded = Occluded; } diff --git a/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/point.fsh b/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/point.fsh index 15c2d8fd6..b76e65599 100644 --- a/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/point.fsh +++ b/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/point.fsh @@ -2,10 +2,12 @@ #include veil:space_helper #include veil:color_utilities #include veil:light +#include veil:voxel_shadow in vec3 lightPos; in vec3 lightColor; in float radius; +in float occluded; uniform sampler2D AlbedoSampler; uniform sampler2D NormalSampler; @@ -31,9 +33,13 @@ void main() { vec3 normalVS = texture(NormalSampler, screenUv).xyz; vec3 lightDirection = normalize((VeilCamera.ViewMat * vec4(offset, 0.0)).xyz); - float diffuse = clamp(0.0, 1.0, dot(normalVS, lightDirection)); + float diffuse = clamp(dot(normalVS, lightDirection), 0.0, 1.0); diffuse = (diffuse + MINECRAFT_AMBIENT_LIGHT) / (1.0 + MINECRAFT_AMBIENT_LIGHT); diffuse *= attenuate_no_cusp(length(offset), radius); + if (occluded > 0.5) { + vec3 normalWS = normalize((VeilCamera.IViewMat * vec4(normalVS, 0.0)).xyz); + diffuse *= voxelshadowVisibility(pos + normalWS * 0.01, lightPos); + } float reflectivity = 0.05; vec3 diffuseColor = diffuse * lightColor; diff --git a/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/point.vsh b/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/point.vsh index fca69b1d2..8f6b6831e 100644 --- a/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/point.vsh +++ b/common/src/main/resources/assets/veil/pinwheel/shaders/program/light/point.vsh @@ -4,10 +4,12 @@ layout (location = 0) in vec3 Position; layout (location = 1) in vec3 LightPosition; layout (location = 2) in vec3 Color; layout (location = 3) in float Distance; +layout (location = 4) in float Occluded; out vec3 lightPos; out vec3 lightColor; out float radius; +out float occluded; void main() { vec3 size = Position * Distance; @@ -19,4 +21,5 @@ void main() { lightPos = LightPosition; lightColor = Color; radius = Distance; + occluded = Occluded; } diff --git a/common/src/main/resources/veil.pipeline.mixins.json b/common/src/main/resources/veil.pipeline.mixins.json index 1e220b893..1d7a00b22 100644 --- a/common/src/main/resources/veil.pipeline.mixins.json +++ b/common/src/main/resources/veil.pipeline.mixins.json @@ -29,4 +29,3 @@ "defaultRequire": 1 } } - \ No newline at end of file