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
);
///
ImageContainer* imageGenerateMips(
bx::AllocatorI* _allocator
, const ImageContainer& _image
);
struct LightingModel
{
enum Enum
{
Phong,
PhongBrdf,
Blinn,
BlinnBrdf,
Count
};
};
///
ImageContainer* imageCubemapRadianceFilter(
bx::AllocatorI* _allocator
, const ImageContainer& _image
, float _filterSize
, LightingModel::Enum _lightingModel = LightingModel::BlinnBrdf
, float _glossScale = 10.0f
, float _glossBias = 1.0f
);
} // namespace bimg

View File

@@ -4,6 +4,7 @@
*/
#include "bimg_p.h"
#include <bimg/encode.h>
namespace bimg
{
@@ -311,10 +312,9 @@ namespace bimg
float texelSolidAngle(float _u, float _v, float _invFaceSize)
{
/// Reference:
/// - 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/
// Reference:
// - 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/
const float x0 = _u - _invFaceSize;
const float x1 = _u + _invFaceSize;
const float y0 = _v - _invFaceSize;
@@ -332,7 +332,7 @@ namespace bimg
{
const uint32_t dstWidth = _size;
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);
@@ -347,11 +347,11 @@ namespace bimg
{
float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16];
const float uu = float(xx)*invDstWidth*2.0f - 1.0f;
const float vv = float(yy)*invDstWidth*2.0f - 1.0f;
const float uu = float(xx)*texelSize*2.0f - 1.0f;
const float vv = float(yy)*texelSize*2.0f - 1.0f;
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(
float* _result
, const ImageContainer& _image
, const ImageContainer& _nsa
, uint8_t _lod
, const Aabb* _aabb
, const float* _dir
@@ -644,12 +645,15 @@ namespace bimg
continue;
}
ImageMip nsaMip;
imageGetRawData(_nsa, side, 0, _nsa.m_data, _nsa.m_size, nsaMip);
ImageMip mip;
if (imageGetRawData(_image, side, _lod, _image.m_data, _image.m_size, mip) )
{
const uint32_t pitch = mip.m_width*bpp/8;
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 maxX = uint32_t(_aabb[side].m_max[0] * widthMinusOne);
@@ -662,14 +666,20 @@ namespace bimg
for (uint32_t xx = minX; xx <= maxX; ++xx)
{
const float uu = float(xx)*invWidth*2.0f - 1.0f;
const float vv = float(yy)*invWidth*2.0f - 1.0f;
#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, invWidth);
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 // 0
if (ndotl >= _specularAngle)
{
@@ -781,29 +791,79 @@ namespace bimg
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)
{
ImageContainer* temp = imageGenerateMips(_allocator, *output);
imageFree(output);
output = temp;
// 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));
}
const uint32_t numMips = output->m_numMips;
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)
{
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;
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;
}
ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(input->m_width), uint16_t(input->m_width), 1, 1, true, true);
const uint32_t numMips = input->m_numMips;
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)
{
for (uint8_t lod = 0; lod < numMips; ++lod)
{
ImageMip mip;
imageGetRawData(*output, side, lod, output->m_data, output->m_size, mip);
const uint32_t dstWidth = mip.m_width;
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)
{
@@ -811,19 +871,21 @@ namespace bimg
{
float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16];
const float uu = float(xx)*invDstWidth*2.0f - 1.0f;
const float vv = float(yy)*invDstWidth*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 dir[3];
texelUvToDir(dir, side, uu, vv);
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;

View File

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