From 76b2fe3e3661928d9794ab6a9f718fe2b833c7fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Fri, 15 Jun 2018 17:39:00 -0700 Subject: [PATCH] Adding CMFT functionality. --- src/image.cpp | 471 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 469 insertions(+), 2 deletions(-) diff --git a/src/image.cpp b/src/image.cpp index d43a756..3852fb9 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -5343,6 +5343,50 @@ namespace bimg // 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]; }; @@ -5380,8 +5424,48 @@ namespace bimg }}, }; + 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* _result, uint8_t _side, float _u, float _v) + void texelUvToDir(float* _outDir, uint8_t _side, float _u, float _v) { const CubeMapFace& face = s_cubeMapFace[_side]; @@ -5389,7 +5473,34 @@ namespace bimg 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(_result, tmp); + 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) @@ -5505,4 +5616,360 @@ namespace bimg 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