Added GGX radiance cubemap filter.

This commit is contained in:
Branimir Karadžić
2018-06-29 13:23:15 -07:00
parent 31defc6701
commit 732e6e88b0
3 changed files with 188 additions and 98 deletions

View File

@@ -149,6 +149,7 @@ namespace bimg
PhongBrdf, PhongBrdf,
Blinn, Blinn,
BlinnBrdf, BlinnBrdf,
Ggx,
Count Count
}; };
@@ -158,9 +159,7 @@ namespace bimg
ImageContainer* imageCubemapRadianceFilter( ImageContainer* imageCubemapRadianceFilter(
bx::AllocatorI* _allocator bx::AllocatorI* _allocator
, const ImageContainer& _image , const ImageContainer& _image
, LightingModel::Enum _lightingModel = LightingModel::BlinnBrdf , LightingModel::Enum _lightingModel
, float _glossScale = 10.0f
, float _glossBias = 1.0f
); );
} // namespace bimg } // namespace bimg

View File

@@ -5,6 +5,7 @@
#include "bimg_p.h" #include "bimg_p.h"
#include <bimg/encode.h> #include <bimg/encode.h>
#include <bx/rng.h>
namespace bimg 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) void importanceSampleGgx(float* _result, float _u, float _v, float _roughness, const float* _normal)
{ {
const float aa = bx::square(_roughness); 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]; _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( void processFilterArea(
float* _result float* _result
@@ -697,24 +833,14 @@ namespace bimg
for (uint32_t yy = minY; yy <= maxY; ++yy) for (uint32_t yy = minY; yy <= maxY; ++yy)
{ {
const uint8_t* row = mip.m_data + yy*pitch; const uint8_t* row = mip.m_data + yy*pitch;
BX_UNUSED(row);
for (uint32_t xx = minX; xx <= maxX; ++xx) 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* normal = (const float*)&nsaMip.m_data[(yy*nsaMip.m_width+xx)*(nsaMip.m_bpp/8)];
const float solidAngle = normal[3]; const float solidAngle = normal[3];
const float ndotl = bx::clamp(bx::vec3Dot(normal, _dir), 0.0f, 1.0f); const float ndotl = bx::clamp(bx::vec3Dot(normal, _dir), 0.0f, 1.0f);
# endif
if (ndotl >= _specularAngle) if (ndotl >= _specularAngle)
{ {
const float weight = solidAngle * bx::pow(ndotl, _specularPower); const float weight = solidAngle * bx::pow(ndotl, _specularPower);
@@ -726,47 +852,6 @@ namespace bimg
color[2] += rgba[2] * weight; color[2] += rgba[2] * weight;
totalWeight += 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/ // - https://web.archive.org/web/20180622232041/https://seblagarde.wordpress.com/2012/03/29/relationship-between-phong-and-blinn-lighting-model/
switch (_lightingModel) switch (_lightingModel)
{ {
case LightingModel::Phong: return _specularPower;
case LightingModel::PhongBrdf: return _specularPower + 1.0f; case LightingModel::PhongBrdf: return _specularPower + 1.0f;
case LightingModel::Blinn: return _specularPower/4.0f; case LightingModel::Blinn: return _specularPower/4.0f;
case LightingModel::BlinnBrdf: return _specularPower/4.0f + 1.0f; case LightingModel::BlinnBrdf: return _specularPower/4.0f + 1.0f;
@@ -900,7 +984,7 @@ namespace bimg
return _specularPower; 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); ImageContainer* input = imageConvert(_allocator, TextureFormat::RGBA32F, _image, true);
@@ -926,6 +1010,9 @@ namespace bimg
bx::memCopy(dstData, srcMip.m_data, srcMip.m_size); 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) for (uint8_t lod = 1, numMips = input->m_numMips; lod < numMips; ++lod)
{ {
ImageContainer* nsa = imageCubemapNormalSolidAngle(_allocator, bx::max<uint32_t>(_image.m_width>>lod, 1) ); ImageContainer* nsa = imageCubemapNormalSolidAngle(_allocator, bx::max<uint32_t>(_image.m_width>>lod, 1) );
@@ -942,7 +1029,8 @@ namespace bimg
const float maxAngle = bx::kPiHalf; const float maxAngle = bx::kPiHalf;
const float toFilterSize = 1.0f/(minAngle*dstWidth*2.0f); const float toFilterSize = 1.0f/(minAngle*dstWidth*2.0f);
const float glossiness = glossinessFor(lod, float(numMips) ); 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 specularPower = applyLightningModel(specularPowerRef, _lightingModel);
const float filterAngle = bx::clamp(cosinePowerFilterAngle(specularPower), minAngle, maxAngle); const float filterAngle = bx::clamp(cosinePowerFilterAngle(specularPower), minAngle, maxAngle);
const float cosAngle = bx::max(0.0f, bx::cos(filterAngle) ); const float cosAngle = bx::max(0.0f, bx::cos(filterAngle) );
@@ -961,14 +1049,17 @@ namespace bimg
float dir[3]; float dir[3];
texelUvToDir(dir, side, uu, vv); texelUvToDir(dir, side, uu, vv);
Aabb aabb[6]; if (LightingModel::Ggx == _lightingModel)
calcFilterArea(aabb, dir, filterSize); {
processFilterAreaGgx(dstData, *input, lod, dir, roughness);
}
else
{
Aabb aabb[6];
calcFilterArea(aabb, dir, filterSize);
#if GGX processFilterArea(dstData, *input, *nsa, lod, aabb, dir, specularPower, cosAngle);
processFilterArea(dstData, *input, *nsa, lod, aabb, dir, 1.0f-glossiness, cosAngle); }
#else
processFilterArea(dstData, *input, *nsa, lod, aabb, dir, specularPower, cosAngle);
#endif // GGX
} }
} }
} }

View File

@@ -32,21 +32,6 @@ BX_ERROR_RESULT(TEXTRUREC_ERROR, BX_MAKEFOURCC('t', 'c', 0, 0) );
struct Options 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() void dump()
{ {
DBG("Options:\n" DBG("Options:\n"
@@ -71,18 +56,18 @@ struct Options
); );
} }
uint32_t maxSize; uint32_t maxSize = UINT32_MAX;
float edge; float edge = 0.0f;
bimg::TextureFormat::Enum format; bimg::TextureFormat::Enum format = bimg::TextureFormat::Count;
bimg::Quality::Enum quality; bimg::Quality::Enum quality = bimg::Quality::Default;
bool mips; bimg::LightingModel::Enum radiance = bimg::LightingModel::Count;
bool normalMap; bool mips = false;
bool equirect; bool normalMap = false;
bool iqa; bool equirect = false;
bool pma; bool iqa = false;
bool sdf; bool pma = false;
bool alphaTest; bool sdf = false;
bool radiance; bool alphaTest = false;
}; };
void imageRgba32fNormalize(void* _dst, uint32_t _width, uint32_t _height, uint32_t _srcPitch, const void* _src) 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.equirect
&& !_options.iqa && !_options.iqa
&& !_options.pma && !_options.pma
&& !_options.radiance && (bimg::LightingModel::Count == _options.radiance)
; ;
if (needResize) if (needResize)
@@ -303,9 +288,9 @@ bimg::ImageContainer* convert(bx::AllocatorI* _allocator, const void* _inputData
bimg::imageFree(dst); 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) 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" " --pma Premultiply alpha into RGB channel.\n"
" --max <max size> Maximum width/height (image will be scaled down and\n" " --max <max size> Maximum width/height (image will be scaled down and\n"
" aspect ratio will be preserved.\n" " aspect ratio will be preserved.\n"
" --radiance <model> Radiance cubemap filter. (Lighting model: Phong, PhongBrdf, Blinn, BlinnBrdf, GGX)\n"
" --as <extension> Save as.\n" " --as <extension> Save as.\n"
" --validate *DEBUG* Validate that output image produced matches after loading.\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.equirect = cmdLine.hasArg("equirect");
options.iqa = cmdLine.hasArg("iqa"); options.iqa = cmdLine.hasArg("iqa");
options.pma = cmdLine.hasArg("pma"); options.pma = cmdLine.hasArg("pma");
options.radiance = cmdLine.hasArg("radiance");
const char* maxSize = cmdLine.findOption("max"); const char* maxSize = cmdLine.findOption("max");
if (NULL != maxSize) 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"); const bool validate = cmdLine.hasArg("validate");
bx::Error err; bx::Error err;