Skip to content

Commit

Permalink
Keep smooth normals
Browse files Browse the repository at this point in the history
Fixed #252 #254

Co-Authored-By: Sinan <[email protected]>
Co-Authored-By: IMS <[email protected]>
  • Loading branch information
3 people committed Sep 7, 2023
1 parent d6f99d4 commit a042617
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
}
}

Expand Down
97 changes: 97 additions & 0 deletions src/main/java/net/coderbot/iris/vertices/NormI8.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
110 changes: 110 additions & 0 deletions src/main/java/net/coderbot/iris/vertices/NormalHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit a042617

Please sign in to comment.