Improve Indigo lighting ()

* Implement new mean light formula

Instead of assembling min light from non-zero values, assemble min light from clear values. Also fix min/max functions not operating on light components separately.

* Use light cache in flat lighting

* Fix issues with center lightmap and clean up AoFace
This commit is contained in:
PepperCode1 2025-04-04 09:52:52 -07:00 committed by GitHub
parent 2ccdb6efc8
commit 8125baf6d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 387 additions and 320 deletions

View file

@ -38,12 +38,8 @@ public class Indigo implements ClientModInitializer {
/** Set true in dev env to confirm results match vanilla when they should. */
public static final boolean DEBUG_COMPARE_LIGHTING;
public static final boolean FIX_SMOOTH_LIGHTING_OFFSET;
/** When true, requires {@link #FIX_SMOOTH_LIGHTING_OFFSET} to be true. */
public static final boolean FIX_MEAN_LIGHT_CALCULATION;
/**
* Same value as {@link #FIX_MEAN_LIGHT_CALCULATION} because it is only required when the mean formula is changed,
* but different field to clearly separate code paths where we change emissive handling.
*/
public static final boolean FIX_EMISSIVE_LIGHTING;
public static final boolean FIX_EXTERIOR_VERTEX_LIGHTING;
public static final boolean FIX_LUMINOUS_AO_SHADE;
@ -115,11 +111,17 @@ public class Indigo implements ClientModInitializer {
AMBIENT_OCCLUSION_MODE = asEnum((String) properties.computeIfAbsent("ambient-occlusion-mode", (a) -> "hybrid"), AoConfig.HYBRID);
DEBUG_COMPARE_LIGHTING = asBoolean((String) properties.computeIfAbsent("debug-compare-lighting", (a) -> "auto"), false);
FIX_SMOOTH_LIGHTING_OFFSET = asBoolean((String) properties.computeIfAbsent("fix-smooth-lighting-offset", (a) -> "auto"), true);
FIX_MEAN_LIGHT_CALCULATION = asBoolean((String) properties.computeIfAbsent("fix-mean-light-calculation", (a) -> "auto"), true);
FIX_EMISSIVE_LIGHTING = FIX_MEAN_LIGHT_CALCULATION;
boolean fixMeanLightCalculation = asBoolean((String) properties.computeIfAbsent("fix-mean-light-calculation", (a) -> "auto"), true);
FIX_EXTERIOR_VERTEX_LIGHTING = asBoolean((String) properties.computeIfAbsent("fix-exterior-vertex-lighting", (a) -> "auto"), true);
FIX_LUMINOUS_AO_SHADE = asBoolean((String) properties.computeIfAbsent("fix-luminous-block-ambient-occlusion", (a) -> "auto"), false);
if (fixMeanLightCalculation && !FIX_SMOOTH_LIGHTING_OFFSET) {
fixMeanLightCalculation = false;
LOGGER.warn("[Indigo] Config enabled 'fix-mean-light-calculation' but disabled 'fix-smooth-lighting-offset'; this is not supported! 'fix-mean-light-calculation' will be considered disabled.");
}
FIX_MEAN_LIGHT_CALCULATION = fixMeanLightCalculation;
try (FileOutputStream stream = new FileOutputStream(configFile)) {
properties.store(stream, "Indigo properties file");
} catch (IOException e) {

View file

@ -19,12 +19,6 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.aocalc;
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.CUBIC_FLAG;
import static net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper.LIGHT_FACE_FLAG;
import static net.minecraft.util.math.Direction.DOWN;
import static net.minecraft.util.math.Direction.EAST;
import static net.minecraft.util.math.Direction.NORTH;
import static net.minecraft.util.math.Direction.SOUTH;
import static net.minecraft.util.math.Direction.UP;
import static net.minecraft.util.math.Direction.WEST;
import org.joml.Vector3f;
import org.joml.Vector3fc;
@ -32,44 +26,29 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.render.block.BlockModelRenderer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.LightType;
import net.fabricmc.fabric.impl.client.indigo.Indigo;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoFace.WeightFunction;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.QuadViewImpl;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.LightDataProvider;
/**
* Adaptation of inner, non-static class in BlockModelRenderer that serves same purpose.
*/
public abstract class AoCalculator {
public class AoCalculator {
private static final Logger LOGGER = LoggerFactory.getLogger(AoCalculator.class);
/**
* Vanilla models with cubic quads have vertices in a certain order, which allows
* us to map them using a lookup. Adapted from enum in vanilla AoCalculator.
*/
private static final int[][] VERTEX_MAP = new int[6][4];
static {
VERTEX_MAP[DOWN.getIndex()] = new int[] { 0, 1, 2, 3 };
VERTEX_MAP[UP.getIndex()] = new int[] { 2, 3, 0, 1 };
VERTEX_MAP[NORTH.getIndex()] = new int[] { 3, 0, 1, 2 };
VERTEX_MAP[SOUTH.getIndex()] = new int[] { 0, 1, 2, 3 };
VERTEX_MAP[WEST.getIndex()] = new int[] { 3, 0, 1, 2 };
VERTEX_MAP[EAST.getIndex()] = new int[] { 1, 2, 3, 0 };
}
private final BlockRenderInfo blockInfo;
private final LightDataProvider dataProvider;
private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
private final BlockPos.Mutable searchPos = new BlockPos.Mutable();
protected final BlockRenderInfo blockInfo;
/** caches results of {@link #computeFace(Direction, boolean, boolean)} for the current block. */
private final AoFaceData[] faceData = new AoFaceData[24];
@ -84,18 +63,15 @@ public abstract class AoCalculator {
public final float[] ao = new float[4];
public final int[] light = new int[4];
public AoCalculator(BlockRenderInfo blockInfo) {
public AoCalculator(BlockRenderInfo blockInfo, LightDataProvider dataProvider) {
this.blockInfo = blockInfo;
this.dataProvider = dataProvider;
for (int i = 0; i < 24; i++) {
faceData[i] = new AoFaceData();
}
}
public abstract int light(BlockPos pos, BlockState state);
public abstract float ao(BlockPos pos, BlockState state);
/** call at start of each new block. */
public void clear() {
completionFlags = 0;
@ -159,16 +135,17 @@ public abstract class AoCalculator {
private void calcFastVanilla(QuadViewImpl quad) {
int flags = quad.geometryFlags();
boolean isOnLightFace = (flags & LIGHT_FACE_FLAG) != 0;
// force to block face if shape is full cube - matches vanilla logic
if ((flags & LIGHT_FACE_FLAG) == 0 && (flags & AXIS_ALIGNED_FLAG) != 0 && blockInfo.blockState.isFullCube(blockInfo.blockView, blockInfo.blockPos)) {
flags |= LIGHT_FACE_FLAG;
if (!isOnLightFace && (flags & AXIS_ALIGNED_FLAG) != 0 && blockInfo.blockState.isFullCube(blockInfo.blockView, blockInfo.blockPos)) {
isOnLightFace = true;
}
if ((flags & CUBIC_FLAG) == 0) {
vanillaPartialFace(quad, quad.lightFace(), (flags & LIGHT_FACE_FLAG) != 0, quad.hasShade());
vanillaPartialFace(quad, quad.lightFace(), isOnLightFace, quad.hasShade());
} else {
vanillaFullFace(quad, quad.lightFace(), (flags & LIGHT_FACE_FLAG) != 0, quad.hasShade());
vanillaFullFace(quad, quad.lightFace(), isOnLightFace, quad.hasShade());
}
}
@ -191,16 +168,16 @@ public abstract class AoCalculator {
}
private void vanillaFullFace(QuadViewImpl quad, Direction lightFace, boolean isOnLightFace, boolean shade) {
computeFace(lightFace, isOnLightFace, shade).toArray(ao, light, VERTEX_MAP[lightFace.getIndex()]);
computeFace(lightFace, isOnLightFace, shade).toArray(ao, light, AoFace.get(lightFace).vertexMap);
}
private void vanillaPartialFace(QuadViewImpl quad, Direction lightFace, boolean isOnLightFace, boolean shade) {
AoFaceData faceData = computeFace(lightFace, isOnLightFace, shade);
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
final AoFace aoFace = AoFace.get(lightFace);
final float[] w = this.w;
for (int i = 0; i < 4; i++) {
wFunc.apply(quad, i, w);
aoFace.computeCornerWeights(quad, i, w);
light[i] = faceData.weightedCombinedLight(w);
ao[i] = faceData.weigtedAo(w);
}
@ -211,7 +188,7 @@ public abstract class AoCalculator {
/** Returns linearly interpolated blend of outer and inner face based on depth of vertex in face. */
private AoFaceData blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade) {
final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
final float w1 = AoFace.get(lightFace).computeDepth(quad, vertexIndex);
final float w0 = 1 - w1;
return AoFaceData.weightedMean(computeFace(lightFace, true, shade), w0, computeFace(lightFace, false, shade), w1, tmpFace);
}
@ -221,7 +198,7 @@ public abstract class AoCalculator {
* Used for irregular faces when depth varies by vertex to avoid unneeded interpolation.
*/
private AoFaceData gatherInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade) {
final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
final float w1 = AoFace.get(lightFace).computeDepth(quad, vertexIndex);
if (MathHelper.approximatelyEquals(w1, 0)) {
return computeFace(lightFace, true, shade);
@ -235,10 +212,10 @@ public abstract class AoCalculator {
private void blendedPartialFace(QuadViewImpl quad, Direction lightFace, boolean shade) {
AoFaceData faceData = blendedInsetFace(quad, 0, lightFace, shade);
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
final AoFace aoFace = AoFace.get(lightFace);
for (int i = 0; i < 4; i++) {
wFunc.apply(quad, i, w);
aoFace.computeCornerWeights(quad, i, w);
light[i] = faceData.weightedCombinedLight(w);
ao[i] = faceData.weigtedAo(w);
}
@ -264,7 +241,7 @@ public abstract class AoCalculator {
if (!MathHelper.approximatelyEquals(0f, x)) {
final Direction face = x > 0 ? Direction.EAST : Direction.WEST;
final AoFaceData fd = gatherInsetFace(quad, i, face, shade);
AoFace.get(face).weightFunc.apply(quad, i, w);
AoFace.get(face).computeCornerWeights(quad, i, w);
final float n = x * x;
final float a = fd.weigtedAo(w);
final int s = fd.weigtedSkyLight(w);
@ -282,7 +259,7 @@ public abstract class AoCalculator {
if (!MathHelper.approximatelyEquals(0f, y)) {
final Direction face = y > 0 ? Direction.UP : Direction.DOWN;
final AoFaceData fd = gatherInsetFace(quad, i, face, shade);
AoFace.get(face).weightFunc.apply(quad, i, w);
AoFace.get(face).computeCornerWeights(quad, i, w);
final float n = y * y;
final float a = fd.weigtedAo(w);
final int s = fd.weigtedSkyLight(w);
@ -300,7 +277,7 @@ public abstract class AoCalculator {
if (!MathHelper.approximatelyEquals(0f, z)) {
final Direction face = z > 0 ? Direction.SOUTH : Direction.NORTH;
final AoFaceData fd = gatherInsetFace(quad, i, face, shade);
AoFace.get(face).weightFunc.apply(quad, i, w);
AoFace.get(face).computeCornerWeights(quad, i, w);
final float n = z * z;
final float a = fd.weigtedAo(w);
final int s = fd.weigtedSkyLight(w);
@ -314,7 +291,7 @@ public abstract class AoCalculator {
}
aoResult[i] = (ao + maxAo) * 0.5f;
lightResult[i] = (((int) ((sky + maxSky) * 0.5f) & 0xF0) << 16) | ((int) ((block + maxBlock) * 0.5f) & 0xF0);
lightResult[i] = (((int) ((sky + maxSky) * 0.5f) & 0xFF) << 16) | ((int) ((block + maxBlock) * 0.5f) & 0xFF);
}
}
@ -361,9 +338,8 @@ public abstract class AoCalculator {
searchPos.set(lightPos, aoFace.neighbors[0]);
searchState = world.getBlockState(searchPos);
final int light0 = light(searchPos, searchState);
final float ao0 = ao(searchPos, searchState);
final boolean em0 = hasEmissiveLighting(world, searchPos, searchState);
final int light0 = dataProvider.light(searchPos, searchState);
final float ao0 = dataProvider.ao(searchPos, searchState);
if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
searchPos.move(lightFace);
@ -374,9 +350,8 @@ public abstract class AoCalculator {
searchPos.set(lightPos, aoFace.neighbors[1]);
searchState = world.getBlockState(searchPos);
final int light1 = light(searchPos, searchState);
final float ao1 = ao(searchPos, searchState);
final boolean em1 = hasEmissiveLighting(world, searchPos, searchState);
final int light1 = dataProvider.light(searchPos, searchState);
final float ao1 = dataProvider.ao(searchPos, searchState);
if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
searchPos.move(lightFace);
@ -387,9 +362,8 @@ public abstract class AoCalculator {
searchPos.set(lightPos, aoFace.neighbors[2]);
searchState = world.getBlockState(searchPos);
final int light2 = light(searchPos, searchState);
final float ao2 = ao(searchPos, searchState);
final boolean em2 = hasEmissiveLighting(world, searchPos, searchState);
final int light2 = dataProvider.light(searchPos, searchState);
final float ao2 = dataProvider.ao(searchPos, searchState);
if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
searchPos.move(lightFace);
@ -400,9 +374,8 @@ public abstract class AoCalculator {
searchPos.set(lightPos, aoFace.neighbors[3]);
searchState = world.getBlockState(searchPos);
final int light3 = light(searchPos, searchState);
final float ao3 = ao(searchPos, searchState);
final boolean em3 = hasEmissiveLighting(world, searchPos, searchState);
final int light3 = dataProvider.light(searchPos, searchState);
final float ao3 = dataProvider.ao(searchPos, searchState);
if (!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
searchPos.move(lightFace);
@ -414,7 +387,7 @@ public abstract class AoCalculator {
// c = corner - values at corners of face
int cLight0, cLight1, cLight2, cLight3;
float cAo0, cAo1, cAo2, cAo3;
boolean cEm0, cEm1, cEm2, cEm3;
boolean cIsClear0, cIsClear1, cIsClear2, cIsClear3;
// 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)
@ -422,67 +395,74 @@ public abstract class AoCalculator {
if (!isClear2 && !isClear0) {
cAo0 = ao0;
cLight0 = light0;
cEm0 = em0;
cIsClear0 = false;
} else {
searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[2]);
searchPos.set(lightPos, aoFace.neighbors[0]).move(aoFace.neighbors[2]);
searchState = world.getBlockState(searchPos);
cAo0 = ao(searchPos, searchState);
cLight0 = light(searchPos, searchState);
cEm0 = hasEmissiveLighting(world, searchPos, searchState);
cAo0 = dataProvider.ao(searchPos, searchState);
cLight0 = dataProvider.light(searchPos, searchState);
cIsClear0 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity() == 0;
}
if (!isClear3 && !isClear0) {
cAo1 = ao0;
cLight1 = light0;
cEm1 = em0;
cIsClear1 = false;
} else {
searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[3]);
searchPos.set(lightPos, aoFace.neighbors[0]).move(aoFace.neighbors[3]);
searchState = world.getBlockState(searchPos);
cAo1 = ao(searchPos, searchState);
cLight1 = light(searchPos, searchState);
cEm1 = hasEmissiveLighting(world, searchPos, searchState);
cAo1 = dataProvider.ao(searchPos, searchState);
cLight1 = dataProvider.light(searchPos, searchState);
cIsClear1 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity() == 0;
}
if (!isClear2 && !isClear1) {
// Use the values from neighbor 1 instead of neighbor 0 since this corner is not adjacent to neighbor 0
cAo2 = ao1;
cLight2 = light1;
cEm2 = em1;
cIsClear2 = false;
} else {
searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[2]);
searchPos.set(lightPos, aoFace.neighbors[1]).move(aoFace.neighbors[2]);
searchState = world.getBlockState(searchPos);
cAo2 = ao(searchPos, searchState);
cLight2 = light(searchPos, searchState);
cEm2 = hasEmissiveLighting(world, searchPos, searchState);
cAo2 = dataProvider.ao(searchPos, searchState);
cLight2 = dataProvider.light(searchPos, searchState);
cIsClear2 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity() == 0;
}
if (!isClear3 && !isClear1) {
// Use the values from neighbor 1 instead of neighbor 0 since this corner is not adjacent to neighbor 0
cAo3 = ao1;
cLight3 = light1;
cEm3 = em1;
cIsClear3 = false;
} else {
searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[3]);
searchPos.set(lightPos, aoFace.neighbors[1]).move(aoFace.neighbors[3]);
searchState = world.getBlockState(searchPos);
cAo3 = ao(searchPos, searchState);
cLight3 = light(searchPos, searchState);
cEm3 = hasEmissiveLighting(world, searchPos, searchState);
cAo3 = dataProvider.ao(searchPos, searchState);
cLight3 = dataProvider.light(searchPos, searchState);
cIsClear3 = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity() == 0;
}
// If on block face or neighbor isn't occluding, "center" will be neighbor light
// If on block face and neighbor isn't occluding, "center" will be neighbor light
// Doesn't use light pos because logic not based solely on this block's geometry
int lightCenter;
boolean emCenter;
boolean isClearCenter;
searchPos.set(pos, lightFace);
searchState = world.getBlockState(searchPos);
if (isOnBlockFace || !searchState.isOpaqueFullCube()) {
lightCenter = light(searchPos, searchState);
emCenter = hasEmissiveLighting(world, searchPos, searchState);
// Vanilla uses an OR operator here; we use an AND operator to invert the result when isOnBlockFace and
// isOpaqueFullCube have the same value. When both are true, the vanilla logic caused inset faces against
// solid blocks to appear too dark when using enhanced AO (i.e. slab below ceiling or fence against wall). When
// both are false, the vanilla logic caused inset faces against non-solid blocks to be lit discontinuously (i.e.
// dark room with active sculk sensor above slabs).
if (isOnBlockFace && !searchState.isOpaqueFullCube()) {
lightCenter = dataProvider.light(searchPos, searchState);
isClearCenter = !searchState.shouldBlockVision(world, searchPos) || searchState.getOpacity() == 0;
} else {
lightCenter = light(pos, blockState);
emCenter = hasEmissiveLighting(world, pos, blockState);
lightCenter = dataProvider.light(pos, blockState);
isClearCenter = !blockState.shouldBlockVision(world, pos) || blockState.getOpacity() == 0;
}
float aoCenter = ao(lightPos, world.getBlockState(lightPos));
float aoCenter = dataProvider.ao(lightPos, world.getBlockState(lightPos));
float shadeBrightness = world.getBrightness(lightFace, shade);
result.a0 = ((ao3 + ao0 + cAo1 + aoCenter) * 0.25F) * shadeBrightness;
@ -490,67 +470,64 @@ public abstract class AoCalculator {
result.a2 = ((ao2 + ao1 + cAo2 + aoCenter) * 0.25F) * shadeBrightness;
result.a3 = ((ao3 + ao1 + cAo3 + aoCenter) * 0.25F) * shadeBrightness;
result.l0(meanLight(light3, light0, cLight1, lightCenter, em3, em0, cEm1, emCenter));
result.l1(meanLight(light2, light0, cLight0, lightCenter, em2, em0, cEm0, emCenter));
result.l2(meanLight(light2, light1, cLight2, lightCenter, em2, em1, cEm2, emCenter));
result.l3(meanLight(light3, light1, cLight3, lightCenter, em3, em1, cEm3, emCenter));
}
public static int getLightmapCoordinates(BlockRenderView world, BlockState state, BlockPos pos) {
if (Indigo.FIX_EMISSIVE_LIGHTING) {
// Same as WorldRenderer.getLightmapCoordinates but without the hasEmissiveLighting check.
// We don't want emissive lighting to influence the minimum lightmap in a quad,
// so when the fix is enabled we apply emissive lighting after the quad minimum is computed.
// See AoCalculator#meanLight.
int sky = world.getLightLevel(LightType.SKY, pos);
int block = world.getLightLevel(LightType.BLOCK, pos);
int luminance = state.getLuminance();
if (block < luminance) {
block = luminance;
}
return LightmapTextureManager.pack(block, sky);
} else {
return WorldRenderer.getLightmapCoordinates(WorldRenderer.BrightnessGetter.DEFAULT, world, state, pos);
}
}
private boolean hasEmissiveLighting(BlockRenderView world, BlockPos pos, BlockState state) {
if (Indigo.FIX_EMISSIVE_LIGHTING) {
return state.hasEmissiveLighting(world, pos);
} else {
// When the fix is disabled, emissive lighting was already applied and does not need to be accounted for.
return false;
}
result.l0(meanLight(light3, light0, cLight1, lightCenter, isClear3, isClear0, cIsClear1, isClearCenter));
result.l1(meanLight(light2, light0, cLight0, lightCenter, isClear2, isClear0, cIsClear0, isClearCenter));
result.l2(meanLight(light2, light1, cLight2, lightCenter, isClear2, isClear1, cIsClear2, isClearCenter));
result.l3(meanLight(light3, light1, cLight3, lightCenter, isClear3, isClear1, cIsClear3, isClearCenter));
}
/**
* Vanilla code excluded missing light values from mean but was not isotropic.
* Still need to substitute or edges are too dark but consistently use the min
* value from all four samples.
* Vanilla code sets light values equal to zero to the center light value (D) before taking the mean, which fixes
* solid blocks near a face making edges too dark. However, a value of zero does not mean it came from a solid
* block; this causes natural zero values to be treated differently from other natural values, causing visual
* inconsistencies. This implementation checks for the source of a light value explicitly. It also fixes samples
* being blended inconsistently based on the center position, which causes discontinuities, by computing a
* consistent minimum light value from all four samples.
*/
private static int meanLight(int lightA, int lightB, int lightC, int lightD, boolean emA, boolean emB, boolean emC, boolean emD) {
private static int meanLight(int lightA, int lightB, int lightC, int lightD, boolean isClearA, boolean isClearB, boolean isClearC, boolean isClearD) {
if (Indigo.FIX_MEAN_LIGHT_CALCULATION) {
if (lightA == 0 || lightB == 0 || lightC == 0 || lightD == 0) {
// Normalize values to non-zero minimum
final int min = nonZeroMin(nonZeroMin(lightA, lightB), nonZeroMin(lightC, lightD));
int lightABlock = lightA & 0xFFFF;
int lightASky = (lightA >>> 16) & 0xFFFF;
int lightBBlock = lightB & 0xFFFF;
int lightBSky = (lightB >>> 16) & 0xFFFF;
int lightCBlock = lightC & 0xFFFF;
int lightCSky = (lightC >>> 16) & 0xFFFF;
int lightDBlock = lightD & 0xFFFF;
int lightDSky = (lightD >>> 16) & 0xFFFF;
lightA = Math.max(lightA, min);
lightB = Math.max(lightB, min);
lightC = Math.max(lightC, min);
lightD = Math.max(lightD, min);
// Compute per-component minimum light, only including values from clear positions
int minBlock = 0x10000;
int minSky = 0x10000;
if (isClearA) {
minBlock = lightABlock;
minSky = lightASky;
}
if (Indigo.FIX_EMISSIVE_LIGHTING) {
// Apply the fullbright lightmap from emissive blocks at the very end so it cannot influence
// the minimum lightmap and produce incorrect results (for example, sculk sensors in a dark room)
if (emA) lightA = LightmapTextureManager.MAX_LIGHT_COORDINATE;
if (emB) lightB = LightmapTextureManager.MAX_LIGHT_COORDINATE;
if (emC) lightC = LightmapTextureManager.MAX_LIGHT_COORDINATE;
if (emD) lightD = LightmapTextureManager.MAX_LIGHT_COORDINATE;
if (isClearB) {
minBlock = Math.min(minBlock, lightBBlock);
minSky = Math.min(minSky, lightBSky);
}
if (isClearC) {
minBlock = Math.min(minBlock, lightCBlock);
minSky = Math.min(minSky, lightCSky);
}
if (isClearD) {
minBlock = Math.min(minBlock, lightDBlock);
minSky = Math.min(minSky, lightDSky);
}
// Ensure that if no positions were clear, minimum is 0
minBlock &= 0xFFFF;
minSky &= 0xFFFF;
lightA = Math.max(lightASky, minSky) << 16 | Math.max(lightABlock, minBlock);
lightB = Math.max(lightBSky, minSky) << 16 | Math.max(lightBBlock, minBlock);
lightC = Math.max(lightCSky, minSky) << 16 | Math.max(lightCBlock, minBlock);
lightD = Math.max(lightDSky, minSky) << 16 | Math.max(lightDBlock, minBlock);
return meanInnerLight(lightA, lightB, lightC, lightD);
} else {
return vanillaMeanLight(lightA, lightB, lightC, lightD);
@ -570,10 +547,4 @@ public abstract class AoCalculator {
// bitwise divide by 4, clamp to expected (positive) range
return a + b + c + d >> 2 & 0xFF00FF;
}
private static int nonZeroMin(int a, int b) {
if (a == 0) return b;
if (b == 0) return a;
return Math.min(a, b);
}
}

View file

@ -18,7 +18,7 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.aocalc;
/**
* Defines the configuration modes for the AoCalculator.
* This determine the appearance of smooth lighting.
* This determines the appearance of smooth lighting.
*/
public enum AoConfig {
/**

View file

@ -17,92 +17,124 @@
package net.fabricmc.fabric.impl.client.indigo.renderer.aocalc;
import static net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoVertexClampFunction.CLAMP_FUNC;
import static net.minecraft.util.math.Direction.DOWN;
import static net.minecraft.util.math.Direction.EAST;
import static net.minecraft.util.math.Direction.NORTH;
import static net.minecraft.util.math.Direction.SOUTH;
import static net.minecraft.util.math.Direction.UP;
import static net.minecraft.util.math.Direction.WEST;
import net.minecraft.util.Util;
import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.QuadViewImpl;
/**
* Adapted from vanilla BlockModelRenderer.AoCalculator.
* Adapted from vanilla BlockModelRenderer.NeighborData and BlockModelRenderer.Translation.
*/
enum AoFace {
AOF_DOWN(new Direction[] { WEST, EAST, NORTH, SOUTH }, (q, i) -> CLAMP_FUNC.clamp(q.y(i)), (q, i, w) -> {
final float u = CLAMP_FUNC.clamp(q.x(i));
final float v = CLAMP_FUNC.clamp(q.z(i));
w[0] = (1 - u) * v;
w[1] = (1 - u) * (1 - v);
w[2] = u * (1 - v);
w[3] = u * v;
}),
AOF_UP(new Direction[] { EAST, WEST, NORTH, SOUTH }, (q, i) -> 1 - CLAMP_FUNC.clamp(q.y(i)), (q, i, w) -> {
final float u = CLAMP_FUNC.clamp(q.x(i));
final float v = CLAMP_FUNC.clamp(q.z(i));
w[0] = u * v;
w[1] = u * (1 - v);
w[2] = (1 - u) * (1 - v);
w[3] = (1 - u) * v;
}),
AOF_NORTH(new Direction[] { UP, DOWN, EAST, WEST }, (q, i) -> CLAMP_FUNC.clamp(q.z(i)), (q, i, w) -> {
final float u = CLAMP_FUNC.clamp(q.y(i));
final float v = CLAMP_FUNC.clamp(q.x(i));
w[0] = u * (1 - v);
w[1] = u * v;
w[2] = (1 - u) * v;
w[3] = (1 - u) * (1 - v);
}),
AOF_SOUTH(new Direction[] { WEST, EAST, DOWN, UP }, (q, i) -> 1 - CLAMP_FUNC.clamp(q.z(i)), (q, i, w) -> {
final float u = CLAMP_FUNC.clamp(q.y(i));
final float v = CLAMP_FUNC.clamp(q.x(i));
w[0] = u * (1 - v);
w[1] = (1 - u) * (1 - v);
w[2] = (1 - u) * v;
w[3] = u * v;
}),
AOF_WEST(new Direction[] { UP, DOWN, NORTH, SOUTH }, (q, i) -> CLAMP_FUNC.clamp(q.x(i)), (q, i, w) -> {
final float u = CLAMP_FUNC.clamp(q.y(i));
final float v = CLAMP_FUNC.clamp(q.z(i));
w[0] = u * v;
w[1] = u * (1 - v);
w[2] = (1 - u) * (1 - v);
w[3] = (1 - u) * v;
}),
AOF_EAST(new Direction[] { DOWN, UP, NORTH, SOUTH }, (q, i) -> 1 - CLAMP_FUNC.clamp(q.x(i)), (q, i, w) -> {
final float u = CLAMP_FUNC.clamp(q.y(i));
final float v = CLAMP_FUNC.clamp(q.z(i));
w[0] = (1 - u) * v;
w[1] = (1 - u) * (1 - v);
w[2] = u * (1 - v);
w[3] = u * v;
});
DOWN(new Direction[] { Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH }, new int[] { 0, 1, 2, 3 }) {
@Override
void computeCornerWeights(QuadViewImpl q, int vertexIndex, float[] out) {
final float u = CLAMP_FUNC.clamp(q.x(vertexIndex));
final float v = CLAMP_FUNC.clamp(q.z(vertexIndex));
out[0] = (1 - u) * v;
out[1] = (1 - u) * (1 - v);
out[2] = u * (1 - v);
out[3] = u * v;
}
@Override
float computeDepth(QuadViewImpl q, int vertexIndex) {
return CLAMP_FUNC.clamp(q.y(vertexIndex));
}
},
UP(new Direction[] { Direction.EAST, Direction.WEST, Direction.NORTH, Direction.SOUTH }, new int[] { 2, 3, 0, 1 }) {
@Override
void computeCornerWeights(QuadViewImpl q, int vertexIndex, float[] out) {
final float u = CLAMP_FUNC.clamp(q.x(vertexIndex));
final float v = CLAMP_FUNC.clamp(q.z(vertexIndex));
out[0] = u * v;
out[1] = u * (1 - v);
out[2] = (1 - u) * (1 - v);
out[3] = (1 - u) * v;
}
@Override
float computeDepth(QuadViewImpl q, int vertexIndex) {
return 1 - CLAMP_FUNC.clamp(q.y(vertexIndex));
}
},
NORTH(new Direction[] { Direction.UP, Direction.DOWN, Direction.EAST, Direction.WEST }, new int[] { 3, 0, 1, 2 }) {
@Override
void computeCornerWeights(QuadViewImpl q, int vertexIndex, float[] out) {
final float u = CLAMP_FUNC.clamp(q.y(vertexIndex));
final float v = CLAMP_FUNC.clamp(q.x(vertexIndex));
out[0] = u * (1 - v);
out[1] = u * v;
out[2] = (1 - u) * v;
out[3] = (1 - u) * (1 - v);
}
@Override
float computeDepth(QuadViewImpl q, int vertexIndex) {
return CLAMP_FUNC.clamp(q.z(vertexIndex));
}
},
SOUTH(new Direction[] { Direction.WEST, Direction.EAST, Direction.DOWN, Direction.UP }, new int[] { 0, 1, 2, 3 }) {
@Override
void computeCornerWeights(QuadViewImpl q, int vertexIndex, float[] out) {
final float u = CLAMP_FUNC.clamp(q.y(vertexIndex));
final float v = CLAMP_FUNC.clamp(q.x(vertexIndex));
out[0] = u * (1 - v);
out[1] = (1 - u) * (1 - v);
out[2] = (1 - u) * v;
out[3] = u * v;
}
@Override
float computeDepth(QuadViewImpl q, int vertexIndex) {
return 1 - CLAMP_FUNC.clamp(q.z(vertexIndex));
}
},
WEST(new Direction[] { Direction.UP, Direction.DOWN, Direction.NORTH, Direction.SOUTH }, new int[] { 3, 0, 1, 2 }) {
@Override
void computeCornerWeights(QuadViewImpl q, int vertexIndex, float[] out) {
final float u = CLAMP_FUNC.clamp(q.y(vertexIndex));
final float v = CLAMP_FUNC.clamp(q.z(vertexIndex));
out[0] = u * v;
out[1] = u * (1 - v);
out[2] = (1 - u) * (1 - v);
out[3] = (1 - u) * v;
}
@Override
float computeDepth(QuadViewImpl q, int vertexIndex) {
return CLAMP_FUNC.clamp(q.x(vertexIndex));
}
},
EAST(new Direction[] { Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH }, new int[] { 1, 2, 3, 0 }) {
@Override
void computeCornerWeights(QuadViewImpl q, int vertexIndex, float[] out) {
final float u = CLAMP_FUNC.clamp(q.y(vertexIndex));
final float v = CLAMP_FUNC.clamp(q.z(vertexIndex));
out[0] = (1 - u) * v;
out[1] = (1 - u) * (1 - v);
out[2] = u * (1 - v);
out[3] = u * v;
}
@Override
float computeDepth(QuadViewImpl q, int vertexIndex) {
return 1 - CLAMP_FUNC.clamp(q.x(vertexIndex));
}
};
private static final AoFace[] VALUES = AoFace.values();
final Direction[] neighbors;
final WeightFunction weightFunc;
final Vertex2Float depthFunc;
/**
* Vanilla models with cubic quads have vertices in a certain order, which allows
* us to map them using a lookup.
*/
final int[] vertexMap;
AoFace(Direction[] faces, Vertex2Float depthFunc, WeightFunction weightFunc) {
this.neighbors = faces;
this.depthFunc = depthFunc;
this.weightFunc = weightFunc;
}
private static final AoFace[] values = Util.make(new AoFace[6], (neighborData) -> {
neighborData[DOWN.getIndex()] = AOF_DOWN;
neighborData[UP.getIndex()] = AOF_UP;
neighborData[NORTH.getIndex()] = AOF_NORTH;
neighborData[SOUTH.getIndex()] = AOF_SOUTH;
neighborData[WEST.getIndex()] = AOF_WEST;
neighborData[EAST.getIndex()] = AOF_EAST;
});
public static AoFace get(Direction direction) {
return values[direction.getIndex()];
AoFace(Direction[] neighbors, int[] vertexMap) {
this.neighbors = neighbors;
this.vertexMap = vertexMap;
}
/**
@ -113,13 +145,11 @@ enum AoFace {
* coordinate pairs together gives sub-area that are the corner weights.
* Weights sum to 1 because it is a unit cube. Values are stored in the provided array.
*/
@FunctionalInterface
interface WeightFunction {
void apply(QuadViewImpl q, int vertexIndex, float[] out);
}
abstract void computeCornerWeights(QuadViewImpl q, int vertexIndex, float[] out);
@FunctionalInterface
interface Vertex2Float {
float apply(QuadViewImpl q, int vertexIndex);
abstract float computeDepth(QuadViewImpl q, int vertexIndex);
static AoFace get(Direction direction) {
return VALUES[direction.getIndex()];
}
}

View file

@ -38,7 +38,7 @@ public final class GeometryHelper {
/** set when a quad touches all four corners of a unit cube. */
public static final int CUBIC_FLAG = 1;
/** set when a quad is parallel to (but not necessarily on) a its light face. */
/** set when a quad is parallel to (but not necessarily on) its light face. */
public static final int AXIS_ALIGNED_FLAG = CUBIC_FLAG << 1;
/** set when a quad is coplanar with its light face. Implies {@link #AXIS_ALIGNED_FLAG} */

View file

@ -25,13 +25,11 @@ import net.minecraft.block.BlockState;
import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.material.ShadeMode;
import net.fabricmc.fabric.api.util.TriState;
import net.fabricmc.fabric.impl.client.indigo.Indigo;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoConfig;
@ -40,7 +38,8 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl;
public abstract class AbstractTerrainRenderContext extends AbstractRenderContext {
protected final BlockRenderInfo blockInfo = new BlockRenderInfo();
protected final AoCalculator aoCalc;
protected final LightDataProvider lightDataProvider;
private final AoCalculator aoCalc;
private int cachedTintIndex = -1;
private int cachedTint;
@ -48,10 +47,11 @@ public abstract class AbstractTerrainRenderContext extends AbstractRenderContext
private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
protected AbstractTerrainRenderContext() {
aoCalc = createAoCalc(blockInfo);
lightDataProvider = createLightDataProvider(blockInfo);
aoCalc = new AoCalculator(blockInfo, lightDataProvider);
}
protected abstract AoCalculator createAoCalc(BlockRenderInfo blockInfo);
protected abstract LightDataProvider createLightDataProvider(BlockRenderInfo blockInfo);
protected abstract VertexConsumer getVertexConsumer(RenderLayer layer);
@ -69,8 +69,7 @@ public abstract class AbstractTerrainRenderContext extends AbstractRenderContext
}
final RenderMaterial mat = quad.material();
final TriState aoMode = mat.ambientOcclusion();
final boolean ao = blockInfo.useAo && (aoMode == TriState.TRUE || (aoMode == TriState.DEFAULT && blockInfo.defaultAo));
final boolean ao = blockInfo.effectiveAo(mat.ambientOcclusion());
final boolean emissive = mat.emissive();
final boolean vanillaShade = mat.shadeMode() == ShadeMode.VANILLA;
final VertexConsumer vertexConsumer = getVertexConsumer(blockInfo.effectiveRenderLayer(mat.blendMode()));
@ -123,7 +122,7 @@ public abstract class AbstractTerrainRenderContext extends AbstractRenderContext
quad.lightmap(i, LightmapTextureManager.MAX_LIGHT_COORDINATE);
}
} else {
final int light = flatLight(quad, blockInfo.blockState, blockInfo.blockPos);
final int light = flatLight(quad);
for (int i = 0; i < 4; i++) {
quad.lightmap(i, ColorHelper.maxLight(quad.lightmap(i), light));
@ -227,7 +226,9 @@ public abstract class AbstractTerrainRenderContext extends AbstractRenderContext
* Handles geometry-based check for using self light or neighbor light.
* That logic only applies in flat lighting.
*/
private int flatLight(MutableQuadViewImpl quad, BlockState blockState, BlockPos pos) {
private int flatLight(MutableQuadViewImpl quad) {
BlockState blockState = blockInfo.blockState;
BlockPos pos = blockInfo.blockPos;
lightPos.set(pos);
// To mirror Vanilla's behavior, if the face has a cull-face, always sample the light value
@ -243,7 +244,6 @@ public abstract class AbstractTerrainRenderContext extends AbstractRenderContext
}
}
// Unfortunately cannot use light cache here unless we implement one specifically for flat lighting. See #329
return WorldRenderer.getLightmapCoordinates(WorldRenderer.BrightnessGetter.DEFAULT, blockInfo.blockView, blockState, lightPos);
return lightDataProvider.light(lightPos, blockState);
}
}

View file

@ -29,6 +29,7 @@ import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
import net.fabricmc.fabric.api.util.TriState;
/**
* Holds, manages, and provides access to the block/world related state
@ -42,9 +43,9 @@ public class BlockRenderInfo {
public BlockPos blockPos;
public BlockState blockState;
boolean useAo;
boolean defaultAo;
RenderLayer defaultLayer;
private boolean useAo;
private boolean defaultAo;
private RenderLayer defaultLayer;
private boolean enableCulling;
private int cullCompletionFlags;
@ -74,11 +75,19 @@ public class BlockRenderInfo {
blockState = null;
}
int blockColor(int tintIndex) {
public int blockColor(int tintIndex) {
return 0xFF000000 | blockColorMap.getColor(blockState, blockView, blockPos, tintIndex);
}
boolean shouldDrawSide(@Nullable Direction side) {
public boolean effectiveAo(TriState aoMode) {
return useAo && aoMode.orElse(defaultAo);
}
public RenderLayer effectiveRenderLayer(BlendMode blendMode) {
return blendMode == BlendMode.DEFAULT ? defaultLayer : blendMode.blockRenderLayer;
}
public boolean shouldDrawSide(@Nullable Direction side) {
if (side == null || !enableCulling) {
return true;
}
@ -99,11 +108,7 @@ public class BlockRenderInfo {
}
}
boolean shouldCullSide(@Nullable Direction side) {
public boolean shouldCullSide(@Nullable Direction side) {
return !shouldDrawSide(side);
}
RenderLayer effectiveRenderLayer(BlendMode blendMode) {
return blendMode == BlendMode.DEFAULT ? this.defaultLayer : blendMode.blockRenderLayer;
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.client.indigo.renderer.render;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
public interface LightDataProvider {
int light(BlockPos pos, BlockState state);
float ao(BlockPos pos, BlockState state);
}

View file

@ -20,6 +20,7 @@ import net.minecraft.block.BlockState;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.crash.CrashException;
@ -30,7 +31,6 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoLuminanceFix;
/**
@ -44,11 +44,12 @@ public class TerrainLikeRenderContext extends AbstractTerrainRenderContext {
private VertexConsumerProvider vertexConsumers;
@Override
protected AoCalculator createAoCalc(BlockRenderInfo blockInfo) {
return new AoCalculator(blockInfo) {
protected LightDataProvider createLightDataProvider(BlockRenderInfo blockInfo) {
// TODO: Use a cache whenever vanilla would use a cache (BrightnessCache.enabled)
return new LightDataProvider() {
@Override
public int light(BlockPos pos, BlockState state) {
return AoCalculator.getLightmapCoordinates(blockInfo.blockView, state, pos);
return WorldRenderer.getLightmapCoordinates(WorldRenderer.BrightnessGetter.DEFAULT, blockInfo.blockView, state, pos);
}
@Override

View file

@ -16,16 +16,15 @@
package net.fabricmc.fabric.impl.client.indigo.renderer.render;
import java.util.Arrays;
import java.util.function.Function;
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.crash.CrashException;
@ -37,7 +36,6 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoLuminanceFix;
/**
@ -46,67 +44,17 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoLuminanceFix;
public class TerrainRenderContext extends AbstractTerrainRenderContext {
public static final ThreadLocal<TerrainRenderContext> POOL = ThreadLocal.withInitial(TerrainRenderContext::new);
// TODO: Allow TerrainLikeRenderContext to also cache these values, including for flat lighting (possible as of
// 1.21.5 rc1), and respect the setting of the vanilla brightness cache.
// This context (TerrainRenderContext) should use an array (or arrays) of length 18^3 instead of maps to cache
// these values since it is known which positions they may be computed for.
/**
* Serves same function as brightness cache in Mojang's AO calculator,
* with some differences as follows...
*
* <ul><li>Mojang limits the cache to 100 values.
* However, a render chunk only has 16^3 blocks in it, and the cache is cleared every chunk.
* For performance and simplicity, we just let map grow to the size of the render chunk.
*
* <li>The Mojang cache is a separate threadlocal with a threadlocal boolean to
* enable and disable. Cache clearing happens on disable. There's no use case for
* us when the cache needs to be disabled (and no apparent case in Mojang's code either)
* so we simply clear the cache at the start of each new chunk. It is also
* not a threadlocal because it's held within a threadlocal TerrainRenderContext.</ul>
*/
private final Long2IntOpenHashMap lightCache = new Long2IntOpenHashMap();
private final Long2FloatOpenHashMap aoCache = new Long2FloatOpenHashMap();
private MatrixStack matrixStack;
private Random random;
private Function<RenderLayer, BufferBuilder> bufferFunc;
public TerrainRenderContext() {
lightCache.defaultReturnValue(Integer.MAX_VALUE);
aoCache.defaultReturnValue(Float.MAX_VALUE);
overlay = OverlayTexture.DEFAULT_UV;
}
@Override
protected AoCalculator createAoCalc(BlockRenderInfo blockInfo) {
return new AoCalculator(blockInfo) {
@Override
public int light(BlockPos pos, BlockState state) {
long key = pos.asLong();
int result = lightCache.get(key);
if (result == Integer.MAX_VALUE) {
result = AoCalculator.getLightmapCoordinates(blockInfo.blockView, state, pos);
lightCache.put(key, result);
}
return result;
}
@Override
public float ao(BlockPos pos, BlockState state) {
long key = pos.asLong();
float result = aoCache.get(key);
if (result == Float.MAX_VALUE) {
result = AoLuminanceFix.INSTANCE.apply(blockInfo.blockView, pos, state);
aoCache.put(key, result);
}
return result;
}
};
protected LightDataProvider createLightDataProvider(BlockRenderInfo blockInfo) {
return new LightDataCache(blockInfo);
}
@Override
@ -114,15 +62,13 @@ public class TerrainRenderContext extends AbstractTerrainRenderContext {
return bufferFunc.apply(layer);
}
public void prepare(BlockRenderView blockView, MatrixStack matrixStack, Random random, Function<RenderLayer, BufferBuilder> bufferFunc) {
public void prepare(BlockRenderView blockView, BlockPos sectionOrigin, MatrixStack matrixStack, Random random, Function<RenderLayer, BufferBuilder> bufferFunc) {
blockInfo.prepareForWorld(blockView, true);
((LightDataCache) lightDataProvider).prepare(sectionOrigin);
this.matrixStack = matrixStack;
this.random = random;
this.bufferFunc = bufferFunc;
lightCache.clear();
aoCache.clear();
}
public void release() {
@ -157,4 +103,89 @@ public class TerrainRenderContext extends AbstractTerrainRenderContext {
matrixStack.pop();
}
}
private static class LightDataCache implements LightDataProvider {
// Since this context is only used during section building, we know ahead of time all positions for which data
// may be requested by flat or smooth lighting, so we use an array instead of a map to cache that data, unlike
// vanilla. Even though cache indices are positions and therefore 3D, the cache is 1D to maximize memory
// locality.
private final int[] lightCache = new int[18 * 18 * 18];
private final float[] aoCache = new float[18 * 18 * 18];
private final BlockRenderInfo blockInfo;
private BlockPos sectionOrigin;
LightDataCache(BlockRenderInfo blockInfo) {
this.blockInfo = blockInfo;
}
private final WorldRenderer.BrightnessGetter lightGetter = (world, pos) -> {
int cacheIndex = cacheIndex(pos);
if (cacheIndex == -1) {
return WorldRenderer.BrightnessGetter.DEFAULT.packedBrightness(world, pos);
}
int result = lightCache[cacheIndex];
if (result == Integer.MAX_VALUE) {
result = WorldRenderer.BrightnessGetter.DEFAULT.packedBrightness(world, pos);
lightCache[cacheIndex] = result;
}
return result;
};
public void prepare(BlockPos sectionOrigin) {
this.sectionOrigin = sectionOrigin;
Arrays.fill(lightCache, Integer.MAX_VALUE);
Arrays.fill(aoCache, Float.NaN);
}
@Override
public int light(BlockPos pos, BlockState state) {
return WorldRenderer.getLightmapCoordinates(lightGetter, blockInfo.blockView, state, pos);
}
@Override
public float ao(BlockPos pos, BlockState state) {
int cacheIndex = cacheIndex(pos);
if (cacheIndex == -1) {
return AoLuminanceFix.INSTANCE.apply(blockInfo.blockView, pos, state);
}
float result = aoCache[cacheIndex];
if (Float.isNaN(result)) {
result = AoLuminanceFix.INSTANCE.apply(blockInfo.blockView, pos, state);
aoCache[cacheIndex] = result;
}
return result;
}
private int cacheIndex(BlockPos pos) {
int localX = pos.getX() - (sectionOrigin.getX() - 1);
if (localX < 0 || localX >= 18) {
return -1;
}
int localY = pos.getY() - (sectionOrigin.getY() - 1);
if (localY < 0 || localY >= 18) {
return -1;
}
int localZ = pos.getZ() - (sectionOrigin.getZ() - 1);
if (localZ < 0 || localZ >= 18) {
return -1;
}
return localZ * 18 * 18 + localY * 18 + localX;
}
}
}

View file

@ -78,12 +78,13 @@ abstract class SectionBuilderMixin {
private void hookBuild(ChunkSectionPos sectionPos, ChunkRendererRegion region, VertexSorter sorter,
BlockBufferAllocatorStorage allocators,
CallbackInfoReturnable<SectionBuilder.RenderData> cir,
@Local(ordinal = 0) BlockPos sectionOrigin,
@Local(ordinal = 0) MatrixStack matrixStack,
@Local(ordinal = 0) Map<RenderLayer, BufferBuilder> builderMap,
@Local(ordinal = 0) Random random) {
// hook just before iterating over the render chunk's blocks to capture the buffer builder map
TerrainRenderContext renderer = TerrainRenderContext.POOL.get();
renderer.prepare(region, matrixStack, random, layer -> beginBufferBuilding(builderMap, allocators, layer));
renderer.prepare(region, sectionOrigin, matrixStack, random, layer -> beginBufferBuilding(builderMap, allocators, layer));
((AccessChunkRendererRegion) region).fabric_setRenderer(renderer);
}