diff --git a/src/main/java/net/coderbot/iris/mixin/vertices/MixinBufferBuilder.java b/src/main/java/net/coderbot/iris/mixin/vertices/MixinBufferBuilder.java index 259c0ae732..efb677b219 100644 --- a/src/main/java/net/coderbot/iris/mixin/vertices/MixinBufferBuilder.java +++ b/src/main/java/net/coderbot/iris/mixin/vertices/MixinBufferBuilder.java @@ -2,6 +2,7 @@ import java.nio.ByteBuffer; +import net.coderbot.iris.vertices.*; import org.jetbrains.annotations.Nullable; import org.lwjgl.opengl.GL11; import org.spongepowered.asm.mixin.Mixin; @@ -19,12 +20,6 @@ import net.coderbot.iris.block_rendering.BlockRenderingSettings; import net.coderbot.iris.vendored.joml.Vector3f; -import net.coderbot.iris.vertices.BlockSensitiveBufferBuilder; -import net.coderbot.iris.vertices.BufferBuilderPolygonView; -import net.coderbot.iris.vertices.ExtendedDataHelper; -import net.coderbot.iris.vertices.ExtendingBufferBuilder; -import net.coderbot.iris.vertices.IrisVertexFormats; -import net.coderbot.iris.vertices.NormalHelper; /** * Dynamically and transparently extends the vanilla vertex formats with additional data @@ -212,15 +207,6 @@ private void fillExtendedData(int vertexAmount) { midU /= vertexAmount; midV /= vertexAmount; - if (vertexAmount == 3) { - NormalHelper.computeFaceNormalTri(normal, polygon); - } else { - NormalHelper.computeFaceNormal(normal, polygon); - } - int packedNormal = NormalHelper.packNormal(normal, 0.0f); - - int tangent = NormalHelper.computeTangent(normal.x, normal.y, normal.z, polygon); - int midUOffset; int midVOffset; int normalOffset; @@ -237,11 +223,29 @@ private void fillExtendedData(int vertexAmount) { tangentOffset = 4; } - for (int vertex = 0; vertex < vertexAmount; vertex++) { - buffer.putFloat(nextElementByte - midUOffset - stride * vertex, midU); - buffer.putFloat(nextElementByte - midVOffset - stride * vertex, midV); - buffer.putInt(nextElementByte - normalOffset - stride * vertex, packedNormal); - buffer.putInt(nextElementByte - tangentOffset - stride * vertex, tangent); + if (vertexAmount == 3) { + // NormalHelper.computeFaceNormalTri(normal, polygon); // Removed to enable smooth shaded triangles. Mods rendering triangles with bad normals need to recalculate their normals manually or otherwise shading might be inconsistent. + + for (int vertex = 0; vertex < vertexAmount; vertex++) { + int packedNormal = buffer.getInt(nextElementByte - normalOffset - stride * vertex); // retrieve per-vertex normal + + int tangent = NormalHelper.computeTangentSmooth(NormI8.unpackX(packedNormal), NormI8.unpackY(packedNormal), NormI8.unpackZ(packedNormal), polygon); + + buffer.putFloat(nextElementByte - midUOffset - stride * vertex, midU); + buffer.putFloat(nextElementByte - midVOffset - stride * vertex, midV); + buffer.putInt(nextElementByte - tangentOffset - stride * vertex, tangent); + } + } else { + NormalHelper.computeFaceNormal(normal, polygon); + int packedNormal = NormI8.pack(normal.x, normal.y, normal.z, 0.0f); + int tangent = NormalHelper.computeTangent(normal.x, normal.y, normal.z, polygon); + + for (int vertex = 0; vertex < vertexAmount; vertex++) { + buffer.putFloat(nextElementByte - midUOffset - stride * vertex, midU); + buffer.putFloat(nextElementByte - midVOffset - stride * vertex, midV); + buffer.putInt(nextElementByte - normalOffset - stride * vertex, packedNormal); + buffer.putInt(nextElementByte - tangentOffset - stride * vertex, tangent); + } } } diff --git a/src/main/java/net/coderbot/iris/vertices/NormI8.java b/src/main/java/net/coderbot/iris/vertices/NormI8.java new file mode 100644 index 0000000000..d383290ca1 --- /dev/null +++ b/src/main/java/net/coderbot/iris/vertices/NormI8.java @@ -0,0 +1,97 @@ +package net.coderbot.iris.vertices; + +import com.mojang.math.Vector3f; +import net.minecraft.util.Mth; + +/** + * Provides some utilities for working with packed normal vectors. Each normal component provides 8 bits of + * precision in the range of [-1.0,1.0]. + * Copied from Sodium, licensed under the LGPLv3. Modified to support a W component. + * + * | 32 | 24 | 16 | 8 | + * | 0000 0000 | 0110 1100 | 0110 1100 | 0110 1100 | + * | W | X | Y | Z | + */ +public class NormI8 { + private static final int X_COMPONENT_OFFSET = 0; + private static final int Y_COMPONENT_OFFSET = 8; + private static final int Z_COMPONENT_OFFSET = 16; + private static final int W_COMPONENT_OFFSET = 24; + + /** + * The maximum value of a normal's vector component. + */ + private static final float COMPONENT_RANGE = 127.0f; + + /** + * Constant value which can be multiplied with a floating-point vector component to get the normalized value. The + * multiplication is slightly faster than a floating point division, and this code is a hot path which justifies it. + */ + private static final float NORM = 1.0f / COMPONENT_RANGE; + + public static int pack(Vector3f normal) { + return pack(normal.x(), normal.y(), normal.z(), 0); + } + + /** + * Packs the specified vector components into a 32-bit integer in XYZ ordering with the 8 bits of padding at the + * end. + * @param x The x component of the normal's vector + * @param y The y component of the normal's vector + * @param z The z component of the normal's vector + */ + public static int pack(float x, float y, float z, float w) { + return ((int) (x * 127) & 0xFF) | (((int) (y * 127) & 0xFF) << 8) | (((int) (z * 127) & 0xFF) << 16) | (((int) (w * 127) & 0xFF) << 24); + } + /** + * Packs the specified vector components into a 32-bit integer in XYZ ordering with the 8 bits of padding at the + * end. + * @param x The x component of the normal's vector + * @param y The y component of the normal's vector + * @param z The z component of the normal's vector + */ + public static int packColor(float x, float y, float z, float w) { + return ((int) (x * 127) & 0xFF) | (((int) (y * 127) & 0xFF) << 8) | (((int) (z * 127) & 0xFF) << 16) | (((int) w & 0xFF) << 24); + } + + /** + * Encodes a float in the range of -1.0..1.0 to a normalized unsigned integer in the range of 0..255 which can then + * be passed to graphics memory. + */ + private static int encode(float comp) { + // TODO: is the clamp necessary here? our inputs should always be normalized vector components + return ((int) (Mth.clamp(comp, -1.0F, 1.0F) * COMPONENT_RANGE) & 255); + } + + /** + * Unpacks the x-component of the packed normal, denormalizing it to a float in the range of -1.0..1.0. + * @param norm The packed normal + */ + public static float unpackX(int norm) { + return ((byte) ((norm >> X_COMPONENT_OFFSET) & 0xFF)) * NORM; + } + + /** + * Unpacks the y-component of the packed normal, denormalizing it to a float in the range of -1.0..1.0. + * @param norm The packed normal + */ + public static float unpackY(int norm) { + return ((byte) ((norm >> Y_COMPONENT_OFFSET) & 0xFF)) * NORM; + } + + /** + * Unpacks the z-component of the packed normal, denormalizing it to a float in the range of -1.0..1.0. + * @param norm The packed normal + */ + public static float unpackZ(int norm) { + return ((byte) ((norm >> Z_COMPONENT_OFFSET) & 0xFF)) * NORM; + } + + /** + * Unpacks the w-component of the packed normal, denormalizing it to a float in the range of -1.0..1.0. + * @param norm The packed normal + */ + public static float unpackW(int norm) { + return ((byte) ((norm >> W_COMPONENT_OFFSET) & 0xFF)) * NORM; + } +} \ No newline at end of file diff --git a/src/main/java/net/coderbot/iris/vertices/NormalHelper.java b/src/main/java/net/coderbot/iris/vertices/NormalHelper.java index de518fbe05..ca7f9f80d9 100644 --- a/src/main/java/net/coderbot/iris/vertices/NormalHelper.java +++ b/src/main/java/net/coderbot/iris/vertices/NormalHelper.java @@ -153,6 +153,116 @@ public static void computeFaceNormalTri(@NotNull Vector3f saveTo, TriView t) { saveTo.set(normX, normY, normZ); } + public static int computeTangentSmooth(float normalX, float normalY, float normalZ, TriView t) { + // Capture all of the relevant vertex positions + float x0 = t.x(0); + float y0 = t.y(0); + float z0 = t.z(0); + + float x1 = t.x(1); + float y1 = t.y(1); + float z1 = t.z(1); + + float x2 = t.x(2); + float y2 = t.y(2); + float z2 = t.z(2); + + // Project all vertices onto normal plane (for smooth normal support). Optionally skip this step for flat shading. + // Procedure: + // project v onto normal + // offset v by the projection to get the point on the plane + // project x0, y0, z0 onto normal + float d0 = x0 * normalX + y0 * normalY + z0 * normalZ; + float d1 = x1 * normalX + y1 * normalY + z1 * normalZ; + float d2 = x2 * normalX + y2 * normalY + z2 * normalZ; + + // offset x, y, z by the projection to get the projected point on the normal plane + x0 -= d0 * normalX; + y0 -= d0 * normalY; + z0 -= d0 * normalZ; + + x1 -= d1 * normalX; + y1 -= d1 * normalY; + z1 -= d1 * normalZ; + + x2 -= d2 * normalX; + y2 -= d2 * normalY; + z2 -= d2 * normalZ; + + + float edge1x = x1 - x0; + float edge1y = y1 - y0; + float edge1z = z1 - z0; + + float edge2x = x2 - x0; + float edge2y = y2 - y0; + float edge2z = z2 - z0; + + float u0 = t.u(0); + float v0 = t.v(0); + + float u1 = t.u(1); + float v1 = t.v(1); + + float u2 = t.u(2); + float v2 = t.v(2); + + float deltaU1 = u1 - u0; + float deltaV1 = v1 - v0; + float deltaU2 = u2 - u0; + float deltaV2 = v2 - v0; + + float fdenom = deltaU1 * deltaV2 - deltaU2 * deltaV1; + float f; + + if (fdenom == 0.0) { + f = 1.0f; + } else { + f = 1.0f / fdenom; + } + + float tangentx = f * (deltaV2 * edge1x - deltaV1 * edge2x); + float tangenty = f * (deltaV2 * edge1y - deltaV1 * edge2y); + float tangentz = f * (deltaV2 * edge1z - deltaV1 * edge2z); + float tcoeff = rsqrt(tangentx * tangentx + tangenty * tangenty + tangentz * tangentz); + tangentx *= tcoeff; + tangenty *= tcoeff; + tangentz *= tcoeff; + + float bitangentx = f * (-deltaU2 * edge1x + deltaU1 * edge2x); + float bitangenty = f * (-deltaU2 * edge1y + deltaU1 * edge2y); + float bitangentz = f * (-deltaU2 * edge1z + deltaU1 * edge2z); + float bitcoeff = rsqrt(bitangentx * bitangentx + bitangenty * bitangenty + bitangentz * bitangentz); + bitangentx *= bitcoeff; + bitangenty *= bitcoeff; + bitangentz *= bitcoeff; + + // predicted bitangent = tangent × normal + // Compute the determinant of the following matrix to get the cross product + // i j k + // tx ty tz + // nx ny nz + + // Be very careful when writing out complex multi-step calculations + // such as vector cross products! The calculation for pbitangentz + // used to be broken because it multiplied values in the wrong order. + + float pbitangentx = tangenty * normalZ - tangentz * normalY; + float pbitangenty = tangentz * normalX - tangentx * normalZ; + float pbitangentz = tangentx * normalY - tangenty * normalX; + + float dot = (bitangentx * pbitangentx) + (bitangenty * pbitangenty) + (bitangentz * pbitangentz); + float tangentW; + + if (dot < 0) { + tangentW = -1.0F; + } else { + tangentW = 1.0F; + } + + return NormI8.pack(tangentx, tangenty, tangentz, tangentW); + } + public static int computeTangent(float normalX, float normalY, float normalZ, TriView t) { // Capture all of the relevant vertex positions float x0 = t.x(0);