Przeglądaj źródła

Added cubemap import with support for list, cross, spherical and cylindrical sources (untested)

BearishSun 9 lat temu
rodzic
commit
c90bcb072b

+ 19 - 0
Source/BansheeCore/Include/BsCommonTypes.h

@@ -478,6 +478,25 @@ namespace bs
 		Convex
 	};
 
+	/** Determines the type of the source image for generating cubemaps. */
+	enum class CubemapSourceType
+	{
+		/** Source is a single image that will be replicated on all cubemap faces. */
+		Single,
+
+		/** 
+		 * Source is a list of 6 images, either sequentially next to each other or in a cross format. The system will 
+		 * automatically guess the layout and orientation based on the aspect ratio.
+		 */
+		Faces,
+
+		/** Source is a single spherical panoramic image. */
+		Spherical,
+
+		/** Source is a single cylindrical panoramic image. */
+		Cylindrical
+	};
+
 	/** 
 	 * Bits that map to a specific surface of a render target. Combine the bits to generate a mask that references
 	 * only specific render target surfaces.

+ 35 - 8
Source/BansheeCore/Include/BsPixelData.h

@@ -155,7 +155,27 @@ namespace bs
 		PCT_PACKED_R10G10B10A2 = 5, /**< 10 bits for first three components, 2 bits for last component */
         PCT_COUNT = 4    /**< Number of pixel types */
     };
-    
+
+	/** Determines how are texture pixels filtered during sampling. */
+	enum TextureFilter
+	{
+		/** Pixel nearest to the sampled location is chosen. */
+		TF_NEAREST,
+		/** Four pixels nearest to the sampled location are interpolated to yield the sampled color. */
+		TF_BILINEAR
+	};
+
+	/** A list of cubemap faces. */
+	enum CubemapFace
+	{
+		PositiveX,
+		NegativeX,
+		PositiveY,
+		NegativeY,
+		PositiveZ,
+		NegativeZ
+	};
+
 	/**
 	 * A buffer describing a volume (3D), image (2D) or line (1D) of pixels in memory. Pixels are stored as a succession 
 	 * of "depth" slices, each containing "height" rows of "width" pixels.
@@ -294,6 +314,16 @@ namespace bs
 		 */
       	PixelData getSubVolume(const PixelVolume &def) const;
         
+		/** 
+		 * Samples a color at the specified coordinates using a specific filter.
+		 * 
+		 * @param[in]	coords	Coordinates to sample the color at. They start at top left corner (0, 0), and are in range
+		 *						[0, 1].
+		 * @param[in]	filter	Filtering mode to use when sampling the color.
+		 * @return				Sampled color.
+		 */
+		Color sampleColorAt(const Vector2& coords, TextureFilter filter = TF_BILINEAR) const;
+
 		/**	Returns pixel color at the specified coordinates. */
 		Color getColorAt(UINT32 x, UINT32 y, UINT32 z = 0) const;
 
@@ -319,17 +349,14 @@ namespace bs
 		void setColors(Color* colors, UINT32 numElements);
 
 		/** 
-		 * Decodes data stored in a depth texture at the specified pixel coordinates, and outputs a floating point depth
-		 * value in range [0, 1]. 
+		 * Interprets pixel data as depth information as retrieved from the GPU's depth buffer. Converts the device specific
+		 * depth value to range [0, 1] and returns it.
 		 */
 		float getDepthAt(UINT32 x, UINT32 y, UINT32 z = 0) const;
 
-		/** Sets a depth value in range [0, 1] at the specified pixel coordinates. */
-		void setDepthAt(float depth, UINT32 x, UINT32 y, UINT32 z = 0);
-
 		/**
-		 * Converts all the internal data into an array of float. Array is mapped as such:
-		 * arrayIdx = x + y * width + z * width * height.
+		 * Converts all the internal data into an array of floats as if each individual pixel is retrieved with 
+		 * getDepthAt(). Array is mapped as such: arrayIdx = x + y * width + z * width * height.
 		 */
 		Vector<float> getDepths() const;
 

+ 23 - 0
Source/BansheeCore/Include/BsTextureImportOptions.h

@@ -60,6 +60,27 @@ namespace bs
 		 */
 		bool getSRGB() const { return mSRGB; }
 
+		/** 
+		 * Determines should the texture be imported as a cubemap. See setCubemapSource to choose how will the source
+		 * texture be converted to a cubemap.
+		 */
+		void setIsCubemap(bool cubemap) { mCubemap = cubemap; }
+
+		/** Checks if the texture will be imported as a cubemap. */
+		bool getIsCubemap() const { return mCubemap; }
+
+		/** 
+		 * Sets a value that determines how should the source texture be interpreted when generating a cubemap. Only
+		 * relevant when setIsCubemap() is set to true.
+		 */
+		void setCubemapSourceType(CubemapSourceType type) { mCubemapSourceType = type; }
+
+		/** 
+		 * Returns a value that determines how should the source texture be interpreted when generating a cubemap. Only
+		 * relevant when setIsCubemap() is set to true.
+		 */
+		CubemapSourceType getCubemapSourceType() const { return mCubemapSourceType; }
+
 		/** Creates a new import options object that allows you to customize how are textures imported. */
 		static SPtr<TextureImportOptions> create();
 
@@ -77,6 +98,8 @@ namespace bs
 		UINT32 mMaxMip;
 		bool mCPUCached;
 		bool mSRGB;
+		bool mCubemap;
+		CubemapSourceType mCubemapSourceType;
 	};
 
 	/** @} */

+ 11 - 21
Source/BansheeCore/Include/BsTextureImportOptionsRTTI.h

@@ -16,30 +16,20 @@ namespace bs
 	class BS_CORE_EXPORT TextureImportOptionsRTTI : public RTTIType<TextureImportOptions, ImportOptions, TextureImportOptionsRTTI>
 	{
 	private:
-		PixelFormat& getPixelFormat(TextureImportOptions* obj) { return obj->mFormat; }
-		void setPixelFormat(TextureImportOptions* obj, PixelFormat& value) { obj->mFormat = value; }
-
-		bool& getGenerateMips(TextureImportOptions* obj) { return obj->mGenerateMips; }
-		void setGenerateMips(TextureImportOptions* obj, bool& value) { obj->mGenerateMips = value; }
-
-		UINT32& getMaxMip(TextureImportOptions* obj) { return obj->mMaxMip; }
-		void setMaxMip(TextureImportOptions* obj, UINT32& value) { obj->mMaxMip = value; }
-
-		bool& getCPUCached(TextureImportOptions* obj) { return obj->mCPUCached; }
-		void setCPUCached(TextureImportOptions* obj, bool& value) { obj->mCPUCached = value; }
-
-		bool& getSRGB(TextureImportOptions* obj) { return obj->mSRGB; }
-		void setSRGB(TextureImportOptions* obj, bool& value) { obj->mSRGB = value; }
+		BS_BEGIN_RTTI_MEMBERS
+			BS_RTTI_MEMBER_PLAIN(mFormat, 0)
+			BS_RTTI_MEMBER_PLAIN(mGenerateMips, 1)
+			BS_RTTI_MEMBER_PLAIN(mMaxMip, 2)
+			BS_RTTI_MEMBER_PLAIN(mCPUCached, 3)
+			BS_RTTI_MEMBER_PLAIN(mSRGB, 4)
+			BS_RTTI_MEMBER_PLAIN(mCubemap, 5)
+			BS_RTTI_MEMBER_PLAIN(mCubemapSourceType, 6)
+		BS_END_RTTI_MEMBERS
 
 	public:
 		TextureImportOptionsRTTI()
-		{
-			addPlainField("mPixelFormat", 0, &TextureImportOptionsRTTI::getPixelFormat, &TextureImportOptionsRTTI::setPixelFormat);
-			addPlainField("mGenerateMips", 1, &TextureImportOptionsRTTI::getGenerateMips, &TextureImportOptionsRTTI::setGenerateMips);
-			addPlainField("mMaxMip", 2, &TextureImportOptionsRTTI::getMaxMip, &TextureImportOptionsRTTI::setMaxMip);
-			addPlainField("mCPUCached", 3, &TextureImportOptionsRTTI::getCPUCached, &TextureImportOptionsRTTI::setCPUCached);
-			addPlainField("mSRGB", 4, &TextureImportOptionsRTTI::getSRGB, &TextureImportOptionsRTTI::setSRGB);
-		}
+			:mInitMembers(this)
+		{ }
 
 		const String& getRTTIName() override
 		{

+ 42 - 7
Source/BansheeCore/Source/BsPixelData.cpp

@@ -4,6 +4,8 @@
 #include "BsPixelUtil.h"
 #include "BsPixelDataRTTI.h"
 #include "BsColor.h"
+#include "BsVector2.h"
+#include "BsMath.h"
 #include "BsDebug.h"
 
 namespace bs
@@ -89,6 +91,46 @@ namespace bs
 		return rval;
 	}
 
+	Color PixelData::sampleColorAt(const Vector2& coords, TextureFilter filter) const
+	{
+		Vector2 pixelCoords = coords * Vector2(mExtents.getWidth(), mExtents.getHeight());
+
+		INT32 maxExtentX = std::max(0, (INT32)mExtents.getWidth() - 1);
+		INT32 maxExtentY = std::max(0, (INT32)mExtents.getHeight() - 1);
+
+		if(filter == TF_BILINEAR)
+		{
+			pixelCoords -= Vector2(0.5f, 0.5f);
+
+			UINT32 x = (UINT32)Math::clamp(Math::floorToInt(pixelCoords.x), 0, maxExtentX);
+			UINT32 y = (UINT32)Math::clamp(Math::floorToInt(pixelCoords.y), 0, maxExtentY);
+
+			float fracX = pixelCoords.x - x;
+			float fracY = pixelCoords.y - y;
+
+			x = Math::clamp(x, 0U, (UINT32)maxExtentX);
+			y = Math::clamp(y, 0U, (UINT32)maxExtentY);
+
+			INT32 x1 = Math::clamp(x + 1, 0U, (UINT32)maxExtentX);
+			INT32 y1 = Math::clamp(y + 1, 0U, (UINT32)maxExtentY);
+
+			Color color;
+			color += (1.0f - fracX) * (1.0f - fracY) * getColorAt(x, y);
+			color += fracX * (1.0f - fracY) * getColorAt(x1, y);
+			color += (1.0f - fracX) * fracY * getColorAt(x, y1);
+			color += fracX * fracY * getColorAt(x1, y1);
+
+			return color;
+		}
+		else
+		{
+			UINT32 x = (UINT32)Math::clamp(Math::floorToInt(pixelCoords.x), 0, maxExtentX);
+			UINT32 y = (UINT32)Math::clamp(Math::floorToInt(pixelCoords.y), 0, maxExtentY);
+
+			return getColorAt(x, y);
+		}
+	}
+
 	Color PixelData::getColorAt(UINT32 x, UINT32 y, UINT32 z) const
 	{
 		Color cv;
@@ -200,13 +242,6 @@ namespace bs
 		return PixelUtil::unpackDepth(mFormat, (unsigned char *)getData() + pixelOffset);;
 	}
 
-	void PixelData::setDepthAt(float depth, UINT32 x, UINT32 y, UINT32 z)
-	{
-		UINT32 pixelSize = PixelUtil::getNumElemBytes(mFormat);
-		UINT32 pixelOffset = pixelSize * (z * mSlicePitch + y * mRowPitch + x);
-		PixelUtil::packDepth(depth, mFormat, (unsigned char *)getData() + pixelOffset);
-	}
-
 	Vector<float> PixelData::getDepths() const
 	{
 		UINT32 depth = mExtents.getDepth();

+ 1 - 1
Source/BansheeCore/Source/BsPixelUtil.cpp

@@ -1797,7 +1797,7 @@ namespace bs
 			return outputMipBuffers;
 		}
 
-		if (!Math::isPow2(src.getWidth()) || !Math::isPow2(src.getHeight()))
+		if (!Bitwise::isPow2(src.getWidth()) || !Bitwise::isPow2(src.getHeight()))
 		{
 			LOGERR("Mipmap generation failed. Texture width & height must be powers of 2.");
 			return outputMipBuffers;

+ 2 - 1
Source/BansheeCore/Source/BsTextureImportOptions.cpp

@@ -6,7 +6,8 @@
 namespace bs
 {
 	TextureImportOptions::TextureImportOptions()
-		: mFormat(PF_R8G8B8A8), mGenerateMips(false), mMaxMip(0), mCPUCached(false), mSRGB(false)
+		: mFormat(PF_R8G8B8A8), mGenerateMips(false), mMaxMip(0), mCPUCached(false), mSRGB(false), mCubemap(false)
+		, mCubemapSourceType(CubemapSourceType::Faces)
 	{ }
 
 	SPtr<TextureImportOptions> TextureImportOptions::create()

+ 17 - 5
Source/BansheeFreeImgImporter/Include/BsFreeImgImporter.h

@@ -22,22 +22,34 @@ namespace bs
 		virtual ~FreeImgImporter();
 
 		/** @copydoc SpecificImporter::isExtensionSupported */
-		virtual bool isExtensionSupported(const WString& ext) const override;
+		bool isExtensionSupported(const WString& ext) const override;
 
 		/** @copydoc SpecificImporter::isMagicNumberSupported */
-		virtual bool isMagicNumberSupported(const UINT8* magicNumPtr, UINT32 numBytes) const override;
+		bool isMagicNumberSupported(const UINT8* magicNumPtr, UINT32 numBytes) const override;
 
 		/** @copydoc SpecificImporter::import */
-		virtual SPtr<Resource> import(const Path& filePath, SPtr<const ImportOptions> importOptions) override;
+		SPtr<Resource> import(const Path& filePath, SPtr<const ImportOptions> importOptions) override;
 
 		/** @copydoc SpecificImporter::createImportOptions */
-		virtual SPtr<ImportOptions> createImportOptions() const override;
+		SPtr<ImportOptions> createImportOptions() const override;
 	private:
 		/**	Converts a magic number into an extension name. */
 		WString magicNumToExtension(const UINT8* magic, UINT32 maxBytes) const;
 
 		/**	Imports an image from the provided data stream. */
-		SPtr<PixelData> importRawImage(SPtr<DataStream> fileData);
+		SPtr<PixelData> importRawImage(const SPtr<DataStream>& fileData);
+
+		/** 
+		 * Generates six cubemap faces from the provided source texture. *
+		 * 
+		 * @param[in]	source		Source texture containing the pixels to generate the cubemap from.
+		 * @param[in]	sourceType	Type of the source texture, determines how is the data interpreted.
+		 * @param[out]	output		Will contain the six cubemap faces, if the method returns true. The faces will be in the
+		 *							same order as presented in the CubemapFace enum.
+		 * @return					True if the cubemap faces were successfully generated, false otherwise.
+		 */
+		bool generateCubemap(const SPtr<PixelData>& source, CubemapSourceType sourceType, 
+			std::array<SPtr<PixelData>, 6>& output);
 
 		Vector<WString> mExtensions;
 		UnorderedMap<WString, int> mExtensionToFID;

+ 331 - 21
Source/BansheeFreeImgImporter/Source/BsFreeImgImporter.cpp

@@ -10,8 +10,11 @@
 #include "BsFileSystem.h"
 #include "BsCoreApplication.h"
 #include "BsCoreThread.h"
-
+#include "BsMath.h"
+#include "BsVector2.h"
+#include "BsVector3.h"
 #include "FreeImage.h"
+#include "BsBitwise.h"
 
 using namespace std::placeholders;
 
@@ -135,18 +138,39 @@ namespace bs
 		if(imgData == nullptr || imgData->getData() == nullptr)
 			return nullptr;
 
+		Vector<SPtr<PixelData>> faceData;
+
+		TextureType texType;
+		if(textureImportOptions->getIsCubemap())
+		{
+			texType = TEX_TYPE_CUBE_MAP;
+
+			std::array<SPtr<PixelData>, 6> cubemapFaces;
+			if (generateCubemap(imgData, textureImportOptions->getCubemapSourceType(), cubemapFaces))
+				faceData.insert(faceData.begin(), cubemapFaces.begin(), cubemapFaces.end());
+			else // Fall-back to 2D texture
+			{
+				texType = TEX_TYPE_2D;
+				faceData.push_back(imgData);
+			}
+		}
+		else
+		{
+			texType = TEX_TYPE_2D;
+			faceData.push_back(imgData);
+		}
+
 		UINT32 numMips = 0;
-		if (textureImportOptions->getGenerateMipmaps())
+		if (textureImportOptions->getGenerateMipmaps() && Bitwise::isPow2(faceData[0]->getWidth()) && 
+			Bitwise::isPow2(faceData[0]->getHeight()))
 		{
-			UINT32 maxPossibleMip = PixelUtil::getMaxMipmaps(imgData->getWidth(), imgData->getHeight(), imgData->getDepth(), imgData->getFormat());
+			UINT32 maxPossibleMip = PixelUtil::getMaxMipmaps(faceData[0]->getWidth(), faceData[0]->getHeight(), 
+				faceData[0]->getDepth(), faceData[0]->getFormat());
+
 			if (textureImportOptions->getMaxMip() == 0)
-			{
 				numMips = maxPossibleMip;
-			}
 			else
-			{
 				numMips = std::min(maxPossibleMip, textureImportOptions->getMaxMip());
-			}
 		}
 
 		int usage = TU_DEFAULT;
@@ -156,9 +180,9 @@ namespace bs
 		bool sRGB = textureImportOptions->getSRGB();
 
 		TEXTURE_DESC texDesc;
-		texDesc.type = TEX_TYPE_2D;
-		texDesc.width = imgData->getWidth();
-		texDesc.height = imgData->getHeight();
+		texDesc.type = texType;
+		texDesc.width = faceData[0]->getWidth();
+		texDesc.height = faceData[0]->getHeight();
 		texDesc.numMips = numMips;
 		texDesc.format = textureImportOptions->getFormat();
 		texDesc.usage = usage;
@@ -166,18 +190,22 @@ namespace bs
 
 		SPtr<Texture> newTexture = Texture::_createPtr(texDesc);
 
-		Vector<SPtr<PixelData>> mipLevels;
-		if (numMips > 0)
-			mipLevels = PixelUtil::genMipmaps(*imgData, MipMapGenOptions());
-		else
-			mipLevels.insert(mipLevels.begin(), imgData);
-
-		for (UINT32 mip = 0; mip < (UINT32)mipLevels.size(); ++mip)
+		UINT32 numFaces = (UINT32)faceData.size();
+		for (UINT32 i = 0; i < numFaces; i++)
 		{
-			SPtr<PixelData> dst = newTexture->getProperties().allocBuffer(0, mip);
+			Vector<SPtr<PixelData>> mipLevels;
+			if (numMips > 0)
+				mipLevels = PixelUtil::genMipmaps(*faceData[i], MipMapGenOptions());
+			else
+				mipLevels.push_back(faceData[i]);
+
+			for (UINT32 mip = 0; mip < (UINT32)mipLevels.size(); ++mip)
+			{
+				SPtr<PixelData> dst = newTexture->getProperties().allocBuffer(0, mip);
 
-			PixelUtil::bulkPixelConversion(*mipLevels[mip], *dst);
-			newTexture->writeData(dst, 0, mip);
+				PixelUtil::bulkPixelConversion(*mipLevels[mip], *dst);
+				newTexture->writeData(dst, i, mip);
+			}
 		}
 
 		fileData->close();
@@ -188,7 +216,7 @@ namespace bs
 		return newTexture;
 	}
 
-	SPtr<PixelData> FreeImgImporter::importRawImage(SPtr<DataStream> fileData)
+	SPtr<PixelData> FreeImgImporter::importRawImage(const SPtr<DataStream>& fileData)
 	{
 		if(fileData->size() > std::numeric_limits<UINT32>::max())
 		{
@@ -362,4 +390,286 @@ namespace bs
 
 		return texData;
 	}
+
+	/** 
+	 * Reads the source texture as a horizontal or vertical list of 6 cubemap faces. 
+	 * 
+	 * @param[in]	source		Source texture to read.
+	 * @param[out]	output		Output array that will contain individual cubemap faces.
+	 * @param[in]	faceSize	Size of a single face, in pixels. Both width & height must match.
+	 * @param[in]	vertical	True if the faces are laid out vertically, false if horizontally.
+	 */
+	void readCubemapList(const SPtr<PixelData>& source, std::array<SPtr<PixelData>, 6>& output, UINT32 faceSize, bool vertical)
+	{
+		Vector2I faceStart;
+		for(UINT32 i = 0; i < 6; i++)
+		{
+			output[i] = PixelData::create(faceSize, faceSize, 1, source->getFormat());
+
+			if (vertical)
+				faceStart.y += faceSize;
+			else
+				faceStart.x += faceSize;
+
+			PixelVolume volume(faceStart.x, faceStart.y, faceStart.x + faceSize, faceStart.y + faceSize);
+			PixelData subVolumeData = source->getSubVolume(volume);
+
+			assert(output[i]->getSize() == subVolumeData.getSize());
+			memcpy(output[i]->getData(), subVolumeData.getData(), subVolumeData.getSize());
+		}
+	}
+
+	/** 
+	 * Reads the source texture as a horizontal or vertical "cross" of 6 cubemap faces. 
+	 * 
+	 * Vertical layout:
+	 *    +Y
+	 * -X -Z +X
+	 *    -Y
+	 *    +Z
+	 * 
+	 * Horizontal layout:
+	 *    +Y
+	 * -X -Z +X +Z
+	 *    -Y
+	 * 
+	 * @param[in]	source		Source texture to read.
+	 * @param[out]	output		Output array that will contain individual cubemap faces.
+	 * @param[in]	faceSize	Size of a single face, in pixels. Both width & height must match.
+	 * @param[in]	vertical	True if the faces are laid out vertically, false if horizontally.
+	 */
+	void readCubemapCross(const SPtr<PixelData>& source, std::array<SPtr<PixelData>, 6>& output, UINT32 faceSize,
+		bool vertical)
+	{
+		const static UINT32 vertFaceIndices[] = { 5, 3, 1, 7, 10, 4 };
+		const static UINT32 horzFaceIndices[] = { 6, 4, 1, 9, 7, 5 };
+
+		const UINT32* faceIndices = vertical ? vertFaceIndices : horzFaceIndices;
+		UINT32 numFacesInRow = vertical ? 3 : 4;
+
+		for (UINT32 i = 0; i < 6; i++)
+		{
+			output[i] = PixelData::create(faceSize, faceSize, 1, source->getFormat());
+
+			UINT32 faceX = (faceIndices[i] % numFacesInRow) * faceSize;
+			UINT32 faceY = (faceIndices[i] / numFacesInRow) * faceSize;
+
+			PixelVolume volume(faceX, faceY, faceX + faceSize, faceY + faceSize);
+			PixelData subVolumeData = source->getSubVolume(volume);
+
+			assert(output[i]->getSize() == subVolumeData.getSize());
+			memcpy(output[i]->getData(), subVolumeData.getData(), subVolumeData.getSize());
+		}
+	}
+
+	/** Method that maps a direction to a point on a plane in range [0, 1] using spherical mapping. */
+	Vector2 mapCubemapDirToSpherical(const Vector3& dir)
+	{
+		Vector3 nrmDir = Vector3::normalize(dir);
+
+		float u = acos(Math::abs(nrmDir.z)) / Math::PI;
+		if (nrmDir.x > 0.0f)
+			u = 1.0f - u;
+
+		float v = 1.0f - acos(nrmDir.y) / Math::PI;
+
+		return Vector2(u, v);
+	}
+
+	/** Method that maps a direction to a point on a plane in range [0, 1] using cylindrical mapping. */
+	Vector2 mapCubemapDirToCylindrical(const Vector3& dir)
+	{
+		Vector3 nrmDir = Vector3::normalize(dir);
+
+		float u = 0.75f - atan2(nrmDir.z, nrmDir.x) / Math::HALF_PI;
+		if (u > 1.0f)
+			u -= 1.0f;
+
+		float v = 1.0f - acos(nrmDir.y) / Math::PI;
+
+		return Vector2(u, v);
+	}
+
+	/** Resizes the provided cubemap faces and outputs a new set of resized faces. */
+	void downsampleCubemap(const std::array<SPtr<PixelData>, 6>& input, std::array<SPtr<PixelData>, 6>& output, UINT32 size)
+	{
+		for(UINT32 i = 0; i < 6; i++)
+		{
+			output[i] = PixelData::create(size, size, 1, input[i]->getFormat());
+			PixelUtil::scale(*input[i], *output[i]);
+		}
+	}
+
+	/** 
+	 * Reads the source texture and remaps its data into six faces of a cubemap.
+	 * 
+	 * @param[in]	source		Source texture to remap.
+	 * @param[out]	output		Remapped faces of the cubemap.
+	 * @param[in]	faceSize	Width/height of each individual face, in pixels.
+	 * @param[in]	remap		Function to use for remapping the cubemap direction to UV.
+	 */
+	void readCubemapUVRemap(const SPtr<PixelData>& source, std::array<SPtr<PixelData>, 6>& output, UINT32 faceSize, 
+		const std::function<Vector2(Vector3)>& remap)
+	{
+		struct RemapInfo
+		{
+			int idx[3];
+			Vector3 sign;
+		};
+
+		// Mapping from default (X, Y, 1.0f) plane to relevant cube face. Also flipping Y so it corresponds to how pixel
+		// coordinates are mapped.
+		static const RemapInfo remapLookup[] = 
+		{
+			{ 2, 1, 0, {  1.0f, -1.0f,  1.0f }}, // X+
+			{ 2, 1, 0, { -1.0f, -1.0f, -1.0f }}, // X-
+			{ 0, 2, 1, { -1.0f,  1.0f, -1.0f }}, // Y+
+			{ 0, 2, 1, { -1.0f, -1.0f,  1.0f }}, // Y-
+			{ 0, 1, 2, {  1.0f, -1.0f, -1.0f }}, // Z+
+			{ 0, 1, 2, { -1.0f, -1.0f,  1.0f }}  // Z-
+		};
+
+		float invSize = 1.0f / faceSize;
+		for (UINT32 faceIdx = 0; faceIdx < 6; faceIdx++)
+		{
+			output[faceIdx] = PixelData::create(faceSize, faceSize, 1, source->getFormat());
+
+			const RemapInfo& remapInfo = remapLookup[faceIdx];
+			for (UINT32 y = 0; y < faceSize; y++)
+			{
+				for (UINT32 x = 0; x < faceSize; x++)
+				{
+					// Map from pixel coordinates to [-1, 1] range.
+					Vector2 sourceCoord = (Vector2((float)x, (float)y) * invSize) * 2.0f - Vector2(1.0f, 1.0f);
+					Vector3 direction = Vector3(sourceCoord.x, sourceCoord.y, 1.0f);
+
+					// Rotate towards current face
+					Vector3 rotatedDir;
+					rotatedDir[remapInfo.idx[0]] = direction.x;
+					rotatedDir[remapInfo.idx[1]] = direction.y;
+					rotatedDir[remapInfo.idx[2]] = direction.z;
+
+					rotatedDir *= remapInfo.sign;
+
+					// Find location in the source to sample from
+					Vector2 sourceUV = remap(rotatedDir);
+					Color color = source->sampleColorAt(sourceUV);
+
+					// Write the sampled color
+					output[faceIdx]->setColorAt(color, x, y);
+				}
+			}
+		}
+	}
+
+	bool FreeImgImporter::generateCubemap(const SPtr<PixelData>& source, CubemapSourceType sourceType,
+						 std::array<SPtr<PixelData>, 6>& output)
+	{
+		Vector2I faceSize;
+		bool cross = false;
+		bool vertical = false;
+
+		switch(sourceType)
+		{
+		case CubemapSourceType::Faces:
+			{
+				float aspect = source->getWidth() / (float)source->getHeight();
+				
+				if(Math::approxEquals(aspect, 6.0f)) // Horizontal list
+				{
+					faceSize.x = source->getWidth() / 6;
+					faceSize.y = source->getHeight();
+				}
+				else if(Math::approxEquals(aspect, 1.0f / 6.0f)) // Vertical list
+				{
+					faceSize.x = source->getWidth();
+					faceSize.y = source->getHeight() / 6;
+					vertical = true;
+				}
+				else if(Math::approxEquals(aspect, 4.0f / 3.0f)) // Horizontal cross
+				{
+					faceSize.x = source->getWidth() / 4;
+					faceSize.y = source->getHeight() / 3;
+					cross = true;
+				}
+				else if(Math::approxEquals(aspect, 3.0f / 4.0f)) // Vertical cross
+				{
+					faceSize.x = source->getWidth() / 3;
+					faceSize.y = source->getHeight() / 4;
+					cross = true;
+					vertical = true;
+				}
+				else
+				{
+					LOGWRN("Unable to generate a cubemap: unrecognized face configuration.");
+					return false;
+				}
+			}
+			break;
+		case CubemapSourceType::Cylindrical:
+		case CubemapSourceType::Spherical:
+			// Half of the source size will be used for each cube face, which should yield good enough quality
+			faceSize.x = std::max(source->getWidth(), source->getHeight()) / 2;
+			faceSize.x = Bitwise::closestPow2(faceSize.x);
+
+			// Don't allow sizes larger than 4096
+			faceSize.x = std::min(faceSize.x, 4096);
+
+			// We also do 4x super-sampling, so increase size accordingly
+			faceSize.x *= 4;
+
+			faceSize.y = faceSize.x;
+
+			break;
+		default: // Assuming single-image
+			faceSize.x = source->getWidth();
+			faceSize.y = source->getHeight();
+			break;
+		}
+
+		if (faceSize.x != faceSize.y)
+		{
+			LOGWRN("Unable to generate a cubemap: width & height must match.");
+			return false;
+		}
+
+		if (!Bitwise::isPow2(faceSize.x))
+		{
+			LOGWRN("Unable to generate a cubemap: width & height must be powers of 2.");
+			return false;
+		}
+
+		switch (sourceType)
+		{
+		case CubemapSourceType::Faces:
+		{
+			if (cross)
+				readCubemapCross(source, output, faceSize.x, vertical);
+			else
+				readCubemapList(source, output, faceSize.x, vertical);
+		}
+		break;
+		case CubemapSourceType::Cylindrical:
+		{			
+			std::array<SPtr<PixelData>, 6> superSampledOutput;
+			readCubemapUVRemap(source, superSampledOutput, faceSize.x, &mapCubemapDirToCylindrical);
+			downsampleCubemap(superSampledOutput, output, faceSize.x / 4);
+		}
+		break;
+		case CubemapSourceType::Spherical:
+		{
+			std::array<SPtr<PixelData>, 6> superSampledOutput;
+			readCubemapUVRemap(source, superSampledOutput, faceSize.x, &mapCubemapDirToSpherical);
+			downsampleCubemap(superSampledOutput, output, faceSize.x / 4);
+		}
+		break;
+		default: // Single-image
+			for (UINT32 i = 0; i < 6; i++)
+				output[i] = source;
+
+			break;
+		}
+
+		return true;
+	}
 }

+ 17 - 5
Source/BansheeUtility/Include/BsBitwise.h

@@ -15,9 +15,9 @@ namespace bs
 	{
     public:
 		/** Returns the most significant bit set in a value. */
-		static unsigned int mostSignificantBitSet(unsigned int value)
+		static UINT32 mostSignificantBitSet(unsigned int value)
         {
-            unsigned int result = 0;
+			UINT32 result = 0;
             while (value != 0) {
                 ++result;
                 value >>= 1;
@@ -25,8 +25,8 @@ namespace bs
             return result-1;
         }
 
-		/** Returns the closest power-of-two number greater or equal to value. */
-        static UINT32 firstPO2From(UINT32 n)
+		/** Returns the power-of-two number greater or equal to the provided value. */
+        static UINT32 nextPow2(UINT32 n)
         {
             --n;            
             n |= n >> 16;
@@ -38,9 +38,21 @@ namespace bs
             return n;
         }
 
+		/** Returns the power-of-two number closest to the provided value. */
+		static UINT32 closestPow2(UINT32 n)
+		{
+			UINT32 next = nextPow2(n);
+
+			UINT32 prev = next >> 1;
+			if (n - prev < next - n)
+				return prev;
+			
+			return next;
+		}
+
 		/** Determines whether the number is power-of-two or not. */
         template<typename T>
-        static bool isPO2(T n)
+        static bool isPow2(T n)
         {
             return (n & (n-1)) == 0;
         }

+ 0 - 7
Source/BansheeUtility/Include/BsMath.h

@@ -77,13 +77,6 @@ namespace bs
 			return std::max(std::min(val, (T)1), (T)0);
 		}
 
-		/** Checks is the specified value a power of two. Only works on integer values. */
-		template <typename T>
-		static bool isPow2(T val)
-		{
-			return (val & (val - 1)) == 0;
-		}
-
 		static bool isNaN(float f)
 		{
 			return f != f;

+ 12 - 1
Source/MBansheeEditor/Inspectors/Texture2DInspector.cs

@@ -20,6 +20,10 @@ namespace BansheeEditor
         private GUIIntField maximumMipsField = new GUIIntField(new LocEdString("Maximum mipmap level"));
         private GUIToggleField srgbField = new GUIToggleField(new LocEdString("Gamma space"));
         private GUIToggleField cpuCachedField = new GUIToggleField(new LocEdString("CPU cached"));
+        private GUIToggleField isCubemapField = new GUIToggleField(new LocEdString("Cubemap"));
+        private GUIEnumField cubemapSourceTypeField = 
+            new GUIEnumField(typeof(CubemapSourceType), new LocEdString("Cubemap source"));
+
         private GUIButton reimportButton = new GUIButton(new LocEdString("Reimport"));
 
         private TextureImportOptions importOptions;
@@ -36,7 +40,8 @@ namespace BansheeEditor
                 maximumMipsField.OnChanged += x => importOptions.MaxMipmapLevel = x;
                 srgbField.OnChanged += x => importOptions.IsSRGB = x;
                 cpuCachedField.OnChanged += x => importOptions.CPUCached = x;
-
+                isCubemapField.OnChanged += x => importOptions.IsCubemap = x;
+                cubemapSourceTypeField.OnSelectionChanged += x => importOptions.CubemapSourceType = (CubemapSourceType)x;
                 reimportButton.OnClick += TriggerReimport;
 
                 Layout.AddElement(formatField);
@@ -44,6 +49,8 @@ namespace BansheeEditor
                 Layout.AddElement(maximumMipsField);
                 Layout.AddElement(srgbField);
                 Layout.AddElement(cpuCachedField);
+                Layout.AddElement(isCubemapField);
+                Layout.AddElement(cubemapSourceTypeField);
                 Layout.AddSpace(10);
 
                 GUILayout reimportButtonLayout = Layout.AddLayoutX();
@@ -62,6 +69,10 @@ namespace BansheeEditor
             maximumMipsField.Value = newImportOptions.MaxMipmapLevel;
             srgbField.Value = newImportOptions.IsSRGB;
             cpuCachedField.Value = newImportOptions.CPUCached;
+            isCubemapField.Value = newImportOptions.IsCubemap;
+            cubemapSourceTypeField.Value = (ulong) newImportOptions.CubemapSourceType;
+
+            cubemapSourceTypeField.Active = importOptions.IsCubemap;
 
             importOptions = newImportOptions;
 

+ 59 - 0
Source/MBansheeEditor/Windows/Library/ImportOptions.cs

@@ -18,6 +18,33 @@ namespace BansheeEditor
 
     }
 
+    /// <summary>
+    /// Determines the type of the source image for generating cubemaps.
+    /// </summary>
+    public enum CubemapSourceType
+    {
+        /// <summary>
+        /// Source is a single image that will be replicated on all cubemap faces.
+        /// </summary>
+        Single,
+
+		/// <summary>
+        /// Source is a list of 6 images, either sequentially next to each other or in a cross format. The system will 
+        /// automatically guess the layout and orientation based on the aspect ratio.
+        /// </summary>
+		Faces,
+
+        /// <summary>
+        /// Source is a single spherical panoramic image.
+        /// </summary>
+        Spherical,
+
+        /// <summary>
+        /// Source is a single cylindrical panoramic image.
+        /// </summary>
+        Cylindrical
+    };
+
     /// <summary>
     /// Provides options for controlling how is a texture resource imported.
     /// </summary>
@@ -77,6 +104,26 @@ namespace BansheeEditor
             set { Internal_SetIsSRGB(mCachedPtr, value); }
         }
 
+        /// <summary>
+        /// Determines should the texture be imported as a cubemap. See <see cref="CubemapSourceType"/> to choose how will
+        /// the source texture be converted to a cubemap.
+        /// </summary>
+        public bool IsCubemap
+        {
+            get { return Internal_GetIsCubemap(mCachedPtr); }
+            set { Internal_SetIsCubemap(mCachedPtr, value); }
+        }
+
+        /// <summary>
+        /// Sets a value that determines how should the source texture be interpreted when generating a cubemap. Only
+        /// relevant when <see cref="IsCubemap"/> is set to true.
+        /// </summary>
+        public CubemapSourceType CubemapSourceType
+        {
+            get { return Internal_GetCubemapSourceType(mCachedPtr); }
+            set { Internal_SetCubemapSourceType(mCachedPtr, value); }
+        }
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_CreateInstance(TextureImportOptions instance);
 
@@ -109,6 +156,18 @@ namespace BansheeEditor
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetIsSRGB(IntPtr thisPtr, bool value);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern bool Internal_GetIsCubemap(IntPtr thisPtr);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetIsCubemap(IntPtr thisPtr, bool value);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern CubemapSourceType Internal_GetCubemapSourceType(IntPtr thisPtr);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_SetCubemapSourceType(IntPtr thisPtr, CubemapSourceType value);
     }
 
     /// <summary>

+ 4 - 0
Source/SBansheeEditor/Include/BsScriptImportOptions.h

@@ -76,6 +76,10 @@ namespace bs
 		static void internal_SetCPUCached(ScriptTextureImportOptions* thisPtr, bool value);
 		static bool internal_GetIsSRGB(ScriptTextureImportOptions* thisPtr);
 		static void internal_SetIsSRGB(ScriptTextureImportOptions* thisPtr, bool value);
+		static bool internal_GetIsCubemap(ScriptTextureImportOptions* thisPtr);
+		static void internal_SetIsCubemap(ScriptTextureImportOptions* thisPtr, bool value);
+		static CubemapSourceType internal_GetCubemapSourceType(ScriptTextureImportOptions* thisPtr);
+		static void internal_SetCubemapSourceType(ScriptTextureImportOptions* thisPtr, CubemapSourceType value);
 	};
 
 	/**	Interop class between C++ & CLR for MeshImportOptions. */

+ 26 - 0
Source/SBansheeEditor/Source/BsScriptImportOptions.cpp

@@ -81,6 +81,10 @@ namespace bs
 		metaData.scriptClass->addInternalCall("Internal_SetCPUCached", &ScriptTextureImportOptions::internal_SetCPUCached);
 		metaData.scriptClass->addInternalCall("Internal_GetIsSRGB", &ScriptTextureImportOptions::internal_GetIsSRGB);
 		metaData.scriptClass->addInternalCall("Internal_SetIsSRGB", &ScriptTextureImportOptions::internal_SetIsSRGB);
+		metaData.scriptClass->addInternalCall("Internal_GetIsCubemap", &ScriptTextureImportOptions::internal_GetIsCubemap);
+		metaData.scriptClass->addInternalCall("Internal_SetIsCubemap", &ScriptTextureImportOptions::internal_SetIsCubemap);
+		metaData.scriptClass->addInternalCall("Internal_GetCubemapSourceType", &ScriptTextureImportOptions::internal_GetCubemapSourceType);
+		metaData.scriptClass->addInternalCall("Internal_SetCubemapSourceType", &ScriptTextureImportOptions::internal_SetCubemapSourceType);
 	}
 
 	SPtr<TextureImportOptions> ScriptTextureImportOptions::getTexImportOptions()
@@ -157,6 +161,28 @@ namespace bs
 		thisPtr->getTexImportOptions()->setSRGB(value);
 	}
 
+
+	bool ScriptTextureImportOptions::internal_GetIsCubemap(ScriptTextureImportOptions* thisPtr)
+	{
+		return thisPtr->getTexImportOptions()->getIsCubemap();
+	}
+
+	void ScriptTextureImportOptions::internal_SetIsCubemap(ScriptTextureImportOptions* thisPtr, bool value)
+	{
+		thisPtr->getTexImportOptions()->setIsCubemap(value);
+	}
+
+	CubemapSourceType ScriptTextureImportOptions::internal_GetCubemapSourceType(ScriptTextureImportOptions* thisPtr)
+	{
+		return thisPtr->getTexImportOptions()->getCubemapSourceType();
+	}
+
+	void ScriptTextureImportOptions::internal_SetCubemapSourceType(ScriptTextureImportOptions* thisPtr, CubemapSourceType value)
+	{
+		thisPtr->getTexImportOptions()->setCubemapSourceType(value);
+	}
+
+
 	ScriptMeshImportOptions::ScriptMeshImportOptions(MonoObject* instance)
 		:ScriptObject(instance)
 	{