From 05c2f43abd8bfb6accff4303fd951b5e55753a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 2 Feb 2022 01:06:09 +0100 Subject: [PATCH] Use frustum matrix for projector depth rendering, to allow flat projection onto surfaces while still offsetting upwards. Ensure render bounds for projectors always work. Render weather into projector depth so that rain casts shadows. --- .../renderer/ProjectorDepthRenderer.java | 32 +++++++++++++----- .../blockentity/ProjectorRenderer.java | 11 ++++++- .../blockentity/ProjectorBlockEntity.java | 17 +++++----- .../li/cil/oc2/common/mixin/FrustumMixin.java | 24 ++++++++++++++ .../oc2/common/mixin/LevelRendererMixin.java | 28 ++++++++++++++-- .../cil/oc2/common/mixin/MinecraftMixin.java | 19 ++++++++++- src/main/resources/oc2.mixins.json | 33 ++++++++++--------- 7 files changed, 126 insertions(+), 38 deletions(-) create mode 100644 src/main/java/li/cil/oc2/common/mixin/FrustumMixin.java 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 + } }