diff --git a/src/main/java/li/cil/oc2/client/renderer/ProjectorDepthRenderer.java b/src/main/java/li/cil/oc2/client/renderer/ProjectorDepthRenderer.java index f8ddb9da..d5a4b4ef 100644 --- a/src/main/java/li/cil/oc2/client/renderer/ProjectorDepthRenderer.java +++ b/src/main/java/li/cil/oc2/client/renderer/ProjectorDepthRenderer.java @@ -13,7 +13,6 @@ import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; import com.mojang.math.Matrix3f; import com.mojang.math.Matrix4f; -import com.mojang.math.Quaternion; import com.mojang.math.Vector3f; import li.cil.oc2.api.API; import li.cil.oc2.common.block.ProjectorBlock; @@ -59,8 +58,6 @@ import static org.lwjgl.opengl.GL30.glBindFramebuffer; @Mod.EventBusSubscriber(modid = API.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE) public final class ProjectorDepthRenderer { private static final int DEPTH_CAPTURE_SIZE = 256; - private static final float PROJECTOR_FOV = 50; - private static final float PROJECTOR_ASPECT_RATIO = ProjectorVMDevice.WIDTH / (float) ProjectorVMDevice.HEIGHT; private static final List VISIBLE_PROJECTORS = new ArrayList<>(); private static final DepthOnlyRenderTarget[] PROJECTOR_DEPTH_TARGETS = new DepthOnlyRenderTarget[ModShaders.MAX_PROJECTORS]; @@ -68,9 +65,16 @@ public final class ProjectorDepthRenderer { private static final Matrix4f[] PROJECTOR_CAMERA_MATRICES = new Matrix4f[ModShaders.MAX_PROJECTORS]; private static final Camera PROJECTOR_DEPTH_CAMERA = new Camera(); private static final DepthOnlyRenderTarget MAIN_CAMERA_DEPTH = new DepthOnlyRenderTarget(MainTarget.DEFAULT_WIDTH, MainTarget.DEFAULT_HEIGHT); - private static final float PROJECTOR_NEAR = 1 / 16f; - private static final float PROJECTOR_FAR = 32f; - private static final Matrix4f DEPTH_CAMERA_PROJECTION_MATRIX = Matrix4f.perspective(PROJECTOR_FOV, PROJECTOR_ASPECT_RATIO, PROJECTOR_NEAR, PROJECTOR_FAR); + private static final float PROJECTOR_FORWARD_SHIFT = 7 / 16f; // From center of projector block. + private static final float PROJECTOR_NEAR = 0.5f - PROJECTOR_FORWARD_SHIFT - 0.5f / 16f; // Not quite forward edge, to allow occluding with neighboring block. + private static final float PROJECTOR_FAR = ProjectorBlockEntity.MAX_RENDER_DISTANCE; + private static final int HALF_FRUSTUM_WIDTH = (ProjectorBlockEntity.MAX_WIDTH - 1) / 2; + private static final int FRUSTUM_HEIGHT = ProjectorBlockEntity.MAX_HEIGHT - 1; + private static final Matrix4f DEPTH_CAMERA_PROJECTION_MATRIX = getFrustumMatrix( + PROJECTOR_NEAR, PROJECTOR_FAR, + ProjectorBlockEntity.MAX_GOOD_RENDER_DISTANCE, + -HALF_FRUSTUM_WIDTH, HALF_FRUSTUM_WIDTH, + FRUSTUM_HEIGHT, 0); private static final Cache RENDER_INFO = CacheBuilder.newBuilder() .expireAfterAccess(Duration.ofSeconds(5)) @@ -213,7 +217,7 @@ public final class ProjectorDepthRenderer { final Direction facing = projector.getBlockState().getValue(ProjectorBlock.FACING); final Vec3 projectorPos = Vec3 .atCenterOf(projector.getBlockPos()) - .add(new Vec3(facing.step()).scale(7 / 16f)); + .add(new Vec3(facing.step()).scale(PROJECTOR_FORWARD_SHIFT)); configureProjectorDepthCamera(level, projectorPos, facing.toYRot()); @@ -268,9 +272,8 @@ public final class ProjectorDepthRenderer { } private static void setupViewModelMatrix(final PoseStack viewModelStack) { - final Quaternion rotation = Vector3f.YP.rotationDegrees(PROJECTOR_DEPTH_CAMERA.getYRot() + 180); viewModelStack.setIdentity(); - viewModelStack.mulPose(rotation); + viewModelStack.mulPose(Vector3f.YP.rotationDegrees(PROJECTOR_DEPTH_CAMERA.getYRot() + 180)); final Matrix3f viewRotationMatrix = viewModelStack.last().normal().copy(); if (viewRotationMatrix.invert()) { @@ -405,6 +408,17 @@ public final class ProjectorDepthRenderer { tesselator.end(); } + private static Matrix4f getFrustumMatrix(final float near, final float far, final float dist, + final float left, final float right, + final float top, final float bottom) { + return new Matrix4f(new float[]{ + 2 * dist / (right - left), 0, (right + left) / (right - left), 0, + 0, 2 * dist / (top - bottom), (top + bottom) / (top - bottom), 0, + 0, 0, -(far + near) / (far - near), -(2 * far * near) / (far - near), + 0, 0, -1, 0, + }); + } + private static RenderInfo getRenderInfo(final ProjectorBlockEntity projector) { try { return RENDER_INFO.get(projector, () -> { diff --git a/src/main/java/li/cil/oc2/client/renderer/blockentity/ProjectorRenderer.java b/src/main/java/li/cil/oc2/client/renderer/blockentity/ProjectorRenderer.java index 1f1a8a98..0b8e0ae8 100644 --- a/src/main/java/li/cil/oc2/client/renderer/blockentity/ProjectorRenderer.java +++ b/src/main/java/li/cil/oc2/client/renderer/blockentity/ProjectorRenderer.java @@ -38,7 +38,16 @@ public final class ProjectorRenderer implements BlockEntityRenderer ci) { + if (ProjectorDepthRenderer.isIsRenderingProjectorDepth()) { + ci.setReturnValue((Frustum) (Object) this); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/mixin/LevelRendererMixin.java b/src/main/java/li/cil/oc2/common/mixin/LevelRendererMixin.java index be2b2f4e..88913833 100644 --- a/src/main/java/li/cil/oc2/common/mixin/LevelRendererMixin.java +++ b/src/main/java/li/cil/oc2/common/mixin/LevelRendererMixin.java @@ -9,6 +9,7 @@ import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.*; import net.minecraft.client.renderer.culling.Frustum; +import net.minecraft.world.phys.Vec3; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -42,6 +43,8 @@ public abstract class LevelRendererMixin { @Nullable private RenderTarget weatherTargetBak; + @Shadow + protected abstract void renderSnowAndRain(final LightTexture lightTexture, final float partialTicks, final double cameraX, final double cameraY, final double cameraZ); @Inject(method = "renderLevel", at = @At("HEAD")) private void prepareDepthRendering(final CallbackInfo ci) { @@ -79,15 +82,18 @@ public abstract class LevelRendererMixin { ) { if (ProjectorDepthRenderer.isIsRenderingProjectorDepth()) { // If we're rendering depth, we can skip most of the rest here: we don't need destruction progress, - // transparency, hit result, debug stuff, clouds or weather. + // transparency, hit result, debug stuff, clouds. cleanupDepthRendering(); - // We do want particles though, because that's a neat effect. + // We do want particles and weather (rain) though, because that's a neat effect. final MultiBufferSource.BufferSource bufferSource = renderBuffers.bufferSource(); minecraft.particleEngine.render(stack, bufferSource, lightTexture, camera, partialTicks, cullingFrustum); + bufferSource.endBatch(); + + final Vec3 cameraPosition = camera.getPosition(); + renderSnowAndRain(lightTexture, partialTicks, cameraPosition.x(), cameraPosition.y(), cameraPosition.z()); // Clean up anything regular return would also clean up. - bufferSource.endBatch(); RenderSystem.depthMask(true); RenderSystem.disableBlend(); RenderSystem.applyModelViewMatrix(); @@ -100,6 +106,19 @@ public abstract class LevelRendererMixin { } } + /** + * Make sure weather effects are rendered with depth, so they cause "shadows" in our projection. + */ + @Inject(method = "renderSnowAndRain", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;depthMask(Z)V", shift = At.Shift.AFTER)) + private void enableDepthForWeatherInDepthBuffer(final CallbackInfo ci) { + if (ProjectorDepthRenderer.isIsRenderingProjectorDepth()) { + RenderSystem.depthMask(true); + } + } + + /** + * Don't render outlines while rendering projector depth. + */ @Inject(method = "shouldShowEntityOutlines", at = @At("HEAD"), cancellable = true) private void skipOutlines(final CallbackInfoReturnable cir) { if (ProjectorDepthRenderer.isIsRenderingProjectorDepth()) { @@ -107,6 +126,9 @@ public abstract class LevelRendererMixin { } } + /** + * Skip rendering the sky when rendering projector depth. + */ @Inject(method = "renderSky", at = @At("HEAD"), cancellable = true) private void skipSky(final CallbackInfo ci) { if (ProjectorDepthRenderer.isIsRenderingProjectorDepth()) { diff --git a/src/main/java/li/cil/oc2/common/mixin/MinecraftMixin.java b/src/main/java/li/cil/oc2/common/mixin/MinecraftMixin.java index 461ac417..b406a93f 100644 --- a/src/main/java/li/cil/oc2/common/mixin/MinecraftMixin.java +++ b/src/main/java/li/cil/oc2/common/mixin/MinecraftMixin.java @@ -1,6 +1,7 @@ package li.cil.oc2.common.mixin; import com.mojang.blaze3d.pipeline.RenderTarget; +import li.cil.oc2.client.renderer.ProjectorDepthRenderer; import li.cil.oc2.common.ext.MinecraftExt; import net.minecraft.client.Minecraft; import org.jetbrains.annotations.Nullable; @@ -18,10 +19,26 @@ public abstract class MinecraftMixin implements MinecraftExt { mainRenderTargetOverride = renderTarget; } - @Inject(method = "getMainRenderTarget", at = @At(value = "HEAD"), cancellable = true) + /** + * Redirect everything into the correct buffer while rendering projector depth. + *

+ * Some things in level rendering may try to re-bind the main render target, so + * we catch that and ensure we bind the projector depth buffer again. + */ + @Inject(method = "getMainRenderTarget", at = @At("HEAD"), cancellable = true) private void getMainRenderTargetOverride(final CallbackInfoReturnable cir) { if (mainRenderTargetOverride != null) { cir.setReturnValue(mainRenderTargetOverride); } } + + /** + * Avoid access to render targets that are null while rendering projector depth to skip some work. + */ + @Inject(method = "useShaderTransparency", at = @At("HEAD"), cancellable = true) + private static void noTransparencyWhileRenderingProjectorDepth(final CallbackInfoReturnable cir) { + if (ProjectorDepthRenderer.isIsRenderingProjectorDepth()) { + cir.setReturnValue(false); + } + } } diff --git a/src/main/resources/oc2.mixins.json b/src/main/resources/oc2.mixins.json index b1f01a19..fbfdb3d7 100644 --- a/src/main/resources/oc2.mixins.json +++ b/src/main/resources/oc2.mixins.json @@ -1,18 +1,19 @@ { - "minVersion": "0.8", - "compatibilityLevel": "JAVA_17", - "required": true, - "package": "li.cil.oc2.common.mixin", - "refmap": "oc2.refmap.json", - "mixins": [ - "ChunkAccessMixin", - "ChunkMapMixin" - ], - "client": [ - "LevelRendererMixin", - "MinecraftMixin" - ], - "injectors": { - "defaultRequire": 1 - } + "minVersion": "0.8", + "compatibilityLevel": "JAVA_17", + "required": true, + "package": "li.cil.oc2.common.mixin", + "refmap": "oc2.refmap.json", + "mixins": [ + "ChunkAccessMixin", + "ChunkMapMixin" + ], + "client": [ + "FrustumMixin", + "LevelRendererMixin", + "MinecraftMixin" + ], + "injectors": { + "defaultRequire": 1 + } }