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.
This commit is contained in:
@@ -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<ProjectorBlockEntity> 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<ProjectorBlockEntity, RenderInfo> 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, () -> {
|
||||
|
||||
@@ -38,7 +38,16 @@ public final class ProjectorRenderer implements BlockEntityRenderer<ProjectorBlo
|
||||
|
||||
@Override
|
||||
public boolean shouldRender(final ProjectorBlockEntity projector, final Vec3 position) {
|
||||
return projector.isProjecting() && BlockEntityRenderer.super.shouldRender(projector, position);
|
||||
return !ProjectorDepthRenderer.isIsRenderingProjectorDepth() &&
|
||||
projector.isProjecting() &&
|
||||
BlockEntityRenderer.super.shouldRender(projector, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRenderOffScreen(final ProjectorBlockEntity p_112306_) {
|
||||
// Render bounding box of projectors (vastly) exceeds their block position, so they need
|
||||
// to be treated as global renderers, and cannot be culled with their chunk.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -46,9 +46,10 @@ public final class ProjectorBlockEntity extends ModBlockEntity implements Tickab
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static final int MAX_RENDER_DISTANCE = 12;
|
||||
private static final int MAX_WIDTH = MAX_RENDER_DISTANCE + 1; // +1 To make it odd, so we can center.
|
||||
private static final int MAX_HEIGHT = (MAX_RENDER_DISTANCE * ProjectorVMDevice.HEIGHT / ProjectorVMDevice.WIDTH) + 1; // + 1 To match horizontal margin.
|
||||
public static final int MAX_RENDER_DISTANCE = 16;
|
||||
public static final int MAX_GOOD_RENDER_DISTANCE = 12;
|
||||
public static final int MAX_WIDTH = MAX_GOOD_RENDER_DISTANCE + 1; // +1 To make it odd, so we can center.
|
||||
public static final int MAX_HEIGHT = (MAX_GOOD_RENDER_DISTANCE * ProjectorVMDevice.HEIGHT / ProjectorVMDevice.WIDTH) + 1; // + 1 To match horizontal margin.
|
||||
|
||||
private static final int FRAME_EVERY_N_TICKS = 5;
|
||||
|
||||
@@ -285,15 +286,15 @@ public final class ProjectorBlockEntity extends ModBlockEntity implements Tickab
|
||||
|
||||
private void updateRenderBounds() {
|
||||
final Direction blockFacing = getBlockState().getValue(ProjectorBlock.FACING);
|
||||
final Direction screenUp = Direction.UP;
|
||||
final Direction screenLeft = blockFacing.getCounterClockWise();
|
||||
final Direction canvasUp = Direction.UP;
|
||||
final Direction canvasLeft = blockFacing.getCounterClockWise();
|
||||
|
||||
final BlockPos projectorPos = getBlockPos();
|
||||
final BlockPos screenBasePos = projectorPos.relative(blockFacing, MAX_RENDER_DISTANCE);
|
||||
final BlockPos screenMinPos = screenBasePos.relative(screenLeft.getOpposite(), MAX_WIDTH / 2);
|
||||
final BlockPos screenMaxPos = screenBasePos.relative(screenLeft, MAX_WIDTH / 2)
|
||||
final BlockPos screenMinPos = screenBasePos.relative(canvasLeft.getOpposite(), MAX_WIDTH / 2);
|
||||
final BlockPos screenMaxPos = screenBasePos.relative(canvasLeft, MAX_WIDTH / 2)
|
||||
// -1 for the MAX_HEIGHT padding, -1 for auto-expansion of AABB constructor
|
||||
.relative(screenUp, MAX_HEIGHT - 2);
|
||||
.relative(canvasUp, MAX_HEIGHT - 2);
|
||||
|
||||
renderBounds = new AABB(getBlockPos()).minmax(new AABB(screenMinPos)).minmax(new AABB(screenMaxPos));
|
||||
}
|
||||
|
||||
24
src/main/java/li/cil/oc2/common/mixin/FrustumMixin.java
Normal file
24
src/main/java/li/cil/oc2/common/mixin/FrustumMixin.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package li.cil.oc2.common.mixin;
|
||||
|
||||
import li.cil.oc2.client.renderer.ProjectorDepthRenderer;
|
||||
import net.minecraft.client.renderer.culling.Frustum;
|
||||
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;
|
||||
|
||||
@Mixin(Frustum.class)
|
||||
public abstract class FrustumMixin {
|
||||
/**
|
||||
* Skip offsetting the frustum to fully contain camera cube, since we have a very
|
||||
* tight frustum when rendering projector depth; so tight that the cube may never
|
||||
* fit inside, which would then leave to an endless loop (would shift out of bounds
|
||||
* and never get back in).
|
||||
*/
|
||||
@Inject(method = "offsetToFullyIncludeCameraCube", at = @At("HEAD"), cancellable = true)
|
||||
private void skipOffset(final CallbackInfoReturnable<Frustum> ci) {
|
||||
if (ProjectorDepthRenderer.isIsRenderingProjectorDepth()) {
|
||||
ci.setReturnValue((Frustum) (Object) this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Boolean> 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()) {
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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<RenderTarget> 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<Boolean> cir) {
|
||||
if (ProjectorDepthRenderer.isIsRenderingProjectorDepth()) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user