mirror of
https://github.com/bkaradzic/bimg.git
synced 2026-02-17 20:52:38 +01:00
Added cubemap radiance filter.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user