Indigo shade related fixes and other changes ()

* Apply disabled shade from vanilla quads directly to material

- Remove QuadViewImpl.shade

* Fix enhanced AO calculation and respect non-terrain culling state

- Fix AoCalculator using AO face data computed with a potentially different shade state
- Move non-cached computation code to separate method in AoCalculator
- Turn AoCalculator's brightnessFunc and aoFunc into abstract methods
- Do not check null check world in non-terrain AO calculation since it cannot be null
- Pass through lightFace and shade state as method arguments in AoCalculator methods to prevent additional lookups
- Do not check for the axis aligned flag in AbstractQuadRenderer.shadeFlatQuad
- Respect cull parameter passed to non-terrain rendering by merging TerrainBlockRenderInfo into BlockRenderInfo
- Use reusable search pos when calling Block.shouldDrawSide to prevent additional BlockPos allocation
- Change BlockRenderContext.render and TerrainRenderContext.tessellateBlock to return void since return value is no longer used
- Remove QuadViewImpl.vertexStart since it is unused

* Add suggestions

- Mark Direction parameter to BlockRenderInfo.shouldDrawFace as Nullable
- Reuse MaterialFinder in FrameBakedModel

(cherry picked from commit 3a95925af4)

(cherry picked from commit 1adbf277ee)
This commit is contained in:
PepperCode1 2023-02-23 10:13:38 +00:00 committed by modmuss50
parent 6bd39c990e
commit bf4864981b
12 changed files with 275 additions and 317 deletions
fabric-renderer-api-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/simple/client
fabric-renderer-indigo/src/main/java/net/fabricmc/fabric

View file

@ -37,9 +37,9 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
@ -57,9 +57,10 @@ final class FrameBakedModel implements BakedModel, FabricBakedModel {
this.frameMesh = frameMesh;
this.frameSprite = frameSprite;
Renderer renderer = RendererAccess.INSTANCE.getRenderer();
this.translucentMaterial = renderer.materialFinder().blendMode(0, BlendMode.TRANSLUCENT).find();
this.translucentEmissiveMaterial = renderer.materialFinder().blendMode(0, BlendMode.TRANSLUCENT).emissive(0, true).find();
MaterialFinder finder = RendererAccess.INSTANCE.getRenderer().materialFinder();
this.translucentMaterial = finder.blendMode(0, BlendMode.TRANSLUCENT).find();
finder.clear();
this.translucentEmissiveMaterial = finder.blendMode(0, BlendMode.TRANSLUCENT).emissive(0, true).find();
}
@Override

View file

@ -52,6 +52,14 @@ public abstract class RenderMaterialImpl {
return VALUES[index];
}
public static Value setDisableDiffuse(Value material, int textureIndex, boolean disable) {
if (material.disableDiffuse(textureIndex) != disable) {
return byIndex(disable ? (material.bits | DIFFUSE_FLAG) : (material.bits & ~DIFFUSE_FLAG));
}
return material;
}
protected int bits;
public BlendMode blendMode(int textureIndex) {

View file

@ -39,8 +39,6 @@ import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3f;
import net.minecraft.world.BlockRenderView;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.impl.client.indigo.Indigo;
import net.fabricmc.fabric.impl.client.indigo.renderer.accessor.AccessAmbientOcclusionCalculator;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoFace.WeightFunction;
@ -52,19 +50,7 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
/**
* Adaptation of inner, non-static class in BlockModelRenderer that serves same purpose.
*/
@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, BlockState state);
}
public abstract class AoCalculator {
/**
* 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.
@ -84,12 +70,14 @@ public class AoCalculator {
private final AccessAmbientOcclusionCalculator vanillaCalc;
private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
private final BlockPos.Mutable searchPos = new BlockPos.Mutable();
private final BlockRenderInfo blockInfo;
private final BrightnessFunc brightnessFunc;
private final AoFunc aoFunc;
protected final BlockRenderInfo blockInfo;
public abstract int light(BlockPos pos, BlockState state);
public abstract float ao(BlockPos pos, BlockState state);
/** caches results of {@link #computeFace(Direction, boolean, boolean)} for the current block. */
private final AoFaceData[] faceData = new AoFaceData[12];
private final AoFaceData[] faceData = new AoFaceData[24];
/** indicates which elements of {@link #faceData} have been computed for the current block. */
private int completionFlags = 0;
@ -101,13 +89,11 @@ public class AoCalculator {
public final float[] ao = new float[4];
public final int[] light = new int[4];
public AoCalculator(BlockRenderInfo blockInfo, BrightnessFunc brightnessFunc, AoFunc aoFunc) {
public AoCalculator(BlockRenderInfo blockInfo) {
this.blockInfo = blockInfo;
this.brightnessFunc = brightnessFunc;
this.aoFunc = aoFunc;
this.vanillaCalc = VanillaAoHelper.get();
for (int i = 0; i < 12; i++) {
for (int i = 0; i < 24; i++) {
faceData[i] = new AoFaceData();
}
}
@ -123,12 +109,7 @@ public class AoCalculator {
switch (config) {
case VANILLA:
// prevent NPE in error case of failed reflection for vanilla calculator access
if (vanillaCalc == null) {
calcFastVanilla(quad);
} else {
calcVanilla(quad);
}
calcVanilla(quad);
// no point in comparing vanilla with itself
shouldCompare = false;
@ -156,7 +137,7 @@ public class AoCalculator {
calcEnhanced(quad);
}
if (shouldCompare && vanillaCalc != null) {
if (shouldCompare) {
float[] vanillaAo = new float[4];
int[] vanillaLight = new int[4];
calcVanilla(quad, vanillaAo, vanillaLight);
@ -188,11 +169,11 @@ public class AoCalculator {
private void calcVanilla(MutableQuadViewImpl quad, float[] aoDest, int[] lightDest) {
vanillaAoControlBits.clear();
final Direction face = quad.lightFace();
final Direction lightFace = quad.lightFace();
quad.toVanilla(0, vertexData, 0, false);
VanillaAoHelper.updateShape(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, face, vanillaAoData, vanillaAoControlBits);
vanillaCalc.fabric_apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, quad.lightFace(), vanillaAoData, vanillaAoControlBits, quad.hasShade());
VanillaAoHelper.updateShape(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, vertexData, lightFace, vanillaAoData, vanillaAoControlBits);
vanillaCalc.fabric_apply(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, lightFace, vanillaAoData, vanillaAoControlBits, quad.hasShade());
System.arraycopy(vanillaCalc.fabric_colorMultiplier(), 0, aoDest, 0, 4);
System.arraycopy(vanillaCalc.fabric_brightness(), 0, lightDest, 0, 4);
@ -207,9 +188,9 @@ public class AoCalculator {
}
if ((flags & CUBIC_FLAG) == 0) {
vanillaPartialFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
vanillaPartialFace(quad, quad.lightFace(), (flags & LIGHT_FACE_FLAG) != 0, quad.hasShade());
} else {
vanillaFullFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
vanillaFullFace(quad, quad.lightFace(), (flags & LIGHT_FACE_FLAG) != 0, quad.hasShade());
}
}
@ -217,28 +198,26 @@ public class AoCalculator {
switch (quad.geometryFlags()) {
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
vanillaPartialFace(quad, true);
vanillaPartialFace(quad, quad.lightFace(), true, quad.hasShade());
break;
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
case AXIS_ALIGNED_FLAG:
blendedPartialFace(quad);
blendedPartialFace(quad, quad.lightFace(), quad.hasShade());
break;
default:
irregularFace(quad);
irregularFace(quad, quad.hasShade());
break;
}
}
private void vanillaFullFace(QuadViewImpl quad, boolean isOnLightFace) {
final Direction lightFace = quad.lightFace();
computeFace(lightFace, isOnLightFace, quad.hasShade()).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
private void vanillaFullFace(QuadViewImpl quad, Direction lightFace, boolean isOnLightFace, boolean shade) {
computeFace(lightFace, isOnLightFace, shade).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
}
private void vanillaPartialFace(QuadViewImpl quad, boolean isOnLightFace) {
final Direction lightFace = quad.lightFace();
AoFaceData faceData = computeFace(lightFace, isOnLightFace, quad.hasShade());
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 float[] w = this.w;
@ -249,36 +228,35 @@ public class AoCalculator {
}
}
/** used in {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} as return variable to avoid new allocation. */
/** used in {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade)} as return variable to avoid new allocation. */
AoFaceData tmpFace = new AoFaceData();
/** 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) {
private AoFaceData blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade) {
final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
final float w0 = 1 - w1;
return AoFaceData.weightedMean(computeFace(lightFace, true, quad.hasShade()), w0, computeFace(lightFace, false, quad.hasShade()), w1, tmpFace);
return AoFaceData.weightedMean(computeFace(lightFace, true, shade), w0, computeFace(lightFace, false, shade), w1, tmpFace);
}
/**
* Like {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} but optimizes if depth is 0 or 1.
* Like {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade)} but optimizes if depth is 0 or 1.
* Used for irregular faces when depth varies by vertex to avoid unneeded interpolation.
*/
private AoFaceData gatherInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace) {
private AoFaceData gatherInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace, boolean shade) {
final float w1 = AoFace.get(lightFace).depthFunc.apply(quad, vertexIndex);
if (MathHelper.approximatelyEquals(w1, 0)) {
return computeFace(lightFace, true, quad.hasShade());
return computeFace(lightFace, true, shade);
} else if (MathHelper.approximatelyEquals(w1, 1)) {
return computeFace(lightFace, false, quad.hasShade());
return computeFace(lightFace, false, shade);
} else {
final float w0 = 1 - w1;
return AoFaceData.weightedMean(computeFace(lightFace, true, quad.hasShade()), w0, computeFace(lightFace, false, quad.hasShade()), w1, tmpFace);
return AoFaceData.weightedMean(computeFace(lightFace, true, shade), w0, computeFace(lightFace, false, shade), w1, tmpFace);
}
}
private void blendedPartialFace(QuadViewImpl quad) {
final Direction lightFace = quad.lightFace();
AoFaceData faceData = blendedInsetFace(quad, 0, lightFace);
private void blendedPartialFace(QuadViewImpl quad, Direction lightFace, boolean shade) {
AoFaceData faceData = blendedInsetFace(quad, 0, lightFace, shade);
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
for (int i = 0; i < 4; i++) {
@ -291,7 +269,7 @@ public class AoCalculator {
/** used exclusively in irregular face to avoid new heap allocations each call. */
private final Vec3f vertexNormal = new Vec3f();
private void irregularFace(MutableQuadViewImpl quad) {
private void irregularFace(MutableQuadViewImpl quad, boolean shade) {
final Vec3f faceNorm = quad.faceNormal();
Vec3f normal;
final float[] w = this.w;
@ -307,7 +285,7 @@ public class AoCalculator {
if (!MathHelper.approximatelyEquals(0f, x)) {
final Direction face = x > 0 ? Direction.EAST : Direction.WEST;
final AoFaceData fd = gatherInsetFace(quad, i, face);
final AoFaceData fd = gatherInsetFace(quad, i, face, shade);
AoFace.get(face).weightFunc.apply(quad, i, w);
final float n = x * x;
final float a = fd.weigtedAo(w);
@ -325,7 +303,7 @@ public class AoCalculator {
if (!MathHelper.approximatelyEquals(0f, y)) {
final Direction face = y > 0 ? Direction.UP : Direction.DOWN;
final AoFaceData fd = gatherInsetFace(quad, i, face);
final AoFaceData fd = gatherInsetFace(quad, i, face, shade);
AoFace.get(face).weightFunc.apply(quad, i, w);
final float n = y * y;
final float a = fd.weigtedAo(w);
@ -343,7 +321,7 @@ public class AoCalculator {
if (!MathHelper.approximatelyEquals(0f, z)) {
final Direction face = z > 0 ? Direction.SOUTH : Direction.NORTH;
final AoFaceData fd = gatherInsetFace(quad, i, face);
final AoFaceData fd = gatherInsetFace(quad, i, face, shade);
AoFace.get(face).weightFunc.apply(quad, i, w);
final float n = z * z;
final float a = fd.weigtedAo(w);
@ -362,6 +340,19 @@ public class AoCalculator {
}
}
private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace, boolean shade) {
final int faceDataIndex = shade ? (isOnBlockFace ? lightFace.getId() : lightFace.getId() + 6) : (isOnBlockFace ? lightFace.getId() + 12 : lightFace.getId() + 18);
final int mask = 1 << faceDataIndex;
final AoFaceData result = faceData[faceDataIndex];
if ((completionFlags & mask) == 0) {
completionFlags |= mask;
computeFace(result, lightFace, isOnBlockFace, shade);
}
return result;
}
/**
* 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 when the second
@ -370,155 +361,145 @@ public class AoCalculator {
* in vanilla logic for some blocks that aren't full opaque cubes.
* Except for parameterization, the logic itself is practically identical to vanilla.
*/
private AoFaceData computeFace(Direction lightFace, boolean isOnBlockFace, boolean shade) {
final int faceDataIndex = isOnBlockFace ? lightFace.getId() : lightFace.getId() + 6;
final int mask = 1 << faceDataIndex;
final AoFaceData result = faceData[faceDataIndex];
private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlockFace, boolean shade) {
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 ((completionFlags & mask) == 0) {
completionFlags |= mask;
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);
}
AoFace aoFace = AoFace.get(lightFace);
// 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 (!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 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]);
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]);
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]);
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]);
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, lightFace);
searchState = world.getBlockState(searchPos);
if (isOnBlockFace || !searchState.isOpaqueFullCube(world, searchPos)) {
lightCenter = brightnessFunc.apply(searchPos, searchState);
} else {
lightCenter = brightnessFunc.apply(pos, blockState);
}
float aoCenter = aoFunc.apply(lightPos, world.getBlockState(lightPos));
float worldBrightness = world.getBrightness(lightFace, shade);
result.a0 = ((ao3 + ao0 + cAo1 + aoCenter) * 0.25F) * worldBrightness;
result.a1 = ((ao2 + ao0 + cAo0 + aoCenter) * 0.25F) * worldBrightness;
result.a2 = ((ao2 + ao1 + cAo2 + aoCenter) * 0.25F) * worldBrightness;
result.a3 = ((ao3 + ao1 + cAo3 + aoCenter) * 0.25F) * worldBrightness;
result.l0(meanBrightness(light3, light0, cLight1, lightCenter));
result.l1(meanBrightness(light2, light0, cLight0, lightCenter));
result.l2(meanBrightness(light2, light1, cLight2, lightCenter));
result.l3(meanBrightness(light3, light1, cLight3, lightCenter));
if (isOnBlockFace) {
lightPos.set(pos, lightFace);
} else {
lightPos.set(pos);
}
return result;
AoFace aoFace = AoFace.get(lightFace);
// 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 = light(searchPos, searchState);
final float ao0 = ao(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 = light(searchPos, searchState);
final float ao1 = ao(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 = light(searchPos, searchState);
final float ao2 = ao(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 = light(searchPos, searchState);
final float ao3 = ao(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 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]);
searchState = world.getBlockState(searchPos);
cAo0 = ao(searchPos, searchState);
cLight0 = light(searchPos, searchState);
}
if (!isClear3 && !isClear0) {
cAo1 = ao0;
cLight1 = light0;
} else {
searchPos.set(lightPos).move(aoFace.neighbors[0]).move(aoFace.neighbors[3]);
searchState = world.getBlockState(searchPos);
cAo1 = ao(searchPos, searchState);
cLight1 = light(searchPos, searchState);
}
if (!isClear2 && !isClear1) {
cAo2 = ao1;
cLight2 = light1;
} else {
searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[2]);
searchState = world.getBlockState(searchPos);
cAo2 = ao(searchPos, searchState);
cLight2 = light(searchPos, searchState);
}
if (!isClear3 && !isClear1) {
cAo3 = ao1;
cLight3 = light1;
} else {
searchPos.set(lightPos).move(aoFace.neighbors[1]).move(aoFace.neighbors[3]);
searchState = world.getBlockState(searchPos);
cAo3 = ao(searchPos, searchState);
cLight3 = light(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, lightFace);
searchState = world.getBlockState(searchPos);
if (isOnBlockFace || !searchState.isOpaqueFullCube(world, searchPos)) {
lightCenter = light(searchPos, searchState);
} else {
lightCenter = light(pos, blockState);
}
float aoCenter = ao(lightPos, world.getBlockState(lightPos));
float worldBrightness = world.getBrightness(lightFace, shade);
result.a0 = ((ao3 + ao0 + cAo1 + aoCenter) * 0.25F) * worldBrightness;
result.a1 = ((ao2 + ao0 + cAo0 + aoCenter) * 0.25F) * worldBrightness;
result.a2 = ((ao2 + ao1 + cAo2 + aoCenter) * 0.25F) * worldBrightness;
result.a3 = ((ao3 + ao1 + cAo3 + aoCenter) * 0.25F) * worldBrightness;
result.l0(meanBrightness(light3, light0, cLight1, lightCenter));
result.l1(meanBrightness(light2, light0, cLight0, lightCenter));
result.l2(meanBrightness(light2, light1, cLight2, lightCenter));
result.l3(meanBrightness(light3, light1, cLight3, lightCenter));
}
/**

View file

@ -38,6 +38,7 @@ import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer;
import net.fabricmc.fabric.impl.client.indigo.renderer.RenderMaterialImpl;
import net.fabricmc.fabric.impl.client.indigo.renderer.RenderMaterialImpl.Value;
import net.fabricmc.fabric.impl.client.indigo.renderer.helper.NormalHelper;
import net.fabricmc.fabric.impl.client.indigo.renderer.helper.TextureHelper;
@ -116,9 +117,13 @@ public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEm
data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(0, cullFace);
nominalFace(quad.getFace());
colorIndex(quad.getColorIndex());
if (!quad.hasShade()) {
material = RenderMaterialImpl.setDisableDiffuse((Value) material, 0, true);
}
material(material);
tag(0);
shade(quad.hasShade());
isGeometryInvalid = true;
return this;
}

View file

@ -18,7 +18,6 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.mesh;
import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_BITS;
import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_COLOR_INDEX;
import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_STRIDE;
import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.HEADER_TAG;
import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.QUAD_STRIDE;
import static net.fabricmc.fabric.impl.client.indigo.renderer.mesh.EncodingFormat.VERTEX_COLOR;
@ -52,7 +51,6 @@ public class QuadViewImpl implements QuadView {
/** True when geometry flags or light face may not match geometry. */
protected boolean isGeometryInvalid = true;
protected final Vec3f faceNormal = new Vec3f();
private boolean shade = true;
/** Size and where it comes from will vary in subtypes. But in all cases quad is fully encoded to array. */
protected int[] data;
@ -268,15 +266,7 @@ public class QuadViewImpl implements QuadView {
return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_V]);
}
public int vertexStart() {
return baseIndex + HEADER_STRIDE;
}
public boolean hasShade() {
return shade && !material().disableDiffuse(0);
}
public void shade(boolean shade) {
this.shade = shade;
return !material().disableDiffuse(0);
}
}

View file

@ -36,7 +36,6 @@ import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
import net.fabricmc.fabric.impl.client.indigo.renderer.RenderMaterialImpl;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper;
import net.fabricmc.fabric.impl.client.indigo.renderer.helper.GeometryHelper;
import net.fabricmc.fabric.impl.client.indigo.renderer.mesh.MutableQuadViewImpl;
/**
@ -234,9 +233,9 @@ public abstract class AbstractQuadRenderer {
* even for un-shaded quads. These are also applied with AO shading but that is done in AO calculator.
*/
private void shadeFlatQuad(MutableQuadViewImpl quad) {
if ((quad.geometryFlags() & GeometryHelper.AXIS_ALIGNED_FLAG) == 0 || quad.hasVertexNormals()) {
// Quads that aren't direction-aligned or that have vertex normals need to be shaded
// using interpolation - vanilla can't handle them. Generally only applies to modded models.
if (quad.hasVertexNormals()) {
// Quads that have vertex normals need to be shaded using interpolation - vanilla can't
// handle them. Generally only applies to modded models.
final float faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), quad.hasShade());
for (int i = 0; i < 4; i++) {

View file

@ -21,7 +21,6 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
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;
@ -43,10 +42,20 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoLuminanceFix;
*/
public class BlockRenderContext extends AbstractRenderContext {
private final BlockRenderInfo blockInfo = new BlockRenderInfo();
private final AoCalculator aoCalc = new AoCalculator(blockInfo, this::brightness, this::aoLevel);
private final AoCalculator aoCalc = new AoCalculator(blockInfo) {
@Override
public int light(BlockPos pos, BlockState state) {
return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, state, pos);
}
@Override
public float ao(BlockPos pos, BlockState state) {
return AoLuminanceFix.INSTANCE.apply(blockInfo.blockView, pos, state);
}
};
private VertexConsumer bufferBuilder;
private boolean didOutput = false;
// These are kept as fields to avoid the heap allocation for a supplier.
// BlockModelRenderer allows the caller to supply both the random object and seed.
private Random random;
@ -94,25 +103,11 @@ public class BlockRenderContext extends AbstractRenderContext {
}
};
private int brightness(BlockPos pos, BlockState state) {
if (blockInfo.blockView == null) {
return LightmapTextureManager.MAX_LIGHT_COORDINATE;
}
return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, state, pos);
}
private float aoLevel(BlockPos pos, BlockState state) {
final BlockRenderView blockView = blockInfo.blockView;
return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos, state);
}
private VertexConsumer outputBuffer(RenderLayer renderLayer) {
didOutput = true;
return bufferBuilder;
}
public boolean render(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, VertexConsumer buffer, Random random, long seed, int overlay) {
public void render(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrixStack, VertexConsumer buffer, boolean cull, Random random, long seed, int overlay) {
this.bufferBuilder = buffer;
this.matrix = matrixStack.peek().getPositionMatrix();
this.normalMatrix = matrixStack.peek().getNormalMatrix();
@ -120,9 +115,8 @@ public class BlockRenderContext extends AbstractRenderContext {
this.seed = seed;
this.overlay = overlay;
this.didOutput = false;
aoCalc.clear();
blockInfo.setBlockView(blockView);
blockInfo.prepareForWorld(blockView, cull);
blockInfo.prepareForBlock(state, pos, model.useAmbientOcclusion());
((FabricBakedModel) model).emitBlockQuads(blockView, state, pos, randomSupplier, this);
@ -131,8 +125,6 @@ public class BlockRenderContext extends AbstractRenderContext {
this.bufferBuilder = null;
this.random = null;
this.seed = seed;
return didOutput;
}
@Override

View file

@ -19,6 +19,9 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render;
import java.util.Random;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.color.block.BlockColors;
@ -39,7 +42,8 @@ import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
*/
public class BlockRenderInfo {
private final BlockColors blockColorMap = MinecraftClient.getInstance().getBlockColors();
public final Random random = new Random();
private final BlockPos.Mutable searchPos = new BlockPos.Mutable();
private final Random random = new Random();
public BlockRenderView blockView;
public BlockPos blockPos;
public BlockState blockState;
@ -47,6 +51,10 @@ public class BlockRenderInfo {
boolean defaultAo;
RenderLayer defaultLayer;
private boolean enableCulling;
private int cullCompletionFlags;
private int cullResultFlags;
public final Supplier<Random> randomSupplier = () -> {
final Random result = random;
long seed = this.seed;
@ -60,18 +68,22 @@ public class BlockRenderInfo {
return result;
};
public void setBlockView(BlockRenderView blockView) {
public void prepareForWorld(BlockRenderView blockView, boolean enableCulling) {
this.blockView = blockView;
this.enableCulling = enableCulling;
}
public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAO) {
this.blockPos = blockPos;
this.blockState = blockState;
// in the unlikely case seed actually matches this, we'll simply retrieve it more than one
// in the unlikely case seed actually matches this, we'll simply retrieve it more than once
seed = -1L;
defaultAo = modelAO && MinecraftClient.isAmbientOcclusionEnabled() && blockState.getLuminance() == 0;
defaultLayer = RenderLayers.getBlockLayer(blockState);
cullCompletionFlags = 0;
cullResultFlags = 0;
}
public void release() {
@ -83,8 +95,25 @@ public class BlockRenderInfo {
return 0xFF000000 | blockColorMap.getColor(blockState, blockView, blockPos, colorIndex);
}
boolean shouldDrawFace(Direction face) {
return true;
boolean shouldDrawFace(@Nullable Direction face) {
if (face == null || !enableCulling) {
return true;
}
final int mask = 1 << face.getId();
if ((cullCompletionFlags & mask) == 0) {
cullCompletionFlags |= mask;
if (Block.shouldDrawSide(blockState, blockView, blockPos, face, searchPos.set(blockPos, face))) {
cullResultFlags |= mask;
return true;
} else {
return false;
}
} else {
return (cullResultFlags & mask) != 0;
}
}
RenderLayer effectiveRenderLayer(BlendMode blendMode) {

View file

@ -1,56 +0,0 @@
/*
* 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.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
public class TerrainBlockRenderInfo extends BlockRenderInfo {
private int cullCompletionFlags;
private int cullResultFlags;
@Override
public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAO) {
super.prepareForBlock(blockState, blockPos, modelAO);
cullCompletionFlags = 0;
cullResultFlags = 0;
}
@Override
boolean shouldDrawFace(Direction face) {
if (face == null) {
return true;
}
final int mask = 1 << face.getId();
if ((cullCompletionFlags & mask) == 0) {
cullCompletionFlags |= mask;
if (Block.shouldDrawSide(blockState, blockView, blockPos, face, blockPos.offset(face))) {
cullResultFlags |= mask;
return true;
} else {
return false;
}
} else {
return (cullResultFlags & mask) != 0;
}
}
}

View file

@ -18,7 +18,6 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.render;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

View file

@ -20,8 +20,8 @@ import java.util.function.Consumer;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.chunk.BlockBufferBuilderStorage;
import net.minecraft.client.render.chunk.ChunkBuilder;
import net.minecraft.client.render.chunk.ChunkBuilder.BuiltChunk;
import net.minecraft.client.render.chunk.ChunkBuilder.ChunkData;
import net.minecraft.client.render.chunk.ChunkRendererRegion;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.math.MatrixStack;
@ -46,9 +46,19 @@ import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoCalculator;
public class TerrainRenderContext extends AbstractRenderContext {
public static final ThreadLocal<TerrainRenderContext> POOL = ThreadLocal.withInitial(TerrainRenderContext::new);
private final TerrainBlockRenderInfo blockInfo = new TerrainBlockRenderInfo();
private final BlockRenderInfo blockInfo = new BlockRenderInfo();
private final ChunkRenderInfo chunkInfo = new ChunkRenderInfo();
private final AoCalculator aoCalc = new AoCalculator(blockInfo, chunkInfo::cachedBrightness, chunkInfo::cachedAoLevel);
private final AoCalculator aoCalc = new AoCalculator(blockInfo) {
@Override
public int light(BlockPos pos, BlockState state) {
return chunkInfo.cachedBrightness(pos, state);
}
@Override
public float ao(BlockPos pos, BlockState state) {
return chunkInfo.cachedAoLevel(pos, state);
}
};
private final AbstractMeshConsumer meshConsumer = new AbstractMeshConsumer(blockInfo, chunkInfo::getInitializedBuffer, aoCalc, this::transform) {
@Override
@ -84,8 +94,8 @@ public class TerrainRenderContext extends AbstractRenderContext {
}
};
public void prepare(ChunkRendererRegion blockView, BuiltChunk chunkRenderer, ChunkData chunkData, BlockBufferBuilderStorage builders) {
blockInfo.setBlockView(blockView);
public void prepare(ChunkRendererRegion blockView, BuiltChunk chunkRenderer, ChunkBuilder.ChunkData chunkData, BlockBufferBuilderStorage builders) {
blockInfo.prepareForWorld(blockView, true);
chunkInfo.prepare(blockView, chunkRenderer, chunkData, builders);
}

View file

@ -50,11 +50,11 @@ public abstract class MixinBlockModelRenderer implements AccessBlockModelRendere
protected abstract void getQuadDimensions(BlockRenderView blockView, BlockState blockState, BlockPos blockPos, int[] vertexData, Direction face, float[] aoData, BitSet controlBits);
@Inject(at = @At("HEAD"), method = "render(Lnet/minecraft/world/BlockRenderView;Lnet/minecraft/client/render/model/BakedModel;Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;ZLjava/util/Random;JI)Z", cancellable = true)
private void hookRender(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrix, VertexConsumer buffer, boolean checkSides, Random rand, long seed, int overlay, CallbackInfoReturnable<Boolean> ci) {
private void hookRender(BlockRenderView blockView, BakedModel model, BlockState state, BlockPos pos, MatrixStack matrix, VertexConsumer buffer, boolean cull, Random rand, long seed, int overlay, CallbackInfoReturnable<Boolean> ci) {
if (!((FabricBakedModel) model).isVanillaAdapter()) {
BlockRenderContext context = fabric_contexts.get();
// Note that we do not support face-culling here (so checkSides is ignored)
ci.setReturnValue(context.render(blockView, model, state, pos, matrix, buffer, rand, seed, overlay));
context.render(blockView, model, state, pos, matrix, buffer, cull, rand, seed, overlay);
ci.cancel();
}
}