From 3e15b757b7d8ed266586c208bc2c959a22d6cae3 Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Sat, 5 Aug 2023 10:05:25 -0600 Subject: [PATCH 1/3] Initial fixes and improvements - Fix AoCalculator using the opaque property incorrectly - Fix AoCalculator not checking emissivity - Change AoCalculator lightCenter calculation to match Sodium - Use LightDataAccess to calculate flat lighting as well - Fix flat lighting quad orientation - Port new Indigo changes --- .../indium/renderer/aocalc/AoCalculator.java | 107 ++++++++++------ .../indium/renderer/mesh/QuadViewImpl.java | 5 + .../render/AbstractBlockRenderContext.java | 117 +++++++++++++----- .../render/NonTerrainBlockRenderContext.java | 6 + .../renderer/render/TerrainRenderContext.java | 17 ++- 5 files changed, 180 insertions(+), 72 deletions(-) diff --git a/src/main/java/link/infra/indium/renderer/aocalc/AoCalculator.java b/src/main/java/link/infra/indium/renderer/aocalc/AoCalculator.java index aa6c12e..992d6bd 100644 --- a/src/main/java/link/infra/indium/renderer/aocalc/AoCalculator.java +++ b/src/main/java/link/infra/indium/renderer/aocalc/AoCalculator.java @@ -16,13 +16,13 @@ package link.infra.indium.renderer.aocalc; -import static java.lang.Math.max; import static link.infra.indium.renderer.helper.GeometryHelper.AXIS_ALIGNED_FLAG; import static link.infra.indium.renderer.helper.GeometryHelper.CUBIC_FLAG; import static link.infra.indium.renderer.helper.GeometryHelper.LIGHT_FACE_FLAG; +import static me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess.getLightmap; import static me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess.unpackAO; +import static me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess.unpackEM; import static me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess.unpackFO; -import static me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess.getLightmap; import static me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess.unpackOP; import static net.minecraft.util.math.Direction.DOWN; import static net.minecraft.util.math.Direction.EAST; @@ -44,6 +44,7 @@ import link.infra.indium.renderer.mesh.QuadViewImpl; import link.infra.indium.renderer.render.BlockRenderInfo; import me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess; +import net.minecraft.client.render.LightmapTextureManager; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; @@ -369,64 +370,77 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo final int word0 = cache.get(lightPosX, lightPosY, lightPosZ, neighbors[0]); final int light0 = getLightmap(word0); final float ao0 = unpackAO(word0); - final boolean isClear0 = unpackOP(word0); + final boolean opaque0 = unpackOP(word0); + final boolean em0 = unpackEM(word0); final int word1 = cache.get(lightPosX, lightPosY, lightPosZ, neighbors[1]); final int light1 = getLightmap(word1); final float ao1 = unpackAO(word1); - final boolean isClear1 = unpackOP(word1); + final boolean opaque1 = unpackOP(word1); + final boolean em1 = unpackEM(word1); final int word2 = cache.get(lightPosX, lightPosY, lightPosZ, neighbors[2]); final int light2 = getLightmap(word2); final float ao2 = unpackAO(word2); - final boolean isClear2 = unpackOP(word2); + final boolean opaque2 = unpackOP(word2); + final boolean em2 = unpackEM(word2); final int word3 = cache.get(lightPosX, lightPosY, lightPosZ, neighbors[3]); final int light3 = getLightmap(word3); final float ao3 = unpackAO(word3); - final boolean isClear3 = unpackOP(word3); + final boolean opaque3 = unpackOP(word3); + final boolean em3 = unpackEM(word3); // c = corner - values at corners of face int cLight0, cLight1, cLight2, cLight3; float cAo0, cAo1, cAo2, cAo3; + boolean cEm0, cEm1, cEm2, cEm3; // 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) { + if (opaque2 && opaque0) { cAo0 = ao0; cLight0 = light0; + cEm0 = em0; } else { final int word02 = cache.get(lightPosX, lightPosY, lightPosZ, neighbors[0], neighbors[2]); cAo0 = unpackAO(word02); cLight0 = getLightmap(word02); + cEm0 = unpackEM(word02); } - if (!isClear3 && !isClear0) { + if (opaque3 && opaque0) { cAo1 = ao0; cLight1 = light0; + cEm1 = em0; } else { final int word03 = cache.get(lightPosX, lightPosY, lightPosZ, neighbors[0], neighbors[3]); cAo1 = unpackAO(word03); cLight1 = getLightmap(word03); + cEm1 = unpackEM(word03); } - if (!isClear2 && !isClear1) { + if (opaque2 && opaque1) { cAo2 = ao1; cLight2 = light1; + cEm2 = em1; } else { final int word12 = cache.get(lightPosX, lightPosY, lightPosZ, neighbors[1], neighbors[2]); cAo2 = unpackAO(word12); cLight2 = getLightmap(word12); + cEm2 = unpackEM(word12); } - if (!isClear3 && !isClear1) { + if (opaque3 && opaque1) { cAo3 = ao1; cLight3 = light1; + cEm3 = em1; } else { final int word13 = cache.get(lightPosX, lightPosY, lightPosZ, neighbors[1], neighbors[3]); cAo3 = unpackAO(word13); cLight3 = getLightmap(word13); + cEm3 = unpackEM(word13); } int centerWord = cache.get(lightPosX, lightPosY, lightPosZ); @@ -434,11 +448,15 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo // 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; + boolean emCenter; - if (isOnBlockFace || !unpackFO(centerWord)) { - lightCenter = getLightmap(centerWord); + if (isOnBlockFace && unpackFO(centerWord)) { + final int originWord = cache.get(x, y, z); + lightCenter = getLightmap(originWord); + emCenter = unpackEM(originWord); } else { - lightCenter = getLightmap(cache.get(x, y, z)); + lightCenter = getLightmap(centerWord); + emCenter = unpackEM(centerWord); } float aoCenter = unpackAO(centerWord); @@ -449,34 +467,51 @@ private void computeFace(AoFaceData result, Direction lightFace, boolean isOnBlo 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)); + result.l0(calculateCornerBrightness(light3, light0, cLight1, lightCenter, em3, em0, cEm1, emCenter)); + result.l1(calculateCornerBrightness(light2, light0, cLight0, lightCenter, em2, em0, cEm0, emCenter)); + result.l2(calculateCornerBrightness(light2, light1, cLight2, lightCenter, em2, em1, cEm2, emCenter)); + result.l3(calculateCornerBrightness(light3, light1, cLight3, lightCenter, em3, em1, cEm3, emCenter)); } - /** - * 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. - */ - private static int meanBrightness(int a, int b, int c, int d) { - return a == 0 || b == 0 || c == 0 || d == 0 ? meanEdgeBrightness(a, b, c, d) : meanInnerBrightness(a, b, c, d); - } + private static int calculateCornerBrightness(int a, int b, int c, int d, boolean aem, boolean bem, boolean cem, boolean dem) { + // FIX: Normalize corner vectors correctly to the minimum non-zero value between each one to prevent + // strange issues + if ((a == 0) || (b == 0) || (c == 0) || (d == 0)) { + // Find the minimum value between all corners + final int min = minNonZero(minNonZero(a, b), minNonZero(c, d)); + + // Normalize the corner values + a = Math.max(a, min); + b = Math.max(b, min); + c = Math.max(c, min); + d = Math.max(d, min); + } - 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 & 0xFF00FF; - } + // FIX: 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 (aem) { + a = LightmapTextureManager.MAX_LIGHT_COORDINATE; + } + if (bem) { + b = LightmapTextureManager.MAX_LIGHT_COORDINATE; + } + if (cem) { + c = LightmapTextureManager.MAX_LIGHT_COORDINATE; + } + if (dem) { + d = LightmapTextureManager.MAX_LIGHT_COORDINATE; + } - private static int nonZeroMin(int a, int b) { - if (a == 0) return b; - if (b == 0) return a; - return Math.min(a, b); + return ((a + b + c + d) >> 2) & 0xFF00FF; } - private static int meanEdgeBrightness(int a, int b, int c, int d) { - final int min = nonZeroMin(nonZeroMin(a, b), nonZeroMin(c, d)); - return meanInnerBrightness(max(a, min), max(b, min), max(c, min), max(d, min)); + private static int minNonZero(int a, int b) { + if (a == 0) { + return b; + } else if (b == 0) { + return a; + } + + return Math.min(a, b); } } diff --git a/src/main/java/link/infra/indium/renderer/mesh/QuadViewImpl.java b/src/main/java/link/infra/indium/renderer/mesh/QuadViewImpl.java index f145249..ee1c103 100644 --- a/src/main/java/link/infra/indium/renderer/mesh/QuadViewImpl.java +++ b/src/main/java/link/infra/indium/renderer/mesh/QuadViewImpl.java @@ -172,6 +172,11 @@ public boolean hasVertexNormals() { return normalFlags() != 0; } + /** True if all vertex normals have been set. */ + public boolean hasAllVertexNormals() { + return (normalFlags() & 0b1111) == 0b1111; + } + protected final int normalIndex(int vertexIndex) { return baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL; } diff --git a/src/main/java/link/infra/indium/renderer/render/AbstractBlockRenderContext.java b/src/main/java/link/infra/indium/renderer/render/AbstractBlockRenderContext.java index ae8178d..b87bc7c 100644 --- a/src/main/java/link/infra/indium/renderer/render/AbstractBlockRenderContext.java +++ b/src/main/java/link/infra/indium/renderer/render/AbstractBlockRenderContext.java @@ -21,15 +21,19 @@ import java.util.List; -import me.jellysquid.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials; -import me.jellysquid.mods.sodium.client.render.chunk.terrain.material.Material; import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; +import link.infra.indium.Indium; import link.infra.indium.renderer.IndiumRenderer; import link.infra.indium.renderer.aocalc.AoCalculator; +import link.infra.indium.renderer.aocalc.AoConfig; import link.infra.indium.renderer.helper.ColorHelper; import link.infra.indium.renderer.mesh.EncodingFormat; import link.infra.indium.renderer.mesh.MutableQuadViewImpl; +import me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess; +import me.jellysquid.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials; +import me.jellysquid.mods.sodium.client.render.chunk.terrain.material.Material; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; @@ -42,6 +46,7 @@ import net.minecraft.client.render.model.BakedQuad; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; +import net.minecraft.world.BlockRenderView; /** * Subclasses must set the {@link #blockInfo} and {@link #aoCalc} fields in their constructor. @@ -64,7 +69,7 @@ public void emitDirectly() { private final BakedModelConsumerImpl vanillaModelConsumer = new BakedModelConsumerImpl(); - private final BlockPos.Mutable lightPos = new BlockPos.Mutable(); + protected abstract LightDataAccess getLightCache(); protected abstract void bufferQuad(MutableQuadViewImpl quad, Material material); @@ -129,14 +134,14 @@ protected void shadeQuad(MutableQuadViewImpl quad, boolean isVanilla, boolean ao } } } else { - shadeFlatQuad(quad); + shadeFlatQuad(quad, isVanilla); if (emissive) { for (int i = 0; i < 4; i++) { quad.lightmap(i, LightmapTextureManager.MAX_LIGHT_COORDINATE); } } else { - final int brightness = flatBrightness(quad, blockInfo.blockState, blockInfo.blockPos); + final int brightness = flatBrightness(quad); for (int i = 0; i < 4; i++) { quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), brightness)); @@ -149,30 +154,57 @@ protected void shadeQuad(MutableQuadViewImpl quad, boolean isVanilla, boolean ao * Starting in 1.16 flat shading uses dimension-specific diffuse factors that can be < 1.0 * 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.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()); + private void shadeFlatQuad(MutableQuadViewImpl quad, boolean isVanilla) { + final boolean hasShade = quad.hasShade(); - for (int i = 0; i < 4; i++) { - quad.color(i, ColorHelper.multiplyRGB(quad.color(i), vertexShade(quad, i, faceShade))); + // Check the AO mode to match how shade is applied during smooth lighting + if ((Indium.AMBIENT_OCCLUSION_MODE == AoConfig.HYBRID && !isVanilla) || Indium.AMBIENT_OCCLUSION_MODE == AoConfig.ENHANCED) { + if (quad.hasAllVertexNormals()) { + for (int i = 0; i < 4; i++) { + float shade = normalShade(quad.normalX(i), quad.normalY(i), quad.normalZ(i), hasShade); + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), shade)); + } + } else { + final float faceShade; + + if ((quad.geometryFlags() & AXIS_ALIGNED_FLAG) != 0) { + faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), hasShade); + } else { + Vector3f faceNormal = quad.faceNormal(); + faceShade = normalShade(faceNormal.x, faceNormal.y, faceNormal.z, hasShade); + } + + if (quad.hasVertexNormals()) { + for (int i = 0; i < 4; i++) { + float shade; + + if (quad.hasNormal(i)) { + shade = normalShade(quad.normalX(i), quad.normalY(i), quad.normalZ(i), hasShade); + } else { + shade = faceShade; + } + + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), shade)); + } + } else { + if (faceShade != 1.0f) { + for (int i = 0; i < 4; i++) { + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), faceShade)); + } + } + } } } else { - final float diffuseShade = blockInfo.blockView.getBrightness(quad.lightFace(), quad.hasShade()); + final float faceShade = blockInfo.blockView.getBrightness(quad.lightFace(), hasShade); - if (diffuseShade != 1.0f) { + if (faceShade != 1.0f) { for (int i = 0; i < 4; i++) { - quad.color(i, ColorHelper.multiplyRGB(quad.color(i), diffuseShade)); + quad.color(i, ColorHelper.multiplyRGB(quad.color(i), faceShade)); } } } } - private float vertexShade(MutableQuadViewImpl quad, int vertexIndex, float faceShade) { - return quad.hasNormal(vertexIndex) ? normalShade(quad.normalX(vertexIndex), quad.normalY(vertexIndex), quad.normalZ(vertexIndex), quad.hasShade()) : faceShade; - } - /** * Finds mean of per-face shading factors weighted by normal components. * Not how light actually works but the vanilla diffuse shading model is a hack to start with @@ -213,26 +245,47 @@ private float normalShade(float normalX, float normalY, float normalZ, boolean h * Handles geometry-based check for using self brightness or neighbor brightness. * That logic only applies in flat lighting. */ - private int flatBrightness(MutableQuadViewImpl quad, BlockState blockState, BlockPos pos) { - lightPos.set(pos); - - // To mirror Vanilla's behavior, if the face has a cull-face, always sample the light value - // offset in that direction. See net.minecraft.client.render.block.BlockModelRenderer.renderQuadsFlat - // for reference. - if (quad.cullFace() != null) { - lightPos.move(quad.cullFace()); + private int flatBrightness(MutableQuadViewImpl quad) { + LightDataAccess lightCache = getLightCache(); + BlockPos pos = blockInfo.blockPos; + Direction cullFace = quad.cullFace(); + + // To match vanilla behavior, use the cull face if it exists/is available + if (cullFace != null) { + return getOffsetLightmap(lightCache, pos, cullFace); } else { final int flags = quad.geometryFlags(); - if ((flags & LIGHT_FACE_FLAG) != 0 || ((flags & AXIS_ALIGNED_FLAG) != 0 && blockState.isFullCube(blockInfo.blockView, pos))) { - lightPos.move(quad.lightFace()); + // If the face is aligned, use the light data above it + // To match vanilla behavior, also treat the face as aligned if it is parallel and the block state is a full cube + if ((flags & LIGHT_FACE_FLAG) != 0 || ((flags & AXIS_ALIGNED_FLAG) != 0 && LightDataAccess.unpackFC(lightCache.get(pos)))) { + return getOffsetLightmap(lightCache, pos, quad.lightFace()); + } else { + return LightDataAccess.getEmissiveLightmap(lightCache.get(pos)); } } - - // Unfortunately cannot use brightness cache here unless we implement one specifically for flat lighting. See #329 - return WorldRenderer.getLightmapCoordinates(blockInfo.blockView, blockState, lightPos); } + /** + * When vanilla computes an offset lightmap with flat lighting, it passes the original BlockState but the + * offset BlockPos to {@link WorldRenderer#getLightmapCoordinates(BlockRenderView, BlockState, BlockPos)}. + * This does not make much sense but fixes certain issues, primarily dark quads on light-emitting blocks + * behind tinted glass. {@link LightDataAccess} cannot efficiently store lightmaps computed with + * inconsistent values so this method exists to mirror vanilla behavior as closely as possible. + */ + private static int getOffsetLightmap(LightDataAccess lightCache, BlockPos pos, Direction face) { + int word = lightCache.get(pos); + + // Check emissivity of the origin state + if (LightDataAccess.unpackEM(word)) { + return LightmapTextureManager.MAX_LIGHT_COORDINATE; + } + + // Use world light values from the offset pos, but luminance from the origin pos + int adjWord = lightCache.get(pos, face); + return LightmapTextureManager.pack(Math.max(LightDataAccess.unpackBL(adjWord), LightDataAccess.unpackLU(word)), LightDataAccess.unpackSL(adjWord)); + } + /** * Consumer for vanilla baked models. Generally intended to give visual results matching a vanilla render, * however there could be subtle (and desirable) lighting variations so is good to be able to render diff --git a/src/main/java/link/infra/indium/renderer/render/NonTerrainBlockRenderContext.java b/src/main/java/link/infra/indium/renderer/render/NonTerrainBlockRenderContext.java index 871227d..897a011 100644 --- a/src/main/java/link/infra/indium/renderer/render/NonTerrainBlockRenderContext.java +++ b/src/main/java/link/infra/indium/renderer/render/NonTerrainBlockRenderContext.java @@ -18,6 +18,7 @@ import link.infra.indium.renderer.aocalc.AoCalculator; import link.infra.indium.renderer.mesh.MutableQuadViewImpl; +import me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess; import me.jellysquid.mods.sodium.client.render.chunk.terrain.material.Material; import net.minecraft.block.BlockState; import net.minecraft.client.render.VertexConsumer; @@ -39,6 +40,11 @@ public NonTerrainBlockRenderContext() { aoCalc = new AoCalculator(blockInfo, lightCache); } + @Override + protected LightDataAccess getLightCache() { + return lightCache; + } + @Override protected void bufferQuad(MutableQuadViewImpl quad, Material material) { bufferQuad(quad, vertexConsumer); diff --git a/src/main/java/link/infra/indium/renderer/render/TerrainRenderContext.java b/src/main/java/link/infra/indium/renderer/render/TerrainRenderContext.java index be27cdd..81c20f6 100644 --- a/src/main/java/link/infra/indium/renderer/render/TerrainRenderContext.java +++ b/src/main/java/link/infra/indium/renderer/render/TerrainRenderContext.java @@ -1,11 +1,14 @@ package link.infra.indium.renderer.render; +import org.joml.Vector3fc; + import link.infra.indium.mixin.sodium.AccessBlockRenderer; import link.infra.indium.other.SpriteFinderCache; import link.infra.indium.renderer.accessor.AccessBlockRenderCache; import link.infra.indium.renderer.aocalc.AoCalculator; import link.infra.indium.renderer.mesh.MutableQuadViewImpl; import me.jellysquid.mods.sodium.client.model.light.data.ArrayLightDataCache; +import me.jellysquid.mods.sodium.client.model.light.data.LightDataAccess; import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing; import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadOrientation; import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; @@ -25,9 +28,10 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.random.LocalRandom; -import org.joml.Vector3fc; public class TerrainRenderContext extends AbstractBlockRenderContext { + private final ArrayLightDataCache lightCache; + private final ChunkVertexEncoder.Vertex[] vertices = ChunkVertexEncoder.Vertex.uninitializedQuad(); private ChunkBuildBuffers buffers; @@ -38,7 +42,7 @@ public class TerrainRenderContext extends AbstractBlockRenderContext { public TerrainRenderContext(BlockRenderCache renderCache) { WorldSlice worldSlice = renderCache.getWorldSlice(); BlockOcclusionCache blockOcclusionCache = ((AccessBlockRenderer) renderCache.getBlockRenderer()).indium$occlusionCache(); - ArrayLightDataCache lightCache = ((AccessBlockRenderCache) renderCache).indium$getLightDataCache(); + lightCache = ((AccessBlockRenderCache) renderCache).indium$getLightDataCache(); blockInfo = new TerrainBlockRenderInfo(blockOcclusionCache); blockInfo.random = new LocalRandom(42L); @@ -50,6 +54,11 @@ public static TerrainRenderContext get(ChunkBuildContext buildContext) { return ((AccessBlockRenderCache) buildContext.cache).indium$getTerrainRenderContext(); } + @Override + protected LightDataAccess getLightCache() { + return lightCache; + } + @Override protected void bufferQuad(MutableQuadViewImpl quad, Material material) { ChunkModelBuilder builder = buffers.get(material); @@ -99,8 +108,8 @@ protected void shadeQuad(MutableQuadViewImpl quad, boolean isVanilla, boolean ao // Assumes aoCalc.ao / aoCalc.light holds the correct values for the current quad. quad.orientation(ModelQuadOrientation.orientByBrightness(aoCalc.ao, aoCalc.light)); } else { - // When using flat lighting, Sodium makes all quads use the flipped orientation. - quad.orientation(ModelQuadOrientation.FLIP); + // When using flat lighting, all quads use the normal orientation. + quad.orientation(ModelQuadOrientation.NORMAL); } } From 737fb1b15a8887ca5c022370e0cf81ceaee77c48 Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Sat, 5 Aug 2023 10:09:25 -0600 Subject: [PATCH 2/3] Fix whitespace --- .../render/AbstractBlockRenderContext.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/link/infra/indium/renderer/render/AbstractBlockRenderContext.java b/src/main/java/link/infra/indium/renderer/render/AbstractBlockRenderContext.java index b87bc7c..0293456 100644 --- a/src/main/java/link/infra/indium/renderer/render/AbstractBlockRenderContext.java +++ b/src/main/java/link/infra/indium/renderer/render/AbstractBlockRenderContext.java @@ -267,24 +267,24 @@ private int flatBrightness(MutableQuadViewImpl quad) { } /** - * When vanilla computes an offset lightmap with flat lighting, it passes the original BlockState but the - * offset BlockPos to {@link WorldRenderer#getLightmapCoordinates(BlockRenderView, BlockState, BlockPos)}. - * This does not make much sense but fixes certain issues, primarily dark quads on light-emitting blocks - * behind tinted glass. {@link LightDataAccess} cannot efficiently store lightmaps computed with - * inconsistent values so this method exists to mirror vanilla behavior as closely as possible. - */ - private static int getOffsetLightmap(LightDataAccess lightCache, BlockPos pos, Direction face) { - int word = lightCache.get(pos); - - // Check emissivity of the origin state - if (LightDataAccess.unpackEM(word)) { - return LightmapTextureManager.MAX_LIGHT_COORDINATE; - } - - // Use world light values from the offset pos, but luminance from the origin pos - int adjWord = lightCache.get(pos, face); - return LightmapTextureManager.pack(Math.max(LightDataAccess.unpackBL(adjWord), LightDataAccess.unpackLU(word)), LightDataAccess.unpackSL(adjWord)); - } + * When vanilla computes an offset lightmap with flat lighting, it passes the original BlockState but the + * offset BlockPos to {@link WorldRenderer#getLightmapCoordinates(BlockRenderView, BlockState, BlockPos)}. + * This does not make much sense but fixes certain issues, primarily dark quads on light-emitting blocks + * behind tinted glass. {@link LightDataAccess} cannot efficiently store lightmaps computed with + * inconsistent values so this method exists to mirror vanilla behavior as closely as possible. + */ + private static int getOffsetLightmap(LightDataAccess lightCache, BlockPos pos, Direction face) { + int word = lightCache.get(pos); + + // Check emissivity of the origin state + if (LightDataAccess.unpackEM(word)) { + return LightmapTextureManager.MAX_LIGHT_COORDINATE; + } + + // Use world light values from the offset pos, but luminance from the origin pos + int adjWord = lightCache.get(pos, face); + return LightmapTextureManager.pack(Math.max(LightDataAccess.unpackBL(adjWord), LightDataAccess.unpackLU(word)), LightDataAccess.unpackSL(adjWord)); + } /** * Consumer for vanilla baked models. Generally intended to give visual results matching a vanilla render, From 7a0e27e39ba608e6fe98aae96b0ccb2555c8ead9 Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Sat, 5 Aug 2023 10:45:19 -0600 Subject: [PATCH 3/3] Use normal face instead of cull face to decide vertex buffer --- .../renderer/helper/GeometryHelper.java | 33 +++++++++++++++++-- .../indium/renderer/mesh/EncodingFormat.java | 17 ++++++++-- .../indium/renderer/mesh/QuadViewImpl.java | 11 ++++++- .../renderer/render/TerrainRenderContext.java | 9 ++--- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/main/java/link/infra/indium/renderer/helper/GeometryHelper.java b/src/main/java/link/infra/indium/renderer/helper/GeometryHelper.java index ea16594..2990af0 100644 --- a/src/main/java/link/infra/indium/renderer/helper/GeometryHelper.java +++ b/src/main/java/link/infra/indium/renderer/helper/GeometryHelper.java @@ -20,12 +20,14 @@ import org.joml.Vector3f; +import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import me.jellysquid.mods.sodium.client.util.DirectionUtil; +import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; import net.minecraft.client.render.model.BakedQuad; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction.Axis; import net.minecraft.util.math.Direction.AxisDirection; - -import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; +import net.minecraft.util.math.MathHelper; /** * Static routines of general utility for renderer implementations. @@ -197,6 +199,33 @@ public static Direction lightFace(QuadView quad) { } } + // Copied from ModelQuadUtil.findNormalFace to prevent unnecessary allocation + public static ModelQuadFacing normalFace(QuadView quad) { + final Vector3f normal = quad.faceNormal(); + + if (!normal.isFinite()) { + return ModelQuadFacing.UNASSIGNED; + } + + float maxDot = 0; + Direction closestFace = null; + + for (Direction face : DirectionUtil.ALL_DIRECTIONS) { + float dot = normal.dot(face.getUnitVector()); + + if (dot > maxDot) { + maxDot = dot; + closestFace = face; + } + } + + if (closestFace != null && MathHelper.approximatelyEquals(maxDot, 1.0f)) { + return ModelQuadFacing.fromDirection(closestFace); + } + + return ModelQuadFacing.UNASSIGNED; + } + /** * Simple 4-way compare, doesn't handle NaN values. */ diff --git a/src/main/java/link/infra/indium/renderer/mesh/EncodingFormat.java b/src/main/java/link/infra/indium/renderer/mesh/EncodingFormat.java index d29a895..07625b7 100644 --- a/src/main/java/link/infra/indium/renderer/mesh/EncodingFormat.java +++ b/src/main/java/link/infra/indium/renderer/mesh/EncodingFormat.java @@ -20,6 +20,7 @@ import link.infra.indium.renderer.helper.GeometryHelper; import link.infra.indium.renderer.material.RenderMaterialImpl; +import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper; import net.minecraft.client.render.VertexFormat; @@ -77,13 +78,17 @@ private EncodingFormat() { } /** used for quick clearing of quad buffers. */ static final int[] EMPTY = new int[TOTAL_STRIDE]; - private static final int DIRECTION_MASK = MathHelper.smallestEncompassingPowerOfTwo(ModelHelper.NULL_FACE_ID) - 1; + private static final int DIRECTION_MASK = MathHelper.smallestEncompassingPowerOfTwo(ModelHelper.NULL_FACE_ID + 1) - 1; private static final int DIRECTION_BIT_COUNT = Integer.bitCount(DIRECTION_MASK); + private static final int FACING_MASK = MathHelper.smallestEncompassingPowerOfTwo(ModelQuadFacing.COUNT) - 1; + private static final int FACING_BIT_COUNT = Integer.bitCount(FACING_MASK); private static final int CULL_SHIFT = 0; private static final int CULL_INVERSE_MASK = ~(DIRECTION_MASK << CULL_SHIFT); private static final int LIGHT_SHIFT = CULL_SHIFT + DIRECTION_BIT_COUNT; private static final int LIGHT_INVERSE_MASK = ~(DIRECTION_MASK << LIGHT_SHIFT); - private static final int NORMALS_SHIFT = LIGHT_SHIFT + DIRECTION_BIT_COUNT; + private static final int NORMAL_FACE_SHIFT = LIGHT_SHIFT + DIRECTION_BIT_COUNT; + private static final int NORMAL_FACE_INVERSE_MASK = ~(FACING_MASK << NORMAL_FACE_SHIFT); + private static final int NORMALS_SHIFT = NORMAL_FACE_SHIFT + FACING_BIT_COUNT; private static final int NORMALS_COUNT = 4; private static final int NORMALS_MASK = (1 << NORMALS_COUNT) - 1; private static final int NORMALS_INVERSE_MASK = ~(NORMALS_MASK << NORMALS_SHIFT); @@ -115,6 +120,14 @@ static int lightFace(int bits, Direction face) { return (bits & LIGHT_INVERSE_MASK) | (ModelHelper.toFaceIndex(face) << LIGHT_SHIFT); } + static ModelQuadFacing normalFace(int bits) { + return ModelQuadFacing.VALUES[(bits >>> NORMAL_FACE_SHIFT) & FACING_MASK]; + } + + static int normalFace(int bits, ModelQuadFacing face) { + return (bits & NORMAL_FACE_INVERSE_MASK) | (face.ordinal() << NORMAL_FACE_SHIFT); + } + /** indicate if vertex normal has been set - bits correspond to vertex ordinals. */ static int normalFlags(int bits) { return (bits >>> NORMALS_SHIFT) & NORMALS_MASK; diff --git a/src/main/java/link/infra/indium/renderer/mesh/QuadViewImpl.java b/src/main/java/link/infra/indium/renderer/mesh/QuadViewImpl.java index ee1c103..328f2ba 100644 --- a/src/main/java/link/infra/indium/renderer/mesh/QuadViewImpl.java +++ b/src/main/java/link/infra/indium/renderer/mesh/QuadViewImpl.java @@ -41,6 +41,7 @@ import link.infra.indium.renderer.helper.GeometryHelper; import link.infra.indium.renderer.helper.NormalHelper; import link.infra.indium.renderer.material.RenderMaterialImpl; +import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView; import net.minecraft.util.math.Direction; @@ -51,7 +52,7 @@ public class QuadViewImpl implements QuadView { @Nullable protected Direction nominalFace; - /** True when face normal, light face, or geometry flags may not match geometry. */ + /** True when face normal, light face, normal face, or geometry flags may not match geometry. */ protected boolean isGeometryInvalid = true; protected final Vector3f faceNormal = new Vector3f(); @@ -81,6 +82,9 @@ protected void computeGeometry() { // depends on face normal data[baseIndex + HEADER_BITS] = EncodingFormat.lightFace(data[baseIndex + HEADER_BITS], GeometryHelper.lightFace(this)); + // depends on face normal + data[baseIndex + HEADER_BITS] = EncodingFormat.normalFace(data[baseIndex + HEADER_BITS], GeometryHelper.normalFace(this)); + // depends on light face data[baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS], GeometryHelper.computeShapeFlags(this)); } @@ -225,6 +229,11 @@ public final Direction lightFace() { return EncodingFormat.lightFace(data[baseIndex + HEADER_BITS]); } + public final ModelQuadFacing normalFace() { + computeGeometry(); + return EncodingFormat.normalFace(data[baseIndex + HEADER_BITS]); + } + @Override @Nullable public final Direction nominalFace() { diff --git a/src/main/java/link/infra/indium/renderer/render/TerrainRenderContext.java b/src/main/java/link/infra/indium/renderer/render/TerrainRenderContext.java index 81c20f6..86b3ff1 100644 --- a/src/main/java/link/infra/indium/renderer/render/TerrainRenderContext.java +++ b/src/main/java/link/infra/indium/renderer/render/TerrainRenderContext.java @@ -25,7 +25,6 @@ import net.minecraft.util.crash.CrashException; import net.minecraft.util.crash.CrashReport; import net.minecraft.util.crash.CrashReportSection; -import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.random.LocalRandom; @@ -61,11 +60,6 @@ protected LightDataAccess getLightCache() { @Override protected void bufferQuad(MutableQuadViewImpl quad, Material material) { - ChunkModelBuilder builder = buffers.get(material); - - Direction cullFace = quad.cullFace(); - var vertexBuffer = builder.getVertexBuffer(cullFace != null ? ModelQuadFacing.fromDirection(cullFace) : ModelQuadFacing.UNASSIGNED); - Vector3fc origin = this.origin; Vec3d modelOffset = this.modelOffset; @@ -89,6 +83,9 @@ protected void bufferQuad(MutableQuadViewImpl quad, Material material) { out.light = quad.lightmap(srcIndex); } + ChunkModelBuilder builder = buffers.get(material); + ModelQuadFacing normalFace = quad.normalFace(); + var vertexBuffer = builder.getVertexBuffer(normalFace); vertexBuffer.push(vertices, material); Sprite sprite = quad.cachedSprite();