Fix Indigo AO calculation ()

* Fix AO calculation

- Ensure calcFastVanilla and computeFace have parity with vanilla
- Ensure BlockStates are not queried more than once for the same position
- Replace deprecated FabricLoader#getConfigDirectory call with FabricLoader#getConfigDir

* Revert opaque sides fix

* More AO fixes and optimizations
This commit is contained in:
PepperCode1 2022-07-21 20:07:20 +01:00 committed by modmuss50
parent 2d1b3c535f
commit 41a02c8a4e
7 changed files with 121 additions and 82 deletions
fabric-renderer-indigo/src/main/java/net/fabricmc/fabric/impl/client/indigo

View file

@ -23,8 +23,8 @@ import java.io.IOException;
import java.util.Locale;
import java.util.Properties;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
@ -89,7 +89,7 @@ public class Indigo implements ClientModInitializer {
}
static {
File configDir = new File(FabricLoader.getInstance().getConfigDirectory(), "fabric");
File configDir = FabricLoader.getInstance().getConfigDir().resolve("fabric").toFile();
if (!configDir.exists()) {
if (!configDir.mkdir()) {

View file

@ -28,16 +28,15 @@ import static net.minecraft.util.math.Direction.UP;
import static net.minecraft.util.math.Direction.WEST;
import java.util.BitSet;
import java.util.function.ToIntFunction;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.block.Block;
import net.minecraft.util.math.Vec3f;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3f;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.api.EnvType;
@ -55,10 +54,15 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
*/
@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);
}
/**
@ -81,10 +85,10 @@ public class AoCalculator {
private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
private final BlockPos.Mutable searchPos = new BlockPos.Mutable();
private final BlockRenderInfo blockInfo;
private final ToIntFunction<BlockPos> 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. */
@ -97,7 +101,7 @@ public class AoCalculator {
public final float[] ao = new float[4];
public final int[] light = new int[4];
public AoCalculator(BlockRenderInfo blockInfo, ToIntFunction<BlockPos> brightnessFunc, AoFunc aoFunc) {
public AoCalculator(BlockRenderInfo blockInfo, BrightnessFunc brightnessFunc, AoFunc aoFunc) {
this.blockInfo = blockInfo;
this.brightnessFunc = brightnessFunc;
this.aoFunc = aoFunc;
@ -198,7 +202,7 @@ public class AoCalculator {
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;
}
@ -360,7 +364,7 @@ public class AoCalculator {
/**
* 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.
@ -376,45 +380,76 @@ public class AoCalculator {
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 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
// 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 (!Indigo.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 (!Indigo.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 (!Indigo.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 (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.move(lightFace);
final boolean isClear3 = world.getBlockState(searchPos).getOpacity(world, searchPos) == 0;
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 (!Indigo.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 (!Indigo.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 (!Indigo.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 (!Indigo.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) {
@ -422,8 +457,9 @@ public class AoCalculator {
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) {
@ -431,8 +467,9 @@ public class AoCalculator {
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) {
@ -440,8 +477,9 @@ public class AoCalculator {
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) {
@ -449,22 +487,24 @@ public class AoCalculator {
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;
@ -500,12 +540,12 @@ public class AoCalculator {
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) {

View file

@ -28,16 +28,15 @@ import net.fabricmc.fabric.impl.client.indigo.Indigo;
*/
@FunctionalInterface
public interface AoLuminanceFix {
float apply(BlockView view, BlockPos pos);
float apply(BlockView view, BlockPos pos, BlockState state);
AoLuminanceFix INSTANCE = Indigo.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;
}
}

View file

@ -19,7 +19,6 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render;
import java.util.function.Consumer;
import java.util.function.Function;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
@ -93,25 +92,22 @@ public abstract class AbstractMeshConsumer extends AbstractQuadRenderer implemen
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 {

View file

@ -16,11 +16,11 @@
package net.fabricmc.fabric.impl.client.indigo.renderer.render;
import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.AXIS_ALIGNED_FLAG;
import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.LIGHT_FACE_FLAG;
import java.util.function.Function;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.RenderLayer;
@ -177,8 +177,12 @@ public abstract class AbstractQuadRenderer {
// for reference.
if (quad.cullFace() != null) {
mpos.move(quad.cullFace());
} else if ((quad.geometryFlags() & LIGHT_FACE_FLAG) != 0 || Block.isShapeFullCube(blockState.getCollisionShape(blockInfo.blockView, pos))) {
mpos.move(quad.lightFace());
} else {
final int flags = quad.geometryFlags();
if ((flags & LIGHT_FACE_FLAG) != 0 || ((flags & 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

View file

@ -94,17 +94,17 @@ public class BlockRenderContext extends AbstractRenderContext {
}
};
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) {

View file

@ -119,27 +119,27 @@ public class ChunkRenderInfo {
}
/**
* 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);
}