From 219a8085f8591bcce488345b6f0ac4ff3e498889 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Sun, 29 Oct 2023 16:04:47 +0100 Subject: [PATCH] skeletal: change normal encoding, shade lighting interpolation --- .../skeletal/mesh/SkeletalMeshUtil.kt | 16 ++- .../shader/includes/skeletal/shade.glsl | 36 ++++-- .../block/state/baked/SkeletalShadeTest.kt | 112 ++++++++++++++++++ 3 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 src/test/java/de/bixilon/minosoft/gui/rendering/models/block/state/baked/SkeletalShadeTest.kt diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/skeletal/mesh/SkeletalMeshUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/skeletal/mesh/SkeletalMeshUtil.kt index 3e74ff45c..f927123dc 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/skeletal/mesh/SkeletalMeshUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/skeletal/mesh/SkeletalMeshUtil.kt @@ -14,17 +14,21 @@ package de.bixilon.minosoft.gui.rendering.skeletal.mesh import de.bixilon.kotlinglm.vec3.Vec3 +import kotlin.math.abs object SkeletalMeshUtil { - private fun encodePart(part: Float): Int { - val unsigned = (part + 1.0f) / 2.0f // remove negative sign - return (unsigned * 15.0f).toInt() and 0x0F + + private fun encodeY(part: Float): Int { + if (part <= -1.0f) return 0 + if (part >= 1.0f) return 0x0F + if (part < 0.0f) return ((part + 1.0f) * 8.0f).toInt() and 0x0F + return 8 + (part * 7.0f).toInt() } fun encodeNormal(normal: Vec3): Int { - val x = encodePart(normal.x) - val y = encodePart(normal.y) - val z = encodePart(normal.z) + val x = (abs(normal.x) * 15.0f).toInt() + val y = encodeY(normal.y) + val z = (abs(normal.z) * 15.0f).toInt() return (y shl 8) or (z shl 4) or (x) } diff --git a/src/main/resources/assets/minosoft/rendering/shader/includes/skeletal/shade.glsl b/src/main/resources/assets/minosoft/rendering/shader/includes/skeletal/shade.glsl index 2c4ef685a..e50413bdc 100644 --- a/src/main/resources/assets/minosoft/rendering/shader/includes/skeletal/shade.glsl +++ b/src/main/resources/assets/minosoft/rendering/shader/includes/skeletal/shade.glsl @@ -10,16 +10,18 @@ * * This software is not affiliated with Mojang AB, the original developer of Minecraft. */ +#define DEGREE_90 1.5707964f float decodeNormalPart(uint data) { - return (data / 15.0f) * 2.0f - 1.0f; + if (data <= 8u) return (data / 8.0f) - 1.0f; + return (data - 8u) / 7.0f; } vec3 decodeNormal(uint normal) { uint x = normal & 0x0Fu; uint y = normal >> 8u & 0x0Fu; uint z = normal >> 4u & 0x0Fu; - return vec3(decodeNormalPart(x), decodeNormalPart(y), decodeNormalPart(z)); + return vec3(x / 15.0f, decodeNormalPart(y), z / 15.0f); } vec3 transformNormal(vec3 normal, mat4 transform) { @@ -27,12 +29,26 @@ vec3 transformNormal(vec3 normal, mat4 transform) { return mat3(transform) * normal; } -float getShade(vec3 normal) { - // TODO: interpolate between 3 sides - if (normal.y < -0.5f) return 0.5f; - if (normal.y > 0.5f) return 1.0f; - if (normal.x < -0.5f || normal.x > 0.5f) return 0.6f; - if (normal.z < -0.5f || normal.z > 0.5f) return 0.8f; - - return 1.0f; +float interpolateShade(float delta, float max) { + if (delta < 0.0f) delta = -delta; + if (delta <= 0.0f) return 0.0f; + if (delta >= 1.0f) return max; + return delta * max; +} + +float getShade(vec3 normal) { + float aX = asin(normal.x) / DEGREE_90; + float aY = asin(normal.y) / DEGREE_90; + float aZ = asin(normal.z) / DEGREE_90; + + float x = interpolateShade(aX, 0.6f); + float y; + if (normal.y < 0.0f) { + y = interpolateShade(-aY, 0.5f); + } else { + y = interpolateShade(aY, 1.0f); + } + float z = interpolateShade(aZ, 0.8f); + + return (x + y + z); } diff --git a/src/test/java/de/bixilon/minosoft/gui/rendering/models/block/state/baked/SkeletalShadeTest.kt b/src/test/java/de/bixilon/minosoft/gui/rendering/models/block/state/baked/SkeletalShadeTest.kt new file mode 100644 index 000000000..1203686c2 --- /dev/null +++ b/src/test/java/de/bixilon/minosoft/gui/rendering/models/block/state/baked/SkeletalShadeTest.kt @@ -0,0 +1,112 @@ +/* + * Minosoft + * Copyright (C) 2020-2023 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.models.block.state.baked + +import de.bixilon.kotlinglm.vec3.Vec3 +import org.junit.jupiter.api.Test +import kotlin.math.abs +import kotlin.math.asin + +class SkeletalShadeTest { + private val DEGREE_90 = 1.5707964f + + + fun interpolateShade(delta: Float, max: Float): Float { + var delta = delta + if (delta < 0.0f) delta = -delta + if (delta <= 0.0f) return 0.0f + if (delta >= 1.0f) return max + return delta * max + } + + fun getShade(normal: Vec3): Float { + normal.normalizeAssign() // for testing purposes + // Take code from skeletal/shade.glsl + + val aX = asin(normal.x) / DEGREE_90 + val aY = asin(normal.y) / DEGREE_90 + val aZ = asin(normal.z) / DEGREE_90 + + val x = interpolateShade(aX, 0.6f) + val y: Float + y = if (normal.y < 0.0f) { + interpolateShade(-aY, 0.5f) + } else { + interpolateShade(aY, 1.0f) + } + val z = interpolateShade(aZ, 0.8f) + + return x + y + z + } + + @Test + fun up() { + assertEquals(1.0f, getShade(Vec3(0, 1, 0))) + } + + @Test + fun down() { + assertEquals(0.5f, getShade(Vec3(0, -1, 0))) + } + + @Test + fun north() { + assertEquals(0.8f, getShade(Vec3(0, 0, 1))) + } + + @Test + fun south() { + assertEquals(0.8f, getShade(Vec3(0, 0, -1))) + } + + @Test + fun west() { + assertEquals(0.6f, getShade(Vec3(1, 0, 0))) + } + + @Test + fun east() { + assertEquals(0.6f, getShade(Vec3(-1, 0, 0))) + } + + @Test + fun northWest() { + assertEquals(0.7f, getShade(Vec3(1, 0, 1))) + } + + @Test + fun eastSouth() { + assertEquals(0.7f, getShade(Vec3(-1, 0, -1))) + } + + @Test + fun westUp() { + assertEquals(0.8f, getShade(Vec3(1, 1, 0))) + } + + @Test + fun westDown() { + assertEquals(0.55f, getShade(Vec3(-1, -1, 0))) + } + + @Test + fun westNorthUp() { + assertEquals(0.94f, getShade(Vec3(1, 1, 1))) + } + + private fun assertEquals(expected: Float, actual: Float) { + if (abs(expected - actual) < 0.03f) return + throw AssertionError("Expected $expected but got $actual") + } +}