Просмотр исходного кода

More work on light probe indirect lighting

BearishSun 8 лет назад
Родитель
Сommit
068c65b9e0

+ 21 - 8
Source/BansheeCore/Include/BsLightProbeVolume.h

@@ -163,15 +163,14 @@ namespace bs
 	/** Information about a single light probe in a light probe volume. */
 	struct LightProbeInfo
 	{
+		/** Unique handle representing the probe. Always remains the same. */
+		UINT32 handle;
+
+		/** Flags representing the current state of the probe. */
 		LightProbeFlags flags;
 
-		union
-		{
-			/** Index into the GPU buffer where probe coefficients are stored. -1 if not assigned. Transient. */
-			UINT32 bufferIdx; 
-			/** Index to next empty spot in the probe array. -1 if none. Only used if probe is empty or removed. */
-			UINT32 nextEmptyIdx; 
-		};
+		/** Index into the GPU buffer where probe coefficients are stored. -1 if not assigned. Transient. */
+		UINT32 bufferIdx; 
 	};
 
 	/** Core thread usable version of bs::LightProbeVolume. */
@@ -185,6 +184,21 @@ namespace bs
 
 		/**	Retrieves an ID that can be used for uniquely identifying this object by the renderer. */
 		UINT32 getRendererId() const { return mRendererId; }
+
+		/** 
+		 * Parses the list of probes and reorganizes it by removing gaps so that all probes are sequential. 
+		 * 
+		 * @param[out]	freedEntries	A list of entries mapping to the GPU buffer where probe SH coefficients are stored.
+		 *								These are the entries that have been freed since the last call to prune().
+		 * @param[in]	freeAll			If true, all probes held by this volume will be marked as freed.
+		 */
+		void prune(Vector<UINT32>& freedEntries, bool freeAll = false);
+
+		/** Returns information about all light probes. */
+		Vector<LightProbeInfo>& getLightProbeInfos() { return mProbeInfos; }
+
+		/** Returns a list of positions for all light probes. */
+		Vector<Vector3>& getLightProbePositions() { return mProbePositions; }
 	protected:
 		friend class bs::LightProbeVolume;
 
@@ -201,7 +215,6 @@ namespace bs
 
 		Vector<Vector3> mProbePositions;
 		Vector<LightProbeInfo> mProbeInfos;
-		UINT32 mNextFreeIdx = -1;
 	};
 	}
 

+ 72 - 7
Source/BansheeCore/Source/BsLightProbeVolume.cpp

@@ -5,6 +5,7 @@
 #include "BsFrameAlloc.h"
 #include "BsRenderer.h"
 #include "BsLight.h"
+#include <atlalloc.h>
 
 namespace bs
 {
@@ -32,7 +33,7 @@ namespace bs
 	void LightProbeVolume::removeProbe(UINT32 handle)
 	{
 		auto iterFind = mProbes.find(handle);
-		if (iterFind != mProbes.end())
+		if (iterFind != mProbes.end() && mProbes.size() > 4)
 		{
 			iterFind->second.flags = LightProbeFlags::Removed;
 			_markCoreDirty();
@@ -178,6 +179,7 @@ namespace bs
 			LightProbeInfo probeInfo;
 			probeInfo.flags = LightProbeFlags::Dirty;
 			probeInfo.bufferIdx = -1;
+			probeInfo.handle = probeIdx;
 
 			mProbeInfos[probeIdx] = probeInfo;
 			probeIdx++;
@@ -196,6 +198,70 @@ namespace bs
 		CoreObject::initialize();
 	}
 
+	void LightProbeVolume::prune(Vector<UINT32>& freedEntries, bool freeAll)
+	{
+		UINT32 numProbes = (UINT32)mProbeInfos.size();
+		INT32 lastSearchIdx = numProbes - 1;
+		
+		for (UINT32 i = 0; i < (UINT32)mProbeInfos.size(); ++i)
+		{
+			LightProbeInfo& info = mProbeInfos[i];
+
+			if (info.flags == LightProbeFlags::Removed)
+			{
+				if (info.bufferIdx != -1)
+					freedEntries.push_back(info.bufferIdx);
+
+				info.flags = LightProbeFlags::Empty;
+
+				// Replace the empty spot with an element from the back
+				while (lastSearchIdx >= (INT32)i)
+				{
+					bool foundNonEmpty = false;
+					LightProbeFlags flags = mProbeInfos[lastSearchIdx].flags;
+					if (flags != LightProbeFlags::Empty)
+					{
+						std::swap(mProbeInfos[i], mProbeInfos[lastSearchIdx]);
+						std::swap(mProbePositions[i], mProbePositions[lastSearchIdx]);
+
+						mProbeMap[mProbeInfos[lastSearchIdx].handle] = i;
+						foundNonEmpty = true;
+					}
+
+					// Remove last element
+					mProbeInfos.erase(mProbeInfos.begin() + lastSearchIdx);
+					mProbePositions.erase(mProbePositions.begin() + lastSearchIdx);
+					lastSearchIdx--;
+
+					// Search is done, we found an element to fill the empty spot
+					if (foundNonEmpty)
+						break;
+				}
+			}
+		}
+
+		if(freeAll)
+		{
+			// Add all remaining (non-removed) probes to the free list, and mark them as dirty so when/if those probes
+			// get used again, the systems knows they are out of date
+			for (UINT32 i = 0; i < (UINT32)mProbeInfos.size(); ++i)
+			{
+				LightProbeInfo& info = mProbeInfos[i];
+
+				if (info.flags != LightProbeFlags::Empty)
+				{
+					if (info.bufferIdx != -1)
+					{
+						freedEntries.push_back(info.bufferIdx);
+						info.bufferIdx = -1;
+					}
+
+					info.flags = LightProbeFlags::Dirty;
+				}
+			}
+		}
+	}
+
 	void LightProbeVolume::syncToCore(const CoreSyncData& data)
 	{
 		char* dataPtr = (char*)data.getBuffer();
@@ -212,8 +278,8 @@ namespace bs
 
 		for (UINT32 i = 0; i < numDirtyProbes; ++i)
 		{
-			UINT32 idx;
-			dataPtr = rttiReadElem(idx, dataPtr);
+			UINT32 handle;
+			dataPtr = rttiReadElem(handle, dataPtr);
 
 			Vector3 position;
 			dataPtr = rttiReadElem(position, dataPtr);
@@ -221,7 +287,7 @@ namespace bs
 			LightProbeFlags flags;
 			dataPtr = rttiReadElem(flags, dataPtr);
 
-			auto iterFind = mProbeMap.find(idx);
+			auto iterFind = mProbeMap.find(handle);
 			if(iterFind != mProbeMap.end())
 			{
 				UINT32 compactIdx = iterFind->second;
@@ -236,11 +302,12 @@ namespace bs
 				LightProbeInfo info;
 				info.flags = LightProbeFlags::Dirty;
 				info.bufferIdx = -1;
+				info.handle = handle;
 
 				mProbeInfos.push_back(info);
 				mProbePositions.push_back(position);
 
-				mProbeMap[idx] = compactIdx;
+				mProbeMap[handle] = compactIdx;
 			}
 		}
 
@@ -256,10 +323,8 @@ namespace bs
 				
 				LightProbeInfo& info = mProbeInfos[compactIdx];
 				info.flags = LightProbeFlags::Removed;
-				info.nextEmptyIdx = mNextFreeIdx;
 				
 				mProbeMap.erase(iterFind);
-				mNextFreeIdx = compactIdx;
 			}
 		}
 

+ 2 - 1
Source/BansheeUtility/Include/BsMath.h

@@ -111,7 +111,8 @@ namespace bs
 		 * Divides an integer by another integer and returns the result, rounded up. Only works if both integers are
 		 * positive. 
 		 */
-		static int divideAndRoundUp(int n, int d) { return (n + d - 1) / d; }
+		template<class T>
+		static T divideAndRoundUp(T n, T d) { return (n + d - 1) / d; }
 
 		/** Returns the nearest integer equal or lower of the provided value. */
 		static float floor(float val) { return (float)std::floor(val); }

+ 115 - 4
Source/RenderBeast/Include/BsLightProbes.h

@@ -5,9 +5,13 @@
 #include "BsRenderBeastPrerequisites.h"
 #include "BsTriangulation.h"
 #include "BsMatrix4.h"
+#include "BsMatrixNxM.h"
 
 namespace bs { namespace ct
 {
+	class LightProbeVolume;
+	struct VisibleLightProbeData;
+
 	/** @addtogroup RenderBeast
 	 *  @{
 	 */
@@ -15,15 +19,122 @@ namespace bs { namespace ct
 	/** Handles any pre-processing for light (irradiance) probe lighting. */
 	class LightProbes
 	{
-		struct LightTetrahedron
+		/** Internal information about a single light probe volume. */
+		struct VolumeInfo
+		{
+			/** Volume containing the information about the probes. */
+			SPtr<LightProbeVolume> volume;
+			/** Remains true as long as there are dirty probes in the volume. */
+			bool isDirty;
+			/** Keeps track of which dirty probe was last updated, so we can perform the update over multiple frames. */
+			UINT32 lastUpdatedProbe; 
+		};
+
+		/** 
+		 * Information about a single tetrahedron, including neighbor information. Neighbor 4th index will be set to -1
+		 * if the tetrahedron represents an outer face (which is not actually a tetrahedron, but a triangle, but is stored
+		 * in the same array for convenience).
+		 */
+		struct TetrahedronData
 		{
 			Tetrahedron volume;
 			Matrix4 transform;
 		};
+	public:
+		LightProbes();
+
+		/** Notifies sthe manager that the provided light probe volume has been added. */
+		void notifyAdded(const SPtr<LightProbeVolume>& volume);
+
+		/** Notifies the manager that the provided light probe volume has some dirty light probes. */
+		void notifyDirty(const SPtr<LightProbeVolume>& volume);
+
+		/** Notifies the manager that all the probes in the provided volume have been removed. */
+		void notifyRemoved(const SPtr<LightProbeVolume>& volume);
+
+		/**
+		 * Updates any dirty light probes by rendering the scene from their perspective and generating their SH 
+		 * coefficients.
+		 *
+		 * @param[in]	maxProbes		Places a limit of how many probes can be updated in a single call to this method.
+		 *								Any probes that weren't updated will be updated when the method is called next 
+		 *								(up to the @p maxProbes limit), as so on.
+		 *								 
+		 *								This limit is provided to ensure there are no massive framerate spikes caused up
+		 *								updating many probes in a single frame - instead this method allows the updates to
+		 *								be distributed over multiple frames. 
+		 *								
+		 *								Provide a limit of 0 to force all probes to be updated.
+		 */
+		void updateProbes(UINT32 maxProbes = 3);
+
+		/** Generates GPU buffers that contain a list of probe tetrahedrons visible from the provided view. */
+		void updateVisibleProbes(const RendererView& view, VisibleLightProbeData& output);
+
+	private:
+		/**
+		 * Perform tetrahedrization of the provided point list, and outputs a list of tetrahedrons and outer faces of the
+		 * volume. Each entry contains connections to nearby tetrahedrons/faces, as well as a matrix that can be used for
+		 * calculating barycentric coordinates within the tetrahedron (or projected triangle barycentric coordinates for
+		 * faces). 
+		 */
+		void generateTetrahedronData(const Vector<Vector3>& positions, Vector<TetrahedronData>& output, 
+			bool includeOuterFaces = false);
+
+		/** Resizes the GPU buffers used for holding tetrahedron data, to the specified size (in number of tetraheda). */
+		void resizeTetrahedronBuffers(VisibleLightProbeData& data, UINT32 count);
+
+		/** 
+		 * Resized the GPU buffer that stores light probe SH coefficients, to the specified size (in the number of probes). 
+		 */
+		void resizeCoefficientBuffer(UINT32 count);
+
+		Vector<VolumeInfo> mVolumes;
+		bool mTetrahedronVolumeDirty;
+
+		UINT32 mNumAllocatedEntries;
+		UINT32 mNumUsedEntries;
+		Vector<UINT32> mEmptyEntries;
+
+		Vector<AABox> mTetrahedronBounds;
+		Vector<TetrahedronData> mTetrahedronInfos;
+
+		SPtr<GpuBuffer> mProbeCoefficientsGPU;
+
+		// Temporary buffers
+		Vector<Vector3> mTempTetrahedronPositions;
+		Vector<UINT32> mTempTetrahedronVisibility;
+	};
+
+	/** Storage of tetrahedron AA box, for use on the GPU. */
+	struct TetrahedronBoundsGPU
+	{
+		Vector4 center;
+		Vector4 extents;
+	};
+
+	/** Information about a single tetrahedron, for use on the GPU. */
+	struct TetrahedronDataGPU
+	{
+		UINT32 indices[4];
+		Matrix3x4 transform;
+	};
+
+	/** Contains information about light probes visible from a particular RendererView. */
+	struct VisibleLightProbeData
+	{
+		/** Current number of visible tetrahedrons in the GPU buffers. */
+		UINT32 numEntries;
+
+		/** Maximum number of tetrahedrons that fit in the GPU buffers, before the buffers need to be resized. */
+		UINT32 maxNumEntries;
+		
+		/** GPU buffer containing tetrahedron bounds in form of TetrahedronBoundsGPU structure. */
+		SPtr<GpuBuffer> tetrahedronBounds;
 
-		// TODO - This should accept LightProbe types as input probably
-		void updateProbes(const Vector<Vector3>& positions);
+		/** GPU buffer containing tetrahedron information in form of TetrahedronDataGPU structure. */
+		SPtr<GpuBuffer> tetrahedronInfos;
 	};
 
 	/** @} */
-}}
+}}

+ 9 - 3
Source/RenderBeast/Include/BsRendererView.h

@@ -259,11 +259,17 @@ namespace bs { namespace ct
 		void calculateVisibility(const Vector<CullInfo>& cullInfos, Vector<bool>& visibility) const;
 
 		/**
-		* Culls the provided set of bounds against the current frustum and outputs a set of visibility flags determining
-		* which entry is or isn't visible by this view. Both inputs must be arrays of the same size.
-		*/
+		 * Culls the provided set of bounds against the current frustum and outputs a set of visibility flags determining
+		 * which entry is or isn't visible by this view. Both inputs must be arrays of the same size.
+		 */
 		void calculateVisibility(const Vector<Sphere>& bounds, Vector<bool>& visibility) const;
 
+		/**
+		 * Culls the provided set of bounds against the current frustum and outputs a set of visibility flags determining
+		 * which entry is or isn't visible by this view. Both inputs must be arrays of the same size.
+		 */
+		void calculateVisibility(const Vector<AABox>& bounds, Vector<bool>& visibility) const;
+
 		/** Returns the visibility mask calculated with the last call to determineVisible(). */
 		const VisibilityInfo& getVisibilityMasks() const { return mVisibility; }
 

+ 517 - 258
Source/RenderBeast/Source/BsLightProbes.cpp

@@ -1,172 +1,299 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "BsLightProbes.h"
-#include "../../External/XShaderCompiler/inc/Xsc/Reflection.h"
+#include "BsLightProbeVolume.h"
+#include "BsGpuBuffer.h"
+#include "BsRendererView.h"
 
 namespace bs { namespace ct 
 {
-	/** Hash value generator for std::pair<INT32, INT32>. */
-	struct pair_hash
+	LightProbes::LightProbes()
+		:mTetrahedronVolumeDirty(false), mNumAllocatedEntries(0), mNumUsedEntries(0)
 	{
-		size_t operator()(const std::pair<INT32, INT32>& key) const
-		{
-			size_t hash = 0;
-			bs::hash_combine(hash, key.first);
-			bs::hash_combine(hash, key.second);
+		resizeCoefficientBuffer(512);
+	}
 
-			return hash;
-		}
-	};
+	void LightProbes::notifyAdded(const SPtr<LightProbeVolume>& volume)
+	{
+		UINT32 handle = (UINT32)mVolumes.size();
+
+		VolumeInfo info;
+		info.volume = volume;
+		info.isDirty = true;
+		info.lastUpdatedProbe = 0;
+
+		mVolumes.push_back(info);
+		volume->setRendererId(handle);
+
+		notifyDirty(volume);
+	}
 
-	void LightProbes::updateProbes(const Vector<Vector3>& positions)
+	void LightProbes::notifyDirty(const SPtr<LightProbeVolume>& volume)
 	{
-		bs_frame_mark();
-		{
-			TetrahedronVolume volume = Triangulation::tetrahedralize(positions);
+		volume->prune(mEmptyEntries);
+		
+		UINT32 handle = volume->getRendererId();
+		mVolumes[handle].isDirty = true;
+		mVolumes[handle].lastUpdatedProbe = 0;
 
-			// Put outer faces into the Tetrahedron structure, for convenience
-			UINT32 outerFaceOffset = (UINT32)volume.tetrahedra.size();
-			FrameVector<Tetrahedron> outerTetrahedrons(volume.outerFaces.size());
+		mTetrahedronVolumeDirty = true;
+	}
 
-			for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
-			{
-				Tetrahedron outerTetrahedron;
-				memcpy(outerTetrahedron.vertices, volume.outerFaces[i].vertices, sizeof(INT32) * 3);
-				memset(outerTetrahedron.neighbors, -1, sizeof(INT32) * 3);
+	void LightProbes::notifyRemoved(const SPtr<LightProbeVolume>& volume)
+	{
+		volume->prune(mEmptyEntries, true);
 
-				outerTetrahedron.vertices[4] = -1; // Marks the tetrahedron as an outer face
-				outerTetrahedron.neighbors[4] = volume.outerFaces[i].tetrahedron;
+		UINT32 handle = volume->getRendererId();
 
-				outerTetrahedrons[i] = outerTetrahedron;
-			}
+		LightProbeVolume* lastVolume = mVolumes.back().volume.get();
+		UINT32 lastHandle = lastVolume->getRendererId();
+		
+		if (handle != lastHandle)
+		{
+			// Swap current last element with the one we want to erase
+			std::swap(mVolumes[handle], mVolumes[lastHandle]);
+			lastVolume->setRendererId(handle);
+		}
+		
+		// Erase last (empty) element
+		mVolumes.erase(mVolumes.end() - 1);
+
+		mTetrahedronVolumeDirty = true;
+	}
 
-			// Connect boundary tetrahedrons with these new outer tetrahedrons
-			for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
+	void LightProbes::updateProbes(UINT32 maxProbes)
+	{
+		if(mTetrahedronVolumeDirty)
+		{
+			// Gather all positions
+			for(auto& entry : mVolumes)
 			{
-				Tetrahedron& tet = volume.tetrahedra[volume.outerFaces[i].tetrahedron];
-				for (UINT32 j = 0; j < 4; j++)
+				const Vector<Vector3>& positions = entry.volume->getLightProbePositions();
+				
+				Vector3 offset = entry.volume->getPosition();
+				Quaternion rotation = entry.volume->getRotation();
+				for(auto& localPos : positions)
 				{
-					if (tet.neighbors[j] == -1)
-						tet.neighbors[j] = outerFaceOffset + i;
+					Vector3 transformedPos = rotation.rotate(localPos) + offset;
+					mTempTetrahedronPositions.push_back(transformedPos);
 				}
 			}
 
-			// Make a map between outer edges and faces, used in the following algorithms
-			struct Edge
-			{
-				INT32 faces[2];
-				INT32 oppositeVerts[2];
-			};
+			mTetrahedronInfos.clear();
+			mTetrahedronBounds.clear();
+
+			generateTetrahedronData(mTempTetrahedronPositions, mTetrahedronInfos, false);
 
-			FrameUnorderedMap<std::pair<INT32, INT32>, Edge, pair_hash> edgeMap;
-			for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
+			// Generate bounds
+			for(auto& entry : mTetrahedronInfos)
 			{
-				for (UINT32 j = 0; j < 3; ++j)
-				{
-					INT32 v0 = volume.outerFaces[i].vertices[j];
-					INT32 v1 = volume.outerFaces[i].vertices[(j + 1) % 3];
+				// Skipping outer faces
+				if (entry.volume.neighbors[3] < 0)
+					continue;
+
+				AABox aabox = AABox(Vector3::INF, -Vector3::INF);
+				for (int i = 0; i < 4; ++i)
+					aabox.merge(mTempTetrahedronPositions[entry.volume.neighbors[i]]);
+
+				mTetrahedronBounds.push_back(aabox);
+			}
+
+			mTempTetrahedronPositions.clear();
+			mTetrahedronVolumeDirty = false;
+		}
 
-					// Keep the same ordering so other faces can find the same edge
-					if (v0 > v1)
-						std::swap(v0, v1);
+		// Render dirty probes
+		UINT32 numProbeUpdates = 0;
+		for(auto& entry : mVolumes)
+		{
+			if (!entry.isDirty)
+				continue;
 
-					auto iterFind = edgeMap.find(std::make_pair(v0, v1));
-					if (iterFind != edgeMap.end())
+			Vector<LightProbeInfo>& probes = entry.volume->getLightProbeInfos();
+			for (; entry.lastUpdatedProbe < (UINT32)probes.size(); ++entry.lastUpdatedProbe)
+			{
+				LightProbeInfo& probeInfo = probes[entry.lastUpdatedProbe];
+
+				// Assign buffer idx, if not assigned
+				if(probeInfo.bufferIdx == -1)
+				{
+					if(!mEmptyEntries.empty())
 					{
-						iterFind->second.faces[1] = i;
-						iterFind->second.oppositeVerts[1] = (j + 2) % 3;
+						probeInfo.bufferIdx = mEmptyEntries.back();
+						mEmptyEntries.erase(mEmptyEntries.end() - 1);
 					}
 					else
 					{
-						Edge edge;
-						edge.faces[0] = i;
-						edge.oppositeVerts[0] = (j + 2) % 3;
+						if(mNumUsedEntries >= mNumAllocatedEntries)
+							resizeCoefficientBuffer(mNumAllocatedEntries * 2);
 
-						edgeMap.insert(std::make_pair(std::make_pair(v0, v1), edge));
+						probeInfo.bufferIdx = mNumUsedEntries++;
 					}
 				}
-			}
 
-			// Form connections between outer tetrahedrons
-			for (auto& entry : edgeMap)
-			{
-				const Edge& edge = entry.second;
+				if(probeInfo.flags == LightProbeFlags::Dirty)
+				{
+					// TODO - Render probe
 
-				Tetrahedron& tet0 = outerTetrahedrons[outerFaceOffset + edge.faces[0]];
-				tet0.neighbors[edge.oppositeVerts[0]] = outerFaceOffset + edge.faces[1];
+					probeInfo.flags = LightProbeFlags::Clean;
+					numProbeUpdates++;
+				}
 
-				Tetrahedron& tet1 = outerTetrahedrons[outerFaceOffset + edge.faces[1]];
-				tet1.neighbors[edge.oppositeVerts[1]] = outerFaceOffset + edge.faces[0];
+				if (maxProbes != 0 && numProbeUpdates >= numProbeUpdates)
+					break;
 			}
 
-			// Generate face normals
-			FrameVector<Vector3> faceNormals(volume.outerFaces.size());
-			for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
-			{
-				const Vector3& v0 = positions[volume.outerFaces[i].vertices[0]];
-				const Vector3& v1 = positions[volume.outerFaces[i].vertices[1]];
-				const Vector3& v2 = positions[volume.outerFaces[i].vertices[2]];
-				
-				Vector3 e0 = v1 - v0;
-				Vector3 e1 = v2 - v0;
+			if (entry.lastUpdatedProbe == (UINT32)probes.size())
+				entry.isDirty = false;
+		}
 
-				faceNormals[i] = Vector3::normalize(e1.cross(e0));
-			}
+		// TODO - In another function, need to find visible tetrahedrons and update their buffers per-view
+		// - Allow culling with an optimal maximum range
+	}
 
-			// Generate vertex normals
-			struct VertexAccum
-			{
-				Vector3 sum;
-				float weight;
-			};
+	void LightProbes::resizeTetrahedronBuffers(VisibleLightProbeData& data, UINT32 count)
+	{
+		{
+			GPU_BUFFER_DESC desc;
+			desc.type = GBT_STRUCTURED;
+			desc.elementSize = sizeof(TetrahedronBoundsGPU);
+			desc.elementCount = count;
+			desc.usage = GBU_STATIC;
+			desc.format = BF_UNKNOWN;
+
+			SPtr<GpuBuffer> newBuffer = GpuBuffer::create(desc);
+			if (data.tetrahedronBounds)
+				newBuffer->copyData(*data.tetrahedronBounds, 0, 0, data.tetrahedronBounds->getSize(), true);
+
+			data.tetrahedronBounds = newBuffer;
+		}
 
-			FrameUnorderedMap<INT32, Vector3> vertexNormals;
-			for (auto& entry : edgeMap)
-			{
-				const Edge& edge = entry.second;
+		{
+			GPU_BUFFER_DESC desc;
+			desc.type = GBT_STRUCTURED;
+			desc.elementSize = sizeof(TetrahedronDataGPU);
+			desc.elementCount = count;
+			desc.usage = GBU_STATIC;
+			desc.format = BF_UNKNOWN;
+
+			SPtr<GpuBuffer> newBuffer = GpuBuffer::create(desc);
+			if (data.tetrahedronInfos)
+				newBuffer->copyData(*data.tetrahedronInfos, 0, 0, data.tetrahedronInfos->getSize(), true);
+
+			data.tetrahedronInfos = newBuffer;
+		}
 
-				auto accumulateNormalForEdgeVertex = [&](UINT32 v0Idx, UINT32 v1Idx)
-				{
-					auto iter = vertexNormals.insert(std::make_pair(v0Idx, Vector3(BsZero)));
+		data.maxNumEntries = count;
+	}
 
-					Vector3& accum = iter.first->second;
-					const Vector3& v0 = positions[v0Idx];
+	void LightProbes::resizeCoefficientBuffer(UINT32 count)
+	{
+		GPU_BUFFER_DESC desc;
+		desc.type = GBT_STRUCTURED;
+		desc.elementSize = sizeof(SHVector3RGB);
+		desc.elementCount = count;
+		desc.usage = GBU_STATIC;
+		desc.format = BF_UNKNOWN;
+
+		SPtr<GpuBuffer> newBuffer = GpuBuffer::create(desc);
+		if (mProbeCoefficientsGPU)
+			newBuffer->copyData(*mProbeCoefficientsGPU, 0, 0, mProbeCoefficientsGPU->getSize(), true);
+
+		mProbeCoefficientsGPU = newBuffer;
+		mNumAllocatedEntries = count;
+	}
 
-					auto accumulateNormalForFace = [&](INT32 faceIdx, INT32 v2LocIdx)
-					{
-						const TetrahedronFace& face = volume.outerFaces[faceIdx];
+	void LightProbes::updateVisibleProbes(const RendererView& view, VisibleLightProbeData& output)
+	{
+		const RendererViewProperties& viewProps = view.getProperties();
+		const ConvexVolume& worldFrustum = viewProps.cullFrustum;
 
-						// Vertices on the face, that aren't the vertex we're calculating the normal for
-						const Vector3& v1 = positions[v1Idx];
-						const Vector3& v2 = positions[face.vertices[v2LocIdx]];
+		for (UINT32 i = 0; i < (UINT32)mTetrahedronBounds.size(); i++)
+		{
+			if (worldFrustum.intersects(mTetrahedronBounds[i]))
+				mTempTetrahedronVisibility.push_back(i);
+		}
 
-						// Weight the contribution to the normal based on the angle spanned by the triangle
-						Vector3 e0 = Vector3::normalize(v1 - v0);
-						Vector3 e1 = Vector3::normalize(v2 - v0);
+		UINT32 numVisibleTets = (UINT32)mTempTetrahedronVisibility.size();
+		if (numVisibleTets > output.maxNumEntries)
+		{
+			UINT32 newBufferSize = 256;
+			if(output.maxNumEntries > 0)
+				newBufferSize = Math::divideAndRoundUp(numVisibleTets, output.maxNumEntries) * output.maxNumEntries;
 
-						float weight = acos(e0.dot(e1));
-						accum += weight * faceNormals[faceIdx];
-					};
+			resizeTetrahedronBuffers(output, newBufferSize);
+		}
 
-					accumulateNormalForFace(edge.faces[0], entry.second.oppositeVerts[0]);
-					accumulateNormalForFace(edge.faces[1], entry.second.oppositeVerts[1]);
-				};
+		// Write bounds
+		{
+			TetrahedronBoundsGPU* dst = (TetrahedronBoundsGPU*)output.tetrahedronBounds->lock(0, 
+				output.tetrahedronBounds->getSize(), GBL_WRITE_ONLY_DISCARD);
+
+			for (auto& entry : mTempTetrahedronVisibility)
+			{
+				const AABox& aabox = mTetrahedronBounds[entry];
+
+				dst->center = aabox.getCenter();
+				dst->extents = aabox.getHalfSize();
 
-				accumulateNormalForEdgeVertex(entry.first.first, entry.first.second);
-				accumulateNormalForEdgeVertex(entry.first.second, entry.first.first);
+				dst++;
 			}
 
-			for (auto& entry : vertexNormals)
-				entry.second.normalize();
+			output.tetrahedronBounds->unlock();
+		}
+
+		// Write other information
+		{
+			TetrahedronDataGPU* dst = (TetrahedronDataGPU*)output.tetrahedronInfos->lock(0, 
+				output.tetrahedronInfos->getSize(), GBL_WRITE_ONLY_DISCARD);
+
+			for (auto& entry : mTempTetrahedronVisibility)
+			{
+				const TetrahedronData& data = mTetrahedronInfos[entry];
+
+				memcpy(dst->indices, data.volume.vertices, sizeof(UINT32) * 4);
+				memcpy(&dst->transform, &data.transform, sizeof(float) * 12);
+
+				dst++;
+			}
+
+			output.tetrahedronBounds->unlock();
+		}
+
+		mTempTetrahedronVisibility.clear();
+	}
+
+	/** Hash value generator for std::pair<INT32, INT32>. */
+	struct pair_hash
+	{
+		size_t operator()(const std::pair<INT32, INT32>& key) const
+		{
+			size_t hash = 0;
+			bs::hash_combine(hash, key.first);
+			bs::hash_combine(hash, key.second);
+
+			return hash;
+		}
+	};
+
+	void LightProbes::generateTetrahedronData(const Vector<Vector3>& positions, Vector<TetrahedronData>& output, 
+		bool includeOuterFaces)
+	{
+		bs_frame_mark();
+		{
+			TetrahedronVolume volume = Triangulation::tetrahedralize(positions);
 
 			// Generate matrices
-			Vector<LightTetrahedron> output;
-			output.reserve(volume.tetrahedra.size() + volume.outerFaces.size());
+			UINT32 numOutputTets = (UINT32)volume.tetrahedra.size();
+			if (includeOuterFaces)
+				numOutputTets += (UINT32)volume.outerFaces.size();
+
+			output.reserve(includeOuterFaces);
 
 			// Insert innert tetrahedrons, generate matrices
 			for(UINT32 i = 0; i < (UINT32)volume.tetrahedra.size(); ++i)
 			{
-				LightTetrahedron entry;
+				TetrahedronData entry;
 				entry.volume = volume.tetrahedra[i];
 
 				// Generate a matrix that can be used for calculating barycentric coordinates
@@ -210,156 +337,288 @@ namespace bs { namespace ct
 				output.push_back(entry);
 			}
 
-			// Insert outer tetrahedrons, generate matrices
-			for(UINT32 i = 0; i < (UINT32)outerTetrahedrons.size(); ++i)
+			if (includeOuterFaces)
 			{
-				LightTetrahedron entry;
-				entry.volume = outerTetrahedrons[i];
+				// Put outer faces into the Tetrahedron structure, for convenience
+				UINT32 outerFaceOffset = (UINT32)volume.tetrahedra.size();
+				FrameVector<Tetrahedron> outerTetrahedrons;
 
-				// We need a way to project a point outside the tetrahedron volume onto an outer face, then calculate
-				// triangle's barycentric coordinates. Use use the per-vertex normals to extrude the triangle face into
-				// infinity.
+				outerTetrahedrons.resize(volume.outerFaces.size());
 
-				// Our point can be represented as:
-				// p == a (p0 + t*v0) + b (p1 + t*v1) + c (p2 + t*v2)
-				//
-				// where a, b and c are barycentric coordinates,
-				// p0, p1, p2 are the corners of the face
-				// v0, v1, v2 are the vertex normals, per corner
-				// t is the distance from the triangle to the point
-				//
-				// Essentially we're calculating the corners of a bigger triangle that's "t" units away from the
-				// face, and its corners lie along the per-vertex normals. Point "p" will lie on that triangle, for which
-				// we can then calculate barycentric coordinates normally.
-				//
-				// First we substitute: c = 1 - a - b
-				// p == a (p0 + t v0) + b (p1 + t v1) + (1 - a - b) (p2 + t v2)
-				// p == a (p0 + t v0) + b (p1 + t v1) + (p2 + t v2) - a (p2 + t v2) - b (p2 + t v2)
-				// p == a (p0 - p2 + t v0 - t v2) + b (p1 - p2 + t v1 - t v2) + (p2 + t v2)
-				//
-				// And move everything to one side:
-				// p - p2 - t v2 == a (p0 - p2 + t ( v0 - v2)) + b (p1 - p2 + t ( v1 - v2))
-				// a (p0 - p2 + t ( v0 - v2)) + b (p1 - p2 + t ( v1 - v2)) - (p - p2 - t v2) == 0
-				//
-				// We rewrite it using:
-				// Ap = p0 - p2
-				// Av = v0 - v2
-				// Bp = p1 - p2
-				// Bv = v1 - v2
-				// Cp = p - p2
-				// Cv = -v2
-				//
-				// Which yields:
-				// a (Ap + t Av) + b (Bp + t Bv) - (Cp + t Cv) == 0
-				//
-				// Which can be written in matrix form:
-				//
-				// M = {Ap + t Av, Bp + t Bv, Cp + t Cv}
-				//       a      0
-				// M * [ b ] = [0]
-				//      -1      0
-				//
-				// From that we can tell that matrix M cannot be inverted, because if we multiply the zero vector with the
-				// inverted matrix the result would be zero, and not [a, b, -1]. Since the matrix cannot be inverted
-				// det(M) == 0.
-				//
-				// We can use that fact to calculate "t". After we have "t" we can calculate barycentric coordinates
-				// normally.
-				//
-				// Solving equation det(M) == 0 yields a cubic in form:
-				// p t^3 + q t^2 + r t + s = 0
-				//
-				// We'll convert this to monic form, by dividing by p:
-				// t^3 + q/p t^2 + r/p t + s/p = 0
-				//
-				// Or if p ends up being zero, we end up with a quadratic instead:
-				// q t^2 + r t + s = 0
-				// 
-				// We want to create a matrix that when multiplied with the position, yields us the three coefficients,
-				// which we can then use to solve for "t". For this we create a 4x3 matrix, where each row represents
-				// a solution for one of the coefficients. We factor contributons to each coefficient whether they depend on
-				// position x, y, z, or don't depend on position (row columns, in that order respectively).
-
-				const Vector3& p0 = positions[entry.volume.vertices[0]];
-				const Vector3& p1 = positions[entry.volume.vertices[1]];
-				const Vector3& p2 = positions[entry.volume.vertices[2]];
-
-				const Vector3& v0 = vertexNormals[entry.volume.vertices[0]];
-				const Vector3& v1 = vertexNormals[entry.volume.vertices[1]];
-				const Vector3& v2 = vertexNormals[entry.volume.vertices[2]];
-
-				float p =
-					v2.x * v1.y * v0.z -
-					v1.x * v2.y * v0.z -
-					v2.x * v0.y * v1.z +
-					v0.x * v2.y * v1.z +
-					v1.x * v0.y * v2.z -
-					v0.x * v1.y * v2.z;
-				
-				float qx = -v1.y * v0.z + v2.y * v0.z + v0.y * v1.z - v2.y * v1.z - v0.y * v2.z + v1.y * v2.z;
-				float qy = v1.x * v0.z - v2.x * v0.z - v0.x * v1.z + v2.x * v1.z + v0.x * v2.z - v1.x * v2.z;
-				float qz = -v1.x * v0.y + v2.x * v0.y + v0.x * v1.y - v2.x * v1.y - v0.x * v2.y + v1.x * v2.y;
-				float qw = v2.y * v1.z * p0.x - v1.y * v2.z * p0.x - v2.y * v0.z * p1.x + v0.y * v2.z * p1.x + 
-					v1.y * v0.z * p2.x - v0.y * v1.z * p2.x - v2.x * v1.z * p0.y + v1.x * v2.z * p0.y + 
-					v2.x * v0.z * p1.y - v0.x * v2.z * p1.y - v1.x * v0.z * p2.y + v0.x * v1.z * p2.y + 
-					v2.x * v1.y * p0.z - v1.x * v2.y * p0.z - v2.x * v0.y * p1.z + v0.x * v2.y * p1.z + 
-					v1.x * v0.y * p2.z - v0.x * v1.y * p2.z;
-
-				float rx = v1.z * p0.y - v2.z * p0.y - v0.z * p1.y + v2.z * p1.y + v0.z * p2.y - v1.z * p2.y -
-					v1.y * p0.z + v2.y * p0.z + v0.y * p1.z - v2.y * p1.z - v0.y * p2.z + v1.y * p2.z;
-				float ry = -v1.z * p0.x + v2.z * p0.x + v0.z * p1.x - v2.z * p1.x - v0.z * p2.x + v1.z * p2.x +
-					v1.x * p0.z - v2.x * p0.z - v0.x * p1.z + v2.x * p1.z + v0.x * p2.z - v1.x * p2.z;
-				float rz = v1.y * p0.x - v2.y * p0.x - v0.y * p1.x + v2.y * p1.x + v0.y * p2.x - v1.y * p2.x -
-					v1.x * p0.y + v2.x * p0.y + v0.x * p1.y - v2.x * p1.y - v0.x * p2.y + v1.x * p2.y;
-				float rw = v2.z * p1.x * p0.y - v1.z * p2.x * p0.y - v2.z * p0.x * p1.y + v0.z * p2.x * p1.y +
-					v1.z * p0.x * p2.y - v0.z * p1.x * p2.y - v2.y * p1.x * p0.z + v1.y * p2.x * p0.z +
-					v2.x * p1.y * p0.z - v1.x * p2.y * p0.z + v2.y * p0.x * p1.z - v0.y * p2.x * p1.z -
-					v2.x * p0.y * p1.z + v0.x * p2.y * p1.z - v1.y * p0.x * p2.z + v0.y * p1.x * p2.z +
-					v1.x * p0.y * p2.z - v0.x * p1.y * p2.z;
-
-				float sx = -p1.y * p0.z + p2.y * p0.z + p0.y * p1.z - p2.y * p1.z - p0.y * p2.z + p1.y * p2.z;
-				float sy = p1.x * p0.z - p2.x * p0.z - p0.x * p1.z + p2.x * p1.z + p0.x * p2.z - p1.x * p2.z;
-				float sz = -p1.x * p0.y + p2.x * p0.y + p0.x * p1.y - p2.x * p1.y - p0.x * p2.y + p1.x * p2.y;
-				float sw = p2.x * p1.y * p0.z - p1.x * p2.y * p0.z - p2.x * p0.y * p1.z + 
-					p0.x * p2.y * p1.z + p1.x * p0.y * p2.z - p0.x * p1.y * p2.z;
-
-				entry.transform[0][0] = qx;
-				entry.transform[0][1] = qy;
-				entry.transform[0][2] = qz;
-				entry.transform[0][3] = qw;
-
-				entry.transform[1][0] = rx;
-				entry.transform[1][1] = ry;
-				entry.transform[1][2] = rz;
-				entry.transform[1][3] = rw;
-
-				entry.transform[2][0] = sx;
-				entry.transform[2][1] = sy;
-				entry.transform[2][2] = sz;
-				entry.transform[2][3] = sw;
-
-				// Unused
-				entry.transform[3][0] = 0.0f;
-				entry.transform[3][1] = 0.0f;
-				entry.transform[3][2] = 0.0f;
-				entry.transform[3][3] = 0.0f;
-
-				if (fabs(p) > 0.00001f)
-					entry.transform = entry.transform * (1.0f / p);
-				else // Quadratic
-					entry.volume.neighbors[3] = -2;
+				for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
+				{
+					Tetrahedron outerTetrahedron;
+					memcpy(outerTetrahedron.vertices, volume.outerFaces[i].vertices, sizeof(INT32) * 3);
+					memset(outerTetrahedron.neighbors, -1, sizeof(INT32) * 3);
 
-				output.push_back(entry);
+					outerTetrahedron.vertices[4] = -1; // Marks the tetrahedron as an outer face
+					outerTetrahedron.neighbors[4] = volume.outerFaces[i].tetrahedron;
+
+					outerTetrahedrons[i] = outerTetrahedron;
+				}
+
+				// Connect boundary tetrahedrons with these new outer tetrahedrons
+				for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
+				{
+					Tetrahedron& tet = volume.tetrahedra[volume.outerFaces[i].tetrahedron];
+					for (UINT32 j = 0; j < 4; j++)
+					{
+						if (tet.neighbors[j] == -1)
+							tet.neighbors[j] = outerFaceOffset + i;
+					}
+				}
+
+				// Make a map between outer edges and faces, used in the following algorithms
+				struct Edge
+				{
+					INT32 faces[2];
+					INT32 oppositeVerts[2];
+				};
+
+				FrameUnorderedMap<std::pair<INT32, INT32>, Edge, pair_hash> edgeMap;
+				for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
+				{
+					for (UINT32 j = 0; j < 3; ++j)
+					{
+						INT32 v0 = volume.outerFaces[i].vertices[j];
+						INT32 v1 = volume.outerFaces[i].vertices[(j + 1) % 3];
+
+						// Keep the same ordering so other faces can find the same edge
+						if (v0 > v1)
+							std::swap(v0, v1);
+
+						auto iterFind = edgeMap.find(std::make_pair(v0, v1));
+						if (iterFind != edgeMap.end())
+						{
+							iterFind->second.faces[1] = i;
+							iterFind->second.oppositeVerts[1] = (j + 2) % 3;
+						}
+						else
+						{
+							Edge edge;
+							edge.faces[0] = i;
+							edge.oppositeVerts[0] = (j + 2) % 3;
+
+							edgeMap.insert(std::make_pair(std::make_pair(v0, v1), edge));
+						}
+					}
+				}
+
+				// Form connections between outer tetrahedrons
+				for (auto& entry : edgeMap)
+				{
+					const Edge& edge = entry.second;
+
+					Tetrahedron& tet0 = outerTetrahedrons[outerFaceOffset + edge.faces[0]];
+					tet0.neighbors[edge.oppositeVerts[0]] = outerFaceOffset + edge.faces[1];
+
+					Tetrahedron& tet1 = outerTetrahedrons[outerFaceOffset + edge.faces[1]];
+					tet1.neighbors[edge.oppositeVerts[1]] = outerFaceOffset + edge.faces[0];
+				}
+
+				// Generate face normals
+				FrameVector<Vector3> faceNormals(volume.outerFaces.size());
+				for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
+				{
+					const Vector3& v0 = positions[volume.outerFaces[i].vertices[0]];
+					const Vector3& v1 = positions[volume.outerFaces[i].vertices[1]];
+					const Vector3& v2 = positions[volume.outerFaces[i].vertices[2]];
+					
+					Vector3 e0 = v1 - v0;
+					Vector3 e1 = v2 - v0;
+
+					faceNormals[i] = Vector3::normalize(e1.cross(e0));
+				}
+
+				// Generate vertex normals
+				struct VertexAccum
+				{
+					Vector3 sum;
+					float weight;
+				};
+
+				FrameUnorderedMap<INT32, Vector3> vertexNormals;
+				for (auto& entry : edgeMap)
+				{
+					const Edge& edge = entry.second;
+
+					auto accumulateNormalForEdgeVertex = [&](UINT32 v0Idx, UINT32 v1Idx)
+					{
+						auto iter = vertexNormals.insert(std::make_pair(v0Idx, Vector3(BsZero)));
+
+						Vector3& accum = iter.first->second;
+						const Vector3& v0 = positions[v0Idx];
+
+						auto accumulateNormalForFace = [&](INT32 faceIdx, INT32 v2LocIdx)
+						{
+							const TetrahedronFace& face = volume.outerFaces[faceIdx];
+
+							// Vertices on the face, that aren't the vertex we're calculating the normal for
+							const Vector3& v1 = positions[v1Idx];
+							const Vector3& v2 = positions[face.vertices[v2LocIdx]];
+
+							// Weight the contribution to the normal based on the angle spanned by the triangle
+							Vector3 e0 = Vector3::normalize(v1 - v0);
+							Vector3 e1 = Vector3::normalize(v2 - v0);
+
+							float weight = acos(e0.dot(e1));
+							accum += weight * faceNormals[faceIdx];
+						};
+
+						accumulateNormalForFace(edge.faces[0], entry.second.oppositeVerts[0]);
+						accumulateNormalForFace(edge.faces[1], entry.second.oppositeVerts[1]);
+					};
+
+					accumulateNormalForEdgeVertex(entry.first.first, entry.first.second);
+					accumulateNormalForEdgeVertex(entry.first.second, entry.first.first);
+				}
+
+				for (auto& entry : vertexNormals)
+					entry.second.normalize();
+
+				// Insert outer tetrahedrons, generate matrices
+				for(UINT32 i = 0; i < (UINT32)outerTetrahedrons.size(); ++i)
+				{
+					TetrahedronData entry;
+					entry.volume = outerTetrahedrons[i];
+
+					// We need a way to project a point outside the tetrahedron volume onto an outer face, then calculate
+					// triangle's barycentric coordinates. Use use the per-vertex normals to extrude the triangle face into
+					// infinity.
+
+					// Our point can be represented as:
+					// p == a (p0 + t*v0) + b (p1 + t*v1) + c (p2 + t*v2)
+					//
+					// where a, b and c are barycentric coordinates,
+					// p0, p1, p2 are the corners of the face
+					// v0, v1, v2 are the vertex normals, per corner
+					// t is the distance from the triangle to the point
+					//
+					// Essentially we're calculating the corners of a bigger triangle that's "t" units away from the
+					// face, and its corners lie along the per-vertex normals. Point "p" will lie on that triangle, for which
+					// we can then calculate barycentric coordinates normally.
+					//
+					// First we substitute: c = 1 - a - b
+					// p == a (p0 + t v0) + b (p1 + t v1) + (1 - a - b) (p2 + t v2)
+					// p == a (p0 + t v0) + b (p1 + t v1) + (p2 + t v2) - a (p2 + t v2) - b (p2 + t v2)
+					// p == a (p0 - p2 + t v0 - t v2) + b (p1 - p2 + t v1 - t v2) + (p2 + t v2)
+					//
+					// And move everything to one side:
+					// p - p2 - t v2 == a (p0 - p2 + t ( v0 - v2)) + b (p1 - p2 + t ( v1 - v2))
+					// a (p0 - p2 + t ( v0 - v2)) + b (p1 - p2 + t ( v1 - v2)) - (p - p2 - t v2) == 0
+					//
+					// We rewrite it using:
+					// Ap = p0 - p2
+					// Av = v0 - v2
+					// Bp = p1 - p2
+					// Bv = v1 - v2
+					// Cp = p - p2
+					// Cv = -v2
+					//
+					// Which yields:
+					// a (Ap + t Av) + b (Bp + t Bv) - (Cp + t Cv) == 0
+					//
+					// Which can be written in matrix form:
+					//
+					// M = {Ap + t Av, Bp + t Bv, Cp + t Cv}
+					//       a      0
+					// M * [ b ] = [0]
+					//      -1      0
+					//
+					// From that we can tell that matrix M cannot be inverted, because if we multiply the zero vector with the
+					// inverted matrix the result would be zero, and not [a, b, -1]. Since the matrix cannot be inverted
+					// det(M) == 0.
+					//
+					// We can use that fact to calculate "t". After we have "t" we can calculate barycentric coordinates
+					// normally.
+					//
+					// Solving equation det(M) == 0 yields a cubic in form:
+					// p t^3 + q t^2 + r t + s = 0
+					//
+					// We'll convert this to monic form, by dividing by p:
+					// t^3 + q/p t^2 + r/p t + s/p = 0
+					//
+					// Or if p ends up being zero, we end up with a quadratic instead:
+					// q t^2 + r t + s = 0
+					// 
+					// We want to create a matrix that when multiplied with the position, yields us the three coefficients,
+					// which we can then use to solve for "t". For this we create a 4x3 matrix, where each row represents
+					// a solution for one of the coefficients. We factor contributons to each coefficient whether they depend on
+					// position x, y, z, or don't depend on position (row columns, in that order respectively).
+
+					const Vector3& p0 = positions[entry.volume.vertices[0]];
+					const Vector3& p1 = positions[entry.volume.vertices[1]];
+					const Vector3& p2 = positions[entry.volume.vertices[2]];
+
+					const Vector3& v0 = vertexNormals[entry.volume.vertices[0]];
+					const Vector3& v1 = vertexNormals[entry.volume.vertices[1]];
+					const Vector3& v2 = vertexNormals[entry.volume.vertices[2]];
+
+					float p =
+						v2.x * v1.y * v0.z -
+						v1.x * v2.y * v0.z -
+						v2.x * v0.y * v1.z +
+						v0.x * v2.y * v1.z +
+						v1.x * v0.y * v2.z -
+						v0.x * v1.y * v2.z;
+					
+					float qx = -v1.y * v0.z + v2.y * v0.z + v0.y * v1.z - v2.y * v1.z - v0.y * v2.z + v1.y * v2.z;
+					float qy = v1.x * v0.z - v2.x * v0.z - v0.x * v1.z + v2.x * v1.z + v0.x * v2.z - v1.x * v2.z;
+					float qz = -v1.x * v0.y + v2.x * v0.y + v0.x * v1.y - v2.x * v1.y - v0.x * v2.y + v1.x * v2.y;
+					float qw = v2.y * v1.z * p0.x - v1.y * v2.z * p0.x - v2.y * v0.z * p1.x + v0.y * v2.z * p1.x + 
+						v1.y * v0.z * p2.x - v0.y * v1.z * p2.x - v2.x * v1.z * p0.y + v1.x * v2.z * p0.y + 
+						v2.x * v0.z * p1.y - v0.x * v2.z * p1.y - v1.x * v0.z * p2.y + v0.x * v1.z * p2.y + 
+						v2.x * v1.y * p0.z - v1.x * v2.y * p0.z - v2.x * v0.y * p1.z + v0.x * v2.y * p1.z + 
+						v1.x * v0.y * p2.z - v0.x * v1.y * p2.z;
+
+					float rx = v1.z * p0.y - v2.z * p0.y - v0.z * p1.y + v2.z * p1.y + v0.z * p2.y - v1.z * p2.y -
+						v1.y * p0.z + v2.y * p0.z + v0.y * p1.z - v2.y * p1.z - v0.y * p2.z + v1.y * p2.z;
+					float ry = -v1.z * p0.x + v2.z * p0.x + v0.z * p1.x - v2.z * p1.x - v0.z * p2.x + v1.z * p2.x +
+						v1.x * p0.z - v2.x * p0.z - v0.x * p1.z + v2.x * p1.z + v0.x * p2.z - v1.x * p2.z;
+					float rz = v1.y * p0.x - v2.y * p0.x - v0.y * p1.x + v2.y * p1.x + v0.y * p2.x - v1.y * p2.x -
+						v1.x * p0.y + v2.x * p0.y + v0.x * p1.y - v2.x * p1.y - v0.x * p2.y + v1.x * p2.y;
+					float rw = v2.z * p1.x * p0.y - v1.z * p2.x * p0.y - v2.z * p0.x * p1.y + v0.z * p2.x * p1.y +
+						v1.z * p0.x * p2.y - v0.z * p1.x * p2.y - v2.y * p1.x * p0.z + v1.y * p2.x * p0.z +
+						v2.x * p1.y * p0.z - v1.x * p2.y * p0.z + v2.y * p0.x * p1.z - v0.y * p2.x * p1.z -
+						v2.x * p0.y * p1.z + v0.x * p2.y * p1.z - v1.y * p0.x * p2.z + v0.y * p1.x * p2.z +
+						v1.x * p0.y * p2.z - v0.x * p1.y * p2.z;
+
+					float sx = -p1.y * p0.z + p2.y * p0.z + p0.y * p1.z - p2.y * p1.z - p0.y * p2.z + p1.y * p2.z;
+					float sy = p1.x * p0.z - p2.x * p0.z - p0.x * p1.z + p2.x * p1.z + p0.x * p2.z - p1.x * p2.z;
+					float sz = -p1.x * p0.y + p2.x * p0.y + p0.x * p1.y - p2.x * p1.y - p0.x * p2.y + p1.x * p2.y;
+					float sw = p2.x * p1.y * p0.z - p1.x * p2.y * p0.z - p2.x * p0.y * p1.z + 
+						p0.x * p2.y * p1.z + p1.x * p0.y * p2.z - p0.x * p1.y * p2.z;
+
+					entry.transform[0][0] = qx;
+					entry.transform[0][1] = qy;
+					entry.transform[0][2] = qz;
+					entry.transform[0][3] = qw;
+
+					entry.transform[1][0] = rx;
+					entry.transform[1][1] = ry;
+					entry.transform[1][2] = rz;
+					entry.transform[1][3] = rw;
+
+					entry.transform[2][0] = sx;
+					entry.transform[2][1] = sy;
+					entry.transform[2][2] = sz;
+					entry.transform[2][3] = sw;
+
+					// Unused
+					entry.transform[3][0] = 0.0f;
+					entry.transform[3][1] = 0.0f;
+					entry.transform[3][2] = 0.0f;
+					entry.transform[3][3] = 0.0f;
+
+					if (fabs(p) > 0.00001f)
+						entry.transform = entry.transform * (1.0f / p);
+					else // Quadratic
+						entry.volume.neighbors[3] = -2;
+
+					output.push_back(entry);
+				}
 			}
 		}
 		bs_frame_clear();
 	}
 }}
-
-/** @cond STDLIB */
-
-namespace std
-{
-}
-
-/** @endcond */

+ 8 - 8
Source/RenderBeast/Source/BsPostProcessing.cpp

@@ -1408,8 +1408,8 @@ namespace bs { namespace ct
 		if(numDownsampleLevels > 0)
 		{
 			Vector2I downsampledSize(
-				std::max(1, Math::divideAndRoundUp(viewProps.viewRect.width, 2)),
-				std::max(1, Math::divideAndRoundUp(viewProps.viewRect.height, 2))
+				std::max(1, Math::divideAndRoundUp((INT32)viewProps.viewRect.width, 2)),
+				std::max(1, Math::divideAndRoundUp((INT32)viewProps.viewRect.height, 2))
 			);
 
 			POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_FLOAT16_RGBA, downsampledSize.x, 
@@ -1423,8 +1423,8 @@ namespace bs { namespace ct
 		if(numDownsampleLevels > 1)
 		{
 			Vector2I downsampledSize(
-				std::max(1, Math::divideAndRoundUp(viewProps.viewRect.width, 4)),
-				std::max(1, Math::divideAndRoundUp(viewProps.viewRect.height, 4))
+				std::max(1, Math::divideAndRoundUp((INT32)viewProps.viewRect.width, 4)),
+				std::max(1, Math::divideAndRoundUp((INT32)viewProps.viewRect.height, 4))
 			);
 
 			POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_FLOAT16_RGBA, downsampledSize.x, 
@@ -1445,8 +1445,8 @@ namespace bs { namespace ct
 			textures.aoSetup = setupTex1->texture;
 
 			Vector2I downsampledSize(
-				std::max(1, Math::divideAndRoundUp(viewProps.viewRect.width, 4)),
-				std::max(1, Math::divideAndRoundUp(viewProps.viewRect.height, 4))
+				std::max(1, Math::divideAndRoundUp((INT32)viewProps.viewRect.width, 4)),
+				std::max(1, Math::divideAndRoundUp((INT32)viewProps.viewRect.height, 4))
 			);
 
 			POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, downsampledSize.x, 
@@ -1468,8 +1468,8 @@ namespace bs { namespace ct
 				textures.aoDownsampled = downAOTex1->texture;
 
 			Vector2I downsampledSize(
-				std::max(1, Math::divideAndRoundUp(viewProps.viewRect.width, 2)),
-				std::max(1, Math::divideAndRoundUp(viewProps.viewRect.height, 2))
+				std::max(1, Math::divideAndRoundUp((INT32)viewProps.viewRect.width, 2)),
+				std::max(1, Math::divideAndRoundUp((INT32)viewProps.viewRect.height, 2))
 			);
 
 			POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, downsampledSize.x, 

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

@@ -234,12 +234,12 @@ namespace bs { namespace ct
 	void RenderBeast::notifyLightProbeVolumeAdded(LightProbeVolume* volume)
 	{
 		assert(false); // TODO
-	}
+	}
 
 	void RenderBeast::notifyLightProbeVolumeUpdated(LightProbeVolume* volume)
 	{
 		assert(false); // TODO
-	}
+	}
 
 	void RenderBeast::notifyLightProbeVolumeRemoved(LightProbeVolume* volume)
 	{
@@ -394,6 +394,10 @@ namespace bs { namespace ct
 		const SceneInfo& sceneInfo = mScene->getSceneInfo();
 		const VisibilityInfo& visibility = viewGroup.getVisibilityInfo();
 
+		// Note: I'm determining light and refl. probe visibility for the entire group. It might be more performance
+		// efficient to do it per view. Additionally I'm using a single GPU buffer to hold their information, which is
+		// then updated when each view group is rendered. It might be better to keep one buffer reserved per-view.
+
 		// Update GPU light data
 		mVisibleLightInfo->update(sceneInfo, viewGroup);
 

+ 11 - 0
Source/RenderBeast/Source/BsRendererView.cpp

@@ -291,6 +291,17 @@ namespace bs { namespace ct
 		}
 	}
 
+	void RendererView::calculateVisibility(const Vector<AABox>& bounds, Vector<bool>& visibility) const
+	{
+		const ConvexVolume& worldFrustum = mProperties.cullFrustum;
+
+		for (UINT32 i = 0; i < (UINT32)bounds.size(); i++)
+		{
+			if (worldFrustum.intersects(bounds[i]))
+				visibility[i] = true;
+		}
+	}
+
 	Vector2 RendererView::getDeviceZToViewZ(const Matrix4& projMatrix)
 	{
 		// Returns a set of values that will transform depth buffer values (in range [0, 1]) to a distance