Przeglądaj źródła

Skybox rendering & cubemap import fixes

BearishSun 9 lat temu
rodzic
commit
e32b4ad844

+ 4 - 0
Data/Raw/Engine/DataList.json

@@ -496,6 +496,10 @@
             "Path": "Resolve.bsl",
             "UUID": "9d5f5101-2d7e-432c-b8ad-1998de9ca5c7"
         },
+        {
+            "Path": "Skybox.bsl",
+            "UUID": "b1c191fa-0c24-4987-8d3b-72b17511621d"
+        },		
         {
             "Path": "SpriteImageAlpha.bsl",
             "UUID": "4c00537f-9d3e-4cb7-8a30-b4ee9f278006"

+ 91 - 0
Data/Raw/Engine/Shaders/Skybox.bsl

@@ -0,0 +1,91 @@
+#include "$ENGINE$\PerCameraData.bslinc"
+
+Parameters =
+{
+	SamplerCUBE 	gSkySamp : alias("gSkyTex");
+	TextureCUBE  	gSkyTex;
+};
+
+Technique : inherits("PerCameraData") =
+{
+	Language = "HLSL11";
+	
+	Pass =
+	{
+		Cull = CW;
+		CompareFunc = LTE;
+		DepthWrite = false;
+		
+		Vertex =
+		{
+			void main(
+				in float3 inPos : POSITION,
+				out float4 oPosition : SV_Position,
+				out float3 oDir : TEXCOORD0)
+			{
+				float4 pos = mul(gMatViewProj, float4(inPos.xyz + gViewOrigin, 1));
+			
+				// Set Z = W so that final depth is 1.0f and it renders behind everything else
+				oPosition = pos.xyww;
+				oDir = inPos;
+			}
+		};
+		
+		Fragment =
+		{
+			TextureCube gSkyTex : register(t0);
+			SamplerState gSkySamp : register(s0);
+		
+			float4 main(
+				in float4 inPos : SV_Position, 
+				in float3 dir : TEXCOORD0) : SV_Target
+			{
+				return gSkyTex.Sample(gSkySamp, dir);
+			}
+		};	
+	};
+};
+
+Technique : inherits("PerCameraData") =
+{
+	Language = "GLSL";
+	
+	Pass =
+	{
+		Cull = CW;
+		CompareFunc = LTE;
+		DepthWrite = false;
+		
+		Vertex = 
+		{
+			layout(location = 0) in vec3 bs_position;
+			layout(location = 0) out vec3 dir;	
+		
+			out gl_PerVertex
+			{
+				vec4 gl_Position;
+			};
+		
+			void main()
+			{
+				vec4 pos = gMatViewProj * vec4(bs_position.xyz + gViewOrigin, 1);
+			
+				// Set Z = W so that final depth is 1.0f and it renders behind everything else
+				gl_Position = pos.xyww;
+				dir = bs_position;
+			}
+		};
+	
+		Fragment =
+		{
+			layout(location = 0) in vec3 dir;		
+			layout(binding = 1) uniform samplerCube gSkyTex;
+			layout(location = 0) out vec4 fragColor;
+			
+			void main()
+			{
+				fragColor = texture(gSkyTex, dir);
+			}	
+		};
+	};
+};

+ 8 - 5
Source/BansheeCore/Include/BsCCamera.h

@@ -136,15 +136,18 @@ namespace bs
 		/** @copydoc Camera::setLayers */
 		void setLayers(UINT64 layers) { mInternal->setLayers(layers); }
 
-		/** Returns number of samples if the camera uses multiple samples per pixel. */
+		/** @copydoc Camera::getMSAACount */
 		UINT32 getMSAACount() const { return mInternal->getMSAACount(); }
 
-		/**
-		* Enables or disables multi-sampled anti-aliasing. Set to zero or one to disable, or to the required number of
-		* samples to enable.
-		*/
+		/** @copydoc Camera::setMSAACount */
 		void setMSAACount(UINT32 count) { mInternal->setMSAACount(count); }
 
+		/** @copydoc Camera::setSkybox */
+		void setSkybox(const HTexture& texture) { mInternal->setSkybox(texture); }
+
+		/** @copydoc Camera::getSkybox */
+		HTexture getSkybox() const { return mInternal->getSkybox(); }
+
 		/** Returns settings that are used for controling post-process operations like tonemapping. */
 		const SPtr<PostProcessSettings>& getPostProcessSettings() const { return mInternal->getPostProcessSettings(); }
 

+ 22 - 2
Source/BansheeCore/Include/BsCamera.h

@@ -506,13 +506,33 @@ namespace bs
 		mutable AABox mBoundingBox; /**< Frustum bounding box. */
      };
 
+	 /** @copydoc CameraBase */
+	 template<bool Core>
+	 class TCamera : public CameraBase
+	 {
+	 public:
+		 typedef typename TTextureType<Core>::Type TextureType;
+
+		/** 
+		 * Sets a texture that will be used for rendering areas of the camera's render target not covered by any geometry. 
+		 * If not set a clear color will be used instead.
+		 */
+		void setSkybox(const TextureType& texture) { mSkyTexture = texture; _markCoreDirty(); }
+
+		/** @see setSkybox() */
+		TextureType getSkybox() const { return mSkyTexture; }
+
+	 protected:
+		 TextureType mSkyTexture;
+	 };
+
 	/** @} */
 	/** @addtogroup Renderer-Engine-Internal
 	 *  @{
 	 */
 
 	/** @copydoc CameraBase */
-	class BS_CORE_EXPORT CameraCore : public CoreObjectCore, public CameraBase
+	class BS_CORE_EXPORT CameraCore : public CoreObjectCore, public TCamera<true>
 	{
 	public:
 		~CameraCore();
@@ -541,7 +561,7 @@ namespace bs
 	};
 
 	/** @copydoc CameraBase */
-	class BS_CORE_EXPORT Camera : public IReflectable, public CoreObject, public CameraBase
+	class BS_CORE_EXPORT Camera : public IReflectable, public CoreObject, public TCamera<false>
     {
     public:
 		/**	Returns the viewport used by the camera. */	

+ 1 - 0
Source/BansheeCore/Include/BsCameraRTTI.h

@@ -42,6 +42,7 @@ namespace bs
 			BS_RTTI_MEMBER_PLAIN(mMSAA, 23)
 			/** BS_RTTI_MEMBER_PLAIN(mPPSettings, 24) */
 			BS_RTTI_MEMBER_REFLPTR(mPPSettings, 25)
+			BS_RTTI_MEMBER_REFL(mSkyTexture, 26)
 		BS_END_RTTI_MEMBERS
 			
 	public:

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

@@ -600,6 +600,10 @@ namespace bs
 	template<> struct TMaterialPtrType < false > { typedef HMaterial Type; };
 	template<> struct TMaterialPtrType < true > { typedef SPtr<MaterialCore> Type; };
 
+	template<bool Core> struct TTextureType {};
+	template<> struct TTextureType < false > { typedef HTexture Type; };
+	template<> struct TTextureType < true > { typedef SPtr<TextureCore> Type; };
+
 	/** @cond SPECIALIZATIONS */
 	BS_ALLOW_MEMCPY_SERIALIZATION(TextureSurface);
 	/** @endcond */

+ 1 - 1
Source/BansheeCore/Include/BsPixelData.h

@@ -312,7 +312,7 @@ namespace bs
 		 * Returns pixel data containing a sub-volume of this object. Returned data will not have its own buffer, but will
 		 * instead point to this one. It is up to the caller to ensure this object outlives any sub-volume objects.
 		 */
-      	PixelData getSubVolume(const PixelVolume &def) const;
+      	PixelData getSubVolume(const PixelVolume& volume) const;
         
 		/** 
 		 * Samples a color at the specified coordinates using a specific filter.

+ 22 - 0
Source/BansheeCore/Include/BsPixelUtil.h

@@ -44,6 +44,17 @@ namespace bs
 		Kaiser
 	};
 
+	/** Determines on which axes to mirror an image. */
+	enum class MirrorModeBits
+	{
+		X = 1 << 0,
+		Y = 1 << 1, 
+		Z = 1 << 2
+	};
+
+	typedef Flags<MirrorModeBits> MirrorMode;
+	BS_FLAGS_OPERATORS(MirrorModeBits);
+
 	/**	Options used to control texture compression. */
 	struct CompressionOptions
 	{
@@ -229,6 +240,17 @@ namespace bs
 		 */
 		static void scale(const PixelData& src, PixelData& dst, Filter filter = FILTER_LINEAR);
 
+		/** 
+		 * Mirrors the contents of the provided object along the X, Y and/or Z axes. */
+		static void mirror(PixelData& pixelData, MirrorMode mode);
+
+		/**
+		 * Copies the contents of the @p src buffer into the @p dst buffer. The size of the copied contents is determined
+		 * by the size of the @p dst buffer. First pixel copied from @p src is determined by offset provided in
+		 * @p offsetX, @p offsetY and @p offsetZ parameters.
+		 */
+		static void copy(const PixelData& src, PixelData& dst, UINT32 offsetX = 0, UINT32 offsetY = 0, UINT32 offsetZ = 0);
+
 		/**
 		 * Applies gamma correction to the pixels in the provided buffer.
 		 *

+ 0 - 4
Source/BansheeCore/Include/BsShader.h

@@ -59,10 +59,6 @@ namespace bs
 	 *  @{
 	 */
 
-	template<bool Core> struct TTextureType {};
-	template<> struct TTextureType < false > { typedef HTexture Type; };
-	template<> struct TTextureType < true > { typedef SPtr<TextureCore> Type; };
-
 	template<bool Core> struct TSamplerStateType {};
 	template<> struct TSamplerStateType < false > { typedef SPtr<SamplerState> Type; };
 	template<> struct TSamplerStateType < true > { typedef SPtr<SamplerStateCore> Type; };

+ 15 - 1
Source/BansheeCore/Source/BsCamera.cpp

@@ -778,6 +778,11 @@ namespace bs
 			dataPtr = rttiReadElem(mIsActive, dataPtr);
 			dataPtr = rttiReadElem(mMSAA, dataPtr);
 
+			SPtr<TextureCore>* skyTexture = (SPtr<TextureCore>*)dataPtr;
+			mSkyTexture = *skyTexture;
+			skyTexture->~SPtr<TextureCore>();
+			dataPtr += sizeof(SPtr<TextureCore>);
+
 			UINT32 ppSize = 0;
 			dataPtr = rttiReadElem(ppSize, dataPtr);
 
@@ -845,6 +850,10 @@ namespace bs
 	{
 		UINT32 dirtyFlag = getCoreDirtyFlags();
 
+		SPtr<TextureCore> skyTexture;
+		if (mSkyTexture.isLoaded())
+			skyTexture = mSkyTexture->getCore();
+
 		UINT32 size = 0;
 		size += rttiGetElemSize(dirtyFlag);
 		size += rttiGetElemSize(mPosition);
@@ -867,7 +876,7 @@ namespace bs
 			size += rttiGetElemSize(mCameraFlags);
 			size += rttiGetElemSize(mIsActive);
 			size += rttiGetElemSize(mMSAA);
-
+			size += sizeof(SPtr<TextureCore>);
 			size += sizeof(UINT32);
 
 			if(mPPSettings != nullptr)
@@ -900,6 +909,11 @@ namespace bs
 			dataPtr = rttiWriteElem(mCameraFlags, dataPtr);
 			dataPtr = rttiWriteElem(mIsActive, dataPtr);
 			dataPtr = rttiWriteElem(mMSAA, dataPtr);
+
+			SPtr<TextureCore>* skyTexDest = new (dataPtr) SPtr<TextureCore>();
+			*skyTexDest = skyTexture;
+			dataPtr += sizeof(skyTexture);
+
 			dataPtr = rttiWriteElem(ppSize, dataPtr);
 
 			if(mPPSettings != nullptr)

+ 11 - 11
Source/BansheeCore/Source/BsPixelData.cpp

@@ -58,12 +58,12 @@ namespace bs
 		return PixelUtil::getMemorySize(mRowPitch, mSlicePitch / mRowPitch, getDepth(), getFormat());
 	}
 
-	PixelData PixelData::getSubVolume(const PixelVolume &def) const
+	PixelData PixelData::getSubVolume(const PixelVolume& volume) const
 	{
 		if (PixelUtil::isCompressed(mFormat))
 		{
-			if (def.left == getLeft() && def.top == getTop() && def.front == getFront() &&
-				def.right == getRight() && def.bottom == getBottom() && def.back == getBack())
+			if (volume.left == getLeft() && volume.top == getTop() && volume.front == getFront() &&
+				volume.right == getRight() && volume.bottom == getBottom() && volume.back == getBack())
 			{
 				// Entire buffer is being queried
 				return *this;
@@ -72,28 +72,28 @@ namespace bs
 			BS_EXCEPT(InvalidParametersException, "Cannot return subvolume of compressed PixelBuffer");
 		}
 
-		if (!mExtents.contains(def))
+		if (!mExtents.contains(volume))
 		{
 			BS_EXCEPT(InvalidParametersException, "Bounds out of range");
 		}
 
 		const size_t elemSize = PixelUtil::getNumElemBytes(mFormat);
-		PixelData rval(def.getWidth(), def.getHeight(), def.getDepth(), mFormat);
+		PixelData rval(volume.getWidth(), volume.getHeight(), volume.getDepth(), mFormat);
 
-		rval.setExternalBuffer(((UINT8*)getData()) + ((def.left - getLeft())*elemSize)
-			+ ((def.top - getTop())*mRowPitch*elemSize)
-			+ ((def.front - getFront())*mSlicePitch*elemSize));
+		rval.setExternalBuffer(((UINT8*)getData()) + ((volume.left - getLeft())*elemSize)
+			+ ((volume.top - getTop())*mRowPitch*elemSize)
+			+ ((volume.front - getFront())*mSlicePitch*elemSize));
 
-		rval.mRowPitch = mRowPitch;
-		rval.mSlicePitch = mSlicePitch;
 		rval.mFormat = mFormat;
+		PixelUtil::getPitch(volume.getWidth(), volume.getHeight(), volume.getDepth(), mFormat, rval.mRowPitch,
+							rval.mSlicePitch);
 
 		return rval;
 	}
 
 	Color PixelData::sampleColorAt(const Vector2& coords, TextureFilter filter) const
 	{
-		Vector2 pixelCoords = coords * Vector2(mExtents.getWidth(), mExtents.getHeight());
+		Vector2 pixelCoords = coords * Vector2((float)mExtents.getWidth(), (float)mExtents.getHeight());
 
 		INT32 maxExtentX = std::max(0, (INT32)mExtents.getWidth() - 1);
 		INT32 maxExtentY = std::max(0, (INT32)mExtents.getHeight() - 1);

+ 132 - 0
Source/BansheeCore/Source/BsPixelUtil.cpp

@@ -1679,6 +1679,138 @@ namespace bs
 		}
 	}
 
+	void PixelUtil::copy(const PixelData& src, PixelData& dst, UINT32 offsetX, UINT32 offsetY, UINT32 offsetZ)
+	{
+		if(src.getFormat() != dst.getFormat())
+		{
+			LOGERR("Source format is different from destination format for copy(). This operation cannot be used for "
+				   "a format conversion. Aborting copy.");
+			return;
+		}
+
+		UINT32 right = offsetX + dst.getWidth();
+		UINT32 bottom = offsetY + dst.getHeight();
+		UINT32 back = offsetZ + dst.getDepth();
+
+		if(right > src.getWidth() || bottom > src.getHeight() || back > src.getDepth())
+		{
+			LOGERR("Provided offset or destination size is too large and is referencing pixels that are out of bounds"
+				   " on the source texture. Aborting copy().");
+			return;
+		}
+
+		UINT8* srcPtr = (UINT8*)src.getData() + offsetZ * src.getSlicePitch();
+		UINT8* dstPtr = (UINT8*)dst.getData();
+
+		UINT32 elemSize = getNumElemBytes(dst.getFormat());
+		UINT32 rowSize = dst.getWidth() * elemSize;
+
+		for(UINT32 z = 0; z < dst.getDepth(); z++)
+		{
+			UINT8* srcRowPtr = srcPtr + offsetY * src.getRowPitch() * elemSize;
+			UINT8* dstRowPtr = dstPtr;
+
+			for(UINT32 y = 0; y < dst.getHeight(); y++)
+			{
+				memcpy(dstRowPtr, srcRowPtr + offsetX * elemSize, rowSize);
+
+				srcRowPtr += src.getRowPitch() * elemSize;
+				dstRowPtr += dst.getRowPitch() * elemSize;
+			}
+
+			srcPtr += src.getSlicePitch() * elemSize;
+			dstPtr += dst.getSlicePitch() * elemSize;
+		}
+	}
+
+	void PixelUtil::mirror(PixelData& pixelData, MirrorMode mode)
+	{
+		UINT32 width = pixelData.getWidth();
+		UINT32 height = pixelData.getHeight();
+		UINT32 depth = pixelData.getDepth();
+
+		UINT32 elemSize = getNumElemBytes(pixelData.getFormat());
+
+		if (mode.isSet(MirrorModeBits::Z))
+		{
+			UINT32 sliceSize = width * height * elemSize;
+			UINT8* sliceTemp = bs_stack_alloc<UINT8>(sliceSize);
+
+			UINT8* dataPtr = pixelData.getData();
+			UINT32 halfDepth = depth / 2;
+			for (UINT32 z = 0; z < halfDepth; z++)
+			{
+				UINT32 srcZ = z * sliceSize;
+				UINT32 dstZ = (depth - z - 1) * sliceSize;
+
+				memcpy(sliceTemp, &dataPtr[dstZ], sliceSize);
+				memcpy(&dataPtr[srcZ], &dataPtr[srcZ], sliceSize);
+				memcpy(&dataPtr[dstZ], sliceTemp, sliceSize);
+			}
+
+			// Note: If flipping Y or X as well I could do it here without an extra set of memcpys
+
+			bs_stack_free(sliceTemp);
+		}
+
+		if(mode.isSet(MirrorModeBits::Y))
+		{
+			UINT32 rowSize = width * elemSize;
+			UINT8* rowTemp = bs_stack_alloc<UINT8>(rowSize);
+
+			UINT8* slicePtr = pixelData.getData();
+			for (UINT32 z = 0; z < depth; z++)
+			{
+				UINT32 halfHeight = height / 2;
+				for (UINT32 y = 0; y < halfHeight; y++)
+				{
+					UINT32 srcY = y * rowSize;
+					UINT32 dstY = (height - y - 1) * rowSize;
+
+					memcpy(rowTemp, &slicePtr[dstY], rowSize);
+					memcpy(&slicePtr[dstY], &slicePtr[srcY], rowSize);
+					memcpy(&slicePtr[srcY], rowTemp, rowSize);
+				}
+
+				// Note: If flipping X as well I could do it here without an extra set of memcpys
+
+				slicePtr += pixelData.getSlicePitch() * elemSize;
+			}
+
+			bs_stack_free(rowTemp);
+		}
+
+		if (mode.isSet(MirrorModeBits::X))
+		{
+			UINT8* elemTemp = bs_stack_alloc<UINT8>(elemSize);
+
+			UINT8* slicePtr = pixelData.getData();
+			for (UINT32 z = 0; z < depth; z++)
+			{
+				UINT8* rowPtr = slicePtr;
+				for (UINT32 y = 0; y < height; y++)
+				{
+					UINT32 halfWidth = width / 2;
+					for (UINT32 x = 0; x < halfWidth; x++)
+					{
+						UINT32 srcX = x * elemSize;
+						UINT32 dstX = (width - x - 1) * elemSize;
+
+						memcpy(elemTemp, &rowPtr[dstX], elemSize);
+						memcpy(&rowPtr[dstX], &rowPtr[srcX], elemSize);
+						memcpy(&rowPtr[srcX], elemTemp, elemSize);
+					}
+
+					rowPtr += pixelData.getRowPitch() * elemSize;
+				}
+
+				slicePtr += pixelData.getSlicePitch() * elemSize;
+			}
+
+			bs_stack_free(elemTemp);
+		}
+	}
+
 	void PixelUtil::applyGamma(UINT8* buffer, float gamma, UINT32 size, UINT8 bpp)
 	{
 		if(gamma == 1.0f)

+ 2 - 1
Source/BansheeD3D11RenderAPI/Source/BsD3D11Texture.cpp

@@ -375,7 +375,8 @@ namespace bs
 
 		if (format != D3D11Mappings::getPF(d3dPF))
 		{
-			BS_EXCEPT(RenderingAPIException, "Provided pixel format is not supported by the driver: " + toString(format));
+			LOGWRN("Provided pixel format is not supported by the driver: " + toString(format) + ". Using " +
+				toString(closestFormat) + " instead.");
 		}
 
 		mDXGIColorFormat = d3dPF;

+ 4 - 0
Source/BansheeEngine/Include/BsRendererUtility.h

@@ -135,10 +135,14 @@ namespace bs
 		/** Returns a stencil mesh used for spot light. Actual vertex positions need to be computed in shader. */
 		SPtr<MeshCore> getSpotLightStencil() const { return mSpotLightStencilMesh; }
 
+		/** Returns a mesh that can be used for rendering a skybox. */
+		SPtr<MeshCore> getSkyBoxMesh() const { return mSkyBoxMesh; }
+
 	private:
 		SPtr<MeshCore> mFullScreenQuadMesh;
 		SPtr<MeshCore> mPointLightStencilMesh;
 		SPtr<MeshCore> mSpotLightStencilMesh;
+		SPtr<MeshCore> mSkyBoxMesh;
 		SPtr<ResolveMat> mResolveMat;
 		SPtr<BlitMat> mBlitMat;
 	};

+ 20 - 0
Source/BansheeEngine/Source/BsRendererUtility.cpp

@@ -107,6 +107,26 @@ namespace bs
 			mSpotLightStencilMesh = MeshCore::create(meshData);
 		}
 
+		{
+			SPtr<VertexDataDesc> vertexDesc = bs_shared_ptr_new<VertexDataDesc>();
+			vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
+
+			UINT32 numVertices = 0;
+			UINT32 numIndices = 0;
+
+			ShapeMeshes3D::getNumElementsAABox(numVertices, numIndices);
+			SPtr<MeshData> meshData = bs_shared_ptr_new<MeshData>(numVertices, numIndices, vertexDesc);
+
+			UINT32* indexData = meshData->getIndices32();
+			UINT8* positionData = meshData->getElementData(VES_POSITION);
+
+			AABox localBox(-Vector3::ONE * 1500.0f, Vector3::ONE * 1500.0f);
+			ShapeMeshes3D::solidAABox(localBox, positionData, nullptr, 0,
+									   vertexDesc->getVertexStride(), indexData, 0);
+
+			mSkyBoxMesh = MeshCore::create(meshData);
+		}
+
 		// TODO - When I add proper preprocessor support, merge these into a single material
 		mResolveMat = bs_shared_ptr_new<ResolveMat>();
 		mBlitMat = bs_shared_ptr_new<BlitMat>();

+ 9 - 6
Source/BansheeEngine/Source/BsShapeMeshes3D.cpp

@@ -447,13 +447,16 @@ namespace bs
 			Vector3(0, -1, 0)
 		};
 
-		outNormals += (vertexOffset * vertexStride);
-		for (UINT32 face = 0; face < 6; face++)
+		if (outNormals != nullptr)
 		{
-			outNormals = writeVector3(outNormals, vertexStride, faceNormals[face]);
-			outNormals = writeVector3(outNormals, vertexStride, faceNormals[face]);
-			outNormals = writeVector3(outNormals, vertexStride, faceNormals[face]);
-			outNormals = writeVector3(outNormals, vertexStride, faceNormals[face]);
+			outNormals += (vertexOffset * vertexStride);
+			for (UINT32 face = 0; face < 6; face++)
+			{
+				outNormals = writeVector3(outNormals, vertexStride, faceNormals[face]);
+				outNormals = writeVector3(outNormals, vertexStride, faceNormals[face]);
+				outNormals = writeVector3(outNormals, vertexStride, faceNormals[face]);
+				outNormals = writeVector3(outNormals, vertexStride, faceNormals[face]);
+			}
 		}
 
 		UINT32* indices = outIndices + indexOffset;

+ 11 - 13
Source/BansheeFreeImgImporter/Source/BsFreeImgImporter.cpp

@@ -412,10 +412,7 @@ namespace bs
 				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());
+			PixelUtil::copy(*source, *output[i], faceStart.x, faceStart.y);
 		}
 	}
 
@@ -424,13 +421,13 @@ namespace bs
 	 * 
 	 * Vertical layout:
 	 *    +Y
-	 * -X -Z +X
+	 * -X +Z +X
 	 *    -Y
-	 *    +Z
+	 *    -Z
 	 * 
 	 * Horizontal layout:
 	 *    +Y
-	 * -X -Z +X +Z
+	 * -X +Z +X -Z
 	 *    -Y
 	 * 
 	 * @param[in]	source		Source texture to read.
@@ -441,8 +438,8 @@ namespace bs
 	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 static UINT32 vertFaceIndices[] = { 5, 3, 1, 7, 4, 10 };
+		const static UINT32 horzFaceIndices[] = { 6, 4, 1, 9, 5, 7 };
 
 		const UINT32* faceIndices = vertical ? vertFaceIndices : horzFaceIndices;
 		UINT32 numFacesInRow = vertical ? 3 : 4;
@@ -455,11 +452,12 @@ namespace bs
 			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());
+			PixelUtil::copy(*source, *output[i], faceX, faceY);
 		}
+
+		// Flip -Z as it's upside down
+		if (vertical)
+			PixelUtil::mirror(*output[5], MirrorModeBits::X | MirrorModeBits::Y);
 	}
 
 	/** Method that maps a direction to a point on a plane in range [0, 1] using spherical mapping. */

+ 3 - 0
Source/BansheeGLRenderAPI/Source/BsGLRenderAPI.cpp

@@ -137,6 +137,9 @@ namespace bs
 		GLVertexArrayObjectManager::startUp();
 		glFrontFace(GL_CW);
 
+		// Ensure cubemaps are filtered across seams
+		glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+
 		mGLInitialised = true;
 
 		RenderAPICore::initializeWithWindow(primaryWindow);

+ 1 - 1
Source/BansheeVulkanRenderAPI/CMakeLists.txt

@@ -2,7 +2,7 @@
 include(CMakeSources.cmake)
 
 # Packages
-if(RENDER_API_MODULE MATCHES "Vulkan")
+if(RENDER_API_MODULE MATCHES "Vulkan" OR INCLUDE_ALL_IN_WORKFLOW)
 	find_package(Vulkan)
 	find_package(glslang)
 endif()

+ 1 - 2
Source/BansheeVulkanRenderAPI/Include/BsVulkanGpuProgram.h

@@ -27,8 +27,7 @@ namespace bs
 	};
 
 	/**	Abstraction of a Vulkan shader object. */
-	class 
-	VulkanGpuProgramCore : public GpuProgramCore
+	class VulkanGpuProgramCore : public GpuProgramCore
 	{
 	public:
 		virtual ~VulkanGpuProgramCore();

+ 8 - 0
Source/BansheeVulkanRenderAPI/Source/BsVulkanTexture.cpp

@@ -193,6 +193,14 @@ namespace bs
 			case VK_IMAGE_VIEW_TYPE_3D:
 				mImageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
 				break;
+			default:
+				break;
+			}
+		} 
+		else if(surface.numArraySlices > 6)
+		{
+			switch (oldViewType)
+			{
 			case VK_IMAGE_VIEW_TYPE_CUBE:
 				mImageViewCI.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
 				break;

+ 1 - 1
Source/CMakeLists.txt

@@ -46,7 +46,7 @@ set_property(CACHE RENDERER_MODULE PROPERTY STRINGS RenderBeast)
 set(BUILD_SCOPE "Runtime" CACHE STRING "Determines which parts of Banshee to build. Pick Framework to build only the low-level C++ framework. Pick Runtime to build everything, including the framework, scripting API and the editor.")
 set_property(CACHE BUILD_SCOPE PROPERTY STRINGS "Runtime" "Framework")
 
-set(INCLUDE_ALL_IN_WORKFLOW OFF CACHE BOOL "If true, all libraries (even those not selected) will be included in the generated workflow. Only relevant for workflow generators like Visual Studio.")
+set(INCLUDE_ALL_IN_WORKFLOW OFF CACHE BOOL "If true, all libraries (even those not selected) will be included in the generated workflow (e.g. Visual Studio solution). This is useful when working on engine internals with a need for easy access to all parts of it. Only relevant for workflow generators like Visual Studio or XCode.")
 
 if(BUILD_SCOPE MATCHES "Runtime")
 	set(BUILD_EDITOR ON)

+ 1 - 0
Source/RenderBeast/Include/BsRenderBeast.h

@@ -212,6 +212,7 @@ namespace bs
 		PointLightInMat* mPointLightInMat;
 		PointLightOutMat* mPointLightOutMat;
 		DirectionalLightMat* mDirLightMat;
+		SkyboxMat* mSkyboxMat;
 
 		ObjectRenderer* mObjectRenderer;
 

+ 17 - 0
Source/RenderBeast/Include/BsRendererCamera.h

@@ -30,6 +30,23 @@ namespace bs
 
 	extern PerCameraParamDef gPerCameraParamDef;
 
+	/** Shader that renders a skybox using a cubemap. */
+	class SkyboxMat : public RendererMaterial<SkyboxMat>
+	{
+		RMAT_DEF("Skybox.bsl");
+
+	public:
+		SkyboxMat();
+
+		/** Binds the material for rendering and sets up any global parameters. */
+		void bind(const SPtr<GpuParamBlockBufferCore>& perCamera);
+
+		/** Updates the skybox texture used by the material. */
+		void setParams(const SPtr<TextureCore>& texture);
+	private:
+		GpuParamTextureCore mSkyTextureParam;
+	};
+
 	/** Contains information about a Camera, used by the Renderer. */
 	class RendererCamera
 	{

+ 16 - 2
Source/RenderBeast/Source/BsRenderBeast.cpp

@@ -43,7 +43,8 @@ namespace bs
 
 	RenderBeast::RenderBeast()
 		: mDefaultMaterial(nullptr), mPointLightInMat(nullptr), mPointLightOutMat(nullptr), mDirLightMat(nullptr)
-		, mObjectRenderer(nullptr), mOptions(bs_shared_ptr_new<RenderBeastOptions>()), mOptionsDirty(true)
+		, mSkyboxMat(nullptr), mObjectRenderer(nullptr), mOptions(bs_shared_ptr_new<RenderBeastOptions>())
+		, mOptionsDirty(true)
 	{ }
 
 	const StringID& RenderBeast::getName() const
@@ -78,6 +79,7 @@ namespace bs
 		mPointLightInMat = bs_new<PointLightInMat>();
 		mPointLightOutMat = bs_new<PointLightOutMat>();
 		mDirLightMat = bs_new<DirectionalLightMat>();
+		mSkyboxMat = bs_new<SkyboxMat>();
 
 		RenderTexturePool::startUp();
 		PostProcessing::startUp();
@@ -106,6 +108,7 @@ namespace bs
 		bs_delete(mPointLightInMat);
 		bs_delete(mPointLightOutMat);
 		bs_delete(mDirLightMat);
+		bs_delete(mSkyboxMat);
 
 		RendererUtility::shutDown();
 
@@ -743,8 +746,19 @@ namespace bs
 			}
 		}
 
+		// Render skybox (if any)
+		SPtr<TextureCore> skyTexture = camera->getSkybox();
+		if (skyTexture != nullptr && skyTexture->getProperties().getTextureType() == TEX_TYPE_CUBE_MAP)
+		{
+			mSkyboxMat->bind(perCameraBuffer);
+			mSkyboxMat->setParams(skyTexture);
+
+			SPtr<MeshCore> mesh = gRendererUtility().getSkyBoxMesh();
+			gRendererUtility().draw(mesh, mesh->getProperties().getSubMesh(0));
+		}
+
 		renderTargets->bindSceneColor(false);
-		
+
 		// Render transparent objects (TODO - No lighting yet)
 		const Vector<RenderQueueElement>& transparentElements = rendererCam->getTransparentQueue()->getSortedElements();
 		for (auto iter = transparentElements.begin(); iter != transparentElements.end(); ++iter)

+ 28 - 1
Source/RenderBeast/Source/BsRendererCamera.cpp

@@ -6,11 +6,38 @@
 #include "BsMaterial.h"
 #include "BsShader.h"
 #include "BsRenderTargets.h"
+#include "BsRendererUtility.h"
+#include "BsGpuParamsSet.h"
 
 namespace bs
 {
 	PerCameraParamDef gPerCameraParamDef;
 
+	SkyboxMat::SkyboxMat()
+	{
+		SPtr<GpuParamsCore> params = mParamsSet->getGpuParams();
+
+		params->getTextureParam(GPT_FRAGMENT_PROGRAM, "gSkyTex", mSkyTextureParam);
+	}
+
+	void SkyboxMat::_initDefines(ShaderDefines& defines)
+	{
+		// Do nothing
+	}
+
+	void SkyboxMat::bind(const SPtr<GpuParamBlockBufferCore>& perCamera)
+	{
+		mParamsSet->setParamBlockBuffer("PerCamera", perCamera, true);
+
+		gRendererUtility().setPass(mMaterial, 0);
+	}
+
+	void SkyboxMat::setParams(const SPtr<TextureCore>& texture)
+	{
+		mSkyTextureParam.set(texture);
+		gRendererUtility().setPassParams(mParamsSet);
+	}
+
 	RendererCamera::RendererCamera()
 		:mCamera(nullptr), mUsingRenderTargets(false)
 	{
@@ -241,4 +268,4 @@ namespace bs
 
 		gPerCameraParamDef.gClipToUVScaleOffset.set(mParamBuffer, clipToUVScaleOffset);
 	}
-}
+}