diff --git a/src/main/java/link/infra/indium/renderer/aocalc/AoCalculator.java b/src/main/java/link/infra/indium/renderer/aocalc/AoCalculator.java index 3a14b5e..df97842 100644 --- a/src/main/java/link/infra/indium/renderer/aocalc/AoCalculator.java +++ b/src/main/java/link/infra/indium/renderer/aocalc/AoCalculator.java @@ -25,7 +25,7 @@ import link.infra.indium.renderer.render.BlockRenderInfo; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.block.Block; +import net.minecraft.block.BlockState; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; @@ -35,7 +35,6 @@ import org.apache.logging.log4j.Logger; import java.util.BitSet; -import java.util.function.ToIntFunction; import static java.lang.Math.max; import static link.infra.indium.renderer.helper.GeometryHelper.*; @@ -46,10 +45,15 @@ */ @Environment(EnvType.CLIENT) public class AoCalculator { + @FunctionalInterface + public interface BrightnessFunc { + int apply(BlockPos pos, BlockState state); + } + /** Used to receive a method reference in constructor for ao value lookup. */ @FunctionalInterface public interface AoFunc { - float apply(BlockPos pos); + float apply(BlockPos pos, BlockState state); } /** @@ -72,10 +76,10 @@ public interface AoFunc { private final BlockPos.Mutable lightPos = new BlockPos.Mutable(); private final BlockPos.Mutable searchPos = new BlockPos.Mutable(); private final BlockRenderInfo blockInfo; - private final ToIntFunction brightnessFunc; + private final BrightnessFunc brightnessFunc; private final AoFunc aoFunc; - /** caches results of {@link #computeFace(Direction, boolean)} for the current block. */ + /** caches results of {@link #computeFace(Direction, boolean, boolean)} for the current block. */ private final AoFaceData[] faceData = new AoFaceData[12]; /** indicates which elements of {@link #faceData} have been computed for the current block. */ @@ -88,7 +92,7 @@ public interface AoFunc { public final float[] ao = new float[4]; public final int[] light = new int[4]; - public AoCalculator(BlockRenderInfo blockInfo, ToIntFunction brightnessFunc, AoFunc aoFunc) { + public AoCalculator(BlockRenderInfo blockInfo, BrightnessFunc brightnessFunc, AoFunc aoFunc) { this.blockInfo = blockInfo; this.brightnessFunc = brightnessFunc; this.aoFunc = aoFunc; @@ -189,7 +193,7 @@ private void calcFastVanilla(MutableQuadViewImpl quad) { int flags = quad.geometryFlags(); // force to block face if shape is full cube - matches vanilla logic - if ((flags & LIGHT_FACE_FLAG) == 0 && (flags & AXIS_ALIGNED_FLAG) == AXIS_ALIGNED_FLAG && Block.isShapeFullCube(blockInfo.blockState.getCollisionShape(blockInfo.blockView, blockInfo.blockPos))) { + if ((flags & LIGHT_FACE_FLAG) == 0 && (flags & AXIS_ALIGNED_FLAG) != 0 && blockInfo.blockState.isFullCube(blockInfo.blockView, blockInfo.blockPos)) { flags |= LIGHT_FACE_FLAG; } @@ -351,7 +355,7 @@ private void irregularFace(MutableQuadViewImpl quad) { /** * Computes smoothed brightness and Ao shading for four corners of a block face. - * Outer block face is what you normally see and what you get get when second + * Outer block face is what you normally see and what you get when the second * parameter is true. Inner is light *within* the block and usually darker. * It is blended with the outer face for inset surfaces, but is also used directly * in vanilla logic for some blocks that aren't full opaque cubes. @@ -367,45 +371,76 @@ private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace, boole final BlockRenderView world = blockInfo.blockView; final BlockPos pos = blockInfo.blockPos; + final BlockState blockState = blockInfo.blockState; final BlockPos.Mutable lightPos = this.lightPos; final BlockPos.Mutable searchPos = this.searchPos; + BlockState searchState; + + if (isOnBlockFace) { + lightPos.set(pos, lightFace); + } else { + lightPos.set(pos); + } - lightPos.set(isOnBlockFace ? pos.offset(lightFace) : pos); AoFace aoFace = AoFace.get(lightFace); - searchPos.set(lightPos).move(aoFace.neighbors[0]); - final int light0 = brightnessFunc.applyAsInt(searchPos); - final float ao0 = aoFunc.apply(searchPos); - searchPos.set(lightPos).move(aoFace.neighbors[1]); - final int light1 = brightnessFunc.applyAsInt(searchPos); - final float ao1 = aoFunc.apply(searchPos); - searchPos.set(lightPos).move(aoFace.neighbors[2]); - final int light2 = brightnessFunc.applyAsInt(searchPos); - final float ao2 = aoFunc.apply(searchPos); - searchPos.set(lightPos).move(aoFace.neighbors[3]); - final int light3 = brightnessFunc.applyAsInt(searchPos); - final float ao3 = aoFunc.apply(searchPos); - - // vanilla was further offsetting these in the direction of the light face - // but it was actually mis-sampling and causing visible artifacts in certain situation - searchPos.set(lightPos).move(aoFace.neighbors[0]); //.setOffset(lightFace); - if (!Indium.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.move(lightFace); - final boolean isClear0 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0; - searchPos.set(lightPos).move(aoFace.neighbors[1]); //.setOffset(lightFace); - if (!Indium.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.move(lightFace); - final boolean isClear1 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0; - searchPos.set(lightPos).move(aoFace.neighbors[2]); //.setOffset(lightFace); - if (!Indium.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.move(lightFace); - final boolean isClear2 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0; - searchPos.set(lightPos).move(aoFace.neighbors[3]); //.setOffset(lightFace); - if (!Indium.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.move(lightFace); - final boolean isClear3 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0; + // Vanilla was further offsetting the positions for opaque block checks in the + // direction of the light face, but it was actually mis-sampling and causing + // visible artifacts in certain situations + + searchPos.set(lightPos, aoFace.neighbors[0]); + searchState = world.getBlockState(searchPos); + final int light0 = brightnessFunc.apply(searchPos, searchState); + final float ao0 = aoFunc.apply(searchPos, searchState); + + if (!Indium.FIX_SMOOTH_LIGHTING_OFFSET) { + searchPos.move(lightFace); + searchState = world.getBlockState(searchPos); + } + + final boolean isClear0 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0; + + searchPos.set(lightPos, aoFace.neighbors[1]); + searchState = world.getBlockState(searchPos); + final int light1 = brightnessFunc.apply(searchPos, searchState); + final float ao1 = aoFunc.apply(searchPos, searchState); + + if (!Indium.FIX_SMOOTH_LIGHTING_OFFSET) { + searchPos.move(lightFace); + searchState = world.getBlockState(searchPos); + } + + final boolean isClear1 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0; + + searchPos.set(lightPos, aoFace.neighbors[2]); + searchState = world.getBlockState(searchPos); + final int light2 = brightnessFunc.apply(searchPos, searchState); + final float ao2 = aoFunc.apply(searchPos, searchState); + + if (!Indium.FIX_SMOOTH_LIGHTING_OFFSET) { + searchPos.move(lightFace); + searchState = world.getBlockState(searchPos); + } + + final boolean isClear2 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0; + + searchPos.set(lightPos, aoFace.neighbors[3]); + searchState = world.getBlockState(searchPos); + final int light3 = brightnessFunc.apply(searchPos, searchState); + final float ao3 = aoFunc.apply(searchPos, searchState); + + if (!Indium.FIX_SMOOTH_LIGHTING_OFFSET) { + searchPos.move(lightFace); + searchState = world.getBlockState(searchPos); + } + + final boolean isClear3 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity(world, searchPos) == 0; // c = corner - values at corners of face int cLight0, cLight1, cLight2, cLight3; float cAo0, cAo1, cAo2, cAo3; - // If neighbors on both side of the corner are opaque, then apparently we use the light/shade + // If neighbors on both sides of the corner are opaque, then apparently we use the light/shade // from one of the sides adjacent to the corner. If either neighbor is clear (no light subtraction) // then we use values from the outwardly diagonal corner. (outwardly = position is one more away from light face) if (!isClear2 && !isClear0) { @@ -413,8 +448,9 @@ private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace, boole cLight0 = light0; } else { searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[2]); - cAo0 = aoFunc.apply(searchPos); - cLight0 = brightnessFunc.applyAsInt(searchPos); + searchState = world.getBlockState(searchPos); + cAo0 = aoFunc.apply(searchPos, searchState); + cLight0 = brightnessFunc.apply(searchPos, searchState); } if (!isClear3 && !isClear0) { @@ -422,8 +458,9 @@ private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace, boole cLight1 = light0; } else { searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[3]); - cAo1 = aoFunc.apply(searchPos); - cLight1 = brightnessFunc.applyAsInt(searchPos); + searchState = world.getBlockState(searchPos); + cAo1 = aoFunc.apply(searchPos, searchState); + cLight1 = brightnessFunc.apply(searchPos, searchState); } if (!isClear2 && !isClear1) { @@ -431,8 +468,9 @@ private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace, boole cLight2 = light1; } else { searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[2]); - cAo2 = aoFunc.apply(searchPos); - cLight2 = brightnessFunc.applyAsInt(searchPos); + searchState = world.getBlockState(searchPos); + cAo2 = aoFunc.apply(searchPos, searchState); + cLight2 = brightnessFunc.apply(searchPos, searchState); } if (!isClear3 && !isClear1) { @@ -440,22 +478,24 @@ private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace, boole cLight3 = light1; } else { searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[3]); - cAo3 = aoFunc.apply(searchPos); - cLight3 = brightnessFunc.applyAsInt(searchPos); + searchState = world.getBlockState(searchPos); + cAo3 = aoFunc.apply(searchPos, searchState); + cLight3 = brightnessFunc.apply(searchPos, searchState); } // If on block face or neighbor isn't occluding, "center" will be neighbor brightness // Doesn't use light pos because logic not based solely on this block's geometry int lightCenter; - searchPos.set(pos).move(lightFace); + searchPos.set(pos, lightFace); + searchState = world.getBlockState(searchPos); - if (isOnBlockFace || !world.getBlockState(searchPos).isOpaqueFullCube(world, searchPos)) { - lightCenter = brightnessFunc.applyAsInt(searchPos); + if (isOnBlockFace || !searchState.isOpaqueFullCube(world, searchPos)) { + lightCenter = brightnessFunc.apply(searchPos, searchState); } else { - lightCenter = brightnessFunc.applyAsInt(pos); + lightCenter = brightnessFunc.apply(pos, blockState); } - float aoCenter = aoFunc.apply(isOnBlockFace ? lightPos : pos); + float aoCenter = aoFunc.apply(lightPos, world.getBlockState(lightPos)); float worldBrightness = world.getBrightness(lightFace, shade); result.a0 = ((ao3 + ao0 + cAo1 + aoCenter) * 0.25F) * worldBrightness; @@ -491,12 +531,12 @@ private static int vanillaMeanBrightness(int a, int b, int c, int d) { if (b == 0) b = d; if (c == 0) c = d; // bitwise divide by 4, clamp to expected (positive) range - return a + b + c + d >> 2 & 16711935; + return a + b + c + d >> 2 & 0xFF00FF; } private static int meanInnerBrightness(int a, int b, int c, int d) { // bitwise divide by 4, clamp to expected (positive) range - return a + b + c + d >> 2 & 16711935; + return a + b + c + d >> 2 & 0xFF00FF; } private static int nonZeroMin(int a, int b) { diff --git a/src/main/java/link/infra/indium/renderer/aocalc/AoLuminanceFix.java b/src/main/java/link/infra/indium/renderer/aocalc/AoLuminanceFix.java index 3559272..0a8d0d6 100644 --- a/src/main/java/link/infra/indium/renderer/aocalc/AoLuminanceFix.java +++ b/src/main/java/link/infra/indium/renderer/aocalc/AoLuminanceFix.java @@ -28,16 +28,15 @@ */ @FunctionalInterface public interface AoLuminanceFix { - float apply(BlockView view, BlockPos pos); + float apply(BlockView view, BlockPos pos, BlockState state); AoLuminanceFix INSTANCE = Indium.FIX_LUMINOUS_AO_SHADE ? AoLuminanceFix::fixed : AoLuminanceFix::vanilla; - static float vanilla(BlockView view, BlockPos pos) { - return view.getBlockState(pos).getAmbientOcclusionLightLevel(view, pos); + static float vanilla(BlockView view, BlockPos pos, BlockState state) { + return state.getAmbientOcclusionLightLevel(view, pos); } - static float fixed(BlockView view, BlockPos pos) { - final BlockState state = view.getBlockState(pos); + static float fixed(BlockView view, BlockPos pos, BlockState state) { return state.getLuminance() == 0 ? state.getAmbientOcclusionLightLevel(view, pos) : 1f; } } diff --git a/src/main/java/link/infra/indium/renderer/render/BaseMeshConsumer.java b/src/main/java/link/infra/indium/renderer/render/BaseMeshConsumer.java index 2f2dec2..3910ffe 100644 --- a/src/main/java/link/infra/indium/renderer/render/BaseMeshConsumer.java +++ b/src/main/java/link/infra/indium/renderer/render/BaseMeshConsumer.java @@ -22,7 +22,6 @@ import link.infra.indium.renderer.mesh.EncodingFormat; import link.infra.indium.renderer.mesh.MeshImpl; import link.infra.indium.renderer.mesh.MutableQuadViewImpl; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.RenderLayer; import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; @@ -91,25 +90,22 @@ private void renderQuad(MutableQuadViewImpl quad) { return; } - final RenderMaterialImpl.Value mat = quad.material(); - - if (!mat.disableAo(0) && MinecraftClient.isAmbientOcclusionEnabled()) { - // needs to happen before offsets are applied - aoCalc.compute(quad, false); - } - - tessellateQuad(quad, mat, 0); + tessellateQuad(quad, 0); } /** * Determines color index and render layer, then routes to appropriate * tessellate routine based on material properties. */ - private void tessellateQuad(MutableQuadViewImpl quad, RenderMaterialImpl.Value mat, int textureIndex) { + private void tessellateQuad(MutableQuadViewImpl quad, int textureIndex) { + final RenderMaterialImpl.Value mat = quad.material(); final int colorIndex = mat.disableColorIndex(textureIndex) ? -1 : quad.colorIndex(); final RenderLayer renderLayer = blockInfo.effectiveRenderLayer(mat.blendMode(textureIndex)); if (blockInfo.defaultAo && !mat.disableAo(textureIndex)) { + // needs to happen before offsets are applied + aoCalc.compute(quad, false); + if (mat.emissive(textureIndex)) { tessellateSmoothEmissive(quad, renderLayer, colorIndex); } else { diff --git a/src/main/java/link/infra/indium/renderer/render/BaseQuadRenderer.java b/src/main/java/link/infra/indium/renderer/render/BaseQuadRenderer.java index 1dc450a..4a89f0c 100644 --- a/src/main/java/link/infra/indium/renderer/render/BaseQuadRenderer.java +++ b/src/main/java/link/infra/indium/renderer/render/BaseQuadRenderer.java @@ -21,7 +21,6 @@ import link.infra.indium.renderer.helper.GeometryHelper; import link.infra.indium.renderer.mesh.MutableQuadViewImpl; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform; -import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.client.render.LightmapTextureManager; import net.minecraft.client.render.RenderLayer; @@ -131,8 +130,12 @@ int flatBrightness(MutableQuadViewImpl quad, BlockState blockState, BlockPos pos // for reference. if (quad.cullFace() != null) { mpos.move(quad.cullFace()); - } else if ((quad.geometryFlags() & GeometryHelper.LIGHT_FACE_FLAG) != 0 || Block.isShapeFullCube(blockState.getCollisionShape(blockInfo.blockView, pos))) { - mpos.move(quad.lightFace()); + } else { + final int flags = quad.geometryFlags(); + + if ((flags & GeometryHelper.LIGHT_FACE_FLAG) != 0 || ((flags & GeometryHelper.AXIS_ALIGNED_FLAG) != 0 && blockState.isFullCube(blockInfo.blockView, pos))) { + mpos.move(quad.lightFace()); + } } // Unfortunately cannot use brightness cache here unless we implement one specifically for flat lighting. See #329 diff --git a/src/main/java/link/infra/indium/renderer/render/BlockRenderContext.java b/src/main/java/link/infra/indium/renderer/render/BlockRenderContext.java index f8e06a9..c2a094c 100644 --- a/src/main/java/link/infra/indium/renderer/render/BlockRenderContext.java +++ b/src/main/java/link/infra/indium/renderer/render/BlockRenderContext.java @@ -63,17 +63,17 @@ public class BlockRenderContext extends MatrixRenderContext { */ private final BaseFallbackConsumer fallbackConsumer = new BaseFallbackConsumer(new QuadBufferer(this::outputBuffer), blockInfo, aoCalc, this::transform); - private int brightness(BlockPos pos) { + private int brightness(BlockPos pos, BlockState state) { if (blockInfo.blockView == null) { return LightmapTextureManager.MAX_LIGHT_COORDINATE; } - return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, blockInfo.blockView.getBlockState(pos), pos); + return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, state, pos); } - private float aoLevel(BlockPos pos) { + private float aoLevel(BlockPos pos, BlockState state) { final BlockRenderView blockView = blockInfo.blockView; - return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos); + return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos, state); } private VertexConsumer outputBuffer(RenderLayer renderLayer) { diff --git a/src/main/java/link/infra/indium/renderer/render/ChunkRenderInfo.java b/src/main/java/link/infra/indium/renderer/render/ChunkRenderInfo.java index cbe73dd..9a10c05 100644 --- a/src/main/java/link/infra/indium/renderer/render/ChunkRenderInfo.java +++ b/src/main/java/link/infra/indium/renderer/render/ChunkRenderInfo.java @@ -89,27 +89,27 @@ public ChunkModelBuilder getChunkModelBuilder(RenderLayer renderLayer) { } /** - * Cached values for {@link BlockState#getBlockBrightness(BlockRenderView, BlockPos)}. + * Cached values for {@link WorldRenderer#getLightmapCoordinates(BlockRenderView, BlockState, BlockPos)}. * See also the comments for {@link #brightnessCache}. */ - int cachedBrightness(BlockPos pos) { + int cachedBrightness(BlockPos pos, BlockState state) { long key = pos.asLong(); int result = brightnessCache.get(key); if (result == Integer.MAX_VALUE) { - result = WorldRenderer.getLightmapCoordinates(blockView, blockView.getBlockState(pos), pos); + result = WorldRenderer.getLightmapCoordinates(blockView, state, pos); brightnessCache.put(key, result); } return result; } - float cachedAoLevel(BlockPos pos) { + float cachedAoLevel(BlockPos pos, BlockState state) { long key = pos.asLong(); float result = aoLevelCache.get(key); if (result == Float.MAX_VALUE) { - result = AoLuminanceFix.INSTANCE.apply(blockView, pos); + result = AoLuminanceFix.INSTANCE.apply(blockView, pos, state); aoLevelCache.put(key, result); }