diff --git a/src/nvtt/CubeSurface.cpp b/src/nvtt/CubeSurface.cpp index 1cc795e..ad92fd7 100644 --- a/src/nvtt/CubeSurface.cpp +++ b/src/nvtt/CubeSurface.cpp @@ -52,7 +52,7 @@ static float solidAngleTerm(uint x, uint y, float inverseEdgeLength) { nvDebugCheck(v >= -1.0f && v <= 1.0f); #if 1 - // Exact solid angle: @@ Not really exact when using seamless filtering... + // Exact solid angle: float x0 = u - inverseEdgeLength; float y0 = v - inverseEdgeLength; float x1 = u + inverseEdgeLength; @@ -73,10 +73,10 @@ static float solidAngleTerm(uint x, uint y, float inverseEdgeLength) { } -static Vector3 texelDirection(uint face, uint x, uint y, int edgeLength, bool seamless) +static Vector3 texelDirection(uint face, uint x, uint y, int edgeLength, EdgeFixup fixupMethod) { float u, v; - if (seamless) { + if (fixupMethod == EdgeFixup_Stretch) { // Transform x,y to [-1, 1] range, match up edges exactly. u = float(x) * 2.0f / (edgeLength - 1) - 1.0f; v = float(y) * 2.0f / (edgeLength - 1) - 1.0f; @@ -86,6 +86,14 @@ static Vector3 texelDirection(uint face, uint x, uint y, int edgeLength, bool se u = (float(x) + 0.5f) * (2.0f / edgeLength) - 1.0f; v = (float(y) + 0.5f) * (2.0f / edgeLength) - 1.0f; } + + if (fixupMethod == EdgeFixup_Warp) { + // Warp texel centers in the proximity of the edges. + float a = powf(float(edgeLength), 2.0f) / powf(float(edgeLength - 1), 3.0f); + u = a * powf(u, 3) + u; + v = a * powf(v, 3) + v; + } + nvDebugCheck(u >= -1.0f && u <= 1.0f); nvDebugCheck(v >= -1.0f && v <= 1.0f); @@ -128,7 +136,7 @@ static Vector3 texelDirection(uint face, uint x, uint y, int edgeLength, bool se } -TexelTable::TexelTable(uint edgeLength, bool seamless) : size(edgeLength) { +TexelTable::TexelTable(uint edgeLength) : size(edgeLength) { uint hsize = size/2; @@ -147,12 +155,10 @@ TexelTable::TexelTable(uint edgeLength, bool seamless) : size(edgeLength) { for (uint f = 0; f < 6; f++) { for (uint y = 0; y < size; y++) { for (uint x = 0; x < size; x++) { - directionArray[(f * size + y) * size + x] = texelDirection(f, x, y, edgeLength, seamless); + directionArray[(f * size + y) * size + x] = texelDirection(f, x, y, edgeLength, EdgeFixup_None); } } } - - } const Vector3 & TexelTable::direction(uint f, uint x, uint y) const { @@ -376,9 +382,34 @@ Surface CubeSurface::unfold(CubeLayout layout) const } +float CubeSurface::average(int channel) const +{ + const uint edgeLength = m->edgeLength; + m->allocateTexelTable(); + + float total = 0.0f; + float sum = 0.0f; + + for (int f = 0; f < 6; f++) { + float * c = m->face[f].m->image->channel(channel); + + for (uint y = 0; y < edgeLength; y++) { + for (uint x = 0; x < edgeLength; x++) { + float solidAngle = m->texelTable->solidAngle(f, x, y); + + total += solidAngle; + sum += c[y * edgeLength + x] * solidAngle; + } + } + } + + return sum / total; +} + + #include "nvmath/SphericalHarmonic.h" -CubeSurface CubeSurface::irradianceFilter(int size, bool seamless) const +CubeSurface CubeSurface::irradianceFilter(int size, EdgeFixup fixupMethod) const { m->allocateTexelTable(); @@ -640,6 +671,7 @@ struct ApplyCosinePowerFilterContext { CubeSurface::Private * filteredCube; float coneAngle; float cosinePower; + EdgeFixup fixupMethod; }; void ApplyCosinePowerFilterTask(void * context, int id) @@ -656,7 +688,7 @@ void ApplyCosinePowerFilterTask(void * context, int id) nvtt::Surface & filteredFace = ctx->filteredCube->face[f]; FloatImage * filteredImage = filteredFace.m->image; - const Vector3 filterDir = texelDirection(f, x, y, size, ctx->filteredCube->seamless); + const Vector3 filterDir = texelDirection(f, x, y, size, ctx->fixupMethod); // Convolve filter against cube. Vector3 color = ctx->inputCube->applyCosinePowerFilter(filterDir, ctx->coneAngle, ctx->cosinePower); @@ -667,14 +699,13 @@ void ApplyCosinePowerFilterTask(void * context, int id) } -CubeSurface CubeSurface::cosinePowerFilter(int size, float cosinePower, bool seamless) const +CubeSurface CubeSurface::cosinePowerFilter(int size, float cosinePower, EdgeFixup fixupMethod) const { const uint edgeLength = m->edgeLength; // Allocate output cube. CubeSurface filteredCube; filteredCube.m->allocate(size); - filteredCube.m->seamless = seamless; // Texel table is stored along with the surface so that it's compute only once. m->allocateTexelTable(); @@ -691,10 +722,10 @@ CubeSurface CubeSurface::cosinePowerFilter(int size, float cosinePower, bool sea for (uint y = 0; y < uint(size); y++) { for (uint x = 0; x < uint(size); x++) { - const Vector3 filterDir = texelDirection(f, x, y, size, seamless); + const Vector3 filterDir = texelDirection(f, x, y, size, fixupMethod); // Convolve filter against cube. - Vector3 color = m->applyCosinePowerFilter(filterDir, coneAngle, cosinePower, seamless); + Vector3 color = m->applyCosinePowerFilter(filterDir, coneAngle, cosinePower); filteredImage->pixel(0, x, y, 0) = color.x; filteredImage->pixel(1, x, y, 0) = color.y; @@ -708,10 +739,31 @@ CubeSurface CubeSurface::cosinePowerFilter(int size, float cosinePower, bool sea context.filteredCube = filteredCube.m; context.coneAngle = coneAngle; context.cosinePower = cosinePower; + context.fixupMethod = fixupMethod; nv::ParallelFor parallelFor(ApplyCosinePowerFilterTask, &context); parallelFor.run(6 * size * size); + // @@ Implement edge averaging. + if (fixupMethod == EdgeFixup_Average) { + for (uint f = 0; f < 6; f++) { + nvtt::Surface filteredFace = filteredCube.m->face[f]; + FloatImage * filteredImage = filteredFace.m->image; + + // For each component. + for (uint c = 0; c < 3; c++) { + // @@ For each corner, sample the two adjacent faces. + filteredImage->pixel(c, 0, 0, 0); + filteredImage->pixel(c, size-1, 0, 0); + filteredImage->pixel(c, 0, size-1, 0); + filteredImage->pixel(c, size-1, size-1, 0); + + // @@ For each edge, sample the adjacent face. + + } + } + } + return filteredCube; } diff --git a/src/nvtt/CubeSurface.h b/src/nvtt/CubeSurface.h index 8427b64..84df471 100644 --- a/src/nvtt/CubeSurface.h +++ b/src/nvtt/CubeSurface.h @@ -39,7 +39,7 @@ namespace nvtt { struct TexelTable { - TexelTable(uint edgeLength, bool seamless); + TexelTable(uint edgeLength); float solidAngle(uint f, uint x, uint y) const; const nv::Vector3 & direction(uint f, uint x, uint y) const; @@ -59,7 +59,6 @@ namespace nvtt nvDebugCheck( refCount() == 0 ); edgeLength = 0; - seamless = false; texelTable = NULL; } Private(const Private & p) : RefCounted() // Copy ctor. inits refcount to 0. @@ -67,7 +66,6 @@ namespace nvtt nvDebugCheck( refCount() == 0 ); edgeLength = p.edgeLength; - seamless = p.seamless; for (uint i = 0; i < 6; i++) { face[i] = p.face[i]; } @@ -91,7 +89,7 @@ namespace nvtt void allocateTexelTable() { if (texelTable == NULL) { - texelTable = new TexelTable(edgeLength, seamless); + texelTable = new TexelTable(edgeLength); } } @@ -99,7 +97,6 @@ namespace nvtt nv::Vector3 applyCosinePowerFilter(const nv::Vector3 & dir, float coneAngle, float cosinePower); uint edgeLength; - bool seamless; Surface face[6]; TexelTable * texelTable; }; diff --git a/src/nvtt/Surface.cpp b/src/nvtt/Surface.cpp index 49c68dd..24c17df 100644 --- a/src/nvtt/Surface.cpp +++ b/src/nvtt/Surface.cpp @@ -1766,6 +1766,46 @@ void Surface::convolve(int channel, int kernelSize, float * kernelData) m->image->convolve(k, channel, (FloatImage::WrapMode)m->wrapMode); } +void Surface::toneMap(ToneMapper tm, float exposure, float * parameters) +{ + if (isNull()) return; + + detach(); + + FloatImage * img = m->image; + float * r = img->channel(0); + float * g = img->channel(1); + float * b = img->channel(2); + const uint count = img->pixelCount(); + + if (tm == ToneMapper_Linear) { + // Clamp preserving the hue. + for (uint i = 0; i < count; i++) { + float m = max(r[i], g[i], b[i]); + if (m > 1.0f) { + r[i] *= 1.0f / m; + g[i] *= 1.0f / m; + b[i] *= 1.0f / m; + } + } + } + else if (tm == ToneMapper_Reindhart) { + for (uint i = 0; i < count; i++) { + r[i] /= r[i] + 1; + g[i] /= g[i] + 1; + b[i] /= b[i] + 1; + } + } + else if (tm == ToneMapper_Halo) { + for (uint i = 0; i < count; i++) { + r[i] = 1 - expf(-r[i]); + g[i] = 1 - expf(-g[i]); + b[i] = 1 - expf(-b[i]); + } + } +} + + /* void Surface::blockLuminanceScale(float scale) { diff --git a/src/nvtt/nvtt.h b/src/nvtt/nvtt.h index c8c901b..da62dfd 100644 --- a/src/nvtt/nvtt.h +++ b/src/nvtt/nvtt.h @@ -413,6 +413,12 @@ namespace nvtt //NormalTransform_DualParaboloid, }; + enum ToneMapper { + ToneMapper_Linear, + ToneMapper_Reindhart, + ToneMapper_Halo, + }; + // A surface is one level of a 2D or 3D texture. // @@ It would be nice to add support for texture borders for correct resizing of tiled textures and constrained DXT compression. @@ -492,6 +498,8 @@ namespace nvtt NVTT_API void abs(int channel); NVTT_API void convolve(int channel, int kernelSize, float * kernelData); + NVTT_API void toneMap(ToneMapper tm, float exposure, float * parameters); + //NVTT_API void blockLuminanceScale(float scale); // Color quantization. @@ -535,6 +543,13 @@ namespace nvtt CubeLayout_LatitudeLongitude }; + enum EdgeFixup { + EdgeFixup_None, + EdgeFixup_Stretch, + EdgeFixup_Warp, + EdgeFixup_Average, + }; + // A CubeSurface is one level of a cube map texture. struct CubeSurface { @@ -548,7 +563,6 @@ namespace nvtt NVTT_API bool isNull() const; NVTT_API int edgeLength() const; NVTT_API int countMipmaps() const; - NVTT_API bool isSeamless() const; // Texture data. NVTT_API bool load(const char * fileName, int mipmap); @@ -570,8 +584,8 @@ namespace nvtt NVTT_API float average(int channel) const; // Filtering. - NVTT_API CubeSurface irradianceFilter(int size, bool seamless) const; - NVTT_API CubeSurface cosinePowerFilter(int size, float cosinePower, bool seamless) const; + NVTT_API CubeSurface irradianceFilter(int size, EdgeFixup fixupMethod) const; + NVTT_API CubeSurface cosinePowerFilter(int size, float cosinePower, EdgeFixup fixupMethod) const; /* diff --git a/src/nvtt/tests/cubemaptest.cpp b/src/nvtt/tests/cubemaptest.cpp index 1db2579..a835be4 100644 --- a/src/nvtt/tests/cubemaptest.cpp +++ b/src/nvtt/tests/cubemaptest.cpp @@ -50,7 +50,7 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - envmap.toLinear(2.2f); + //envmap.toLinear(2.2f); // Setup compression options. @@ -59,19 +59,24 @@ int main(int argc, char *argv[]) compressionOptions.setPixelType(nvtt::PixelType_Float); compressionOptions.setPixelFormat(16, 16, 16, 16); + // Setup output options. nvtt::OutputOptions outputOptions; outputOptions.setFileName("filtered_envmap.dds"); - outputOptions.setSrgbFlag(true); + //outputOptions.setSrgbFlag(true); const int MAX_MIPMAP_COUNT = 7; // nv::log2(64) + 1; //const int mipmapCount = MAX_MIPMAP_COUNT; const int mipmapCount = 4; //const int mipmapCount = 1; + const int firstMipmap = 0; + + int topSize = 64; + float topPower = 64; // Output header. - context.outputHeader(nvtt::TextureType_Cube, 64, 64, 1, mipmapCount, false, compressionOptions, outputOptions); + context.outputHeader(nvtt::TextureType_Cube, topSize >> firstMipmap, topSize >> firstMipmap, 1, mipmapCount-firstMipmap, false, compressionOptions, outputOptions); nv::Timer timer; timer.start(); @@ -79,20 +84,20 @@ int main(int argc, char *argv[]) nvtt::CubeSurface filteredEnvmap[mipmapCount]; // Output filtered mipmaps. - for (int m = 0; m < mipmapCount; m++) { - int size = 64 / (1 << m); // 64, 32, 16, 8 - float cosine_power = float(64) / (1 << (2 * m)); // 64, 16, 4, 1 + for (int m = firstMipmap; m < mipmapCount; m++) { + int size = topSize >> m; // 64, 32, 16, 8 + float cosine_power = topPower / (1 << (2 * m)); // 64, 16, 4, 1 cosine_power = nv::max(1.0f, cosine_power); printf("filtering step: %d/%d\n", m+1, mipmapCount); - filteredEnvmap[m] = envmap.cosinePowerFilter(size, cosine_power, false); - filteredEnvmap[m].toGamma(2.2f); + filteredEnvmap[m] = envmap.cosinePowerFilter(size, cosine_power, nvtt::EdgeFixup_Warp); + //filteredEnvmap[m].toGamma(2.2f); } for (int f = 0; f < 6; f++) { - for (int m = 0; m < mipmapCount; m++) { - context.compress(filteredEnvmap[m].face(f), f, m, compressionOptions, outputOptions); + for (int m = firstMipmap; m < mipmapCount; m++) { + context.compress(filteredEnvmap[m].face(f), f, m-firstMipmap, compressionOptions, outputOptions); } }