From 732e6e88b03d497c8598381051afdd6b012cf408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Fri, 29 Jun 2018 13:23:15 -0700 Subject: [PATCH] Added GGX radiance cubemap filter. --- include/bimg/encode.h | 5 +- src/image_cubemap_filter.cpp | 219 +++++++++++++++++++++++++---------- tools/texturec/texturec.cpp | 62 +++++----- 3 files changed, 188 insertions(+), 98 deletions(-) diff --git a/include/bimg/encode.h b/include/bimg/encode.h index b0b73ba..4165f5e 100644 --- a/include/bimg/encode.h +++ b/include/bimg/encode.h @@ -149,6 +149,7 @@ namespace bimg PhongBrdf, Blinn, BlinnBrdf, + Ggx, Count }; @@ -158,9 +159,7 @@ namespace bimg ImageContainer* imageCubemapRadianceFilter( bx::AllocatorI* _allocator , const ImageContainer& _image - , LightingModel::Enum _lightingModel = LightingModel::BlinnBrdf - , float _glossScale = 10.0f - , float _glossBias = 1.0f + , LightingModel::Enum _lightingModel ); } // namespace bimg diff --git a/src/image_cubemap_filter.cpp b/src/image_cubemap_filter.cpp index 62732dc..6793e86 100644 --- a/src/image_cubemap_filter.cpp +++ b/src/image_cubemap_filter.cpp @@ -5,6 +5,7 @@ #include "bimg_p.h" #include +#include namespace bimg { @@ -620,6 +621,63 @@ namespace bimg } } + struct Sampler + { + Sampler(const ImageContainer& _image, uint16_t _side, float _lod) + { + const uint8_t lod = uint8_t(bx::clamp(_lod, 0.0f, 1.0f) * _image.m_numMips); + imageGetRawData(_image, _side, lod, _image.m_data, _image.m_size, mip); + } + + ImageMip mip; + }; + + void texelFetch(float* _rgba, const Sampler& _sampler, uint32_t _u, uint32_t _v) + { + const uint32_t bpp = _sampler.mip.m_bpp; + const uint32_t pitch = _sampler.mip.m_width*bpp/8; + const uint8_t* texel = _sampler.mip.m_data + _v*pitch + _u*bpp/8; + + UnpackFn unpack = getUnpack(_sampler.mip.m_format); + unpack(_rgba, texel); + } + + void sampleCubeMap(float* _rgba, const ImageContainer& _image, const float* _dir, float _lod) + { + float uu, vv; + uint8_t side; + dirToTexelUv(uu, vv, side, _dir); + + Sampler sampler(_image, side, _lod); + + const uint32_t widthMinusOne = sampler.mip.m_width-1; + + const uint32_t u0 = uint32_t(uu*widthMinusOne+0.5f); + const uint32_t v0 = uint32_t(vv*widthMinusOne+0.5f); + const uint32_t u1 = bx::min(u0 + 1, widthMinusOne); + const uint32_t v1 = bx::min(v0 + 1, widthMinusOne); + + const float fu = bx::fract(uu); + const float fv = bx::fract(vv); + + float rgba00[4]; + texelFetch(rgba00, sampler, u0, v0); + + float rgba01[4]; + texelFetch(rgba01, sampler, u0, v1); + + float rgba10[4]; + texelFetch(rgba10, sampler, u1, v0); + + float rgba11[4]; + texelFetch(rgba11, sampler, u1, v1); + + _rgba[0] = bx::lerp(bx::lerp(rgba00[0], rgba01[0], fv), bx::lerp(rgba10[0], rgba11[0], fv), fu); + _rgba[1] = bx::lerp(bx::lerp(rgba00[1], rgba01[1], fv), bx::lerp(rgba10[1], rgba11[1], fv), fu); + _rgba[2] = bx::lerp(bx::lerp(rgba00[2], rgba01[2], fv), bx::lerp(rgba10[2], rgba11[2], fv), fu); + _rgba[3] = bx::lerp(bx::lerp(rgba00[3], rgba01[3], fv), bx::lerp(rgba10[3], rgba11[3], fv), fu); + } + void importanceSampleGgx(float* _result, float _u, float _v, float _roughness, const float* _normal) { const float aa = bx::square(_roughness); @@ -651,7 +709,85 @@ namespace bimg _result[2] = tangentX[2] * hh[0] + tangentY[2] * hh[1] + _normal[2] * hh[2]; } -#define GGX 0 + void processFilterAreaGgx( + float* _result + , const ImageContainer& _image + , uint8_t _lod + , const float* _dir + , float _roughness + ) + { + ImageMip mip; + imageGetRawData(_image, 0, _lod, _image.m_data, _image.m_size, mip); + + const uint32_t bpp = getBitsPerPixel(_image.m_format); + const float lod = float(_lod)/_image.m_numMips; + + const uint32_t pitch = mip.m_width*bpp/8; + const float widthMinusOne = float(mip.m_width-1); + + UnpackFn unpack = getUnpack(_image.m_format); + + float color[3] = { 0.0f, 0.0f, 0.0f }; + float totalWeight = 0.0f; + + bx::RngMwc mwc; + + for (uint32_t ii = 0; ii < 1024; ++ii) + { + const float uu = ii/1024.0f; + const float vv = bx::frnd(&mwc); + + float hh[3]; + importanceSampleGgx(hh, uu, vv, _roughness, _dir); + + const float ddoth2 = 2.0f * bx::vec3Dot(_dir, hh); + + float ll[3]; + ll[0] = ddoth2 * hh[0] - _dir[0]; + ll[1] = ddoth2 * hh[1] - _dir[1]; + ll[2] = ddoth2 * hh[2] - _dir[2]; + + const float ndotl = bx::clamp(bx::vec3Dot(_dir, ll), 0.0f, 1.0f); + + if (ndotl > 0.0f) + { + float rgba[4]; + sampleCubeMap(rgba, _image, ll, lod); + + color[0] += rgba[0] * ndotl; + color[1] += rgba[1] * ndotl; + color[2] += rgba[2] * ndotl; + totalWeight += ndotl; + } + } + + if (0.0f < totalWeight) + { + const float invWeight = 1.0f/totalWeight; + _result[0] = color[0] * invWeight; + _result[1] = color[1] * invWeight; + _result[2] = color[2] * invWeight; + } + else + { + float uu, vv; + uint8_t face; + dirToTexelUv(uu, vv, face, _dir); + + imageGetRawData(_image, face, 0, _image.m_data, _image.m_size, mip); + + const uint32_t xx = uint32_t(uu*widthMinusOne); + const uint32_t yy = uint32_t(vv*widthMinusOne); + + float rgba[4]; + unpack(rgba, mip.m_data + yy*pitch + xx*bpp/8); + + _result[0] = rgba[0]; + _result[1] = rgba[1]; + _result[2] = rgba[2]; + } + } void processFilterArea( float* _result @@ -697,24 +833,14 @@ namespace bimg for (uint32_t yy = minY; yy <= maxY; ++yy) { const uint8_t* row = mip.m_data + yy*pitch; + BX_UNUSED(row); for (uint32_t xx = minX; xx <= maxX; ++xx) { -#if !GGX -# if 0 - const float uu = float(xx)*texelSize*2.0f - 1.0f; - const float vv = float(yy)*texelSize*2.0f - 1.0f; - - float normal[4]; - texelUvToDir(normal, side, uu, vv); - - const float solidAngle = texelSolidAngle(uu, vv, texelSize); - const float ndotl = bx::clamp(bx::vec3Dot(normal, _dir), 0.0f, 1.0f); -# else const float* normal = (const float*)&nsaMip.m_data[(yy*nsaMip.m_width+xx)*(nsaMip.m_bpp/8)]; const float solidAngle = normal[3]; const float ndotl = bx::clamp(bx::vec3Dot(normal, _dir), 0.0f, 1.0f); -# endif + if (ndotl >= _specularAngle) { const float weight = solidAngle * bx::pow(ndotl, _specularPower); @@ -726,47 +852,6 @@ namespace bimg color[2] += rgba[2] * weight; totalWeight += weight; } -#else - BX_UNUSED(_specularAngle); - const float uu = float(xx)*texelSize; //*2.0f - 1.0f; - const float vv = float(yy)*texelSize; //*2.0f - 1.0f; - - const float* normal = (const float*)&nsaMip.m_data[(yy*nsaMip.m_width+xx)*(nsaMip.m_bpp/8)]; - - float hh[3]; - importanceSampleGgx(hh, uu, vv, _specularPower, normal); - - const float ddoth2 = 2.0f * bx::vec3Dot(normal, hh); - - float ll[3]; - ll[0] = ddoth2 * hh[0] - normal[0]; - ll[1] = ddoth2 * hh[1] - normal[1]; - ll[2] = ddoth2 * hh[2] - normal[2]; - - const float ndotl = bx::clamp(bx::vec3Dot(normal, ll), 0.0f, 1.0f); - -// if (ndotl > 0.0f) - { - const float solidAngle = normal[3]; - - const float weight = solidAngle * ndotl; - - float rgba[4]; - unpack(rgba, row + xx*bpp/8); -# if 1 - color[0] += rgba[0] * weight; - color[1] += rgba[1] * weight; - color[2] += rgba[2] * weight; - totalWeight += weight; -# else - color[0] += ll[0]; - color[1] += ll[1]; - color[2] += ll[2]; - totalWeight += 1.0f; -# endif // 0 - } -#endif // 0 - } } @@ -890,7 +975,6 @@ namespace bimg // - https://web.archive.org/web/20180622232041/https://seblagarde.wordpress.com/2012/03/29/relationship-between-phong-and-blinn-lighting-model/ switch (_lightingModel) { - case LightingModel::Phong: return _specularPower; case LightingModel::PhongBrdf: return _specularPower + 1.0f; case LightingModel::Blinn: return _specularPower/4.0f; case LightingModel::BlinnBrdf: return _specularPower/4.0f + 1.0f; @@ -900,7 +984,7 @@ namespace bimg return _specularPower; } - ImageContainer* imageCubemapRadianceFilter(bx::AllocatorI* _allocator, const ImageContainer& _image, LightingModel::Enum _lightingModel, float _glossScale, float _glossBias) + ImageContainer* imageCubemapRadianceFilter(bx::AllocatorI* _allocator, const ImageContainer& _image, LightingModel::Enum _lightingModel) { ImageContainer* input = imageConvert(_allocator, TextureFormat::RGBA32F, _image, true); @@ -926,6 +1010,9 @@ namespace bimg bx::memCopy(dstData, srcMip.m_data, srcMip.m_size); } + const float glossScale = 10.0f; + const float glossBias = 1.0f; + for (uint8_t lod = 1, numMips = input->m_numMips; lod < numMips; ++lod) { ImageContainer* nsa = imageCubemapNormalSolidAngle(_allocator, bx::max(_image.m_width>>lod, 1) ); @@ -942,7 +1029,8 @@ namespace bimg const float maxAngle = bx::kPiHalf; const float toFilterSize = 1.0f/(minAngle*dstWidth*2.0f); const float glossiness = glossinessFor(lod, float(numMips) ); - const float specularPowerRef = bx::pow(2.0f, _glossScale * glossiness + _glossBias); + const float roughness = 1.0f-glossiness; + const float specularPowerRef = bx::pow(2.0f, glossiness*glossScale + glossBias); const float specularPower = applyLightningModel(specularPowerRef, _lightingModel); const float filterAngle = bx::clamp(cosinePowerFilterAngle(specularPower), minAngle, maxAngle); const float cosAngle = bx::max(0.0f, bx::cos(filterAngle) ); @@ -961,14 +1049,17 @@ namespace bimg float dir[3]; texelUvToDir(dir, side, uu, vv); - Aabb aabb[6]; - calcFilterArea(aabb, dir, filterSize); + if (LightingModel::Ggx == _lightingModel) + { + processFilterAreaGgx(dstData, *input, lod, dir, roughness); + } + else + { + Aabb aabb[6]; + calcFilterArea(aabb, dir, filterSize); -#if GGX - processFilterArea(dstData, *input, *nsa, lod, aabb, dir, 1.0f-glossiness, cosAngle); -#else - processFilterArea(dstData, *input, *nsa, lod, aabb, dir, specularPower, cosAngle); -#endif // GGX + processFilterArea(dstData, *input, *nsa, lod, aabb, dir, specularPower, cosAngle); + } } } } diff --git a/tools/texturec/texturec.cpp b/tools/texturec/texturec.cpp index fdabf7f..eeb4a2b 100644 --- a/tools/texturec/texturec.cpp +++ b/tools/texturec/texturec.cpp @@ -32,21 +32,6 @@ BX_ERROR_RESULT(TEXTRUREC_ERROR, BX_MAKEFOURCC('t', 'c', 0, 0) ); struct Options { - Options() - : maxSize(UINT32_MAX) - , edge(0.0f) - , format(bimg::TextureFormat::Count) - , quality(bimg::Quality::Default) - , mips(false) - , normalMap(false) - , equirect(false) - , iqa(false) - , pma(false) - , sdf(false) - , alphaTest(false) - { - } - void dump() { DBG("Options:\n" @@ -71,18 +56,18 @@ struct Options ); } - uint32_t maxSize; - float edge; - bimg::TextureFormat::Enum format; - bimg::Quality::Enum quality; - bool mips; - bool normalMap; - bool equirect; - bool iqa; - bool pma; - bool sdf; - bool alphaTest; - bool radiance; + uint32_t maxSize = UINT32_MAX; + float edge = 0.0f; + bimg::TextureFormat::Enum format = bimg::TextureFormat::Count; + bimg::Quality::Enum quality = bimg::Quality::Default; + bimg::LightingModel::Enum radiance = bimg::LightingModel::Count; + bool mips = false; + bool normalMap = false; + bool equirect = false; + bool iqa = false; + bool pma = false; + bool sdf = false; + bool alphaTest = false; }; void imageRgba32fNormalize(void* _dst, uint32_t _width, uint32_t _height, uint32_t _srcPitch, const void* _src) @@ -231,7 +216,7 @@ bimg::ImageContainer* convert(bx::AllocatorI* _allocator, const void* _inputData && !_options.equirect && !_options.iqa && !_options.pma - && !_options.radiance + && (bimg::LightingModel::Count == _options.radiance) ; if (needResize) @@ -303,9 +288,9 @@ bimg::ImageContainer* convert(bx::AllocatorI* _allocator, const void* _inputData bimg::imageFree(dst); } - if (_options.radiance) + if (bimg::LightingModel::Count != _options.radiance) { - output = bimg::imageCubemapRadianceFilter(_allocator, *input); + output = bimg::imageCubemapRadianceFilter(_allocator, *input, _options.radiance); if (bimg::TextureFormat::RGBA32F != outputFormat) { @@ -814,6 +799,7 @@ void help(const char* _error = NULL, bool _showHelp = true) " --pma Premultiply alpha into RGB channel.\n" " --max Maximum width/height (image will be scaled down and\n" " aspect ratio will be preserved.\n" + " --radiance Radiance cubemap filter. (Lighting model: Phong, PhongBrdf, Blinn, BlinnBrdf, GGX)\n" " --as Save as.\n" " --validate *DEBUG* Validate that output image produced matches after loading.\n" @@ -937,7 +923,6 @@ int main(int _argc, const char* _argv[]) options.equirect = cmdLine.hasArg("equirect"); options.iqa = cmdLine.hasArg("iqa"); options.pma = cmdLine.hasArg("pma"); - options.radiance = cmdLine.hasArg("radiance"); const char* maxSize = cmdLine.findOption("max"); if (NULL != maxSize) @@ -1001,6 +986,21 @@ int main(int _argc, const char* _argv[]) } } + const char* radiance = cmdLine.findOption("radiance"); + if (NULL != radiance) + { + if (0 == bx::strCmpI(radiance, "phong" ) ) { options.radiance = bimg::LightingModel::Phong; } + else if (0 == bx::strCmpI(radiance, "phongbrdf") ) { options.radiance = bimg::LightingModel::PhongBrdf; } + else if (0 == bx::strCmpI(radiance, "blinn" ) ) { options.radiance = bimg::LightingModel::Blinn; } + else if (0 == bx::strCmpI(radiance, "blinnbrdf") ) { options.radiance = bimg::LightingModel::BlinnBrdf; } + else if (0 == bx::strCmpI(radiance, "ggx" ) ) { options.radiance = bimg::LightingModel::Ggx; } + else + { + help("Invalid radiance lighting model specified."); + return bx::kExitFailure; + } + } + const bool validate = cmdLine.hasArg("validate"); bx::Error err;