From ba82899651745227a2a3edf65a9bd98c6ee07c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Tue, 19 Jun 2018 17:52:14 -0700 Subject: [PATCH] Adding CMFT functionality. --- include/bimg/bimg.h | 8 - include/bimg/encode.h | 15 + scripts/bimg_encode.lua | 1 + src/image.cpp | 648 ------------------------------ src/image_cubemap_filter.cpp | 752 +++++++++++++++++++++++++++++++++++ 5 files changed, 768 insertions(+), 656 deletions(-) create mode 100644 src/image_cubemap_filter.cpp diff --git a/include/bimg/bimg.h b/include/bimg/bimg.h index ae804c3..7866a11 100644 --- a/include/bimg/bimg.h +++ b/include/bimg/bimg.h @@ -640,14 +640,6 @@ namespace bimg , ImageMip& _mip ); - /// - ImageContainer* imageCubemapFromLatLongRgba32F( - bx::AllocatorI* _allocator - , const ImageContainer& _input - , bool _useBilinearInterpolation - , bx::Error* _err - ); - } // namespace bimg #endif // BIMG_IMAGE_H_HEADER_GUARD diff --git a/include/bimg/encode.h b/include/bimg/encode.h index b508745..5e8b24c 100644 --- a/include/bimg/encode.h +++ b/include/bimg/encode.h @@ -127,6 +127,21 @@ namespace bimg , float _alphaRef ); + /// + ImageContainer* imageCubemapFromLatLongRgba32F( + bx::AllocatorI* _allocator + , const ImageContainer& _input + , bool _useBilinearInterpolation + , bx::Error* _err + ); + + /// + ImageContainer* imageCubemapRadianceFilter( + bx::AllocatorI* _allocator + , const ImageContainer& _image + , float _filterSize + ); + } // namespace bimg #endif // BIMG_ENCODE_H_HEADER_GUARD diff --git a/scripts/bimg_encode.lua b/scripts/bimg_encode.lua index cb00082..9001bed 100644 --- a/scripts/bimg_encode.lua +++ b/scripts/bimg_encode.lua @@ -17,6 +17,7 @@ project "bimg_encode" files { path.join(BIMG_DIR, "include/**"), path.join(BIMG_DIR, "src/image_encode.*"), + path.join(BIMG_DIR, "src/image_cubemap_filter.*"), path.join(BIMG_DIR, "3rdparty/libsquish/**.cpp"), path.join(BIMG_DIR, "3rdparty/libsquish/**.h"), path.join(BIMG_DIR, "3rdparty/edtaa3/**.cpp"), diff --git a/src/image.cpp b/src/image.cpp index 3852fb9..1d3db08 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -5324,652 +5324,4 @@ namespace bimg return total; } - // +----------+ - // |-z 2| - // | ^ +y | - // | | | - // | +---->+x | - // +----------+----------+----------+----------+ - // |+y 1|+y 4|+y 0|+y 5| - // | ^ -x | ^ +z | ^ +x | ^ -z | - // | | | | | | | | | - // | +---->+z | +---->+x | +---->-z | +---->-x | - // +----------+----------+----------+----------+ - // |+z 3| - // | ^ -y | - // | | | - // | +---->+x | - // +----------+ - // - struct CubeMapFace - { - enum Enum - { - PositiveX, - NegativeX, - PositiveY, - NegativeY, - PositiveZ, - NegativeZ, - - Count - }; - - struct Edge - { - enum Enum - { - Left, - Right, - Top, - Bottom, - - Count - }; - }; - - // --> U _____ - // | | | - // v | +Y | - // V _____|_____|_____ _____ - // | | | | | - // | -X | +Z | +X | -Z | - // |_____|_____|_____|_____| - // | | - // | -Y | - // |_____| - // - // Neighbour faces in order: left, right, top, bottom. - // FaceEdge is the edge that belongs to the neighbour face. - struct Neighbour - { - uint8_t m_faceIdx; - uint8_t m_faceEdge; - }; - - float uv[3][3]; - }; - - static const CubeMapFace s_cubeMapFace[] = - { - {{ // +x face - { 0.0f, 0.0f, -1.0f }, // u -> -z - { 0.0f, -1.0f, 0.0f }, // v -> -y - { 1.0f, 0.0f, 0.0f }, // +x face - }}, - {{ // -x face - { 0.0f, 0.0f, 1.0f }, // u -> +z - { 0.0f, -1.0f, 0.0f }, // v -> -y - { -1.0f, 0.0f, 0.0f }, // -x face - }}, - {{ // +y face - { 1.0f, 0.0f, 0.0f }, // u -> +x - { 0.0f, 0.0f, 1.0f }, // v -> +z - { 0.0f, 1.0f, 0.0f }, // +y face - }}, - {{ // -y face - { 1.0f, 0.0f, 0.0f }, // u -> +x - { 0.0f, 0.0f, -1.0f }, // v -> -z - { 0.0f, -1.0f, 0.0f }, // -y face - }}, - {{ // +z face - { 1.0f, 0.0f, 0.0f }, // u -> +x - { 0.0f, -1.0f, 0.0f }, // v -> -y - { 0.0f, 0.0f, 1.0f }, // +z face - }}, - {{ // -z face - { -1.0f, 0.0f, 0.0f }, // u -> -x - { 0.0f, -1.0f, 0.0f }, // v -> -y - { 0.0f, 0.0f, -1.0f }, // -z face - }}, - }; - - static const CubeMapFace::Neighbour s_cubeMapFaceNeighbours[6][4] = - { - { // +X - { CubeMapFace::PositiveZ, CubeMapFace::Edge::Right }, - { CubeMapFace::NegativeZ, CubeMapFace::Edge::Left }, - { CubeMapFace::PositiveY, CubeMapFace::Edge::Right }, - { CubeMapFace::NegativeY, CubeMapFace::Edge::Right }, - }, - { // -X - { CubeMapFace::NegativeZ, CubeMapFace::Edge::Right }, - { CubeMapFace::PositiveZ, CubeMapFace::Edge::Left }, - { CubeMapFace::PositiveY, CubeMapFace::Edge::Left }, - { CubeMapFace::NegativeY, CubeMapFace::Edge::Left }, - }, - { // +Y - { CubeMapFace::NegativeX, CubeMapFace::Edge::Top }, - { CubeMapFace::PositiveX, CubeMapFace::Edge::Top }, - { CubeMapFace::NegativeZ, CubeMapFace::Edge::Top }, - { CubeMapFace::PositiveZ, CubeMapFace::Edge::Top }, - }, - { // -Y - { CubeMapFace::NegativeX, CubeMapFace::Edge::Bottom }, - { CubeMapFace::PositiveX, CubeMapFace::Edge::Bottom }, - { CubeMapFace::PositiveZ, CubeMapFace::Edge::Bottom }, - { CubeMapFace::NegativeZ, CubeMapFace::Edge::Bottom }, - }, - { // +Z - { CubeMapFace::NegativeX, CubeMapFace::Edge::Right }, - { CubeMapFace::PositiveX, CubeMapFace::Edge::Left }, - { CubeMapFace::PositiveY, CubeMapFace::Edge::Bottom }, - { CubeMapFace::NegativeY, CubeMapFace::Edge::Top }, - }, - { // -Z - { CubeMapFace::PositiveX, CubeMapFace::Edge::Right }, - { CubeMapFace::NegativeX, CubeMapFace::Edge::Left }, - { CubeMapFace::PositiveY, CubeMapFace::Edge::Top }, - { CubeMapFace::NegativeY, CubeMapFace::Edge::Bottom }, - }, - }; - - /// _u and _v should be center addressing and in [-1.0+invSize..1.0-invSize] range. - void texelUvToDir(float* _outDir, uint8_t _side, float _u, float _v) - { - const CubeMapFace& face = s_cubeMapFace[_side]; - - float tmp[3]; - tmp[0] = face.uv[0][0] * _u + face.uv[1][0] * _v + face.uv[2][0]; - tmp[1] = face.uv[0][1] * _u + face.uv[1][1] * _v + face.uv[2][1]; - tmp[2] = face.uv[0][2] * _u + face.uv[1][2] * _v + face.uv[2][2]; - bx::vec3Norm(_outDir, tmp); - } - - void dirToTexelUv(float& _outU, float& _outV, uint8_t& _outSide, const float* _dir) - { - float absVec[3]; - bx::vec3Abs(absVec, _dir); - - const float max = bx::max(absVec[0], absVec[1], absVec[2]); - - if (max == absVec[0]) - { - _outSide = (_dir[0] >= 0.0f) ? uint8_t(CubeMapFace::PositiveX) : uint8_t(CubeMapFace::NegativeX); - } - else if (max == absVec[1]) - { - _outSide = (_dir[1] >= 0.0f) ? uint8_t(CubeMapFace::PositiveY) : uint8_t(CubeMapFace::NegativeY); - } - else - { - _outSide = (_dir[2] >= 0.0f) ? uint8_t(CubeMapFace::PositiveZ) : uint8_t(CubeMapFace::NegativeZ); - } - - float faceVec[3]; - bx::vec3Mul(faceVec, _dir, 1.0f/max); - - _outU = (bx::vec3Dot(s_cubeMapFace[_outSide].uv[0], faceVec) + 1.0f) * 0.5f; - _outV = (bx::vec3Dot(s_cubeMapFace[_outSide].uv[1], faceVec) + 1.0f) * 0.5f; - } - - ImageContainer* imageCubemapFromLatLongRgba32F(bx::AllocatorI* _allocator, const ImageContainer& _input, bool _useBilinearInterpolation, bx::Error* _err) - { - BX_ERROR_SCOPE(_err); - - if (_input.m_depth != 1 - && _input.m_numLayers != 1 - && _input.m_format != TextureFormat::RGBA32F - && _input.m_width/2 != _input.m_height) - { - BX_ERROR_SET(_err, BIMG_ERROR, "Input image format is not equirectangular projection."); - return NULL; - } - - const uint32_t srcWidthMinusOne = _input.m_width-1; - const uint32_t srcHeightMinusOne = _input.m_height-1; - const uint32_t srcPitch = _input.m_width*16; - const uint32_t dstWidth = _input.m_height/2; - const uint32_t dstPitch = dstWidth*16; - const float invDstWidth = 1.0f / float(dstWidth); - - ImageContainer* output = imageAlloc(_allocator - , _input.m_format - , uint16_t(dstWidth) - , uint16_t(dstWidth) - , uint16_t(1) - , 1 - , true - , false - ); - - const uint8_t* srcData = (const uint8_t*)_input.m_data; - - for (uint8_t side = 0; side < 6 && _err->isOk(); ++side) - { - ImageMip mip; - imageGetRawData(*output, side, 0, output->m_data, output->m_size, mip); - - for (uint32_t yy = 0; yy < dstWidth; ++yy) - { - for (uint32_t xx = 0; xx < dstWidth; ++xx) - { - float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16]; - - const float uu = 2.0f*xx*invDstWidth - 1.0f; - const float vv = 2.0f*yy*invDstWidth - 1.0f; - - float dir[3]; - texelUvToDir(dir, side, uu, vv); - - float srcU, srcV; - bx::vec3ToLatLong(&srcU, &srcV, dir); - - srcU *= srcWidthMinusOne; - srcV *= srcHeightMinusOne; - - if (_useBilinearInterpolation) - { - const uint32_t x0 = uint32_t(srcU); - const uint32_t y0 = uint32_t(srcV); - const uint32_t x1 = bx::min(x0 + 1, srcWidthMinusOne); - const uint32_t y1 = bx::min(y0 + 1, srcHeightMinusOne); - - const float* src0 = (const float*)&srcData[y0*srcPitch + x0*16]; - const float* src1 = (const float*)&srcData[y0*srcPitch + x1*16]; - const float* src2 = (const float*)&srcData[y1*srcPitch + x0*16]; - const float* src3 = (const float*)&srcData[y1*srcPitch + x1*16]; - - const float tx = srcU - float(int32_t(x0) ); - const float ty = srcV - float(int32_t(y0) ); - const float omtx = 1.0f - tx; - const float omty = 1.0f - ty; - - float p0[4]; - bx::vec4Mul(p0, src0, omtx*omty); - - float p1[4]; - bx::vec4Mul(p1, src1, tx*omty); - - float p2[4]; - bx::vec4Mul(p2, src2, omtx*ty); - - float p3[4]; - bx::vec4Mul(p3, src3, tx*ty); - - const float rr = p0[0] + p1[0] + p2[0] + p3[0]; - const float gg = p0[1] + p1[1] + p2[1] + p3[1]; - const float bb = p0[2] + p1[2] + p2[2] + p3[2]; - const float aa = p0[3] + p1[3] + p2[3] + p3[3]; - - dstData[0] = rr; - dstData[1] = gg; - dstData[2] = bb; - dstData[3] = aa; - } - else - { - const uint32_t x0 = uint32_t(srcU); - const uint32_t y0 = uint32_t(srcV); - const float* src0 = (const float*)&srcData[y0*srcPitch + x0*16]; - - dstData[0] = src0[0]; - dstData[1] = src0[1]; - dstData[2] = src0[2]; - dstData[3] = src0[3]; - } - - } - } - } - - return output; - } - - inline float areaElement(float _x, float _y) - { - return bx::atan2(_x*_y, bx::sqrt(_x*_x + _y*_y + 1.0f)); - } - - 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/ - - const float x0 = _u - _invFaceSize; - const float x1 = _u + _invFaceSize; - const float y0 = _v - _invFaceSize; - const float y1 = _v + _invFaceSize; - - return - + areaElement(x1, y1) - - areaElement(x0, y1) - - areaElement(x1, y0) - + areaElement(x0, y0) - ; - } - - ImageContainer* imageCubemapNormalSolidAngle(bx::AllocatorI* _allocator, uint32_t _size) - { - const uint32_t dstWidth = _size; - const uint32_t dstPitch = dstWidth*16; - const float invDstWidth = 1.0f / float(dstWidth); - - ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(dstWidth), uint16_t(dstWidth), 1, 1, true, false); - - for (uint8_t side = 0; side < 6; ++side) - { - ImageMip mip; - imageGetRawData(*output, side, 0, output->m_data, output->m_size, mip); - - for (uint32_t yy = 0; yy < dstWidth; ++yy) - { - for (uint32_t xx = 0; xx < dstWidth; ++xx) - { - 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; - - texelUvToDir(dstData, side, uu, vv); - dstData[3] = texelSolidAngle(uu, vv, invDstWidth); - } - } - } - - return output; - } - - /* - * Copyright 2014-2015 Dario Manesku. All rights reserved. - * License: http://www.opensource.org/licenses/BSD-2-Clause - */ - struct Aabb - { - Aabb() - { - m_min[0] = bx::kFloatMax; - m_min[1] = bx::kFloatMax; - m_max[0] = -bx::kFloatMax; - m_max[1] = -bx::kFloatMax; - } - - void add(float _x, float _y) - { - m_min[0] = bx::min(m_min[0], _x); - m_min[1] = bx::min(m_min[1], _y); - m_max[0] = bx::max(m_max[0], _x); - m_max[1] = bx::max(m_max[1], _y); - } - - void clamp(float _min, float _max) - { - bx::clamp(m_min[0], _min, _max); - bx::clamp(m_min[1], _min, _max); - bx::clamp(m_max[0], _min, _max); - bx::clamp(m_max[1], _min, _max); - } - - bool isEmpty() - { - // Has to have at least two points added so that no value is equal to initial state. - return ( (m_min[0] == bx::kFloatMax) - || (m_min[1] == bx::kFloatMax) - || (m_max[0] == -bx::kFloatMax) - || (m_max[1] == -bx::kFloatMax) - ); - } - - float m_min[2]; - float m_max[2]; - }; - - void calcFilterArea(Aabb* _outFilterArea, const float* _dir, float _filterSize) - { - /// ______ - /// | | - /// | | - /// | x | - /// |______| - /// - // Get face and hit coordinates. - float uu, vv; - uint8_t hitFaceIdx; - dirToTexelUv(uu, vv, hitFaceIdx, _dir); - - /// ........ - /// . . - /// . ___. - /// . | x | - /// ...|___| - /// - // Calculate hit face filter bounds. - Aabb hitFaceFilterBounds; - hitFaceFilterBounds.add(uu-_filterSize, vv-_filterSize); - hitFaceFilterBounds.add(uu+_filterSize, vv+_filterSize); - hitFaceFilterBounds.clamp(0.0f, 1.0f); - - // Output result for hit face. - bx::memCopy(&_outFilterArea[hitFaceIdx], &hitFaceFilterBounds, sizeof(Aabb)); - - /// Filter area might extend on neighbour faces. - /// Case when extending over the right edge: - /// - /// --> U - /// | ...... - /// v . . - /// V . . - /// . . - /// ....... ...... ....... - /// . . . . - /// . . .....__min . - /// . . . . | -> amount - /// ....... .....x.__|.... - /// . . . max - /// . ........ - /// . . - /// ...... - /// . . - /// . . - /// . . - /// ...... - /// - - struct NeighourFaceBleed - { - float m_amount; - float m_bbMin; - float m_bbMax; - }; - - const NeighourFaceBleed bleed[CubeMapFace::Edge::Count] = - { - { // Left - _filterSize - uu, - hitFaceFilterBounds.m_min[1], - hitFaceFilterBounds.m_max[1], - }, - { // Right - uu + _filterSize - 1.0f, - hitFaceFilterBounds.m_min[1], - hitFaceFilterBounds.m_max[1], - }, - { // Top - _filterSize - vv, - hitFaceFilterBounds.m_min[0], - hitFaceFilterBounds.m_max[0], - }, - { // Bottom - vv + _filterSize - 1.0f, - hitFaceFilterBounds.m_min[0], - hitFaceFilterBounds.m_max[0], - }, - }; - - // Determine bleeding for each side. - for (uint8_t side = 0; side < 4; ++side) - { - uint8_t currentFaceIdx = hitFaceIdx; - - for (float bleedAmount = bleed[side].m_amount; bleedAmount > 0.0f; bleedAmount -= 1.0f) - { - uint8_t neighbourFaceIdx = s_cubeMapFaceNeighbours[currentFaceIdx][side].m_faceIdx; - uint8_t neighbourFaceEdge = s_cubeMapFaceNeighbours[currentFaceIdx][side].m_faceEdge; - currentFaceIdx = neighbourFaceIdx; - - /// https://code.google.com/p/cubemapgen/source/browse/trunk/CCubeMapProcessor.cpp#773 - /// - /// Handle situations when bbMin and bbMax should be flipped. - /// - /// L - Left ....................T-T - /// R - Right v . - /// T - Top __________ . - /// B - Bottom . | . - /// . | . - /// . |<...R-T . - /// . | v v - /// .......... ..........|__________ __________ - /// . . . . . - /// . . . . . - /// . . . . . - /// . . . . . - /// __________ .......... .......... __________ - /// ^ | . ^ - /// . | . . - /// B-L..>| . . - /// | . . - /// |__________. . - /// ^ . - /// ....................B-B - /// - /// Those are: - /// B-L, B-B - /// T-R, T-T - /// (and in reverse order, R-T and L-B) - /// - /// If we add, R-R and L-L (which never occur), we get: - /// B-L, B-B - /// T-R, T-T - /// R-T, R-R - /// L-B, L-L - /// - /// And if L = 0, R = 1, T = 2, B = 3 as in NeighbourSides enumeration, - /// a general rule can be derived for when to flip bbMin and bbMax: - /// if ((a+b) == 3 || (a == b)) - /// { - /// ..flip bbMin and bbMax - /// } - /// - float bbMin = bleed[side].m_bbMin; - float bbMax = bleed[side].m_bbMax; - if ( (side == neighbourFaceEdge) - || (3 == (side + neighbourFaceEdge) ) ) - { - // Flip. - bbMin = 1.0f - bbMin; - bbMax = 1.0f - bbMax; - } - - switch (neighbourFaceEdge) - { - case CubeMapFace::Edge::Left: - { - /// --> U - /// | ............. - /// v . . - /// V x___ . - /// | | . - /// | | . - /// |___x . - /// . . - /// ............. - /// - _outFilterArea[neighbourFaceIdx].add(0.0f, bbMin); - _outFilterArea[neighbourFaceIdx].add(bleedAmount, bbMax); - } - break; - - case CubeMapFace::Edge::Right: - { - /// --> U - /// | ............. - /// v . . - /// V . x___. - /// . | | - /// . | | - /// . |___x - /// . . - /// ............. - /// - _outFilterArea[neighbourFaceIdx].add(1.0f - bleedAmount, bbMin); - _outFilterArea[neighbourFaceIdx].add(1.0f, bbMax); - } - break; - - case CubeMapFace::Edge::Top: - { - /// --> U - /// | ...x____ ... - /// v . | | . - /// V . |____x . - /// . . - /// . . - /// . . - /// ............ - /// - _outFilterArea[neighbourFaceIdx].add(bbMin, 0.0f); - _outFilterArea[neighbourFaceIdx].add(bbMax, bleedAmount); - } - break; - - case CubeMapFace::Edge::Bottom: - { - /// --> U - /// | ............ - /// v . . - /// V . . - /// . . - /// . x____ . - /// . | | . - /// ...|____x... - /// - _outFilterArea[neighbourFaceIdx].add(bbMin, 1.0f - bleedAmount); - _outFilterArea[neighbourFaceIdx].add(bbMax, 1.0f); - } - break; - } - - // Clamp bounding box to face size. - _outFilterArea[neighbourFaceIdx].clamp(0.0f, 1.0f); - } - } - } - - ImageContainer* imageCubemapRadianceFilter(bx::AllocatorI* _allocator, const ImageContainer& _image, float _filterSize) - { - const uint32_t dstWidth = _image.m_width; - const uint32_t dstPitch = dstWidth*16; - const float invDstWidth = 1.0f / float(dstWidth); - - ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(dstWidth), uint16_t(dstWidth), 1, 1, true, false); - - for (uint8_t side = 0; side < 6; ++side) - { - ImageMip mip; - imageGetRawData(*output, side, 0, output->m_data, output->m_size, mip); - - for (uint32_t yy = 0; yy < dstWidth; ++yy) - { - for (uint32_t xx = 0; xx < dstWidth; ++xx) - { - 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; - - float dir[3]; - texelUvToDir(dir, side, uu, vv); - - Aabb aabb[6]; - calcFilterArea(aabb, dir, _filterSize); - - BX_UNUSED(dstData); - } - } - } - - return output; - } - } // namespace bimg diff --git a/src/image_cubemap_filter.cpp b/src/image_cubemap_filter.cpp new file mode 100644 index 0000000..c366e45 --- /dev/null +++ b/src/image_cubemap_filter.cpp @@ -0,0 +1,752 @@ +/* + * Copyright 2011-2018 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bimg#license-bsd-2-clause + */ + +#include "bimg_p.h" + +namespace bimg +{ + /* + * Copyright 2014-2015 Dario Manesku. All rights reserved. + * License: http://www.opensource.org/licenses/BSD-2-Clause + */ + + // +----------+ + // |-z 2| + // | ^ +y | + // | | | + // | +---->+x | + // +----------+----------+----------+----------+ + // |+y 1|+y 4|+y 0|+y 5| + // | ^ -x | ^ +z | ^ +x | ^ -z | + // | | | | | | | | | + // | +---->+z | +---->+x | +---->-z | +---->-x | + // +----------+----------+----------+----------+ + // |+z 3| + // | ^ -y | + // | | | + // | +---->+x | + // +----------+ + // + struct CubeMapFace + { + enum Enum + { + PositiveX, + NegativeX, + PositiveY, + NegativeY, + PositiveZ, + NegativeZ, + + Count + }; + + struct Edge + { + enum Enum + { + Left, + Right, + Top, + Bottom, + + Count + }; + }; + + // --> U _____ + // | | | + // v | +Y | + // V _____|_____|_____ _____ + // | | | | | + // | -X | +Z | +X | -Z | + // |_____|_____|_____|_____| + // | | + // | -Y | + // |_____| + // + // Neighbour faces in order: left, right, top, bottom. + // FaceEdge is the edge that belongs to the neighbour face. + struct Neighbour + { + uint8_t m_faceIdx; + uint8_t m_faceEdge; + }; + + float uv[3][3]; + }; + + static const CubeMapFace s_cubeMapFace[] = + { + {{ // +x face + { 0.0f, 0.0f, -1.0f }, // u -> -z + { 0.0f, -1.0f, 0.0f }, // v -> -y + { 1.0f, 0.0f, 0.0f }, // +x face + }}, + {{ // -x face + { 0.0f, 0.0f, 1.0f }, // u -> +z + { 0.0f, -1.0f, 0.0f }, // v -> -y + { -1.0f, 0.0f, 0.0f }, // -x face + }}, + {{ // +y face + { 1.0f, 0.0f, 0.0f }, // u -> +x + { 0.0f, 0.0f, 1.0f }, // v -> +z + { 0.0f, 1.0f, 0.0f }, // +y face + }}, + {{ // -y face + { 1.0f, 0.0f, 0.0f }, // u -> +x + { 0.0f, 0.0f, -1.0f }, // v -> -z + { 0.0f, -1.0f, 0.0f }, // -y face + }}, + {{ // +z face + { 1.0f, 0.0f, 0.0f }, // u -> +x + { 0.0f, -1.0f, 0.0f }, // v -> -y + { 0.0f, 0.0f, 1.0f }, // +z face + }}, + {{ // -z face + { -1.0f, 0.0f, 0.0f }, // u -> -x + { 0.0f, -1.0f, 0.0f }, // v -> -y + { 0.0f, 0.0f, -1.0f }, // -z face + }}, + }; + + static const CubeMapFace::Neighbour s_cubeMapFaceNeighbours[6][4] = + { + { // +X + { CubeMapFace::PositiveZ, CubeMapFace::Edge::Right }, + { CubeMapFace::NegativeZ, CubeMapFace::Edge::Left }, + { CubeMapFace::PositiveY, CubeMapFace::Edge::Right }, + { CubeMapFace::NegativeY, CubeMapFace::Edge::Right }, + }, + { // -X + { CubeMapFace::NegativeZ, CubeMapFace::Edge::Right }, + { CubeMapFace::PositiveZ, CubeMapFace::Edge::Left }, + { CubeMapFace::PositiveY, CubeMapFace::Edge::Left }, + { CubeMapFace::NegativeY, CubeMapFace::Edge::Left }, + }, + { // +Y + { CubeMapFace::NegativeX, CubeMapFace::Edge::Top }, + { CubeMapFace::PositiveX, CubeMapFace::Edge::Top }, + { CubeMapFace::NegativeZ, CubeMapFace::Edge::Top }, + { CubeMapFace::PositiveZ, CubeMapFace::Edge::Top }, + }, + { // -Y + { CubeMapFace::NegativeX, CubeMapFace::Edge::Bottom }, + { CubeMapFace::PositiveX, CubeMapFace::Edge::Bottom }, + { CubeMapFace::PositiveZ, CubeMapFace::Edge::Bottom }, + { CubeMapFace::NegativeZ, CubeMapFace::Edge::Bottom }, + }, + { // +Z + { CubeMapFace::NegativeX, CubeMapFace::Edge::Right }, + { CubeMapFace::PositiveX, CubeMapFace::Edge::Left }, + { CubeMapFace::PositiveY, CubeMapFace::Edge::Bottom }, + { CubeMapFace::NegativeY, CubeMapFace::Edge::Top }, + }, + { // -Z + { CubeMapFace::PositiveX, CubeMapFace::Edge::Right }, + { CubeMapFace::NegativeX, CubeMapFace::Edge::Left }, + { CubeMapFace::PositiveY, CubeMapFace::Edge::Top }, + { CubeMapFace::NegativeY, CubeMapFace::Edge::Bottom }, + }, + }; + + /// _u and _v should be center addressing and in [-1.0+invSize..1.0-invSize] range. + void texelUvToDir(float* _outDir, uint8_t _side, float _u, float _v) + { + const CubeMapFace& face = s_cubeMapFace[_side]; + + float tmp[3]; + tmp[0] = face.uv[0][0] * _u + face.uv[1][0] * _v + face.uv[2][0]; + tmp[1] = face.uv[0][1] * _u + face.uv[1][1] * _v + face.uv[2][1]; + tmp[2] = face.uv[0][2] * _u + face.uv[1][2] * _v + face.uv[2][2]; + bx::vec3Norm(_outDir, tmp); + } + + void dirToTexelUv(float& _outU, float& _outV, uint8_t& _outSide, const float* _dir) + { + float absVec[3]; + bx::vec3Abs(absVec, _dir); + + const float max = bx::max(absVec[0], absVec[1], absVec[2]); + + if (max == absVec[0]) + { + _outSide = (_dir[0] >= 0.0f) ? uint8_t(CubeMapFace::PositiveX) : uint8_t(CubeMapFace::NegativeX); + } + else if (max == absVec[1]) + { + _outSide = (_dir[1] >= 0.0f) ? uint8_t(CubeMapFace::PositiveY) : uint8_t(CubeMapFace::NegativeY); + } + else + { + _outSide = (_dir[2] >= 0.0f) ? uint8_t(CubeMapFace::PositiveZ) : uint8_t(CubeMapFace::NegativeZ); + } + + float faceVec[3]; + bx::vec3Mul(faceVec, _dir, 1.0f/max); + + _outU = (bx::vec3Dot(s_cubeMapFace[_outSide].uv[0], faceVec) + 1.0f) * 0.5f; + _outV = (bx::vec3Dot(s_cubeMapFace[_outSide].uv[1], faceVec) + 1.0f) * 0.5f; + } + + ImageContainer* imageCubemapFromLatLongRgba32F(bx::AllocatorI* _allocator, const ImageContainer& _input, bool _useBilinearInterpolation, bx::Error* _err) + { + BX_ERROR_SCOPE(_err); + + if (_input.m_depth != 1 + && _input.m_numLayers != 1 + && _input.m_format != TextureFormat::RGBA32F + && _input.m_width/2 != _input.m_height) + { + BX_ERROR_SET(_err, BIMG_ERROR, "Input image format is not equirectangular projection."); + return NULL; + } + + const uint32_t srcWidthMinusOne = _input.m_width-1; + const uint32_t srcHeightMinusOne = _input.m_height-1; + const uint32_t srcPitch = _input.m_width*16; + const uint32_t dstWidth = _input.m_height/2; + const uint32_t dstPitch = dstWidth*16; + const float invDstWidth = 1.0f / float(dstWidth); + + ImageContainer* output = imageAlloc(_allocator + , _input.m_format + , uint16_t(dstWidth) + , uint16_t(dstWidth) + , uint16_t(1) + , 1 + , true + , false + ); + + const uint8_t* srcData = (const uint8_t*)_input.m_data; + + for (uint8_t side = 0; side < 6 && _err->isOk(); ++side) + { + ImageMip mip; + imageGetRawData(*output, side, 0, output->m_data, output->m_size, mip); + + for (uint32_t yy = 0; yy < dstWidth; ++yy) + { + for (uint32_t xx = 0; xx < dstWidth; ++xx) + { + float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16]; + + const float uu = 2.0f*xx*invDstWidth - 1.0f; + const float vv = 2.0f*yy*invDstWidth - 1.0f; + + float dir[3]; + texelUvToDir(dir, side, uu, vv); + + float srcU, srcV; + bx::vec3ToLatLong(&srcU, &srcV, dir); + + srcU *= srcWidthMinusOne; + srcV *= srcHeightMinusOne; + + if (_useBilinearInterpolation) + { + const uint32_t x0 = uint32_t(srcU); + const uint32_t y0 = uint32_t(srcV); + const uint32_t x1 = bx::min(x0 + 1, srcWidthMinusOne); + const uint32_t y1 = bx::min(y0 + 1, srcHeightMinusOne); + + const float* src0 = (const float*)&srcData[y0*srcPitch + x0*16]; + const float* src1 = (const float*)&srcData[y0*srcPitch + x1*16]; + const float* src2 = (const float*)&srcData[y1*srcPitch + x0*16]; + const float* src3 = (const float*)&srcData[y1*srcPitch + x1*16]; + + const float tx = srcU - float(int32_t(x0) ); + const float ty = srcV - float(int32_t(y0) ); + const float omtx = 1.0f - tx; + const float omty = 1.0f - ty; + + float p0[4]; + bx::vec4Mul(p0, src0, omtx*omty); + + float p1[4]; + bx::vec4Mul(p1, src1, tx*omty); + + float p2[4]; + bx::vec4Mul(p2, src2, omtx*ty); + + float p3[4]; + bx::vec4Mul(p3, src3, tx*ty); + + const float rr = p0[0] + p1[0] + p2[0] + p3[0]; + const float gg = p0[1] + p1[1] + p2[1] + p3[1]; + const float bb = p0[2] + p1[2] + p2[2] + p3[2]; + const float aa = p0[3] + p1[3] + p2[3] + p3[3]; + + dstData[0] = rr; + dstData[1] = gg; + dstData[2] = bb; + dstData[3] = aa; + } + else + { + const uint32_t x0 = uint32_t(srcU); + const uint32_t y0 = uint32_t(srcV); + const float* src0 = (const float*)&srcData[y0*srcPitch + x0*16]; + + dstData[0] = src0[0]; + dstData[1] = src0[1]; + dstData[2] = src0[2]; + dstData[3] = src0[3]; + } + + } + } + } + + return output; + } + + inline float areaElement(float _x, float _y) + { + return bx::atan2(_x*_y, bx::sqrt(_x*_x + _y*_y + 1.0f)); + } + + 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/ + + const float x0 = _u - _invFaceSize; + const float x1 = _u + _invFaceSize; + const float y0 = _v - _invFaceSize; + const float y1 = _v + _invFaceSize; + + return + + areaElement(x1, y1) + - areaElement(x0, y1) + - areaElement(x1, y0) + + areaElement(x0, y0) + ; + } + + ImageContainer* imageCubemapNormalSolidAngle(bx::AllocatorI* _allocator, uint32_t _size) + { + const uint32_t dstWidth = _size; + const uint32_t dstPitch = dstWidth*16; + const float invDstWidth = 1.0f / float(dstWidth); + + ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(dstWidth), uint16_t(dstWidth), 1, 1, true, false); + + for (uint8_t side = 0; side < 6; ++side) + { + ImageMip mip; + imageGetRawData(*output, side, 0, output->m_data, output->m_size, mip); + + for (uint32_t yy = 0; yy < dstWidth; ++yy) + { + for (uint32_t xx = 0; xx < dstWidth; ++xx) + { + 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; + + texelUvToDir(dstData, side, uu, vv); + dstData[3] = texelSolidAngle(uu, vv, invDstWidth); + } + } + } + + return output; + } + + struct Aabb + { + Aabb() + { + m_min[0] = bx::kFloatMax; + m_min[1] = bx::kFloatMax; + m_max[0] = -bx::kFloatMax; + m_max[1] = -bx::kFloatMax; + } + + void add(float _x, float _y) + { + m_min[0] = bx::min(m_min[0], _x); + m_min[1] = bx::min(m_min[1], _y); + m_max[0] = bx::max(m_max[0], _x); + m_max[1] = bx::max(m_max[1], _y); + } + + void clamp(float _min, float _max) + { + m_min[0] = bx::clamp(m_min[0], _min, _max); + m_min[1] = bx::clamp(m_min[1], _min, _max); + m_max[0] = bx::clamp(m_max[0], _min, _max); + m_max[1] = bx::clamp(m_max[1], _min, _max); + } + + bool isEmpty() const + { + // Has to have at least two points added so that no value is equal to initial state. + return ( (m_min[0] == bx::kFloatMax) + || (m_min[1] == bx::kFloatMax) + || (m_max[0] == -bx::kFloatMax) + || (m_max[1] == -bx::kFloatMax) + ); + } + + float m_min[2]; + float m_max[2]; + }; + + void calcFilterArea(Aabb* _outFilterArea, const float* _dir, float _filterSize) + { + /// ______ + /// | | + /// | | + /// | x | + /// |______| + /// + // Get face and hit coordinates. + float uu, vv; + uint8_t hitFaceIdx; + dirToTexelUv(uu, vv, hitFaceIdx, _dir); + + /// ........ + /// . . + /// . ___. + /// . | x | + /// ...|___| + /// + // Calculate hit face filter bounds. + Aabb hitFaceFilterBounds; + hitFaceFilterBounds.add(uu-_filterSize, vv-_filterSize); + hitFaceFilterBounds.add(uu+_filterSize, vv+_filterSize); + hitFaceFilterBounds.clamp(0.0f, 1.0f); + + // Output result for hit face. + bx::memCopy(&_outFilterArea[hitFaceIdx], &hitFaceFilterBounds, sizeof(Aabb)); + + /// Filter area might extend on neighbour faces. + /// Case when extending over the right edge: + /// + /// --> U + /// | ...... + /// v . . + /// V . . + /// . . + /// ....... ...... ....... + /// . . . . + /// . . .....__min . + /// . . . . | -> amount + /// ....... .....x.__|.... + /// . . . max + /// . ........ + /// . . + /// ...... + /// . . + /// . . + /// . . + /// ...... + /// + + struct NeighourFaceBleed + { + float m_amount; + float m_bbMin; + float m_bbMax; + }; + + const NeighourFaceBleed bleed[CubeMapFace::Edge::Count] = + { + { // Left + _filterSize - uu, + hitFaceFilterBounds.m_min[1], + hitFaceFilterBounds.m_max[1], + }, + { // Right + uu + _filterSize - 1.0f, + hitFaceFilterBounds.m_min[1], + hitFaceFilterBounds.m_max[1], + }, + { // Top + _filterSize - vv, + hitFaceFilterBounds.m_min[0], + hitFaceFilterBounds.m_max[0], + }, + { // Bottom + vv + _filterSize - 1.0f, + hitFaceFilterBounds.m_min[0], + hitFaceFilterBounds.m_max[0], + }, + }; + + // Determine bleeding for each side. + for (uint8_t side = 0; side < 4; ++side) + { + uint8_t currentFaceIdx = hitFaceIdx; + + for (float bleedAmount = bleed[side].m_amount; bleedAmount > 0.0f; bleedAmount -= 1.0f) + { + uint8_t neighbourFaceIdx = s_cubeMapFaceNeighbours[currentFaceIdx][side].m_faceIdx; + uint8_t neighbourFaceEdge = s_cubeMapFaceNeighbours[currentFaceIdx][side].m_faceEdge; + currentFaceIdx = neighbourFaceIdx; + + /// https://code.google.com/p/cubemapgen/source/browse/trunk/CCubeMapProcessor.cpp#773 + /// + /// Handle situations when bbMin and bbMax should be flipped. + /// + /// L - Left ....................T-T + /// R - Right v . + /// T - Top __________ . + /// B - Bottom . | . + /// . | . + /// . |<...R-T . + /// . | v v + /// .......... ..........|__________ __________ + /// . . . . . + /// . . . . . + /// . . . . . + /// . . . . . + /// __________ .......... .......... __________ + /// ^ | . ^ + /// . | . . + /// B-L..>| . . + /// | . . + /// |__________. . + /// ^ . + /// ....................B-B + /// + /// Those are: + /// B-L, B-B + /// T-R, T-T + /// (and in reverse order, R-T and L-B) + /// + /// If we add, R-R and L-L (which never occur), we get: + /// B-L, B-B + /// T-R, T-T + /// R-T, R-R + /// L-B, L-L + /// + /// And if L = 0, R = 1, T = 2, B = 3 as in NeighbourSides enumeration, + /// a general rule can be derived for when to flip bbMin and bbMax: + /// if ((a+b) == 3 || (a == b)) + /// { + /// ..flip bbMin and bbMax + /// } + /// + float bbMin = bleed[side].m_bbMin; + float bbMax = bleed[side].m_bbMax; + if ( (side == neighbourFaceEdge) + || (3 == (side + neighbourFaceEdge) ) ) + { + // Flip. + bbMin = 1.0f - bbMin; + bbMax = 1.0f - bbMax; + } + + switch (neighbourFaceEdge) + { + case CubeMapFace::Edge::Left: + { + /// --> U + /// | ............. + /// v . . + /// V x___ . + /// | | . + /// | | . + /// |___x . + /// . . + /// ............. + /// + _outFilterArea[neighbourFaceIdx].add(0.0f, bbMin); + _outFilterArea[neighbourFaceIdx].add(bleedAmount, bbMax); + } + break; + + case CubeMapFace::Edge::Right: + { + /// --> U + /// | ............. + /// v . . + /// V . x___. + /// . | | + /// . | | + /// . |___x + /// . . + /// ............. + /// + _outFilterArea[neighbourFaceIdx].add(1.0f - bleedAmount, bbMin); + _outFilterArea[neighbourFaceIdx].add(1.0f, bbMax); + } + break; + + case CubeMapFace::Edge::Top: + { + /// --> U + /// | ...x____ ... + /// v . | | . + /// V . |____x . + /// . . + /// . . + /// . . + /// ............ + /// + _outFilterArea[neighbourFaceIdx].add(bbMin, 0.0f); + _outFilterArea[neighbourFaceIdx].add(bbMax, bleedAmount); + } + break; + + case CubeMapFace::Edge::Bottom: + { + /// --> U + /// | ............ + /// v . . + /// V . . + /// . . + /// . x____ . + /// . | | . + /// ...|____x... + /// + _outFilterArea[neighbourFaceIdx].add(bbMin, 1.0f - bleedAmount); + _outFilterArea[neighbourFaceIdx].add(bbMax, 1.0f); + } + break; + } + + // Clamp bounding box to face size. + _outFilterArea[neighbourFaceIdx].clamp(0.0f, 1.0f); + } + } + } + + void processFilterArea( + float* _result + , const ImageContainer& _image + , const Aabb* _aabb + , const float* _dir + , float _specularPower + , float _specularAngle + ) + { + float color[3] = { 0.0f, 0.0f, 0.0f }; + float totalWeight = 0.0f; + + const uint32_t bpp = getBitsPerPixel(_image.m_format); + const uint32_t pitch = _image.m_width*bpp/8; + const float widthMinusOne = float(_image.m_width-1); + const float invWidth = 1.0f/float(_image.m_width); + + UnpackFn unpack = getUnpack(_image.m_format); + + for (uint8_t side = 0; side < 6; ++side) + { + if (_aabb[side].isEmpty() ) + { + continue; + } + + 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 minY = uint32_t(_aabb[side].m_min[1] * widthMinusOne); + const uint32_t maxY = uint32_t(_aabb[side].m_max[1] * widthMinusOne); + + ImageMip mip; + if (imageGetRawData(_image, side, 0, _image.m_data, _image.m_size, mip) ) + { + for (uint32_t yy = minY; yy <= maxY; ++yy) + { + const uint8_t* row = mip.m_data + yy*pitch; + + 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; + + float normal[4]; + texelUvToDir(normal, side, uu, vv); + const float solidAngle = texelSolidAngle(uu, vv, invWidth); + + const float ndotl = bx::clamp(bx::vec3Dot(normal, _dir), 0.0f, 1.0f); + + if (ndotl >= _specularAngle) + { + const float weight = solidAngle * bx::pow(ndotl, _specularPower); + + float rgba[4]; + unpack(rgba, row + xx*bpp/8); + + color[0] += rgba[0] * weight; + color[1] += rgba[1] * weight; + color[2] += rgba[2] * weight; + totalWeight += weight; + } + } + } + + 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]; + } + } + } + } + + ImageContainer* imageCubemapRadianceFilter(bx::AllocatorI* _allocator, const ImageContainer& _image, float _filterSize) + { + const uint32_t dstWidth = _image.m_width; + const uint32_t dstPitch = dstWidth*16; + const float invDstWidth = 1.0f / float(dstWidth); + + ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(dstWidth), uint16_t(dstWidth), 1, 1, true, true); + + for (uint8_t side = 0; side < 6; ++side) + { + ImageMip mip; + imageGetRawData(*output, side, 0, output->m_data, output->m_size, mip); + + for (uint32_t yy = 0; yy < dstWidth; ++yy) + { + for (uint32_t xx = 0; xx < dstWidth; ++xx) + { + 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; + + float dir[3]; + texelUvToDir(dir, side, uu, vv); + + Aabb aabb[6]; + calcFilterArea(aabb, dir, _filterSize); + + processFilterArea(dstData, _image, aabb, dir, 10.0f, 0.2f); + } + } + } + + return output; + } + +} // namespace bimg