From 4a42535f13fda057a77fdc0766b90e9de00b942f Mon Sep 17 00:00:00 2001 From: David Williams Date: Wed, 23 Jul 2014 23:35:46 +0200 Subject: [PATCH] Added functions to perform 'octahedral' encoding and decoding of normals. See http://jcgt.org/published/0003/02/01/paper-lowres.pdf --- examples/Basic/main.cpp | 42 +++++++++---- examples/common/example.frag | 38 ++++++------ examples/common/example.vert | 45 ++++++++------ .../MarchingCubesSurfaceExtractor.h | 62 ++++++++++++++++++- 4 files changed, 135 insertions(+), 52 deletions(-) diff --git a/examples/Basic/main.cpp b/examples/Basic/main.cpp index a13da717..1f4b210e 100644 --- a/examples/Basic/main.cpp +++ b/examples/Basic/main.cpp @@ -50,14 +50,34 @@ void createSphereInVolume(SimpleVolume& volData, float fRadius) //And compute how far the current position is from the center of the volume float fDistToCenter = (v3dCurrentPos - v3dVolCenter).length(); - uint8_t uVoxelValue = 0; + // We actually want our volume to have high values in the center and low values as we move out, because our + // eath should be a solid sphere surrounded by empty space. If we invert the distance then this is a step in + // the right direction. We still have zero in the center, but lower (negative) values as we move out. + float density = -fDistToCenter; - //If the current voxel is less than 'radius' units from the center then we make it solid. - if(fDistToCenter <= fRadius) - { - //Our new voxel value - uVoxelValue = 255; - } + // By adding the 'planetRadius' we now have a function which starts at 'planetRadius' and still decreases as it + // moves out. The function passes through zero at a distance of 'planetRadius' and then continues do decrease + // as it gets even further out. + density += fRadius; + + // Ideally we would like our final density value to be '255' for voxels inside the planet and '0' for voxels + // outside the planet. At the surface there should be a transition but this should occur not too quickly and + // not too slowly, as both of these will result in a jagged appearance to the mesh. + // + // We probably want the transition to occur over a few voxels, whereas it currently occurs over 255 voxels + // because it was derived from the distance. By scaling the density field we effectivly compress the rate + // at which it changes at the surface. We also make the center much too high and the outside very low, but + // we will clamp these to the corect range later. + // + // Note: You can try commenting out or changing the value on this line to see the effect it has. + density *= 50; + + // Until now we've been defining our density field as if the threshold was at zero, with positive densities + // being solid and negative densities being empty. But actually Cubiquity operates on the range 0 to 255, and + // uses a threashold of 127 to decide where to place the generated surface. Therefore we shift and clamp our + // density value and store it in a byte. + density += 127; + uint8_t uVoxelValue = (uint8_t)(clamp(density, 0.0f, 255.0f)); //Wrte the voxel value into the volume volData.setVoxelAt(x, y, z, uVoxelValue); @@ -74,12 +94,12 @@ int main(int argc, char *argv[]) openGLWidget.show(); //Create an empty volume and then place a sphere in it - SimpleVolume volData(PolyVox::Region(Vector3DInt32(0,0,0), Vector3DInt32(63, 63, 63))); - createSphereInVolume(volData, 30); + SimpleVolume volData(PolyVox::Region(Vector3DInt32(0,0,0), Vector3DInt32(31, 31, 31))); + createSphereInVolume(volData, 15); // Extract the surface for the specified region of the volume. Uncomment the line for the kind of surface extraction you want to see. - auto mesh = extractCubicMesh(&volData, volData.getEnclosingRegion()); - //auto mesh = extractMarchingCubesMesh(&volData, volData.getEnclosingRegion()); + //auto mesh = extractCubicMesh(&volData, volData.getEnclosingRegion()); + auto mesh = extractMarchingCubesMesh(&volData, volData.getEnclosingRegion()); // 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. diff --git a/examples/common/example.frag b/examples/common/example.frag index c9cbf215..56e7d9d6 100644 --- a/examples/common/example.frag +++ b/examples/common/example.frag @@ -1,19 +1,19 @@ -#version 130 - -// Passed in from the vertex shader -in vec4 worldPosition; -in vec4 worldNormal; - -// the color that gets written to the display -out vec4 outputColor; - -void main() -{ - // Again, for the purposes of these examples we cannot be sure that per-vertex normals are provided. A sensible fallback - // is to use this little trick to compute per-fragment flat-shaded normals from the world positions using derivative operations. - vec3 normal = normalize(cross(dFdy(worldPosition.xyz), dFdx(worldPosition.xyz))); - - // We are just using the normal as the output color, and making it lighter so it looks a bit nicer. - // Obviously a real shader would also do texuring, lighting, or whatever is required for the application. - outputColor = vec4(abs(normal) * 0.5 + vec3(0.5, 0.5, 0.5), 1.0); -} +#version 130 + +// Passed in from the vertex shader +in vec4 worldPosition; +in vec3 worldNormal; + +// the color that gets written to the display +out vec4 outputColor; + +void main() +{ + // Again, for the purposes of these examples we cannot be sure that per-vertex normals are provided. A sensible fallback + // is to use this little trick to compute per-fragment flat-shaded normals from the world positions using derivative operations. + vec3 normal = normalize(cross(dFdy(worldPosition.xyz), dFdx(worldPosition.xyz))); + + // We are just using the normal as the output color, and making it lighter so it looks a bit nicer. + // Obviously a real shader would also do texuring, lighting, or whatever is required for the application. + outputColor = vec4(abs(worldNormal.xyz), 1.0); +} diff --git a/examples/common/example.vert b/examples/common/example.vert index 94d35f44..86f60296 100644 --- a/examples/common/example.vert +++ b/examples/common/example.vert @@ -1,20 +1,25 @@ -#version 140 - -in vec4 position; // This will be the position of the vertex in model-space - -// The usual matrices are provided -uniform mat4 cameraToClipMatrix; -uniform mat4 worldToCameraMatrix; -uniform mat4 modelToWorldMatrix; - -// This will be used by the fragment shader to calculate flat-shaded normals. This is an unconventional approach -// but we use it in this example framework because not all surface extractor generate surface normals. -out vec4 worldPosition; - -void main() -{ - // Standard sequence of OpenGL transformations. - worldPosition = modelToWorldMatrix * position; - vec4 cameraPosition = worldToCameraMatrix * worldPosition; - gl_Position = cameraToClipMatrix * cameraPosition; -} +#version 140 + +in vec4 position; // This will be the position of the vertex in model-space +in vec3 normal; + +// The usual matrices are provided +uniform mat4 cameraToClipMatrix; +uniform mat4 worldToCameraMatrix; +uniform mat4 modelToWorldMatrix; + +// This will be used by the fragment shader to calculate flat-shaded normals. This is an unconventional approach +// but we use it in this example framework because not all surface extractor generate surface normals. +out vec4 worldPosition; +out vec3 worldNormal; + +void main() +{ + // Standard sequence of OpenGL transformations. + worldPosition = modelToWorldMatrix * position; + vec4 cameraPosition = worldToCameraMatrix * worldPosition; + + worldNormal = normal; + + gl_Position = cameraToClipMatrix * cameraPosition; +} diff --git a/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h b/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h index e927af7a..8317b4b4 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h +++ b/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h @@ -50,7 +50,7 @@ namespace PolyVox // 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. - uint16_t encodedNormal; + Vector2DFloat encodedNormal; // User data DataType data; @@ -64,7 +64,7 @@ namespace PolyVox return result; } - inline uint16_t encodeNormal(const Vector3DFloat& normal) + /*inline uint16_t encodeNormal(const Vector3DFloat& normal) { Vector3DFloat v3dNormal = normal; v3dNormal += Vector3DFloat(1.0f, 1.0f, 1.0f); @@ -96,6 +96,64 @@ namespace PolyVox result -= Vector3DFloat(1.0f, 1.0f, 1.0f); return result; + }*/ + + // Returns ±1 + float signNotZero(float v) + { + return v >= 0.0 ? +1.0 : -1.0; + } + + Vector2DFloat signNotZero(Vector2DFloat v) + { + return Vector2DFloat((v.getX() >= 0.0) ? +1.0 : -1.0, (v.getY() >= 0.0) ? +1.0 : -1.0); + } + + // Assume normalized input. Output is on [-1, 1] for each component. + Vector2DFloat float32x3_to_oct(Vector3DFloat v) + { + // Project the sphere onto the octahedron, and then onto the xy plane + Vector2DFloat p(v.getX(), v.getY()); + p = p * (1.0f / (abs(v.getX()) + abs(v.getY()) + abs(v.getZ()))); + + float refX = ((1.0f - abs(p.getY())) * signNotZero(p.getX())); + float refY = ((1.0f - abs(p.getX())) * signNotZero(p.getY())); + + Vector2DFloat ref(refX, refY); + + // Reflect the folds of the lower hemisphere over the diagonals + return (v.getZ() <= 0.0) ? ref : p; + } + + Vector3DFloat oct_to_float32x3(Vector2DFloat e) + { + Vector3DFloat v = Vector3DFloat(e.getX(), e.getY(), 1.0 - abs(e.getX()) - abs(e.getY())); + + float refX = ((1.0f - abs(v.getY())) * signNotZero(v.getX())); + float refY = ((1.0f - abs(v.getX())) * signNotZero(v.getY())); + + Vector2DFloat ref(refX, refY); + + if (v.getZ() < 0.0f) + { + //v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy); + v.setX(refX); + v.setY(refY); + } + + v.normalise(); + + return v; + } + + inline Vector2DFloat encodeNormal(const Vector3DFloat& normal) + { + return float32x3_to_oct(normal); + } + + inline Vector3DFloat decode(const Vector2DFloat& encodedNormal) + { + return oct_to_float32x3(encodedNormal); } /// Decodes a MarchingCubesVertex by converting it into a regular Vertex which can then be directly used for rendering.