diff --git a/examples/Basic/main.cpp b/examples/Basic/main.cpp index a13da717..b697b189 100644 --- a/examples/Basic/main.cpp +++ b/examples/Basic/main.cpp @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) // The surface extractor outputs the mesh in an efficient compressed format which is not directly suitable for rendering. The easiest approach is to // decode this on the CPU as shown below, though more advanced applications can upload the compressed mesh to the GPU and decompress in shader code. - auto decodedMesh = decode(mesh); + auto decodedMesh = decodeMesh(mesh); //Pass the surface to the OpenGL window openGLWidget.addMesh(decodedMesh); diff --git a/examples/DecodeOnGPU/decode.vert b/examples/DecodeOnGPU/decode.vert index 5164468a..1ca80d09 100644 --- a/examples/DecodeOnGPU/decode.vert +++ b/examples/DecodeOnGPU/decode.vert @@ -13,17 +13,30 @@ uniform mat4 modelToWorldMatrix; out vec4 worldPosition; out vec4 worldNormal; +// Returns +/- 1 +vec2 signNotZero(vec2 v) +{ + return vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0); +} + void main() { vec4 decodedPosition = position; decodedPosition.xyz = decodedPosition.xyz * (1.0 / 256.0); - uint encodedX = (normal >> 10u) & 0x1Fu; - uint encodedY = (normal >> 5u) & 0x1Fu; - uint encodedZ = (normal) & 0x1Fu; - worldNormal.xyz = vec3(encodedX, encodedY, encodedZ); - worldNormal.xyz = worldNormal.xyz / 15.5; - worldNormal.xyz = worldNormal.xyz - vec3(1.0, 1.0, 1.0); + //Get the encoded bytes of the normal + uint encodedX = (normal >> 8u) & 0xFFu; + uint encodedY = (normal) & 0xFFu; + + // Map to range [-1.0, +1.0] + vec2 e = vec2(encodedX, encodedY); + e = e * vec2(1.0 / 127.5, 1.0 / 127.5); + e = e - vec2(1.0, 1.0); + + // Now decode normal using listing 2 of http://jcgt.org/published/0003/02/01/ + vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y)); + if (v.z < 0) v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy); + worldNormal.xyz = normalize(v); worldNormal.w = 1.0; // Standard sequence of OpenGL transformations. diff --git a/examples/DecodeOnGPU/main.cpp b/examples/DecodeOnGPU/main.cpp index 2c0fff6d..7f2d83c8 100644 --- a/examples/DecodeOnGPU/main.cpp +++ b/examples/DecodeOnGPU/main.cpp @@ -151,7 +151,7 @@ int main(int argc, char *argv[]) // The surface extractor outputs the mesh in an efficient compressed format which is not directly suitable for rendering. The easiest approach is to // decode this on the CPU as shown below, though more advanced applications can upload the compressed mesh to the GPU and decompress in shader code. - //auto decodedMesh = decode(mesh); + //auto decodedMesh = decodeMesh(mesh); //Pass the surface to the OpenGL window OpenGLMeshData meshData = buildOpenGLMeshData(mesh); diff --git a/examples/OpenGL/main.cpp b/examples/OpenGL/main.cpp index 12d1068f..bdf700e0 100644 --- a/examples/OpenGL/main.cpp +++ b/examples/OpenGL/main.cpp @@ -128,7 +128,7 @@ int main(int argc, char *argv[]) auto mesh = extractMarchingCubesMesh(&volData, regToExtract); // The returned mesh needs to be decoded to be appropriate for GPU rendering. - auto decodedMesh = decode(mesh); + auto decodedMesh = decodeMesh(mesh); // Pass the surface to the OpenGL window. Note that we are also passing an offset in this multi-mesh example. This is because // the surface extractors return a mesh with 'local space' positions to reduce storage requirements and precision problems. diff --git a/examples/Paging/main.cpp b/examples/Paging/main.cpp index ee2d2d23..58e94b5d 100644 --- a/examples/Paging/main.cpp +++ b/examples/Paging/main.cpp @@ -189,7 +189,7 @@ int main(int argc, char *argv[]) auto mesh = extractCubicMesh(&volData, reg2); std::cout << "#vertices: " << mesh.getNoOfVertices() << std::endl; - auto decodedMesh = decode(mesh); + auto decodedMesh = decodeMesh(mesh); //Pass the surface to the OpenGL window openGLWidget.addMesh(decodedMesh); diff --git a/examples/SmoothLOD/main.cpp b/examples/SmoothLOD/main.cpp index 01c2fdaf..0054c5ce 100644 --- a/examples/SmoothLOD/main.cpp +++ b/examples/SmoothLOD/main.cpp @@ -92,12 +92,12 @@ int main(int argc, char *argv[]) //Extract the surface auto meshLowLOD = extractMarchingCubesMesh(&volDataLowLOD, volDataLowLOD.getEnclosingRegion()); // The returned mesh needs to be decoded to be appropriate for GPU rendering. - auto decodedMeshLowLOD = decode(meshLowLOD); + auto decodedMeshLowLOD = decodeMesh(meshLowLOD); //Extract the surface auto meshHighLOD = extractMarchingCubesMesh(&volData, PolyVox::Region(Vector3DInt32(30, 0, 0), Vector3DInt32(63, 63, 63))); // The returned mesh needs to be decoded to be appropriate for GPU rendering. - auto decodedMeshHighLOD = decode(meshHighLOD); + auto decodedMeshHighLOD = decodeMesh(meshHighLOD); //Pass the surface to the OpenGL window openGLWidget.addMesh(decodedMeshHighLOD, Vector3DInt32(30, 0, 0)); diff --git a/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.h b/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.h index 71571038..5e059e10 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.h +++ b/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.h @@ -54,7 +54,7 @@ namespace PolyVox }; /// Decodes a position from a CubicVertex - inline Vector3DFloat decode(const Vector3DUint8& encodedPosition) + inline Vector3DFloat decodePosition(const Vector3DUint8& encodedPosition) { Vector3DFloat result(encodedPosition.getX(), encodedPosition.getY(), encodedPosition.getZ()); result -= 0.5f; // Apply the required offset @@ -63,10 +63,10 @@ namespace PolyVox /// Decodes a MarchingCubesVertex by converting it into a regular Vertex which can then be directly used for rendering. template - Vertex decode(const CubicVertex& cubicVertex) + Vertex decodeVertex(const CubicVertex& cubicVertex) { Vertex result; - result.position = decode(cubicVertex.encodedPosition); + result.position = decodePosition(cubicVertex.encodedPosition); result.normal.setElements(0.0f, 0.0f, 0.0f); // Currently not calculated result.data = cubicVertex.data; // Data is not encoded return result; diff --git a/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h b/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h index e927af7a..18d385f1 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h +++ b/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h @@ -47,9 +47,8 @@ namespace PolyVox // Each component of the position is stored using 8.8 fixed-point encoding. Vector3DUint16 encodedPosition; - // Each component of the normal is encoded using 5 bits of this variable. - // The 16 bits are -xxxxxyyyyyzzzzz (note the left-most bit is currently - // unused). Some extra shifting and scaling is required to make it signed. + // The normal is encoded as a 16-bit unsigned integer using the 'oct16' + // encoding described here: http://jcgt.org/published/0003/02/01/ uint16_t encodedNormal; // User data @@ -57,7 +56,7 @@ namespace PolyVox }; /// Decodes a position from a MarchingCubesVertex - inline Vector3DFloat decode(const Vector3DUint16& encodedPosition) + inline Vector3DFloat decodePosition(const Vector3DUint16& encodedPosition) { Vector3DFloat result(encodedPosition.getX(), encodedPosition.getY(), encodedPosition.getZ()); result *= (1.0f / 256.0f); // Division is compile-time constant @@ -66,45 +65,84 @@ namespace PolyVox inline uint16_t encodeNormal(const Vector3DFloat& normal) { - Vector3DFloat v3dNormal = normal; - v3dNormal += Vector3DFloat(1.0f, 1.0f, 1.0f); - uint16_t encodedX = static_cast(roundToNearestInteger(v3dNormal.getX() * 15.5f)); - uint16_t encodedY = static_cast(roundToNearestInteger(v3dNormal.getY() * 15.5f)); - uint16_t encodedZ = static_cast(roundToNearestInteger(v3dNormal.getZ() * 15.5f)); - POLYVOX_ASSERT(encodedX < 32, "Encoded value out of range"); - POLYVOX_ASSERT(encodedY < 32, "Encoded value out of range"); - POLYVOX_ASSERT(encodedZ < 32, "Encoded value out of range"); - uint16_t encodedNormal = (encodedX << 10) | (encodedY << 5) | encodedZ; - return encodedNormal; + // The first part of this function is based off the code in Listing 1 of http://jcgt.org/published/0003/02/01/ + // It was rewritten in C++ and is restructued for the CPU rather than the GPU. + + // Get the input components + float vx = normal.getX(); + float vy = normal.getY(); + float vz = normal.getZ(); + + // Project the sphere onto the octahedron, and then onto the xy plane + float px = vx * (1.0f / (abs(vx) + abs(vy) + abs(vz))); + float py = vy * (1.0f / (abs(vx) + abs(vy) + abs(vz))); + + // Reflect the folds of the lower hemisphere over the diagonals. + if (vz <= 0.0f) + { + float refx = ((1.0f - abs(py)) * (px >= 0.0f ? +1.0f : -1.0f)); + float refy = ((1.0f - abs(px)) * (py >= 0.0f ? +1.0f : -1.0f)); + px = refx; + py = refy; + } + + // The next part was not given in the paper. We map our two + // floats into two bytes and store them in a single uint16_t + + // Move from range [-1.0f, 1.0f] to [0.0f, 255.0f] + px = (px + 1.0) * 127.5f; + py = (py + 1.0) * 127.5f; + + // Convert to uints + uint16_t resultX = static_cast(px + 0.5f); + uint16_t resultY = static_cast(py + 0.5f); + + // Make sure only the lower bits are set. Probably + // not necessary but we're just being careful really. + resultX &= 0xFF; + resultY &= 0xFF; + + // Contatenate the bytes and return the result. + return (resultX << 8) | resultY; } - /// Decodes a normal from a MarchingCubesVertex - inline Vector3DFloat decode(const uint16_t encodedNormal) + inline Vector3DFloat decodeNormal(const uint16_t& encodedNormal) { - // Get normal components in the range 0 to 31 - uint16_t x = (encodedNormal >> 10) & 0x1F; - uint16_t y = (encodedNormal >> 5) & 0x1F; - uint16_t z = (encodedNormal) & 0x1F; + // Extract the two bytes from the uint16_t. + uint16_t ux = (encodedNormal >> 8) & 0xFF; + uint16_t uy = (encodedNormal ) & 0xFF; - // Build the resulting vector - Vector3DFloat result(x, y, z); + // Convert to floats in the range [-1.0f, +1.0f]. + float ex = ux / 127.5f - 1.0f; + float ey = uy / 127.5f - 1.0f; - // Convert to range 0.0 to 2.0 - result *= (1.0f / 15.5f); // Division is compile-time constant + // Reconstruct the origninal vector. This is a C++ implementation + // of Listing 2 of http://jcgt.org/published/0003/02/01/ + float vx = ex; + float vy = ey; + float vz = 1.0f - abs(ex) - abs(ey); - // Convert to range -1.0 to 1.0 - result -= Vector3DFloat(1.0f, 1.0f, 1.0f); + if (vz < 0.0f) + { + float refX = ((1.0f - abs(vy)) * (vx >= 0.0f ? +1.0f : -1.0f)); + float refY = ((1.0f - abs(vx)) * (vy >= 0.0f ? +1.0f : -1.0f)); + vx = refX; + vy = refY; + } - return result; + // Normalise and return the result. + Vector3DFloat v(vx, vy, vz); + v.normalise(); + return v; } /// Decodes a MarchingCubesVertex by converting it into a regular Vertex which can then be directly used for rendering. template - Vertex decode(const MarchingCubesVertex& marchingCubesVertex) + Vertex decodeVertex(const MarchingCubesVertex& marchingCubesVertex) { Vertex result; - result.position = decode(marchingCubesVertex.encodedPosition); - result.normal = decode(marchingCubesVertex.encodedNormal); + result.position = decodePosition(marchingCubesVertex.encodedPosition); + result.normal = decodeNormal(marchingCubesVertex.encodedNormal); result.data = marchingCubesVertex.data; // Data is not encoded return result; } diff --git a/library/PolyVoxCore/include/PolyVoxCore/Mesh.h b/library/PolyVoxCore/include/PolyVoxCore/Mesh.h index a1c23b50..64356785 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/Mesh.h +++ b/library/PolyVoxCore/include/PolyVoxCore/Mesh.h @@ -72,14 +72,14 @@ namespace PolyVox }; template - Mesh< Vertex< typename MeshType::VertexType::DataType >, typename MeshType::IndexType > decode(const MeshType& mesh) + Mesh< Vertex< typename MeshType::VertexType::DataType >, typename MeshType::IndexType > decodeMesh(const MeshType& mesh) { Mesh< Vertex< typename MeshType::VertexType::DataType >, typename MeshType::IndexType > result; result.m_vecVertices.resize(mesh.m_vecVertices.size()); for(typename MeshType::IndexType ct = 0; ct < mesh.m_vecVertices.size(); ct++) { - result.m_vecVertices[ct] = decode(mesh.m_vecVertices[ct]); + result.m_vecVertices[ct] = decodeVertex(mesh.m_vecVertices[ct]); } result.m_vecTriangleIndices = mesh.m_vecTriangleIndices;