diff --git a/src/main/java/me/cortex/zenith/client/core/model/ColourDepthTextureData.java b/src/main/java/me/cortex/zenith/client/core/model/ColourDepthTextureData.java index ca19f3f0..c9c3d689 100644 --- a/src/main/java/me/cortex/zenith/client/core/model/ColourDepthTextureData.java +++ b/src/main/java/me/cortex/zenith/client/core/model/ColourDepthTextureData.java @@ -2,7 +2,7 @@ package me.cortex.zenith.client.core.model; import java.util.Arrays; -public record ColourDepthTextureData(int[] colour, int[] depth) { +public record ColourDepthTextureData(int[] colour, int[] depth, int width, int height) { @Override public boolean equals(Object obj) { if (obj == null) return false; @@ -12,6 +12,6 @@ public record ColourDepthTextureData(int[] colour, int[] depth) { @Override public int hashCode() { - return Arrays.hashCode(this.colour) ^ Arrays.hashCode(this.depth); + return (this.width * 312337173 * (Arrays.hashCode(this.colour) ^ Arrays.hashCode(this.depth))) ^ this.height; } } diff --git a/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java b/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java index 91041202..ab021137 100644 --- a/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java +++ b/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java @@ -7,6 +7,7 @@ import me.cortex.zenith.client.core.gl.GlBuffer; import me.cortex.zenith.client.core.gl.GlTexture; import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.RenderLayers; import net.minecraft.registry.Registries; import net.minecraft.util.math.Direction; @@ -97,9 +98,9 @@ public class ModelManager { //TODO: so need a few things, per face sizes and offsets, the sizes should be computed from the pixels and find the minimum bounding pixel // while the depth is computed from the depth buffer data public int addEntry(int blockId, BlockState blockState) { - if (this.idMappings[blockId] != -1) { - throw new IllegalArgumentException("Trying to add entry for duplicate id"); - } + //if (this.idMappings[blockId] != -1) { + // throw new IllegalArgumentException("Trying to add entry for duplicate id"); + //} int modelId = -1; var textureData = this.bakery.renderFaces(blockState, 123456); @@ -107,7 +108,8 @@ public class ModelManager { int possibleDuplicate = this.modelTexture2id.getInt(List.of(textureData)); if (possibleDuplicate != -1) {//Duplicate found this.idMappings[blockId] = possibleDuplicate; - return possibleDuplicate; + modelId = possibleDuplicate; + //return possibleDuplicate; } else {//Not a duplicate so create a new entry modelId = this.modelTexture2id.size(); this.idMappings[blockId] = modelId; @@ -119,22 +121,15 @@ public class ModelManager { var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(blockState.getBlock())); var blockRenderLayer = RenderLayers.getBlockLayer(blockState); + int checkMode = blockRenderLayer==RenderLayer.getSolid()?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA; + //If it is the solid layer, it is _always_ going to occlude fully for all written pixels, even if they are 100% translucent, this should save alot of resources // if it is cutout it might occlude might not, need to test // if it is translucent it will _never_ occlude //NOTE: this is excluding fluid states - //This also checks if there is a block colour resolver for the given blockstate and marks that the block has a resolver - var sizes = this.computeModelDepth(textureData); - for (int face = 0; face < 6; face++) { - if (sizes[face] == -1) {//Face is empty, so ignore - continue; - } - //TODO: combine all the methods into a single - //boolean fullyOccluding = TextureUtils.hasAlpha() - } //Model data contains, the quad size and offset of each face and whether the face needs to be resolved with a colour modifier @@ -162,7 +157,20 @@ public class ModelManager { + //This also checks if there is a block colour resolver for the given blockstate and marks that the block has a resolver + var sizes = this.computeModelDepth(textureData, checkMode); + for (int face = 0; face < 6; face++) { + if (sizes[face] < -0.1) {//Face is empty, so ignore + continue; + } + //TODO: replace this with a more intelligent method that + // if using solid use TextureUtils.computeBounds() with depth testing + // if using translucent or transparent compare if alpha is 0 + var faceSize = TextureUtils.computeBounds(textureData[face], checkMode); + + int eee = 0; + } @@ -170,17 +178,30 @@ public class ModelManager { return modelId; } - private int[] computeModelDepth(ColourDepthTextureData[] textures) { - int[] res = new int[6]; + + + + + + + + + + + + + + private float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) { + float[] res = new float[6]; for (var dir : Direction.values()) { var data = textures[dir.getId()]; - float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_MIN);//Compute the min float depth, smaller means closer to the camera, range 0-1 + float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_MIN, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1 int depth = Math.round(fd * this.modelTextureSize); //If fd is -1, it means that there was nothing rendered on that face and it should be discarded if (fd < -0.1) { res[dir.ordinal()] = -1; } else { - res[dir.ordinal()] = depth; + res[dir.ordinal()] = ((float) depth)/this.modelTextureSize; } } return res; diff --git a/src/main/java/me/cortex/zenith/client/core/model/ModelTextureBakery.java b/src/main/java/me/cortex/zenith/client/core/model/ModelTextureBakery.java index ca646945..8e03671b 100644 --- a/src/main/java/me/cortex/zenith/client/core/model/ModelTextureBakery.java +++ b/src/main/java/me/cortex/zenith/client/core/model/ModelTextureBakery.java @@ -90,7 +90,12 @@ public class ModelTextureBakery { var oldProjection = new Matrix4f(RenderSystem.getProjectionMatrix()); GL11C.glViewport(0, 0, this.width, this.height); - RenderSystem.setProjectionMatrix(new Matrix4f().identity().scale(2,2,-1f).translate(-0.5f, -0.5f, 0.0f), VertexSorter.BY_Z); + RenderSystem.setProjectionMatrix(new Matrix4f().identity().set(new float[]{ + 2,0,0,0, + 0, 2,0,0, + 0,0, -1f,0, + -1,-1,0,1, + }), VertexSorter.BY_Z); glClearColor(0,0,0,0); glClearDepth(1); @@ -100,6 +105,8 @@ public class ModelTextureBakery { var renderLayer = RenderLayers.getBlockLayer(state); renderLayer.startDrawing(); + glEnable(GL_STENCIL_TEST); + glDepthRange(0, 1); RenderSystem.depthMask(true); RenderSystem.enableBlend(); RenderSystem.enableDepthTest(); @@ -107,7 +114,10 @@ public class ModelTextureBakery { RenderSystem.depthFunc(GL_LESS); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - //TODO: bind the required uniforms and + glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + glStencilFunc(GL_ALWAYS, 1, 0xFF); + glStencilMask(0xFF); + this.rasterShader.bind(); RenderSystem.bindTexture(RenderSystem.getShaderTexture(0)); GlUniform.uniform1(0, 0); @@ -124,14 +134,18 @@ public class ModelTextureBakery { var faces = new ColourDepthTextureData[FACE_VIEWS.size()]; for (int i = 0; i < faces.length; i++) { faces[i] = captureView(state, model, FACE_VIEWS.get(i), randomValue); + //glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,300*(i%3),300*(i/3),300*(i%3)+256,300*(i/3)+256, GL_COLOR_BUFFER_BIT, GL_NEAREST); } renderLayer.endDrawing(); + glDisable(GL_STENCIL_TEST); RenderSystem.setProjectionMatrix(oldProjection, VertexSorter.BY_DISTANCE); glBindFramebuffer(GL_FRAMEBUFFER, oldFB); GL11C.glViewport(GlStateManager.Viewport.getX(), GlStateManager.Viewport.getY(), GlStateManager.Viewport.getWidth(), GlStateManager.Viewport.getHeight()); - glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,0,0,256,256, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + //TODO: FIXME: fully revert the state of opengl + return faces; } @@ -142,7 +156,7 @@ public class ModelTextureBakery { renderQuads(vc, state, model, stack, randomValue); float[] mat = new float[4*4]; - new Matrix4f(RenderSystem.getModelViewMatrix()).mul(RenderSystem.getProjectionMatrix()).get(mat); + RenderSystem.getProjectionMatrix().get(mat); glUniformMatrix4fv(1, false, mat); BufferRenderer.draw(vc.end()); @@ -151,7 +165,7 @@ public class ModelTextureBakery { int[] depthData = new int[this.width*this.height]; glGetTextureImage(this.colourTex.id, 0, GL_RGBA, GL_UNSIGNED_BYTE, colourData); glGetTextureImage(this.depthTex.id, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, depthData); - return new ColourDepthTextureData(colourData, depthData); + return new ColourDepthTextureData(colourData, depthData, this.width, this.height); } private static void renderQuads(BufferBuilder builder, BlockState state, BakedModel model, MatrixStack stack, long randomValue) { diff --git a/src/main/java/me/cortex/zenith/client/core/model/TextureUtils.java b/src/main/java/me/cortex/zenith/client/core/model/TextureUtils.java index 326d5380..c72358fb 100644 --- a/src/main/java/me/cortex/zenith/client/core/model/TextureUtils.java +++ b/src/main/java/me/cortex/zenith/client/core/model/TextureUtils.java @@ -22,7 +22,7 @@ public class TextureUtils { public static int getNonWrittenPixels(ColourDepthTextureData texture) { int count = 0; for (int pixel : texture.depth()) { - count += (((pixel>>8)&0xFFFFFF) == 0xFFFFFF)?1:0; + count += ((pixel&0xFF) == 0)?1:0; } return count; } @@ -36,12 +36,27 @@ public class TextureUtils { return true; } + public static final int WRITE_CHECK_STENCIL = 1; + public static final int WRITE_CHECK_DEPTH = 2; + public static final int WRITE_CHECK_ALPHA = 3; + private static boolean wasPixelWritten(ColourDepthTextureData data, int mode, int index) { + if (mode == WRITE_CHECK_STENCIL) { + return (data.depth()[index]&0xFF)!=0; + } else if (mode == WRITE_CHECK_DEPTH) { + return (data.depth()[index]>>>8)!=((1<<24)-1); + } else if (mode == WRITE_CHECK_ALPHA) { + return ((data.colour()[index]>>>24)&0xff)!=0; + } + throw new IllegalArgumentException(); + } + public static final int DEPTH_MODE_AVG = 1; public static final int DEPTH_MODE_MAX = 2; public static final int DEPTH_MODE_MIN = 3; + //Computes depth info based on written pixel data - public static float computeDepth(ColourDepthTextureData texture, int mode) { + public static float computeDepth(ColourDepthTextureData texture, int mode, int checkMode) { final var colourData = texture.colour(); final var depthData = texture.depth(); long a = 0; @@ -53,7 +68,7 @@ public class TextureUtils { a = Long.MIN_VALUE; } for (int i = 0; i < colourData.length; i++) { - if ((colourData[i]&0xFF)==0) { + if (!wasPixelWritten(texture, checkMode, i)) { continue; } int depth = depthData[i]>>>8; @@ -87,6 +102,74 @@ public class TextureUtils { } private static float u2fdepth(int depth) { - return (((float)depth)/(float)(1<<24)); + float depthF = (float) ((double)depth/((1<<24)-1)); + //https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthRange.xhtml + // due to this and the unsigned bullshit, i believe the depth value needs to get multiplied by 2 + depthF *= 2; + if (depthF > 1.00001f) { + throw new IllegalArgumentException("Depth greater than 1"); + } + return depthF; + } + + + //NOTE: data goes from bottom left to top right (x first then y) + public static int[] computeBounds(ColourDepthTextureData data, int checkMode) { + final var depth = data.depth(); + //Compute x bounds first + int minX = 0; + minXCheck: + do { + for (int y = 0; y < data.height(); y++) { + int idx = minX + (y * data.width()); + if (wasPixelWritten(data, checkMode, idx)) { + break minXCheck;//pixel was written too so break from loop + } + } + minX++; + } while (minX != data.width()); + + int maxX = data.width()-1; + maxXCheck: + do { + for (int y = data.height()-1; y!=-1; y--) { + int idx = maxX + (y * data.width()); + if (wasPixelWritten(data, checkMode, idx)) { + break maxXCheck;//pixel was written too so break from loop + } + } + maxX--; + } while (maxX != -1); + maxX++; + + + //Compute y bounds + int minY = 0; + minYCheck: + do { + for (int x = 0; x < data.width(); x++) { + int idx = (minY * data.height()) + x; + if (wasPixelWritten(data, checkMode, idx)) { + break minYCheck;//pixel was written too + } + } + minY++; + } while (minY != data.height()); + + + int maxY = data.height()-1; + maxYCheck: + do { + for (int x = data.width()-1; x!=-1; x--) { + int idx = (maxY * data.height()) + x; + if (wasPixelWritten(data, checkMode, idx)) { + break maxYCheck;//pixel was written too so break from loop + } + } + maxY--; + } while (maxY != -1); + maxY++; + + return new int[]{minX, maxX, minY, maxY}; } } diff --git a/src/main/java/me/cortex/zenith/client/core/rendering/Gl46FarWorldRenderer.java b/src/main/java/me/cortex/zenith/client/core/rendering/Gl46FarWorldRenderer.java index 86f6c7b5..ec1cc68c 100644 --- a/src/main/java/me/cortex/zenith/client/core/rendering/Gl46FarWorldRenderer.java +++ b/src/main/java/me/cortex/zenith/client/core/rendering/Gl46FarWorldRenderer.java @@ -7,8 +7,11 @@ import me.cortex.zenith.client.core.gl.shader.ShaderType; import me.cortex.zenith.client.core.rendering.util.UploadStream; import me.cortex.zenith.client.mixin.joml.AccessFrustumIntersection; import net.minecraft.block.Blocks; +import net.minecraft.block.WallMountedBlock; +import net.minecraft.block.enums.BlockFace; import net.minecraft.client.render.RenderLayer; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.Direction; import org.joml.Matrix4f; import org.joml.Vector3f; import org.lwjgl.opengl.GL11C; @@ -75,11 +78,13 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer { } public void renderFarAwayOpaque(MatrixStack stack, double cx, double cy, double cz) { + this.getModelManager().addEntry(this.frameId%(1<<15), Blocks.OAK_BUTTON.getDefaultState().with(WallMountedBlock.FACE, BlockFace.FLOOR).with(WallMountedBlock.FACING, Direction.SOUTH)); + //this.getModelManager().addEntry(this.frameId%(1<<15), Blocks.HEAVY_WEIGHTED_PRESSURE_PLATE.getDefaultState()); + //this.getModelManager().addEntry(this.frameId%(1<<15), Blocks.COMPARATOR.getDefaultState()); if (this.geometry.getSectionCount() == 0) { return; } - //this.getModelManager().addEntry(this.frameId%(1<<15), Blocks.OAK_BUTTON.getDefaultState()); RenderLayer.getCutoutMipped().startDrawing(); int oldActiveTexture = glGetInteger(GL_ACTIVE_TEXTURE);