Continued work on block models

This commit is contained in:
mcrcortex
2024-01-27 01:32:50 +10:00
parent 1eeb8c069c
commit 3f6647af70
8 changed files with 216 additions and 43 deletions

View File

@@ -1,8 +1,6 @@
package me.cortex.zenith.client.core;
import me.cortex.zenith.client.config.ZenithConfig;
import me.cortex.zenith.client.core.model.ModelTextureBakery;
import me.cortex.zenith.client.core.model.TextureUtils;
import me.cortex.zenith.client.core.rendering.*;
import me.cortex.zenith.client.core.rendering.building.RenderGenerationService;
import me.cortex.zenith.client.core.util.DebugUtil;
@@ -11,8 +9,6 @@ import me.cortex.zenith.client.importers.WorldImporter;
import me.cortex.zenith.common.world.storage.FragmentedStorageBackendAdaptor;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.CropBlock;
import net.minecraft.block.SnowBlock;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum;
import net.minecraft.client.util.math.MatrixStack;
@@ -87,7 +83,7 @@ public class VoxelCore {
for (var state : this.world.getMapper().getStateEntries()) {
this.renderer.getModelManager().updateEntry(state.id, state.state);
this.renderer.getModelManager().addEntry(state.id, state.state);
}
//this.renderer.getModelManager().updateEntry(0, Blocks.GRASS_BLOCK.getDefaultState());
}

View File

@@ -1,4 +1,17 @@
package me.cortex.zenith.client.core.model;
import java.util.Arrays;
public record ColourDepthTextureData(int[] colour, int[] depth) {
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
var other = ((ColourDepthTextureData)obj);
return Arrays.equals(other.colour, this.colour) && Arrays.equals(other.depth, this.depth);
}
@Override
public int hashCode() {
return Arrays.hashCode(this.colour) ^ Arrays.hashCode(this.depth);
}
}

View File

@@ -2,13 +2,18 @@ package me.cortex.zenith.client.core.model;
import com.mojang.blaze3d.platform.GlConst;
import com.mojang.blaze3d.platform.GlStateManager;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
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.color.block.BlockColors;
import net.minecraft.client.render.RenderLayers;
import net.minecraft.registry.Registries;
import org.lwjgl.opengl.GL45C;
import net.minecraft.util.math.Direction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
@@ -34,6 +39,8 @@ public class ModelManager {
private final GlTexture textures;
private final int blockSampler = glGenSamplers();
private final int modelTextureSize;
//Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the
// section buffer reverse indexing
@@ -65,7 +72,7 @@ public class ModelManager {
//Provides a map from id -> model id as multiple ids might have the same internal model id
private final int[] idMappings;
private final int modelTextureSize;
private final Object2IntOpenHashMap<List<ColourDepthTextureData>> modelTexture2id = new Object2IntOpenHashMap<>();
public ModelManager(int modelTextureSize) {
this.modelTextureSize = modelTextureSize;
@@ -75,24 +82,59 @@ public class ModelManager {
this.textures = new GlTexture().store(GL_RGBA8, 1, modelTextureSize*3*256,modelTextureSize*2*256);
this.metadataCache = new long[1<<16];
this.idMappings = new int[1<<16];
Arrays.fill(this.idMappings, -1);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, 4);
this.modelTexture2id.defaultReturnValue(-1);
}
public void updateEntry(int id, BlockState blockState) {
//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");
}
int modelId = -1;
var textureData = this.bakery.renderFaces(blockState, 123456);
{//Deduplicate same entries
int possibleDuplicate = this.modelTexture2id.getInt(List.of(textureData));
if (possibleDuplicate != -1) {//Duplicate found
this.idMappings[blockId] = possibleDuplicate;
return possibleDuplicate;
} else {//Not a duplicate so create a new entry
modelId = this.modelTexture2id.size();
this.idMappings[blockId] = modelId;
this.modelTexture2id.put(List.of(textureData), modelId);
}
}
this.putTextures(modelId, textureData);
var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(blockState.getBlock()));
var blockRenderLayer = RenderLayers.getBlockLayer(blockState);
//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 textureData = this.bakery.renderFaces(blockState, 123456);
int depth = TextureUtils.computeDepth(textureData[0], TextureUtils.DEPTH_MODE_AVG);
var sizes = this.computeModelDepth(textureData);
int aaaa = 1;
this.putTextures(id, 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
@@ -104,6 +146,60 @@ public class ModelManager {
//TODO: need to make an option for like leaves to be fully opaque as by default they are not!!!!
//Models data is computed per face, the axis offset of the face is computed from the depth component of the rasterized data
// the size and offset of the face data is computed from the remaining pixels that where actually rastered (e.g. depth != 1.0)
// (note this might be able to be optimized for cuttout layers where it automatically tries to squish as much as possible)
// solid layer renders it as black so might need to add a bitset in the model data of whether the face is rendering
// in discard or solid mode maybe?
return modelId;
}
private int[] computeModelDepth(ColourDepthTextureData[] textures) {
int[] res = new int[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
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;
}
}
return res;
}
public long getModelMetadata(int blockId) {
int map = this.idMappings[blockId];
if (map == -1) {
throw new IllegalArgumentException("Id hasnt been computed yet");
}
return this.metadataCache[map];
}
public int getModelId(int blockId) {
int map = this.idMappings[blockId];
if (map == -1) {
throw new IllegalArgumentException("Id hasnt been computed yet");
}
return map;
}
private void putTextures(int id, ColourDepthTextureData[] textures) {
@@ -121,10 +217,6 @@ public class ModelManager {
}
}
public long getModelMetadata(int id) {
return this.metadataCache[id];
}
public int getBufferId() {
return this.modelBuffer.id;
}
@@ -132,6 +224,7 @@ public class ModelManager {
public int getTextureId() {
return this.textures.id;
}
public int getSamplerId() {
return this.blockSampler;
}

View File

@@ -1,15 +1,22 @@
package me.cortex.zenith.client.core.model;
import com.mojang.blaze3d.platform.GlConst;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.systems.VertexSorter;
import me.cortex.zenith.client.core.gl.GlFramebuffer;
import me.cortex.zenith.client.core.gl.GlTexture;
import me.cortex.zenith.client.core.gl.shader.Shader;
import me.cortex.zenith.client.core.gl.shader.ShaderLoader;
import me.cortex.zenith.client.core.gl.shader.ShaderType;
import me.jellysquid.mods.sodium.client.gl.shader.GlShader;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.GlUniform;
import net.minecraft.client.render.*;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.RotationAxis;
import net.minecraft.util.math.random.LocalRandom;
@@ -24,6 +31,8 @@ import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BI
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL20C.glUniformMatrix4fv;
import static org.lwjgl.opengl.GL20C.glUseProgram;
import static org.lwjgl.opengl.GL45C.glBlitNamedFramebuffer;
import static org.lwjgl.opengl.GL45C.glGetTextureImage;
@@ -34,6 +43,10 @@ public class ModelTextureBakery {
private final GlTexture colourTex;
private final GlTexture depthTex;
private final GlFramebuffer framebuffer;
private final Shader rasterShader = Shader.make()
.add(ShaderType.VERTEX, "zenith:bakery/position_tex.vsh")
.add(ShaderType.FRAGMENT, "zenith:bakery/position_tex.fsh")
.compile();
private static final List<MatrixStack> FACE_VIEWS = new ArrayList<>();
static {
@@ -42,8 +55,8 @@ public class ModelTextureBakery {
addView(0,180, 0);//Direction.NORTH
addView(0,0, 0);//Direction.SOUTH
//TODO: check these arnt the wrong way round
addView(0,90, -90);//Direction.EAST
addView(0,270, -90);//Direction.WEST
addView(0,90, 270);//Direction.EAST
addView(0,270, 270);//Direction.WEST
}
public ModelTextureBakery(int width, int height) {
@@ -64,6 +77,9 @@ public class ModelTextureBakery {
FACE_VIEWS.add(stack);
}
//TODO: For block entities, also somehow attempt to render the default block entity, e.g. chests and stuff
// cause that will result in ok looking micro details in the terrain
public ColourDepthTextureData[] renderFaces(BlockState state, long randomValue) {
var model = MinecraftClient.getInstance()
.getBakedModelManager()
@@ -82,9 +98,7 @@ public class ModelTextureBakery {
var renderLayer = RenderLayers.getBlockLayer(state);
if (renderLayer == RenderLayer.getTranslucent()) {
//TODO: TRANSLUCENT, must sort the quad first
}
renderLayer.startDrawing();
RenderSystem.depthMask(true);
RenderSystem.enableBlend();
@@ -92,8 +106,16 @@ public class ModelTextureBakery {
RenderSystem.enableCull();
RenderSystem.depthFunc(GL_LESS);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
RenderSystem.setShader(GameRenderer::getPositionTexProgram);
//TODO: bind the required uniforms and
this.rasterShader.bind();
RenderSystem.bindTexture(RenderSystem.getShaderTexture(0));
GlUniform.uniform1(0, 0);
RenderSystem.activeTexture(GlConst.GL_TEXTURE0);
if (renderLayer == RenderLayer.getTranslucent()) {
//TODO: TRANSLUCENT, must sort the quad first, or something idk
}
if (!state.getFluidState().isEmpty()) {
//TODO: render fluid
@@ -109,7 +131,7 @@ public class ModelTextureBakery {
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);
glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,0,0,256,256, GL_COLOR_BUFFER_BIT, GL_NEAREST);
return faces;
}
@@ -118,7 +140,11 @@ public class ModelTextureBakery {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE);
renderQuads(vc, state, model, stack, randomValue);
BufferRenderer.drawWithGlobalProgram(vc.end());
float[] mat = new float[4*4];
new Matrix4f(RenderSystem.getModelViewMatrix()).mul(RenderSystem.getProjectionMatrix()).get(mat);
glUniformMatrix4fv(1, false, mat);
BufferRenderer.draw(vc.end());
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
int[] colourData = new int[this.width*this.height];
@@ -141,5 +167,6 @@ public class ModelTextureBakery {
this.framebuffer.free();
this.colourTex.free();
this.depthTex.free();
this.rasterShader.free();
}
}

View File

@@ -4,15 +4,27 @@ import java.util.Map;
//Texturing utils to manipulate data from the model bakery
public class TextureUtils {
//Returns if any pixels are not fully transparent or fully translucent
public static boolean hasAlpha(ColourDepthTextureData texture) {
for (int pixel : texture.colour()) {
//Returns a bitset of
public static int computeColourData(ColourDepthTextureData texture) {
final var colour = texture.colour();
int bitset = 0b101;
for (int i = 0; i < colour.length && bitset != 0b010; i++) {
int pixel = colour[i];
int alpha = (pixel>>24)&0xFF;
if (alpha != 0 && alpha != 255) {
return true;
}
bitset |= (alpha != 0 && alpha != 255)?2:0;//Test if the pixel is translucent (has alpha)
bitset &= (alpha != 0)?3:7;// test if the pixel is not empty (assumes that if alpha is 0 it wasnt written to!!) FIXME: THIS MIGHT NOT BE CORRECT
bitset &= alpha != 255?6:7;// test if the pixel is anything but solid, (occlusion culling stuff)
}
return false;
return bitset;
}
//Returns the number of non pixels not written to
public static int getNonWrittenPixels(ColourDepthTextureData texture) {
int count = 0;
for (int pixel : texture.depth()) {
count += (((pixel>>8)&0xFFFFFF) == 0xFFFFFF)?1:0;
}
return count;
}
public static boolean isSolid(ColourDepthTextureData texture) {
@@ -29,7 +41,7 @@ public class TextureUtils {
public static final int DEPTH_MODE_MIN = 3;
//Computes depth info based on written pixel data
public static int computeDepth(ColourDepthTextureData texture, int mode) {
public static float computeDepth(ColourDepthTextureData texture, int mode) {
final var colourData = texture.colour();
final var depthData = texture.depth();
long a = 0;
@@ -37,11 +49,14 @@ public class TextureUtils {
if (mode == DEPTH_MODE_MIN) {
a = Long.MAX_VALUE;
}
if (mode == DEPTH_MODE_MAX) {
a = Long.MIN_VALUE;
}
for (int i = 0; i < colourData.length; i++) {
if ((colourData[0]&0xFF)==0) {
if ((colourData[i]&0xFF)==0) {
continue;
}
int depth = depthData[0]>>>8;
int depth = depthData[i]>>>8;
if (mode == DEPTH_MODE_AVG) {
a++;
b += depth;
@@ -56,10 +71,22 @@ public class TextureUtils {
if (a == 0) {
return -1;
}
return (int) (b/a);
} else if (mode == DEPTH_MODE_MAX || mode == DEPTH_MODE_MIN) {
return (int) a;
return u2fdepth((int) (b/a));
} else if (mode == DEPTH_MODE_MAX) {
if (a == Long.MIN_VALUE) {
return -1;
}
return u2fdepth((int) a);
} else if (mode == DEPTH_MODE_MIN) {
if (a == Long.MAX_VALUE) {
return -1;
}
return u2fdepth((int) a);
}
throw new IllegalArgumentException();
}
private static float u2fdepth(int depth) {
return (((float)depth)/(float)(1<<24));
}
}

View File

@@ -1,6 +1,5 @@
package me.cortex.zenith.client.core.rendering;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.zenith.client.core.gl.GlBuffer;
import me.cortex.zenith.client.core.gl.shader.Shader;
@@ -13,7 +12,6 @@ import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL45C;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
@@ -31,7 +29,6 @@ import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
private final Shader commandGen = Shader.make()
@@ -82,7 +79,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
return;
}
//this.getModelManager().updateEntry(this.frameId%(1<<15), Blocks.STONE.getDefaultState());
//this.getModelManager().addEntry(this.frameId%(1<<15), Blocks.OAK_BUTTON.getDefaultState());
RenderLayer.getCutoutMipped().startDrawing();
int oldActiveTexture = glGetInteger(GL_ACTIVE_TEXTURE);

View File

@@ -0,0 +1,8 @@
#version 430
layout(location=0) uniform sampler2D tex;
in vec2 texCoord;
out vec4 colour;
void main() {
colour = texture(tex, texCoord);
}

View File

@@ -0,0 +1,12 @@
#version 430
in vec3 pos;
in vec2 uv;
layout(location=1) uniform mat4 transform;
out vec2 texCoord;
void main() {
gl_Position = transform * vec4(pos, 1.0);
texCoord = uv;
}