Added cubemap radiance filter.

This commit is contained in:
Branimir Karadžić
2018-06-22 18:14:16 -07:00
parent c3a55957f2
commit 4e42a179ff
3 changed files with 125 additions and 29 deletions

View File

@@ -135,11 +135,32 @@ namespace bimg
, bx::Error* _err , bx::Error* _err
); );
///
ImageContainer* imageGenerateMips(
bx::AllocatorI* _allocator
, const ImageContainer& _image
);
struct LightingModel
{
enum Enum
{
Phong,
PhongBrdf,
Blinn,
BlinnBrdf,
Count
};
};
/// ///
ImageContainer* imageCubemapRadianceFilter( ImageContainer* imageCubemapRadianceFilter(
bx::AllocatorI* _allocator bx::AllocatorI* _allocator
, const ImageContainer& _image , const ImageContainer& _image
, float _filterSize , LightingModel::Enum _lightingModel = LightingModel::BlinnBrdf
, float _glossScale = 10.0f
, float _glossBias = 1.0f
); );
} // namespace bimg } // namespace bimg

View File

@@ -4,6 +4,7 @@
*/ */
#include "bimg_p.h" #include "bimg_p.h"
#include <bimg/encode.h>
namespace bimg namespace bimg
{ {
@@ -311,10 +312,9 @@ namespace bimg
float texelSolidAngle(float _u, float _v, float _invFaceSize) float texelSolidAngle(float _u, float _v, float _invFaceSize)
{ {
/// Reference: // Reference:
/// - https://web.archive.org/web/20180614195754/http://www.mpia.de/~mathar/public/mathar20051002.pdf // - https://web.archive.org/web/20180614195754/http://www.mpia.de/~mathar/public/mathar20051002.pdf
/// - https://web.archive.org/web/20180614195725/http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/ // - https://web.archive.org/web/20180614195725/http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/
const float x0 = _u - _invFaceSize; const float x0 = _u - _invFaceSize;
const float x1 = _u + _invFaceSize; const float x1 = _u + _invFaceSize;
const float y0 = _v - _invFaceSize; const float y0 = _v - _invFaceSize;
@@ -332,7 +332,7 @@ namespace bimg
{ {
const uint32_t dstWidth = _size; const uint32_t dstWidth = _size;
const uint32_t dstPitch = dstWidth*16; const uint32_t dstPitch = dstWidth*16;
const float invDstWidth = 1.0f / float(dstWidth); const float texelSize = 1.0f / float(dstWidth);
ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(dstWidth), uint16_t(dstWidth), 1, 1, true, false); ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(dstWidth), uint16_t(dstWidth), 1, 1, true, false);
@@ -347,11 +347,11 @@ namespace bimg
{ {
float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16]; float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16];
const float uu = float(xx)*invDstWidth*2.0f - 1.0f; const float uu = float(xx)*texelSize*2.0f - 1.0f;
const float vv = float(yy)*invDstWidth*2.0f - 1.0f; const float vv = float(yy)*texelSize*2.0f - 1.0f;
texelUvToDir(dstData, side, uu, vv); texelUvToDir(dstData, side, uu, vv);
dstData[3] = texelSolidAngle(uu, vv, invDstWidth); dstData[3] = texelSolidAngle(uu, vv, texelSize);
} }
} }
} }
@@ -623,6 +623,7 @@ namespace bimg
void processFilterArea( void processFilterArea(
float* _result float* _result
, const ImageContainer& _image , const ImageContainer& _image
, const ImageContainer& _nsa
, uint8_t _lod , uint8_t _lod
, const Aabb* _aabb , const Aabb* _aabb
, const float* _dir , const float* _dir
@@ -630,10 +631,10 @@ namespace bimg
, float _specularAngle , float _specularAngle
) )
{ {
float color[3] = { 0.0f, 0.0f, 0.0f }; float color[3] = { 0.0f, 0.0f, 0.0f };
float totalWeight = 0.0f; float totalWeight = 0.0f;
const uint32_t bpp = getBitsPerPixel(_image.m_format); const uint32_t bpp = getBitsPerPixel(_image.m_format);
UnpackFn unpack = getUnpack(_image.m_format); UnpackFn unpack = getUnpack(_image.m_format);
@@ -644,12 +645,15 @@ namespace bimg
continue; continue;
} }
ImageMip nsaMip;
imageGetRawData(_nsa, side, 0, _nsa.m_data, _nsa.m_size, nsaMip);
ImageMip mip; ImageMip mip;
if (imageGetRawData(_image, side, _lod, _image.m_data, _image.m_size, mip) ) if (imageGetRawData(_image, side, _lod, _image.m_data, _image.m_size, mip) )
{ {
const uint32_t pitch = mip.m_width*bpp/8; const uint32_t pitch = mip.m_width*bpp/8;
const float widthMinusOne = float(mip.m_width-1); const float widthMinusOne = float(mip.m_width-1);
const float invWidth = 1.0f/float(mip.m_width); const float texelSize = 1.0f/float(mip.m_width);
const uint32_t minX = uint32_t(_aabb[side].m_min[0] * widthMinusOne); const uint32_t minX = uint32_t(_aabb[side].m_min[0] * widthMinusOne);
const uint32_t maxX = uint32_t(_aabb[side].m_max[0] * widthMinusOne); const uint32_t maxX = uint32_t(_aabb[side].m_max[0] * widthMinusOne);
@@ -662,14 +666,20 @@ namespace bimg
for (uint32_t xx = minX; xx <= maxX; ++xx) for (uint32_t xx = minX; xx <= maxX; ++xx)
{ {
const float uu = float(xx)*invWidth*2.0f - 1.0f; #if 0
const float vv = float(yy)*invWidth*2.0f - 1.0f; const float uu = float(xx)*texelSize*2.0f - 1.0f;
const float vv = float(yy)*texelSize*2.0f - 1.0f;
float normal[4]; float normal[4];
texelUvToDir(normal, side, uu, vv); texelUvToDir(normal, side, uu, vv);
const float solidAngle = texelSolidAngle(uu, vv, invWidth);
const float solidAngle = texelSolidAngle(uu, vv, texelSize);
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);
#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 // 0
if (ndotl >= _specularAngle) if (ndotl >= _specularAngle)
{ {
@@ -781,29 +791,79 @@ namespace bimg
return output; return output;
} }
ImageContainer* imageCubemapRadianceFilter(bx::AllocatorI* _allocator, const ImageContainer& _image, float _filterSize) /// Returns the angle of cosine power function where the results are above a small empirical treshold.
static float cosinePowerFilterAngle(float _cosinePower)
{ {
ImageContainer* output = imageConvert(_allocator, TextureFormat::RGBA32F, _image, true); // Bigger value leads to performance improvement but might hurt the results.
// 0.00001f was tested empirically and it gives almost the same values as reference.
const float treshold = 0.00001f;
if (1 >= output->m_numMips) // Cosine power filter is: pow(cos(angle), power).
// We want the value of the angle above each result is <= treshold.
// So: angle = acos(pow(treshold, 1.0 / power))
return bx::acos(bx::pow(treshold, 1.0f / _cosinePower));
}
float specularPowerFor(float _mip, float _mipCount, float _glossScale, float _glossBias)
{
const float glossiness = bx::max(0.0f, 1.0f - _mip/(_mipCount-1.0000001f) );
const float specularPower = bx::pow(2.0f, _glossScale * glossiness + _glossBias);
return specularPower;
}
float applyLightningModel(float _specularPower, LightingModel::Enum _lightingModel)
{
// Reference:
// - https://web.archive.org/web/20180622232018/https://seblagarde.wordpress.com/2012/06/10/amd-cubemapgen-for-physically-based-rendering/
// - https://web.archive.org/web/20180622232041/https://seblagarde.wordpress.com/2012/03/29/relationship-between-phong-and-blinn-lighting-model/
switch (_lightingModel)
{ {
ImageContainer* temp = imageGenerateMips(_allocator, *output); case LightingModel::Phong: return _specularPower;
imageFree(output); case LightingModel::PhongBrdf: return _specularPower + 1.0f;
output = temp; case LightingModel::Blinn: return _specularPower/4.0f;
case LightingModel::BlinnBrdf: return _specularPower/4.0f + 1.0f;
default: break;
};
return _specularPower;
}
ImageContainer* imageCubemapRadianceFilter(bx::AllocatorI* _allocator, const ImageContainer& _image, LightingModel::Enum _lightingModel, float _glossScale, float _glossBias)
{
ImageContainer* input = imageConvert(_allocator, TextureFormat::RGBA32F, _image, true);
if (1 >= input->m_numMips)
{
ImageContainer* temp = imageGenerateMips(_allocator, *input);
imageFree(input);
input = temp;
} }
const uint32_t numMips = output->m_numMips; ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(input->m_width), uint16_t(input->m_width), 1, 1, true, true);
for (uint8_t side = 0; side < 6; ++side) const uint32_t numMips = input->m_numMips;
for (uint8_t lod = 0; lod < numMips; ++lod)
{ {
for (uint8_t lod = 0; lod < numMips; ++lod) ImageContainer* nsa = imageCubemapNormalSolidAngle(_allocator, bx::max<uint32_t>(_image.m_width>>lod, 1) );
for (uint8_t side = 0; side < 6; ++side)
{ {
ImageMip mip; ImageMip mip;
imageGetRawData(*output, side, lod, output->m_data, output->m_size, mip); imageGetRawData(*output, side, lod, output->m_data, output->m_size, mip);
const uint32_t dstWidth = mip.m_width; const uint32_t dstWidth = mip.m_width;
const uint32_t dstPitch = dstWidth*16; const uint32_t dstPitch = dstWidth*16;
const float invDstWidth = 1.0f / float(dstWidth);
const float minAngle = bx::atan2(1.0f, float(dstWidth) );
const float maxAngle = bx::kPiHalf;
const float toFilterSize = 1.0f/(minAngle*dstWidth*2.0f);
const float specularPowerRef = specularPowerFor(lod, float(numMips), _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) );
const float texelSize = 1.0f/float(dstWidth);
const float filterSize = bx::max(texelSize, filterAngle * toFilterSize);
for (uint32_t yy = 0; yy < dstWidth; ++yy) for (uint32_t yy = 0; yy < dstWidth; ++yy)
{ {
@@ -811,19 +871,21 @@ namespace bimg
{ {
float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16]; float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16];
const float uu = float(xx)*invDstWidth*2.0f - 1.0f; const float uu = float(xx)*texelSize*2.0f - 1.0f;
const float vv = float(yy)*invDstWidth*2.0f - 1.0f; const float vv = float(yy)*texelSize*2.0f - 1.0f;
float dir[3]; float dir[3];
texelUvToDir(dir, side, uu, vv); texelUvToDir(dir, side, uu, vv);
Aabb aabb[6]; Aabb aabb[6];
calcFilterArea(aabb, dir, _filterSize); calcFilterArea(aabb, dir, filterSize);
processFilterArea(dstData, *output, lod, aabb, dir, 10.0f, 0.2f); processFilterArea(dstData, *input, *nsa, lod, aabb, dir, specularPower, cosAngle);
} }
} }
} }
imageFree(nsa);
} }
return output; return output;

View File

@@ -58,6 +58,7 @@ struct Options
"\t iqa: %s\n" "\t iqa: %s\n"
"\t pma: %s\n" "\t pma: %s\n"
"\t sdf: %s\n" "\t sdf: %s\n"
"\t radiance: %s\n"
, maxSize , maxSize
, edge , edge
, bimg::getName(format) , bimg::getName(format)
@@ -66,6 +67,7 @@ struct Options
, iqa ? "true" : "false" , iqa ? "true" : "false"
, pma ? "true" : "false" , pma ? "true" : "false"
, sdf ? "true" : "false" , sdf ? "true" : "false"
, radiance ? "true" : "false"
); );
} }
@@ -80,6 +82,7 @@ struct Options
bool pma; bool pma;
bool sdf; bool sdf;
bool alphaTest; bool alphaTest;
bool radiance;
}; };
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)
@@ -228,6 +231,7 @@ bimg::ImageContainer* convert(bx::AllocatorI* _allocator, const void* _inputData
&& !_options.equirect && !_options.equirect
&& !_options.iqa && !_options.iqa
&& !_options.pma && !_options.pma
&& !_options.radiance
; ;
if (needResize) if (needResize)
@@ -299,6 +303,14 @@ bimg::ImageContainer* convert(bx::AllocatorI* _allocator, const void* _inputData
bimg::imageFree(dst); bimg::imageFree(dst);
} }
if (_options.radiance)
{
output = bimg::imageCubemapRadianceFilter(_allocator, *input);
bimg::imageFree(input);
return output;
}
output = bimg::imageAlloc( output = bimg::imageAlloc(
_allocator _allocator
, outputFormat , outputFormat
@@ -917,6 +929,7 @@ 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)