Browse Source

Adding CMFT functionality.

Branimir Karadžić 7 years ago
parent
commit
76b2fe3e36
1 changed files with 469 additions and 2 deletions
  1. 469 2
      src/image.cpp

+ 469 - 2
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