Skip to content

Commit

Permalink
Fix AO calculation (#131)
Browse files Browse the repository at this point in the history
- Direct port of Indigo AO fix (FabricMC/fabric#2344)
  • Loading branch information
PepperCode1 authored and comp500 committed Aug 1, 2022
1 parent 62b70c2 commit 442335f
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 79 deletions.
144 changes: 92 additions & 52 deletions src/main/java/link/infra/indium/renderer/aocalc/AoCalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.*;
Expand All @@ -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);
}

/**
Expand All @@ -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<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. */
Expand All @@ -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<BlockPos> brightnessFunc, AoFunc aoFunc) {
public AoCalculator(BlockRenderInfo blockInfo, BrightnessFunc brightnessFunc, AoFunc aoFunc) {
this.blockInfo = blockInfo;
this.brightnessFunc = brightnessFunc;
this.aoFunc = aoFunc;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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.
Expand All @@ -367,95 +371,131 @@ 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) {
cAo0 = ao0;
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) {
cAo1 = ao0;
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) {
cAo2 = ao1;
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) {
cAo3 = ao1;
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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 442335f

Please sign in to comment.