summaryrefslogtreecommitdiff
path: root/Runtime/Camera
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Camera')
-rw-r--r--Runtime/Camera/BaseRenderer.cpp158
-rw-r--r--Runtime/Camera/BaseRenderer.h148
-rw-r--r--Runtime/Camera/Camera.cpp2340
-rw-r--r--Runtime/Camera/Camera.h503
-rw-r--r--Runtime/Camera/CameraCullingParameters.h36
-rw-r--r--Runtime/Camera/CameraUtil.cpp452
-rw-r--r--Runtime/Camera/CameraUtil.h61
-rw-r--r--Runtime/Camera/CullResults.cpp81
-rw-r--r--Runtime/Camera/CullResults.h153
-rw-r--r--Runtime/Camera/Culler.cpp261
-rw-r--r--Runtime/Camera/Culler.h16
-rw-r--r--Runtime/Camera/CullingParameters.h140
-rw-r--r--Runtime/Camera/Flare.cpp611
-rw-r--r--Runtime/Camera/Flare.h169
-rw-r--r--Runtime/Camera/GraphicsSettings.cpp79
-rw-r--r--Runtime/Camera/GraphicsSettings.h48
-rw-r--r--Runtime/Camera/HaloManager.cpp282
-rw-r--r--Runtime/Camera/HaloManager.h69
-rw-r--r--Runtime/Camera/ImageFilters.cpp613
-rw-r--r--Runtime/Camera/ImageFilters.h46
-rw-r--r--Runtime/Camera/IntermediateRenderer.cpp284
-rw-r--r--Runtime/Camera/IntermediateRenderer.h148
-rw-r--r--Runtime/Camera/IntermediateUsers.cpp21
-rw-r--r--Runtime/Camera/IntermediateUsers.h26
-rw-r--r--Runtime/Camera/LODGroup.cpp395
-rw-r--r--Runtime/Camera/LODGroup.h105
-rw-r--r--Runtime/Camera/LODGroupManager.cpp462
-rw-r--r--Runtime/Camera/LODGroupManager.h116
-rw-r--r--Runtime/Camera/Light.cpp668
-rw-r--r--Runtime/Camera/Light.h254
-rw-r--r--Runtime/Camera/LightCulling.cpp669
-rw-r--r--Runtime/Camera/LightCulling.h7
-rw-r--r--Runtime/Camera/LightManager.cpp635
-rw-r--r--Runtime/Camera/LightManager.h62
-rw-r--r--Runtime/Camera/LightProbes.cpp350
-rw-r--r--Runtime/Camera/LightProbes.h98
-rw-r--r--Runtime/Camera/LightTypes.h28
-rw-r--r--Runtime/Camera/Lighting.h34
-rw-r--r--Runtime/Camera/OcclusionArea.cpp67
-rw-r--r--Runtime/Camera/OcclusionArea.h46
-rw-r--r--Runtime/Camera/OcclusionPortal.cpp60
-rw-r--r--Runtime/Camera/OcclusionPortal.h37
-rw-r--r--Runtime/Camera/Projector.cpp313
-rw-r--r--Runtime/Camera/Projector.h74
-rw-r--r--Runtime/Camera/RenderLayers/GUIElement.cpp31
-rw-r--r--Runtime/Camera/RenderLayers/GUIElement.h30
-rw-r--r--Runtime/Camera/RenderLayers/GUILayer.cpp104
-rw-r--r--Runtime/Camera/RenderLayers/GUILayer.h33
-rw-r--r--Runtime/Camera/RenderLayers/GUIText.cpp303
-rw-r--r--Runtime/Camera/RenderLayers/GUIText.h93
-rw-r--r--Runtime/Camera/RenderLayers/GUITexture.cpp524
-rw-r--r--Runtime/Camera/RenderLayers/GUITexture.h73
-rw-r--r--Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.cpp19
-rw-r--r--Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h11
-rw-r--r--Runtime/Camera/RenderLoops/ForwardShaderRenderLoop.cpp1403
-rw-r--r--Runtime/Camera/RenderLoops/ForwardVertexRenderLoop.cpp637
-rw-r--r--Runtime/Camera/RenderLoops/GlobalLayeringData.h26
-rw-r--r--Runtime/Camera/RenderLoops/PrePassRenderLoop.cpp1958
-rw-r--r--Runtime/Camera/RenderLoops/RenderLoop.h25
-rw-r--r--Runtime/Camera/RenderLoops/RenderLoopEnums.h29
-rw-r--r--Runtime/Camera/RenderLoops/RenderLoopPrivate.cpp469
-rw-r--r--Runtime/Camera/RenderLoops/RenderLoopPrivate.h86
-rw-r--r--Runtime/Camera/RenderLoops/ReplacementRenderLoop.cpp245
-rw-r--r--Runtime/Camera/RenderLoops/ReplacementRenderLoop.h11
-rw-r--r--Runtime/Camera/RenderManager.cpp272
-rw-r--r--Runtime/Camera/RenderManager.h83
-rw-r--r--Runtime/Camera/RenderSettings.cpp262
-rw-r--r--Runtime/Camera/RenderSettings.h94
-rw-r--r--Runtime/Camera/Renderable.h30
-rw-r--r--Runtime/Camera/Renderqueue.cpp268
-rw-r--r--Runtime/Camera/Renderqueue.h23
-rw-r--r--Runtime/Camera/SceneCulling.cpp412
-rw-r--r--Runtime/Camera/SceneCulling.h18
-rw-r--r--Runtime/Camera/SceneNode.h23
-rw-r--r--Runtime/Camera/SceneSettings.cpp140
-rw-r--r--Runtime/Camera/SceneSettings.h94
-rw-r--r--Runtime/Camera/ShaderReplaceData.h14
-rw-r--r--Runtime/Camera/ShadowCulling.cpp885
-rw-r--r--Runtime/Camera/ShadowCulling.h121
-rw-r--r--Runtime/Camera/ShadowSettings.cpp14
-rw-r--r--Runtime/Camera/ShadowSettings.h33
-rw-r--r--Runtime/Camera/Shadows.cpp1227
-rw-r--r--Runtime/Camera/Shadows.h48
-rw-r--r--Runtime/Camera/Skybox.cpp199
-rw-r--r--Runtime/Camera/Skybox.h27
-rw-r--r--Runtime/Camera/UmbraBackwardsCompatibility.cpp37
-rw-r--r--Runtime/Camera/UmbraBackwardsCompatibility.h85
-rw-r--r--Runtime/Camera/UmbraBackwardsCompatibilityDefine.h4
-rw-r--r--Runtime/Camera/UmbraTomeData.h22
-rw-r--r--Runtime/Camera/UnityScene.cpp505
-rw-r--r--Runtime/Camera/UnityScene.h154
91 files changed, 22429 insertions, 0 deletions
diff --git a/Runtime/Camera/BaseRenderer.cpp b/Runtime/Camera/BaseRenderer.cpp
new file mode 100644
index 0000000..a94c840
--- /dev/null
+++ b/Runtime/Camera/BaseRenderer.cpp
@@ -0,0 +1,158 @@
+#include "UnityPrefix.h"
+#include "BaseRenderer.h"
+#include "Runtime/Shaders/Material.h"
+#include "UnityScene.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Shaders/MaterialProperties.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "External/shaderlab/Library/subshader.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "External/MurmurHash/MurmurHash2.h"
+
+using namespace Unity;
+
+
+BaseRenderer::BaseRenderer (RendererType type)
+: m_RendererType(type)
+, m_LightmapST(1.0f,1.0f,0.0f,0.0f)
+, m_LightmapIndex (0xFF)
+, m_CastShadows(true)
+, m_ReceiveShadows(true)
+, m_IsVisibleInScene(false)
+, m_CustomProperties(NULL)
+, m_CustomPropertiesHash(0)
+, m_TransformDirty(true)
+, m_BoundsDirty(true)
+, m_GlobalLayeringData(GlobalLayeringDataCleared())
+{
+ Assert(type <= 0xFF);
+ #if UNITY_EDITOR
+ m_ScaleInLightmap = 1.0f;
+ #endif
+}
+
+BaseRenderer::~BaseRenderer ()
+{
+}
+
+void BaseRenderer::GetLocalAABB (AABB& aabb)
+{
+ GetTransformInfo (); // updates if needed
+ aabb = m_TransformInfo.localAABB;
+}
+
+void BaseRenderer::GetWorldAABB (AABB& aabb)
+{
+ GetTransformInfo (); // updates if needed
+ aabb = m_TransformInfo.worldAABB;
+}
+
+
+int BaseRenderer::GetLightmapIndexInt() const
+{
+ if (m_LightmapIndex == 0xFF)
+ return -1;
+ else
+ return m_LightmapIndex;
+}
+
+void BaseRenderer::SetLightmapIndexIntNoDirty(int index)
+{
+ if (index == -1)
+ m_LightmapIndex = 0xFF;
+ else if (index < 0 || index > 0xFF)
+ {
+ m_LightmapIndex = 0xFF;
+ ErrorString("Lightmap index must be less than 256");
+ }
+ else
+ m_LightmapIndex = index;
+}
+
+// Treats objects that actually _use_ lightmaps as lightmapped.
+bool BaseRenderer::IsLightmappedForRendering() const
+{
+ // Special indices:
+ // 0xFF: object does not use lightmaps
+ // 0xFE: object only influences lightmaps, but does not use them itself
+
+ return m_LightmapIndex != 0xFF && m_LightmapIndex != 0xFE;
+}
+
+// Treats objects that _influence_ lightmaps as lightmapped.
+bool BaseRenderer::IsLightmappedForShadows() const
+{
+ return m_LightmapIndex != 0xFF;
+}
+
+bool operator == (const TransformInfo& a, const TransformInfo& b){
+ return a.invScale == b.invScale
+ && a.localAABB.GetCenter() == b.localAABB.GetCenter()
+ && a.localAABB.GetExtent() == b.localAABB.GetExtent()
+ && a.transformType == b.transformType
+ && a.worldAABB.GetCenter() == b.worldAABB.GetCenter()
+ && a.worldAABB.GetExtent() == b.worldAABB.GetExtent()
+ && a.worldMatrix.GetAxisX() == b.worldMatrix.GetAxisX();
+}
+
+
+void BaseRenderer::ComputeCustomPropertiesHash()
+{
+ if (m_CustomProperties)
+ {
+ const float* buf = m_CustomProperties->GetBufferBegin();
+ const float* bufEnd = m_CustomProperties->GetBufferEnd();
+ m_CustomPropertiesHash = MurmurHash2A (buf, (const UInt8*)bufEnd - (const UInt8*)buf, 0x9747b28c);
+ }
+ else
+ {
+ m_CustomPropertiesHash = 0;
+ }
+}
+
+
+void BaseRenderer::ApplyCustomProperties (Unity::Material& mat, Shader* shader, int subshaderIndex) const
+{
+ if (!m_CustomProperties)
+ return;
+
+ // Hopefully most of per-instance custom properties only go into shader constants; those
+ // are applied later on in BeforeDrawCall. This is a fast path since it does not involve changing
+ // the material. Some properties however might affect fixed function state (alpha test reference, texture
+ // combiner colors, fixed function material etc.), those need to be applied here.
+
+ const dynamic_array<int>& fixedFunctionProps = shader->GetShaderLabShader()->GetSubShader(subshaderIndex).GetPropsAffectingFF();
+ if (fixedFunctionProps.empty())
+ return; // no props that affect fixed function state, great!
+
+ //@TODO: slow implementation for now just to get this working properly!
+ const MaterialPropertyBlock::Property* curProp = m_CustomProperties->GetPropertiesBegin();
+ const MaterialPropertyBlock::Property* propEnd = m_CustomProperties->GetPropertiesEnd();
+ const float* propBuffer = m_CustomProperties->GetBufferBegin();
+ for (; curProp != propEnd; ++curProp)
+ {
+ if (std::find(fixedFunctionProps.begin(), fixedFunctionProps.end(), curProp->nameIndex) == fixedFunctionProps.end())
+ continue; // this property does not affect fixed function state
+
+ ShaderLab::FastPropertyName name;
+ name.index = curProp->nameIndex;
+ const float* src = &propBuffer[curProp->offset];
+ if (curProp->rows == 1 && curProp->cols == 1)
+ {
+ mat.SetFloat (name, *src);
+ }
+ else if (curProp->rows == 1 && curProp->cols == 4)
+ {
+ mat.SetColor (name, ColorRGBAf(src));
+ }
+ else if (curProp->rows == 4 && curProp->cols == 4)
+ {
+ mat.SetMatrix (name, Matrix4x4f(src));
+ }
+ else
+ {
+ AssertString ("Unknown property dimensions");
+ }
+ }
+}
diff --git a/Runtime/Camera/BaseRenderer.h b/Runtime/Camera/BaseRenderer.h
new file mode 100644
index 0000000..26350fb
--- /dev/null
+++ b/Runtime/Camera/BaseRenderer.h
@@ -0,0 +1,148 @@
+#ifndef BASE_RENDERER_H
+#define BASE_RENDERER_H
+
+#include "Runtime/Math/Vector4.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Geometry/AABB.h"
+#include "Runtime/Modules/ExportModules.h"
+#include "Runtime/Camera/RenderLoops/GlobalLayeringData.h"
+
+namespace Unity { class Material; }
+class ChannelAssigns;
+using namespace Unity;
+class AABB;
+class Matrix4x4f;
+class Vector3f;
+class Quaternionf;
+class MaterialPropertyBlock;
+class Shader;
+class Renderer;
+template<class T> class PPtr;
+
+enum RendererType {
+ kRendererUnknown,
+
+ kRendererMesh,
+ kRendererSkinnedMesh,
+ kRendererCloth,
+ kRendererSprite,
+
+ kRendererParticle,
+ kRendererTrail,
+ kRendererLine,
+ kRendererParticleSystem,
+
+ kRendererIntermediate,
+
+ kRendererTypeCount
+};
+
+struct BatchInstanceData
+{
+ Matrix4x4f xform; // 64
+ Renderer* renderer; // 4
+ int subsetIndex; // 4
+ int xformType; // 4
+ int dummy; // 4 byte padding
+};
+
+struct TransformInfo
+{
+ Matrix4x4f worldMatrix; // 64
+ AABB worldAABB; // 24
+ AABB localAABB; // 24 used by LightManager and Shadows
+ float invScale; // 4
+ TransformType transformType; // 4
+};
+
+// Abstract base class for renderers.
+class EXPORT_COREMODULE BaseRenderer {
+public:
+ BaseRenderer(RendererType type);
+ virtual ~BaseRenderer();
+
+ // The main Render Function. Implement this in order to draw the graphics
+ // When this is called, the correct material and transform have been set up.
+ virtual void Render (int materialIndex, const ChannelAssigns& channels) = 0;
+
+ void GetWorldAABB( AABB& result );
+ const TransformInfo& GetTransformInfo ();
+ void GetLocalAABB( AABB& result );
+
+ virtual void UpdateTransformInfo() = 0;
+
+ virtual void RendererBecameVisible() { m_IsVisibleInScene = true; }
+ virtual void RendererBecameInvisible() { m_IsVisibleInScene = false; }
+ virtual int GetLayer() const = 0;
+
+ virtual float GetSortingFudge () const { return 0.0f; }
+
+ virtual int GetMaterialCount() const = 0;
+ virtual PPtr<Material> GetMaterial(int i) const = 0;
+ virtual int GetSubsetIndex(int i) const { return i; }
+ virtual int GetStaticBatchIndex() const { return 0; }
+ virtual UInt32 GetMeshIDSmall() const { return 0; }
+
+ RendererType GetRendererType() const { return static_cast<RendererType>(m_RendererType); }
+
+ UInt32 GetLayerMask() const { return 1<<GetLayer(); }
+
+ bool GetCastShadows() const { return m_CastShadows; }
+ bool GetReceiveShadows() const { return m_ReceiveShadows; }
+
+ void ApplyCustomProperties(Unity::Material& mat, Shader* shader, int subshaderIndex) const;
+
+ const Vector4f& GetLightmapST() const { return m_LightmapST; }
+ // If the renderer's mesh is batched, UVs were already transformed by lightmapST, so return identity transform.
+ // The static batch index equal to 0 means there is no batched mesh, i.e. the renderer is not batched.
+ const Vector4f GetLightmapSTForRendering () const { return GetStaticBatchIndex() == 0 ? m_LightmapST : Vector4f(1,1,0,0); }
+
+ UInt8 GetLightmapIndex() const { return m_LightmapIndex; }
+ int GetLightmapIndexInt() const;
+
+ void SetLightmapIndexIntNoDirty(int index);
+
+ bool IsLightmappedForRendering() const;
+ bool IsLightmappedForShadows() const;
+
+ #if UNITY_EDITOR
+ float GetScaleInLightmap() const { return m_ScaleInLightmap; }
+ void SetScaleInLightmap(float scale) { m_ScaleInLightmap = scale; }
+ #endif
+
+ void ComputeCustomPropertiesHash();
+ UInt32 GetCustomPropertiesHash() const { return m_CustomPropertiesHash; }
+ const MaterialPropertyBlock* GetCustomProperties() const { return m_CustomProperties; }
+
+
+ GlobalLayeringData GetGlobalLayeringData () const { return m_GlobalLayeringData; }
+ void SetGlobalLayeringData (GlobalLayeringData data) { m_GlobalLayeringData = data; }
+protected:
+ Vector4f m_LightmapST; ///< Lightmap tiling and offset
+ UInt8 m_RendererType; // enum RendererType
+ UInt8 m_LightmapIndex;
+ bool m_CastShadows;
+ bool m_ReceiveShadows;
+ bool m_IsVisibleInScene;
+#if UNITY_EDITOR
+ float m_ScaleInLightmap; ///< A multiplier to object's area used in atlasing
+#endif
+ bool m_TransformDirty;
+ bool m_BoundsDirty;
+ TransformInfo m_TransformInfo;
+ MaterialPropertyBlock* m_CustomProperties;
+ UInt32 m_CustomPropertiesHash;
+ GlobalLayeringData m_GlobalLayeringData;
+};
+
+inline const TransformInfo& BaseRenderer::GetTransformInfo()
+{
+ if (m_TransformDirty || m_BoundsDirty)
+ {
+ UpdateTransformInfo();
+ m_TransformDirty = false;
+ m_BoundsDirty = false;
+ }
+ return m_TransformInfo;
+}
+#endif
diff --git a/Runtime/Camera/Camera.cpp b/Runtime/Camera/Camera.cpp
new file mode 100644
index 0000000..ef5fa4c
--- /dev/null
+++ b/Runtime/Camera/Camera.cpp
@@ -0,0 +1,2340 @@
+#include "UnityPrefix.h"
+#include "Camera.h"
+#include "Runtime/Graphics/GraphicsHelper.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+#include "RenderLoops/RenderLoop.h"
+#include "UnityScene.h"
+#include "SceneSettings.h"
+#include "Runtime/Geometry/Ray.h"
+#include "Culler.h"
+#include "ImageFilters.h"
+#include "Runtime/Shaders/Material.h"
+#include "RenderSettings.h"
+#include "External/shaderlab/Library/properties.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "RenderManager.h"
+#include "Skybox.h"
+#include "Flare.h"
+#include "Light.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Camera/RenderLayers/GUILayer.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "CameraUtil.h"
+#include "CameraCullingParameters.h"
+#include "Runtime/Graphics/CubemapTexture.h"
+#include "Runtime/Graphics/RenderBufferManager.h"
+#include "IntermediateRenderer.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "RenderLoops/ReplacementRenderLoop.h"
+#include "Runtime/Misc/ResourceManager.h"
+#include "Runtime/Misc/PlayerSettings.h"
+#include "Runtime/Graphics/ScreenManager.h"
+#include "Runtime/Shaders/ShaderNameRegistry.h"
+#include "Runtime/Shaders/ShaderKeywords.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Scripting/ScriptingManager.h"
+#include "Configuration/UnityConfigure.h"
+#include "LODGroupManager.h"
+#include "ShadowCulling.h"
+#include "External/Umbra/builds/interface/runtime/umbraQuery.hpp"
+#include "External/Umbra/builds/interface/runtime/umbraTome.hpp"
+#include "Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h"
+#include "Runtime/Graphics/Image.h"
+#include "Runtime/Graphics/RenderSurface.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Interfaces/ITerrainManager.h"
+
+#if UNITY_EDITOR
+#include "Editor/Platform/Interface/EditorWindows.h"
+#include "Editor/Platform/Interface/RepaintController.h"
+#endif
+
+///@TODO: Ensure that we use cullingparameters.renderingPath, otherwise potential inconsistency when switching renderpath after culling.
+RenderingPath CalculateRenderingPath (RenderingPath rp);
+
+using namespace std;
+
+static SHADERPROP(CameraDepthTexture);
+static SHADERPROP(CameraDepthNormalsTexture);
+static SHADERPROP(Reflection);
+
+static ShaderKeyword kKeywordSoftParticles = keywords::Create ("SOFTPARTICLES_ON");
+
+/////***@TODO: Write test for stressing multithreaded breaking when OnWillRenderObjects does nasty things...
+
+
+void Camera::InitializeClass ()
+{
+ REGISTER_MESSAGE_VOID (Camera, kTransformChanged, TransformChanged);
+ RegisterAllowNameConversion (Camera::GetClassStringStatic(), "is ortho graphic", "orthographic");
+}
+
+
+Camera::Camera (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_DirtyProjectionMatrix(true)
+, m_DirtyWorldToCameraMatrix(true)
+, m_DirtyWorldToClipMatrix(true)
+, m_DepthTextureMode(0)
+, m_DepthTexture(NULL)
+, m_DepthNormalsTexture(NULL)
+, m_ClearStencilAfterLightingPass(false)
+#if UNITY_EDITOR
+, m_OnlyRenderIntermediateObjects(false)
+, m_IsSceneCamera(false)
+, m_FilterMode (0)
+, m_AnimateMaterials(false)
+, m_AnimateMaterialsTime(0.0f)
+#endif
+{
+ m_RenderLoop = CreateRenderLoop (*this);
+
+ m_CullingMask.m_Bits = 0xFFFFFFFF;
+ m_EventMask.m_Bits = 0xFFFFFFFF;
+
+ for(int i=0;i<32;i++)
+ m_LayerCullDistances[i] = 0;
+ m_LayerCullSpherical = false;
+ m_SortMode = kSortDefault;
+ m_ImplicitProjectionMatrix = m_ImplicitWorldToCameraMatrix = true;
+ m_ImplicitAspect = true;
+ m_HDR = false;
+ m_UsingHDR = false;
+ m_IsRendering = false;
+
+ m_Velocity = Vector3f::zero;
+ m_LastPosition = Vector3f::zero;
+ m_WorldToCameraMatrix = m_WorldToClipMatrix = m_ProjectionMatrix = Matrix4x4f::identity;
+ m_CurrentTargetTexture = NULL;
+ m_CurrentTargetFace = kCubeFaceUnknown;
+ m_OcclusionCulling = true;
+
+ m_TargetBuffersOriginatedFrom = 0;
+ m_TargetColorBufferCount = 1;
+ ::memset(m_TargetColorBuffer, 0x00, sizeof(m_TargetColorBuffer));
+
+ m_TargetColorBuffer[0] = GetUncheckedGfxDevice().GetBackBufferColorSurface();
+ m_TargetDepthBuffer = GetUncheckedGfxDevice().GetBackBufferDepthSurface();
+
+ m_IntermediateRenderers = UNITY_NEW(IntermediateRenderers, GetMemoryLabel());
+#if UNITY_EDITOR
+ m_OldCameraState.Reset();
+#endif
+}
+
+void Camera::Reset ()
+{
+ Super::Reset();
+
+ m_NormalizedViewPortRect = Rectf (0, 0, 1, 1);
+
+ m_BackGroundColor = ColorRGBA32 (49, 77, 121, 5); // very small alpha to not get "everything glows" by default
+ m_Depth = 0.0F;
+ m_NearClip = 0.3F;
+ m_FarClip = 1000.0F;
+ m_RenderingPath = -1;
+ m_Aspect = 1.0F;
+ m_Orthographic = false;
+ m_HDR = false;
+ m_SortMode = kSortDefault;
+
+ m_OrthographicSize = 5.0F;
+ m_FieldOfView = 60.0F;
+ m_ClearFlags = kSkybox;
+ m_DirtyWorldToCameraMatrix = m_DirtyProjectionMatrix = m_DirtyWorldToClipMatrix = true;
+}
+
+
+
+void Camera::CheckConsistency ()
+{
+ Super::CheckConsistency();
+ m_RenderingPath = clamp(m_RenderingPath, -1, kRenderPathCount-1);
+ if(!m_Orthographic && m_NearClip < 0.01F)
+ m_NearClip = 0.01F;
+ if(m_FarClip < m_NearClip + 0.01F)
+ m_FarClip = m_NearClip + 0.01F;
+}
+
+
+Camera::~Camera ()
+{
+ CleanupDepthTextures ();
+ m_IntermediateRenderers->Clear();
+ UNITY_DELETE(m_IntermediateRenderers, GetMemoryLabel());
+
+ DeleteRenderLoop (m_RenderLoop);
+}
+
+
+void Camera::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad (awakeMode);
+ if ((awakeMode & kDidLoadFromDisk) == 0 && IsAddedToManager ())
+ {
+ GetRenderManager().RemoveCamera (this);
+ GetRenderManager().AddCamera (this);
+ }
+ m_DirtyWorldToCameraMatrix = m_DirtyProjectionMatrix = m_DirtyWorldToClipMatrix = true;
+ WindowSizeHasChanged ();
+ if(m_HDR)
+ DisplayHDRWarnings();
+}
+
+
+void Camera::ClearIntermediateRenderers( size_t startIndex )
+{
+ m_IntermediateRenderers->Clear(startIndex);
+}
+
+void Camera::AddToManager ()
+{
+ GetRenderManager().AddCamera (this);
+ WindowSizeHasChanged ();
+ m_LastPosition = GetComponent (Transform).GetPosition ();
+ m_Velocity = Vector3f (0.0F, 0.0F, 0.0F);
+}
+
+void Camera::TransformChanged()
+{
+ m_DirtyWorldToCameraMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+}
+
+void Camera::RemoveFromManager ()
+{
+ GetRenderManager().RemoveCamera (this);
+}
+
+
+
+template<class TransferFunction>
+void Camera::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ // Note: transfer code for version 1 was just removed. It was around Unity 1.2 times,
+ // and now we're fine with losing project folder compatibility with that.
+ transfer.SetVersion (2);
+
+ TRANSFER_SIMPLE (m_ClearFlags);
+ TRANSFER_SIMPLE (m_BackGroundColor);
+
+ TRANSFER (m_NormalizedViewPortRect);
+ transfer.Transfer (m_NearClip, "near clip plane");
+ transfer.Transfer (m_FarClip, "far clip plane");
+ transfer.Transfer (m_FieldOfView, "field of view", kSimpleEditorMask);
+ transfer.Transfer (m_Orthographic, "orthographic");
+ transfer.Align();
+ transfer.Transfer (m_OrthographicSize, "orthographic size");
+
+ TRANSFER (m_Depth);
+ TRANSFER (m_CullingMask);
+ //TRANSFER (m_EventMask);
+ TRANSFER (m_RenderingPath);
+
+ transfer.Transfer (m_TargetTexture, "m_TargetTexture");
+ TRANSFER (m_HDR);
+ TRANSFER (m_OcclusionCulling);
+}
+
+
+static inline Rectf GetCameraTargetRect (const Camera& camera, bool zeroOrigin)
+{
+ RenderTexture* target = camera.GetTargetTexture();
+ if (target != NULL)
+ return Rectf(0, 0, target->GetWidth(), target->GetHeight());
+
+ RenderSurfaceHandle colorTarget = camera.GetTargetColorBuffer();
+ if(colorTarget.IsValid() && !colorTarget.object->backBuffer)
+ return Rectf(0, 0, colorTarget.object->width, colorTarget.object->height);
+
+ const RenderManager& renderMgr = GetRenderManager();
+ Rectf rect = renderMgr.GetWindowRect();
+
+#if UNITY_EDITOR
+ // In the editor, if we're trying to get rect of a regular camera (visible in hierarchy etc.),
+ // use game view size instead of "whatever editor window was processed last" size.
+ // Otherwise Camera.main.aspect would return aspect of inspector when repainting it, for example.
+ //
+ // Only do this for regular cameras however; keep hidden cameras (scene view, material preview etc.)
+ // using the old behavior.
+ Unity::GameObject* go = camera.GetGameObjectPtr();
+ if (go && (go->GetHideFlags() & Object::kHideAndDontSave) != Object::kHideAndDontSave)
+ {
+ // If the current guiview is a GameView then GetRenderManager().GetWindowRect() is already set up correctly and
+ // we do not need to find first available game view to get a valid rect. Fix for case 517158
+ bool isCurrentGUIViewAGameView = GUIView::GetCurrent() != NULL && GUIView::GetCurrent()->IsGameView();
+ if (!isCurrentGUIViewAGameView)
+ {
+ bool gameViewFocus;
+ Rectf gameViewRect;
+ GetScreenParamsFromGameView(false, false, &gameViewFocus, &gameViewRect, &rect);
+ }
+ }
+#endif
+
+ if (zeroOrigin)
+ rect.x = rect.y = 0.0f;
+ return rect;
+}
+
+
+Rectf Camera::GetCameraRect (bool zeroOrigin) const
+{
+ // Get the screen rect from either the target texture or the viewport we're inside
+ Rectf screenRect = GetCameraTargetRect (*this, zeroOrigin);
+
+ // Now figure out how large this camera is depending on the normalized viewRect.
+ Rectf viewRect = m_NormalizedViewPortRect;
+ viewRect.Scale (screenRect.width, screenRect.height);
+ viewRect.Move (screenRect.x, screenRect.y);
+ viewRect.Clamp (screenRect);
+ return viewRect;
+}
+
+
+void Camera::SetScreenViewportRect (const Rectf& pixelRect)
+{
+ // Get the screen rect from either the target texture or the viewport we're inside
+ // Use zero base the screen rect; all game code assumes that the visible viewport starts at zero.
+ Rectf screenRect = GetCameraTargetRect (*this, true);
+
+ // Now translate from pixel to viewport space
+ Rectf viewRect = pixelRect;
+ viewRect.Move (-screenRect.x, -screenRect.y);
+ if (screenRect.width > 0.0f && screenRect.height > 0.0f)
+ viewRect.Scale (1.0F / screenRect.width, 1.0F / screenRect.height);
+ else
+ viewRect.Reset();
+ SetNormalizedViewportRect(viewRect);
+}
+
+static void InitShaderReplaceData (Shader* replacementShader, const std::string& shaderReplaceTag, ShaderReplaceData& output)
+{
+ // Shader replacement might be passed explicitly (camera.RenderWithShader) OR shader replacement can be setup as camera's state (camera.SetReplacementShader)
+ if( replacementShader != NULL )
+ {
+ output.replacementShader = replacementShader;
+ output.replacementTagID = ShaderLab::GetShaderTagID(shaderReplaceTag);
+ output.replacementTagSet = !shaderReplaceTag.empty();
+ }
+ else
+ {
+ Assert(output.replacementShader == NULL);
+ }
+}
+
+bool Camera::IsValidToRender() const
+{
+ if( m_NormalizedViewPortRect.IsEmpty() )
+ return false;
+ if( m_NormalizedViewPortRect.x >= 1.0F || m_NormalizedViewPortRect.GetRight() <= 0.0F)
+ return false;
+ if( m_NormalizedViewPortRect.y >= 1.0F || m_NormalizedViewPortRect.GetBottom() <= 0.0F)
+ return false;
+
+ if( m_FarClip <= m_NearClip )
+ return false;
+ if( !m_Orthographic )
+ {
+ if( m_NearClip <= 0.0f )
+ return false; // perspective camera needs positive near plane
+ if( Abs(m_FieldOfView) < 1.0e-6f )
+ return false; // field of view has to be non zero
+ }
+ else
+ {
+ if( Abs(m_OrthographicSize) < 1.0e-6f )
+ return false; // orthographic size has to be non zero
+ }
+ return true;
+}
+
+bool Camera::GetUsesScreenForCompositing (bool forceIntoRT) const
+{
+ // If rendering into a texture, don't composite to screen
+ if (forceIntoRT || m_TargetTexture.IsValid() || !m_TargetColorBuffer[0].IsValid() || !m_TargetColorBuffer[0].object->backBuffer)
+ return false;
+
+ #if UNITY_OSX && WEBPLUG && !UNITY_PEPPER
+ // In CoreAnimation plugin, we use frame buffer blit extension for ImageFX with FSAA.
+ // I assume any mac which does core animation supports that, but better safe then sorry :)
+ if (GetScreenManager().IsUsingCoreAnimation() && !gGraphicsCaps.gl.hasFrameBufferBlit )
+ return false;
+ #endif
+
+ // If FSAA is used: composite to screen!
+ if (GetQualitySettings().GetCurrent().antiAliasing > 1 && gGraphicsCaps.hasMultiSample)
+ return true;
+
+ // If camera is part of multi-layer setup (does not clear): composite to screen!
+ // Except if this is a scene view camera; do not composite it to screen because it would break
+ // Image FX + AA + Shadows
+ #if UNITY_EDITOR
+ if (m_IsSceneCamera)
+ return false;
+ #endif
+ if (m_ClearFlags != kSkybox && m_ClearFlags != kSolidColor)
+ return true;
+
+ // Otherwise, it's a clearing camera with no AA used
+ return false;
+}
+
+
+void Camera::SetupRender( int renderFlags )
+{
+ GfxDevice& device = GetGfxDevice();
+
+ // Cache whether we use HDR for rendering.
+ m_UsingHDR = CalculateUsingHDR();
+
+ bool forceIntoRT = CalculateNeedsToRenderIntoRT();
+ int antiAliasing = CalculateAntiAliasingForRT();
+
+ if (renderFlags & kRenderFlagPrepareImageFilters)
+ {
+ // figure out if we need to render into a texture & prepare for that
+ GetRenderLoopImageFilters(*m_RenderLoop).Prepare (forceIntoRT, GetUsingHDR(), antiAliasing);
+ }
+
+ // Set the current target texture to be the one calculated.
+ m_CurrentTargetTexture = NULL;
+ if (!GetUsesScreenForCompositing(forceIntoRT))
+ {
+ ImageFilters& imageFilters = GetRenderLoopImageFilters(*m_RenderLoop);
+
+ // If kFlagSetRenderTargetFinal is set we want to set the current target to the one image filters blitted to.
+ // This is the target transparent objects were rendered to and the lens flare needs to be rendered to as well. (case 443687)
+ m_CurrentTargetTexture = (renderFlags & kRenderFlagSetRenderTargetFinal) ? imageFilters.GetTargetFinal() : imageFilters.GetTargetBeforeOpaque ();
+
+ if(!m_CurrentTargetTexture)
+ m_CurrentTargetTexture = m_TargetTexture;
+ }
+
+ // Compute the viewport rect in the window
+ // This is only used when setting current render texture to NULL,
+ // so that it can restore the viewport.
+ int* viewPortCoords = GetRenderManager().GetCurrentViewPortWriteable();
+ RectfToViewport( GetPhysicalViewportRect(), viewPortCoords );
+
+ if(renderFlags & kRenderFlagSetRenderTarget)
+ {
+ m_CurrentTargetTexture = EnsureRenderTextureIsCreated(m_CurrentTargetTexture);
+
+ CubemapFace curFace = kCubeFaceUnknown;
+ if(m_CurrentTargetTexture && m_CurrentTargetTexture->GetDimension() == kTexDimCUBE)
+ curFace = m_CurrentTargetFace;
+
+ // while we could return const ref (and grab address and use uniformly)
+ // we pass non const pointer to SetActive (because surfaces can be reset internally)
+ // so create local handle copy if we draw to real texture
+ RenderSurfaceHandle rtcolor = m_CurrentTargetTexture ? m_CurrentTargetTexture->GetColorSurfaceHandle() : RenderSurfaceHandle();
+
+ RenderSurfaceHandle* color = m_CurrentTargetTexture ? &rtcolor : m_TargetColorBuffer;
+ RenderSurfaceHandle depth = m_CurrentTargetTexture ? m_CurrentTargetTexture->GetDepthSurfaceHandle() : m_TargetDepthBuffer;
+ int count = m_CurrentTargetTexture ? 1 : m_TargetColorBufferCount;
+
+ if(!m_CurrentTargetTexture)
+ m_CurrentTargetTexture = m_TargetBuffersOriginatedFrom;
+
+ RenderTexture::SetActive(count, color, depth, m_CurrentTargetTexture, 0, curFace, RenderTexture::kFlagDontSetViewport);
+
+ int viewcoord[4];
+ if(color[0].IsValid() && color[0].object->backBuffer)
+ ::memcpy(viewcoord, viewPortCoords, 4*sizeof(int));
+ else
+ RectfToViewport(GetRenderRectangle(), viewcoord);
+
+ FlipScreenRectIfNeeded(device, viewcoord);
+ device.SetViewport(viewcoord[0], viewcoord[1], viewcoord[2], viewcoord[3]);
+ }
+
+ device.SetProjectionMatrix (GetProjectionMatrix());
+ device.SetViewMatrix( GetWorldToCameraMatrix().GetPtr() );
+ SetCameraShaderProps();
+}
+
+
+void Camera::SetCameraShaderProps()
+{
+ GfxDevice& device = GetGfxDevice();
+ BuiltinShaderParamValues& params = device.GetBuiltinParamValues();
+
+ Transform &tc = GetComponent (Transform);
+
+ Vector3f pos = tc.GetPosition ();
+ params.SetVectorParam(kShaderVecWorldSpaceCameraPos, Vector4f(pos, 0.0f));
+
+
+ Matrix4x4f temp;
+
+ // World to camera matrix
+ params.SetMatrixParam(kShaderMatWorldToCamera, tc.GetWorldToLocalMatrixNoScale());
+
+ // Camera to world matrix
+ temp = tc.GetLocalToWorldMatrixNoScale ();
+ params.SetMatrixParam(kShaderMatCameraToWorld, temp);
+
+ // Get the matrix to use for cubemap reflections.
+ // It's camera to world matrix; rotation only, and mirrored on Y.
+ temp.GetPtr()[12] = temp.GetPtr()[13] = temp.GetPtr()[14] = 0; // clear translation
+ Matrix4x4f invertY;
+ invertY.SetScale(Vector3f (1,-1,1));
+ Matrix4x4f reflMat;
+ MultiplyMatrices4x4 (&temp, &invertY, &reflMat);
+ ShaderLab::g_GlobalProperties->SetValueProp (kSLPropReflection, 16, reflMat.GetPtr());
+
+ // Camera clipping planes
+ SetClippingPlaneShaderProps();
+
+ // Setup time & misc properties
+
+ const TimeManager& timeMgr = GetTimeManager();
+ float time;
+ if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion3_5_a1))
+ time = timeMgr.GetTimeSinceLevelLoad ();
+ else
+ time = timeMgr.GetCurTime ();
+
+#if UNITY_EDITOR
+ if (m_AnimateMaterials)
+ time = m_AnimateMaterialsTime;
+#endif
+
+ const float kMinDT = 0.005f;
+ const float kMaxDT = 0.2f;
+ const float deltaTime = clamp(timeMgr.GetDeltaTime(), kMinDT, kMaxDT);
+ const float smoothDeltaTime = clamp(timeMgr.GetSmoothDeltaTime(), kMinDT, kMaxDT);
+
+ // The 0.05 in kShaderVecTime is a typo, but can't change it now. There are water shaders out there that
+ // use exactly .x component :(
+
+ params.SetVectorParam(kShaderVecTime, Vector4f(0.05f*time, time, 2.0f*time, 3.0f*time));
+ params.SetVectorParam(kShaderVecSinTime, Vector4f(sinf(0.125f*time), sinf(0.25f*time), sinf(0.5f*time), sinf(time)));
+ params.SetVectorParam(kShaderVecCosTime, Vector4f(cosf(0.125f*time), cosf(0.25f*time), cosf(0.5f*time), cosf(time)));
+ params.SetVectorParam(kShaderVecPiTime, Vector4f(fmodf(time,kPI), fmodf(2.0f*time, kPI), fmodf(3.0f*time, kPI), fmodf(4.0f*time, kPI)));
+ params.SetVectorParam(kShaderVecDeltaTime, Vector4f(deltaTime, 1.0f/deltaTime, smoothDeltaTime, 1.0f/smoothDeltaTime));
+
+ float projNear = GetProjectionNear();
+ float projFar = GetProjectionFar();
+ params.SetVectorParam(kShaderVecProjectionParams, Vector4f(device.GetInvertProjectionMatrix() ? -1.0f : 1.0f, projNear, projFar, 1.0 / projFar));
+
+ Rectf view = GetScreenViewportRect();
+ params.SetVectorParam(kShaderVecScreenParams, Vector4f(view.width, view.height, 1.0f+1.0f/view.width, 1.0f+1.0f/view.height));
+
+ // From http://www.humus.name/temp/Linearize%20depth.txt
+ // But as depth component textures on OpenGL always return in 0..1 range (as in D3D), we have to use
+ // the same constants for both D3D and OpenGL here.
+ double zc0, zc1;
+ // OpenGL would be this:
+ // zc0 = (1.0 - projFar / projNear) / 2.0;
+ // zc1 = (1.0 + projFar / projNear) / 2.0;
+ // D3D is this:
+ zc0 = 1.0 - projFar / projNear;
+ zc1 = projFar / projNear;
+ params.SetVectorParam(kShaderVecZBufferParams, Vector4f(zc0, zc1, zc0/projFar, zc1/projFar));
+
+ // make sure we have a gamma correct grey value available
+ float correctGreyValue = GammaToActiveColorSpace(0.5f);
+ params.SetVectorParam(kShaderVecColorSpaceGrey, Vector4f(correctGreyValue,correctGreyValue,correctGreyValue,0.5f));
+}
+
+PROFILER_INFORMATION(gCameraClearProfile, "Clear", kProfilerRender)
+
+static const ColorRGBAf ConvertColorToActiveColorSpace(const ColorRGBAf& color)
+{
+#if UNITY_PS3
+ return color;
+#else
+ return GammaToActiveColorSpace(color);
+#endif
+}
+
+
+static void ClearFramebuffer(GfxClearFlags gfxClearFlags, Rectf rect, ColorRGBAf const& color)
+{
+ PROFILER_AUTO_GFX(gCameraClearProfile, NULL);
+ const float depth = 1.0f;
+ const int stencil = 0;
+
+ GfxDevice& device = GetGfxDevice();
+ // If we're rendering into a temporary texture, we always have (0,0) in the bottom-left corner
+ // No matter what the view coords say.
+ int si[4];
+ RectfToViewport( rect, si );
+ FlipScreenRectIfNeeded( device, si );
+ device.SetScissorRect( si[0], si[1], si[2], si[3] );
+
+ // seems like the best place to time clear
+ ABSOLUTE_TIME clearStart = START_TIME;
+#if UNITY_OSX && WEBPLUG && !UNITY_PEPPER
+ if (gGraphicsCaps.gl.mustWriteToDepthBufferBeforeClear && gfxClearFlags & kGfxClearDepth)
+ {
+ // Mac OS X 10.7.2 introduced a bug in the NVidia GPU drivers, where the depth buffer would
+ // contain garbage if used in a CoreAnimation content with enabled stencil buffer, if the
+ // depth buffer is not written to at least once between buffer clears. This breaks scenes which
+ // only read but never write to the depth buffer (such as a scene using only particle shaders).
+ // So we render an invisble, depth only triangle in that case.
+ DeviceMVPMatricesState preserveMVP;
+ LoadFullScreenOrthoMatrix();
+
+ static Material* s_UpdateDepthBufferMaterial = NULL;
+ if (!s_UpdateDepthBufferMaterial)
+ {
+ const char* kUpdateDepthBufferShader =
+ "Shader \"Hidden/UpdateDepthBuffer\" {\n"
+ "SubShader { Pass {\n"
+ " ZTest Always Cull Off Fog { Mode Off } ColorMask 0\n"
+ "}}}";
+ s_UpdateDepthBufferMaterial = Material::CreateMaterial (kUpdateDepthBufferShader, Object::kHideAndDontSave);
+ }
+
+ s_UpdateDepthBufferMaterial->SetPass (0);
+ device.ImmediateBegin (kPrimitiveTriangles);
+ device.ImmediateVertex (0.0f, 0.0f, 0.1f);
+ device.ImmediateVertex (0.0f, 0.1f, 0.1f);
+ device.ImmediateVertex (0.1f, 0.1f, 0.1f);
+ device.ImmediateEnd ();
+ }
+#endif
+ GraphicsHelper::Clear (gfxClearFlags, color.GetPtr(), depth, stencil);
+ GPU_TIMESTAMP();
+ GetGfxDevice().GetFrameStats().AddClear(ELAPSED_TIME(clearStart));
+
+ device.DisableScissor();
+}
+
+
+static void ClearFramebuffer(Camera::ClearMode clearMode, Rectf rect, ColorRGBAf const& color, bool hasSkybox)
+{
+ GfxClearFlags gfxClearFlags = kGfxClearAll;
+ switch (clearMode)
+ {
+ case Camera::kDontClear:
+ return;
+ case Camera::kDepthOnly:
+ gfxClearFlags = kGfxClearDepthStencil;
+ break;
+ case Camera::kSolidColor:
+ gfxClearFlags = kGfxClearAll;
+ break;
+ case Camera::kSkybox:
+ gfxClearFlags = hasSkybox ? kGfxClearDepthStencil : kGfxClearAll;
+ break;
+ }
+ ClearFramebuffer(gfxClearFlags, rect, color);
+}
+
+
+void Camera::Clear()
+{
+ //Do not need to convert background color to correct space as this is done in gamma space always.
+ ClearFramebuffer(GetClearFlags(), GetRenderRectangle(), m_BackGroundColor, GetSkyboxMaterial() != NULL);
+ RenderSkybox();
+}
+
+void Camera::ClearNoSkybox(bool noDepth)
+{
+ ClearMode clearMode = GetClearFlags();
+ UInt32 flags = kGfxClearAll;
+ switch (clearMode)
+ {
+ case Camera::kDontClear: flags = 0; break;
+ case Camera::kDepthOnly: flags = kGfxClearDepthStencil; break;
+ case Camera::kSolidColor: flags = kGfxClearAll; break;
+ case Camera::kSkybox: flags = kGfxClearAll; break;
+ }
+ if (noDepth)
+ flags &= ~kGfxClearDepthStencil;
+ if (flags == 0)
+ return;
+
+ ClearFramebuffer((GfxClearFlags)flags, GetRenderRectangle(), ConvertColorToActiveColorSpace(m_BackGroundColor));
+}
+
+void Camera::RenderSkybox()
+{
+ if (m_ClearFlags != kSkybox)
+ return;
+
+ Material* skybox = GetSkyboxMaterial();
+ if (!skybox)
+ return;
+
+ Skybox::RenderSkybox (skybox, *this);
+}
+
+Material *Camera::GetSkyboxMaterial () const
+{
+ Skybox *sb = QueryComponent (Skybox);
+ if (sb && sb->GetEnabled() && sb->GetMaterial())
+ return sb->GetMaterial();
+ else
+ return GetRenderSettings().GetSkyboxMaterial();
+}
+
+Rectf Camera::GetRenderRectangle() const
+{
+ if( m_CurrentTargetTexture && m_CurrentTargetTexture != (RenderTexture*)m_TargetTexture )
+ {
+ return Rectf (0, 0, m_CurrentTargetTexture->GetWidth(), m_CurrentTargetTexture->GetHeight());
+ }
+ else
+ {
+ return GetPhysicalViewportRect();
+ }
+}
+
+PROFILER_INFORMATION(gCameraRenderProfile, "Camera.Render", kProfilerRender)
+PROFILER_INFORMATION(gCameraRenderToCubemapProfile, "Camera.RenderToCubemap", kProfilerRender)
+PROFILER_INFORMATION(gCameraCullProfile, "Culling", kProfilerRender)
+PROFILER_INFORMATION(gCameraDrawProfile, "Drawing", kProfilerRender)
+PROFILER_INFORMATION(gCameraDepthTextureProfile, "UpdateDepthTexture", kProfilerRender)
+PROFILER_INFORMATION(gCameraDepthNormalsTextureProfile, "UpdateDepthNormalsTexture", kProfilerRender)
+
+void Camera::CalculateFrustumPlanes(Plane frustum[kPlaneFrustumNum], const Matrix4x4f& overrideWorldToClip, float overrideFarPlane, float& outBaseFarDistance, bool implicitNearFar) const
+{
+ ExtractProjectionPlanes (overrideWorldToClip, frustum);
+
+ Plane& nearPlane = frustum[kPlaneFrustumNear];
+ Plane& farPlane = frustum[kPlaneFrustumFar];
+
+ if (IsImplicitWorldToCameraMatrix() || implicitNearFar)
+ {
+ // Extracted near and far planes may be unsuitable for culling.
+ // E.g. oblique near plane for water refraction busts both planes.
+ // Also very large far/near ratio causes precision problems.
+ // Instead we calculate the planes from our position/direction.
+
+ Matrix4x4f cam2world = GetCameraToWorldMatrix();
+ Vector3f eyePos = cam2world.GetPosition();
+ Vector3f viewDir = -NormalizeSafe(cam2world.GetAxisZ());
+
+ nearPlane.SetNormalAndPosition(viewDir, eyePos);
+ nearPlane.distance -= m_NearClip;
+
+ farPlane.SetNormalAndPosition(-viewDir, eyePos);
+ outBaseFarDistance = farPlane.distance;
+ farPlane.distance += overrideFarPlane;
+ }
+ else
+ outBaseFarDistance = farPlane.distance - overrideFarPlane;
+}
+
+void Camera::CalculateCullingParameters(CullingParameters& cullingParameters) const
+{
+ Plane frustum[kPlaneFrustumNum];
+ float baseFarDistance;
+
+ Matrix4x4f worldToClipMatrix = GetWorldToClipMatrix();
+ cullingParameters.worldToClipMatrix = worldToClipMatrix;
+ cullingParameters.position = GetPosition();
+
+ CalculateFrustumPlanes(frustum, worldToClipMatrix, m_FarClip, baseFarDistance, false);
+ CalculateCustomCullingParameters(cullingParameters, frustum, kPlaneFrustumNum);
+
+ if (m_LayerCullSpherical)
+ {
+ std::copy(m_LayerCullDistances, m_LayerCullDistances + kNumLayers, cullingParameters.layerFarCullDistances);
+ cullingParameters.layerCull = CullingParameters::kLayerCullSpherical;
+ }
+ else
+ {
+ CalculateFarCullDistances(cullingParameters.layerFarCullDistances, baseFarDistance);
+ cullingParameters.layerCull = CullingParameters::kLayerCullPlanar;
+ }
+}
+
+void Camera::CalculateCustomCullingParameters(CullingParameters& cullingParameters, const Plane* planes, int planeCount) const
+{
+ cullingParameters.lodPosition = GetPosition();
+ cullingParameters.lodFieldOfView = m_FieldOfView;
+ cullingParameters.orthoSize = m_OrthographicSize;
+ cullingParameters.cameraPixelHeight = int(GetPhysicalViewportRect().height);
+
+ // Shadow code handles per-layer cull distances itself
+
+ Assert(planeCount <= CullingParameters::kMaxPlanes);
+ for (int i = 0; i < planeCount; i++)
+ cullingParameters.cullingPlanes[i] = planes[i];
+ cullingParameters.cullingPlaneCount = planeCount;
+ cullingParameters.layerCull = CullingParameters::kLayerCullNone;
+ cullingParameters.isOrthographic = GetOrthographic();
+ cullingParameters.cullingMask = m_CullingMask.m_Bits;
+
+ Matrix4x4f worldToClipMatrix = GetWorldToClipMatrix();
+ cullingParameters.worldToClipMatrix = worldToClipMatrix;
+ cullingParameters.position = GetPosition();
+}
+
+
+void Camera::StandaloneCull (Shader* replacementShader, const std::string& replacementTag, CullResults& results)
+{
+ CameraCullingParameters parameters (*this, kCullFlagNeedsLighting | kCullFlagForceEvenIfCameraIsNotActive);
+ if (GetUseOcclusionCulling())
+ parameters.cullFlag |= kCullFlagOcclusionCull;
+
+ InitShaderReplaceData(replacementShader, replacementTag, parameters.explicitShaderReplace);
+
+ CustomCull(parameters, results);
+}
+
+void Camera::Cull (CullResults& results)
+{
+ CameraCullingParameters parameters (*this, kCullFlagNeedsLighting);
+ if (GetUseOcclusionCulling())
+ parameters.cullFlag |= kCullFlagOcclusionCull;
+
+ CustomCull(parameters, results);
+}
+
+static bool HasValidUmbraShadowCullingData (const Umbra::Visibility* umbraVisibility, const Umbra::Tome* tome)
+{
+ // Exact visibility is useful for pixel lights only, so check rendering path.
+ // Only Portal culling mode support shadow culling
+
+ return umbraVisibility != NULL && tome != NULL;
+}
+
+bool static IsCullPerObjectLightsNeeded(SceneCullingParameters& sceneCullParameters, RenderingPath renderPath)
+{
+ if (renderPath != kRenderPathPrePass)
+ return true;
+
+ // Check whether there are objects/shaders that are not handled by deferred, but are rendered with forward path
+ for (int i=0; i<kVisibleListCount; i++)
+ {
+ RendererCullData& cullData = sceneCullParameters.renderers[i];
+ for( size_t i = 0; i < cullData.rendererCount; ++i )
+ {
+ BaseRenderer* r = cullData.nodes[i].renderer;
+
+ //Fix for case 570036. TODO: Look if we can catch the null Renderer case sooner
+ if (!r)
+ continue;
+
+ const int matCount = r->GetMaterialCount();
+ for (int mi = 0; mi < matCount; ++mi)
+ {
+ Material* mat = r->GetMaterial (mi);
+ // It is possible for this to be NULL if we have a missing reference.
+ if (mat)
+ {
+ Shader* shader = mat->GetShader();
+ int ss = shader->GetShaderLabShader()->GetDefaultSubshaderIndex (kRenderPathExtPrePass);
+ if (ss == -1)
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+////@TODO: Find a better name for this function
+
+#include "Runtime/Graphics/LightmapSettings.h"
+
+void Camera::PrepareSceneCullingParameters (const CameraCullingParameters& parameters, RenderingPath renderPath, CullResults& results)
+{
+ UmbraTomeData tomeData;
+ if ((parameters.cullFlag & kCullFlagOcclusionCull) != 0)
+ tomeData = GetScene().GetUmbraTome();
+
+ SceneCullingParameters& sceneCullParameters = results.sceneCullParameters;
+
+ if (parameters.cullingCamera->GetRenderImmediateObjects())
+ {
+ IntermediateRenderers& cameraIntermediate = parameters.cullingCamera->GetIntermediateRenderers();
+ sceneCullParameters.renderers[kCameraIntermediate].bounds = cameraIntermediate.GetBoundingBoxes();
+ sceneCullParameters.renderers[kCameraIntermediate].nodes = cameraIntermediate.GetSceneNodes();
+ sceneCullParameters.renderers[kCameraIntermediate].rendererCount = cameraIntermediate.GetRendererCount();
+ }
+ else
+ {
+ sceneCullParameters.renderers[kStaticRenderers].bounds = GetScene().GetStaticBoundingBoxes();
+ sceneCullParameters.renderers[kStaticRenderers].nodes = GetScene().GetStaticSceneNodes();
+ sceneCullParameters.renderers[kStaticRenderers].rendererCount = GetScene().GetStaticObjectCount();
+
+ sceneCullParameters.renderers[kDynamicRenderer].bounds = GetScene().GetDynamicBoundingBoxes();
+ sceneCullParameters.renderers[kDynamicRenderer].nodes = GetScene().GetDynamicSceneNodes();
+ sceneCullParameters.renderers[kDynamicRenderer].rendererCount = GetScene().GetDynamicObjectCount();
+
+ IntermediateRenderers& sceneIntermediate = GetScene().GetIntermediateRenderers();
+ sceneCullParameters.renderers[kSceneIntermediate].bounds = sceneIntermediate.GetBoundingBoxes();
+ sceneCullParameters.renderers[kSceneIntermediate].nodes = sceneIntermediate.GetSceneNodes();
+ sceneCullParameters.renderers[kSceneIntermediate].rendererCount = sceneIntermediate.GetRendererCount();
+
+ IntermediateRenderers& cameraIntermediate = parameters.cullingCamera->GetIntermediateRenderers();
+ sceneCullParameters.renderers[kCameraIntermediate].bounds = cameraIntermediate.GetBoundingBoxes();
+ sceneCullParameters.renderers[kCameraIntermediate].nodes = cameraIntermediate.GetSceneNodes();
+ sceneCullParameters.renderers[kCameraIntermediate].rendererCount = cameraIntermediate.GetRendererCount();
+
+#if ENABLE_TERRAIN
+ ITerrainManager* terrainManager = GetITerrainManager();
+ if (terrainManager != NULL)
+ {
+ terrainManager->CollectTreeRenderers(results.treeSceneNodes, results.treeBoundingBoxes);
+ }
+ sceneCullParameters.renderers[kTreeRenderer].bounds = results.treeBoundingBoxes.data();
+ sceneCullParameters.renderers[kTreeRenderer].nodes = results.treeSceneNodes.data();
+ sceneCullParameters.renderers[kTreeRenderer].rendererCount = results.treeBoundingBoxes.size();
+#endif
+
+ }
+
+ // Prepare cull results and allocate all culling memory
+ results.Init(tomeData, sceneCullParameters.renderers);
+
+ parameters.cullingCamera->CalculateCullingParameters(sceneCullParameters);
+
+ sceneCullParameters.useOcclusionCulling = tomeData.HasTome();
+ sceneCullParameters.sceneVisbilityForShadowCulling = &results.sceneCullingOutput;
+ sceneCullParameters.umbraDebugRenderer = parameters.umbraDebugRenderer;
+ sceneCullParameters.umbraDebugFlags = parameters.umbraDebugFlags;
+ sceneCullParameters.umbraTome = tomeData;
+ sceneCullParameters.umbraQuery = GetScene().GetUmbraQuery();
+#if UNITY_EDITOR
+ sceneCullParameters.filterMode = (CullFiltering)parameters.cullingCamera->m_FilterMode;
+#endif
+
+ // shadow culling is oly supported with the latest version of umbra (not with legacy umbra runtime)
+ sceneCullParameters.useShadowCasterCulling = sceneCullParameters.useOcclusionCulling && tomeData.tome != NULL;
+ sceneCullParameters.useLightOcclusionCulling = sceneCullParameters.useShadowCasterCulling;
+ sceneCullParameters.cullLights = parameters.cullFlag & kCullFlagNeedsLighting;
+ sceneCullParameters.excludeLightmappedShadowCasters = (renderPath == kRenderPathForward) ? !GetLightmapSettings().GetUseDualLightmapsInForward() : false;
+ sceneCullParameters.cullPerObjectLights = IsCullPerObjectLightsNeeded(sceneCullParameters, renderPath);
+ sceneCullParameters.renderPath = renderPath;
+
+ // Prepare LOD data
+ LODGroupManager& lodGroupManager = GetLODGroupManager();
+ size_t lodGroupCount = lodGroupManager.GetLODGroupCount();
+ results.lodMasks.resize_uninitialized(lodGroupCount);
+ results.lodFades.resize_uninitialized(lodGroupCount);
+ lodGroupManager.CalculateLODMasks(sceneCullParameters, results.lodMasks.begin(), results.lodFades.begin());
+
+ sceneCullParameters.lodMasks = results.lodMasks.begin();
+ sceneCullParameters.lodGroupCount = results.lodMasks.size();
+}
+
+
+void Camera::CustomCull (const CameraCullingParameters& parameters, CullResults& results)
+{
+ Assert(results.sceneCullingOutput.umbraVisibility == NULL);
+
+ PROFILER_AUTO(gCameraCullProfile, this)
+
+ // if camera's viewport rect is empty or invalid, do nothing
+ if( !IsValidToRender() )
+ {
+ return;
+ }
+
+ // Send cull message to game object
+ SendMessage (kPreCull);
+
+ // OnPreCull message might disable the camera!
+ // So we check one last time.
+ bool enabledAndActive = IsActive() && GetEnabled();
+ if (!enabledAndActive && (parameters.cullFlag & kCullFlagForceEvenIfCameraIsNotActive) == 0)
+ return;
+
+#if ENABLE_TERRAIN
+ UInt32 cullingMask = m_CullingMask.m_Bits;
+ ITerrainManager* terrainManager = GetITerrainManager();
+ if (cullingMask != 0 && terrainManager != NULL && !GetRenderImmediateObjects())
+ {
+ // Pass culllingParameters instead
+ terrainManager->CullAllTerrains(cullingMask);
+ }
+#endif // ENABLE_TERRAIN
+
+ // Update scene dirty bounds
+ GetScene().RecalculateDirtyBounds ();
+
+ // Calculate parameters after OnPreCull (case 401765)
+ // In case the user moves the camera in OnPreCull
+ PrepareSceneCullingParameters(parameters, CalculateRenderingPath(), results);
+
+ // Setup shader replacement
+ if (parameters.explicitShaderReplace.replacementShader != NULL)
+ results.shaderReplaceData = parameters.explicitShaderReplace;
+ else
+ InitShaderReplaceData (m_ReplacementShader, m_ReplacementTag, results.shaderReplaceData);
+
+ // Prepare light culling information
+ if (results.sceneCullParameters.cullLights)
+ {
+ ShadowCullData& shadowCullData = *UNITY_NEW(ShadowCullData, kMemTempAlloc);
+ SetupShadowCullData(*parameters.cullingCamera, parameters.cullingCamera->GetPosition(), results.shaderReplaceData, &results.sceneCullParameters, shadowCullData);
+
+ if (HasValidUmbraShadowCullingData (results.sceneCullingOutput.umbraVisibility, results.sceneCullParameters.umbraTome.tome))
+ shadowCullData.visbilityForShadowCulling = &results.sceneCullingOutput;
+
+ results.shadowCullData = &shadowCullData;
+ }
+
+ // Cull
+ if (GetRenderImmediateObjects())
+ CullIntermediateRenderersOnly(results.sceneCullParameters, results);
+ else
+ CullScene (results.sceneCullParameters, results);
+}
+
+
+void Camera::CalculateFarCullDistances (float* farCullDistances, float baseFarDistance) const
+{
+ // baseFarDistance is the distance of the far plane shifted to the camera position
+ // This is so layer distances work properly even if the far distance is very large
+ for(int i=0; i<kNumLayers; i++)
+ {
+ if(m_LayerCullDistances[i])
+ farCullDistances[i] = baseFarDistance + m_LayerCullDistances[i];
+ else
+ farCullDistances[i] = baseFarDistance + m_FarClip;
+ }
+}
+
+void Camera::DoRenderPostLayers ()
+{
+ FlareLayer* flareLayer = QueryComponent(FlareLayer);
+ if (flareLayer && flareLayer->GetEnabled())
+ {
+ GetFlareManager().RenderFlares();
+ }
+ GetRenderManager().InvokeOnRenderObjectCallbacks ();
+}
+
+void Camera::DoRenderGUILayer()
+{
+ GUILayer* guiLayer = QueryComponent(GUILayer);
+ if( guiLayer && guiLayer->GetEnabled())
+ guiLayer->RenderGUILayer ();
+}
+
+void Camera::DoRender (CullResults& cullResults, PerformRenderFunction* customRender, int renderFlags)
+{
+ if (!IsValidToRender())
+ return;
+
+ PROFILER_AUTO_GFX(gCameraDrawProfile, this)
+
+ // Shader replacement might be passed explicitly (camera.RenderWithShader), in which case we don't
+ // send Pre/Post render events, and don't render image effects.
+ // OR shader replacement can be setup as camera's state (camera.SetReplacementShader), in which case
+ // camera functions as usually, just with shaders replaced.
+ bool preAndPostRenderCallbacks = (renderFlags & kRenderFlagExplicitShaderReplace) == 0;
+
+ if (preAndPostRenderCallbacks)
+ SendMessage (kPreRender);
+
+
+ RenderingPath renderPath = (RenderingPath)cullResults.sceneCullParameters.renderPath;
+
+ // Render the culled contents
+ if( customRender )
+ customRender (*this, *m_RenderLoop, cullResults);
+ else if (cullResults.shaderReplaceData.replacementShader != NULL)
+ {
+ bool useLitReplace = IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_1_a1);
+
+ if (useLitReplace)
+ {
+ DoRenderLoop (*m_RenderLoop, renderPath, cullResults, GetRenderImmediateObjects ());
+ }
+ else
+ {
+ RenderTexture* rt = RenderTexture::GetActive();
+ if(rt)
+ GetGfxDevice().SetSRGBWrite(rt->GetSRGBReadWrite());
+ else
+ GetGfxDevice().SetSRGBWrite(GetActiveColorSpace()==kLinearColorSpace);
+
+ ClearFramebuffer(GetClearFlags(),
+ GetRenderRectangle(),
+ ConvertColorToActiveColorSpace(m_BackGroundColor),
+ (cullResults.shaderReplaceData.replacementShader) ? NULL : GetSkyboxMaterial());
+ GetGfxDevice().SetSRGBWrite(false);
+
+ RenderSceneShaderReplacement (cullResults.nodes, cullResults.shaderReplaceData);
+ }
+ }
+ else
+ {
+ DoRenderLoop (*m_RenderLoop, renderPath, cullResults, GetRenderImmediateObjects ());
+ }
+
+ if (preAndPostRenderCallbacks)
+ SendMessage (kPostRender);
+
+ // The last renderer _might_ have toggled the back facing mode (to deal with mirrored geometry), so we reset this
+ // in order to make the back facing well-defined.
+ GetGfxDevice().SetNormalizationBackface( kNormalizationDisabled, false );
+
+}
+
+
+RenderingPath Camera::CalculateRenderingPath () const
+{
+ // Get rendering path from builds settings or per-camera params
+ RenderingPath rp = (m_RenderingPath==-1) ?
+ GetPlayerSettings().GetRenderingPathRuntime() :
+ static_cast<RenderingPath>(m_RenderingPath);
+
+ // Figure out what we can support on this hardware
+ if (rp == kRenderPathPrePass)
+ {
+ bool canDoPrePass =
+ gGraphicsCaps.hasPrePassRenderLoop && // basic GPU support
+ !m_Orthographic && // can't be ortho
+ RenderTexture::IsEnabled(); // render textures not disabled right now
+ if (!canDoPrePass)
+ rp = kRenderPathForward;
+ }
+ if (rp == kRenderPathForward)
+ {
+ bool canDoForwardShader = (gGraphicsCaps.shaderCaps >= kShaderLevel2);
+ if (!canDoForwardShader)
+ rp = kRenderPathVertex;
+ }
+ return rp;
+}
+
+bool Camera::CalculateNeedsToRenderIntoRT() const
+{
+ // Deferred needs to render into RT
+ if (CalculateRenderingPath() == kRenderPathPrePass)
+ return true;
+
+ // If we have image filters between opaque & transparent, but no AA: render into RT;
+ // much easier to share depth buffer between passes
+ const bool aa = gGraphicsCaps.hasMultiSample && GetQualitySettings().GetCurrent().antiAliasing > 1;
+ if (!aa && GetRenderLoopImageFilters(*m_RenderLoop).HasAfterOpaqueFilters())
+ return true;
+
+ return false;
+}
+
+int Camera::CalculateAntiAliasingForRT() const
+{
+ // Don't use MSAA for image effects if we're rendering to the back buffer
+ // Maybe we should find a way to enable this if people really want it?
+ // Previously there were no MSAA RTs so this wasn't an option
+ if (!m_TargetTexture)
+ return 1;
+
+ if (!gGraphicsCaps.hasMultiSample)
+ return 1;
+
+ // Deferred is not compatible with MSAA
+ if (CalculateRenderingPath() == kRenderPathPrePass)
+ return 1;
+
+ return m_TargetTexture->GetAntiAliasing();
+}
+
+
+void StoreRenderState (CameraRenderOldState& state)
+{
+ GfxDevice& device = GetGfxDevice();
+ device.GetViewport(state.viewport);
+
+ state.activeRT = RenderTexture::GetActive();
+ state.camera = GetCurrentCameraPtr ();
+
+ CopyMatrix(device.GetViewMatrix(), state.matView);
+ CopyMatrix(device.GetWorldMatrix(), state.matWorld);
+ CopyMatrix(device.GetProjectionMatrix(), state.matProj.GetPtr());
+}
+
+void RestoreRenderState (CameraRenderOldState& state)
+{
+ GfxDevice& device = GetGfxDevice();
+ Camera* oldCamera = state.camera;
+ GetRenderManager ().SetCurrentCamera (oldCamera);
+
+ // We should not pass "prepare image effects" flag here, because we're restoring previous render texture
+ // ourselves. I'm not sure if we should even call DoSetup on the camera; can't figure it out right now.
+ if (oldCamera)
+ oldCamera->SetupRender ();
+
+ RenderTexture::SetActive(state.activeRT);
+ device.SetViewport(state.viewport[0], state.viewport[1], state.viewport[2], state.viewport[3]);
+ device.SetViewMatrix(state.matView);
+ device.SetWorldMatrix(state.matWorld);
+ device.SetProjectionMatrix(state.matProj);
+ SetClippingPlaneShaderProps();
+}
+
+
+void Camera::StandaloneRender( UInt32 renderFlags, Shader* replacementShader, const std::string& replacementTag )
+{
+ PROFILER_AUTO_GFX(gCameraRenderProfile, this)
+
+ renderFlags |= kRenderFlagStandalone;
+
+ RenderManager::UpdateAllRenderers();
+
+ CameraRenderOldState state;
+ if( !(renderFlags & kRenderFlagDontRestoreRenderState) )
+ StoreRenderState(state);
+
+ GetRenderManager().SetCurrentCamera (this);
+ WindowSizeHasChanged ();
+
+ CullResults cullResults;
+
+ StandaloneCull(replacementShader, replacementTag, cullResults);
+
+ // We may need BeginFrame() if we're called from script outside rendering loop (case 464376)
+ AutoGfxDeviceBeginEndFrame frame;
+ if( !frame.GetSuccess() )
+ return;
+
+ // Shader replacement might be passed explicitly (camera.RenderWithShader), in which case we don't
+ // send Pre/Post render events, and don't render image effects.
+ // OR shader replacement can be setup as camera's state (camera.SetReplacementShader), in which case
+ // camera functions as usually, just with shaders replaced.
+ if (replacementShader != NULL)
+ renderFlags |= kRenderFlagExplicitShaderReplace;
+
+ // Render this camera
+ Render( cullResults, renderFlags );
+
+ if( !(renderFlags & kRenderFlagDontRestoreRenderState) )
+ RestoreRenderState(state);
+}
+
+
+static const Vector3f kCubemapOrthoBases[6*3] = {
+ Vector3f( 0, 0,-1), Vector3f( 0,-1, 0), Vector3f(-1, 0, 0),
+ Vector3f( 0, 0, 1), Vector3f( 0,-1, 0), Vector3f( 1, 0, 0),
+ Vector3f( 1, 0, 0), Vector3f( 0, 0, 1), Vector3f( 0,-1, 0),
+ Vector3f( 1, 0, 0), Vector3f( 0, 0,-1), Vector3f( 0, 1, 0),
+ Vector3f( 1, 0, 0), Vector3f( 0,-1, 0), Vector3f( 0, 0,-1),
+ Vector3f(-1, 0, 0), Vector3f( 0,-1, 0), Vector3f( 0, 0, 1),
+};
+
+
+bool Camera::StandaloneRenderToCubemap( RenderTexture* rt, int faceMask )
+{
+ PROFILER_AUTO_GFX(gCameraRenderToCubemapProfile, this)
+
+ if (rt->GetDimension() != kTexDimCUBE)
+ {
+ ErrorString( "Render texture must be a cubemap" );
+ return false;
+ }
+ if (!gGraphicsCaps.hasRenderToTexture || !gGraphicsCaps.hasRenderToCubemap || gGraphicsCaps.buggyCameraRenderToCubemap)
+ {
+ //ErrorString( "Render to cubemap is not supported on this hardware" );
+ // Do not print the message; if returns false that means unsupported. No need to spam the console.
+ return false;
+ }
+
+ CameraRenderOldState state;
+ StoreRenderState (state);
+
+ GetRenderManager().SetCurrentCamera (this);
+
+ PPtr<RenderTexture> oldTargetTexture = m_TargetTexture;
+ m_TargetTexture = rt;
+
+ Matrix4x4f viewMatrix;
+
+ // save FOV, aspect & render path (careful to not cause SetDirty)
+ CameraTemporarySettings settings;
+ GetTemporarySettings(settings);
+
+ m_FieldOfView = 90.0f;
+ m_Aspect = 1.0f;
+ m_ImplicitAspect = false;
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+
+ // rendering into cubemap does not play well with deferred
+ if (CalculateRenderingPath() == kRenderPathPrePass)
+ m_RenderingPath = kRenderPathForward;
+ DebugAssert (CalculateRenderingPath() != kRenderPathPrePass);
+
+ // We may need BeginFrame() if we're called from script outside rendering loop (case 464376)
+ AutoGfxDeviceBeginEndFrame frame;
+ if( !frame.GetSuccess() )
+ return false;
+
+ GfxDevice& device = GetGfxDevice();
+
+ // render each face
+ Matrix4x4f translateMat;
+ translateMat.SetTranslate( -GetComponent(Transform).GetPosition() );
+ for( int i = 0; i < 6; ++i )
+ {
+ if( !(faceMask & (1<<i)) )
+ continue;
+ m_CurrentTargetFace = (CubemapFace)i;
+ RenderTexture::SetActive (rt, 0, (CubemapFace)i);
+ device.SetUserBackfaceMode( true ); // do this for each face (different contexts in GL!)
+ viewMatrix.SetOrthoNormalBasisInverse( kCubemapOrthoBases[i*3+0], kCubemapOrthoBases[i*3+1], kCubemapOrthoBases[i*3+2] );
+ viewMatrix *= translateMat;
+ SetWorldToCameraMatrix( viewMatrix );
+
+ CullResults cullResults;
+ StandaloneCull( NULL, "", cullResults );
+
+ Render( cullResults, kRenderFlagStandalone);
+ }
+
+ ResetWorldToCameraMatrix();
+
+ // restore FOV, aspect & render path (careful to not cause SetDirty)
+ SetTemporarySettings(settings);
+
+ m_TargetTexture = oldTargetTexture;
+
+ RestoreRenderState (state);
+ device.SetUserBackfaceMode( false );
+
+ return true;
+}
+
+bool Camera::StandaloneRenderToCubemap( Cubemap* cubemap, int faceMask )
+{
+ PROFILER_AUTO_GFX(gCameraRenderToCubemapProfile, this)
+
+ if( !cubemap )
+ {
+ ErrorString( "Cubemap is null" );
+ return false;
+ }
+ if( cubemap->GetTextureFormat() != kTexFormatARGB32 && cubemap->GetTextureFormat() != kTexFormatRGB24 )
+ {
+ ErrorString( "Unsupported cubemap format - needs to be ARGB32 or RGB24" );
+ return false;
+ }
+ if (!gGraphicsCaps.hasRenderToTexture)
+ {
+ //ErrorString( "Render to cubemap is not supported on this hardware" );
+ // Do not print the message; if returns false that means unsupported. No need to spam the console.
+ return false;
+ }
+
+ RenderManager::UpdateAllRenderers();
+
+ int size = cubemap->GetDataWidth();
+ //UInt32 flags = RenderBufferManager::kRBCreatedFromScript;
+ RenderTexture* rtt = GetRenderBufferManager().GetTempBuffer (size, size, kDepthFormat16, kRTFormatARGB32, 0, kRTReadWriteDefault);
+ if( !rtt )
+ {
+ ErrorString( "Error while rendering to cubemap - failed to get temporary render texture" );
+ return false;
+ }
+
+ CameraRenderOldState state;
+ StoreRenderState (state);
+
+ GetRenderManager().SetCurrentCamera (this);
+
+ PPtr<RenderTexture> oldTargetTexture = m_TargetTexture;
+ m_TargetTexture = rtt;
+
+ Matrix4x4f viewMatrix;
+
+ // save FOV, aspect & render path (careful to not cause SetDirty)
+ CameraTemporarySettings oldSettings;
+ GetTemporarySettings(oldSettings);
+ m_FieldOfView = 90.0f;
+ m_Aspect = 1.0f;
+ m_ImplicitAspect = false;
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+ // rendering into cubemap does not play well with deferred
+ if (CalculateRenderingPath() == kRenderPathPrePass)
+ m_RenderingPath = kRenderPathForward;
+ DebugAssert (CalculateRenderingPath() != kRenderPathPrePass);
+
+ // We may need BeginFrame() if we're called from script outside rendering loop (case 464376)
+ AutoGfxDeviceBeginEndFrame frame;
+ if( !frame.GetSuccess() )
+ return false;
+
+ GfxDevice& device = GetGfxDevice();
+
+ // render each face
+ Matrix4x4f translateMat;
+ translateMat.SetTranslate( -GetComponent(Transform).GetPosition() );
+ RenderTexture::SetActive( rtt );
+ device.SetUserBackfaceMode( true );
+ for( int i = 0; i < 6; ++i )
+ {
+ if( !(faceMask & (1<<i)) )
+ continue;
+
+ // render the cubemap face
+ viewMatrix.SetOrthoNormalBasisInverse( kCubemapOrthoBases[i*3+0], kCubemapOrthoBases[i*3+1], kCubemapOrthoBases[i*3+2] );
+ viewMatrix *= translateMat;
+ SetWorldToCameraMatrix( viewMatrix );
+
+ CullResults cullResults;
+ StandaloneCull( NULL, "", cullResults );
+
+ Render( cullResults, kRenderFlagStandalone );
+
+ // Read back render texture into the cubemap face.
+ // If projection matrix is flipped (happens on D3D), we have to flip
+ // the image vertically so that result is correct.
+ cubemap->ReadPixels( i, 0, 0, size, size, 0, 0, device.GetInvertProjectionMatrix(), false );
+ }
+
+ ResetWorldToCameraMatrix();
+
+ SetTemporarySettings (oldSettings);
+
+ m_TargetTexture = oldTargetTexture;
+
+ RestoreRenderState (state);
+
+ device.SetUserBackfaceMode( false );
+
+ GetRenderBufferManager().ReleaseTempBuffer( rtt );
+
+ // Rendering cubemaps takes place for a sRGB color space:
+ cubemap->SetStoredColorSpace( kTexColorSpaceSRGB );
+
+ cubemap->UpdateImageData();
+
+ return true;
+}
+
+Shader* GetCameraDepthTextureShader ()
+{
+ Shader* depthShader = GetScriptMapper().FindShader("Hidden/Camera-DepthTexture");
+ if (depthShader && !depthShader->IsSupported())
+ depthShader = NULL;
+ return depthShader;
+}
+
+Shader* GetCameraDepthNormalsTextureShader ()
+{
+ Shader* depthShader = GetScriptMapper().FindShader("Hidden/Camera-DepthNormalTexture");
+ if (depthShader && !depthShader->IsSupported())
+ depthShader = NULL;
+ return depthShader;
+}
+
+
+void Camera::RenderDepthTexture (const CullResults& cullResults, RenderTexture** rt, RenderTextureFormat format, Shader* shader, const ColorRGBAf& clearColor, ShaderLab::FastPropertyName name)
+{
+ Assert (rt != NULL);
+
+ if (!shader)
+ return;
+
+ GPU_AUTO_SECTION(kGPUSectionShadowPass);
+
+ if (*rt)
+ {
+ GetRenderBufferManager().ReleaseTempBuffer (*rt);
+ *rt = NULL;
+ }
+
+ DepthBufferFormat depthFormat = UNITY_PS3 ? kDepthFormat24 : kDepthFormat16;
+ *rt = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, depthFormat, format, 0, kRTReadWriteLinear);
+ if (!*rt)
+ return;
+
+ GfxDevice& device = GetGfxDevice();
+ RenderTexture::SetActive (*rt);
+
+ GraphicsHelper::Clear (kGfxClearAll, clearColor.GetPtr(), 1.0f, 0);
+ GPU_TIMESTAMP();
+ SetupRender ();
+
+ RenderSceneShaderReplacement (cullResults.nodes, shader, "RenderType");
+
+ // The last renderer _might_ have toggled the back facing mode (to deal with mirrored geometry), so we reset this
+ // in order to make the back facing well-defined.
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+
+ ShaderLab::g_GlobalProperties->SetTexture (name, *rt);
+}
+
+void Camera::CleanupDepthTextures ()
+{
+ if (m_DepthTexture != NULL)
+ {
+ GetRenderBufferManager().ReleaseTempBuffer (m_DepthTexture);
+ m_DepthTexture = NULL;
+ }
+ if (m_DepthNormalsTexture != NULL)
+ {
+ GetRenderBufferManager().ReleaseTempBuffer (m_DepthNormalsTexture);
+ m_DepthNormalsTexture = NULL;
+ }
+}
+
+void Camera::UpdateDepthTextures (const CullResults& cullResults)
+{
+ g_ShaderKeywords.Disable (kKeywordSoftParticles);
+ bool softParticles = GetQualitySettings().GetCurrent().softParticles;
+
+ UInt32 depthTexMask = m_DepthTextureMode;
+ RenderingPath renderPath = CalculateRenderingPath();
+
+ if (softParticles && renderPath == kRenderPathPrePass)
+ g_ShaderKeywords.Enable (kKeywordSoftParticles);
+
+ if (!gGraphicsCaps.hasStencilInDepthTexture && renderPath == kRenderPathPrePass)
+ depthTexMask |= kDepthTexDepthBit; // prepass needs to generate depth texture if we don't have native capability
+
+ // In case we need depth texture:
+ // If HW supports native depth textures AND we're going to use light pre-pass:
+ // it comes for free, nothing extra to do.
+ if ((depthTexMask & kDepthTexDepthBit) && renderPath == kRenderPathPrePass && gGraphicsCaps.hasStencilInDepthTexture)
+ depthTexMask &= ~kDepthTexDepthBit;
+
+ // In case we need depth+normals texture:
+ // If we're going to use light pre-pass, it will built it for us. Nothing extra to do.
+ if ((depthTexMask & kDepthTexDepthNormalsBit) && renderPath == kRenderPathPrePass)
+ depthTexMask &= ~kDepthTexDepthNormalsBit;
+
+ // No depth textures needed
+ if (depthTexMask == 0)
+ return;
+
+ // Depth textures require some hardware support. We'll just say "need SM2.0+ and depth textures".
+ if (!RenderTexture::IsEnabled() || (int)gGraphicsCaps.shaderCaps < kShaderLevel2 || !gGraphicsCaps.supportsRenderTextureFormat[kRTFormatDepth])
+ return;
+
+ if (softParticles && (depthTexMask & kDepthTexDepthBit))
+ g_ShaderKeywords.Enable (kKeywordSoftParticles);
+
+ // if camera's viewport rect is empty or invalid, do nothing
+ if( !IsValidToRender() )
+ return;
+
+ Assert (depthTexMask != 0);
+ Assert (m_DepthTexture == NULL && m_DepthNormalsTexture == NULL);
+
+ if (depthTexMask & kDepthTexDepthBit)
+ {
+ PROFILER_AUTO_GFX(gCameraDepthTextureProfile, this)
+ RenderDepthTexture (cullResults, &m_DepthTexture, kRTFormatDepth, GetCameraDepthTextureShader(), ColorRGBAf(1,1,1,1), kSLPropCameraDepthTexture);
+
+ }
+ if (depthTexMask & kDepthTexDepthNormalsBit)
+ {
+ PROFILER_AUTO_GFX(gCameraDepthNormalsTextureProfile, this)
+ RenderDepthTexture (cullResults, &m_DepthNormalsTexture, kRTFormatARGB32, GetCameraDepthNormalsTextureShader(), ColorRGBAf(0.5f,0.5f,1,1), kSLPropCameraDepthNormalsTexture);
+ }
+
+#if GFX_SUPPORTS_OPENGLES20 || GFX_SUPPORTS_OPENGLES30
+ // when we are prepping image filters we need current rt info to determine formats of intermediate RTs
+ // so we should reset info from possible depth path
+ GfxDeviceRenderer renderer = GetGfxDevice().GetRenderer();
+ if (renderer == kGfxRendererOpenGLES20Desktop || renderer == kGfxRendererOpenGLES20Mobile || renderer == kGfxRendererOpenGLES30)
+ {
+ if (depthTexMask & (kDepthTexDepthBit | kDepthTexDepthNormalsBit))
+ RenderTexture::SetActive(m_CurrentTargetTexture);
+ }
+#endif
+}
+
+
+void Camera::StandaloneSetup ()
+{
+ GetRenderManager ().SetCurrentCamera (this);
+ // This does not setup image filters! The usage pattern (e.g. terrain engine impostors) is:
+ // Camera old = Camera.current;
+ // newcamera.RenderDontRestore();
+ // ... render our stuff
+ // Camera.SetupCurrent(old);
+ //
+ // So the last call should preserve whatever image filters were used before.
+ SetupRender( kRenderFlagStandalone | kRenderFlagSetRenderTarget );
+}
+
+void Camera::Render (CullResults& cullResults, int renderFlags)
+{
+ // if camera's viewport rect is empty or invalid, do nothing
+ if( !IsValidToRender () )
+ return;
+
+ if (m_IsRendering && IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_1_a3))
+ {
+ WarningStringObject("Attempting to render from a camera that is currently rendering. Create a copy of the camera (Camear.CopyFrom) if you wish to do this.", this);
+ return;
+ }
+
+ m_IsRendering = true;
+
+ Vector3f curPosition = GetPosition ();
+ m_Velocity = (curPosition - m_LastPosition) * GetInvDeltaTime ();
+ m_LastPosition = curPosition;
+// printf_console ("Rendering: %s\n", GetName().c_str());
+ GetRenderManager ().SetCurrentCamera (this);
+
+ // Update depth textures if needed
+ UpdateDepthTextures (cullResults);
+
+ const bool explicitReplacement = (renderFlags & kRenderFlagExplicitShaderReplace) != 0;
+
+ // Setup for rendering, also sets render texture!
+ if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_1_a3))
+ SetupRender ( renderFlags | kRenderFlagPrepareImageFilters );
+ else
+ SetupRender ( renderFlags | (explicitReplacement ? 0 : kRenderFlagPrepareImageFilters) );
+
+ DoRender ( cullResults, NULL, renderFlags ); // Render all geometry
+
+ if ( (renderFlags & kRenderFlagStandalone) || GetEnabled() ) // camera may be already disabled here (OnPostRender)
+ {
+ //@TODO: This is inconsistent with renderGUILayer.
+ // Is there any reason for it?
+ bool renderPostLayers = cullResults.shaderReplaceData.replacementShader == NULL;
+
+ if (renderPostLayers)
+ DoRenderPostLayers (); // Handle any post-layer
+
+ if (!explicitReplacement || IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_1_a3))
+ RenderImageFilters (*m_RenderLoop, m_TargetTexture, false);
+ }
+ m_CurrentTargetTexture = m_TargetTexture;
+
+ m_IsRendering = false;
+
+ if ( (renderFlags & kRenderFlagStandalone) || GetEnabled() ) { // camera may be already disabled here (OnPostRender)
+ // When camera is rendering with replacement that is part of state (and not explicitly passed in),
+ // render GUI using regular shaders.
+ if (!explicitReplacement)
+ DoRenderGUILayer ();
+ }
+
+ // Clear camera's intermediate renderers here
+ ClearIntermediateRenderers ();
+
+ // Cleanup after all rendering
+ CleanupAfterRenderLoop (*m_RenderLoop);
+
+ CleanupDepthTextures ();
+}
+
+void Camera::AddImageFilter (const ImageFilter& filter)
+{
+ GetRenderLoopImageFilters(*m_RenderLoop).AddImageFilter (filter);
+}
+
+void Camera::RemoveImageFilter (const ImageFilter& filter)
+{
+ GetRenderLoopImageFilters(*m_RenderLoop).RemoveImageFilter (filter);
+}
+
+
+Ray Camera::ScreenPointToRay (const Vector2f& viewPortPos) const
+{
+ int viewPort[4];
+ RectfToViewport (GetScreenViewportRect (), viewPort);
+
+ Ray ray;
+ Vector3f out;
+ Matrix4x4f clipToWorld;
+ GetClipToWorldMatrix( clipToWorld );
+
+ const Matrix4x4f& camToWorld = GetCameraToWorldMatrix();
+ if( !CameraUnProject( Vector3f(viewPortPos.x, viewPortPos.y, m_NearClip), camToWorld, clipToWorld, viewPort, out ) )
+ {
+ if(viewPort[0] > 0 || viewPort[1] > 0 || viewPort[2] > 0 || viewPort[3] > 0)
+ {
+ AssertString (Format("Screen position out of view frustum (screen pos %f, %f) (Camera rect %d %d %d %d)", viewPortPos.x, viewPortPos.y, viewPort[0], viewPort[1], viewPort[2], viewPort[3]));
+ }
+ return Ray (GetPosition(), Vector3f(0, 0, 1));
+ }
+ ray.SetOrigin( out );
+ if( !CameraUnProject( Vector3f(viewPortPos.x, viewPortPos.y, m_NearClip + 1.0f), camToWorld, clipToWorld, viewPort, out ) )
+ {
+ if(viewPort[0] > 0 || viewPort[1] > 0 || viewPort[2] > 0 || viewPort[3] > 0)
+ {
+ AssertString (Format("Screen position out of view frustum (screen pos %f, %f) (Camera rect %d %d %d %d)", viewPortPos.x, viewPortPos.y, viewPort[0], viewPort[1], viewPort[2], viewPort[3]));
+ }
+ return Ray (GetPosition(), Vector3f(0, 0, 1));
+ }
+ Vector3f dir = out - ray.GetOrigin();
+ ray.SetDirection (Normalize (dir));
+
+ return ray;
+}
+
+Vector3f Camera::WorldToScreenPoint (const Vector3f& v, bool* canProject) const
+{
+ int viewPort[4];
+ RectfToViewport (GetScreenViewportRect (), viewPort);
+
+ Vector3f out;
+ bool ok = CameraProject( v, GetCameraToWorldMatrix(), GetWorldToClipMatrix(), viewPort, out );
+ if( canProject != NULL )
+ *canProject = ok;
+ return out;
+}
+
+Vector3f Camera::ScreenToWorldPoint (const Vector3f& v) const
+{
+ int viewPort[4];
+ RectfToViewport( GetScreenViewportRect(), viewPort );
+
+ Vector3f out;
+ Matrix4x4f clipToWorld;
+ GetClipToWorldMatrix( clipToWorld );
+ if( !CameraUnProject( v, GetCameraToWorldMatrix(), clipToWorld, viewPort, out ) )
+ {
+ AssertString (Format("Screen position out of view frustum (screen pos %f, %f, %f) (Camera rect %d %d %d %d)", v.x, v.y, v.z, viewPort[0], viewPort[1], viewPort[2], viewPort[3]));
+ }
+ return out;
+}
+
+Vector3f Camera::WorldToViewportPoint (const Vector3f &worldPoint) const {
+ bool tempBool;
+ Vector3f screenPoint = WorldToScreenPoint (worldPoint, &tempBool);
+ return ScreenToViewportPoint (screenPoint);
+}
+
+Vector3f Camera::ViewportToWorldPoint (const Vector3f &viewPortPoint) const {
+ Vector3f screenPoint = ViewportToScreenPoint (viewPortPoint);
+ return ScreenToWorldPoint (screenPoint);
+}
+
+Vector3f Camera::ViewportToCameraPoint (const Vector3f &viewPort) const {
+ Vector3f ndc;
+ Matrix4x4f invProjection;
+ Matrix4x4f::Invert_Full (GetProjectionMatrix(), invProjection);
+
+ ndc.x = Lerp (-1, 1, viewPort.x);
+ ndc.y = Lerp (-1, 1, viewPort.y);
+ ndc.z = Lerp (-1, 1, (viewPort.z - m_NearClip) / m_FarClip);
+
+ Vector3f cameraPoint;
+ invProjection.PerspectiveMultiplyPoint3 (ndc, cameraPoint);
+
+ cameraPoint.z = viewPort.z;
+
+ return cameraPoint;
+}
+
+Ray Camera::ViewportPointToRay (const Vector2f& viewPortPos) const {
+ Vector3f screenPos = ViewportToScreenPoint (Vector3f (viewPortPos.x, viewPortPos.y, 0.0F));
+ return ScreenPointToRay (Vector2f (screenPos.x, screenPos.y));
+}
+
+Vector3f Camera::ScreenToViewportPoint (const Vector3f& screenPos) const
+{
+ Rectf r = GetScreenViewportRect ();
+ float nx = (screenPos.x - r.x) / r.Width ();
+ float ny = (screenPos.y - r.y) / r.Height ();
+ return Vector3f (nx, ny, screenPos.z);
+}
+
+Vector3f Camera::ViewportToScreenPoint (const Vector3f& viewPos) const
+{
+ Rectf r = GetScreenViewportRect();
+ float nx = viewPos.x * r.Width () + r.x;
+ float ny = viewPos.y * r.Height () + r.y;
+ return Vector3f (nx, ny, viewPos.z);
+}
+
+float Camera::CalculateFarPlaneWorldSpaceLength () const
+{
+ Rectf screenRect = GetScreenViewportRect ();
+ Vector3f p0 = ScreenToWorldPoint (Vector3f (screenRect.x, screenRect.y, m_FarClip));
+ Vector3f p1 = ScreenToWorldPoint (Vector3f (screenRect.x + screenRect.width, screenRect.y, m_FarClip));
+
+ return Magnitude (p0 - p1);
+}
+
+float Camera::CalculateNearPlaneWorldSpaceLength () const
+{
+ Rectf screenRect = GetScreenViewportRect ();
+ Vector3f p0 = ScreenToWorldPoint (Vector3f (screenRect.x, screenRect.y, m_NearClip));
+ Vector3f p1 = ScreenToWorldPoint (Vector3f (screenRect.x + screenRect.width, screenRect.y, m_NearClip));
+ return Magnitude (p0 - p1);
+}
+
+void Camera::SetDepth (float depth)
+{
+ SetDirty();
+ m_Depth = depth;
+ if (IsActive () && GetEnabled ()) {
+ RemoveFromManager ();
+ AddToManager ();
+ }
+}
+
+void Camera::SetNormalizedViewportRect (const Rectf& normalizedRect) {
+ SetDirty();
+ m_NormalizedViewPortRect = normalizedRect;
+ WindowSizeHasChanged ();
+}
+
+void Camera::WindowSizeHasChanged () {
+ if (m_ImplicitAspect)
+ ResetAspect ();
+#if UNITY_WP8
+ else
+ {
+ // Screen rotation is handled when the projection matrix is generated, so
+ // it is necessary to mark it dirty here when a custom aspect ratio is set
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+ }
+#endif
+}
+
+void Camera::SetAspect (float aspect)
+{
+ m_Aspect = aspect;
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+ m_ImplicitAspect = false;
+}
+
+void Camera::ResetAspect ()
+{
+ Rectf r = GetScreenViewportRect();
+ if (r.Height () != 0)
+ m_Aspect = (r.Width () / r.Height ());
+ else
+ m_Aspect = 1.0f;
+
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+ m_ImplicitAspect = true;
+}
+
+Vector3f Camera::GetPosition () const {
+ return GetComponent (Transform).GetPosition();
+}
+
+inline bool IsMatrixValid (const Matrix4x4f& m)
+{
+ for (int i=0;i<16;i++)
+ {
+ if (!IsFinite (m.GetPtr ()[i]))
+ return false;
+ }
+ return true;
+}
+
+const Matrix4x4f& Camera::GetWorldToCameraMatrix () const
+{
+ if( m_DirtyWorldToCameraMatrix && m_ImplicitWorldToCameraMatrix )
+ {
+ m_WorldToCameraMatrix.SetScale (Vector3f (1.0F, 1.0F, -1.0F));
+ m_WorldToCameraMatrix *= GetComponent (Transform).GetWorldToLocalMatrixNoScale ();
+ m_DirtyWorldToCameraMatrix = false;
+ }
+ return m_WorldToCameraMatrix;
+}
+
+Matrix4x4f Camera::GetCameraToWorldMatrix () const
+{
+ Matrix4x4f m;
+ Matrix4x4f::Invert_Full( GetWorldToCameraMatrix(), m );
+ return m;
+}
+
+const Matrix4x4f& Camera::GetProjectionMatrix () const
+{
+ if( m_DirtyProjectionMatrix && m_ImplicitProjectionMatrix )
+ {
+ if (!m_Orthographic)
+ m_ProjectionMatrix.SetPerspective( m_FieldOfView, m_Aspect, m_NearClip, m_FarClip );
+ else
+ m_ProjectionMatrix.SetOrtho( -m_OrthographicSize * m_Aspect, m_OrthographicSize * m_Aspect, -m_OrthographicSize, m_OrthographicSize, m_NearClip, m_FarClip );
+
+ #if UNITY_WP8
+ // Off-screen cameras don't need to be rotated with device orientation changes (case 561859)
+ if (GetTargetTexture() == NULL)
+ {
+ void RotateScreenIfNeeded(Matrix4x4f& mat);
+ RotateScreenIfNeeded(m_ProjectionMatrix);
+ }
+ #endif
+ m_DirtyProjectionMatrix = false;
+ }
+ return m_ProjectionMatrix;
+}
+
+void Camera::GetImplicitProjectionMatrix (float overrideNearPlane, Matrix4x4f& outMatrix) const
+{
+ if( !m_Orthographic )
+ outMatrix.SetPerspective( m_FieldOfView, m_Aspect, overrideNearPlane, m_FarClip );
+ else
+ outMatrix.SetOrtho( -m_OrthographicSize * m_Aspect, m_OrthographicSize * m_Aspect, -m_OrthographicSize, m_OrthographicSize, overrideNearPlane, m_FarClip );
+}
+
+
+void Camera::GetImplicitProjectionMatrix (float overrideNearPlane, float overrideFarPlane, Matrix4x4f& outMatrix) const
+{
+ if( !m_Orthographic )
+ outMatrix.SetPerspective( m_FieldOfView, m_Aspect, overrideNearPlane, overrideFarPlane );
+ else
+ outMatrix.SetOrtho( -m_OrthographicSize * m_Aspect, m_OrthographicSize * m_Aspect, -m_OrthographicSize, m_OrthographicSize, overrideNearPlane, overrideFarPlane );
+}
+
+
+void Camera::SetWorldToCameraMatrix (const Matrix4x4f& matrix)
+{
+ Assert (IsMatrixValid (matrix));
+ m_WorldToCameraMatrix = matrix;
+ m_ImplicitWorldToCameraMatrix = false;
+ m_DirtyWorldToClipMatrix = true;
+}
+
+void Camera::SetProjectionMatrix (const Matrix4x4f& matrix)
+{
+ Assert (IsMatrixValid (matrix));
+ m_ProjectionMatrix = matrix;
+ m_ImplicitProjectionMatrix = false;
+ m_DirtyWorldToClipMatrix = true;
+}
+
+const Matrix4x4f& Camera::GetWorldToClipMatrix() const
+{
+ if( m_DirtyWorldToClipMatrix )
+ {
+ MultiplyMatrices4x4 (&GetProjectionMatrix(), &GetWorldToCameraMatrix(), &m_WorldToClipMatrix);
+ m_DirtyWorldToClipMatrix = false;
+ }
+ return m_WorldToClipMatrix;
+}
+
+void Camera::GetClipToWorldMatrix( Matrix4x4f& outMatrix ) const
+{
+ Matrix4x4f::Invert_Full( GetWorldToClipMatrix(), outMatrix );
+}
+
+void Camera::SetTargetTextureBuffers(RenderTexture* tex, int colorCount, RenderSurfaceHandle* color, RenderSurfaceHandle depth, RenderTexture* rbOrigin)
+{
+ if (m_TargetTexture == PPtr<RenderTexture>(tex))
+ {
+ bool buffSame = colorCount == m_TargetColorBufferCount
+ && ::memcmp(color, m_TargetColorBuffer, colorCount*sizeof(RenderSurfaceHandle)) == 0
+ && depth == m_TargetDepthBuffer;
+
+ if(tex != 0 || buffSame)
+ return;
+ }
+
+ bool wasCurrent = GetRenderManager().GetCurrentCameraPtr() == this;
+ bool wasOffscreen = (RenderTexture*)m_TargetTexture != 0 || m_TargetBuffersOriginatedFrom != 0;
+
+ m_TargetTexture = tex;
+
+ ::memcpy(m_TargetColorBuffer, color, colorCount*sizeof(RenderSurfaceHandle));
+ if(colorCount < kMaxSupportedRenderTargets)
+ ::memset(m_TargetColorBuffer+colorCount, 0x00, (kMaxSupportedRenderTargets-colorCount)*sizeof(RenderSurfaceHandle));
+
+ m_TargetColorBufferCount = colorCount;
+ m_TargetDepthBuffer = depth;
+ m_TargetBuffersOriginatedFrom = rbOrigin;
+
+ SetDirty();
+ if (IsAddedToManager ()) {
+ GetRenderManager().RemoveCamera (this);
+ GetRenderManager().AddCamera (this);
+
+ // special case: if we were rendering to offscreen camera and changed rt in process - reactivate it
+ // other possible cases:
+ // wasn't current: nothing changes (? maybe check that was rendered already, what was the intention?)
+ // onscreen -> offscreen: we shouldn'd draw in here in that pass (and if we really wants?)
+ // offscreen -> onscreen: will be correctly drawn next time we draw onscreen cameras
+ if( wasCurrent && wasOffscreen && (tex || rbOrigin) )
+ GetRenderManager().SetCurrentCamera(this);
+ }
+}
+
+void Camera::SetTargetBuffers (int colorCount, RenderSurfaceHandle* color, RenderSurfaceHandle depth, RenderTexture* originatedFrom)
+{
+ SetTargetTextureBuffers(0, colorCount, color, depth, originatedFrom);
+}
+
+void Camera::SetTargetBuffersScript (int colorCount, const ScriptingRenderBuffer* colorScript, ScriptingRenderBuffer* depthScript)
+{
+ #define RETURN_WITH_ERROR(msg) do { ErrorString(msg); return; } while(0)
+
+ RenderSurfaceHandle color[kMaxSupportedRenderTargets];
+ for (int i = 0; i < colorCount; ++i)
+ color[i] = colorScript[i].m_BufferPtr ? RenderSurfaceHandle(colorScript[i].m_BufferPtr) : GetGfxDevice().GetBackBufferColorSurface();
+
+ RenderSurfaceHandle depth = depthScript->m_BufferPtr ? RenderSurfaceHandle(depthScript->m_BufferPtr) : GetGfxDevice().GetBackBufferDepthSurface();
+
+ // check rt/screen originated (cant mix)
+ // TODO: maybe we should simply check backBuffer flag?
+ PPtr<RenderTexture> originatedFrom(colorScript[0].m_RenderTextureInstanceID);
+ {
+ bool onScreen = originatedFrom.IsNull();
+ for(int i = 1 ; i < colorCount ; ++i)
+ {
+ if( PPtr<RenderTexture>(colorScript[i].m_RenderTextureInstanceID).IsNull() != onScreen )
+ RETURN_WITH_ERROR("You're trying to mix color buffers from RenderTexture and from screen.");
+ }
+
+ if( PPtr<RenderTexture>(depthScript->m_RenderTextureInstanceID).IsNull() != onScreen )
+ RETURN_WITH_ERROR("You're trying to mix color and depth buffers from RenderTexture and from screen.");
+ }
+
+ // check that we have matching exts
+ {
+ int colorW = color[0].object->width;
+ int colorH = color[0].object->height;
+ int depthW = depth.object->width;
+ int depthH = depth.object->height;
+
+ for(int i = 1 ; i < colorCount ; ++i)
+ {
+ int w = color[i].object->width;
+ int h = color[i].object->height;
+ if(colorW != w || colorH != h)
+ RETURN_WITH_ERROR("Camera.SetTargetBuffers can only accept RenderBuffers with same size.");
+ }
+
+ if(colorW != depthW || colorH != depthH)
+ RETURN_WITH_ERROR("Camera.SetTargetBuffers can only accept RenderBuffers with same size.");
+ }
+
+ SetTargetTextureBuffers(0, colorCount, color, depth, PPtr<RenderTexture>(colorScript[0].m_RenderTextureInstanceID));
+
+ return;
+ #undef RETURN_WITH_ERROR
+}
+
+
+void Camera::SetTargetTexture (RenderTexture *tex)
+{
+ RenderSurfaceHandle color = tex ? tex->GetColorSurfaceHandle() : GetGfxDevice().GetBackBufferColorSurface();
+ RenderSurfaceHandle depth = tex ? tex->GetDepthSurfaceHandle() : GetGfxDevice().GetBackBufferDepthSurface();
+ SetTargetTextureBuffers(tex, 1, &color, depth, 0);
+}
+
+void Camera::CopyFrom( const Camera& other )
+{
+ // copy transform from other
+ Transform& transform = GetComponent (Transform);
+ const Transform& otherTransform = other.GetComponent (Transform);
+ transform.SetLocalScale( otherTransform.GetLocalScale() );
+ transform.SetPosition( otherTransform.GetPosition() );
+
+ // normalize this... the camera can come from a very deep
+ // hierarchy. This can lead to float rounding errors :(
+ Quaternionf quat = Normalize (otherTransform.GetRotation());
+ transform.SetRotation(quat);
+
+ // copy layer of this gameobject from other
+ GetGameObject().SetLayer( other.GetGameObject().GetLayer() );
+
+ // copy camera's variables from other
+ m_ClearFlags = other.m_ClearFlags;
+ m_BackGroundColor = other.m_BackGroundColor;
+ m_NormalizedViewPortRect = other.m_NormalizedViewPortRect;
+ m_CullingMask = other.m_CullingMask;
+ m_EventMask = other.m_EventMask;
+
+ m_Depth = other.m_Depth;
+ m_Velocity = other.m_Velocity;
+ m_LastPosition = other.m_LastPosition;
+ m_OrthographicSize = other.m_OrthographicSize;
+ m_FieldOfView = other.m_FieldOfView;
+ m_NearClip = other.m_NearClip;
+ m_FarClip = other.m_FarClip;
+ m_Aspect = other.m_Aspect;
+
+ m_WorldToCameraMatrix = other.m_WorldToCameraMatrix;
+ m_ProjectionMatrix = other.m_ProjectionMatrix;
+ m_WorldToClipMatrix = other.m_WorldToClipMatrix;
+ m_DirtyWorldToCameraMatrix = other.m_DirtyWorldToCameraMatrix;
+ m_DirtyProjectionMatrix = other.m_DirtyProjectionMatrix;
+ m_DirtyWorldToClipMatrix = other.m_DirtyWorldToClipMatrix;
+ m_ImplicitWorldToCameraMatrix = other.m_ImplicitWorldToCameraMatrix;
+ m_ImplicitAspect = other.m_ImplicitAspect;
+ m_Orthographic = other.m_Orthographic;
+
+ m_TargetTexture = other.m_TargetTexture;
+ m_CurrentTargetTexture = other.m_CurrentTargetTexture;
+ m_TargetColorBufferCount = other.m_TargetColorBufferCount;
+ ::memcpy(m_TargetColorBuffer, other.m_TargetColorBuffer, sizeof(m_TargetColorBuffer));
+ m_TargetDepthBuffer = other.m_TargetDepthBuffer;
+ m_TargetBuffersOriginatedFrom = other.m_TargetBuffersOriginatedFrom;
+
+ m_ReplacementShader = other.m_ReplacementShader;
+ m_ReplacementTag = other.m_ReplacementTag;
+
+ m_DepthTextureMode = other.m_DepthTextureMode;
+ m_ClearStencilAfterLightingPass = other.m_ClearStencilAfterLightingPass;
+
+ if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_2_a1))
+ {
+ m_RenderingPath = other.m_RenderingPath;
+ memcpy(m_LayerCullDistances, other.m_LayerCullDistances, sizeof(m_LayerCullDistances));
+ m_SortMode = other.m_SortMode;
+ m_LayerCullSpherical = other.m_LayerCullSpherical;
+ m_OcclusionCulling = other.m_OcclusionCulling;
+ m_ImplicitProjectionMatrix = other.m_ImplicitProjectionMatrix;
+ }
+
+#if UNITY_EDITOR
+ m_AnimateMaterials = other.m_AnimateMaterials;
+ m_AnimateMaterialsTime = other.m_AnimateMaterialsTime;
+#endif
+
+ SetDirty();
+}
+
+void Camera::GetTemporarySettings (CameraTemporarySettings& settings) const
+{
+ settings.renderingPath = m_RenderingPath;
+ settings.fieldOfView = m_FieldOfView;
+ settings.aspect = m_Aspect;
+ settings.implicitAspect = m_ImplicitAspect;
+}
+
+void Camera::SetTemporarySettings (const CameraTemporarySettings& settings)
+{
+ m_RenderingPath = settings.renderingPath;
+ m_FieldOfView = settings.fieldOfView;
+ m_Aspect = settings.aspect;
+ m_ImplicitAspect = settings.implicitAspect;
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+}
+
+void Camera::SetReplacementShader( Shader* shader, const std::string& replacementTag )
+{
+ m_ReplacementShader = shader;
+ m_ReplacementTag = replacementTag;
+}
+
+void Camera::SetFov (float deg)
+{
+ SetDirty();
+ m_FieldOfView = deg;
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+}
+
+void Camera::SetNear (float n)
+{
+ SetDirty();
+ m_NearClip = n;
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+}
+
+void Camera::SetFar (float f)
+{
+ SetDirty();
+ m_FarClip = f;
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+}
+
+static bool IsNonStandardProjection(const Matrix4x4f& mat)
+{
+ for (int i = 0; i < 3; i++)
+ for (int j = 0; j < 3; j++)
+ if (i != j && mat.Get(i, j) != 0.0f)
+ return true;
+ return false;
+}
+
+float Camera::GetProjectionNear () const
+{
+ if (m_ImplicitProjectionMatrix)
+ return m_NearClip;
+
+ const Matrix4x4f& proj = GetProjectionMatrix();
+ if (IsNonStandardProjection(proj))
+ return m_NearClip;
+
+ Vector4f nearPlane = proj.GetRow(3) + proj.GetRow(2);
+ Vector3f nearNormal(nearPlane.x, nearPlane.y, nearPlane.z);
+ return -nearPlane.w / Magnitude(nearNormal);
+}
+
+float Camera::GetProjectionFar () const
+{
+ if (m_ImplicitProjectionMatrix)
+ return m_FarClip;
+
+ const Matrix4x4f& proj = GetProjectionMatrix();
+ if (IsNonStandardProjection(proj))
+ return m_FarClip;
+
+ Vector4f farPlane = proj.GetRow(3) - proj.GetRow(2);
+ Vector3f farNormal(farPlane.x, farPlane.y, farPlane.z);
+ return farPlane.w / Magnitude(farNormal);
+}
+
+bool Camera::CalculateUsingHDR () const
+{
+ // some HDR sanity checks
+ return (m_HDR &&
+ GetBuildSettings().hasRenderTexture && gGraphicsCaps.supportsRenderTextureFormat[GetGfxDevice().GetDefaultHDRRTFormat()] &&
+ (!GetQualitySettings().GetCurrent().antiAliasing || (CalculateRenderingPath () == kRenderPathPrePass)));
+}
+
+void Camera::SetOrthographicSize (float f)
+{
+ SetDirty();
+ m_OrthographicSize = f;
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+}
+
+void Camera::SetOrthographic (bool v)
+{
+ SetDirty();
+ m_Orthographic = v;
+ m_DirtyProjectionMatrix = true;
+ m_DirtyWorldToClipMatrix = true;
+}
+
+void Camera::SetBackgroundColor (const ColorRGBAf& color)
+{
+ m_BackGroundColor = color;
+ SetDirty();
+}
+
+void Camera::SetClearFlags (int flags)
+{
+ m_ClearFlags = flags;
+ SetDirty();
+}
+
+void Camera::SetCullingMask (UInt32 cullingMask)
+{
+ m_CullingMask.m_Bits = cullingMask;
+ SetDirty();
+}
+
+void Camera::SetEventMask (UInt32 eventMask)
+{
+ m_EventMask.m_Bits = eventMask;
+ SetDirty();
+}
+
+void Camera::DisplayHDRWarnings() const
+{
+ if((GetQualitySettings().GetCurrent().antiAliasing > 0) && (CalculateRenderingPath () == kRenderPathForward))
+ WarningStringObject("HDR and MultisampleAntiAliasing (in Forward Rendering Path) is not supported. This camera will render without HDR buffers. Disable Antialiasing in the Quality settings if you want to use HDR.", this);
+ if((!gGraphicsCaps.supportsRenderTextureFormat[GetGfxDevice().GetDefaultHDRRTFormat()]) || (!GetBuildSettings().hasRenderTexture))
+ WarningStringObject("HDR RenderTexture format is not supported on this platform. This camera will render without HDR buffers.", this);
+}
+
+bool Camera::GetRenderImmediateObjects () const
+{
+#if UNITY_EDITOR
+ return m_OnlyRenderIntermediateObjects;
+#else
+ return false;
+#endif
+}
+
+#if UNITY_EDITOR
+bool Camera::IsFiltered (Unity::GameObject& gameObject) const
+{
+ return IsGameObjectFiltered (gameObject, (CullFiltering)m_FilterMode);
+}
+#endif
+
+IMPLEMENT_CLASS_HAS_INIT (Camera)
+IMPLEMENT_OBJECT_SERIALIZE (Camera)
+
+
+void ClearWithSkybox (bool clearDepth, Camera const* camera)
+{
+ if (!camera)
+ return;
+
+ Material* skybox = camera->GetSkyboxMaterial ();
+ if (!skybox)
+ return;
+
+ GfxDevice& device = GetGfxDevice();
+ device.SetProjectionMatrix (camera->GetProjectionMatrix());
+ device.SetViewMatrix (camera->GetWorldToCameraMatrix().GetPtr());
+ SetClippingPlaneShaderProps();
+
+ if (clearDepth) {
+ float zero[4] = {0,0,0,0};
+ GraphicsHelper::Clear (kGfxClearDepthStencil, zero, 1.0f, 0);
+ GPU_TIMESTAMP();
+ }
+ Skybox::RenderSkybox (skybox, *camera);
+}
+
+
+Rectf GetCameraOrWindowRect (const Camera* camera)
+{
+ if (camera)
+ return camera->GetScreenViewportRect ();
+ else
+ {
+ Rectf rect = GetRenderManager().GetWindowRect();
+ rect.x = rect.y = 0.0f;
+ return rect;
+ }
+}
+
+/*
+Texture* Camera::GetUmbraOcclusionBufferTexture ()
+{
+ Umbra::OcclusionBuffer::BufferDesc SrcDesc;
+ Umbra::OcclusionBuffer* occlusionBuffer = m_UmbraVisibility->getOutputBuffer();
+ occlusionBuffer->getBufferDesc(SrcDesc);
+
+ if (!SrcDesc.width || !SrcDesc.height)
+ return NULL;
+
+ Assert(SrcDesc.width == 128 && SrcDesc.height == 128);
+
+ UInt8 TempBuffer[128*128];
+ occlusionBuffer->getBuffer(TempBuffer);
+
+ if (!m_OcclusionBufferTexture)
+ {
+ m_OcclusionBufferTexture = CreateObjectFromCode<Texture2D>();
+ m_OcclusionBufferTexture->InitTexture(SrcDesc.width, SrcDesc.height, kTexFormatRGBA32, 0);
+ }
+
+ ImageReference DstDesc;
+ m_OcclusionBufferTexture->GetWriteImageReference(&DstDesc, 0, 0);
+
+ UInt32* DstBuffer = (UInt32*)DstDesc.GetImageData();
+ int DstStride = DstDesc.GetRowBytes();
+
+ for (int y=0; y<DstDesc.GetHeight(); y++)
+ {
+ for (int x=0; x<DstDesc.GetWidth(); x++)
+ {
+ int c = (int)TempBuffer[y*128+x];
+ int a = 0xff;
+#if UNITY_LITTLE_ENDIAN
+ DstBuffer[y*DstDesc.GetWidth()+x] = (a<<24) + (c<<16) + (c<<8) + c;
+#else
+ DstBuffer[y*DstDesc.GetWidth()+x] = (c<<24) + (c<<16) + (c<<8) + a;
+#endif
+ }
+ }
+
+ return dynamic_pptr_cast<Texture*>(m_OcclusionBufferTexture);
+}
+ */
diff --git a/Runtime/Camera/Camera.h b/Runtime/Camera/Camera.h
new file mode 100644
index 0000000..184aa8e
--- /dev/null
+++ b/Runtime/Camera/Camera.h
@@ -0,0 +1,503 @@
+#ifndef CAMERA_H
+#define CAMERA_H
+
+#include "Runtime/GameCode/Behaviour.h"
+#include <vector>
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Geometry/Ray.h"
+#include "Runtime/Graphics/RenderTexture.h" //@TODO remove
+#include "Runtime/Camera/RenderLoops/RenderLoop.h" //@TODO remove
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Modules/ExportModules.h"
+
+struct ImageFilter;
+class Cubemap;
+class Plane;
+class IntermediateRenderers;
+class Shader;
+struct RenderLoop;
+struct ShadowCullData;
+struct CullResults;
+struct CullingParameters;
+struct ShaderReplaceData;
+struct SceneCullingParameters;
+struct CameraCullingParameters;
+class Vector2f;
+
+struct DrawGridParameters;
+
+namespace Unity {class Material;}
+namespace Umbra { class DebugRenderer; }
+
+struct CameraRenderOldState
+{
+ void Reset()
+ {
+ subshaderIndex = 0;
+ currentPass = 0;
+ memset (&viewport, 0, sizeof(viewport));
+ memset (&matWorld, 0, sizeof(matWorld));
+ memset (&matView, 0, sizeof(matView));
+ matProj = Matrix4x4f::identity;
+ }
+ PPtr<Material> material;
+ PPtr<Shader> shader;
+ int subshaderIndex;
+ int currentPass;
+
+ int viewport[4];
+ PPtr<Camera> camera;
+ PPtr<RenderTexture> activeRT;
+
+ float matWorld[16];
+ float matView[16];
+ Matrix4x4f matProj;
+};
+
+struct CameraTemporarySettings
+{
+ int renderingPath;
+ float fieldOfView;
+ float aspect;
+ bool implicitAspect;
+};
+
+// The Camera
+class EXPORT_COREMODULE Camera : public Behaviour {
+public:
+ enum SortMode {
+ kSortDefault = 0,
+ kSortPerspective = 1,
+ kSortOrthographic = 2,
+ };
+
+ enum { kNumLayers = 32 };
+
+public:
+ Camera (MemLabelId label, ObjectCreationMode mode);
+ // ~Camera (); declared-by-macro
+ REGISTER_DERIVED_CLASS (Camera, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (Camera)
+
+ // Tag class as sealed, this makes QueryComponent faster.
+ static bool IsSealedClass () { return true; }
+
+ virtual void Reset ();
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ virtual void CheckConsistency ();
+
+ enum RenderFlag {
+ kRenderFlagStandalone = (1<<0),
+ kRenderFlagSetRenderTarget = (1<<1),
+ kRenderFlagPrepareImageFilters = (1<<2),
+ kRenderFlagDontRestoreRenderState = (1<<3),
+ kRenderFlagSetRenderTargetFinal = (1<<4),
+ kRenderFlagExplicitShaderReplace = (1<<5),
+ };
+
+
+ // Set up the viewport, render target, load modelview & projection matrices
+ void SetupRender (int renderFlags = 0); // bitmask of kRenderFlagXXX
+
+ // Cull with default culling parameters
+ void Cull (CullResults& results);
+ void StandaloneCull (Shader* replacementShader, const std::string& replacementTag, CullResults& results);
+
+ // Cull With custom parameters
+ void CustomCull (const CameraCullingParameters& params, CullResults& cullResults);
+
+
+ static void PrepareSceneCullingParameters (const CameraCullingParameters& parameters, RenderingPath renderPath, CullResults& results);
+ void CalculateFrustumPlanes(Plane* frustum, const Matrix4x4f& overrideWorldToClip, float overrideFarPlane, float& outBaseFarDistance, bool implicitNearFar) const;
+ void CalculateCullingParameters(CullingParameters& cullingParameters) const;
+ void CalculateCustomCullingParameters(CullingParameters& cullingParameters, const Plane* planes, int planeCount) const;
+
+
+ // Clear the camera for how its set up - use skybox if neccessary
+ void Clear ();
+ void ClearNoSkybox (bool noDepth);
+ void RenderSkybox ();
+
+ // Set up the camera transform & render
+ void Render (CullResults& cullResults, int renderFlags); // bitmask of kRenderFlagXXX; kRenderFlagPrepareImageFilters always implied
+
+ // Out-of-order cull / rendering.
+ void StandaloneRender (UInt32 renderFlags, Shader* replacementShader, const std::string& replacementTag);
+ void StandaloneSetup ();
+ bool StandaloneRenderToCubemap (RenderTexture* rt, int faceMask);
+ bool StandaloneRenderToCubemap (Cubemap* cubemap, int faceMask);
+
+ void SetFov (float deg);
+ float GetFov () const { return m_FieldOfView; }
+ void SetNear (float n);
+ float GetNear () const { return m_NearClip; }
+ void SetFar (float f);
+ float GetFar () const { return m_FarClip; }
+
+ // Projection's near and far plane (can differ from GetNear/Far() for custom projection matrix)
+ float GetProjectionNear () const;
+ float GetProjectionFar () const;
+
+ void SetHDR (bool enable) { m_HDR = enable; SetDirty(); }
+ bool GetUsingHDR () const { return m_UsingHDR; } // Cached when setting up the render
+ // A recompute is needed if Camera.hdr is called from script before rendering (case 483603)
+ bool CalculateUsingHDR () const;
+
+ void SetRenderingPath (int rp) { m_RenderingPath = rp; SetDirty(); }
+ RenderingPath GetRenderingPath () const { return static_cast<RenderingPath>(m_RenderingPath); }
+
+ void SetOrthographicSize (float f);
+ float GetOrthographicSize () const { return m_OrthographicSize; }
+
+ bool GetOrthographic() const { return m_Orthographic; }
+ void SetOrthographic (bool v);
+
+ void GetTemporarySettings (CameraTemporarySettings& settings) const;
+ void SetTemporarySettings (const CameraTemporarySettings& settings);
+
+ float GetAspect() const { return m_Aspect; }
+ void SetAspect (float aspect);
+ void ResetAspect ();
+
+
+ bool IsValidToRender() const;
+
+ void SetNormalizedViewportRect (const Rectf& normalizedRect);
+ Rectf GetNormalizedViewportRect () const { return m_NormalizedViewPortRect; }
+
+ // The screen view port rect of the camera.
+ // If the cameras normalized viewport rect is set to be the fullscreen, then this will always go from
+ // 0, 0 to width, height.
+ Rectf GetScreenViewportRect () const { return GetCameraRect(true); }
+ // Similar to GetScreenViewportRect, except this can have non-zero origin even for fullscreen cameras.
+ // This only ever happens in editor's game view when using forced aspect ratio or size.
+ Rectf GetPhysicalViewportRect() const { return GetCameraRect(false); }
+
+ void SetScreenViewportRect (const Rectf& pixelRect);
+
+ // Get the final in-rendertarget render rectangle.
+ // This takes into account any render texture setup we may have.
+ Rectf GetRenderRectangle() const;
+
+
+ /// The Camera render order is determined by sorting all cameras by depth.
+ /// Small depth camera are rendered first, big depth cameras last.
+ float GetDepth () const { return m_Depth; }
+ void SetDepth (float depth);
+
+ /// Set the background color of the camera.
+ void SetBackgroundColor (const ColorRGBAf& color);
+ ColorRGBAf GetBackgroundColor () const { return m_BackGroundColor; }
+
+ // The clearing mode used for the camera.
+ enum ClearMode {
+ kSkybox = 1,
+ kSolidColor = 2,
+ kDepthOnly = 3,
+ kDontClear = 4
+ // Watch out for check consistency when changing this!
+ };
+
+ void SetClearFlags (int flags);
+ ClearMode GetClearFlags () const { return static_cast<ClearMode>(m_ClearFlags); }
+
+ void SetCullingMask (UInt32 cullingMask);
+ UInt32 GetCullingMask () const { return m_CullingMask.m_Bits; }
+
+ void SetEventMask (UInt32 cullingMask);
+ UInt32 GetEventMask () const { return m_EventMask.m_Bits; }
+
+ Vector3f GetPosition () const;
+
+ void SetUseOcclusionCulling (bool occlusionCull) { m_OcclusionCulling = occlusionCull; }
+ bool GetUseOcclusionCulling () const { return m_OcclusionCulling; }
+
+ /// A screen space point is defined in pixels.
+ /// The left-bottom of the screen is (0,0). The right-top is (screenWidth,screenHeight)
+ /// The z position is between 0...1. 0 is on the near plane. 1 is on the far plane
+
+ /// A viewport space point is normalized and relative to the camera
+ /// The left-bottom of the camera is (0,0). The top-right is (1,1)
+ /// The z position is between 0...1. 0 is on the near plane. 1 is on the far plane
+
+ /// Projects a World space point into screen space.
+ /// on return: canProject is true if the point could be projected to the screen (The point is inside the frustum)
+ Vector3f WorldToScreenPoint (const Vector3f& worldSpacePoint, bool* canProject = NULL) const;
+ /// Unprojects a screen space point into world space
+ Vector3f ScreenToWorldPoint (const Vector3f& screenSpacePoint) const;
+
+ /// Projects a world space point into viewport space
+ Vector3f WorldToViewportPoint (const Vector3f &worldSpace) const;
+ /// Unprojects a view port space into world space
+ Vector3f ViewportToWorldPoint (const Vector3f &viewPort) const;
+
+ /// Unprojects a view port space into camera space
+ Vector3f ViewportToCameraPoint (const Vector3f &viewPort) const;
+
+ // Converts a screen point into a world space ray
+ Ray ScreenPointToRay (const Vector2f& screenPos) const;
+ // Converts a viewport point into a world space ray
+ Ray ViewportPointToRay (const Vector2f& viewportPos) const;
+
+ // Converts a point between screen space and viewport space
+ Vector3f ScreenToViewportPoint (const Vector3f& screenPos) const;
+ Vector3f ViewportToScreenPoint (const Vector3f& viewPortPos) const;
+
+ // Calculates the distance between the left and right
+ // edges of the frustum pyramid at the far plane
+ float CalculateFarPlaneWorldSpaceLength () const;
+
+ // Calculates the distance between the left and right
+ // edges of the frustum pyramid at the near plane
+ float CalculateNearPlaneWorldSpaceLength () const;
+
+ void WindowSizeHasChanged ();
+
+ void AddImageFilter (const ImageFilter& filter);
+ void RemoveImageFilter (const ImageFilter& filter);
+
+ void ClearIntermediateRenderers( size_t startIndex = 0 );
+ IntermediateRenderers& GetIntermediateRenderers() { return *m_IntermediateRenderers; }
+
+ const Vector3f& GetVelocity () const { return m_Velocity; }
+
+ const Matrix4x4f& GetWorldToCameraMatrix () const;
+ Matrix4x4f GetCameraToWorldMatrix () const;
+
+ const Matrix4x4f& GetProjectionMatrix () const;
+ void GetImplicitProjectionMatrix (float overrideNearPlane, Matrix4x4f& outMatrix) const;
+ void GetImplicitProjectionMatrix (float overrideNearPlane, float overrideFarPlane, Matrix4x4f& outMatrix) const;
+
+ void GetClipToWorldMatrix( Matrix4x4f& outMatrix ) const;
+ const Matrix4x4f& GetWorldToClipMatrix() const;
+
+ void SetWorldToCameraMatrix (const Matrix4x4f& matrix);
+ void SetProjectionMatrix (const Matrix4x4f& matrix);
+
+ void ResetWorldToCameraMatrix () { m_ImplicitWorldToCameraMatrix = true; m_DirtyWorldToCameraMatrix = true; m_DirtyWorldToClipMatrix = true; }
+ void ResetProjectionMatrix () { m_ImplicitProjectionMatrix = true; m_DirtyProjectionMatrix = true; m_DirtyWorldToClipMatrix = true; }
+ bool IsImplicitWorldToCameraMatrix() const { return m_ImplicitWorldToCameraMatrix; }
+ bool IsImplicitProjectionMatrix() const { return m_ImplicitProjectionMatrix; }
+
+ void SetReplacementShader( Shader* shader, const std::string& replacementTag );
+ void ResetReplacementShader() { m_ReplacementShader.SetInstanceID(0); m_ReplacementTag.clear(); }
+ Shader *GetReplacementShader() const { return m_ReplacementShader; }
+ string GetReplacementShaderTag() const {return m_ReplacementTag; }
+
+ // Get/Set the texture to render into.
+ RenderTexture *GetTargetTexture () const { return m_TargetTexture; }
+ void SetTargetTexture (RenderTexture *tex);
+
+ void SetTargetBuffers (int colorCount, RenderSurfaceHandle* color, RenderSurfaceHandle depth, RenderTexture* originatedFrom);
+ void SetTargetBuffersScript (int colorCount, const ScriptingRenderBuffer* color, ScriptingRenderBuffer* depth);
+
+ // TODO: mrt support?
+ RenderSurfaceHandle GetTargetColorBuffer() const { return m_TargetColorBuffer[0]; }
+ RenderSurfaceHandle GetTargetDepthBuffer() const { return m_TargetDepthBuffer; }
+
+ const float *GetLayerCullDistances() const { return m_LayerCullDistances; }
+ void SetLayerCullDistances(float *layerCullDistances) {memcpy(m_LayerCullDistances,layerCullDistances,sizeof(float)*kNumLayers);}
+
+ bool GetLayerCullSpherical() const { return m_LayerCullSpherical; }
+ void SetLayerCullSpherical(bool enable) { m_LayerCullSpherical = enable; }
+
+ SortMode GetSortMode() const { return m_SortMode; }
+ void SetSortMode (SortMode m) { m_SortMode = m; }
+
+ // Get the current target. This can be different than the textureTarget if some image filters require
+ // A temporary buffer to render into.
+ RenderTexture *GetCurrentTargetTexture () const { return m_CurrentTargetTexture; }
+ void SetCurrentTargetTexture (RenderTexture* rt) { m_CurrentTargetTexture = rt; }
+
+ void TransformChanged ();
+ static void InitializeClass ();
+ static void CleanupClass () {}
+
+ void CopyFrom( const Camera& other );
+
+ void CalculateFarCullDistances (float* farCullDistances, float baseFarDistance) const;
+
+ enum DepthTextureModes {
+ kDepthTexDepthBit = (1<<0),
+ kDepthTexDepthNormalsBit = (1<<1),
+ };
+ UInt32 GetDepthTextureMode() const { return m_DepthTextureMode; }
+ void SetDepthTextureMode (UInt32 m) { m_DepthTextureMode = m; }
+
+ bool GetClearStencilAfterLightingPass() const { return m_ClearStencilAfterLightingPass; }
+ void SetClearStencilAfterLightingPass (bool clear) { m_ClearStencilAfterLightingPass = clear; }
+
+ RenderingPath CalculateRenderingPath() const;
+ bool CalculateNeedsToRenderIntoRT() const;
+ int CalculateAntiAliasingForRT() const;
+
+ bool GetUsesScreenForCompositing (bool forceIntoRT) const;
+
+
+ // Get the resolved skybox material we want to render with.
+ Material *GetSkyboxMaterial () const;
+
+ // Implementations in EditorCameraDrawing.cpp
+ #if UNITY_EDITOR
+
+ enum EditorDrawingMode {
+ kEditorDrawTextured = 0,
+ kEditorDrawWire = 1,
+ kEditorDrawTexturedWire = 2,
+ kEditorDrawRenderPaths = 3,
+ kEditorDrawLightmapResolution = 4,
+ kEditorDrawModeCount
+ };
+
+
+ void ClearEditorCamera (); // clears
+ void RenderEditorCamera (EditorDrawingMode mode, const DrawGridParameters* gridParam); // renders camera content (does not clear)
+ void FinishRenderingEditorCamera ();
+ void SetOnlyRenderIntermediateObjects() { m_OnlyRenderIntermediateObjects = true; }
+ static bool ShouldShowChannelErrors (const Camera* ptr);
+ void RenderEditorCameraFade (float fade);
+ void SetAnimateMaterials (bool animate);
+ bool GetAnimateMaterials () const { return m_AnimateMaterials; }
+ void SetAnimateMaterialsTime (float time);
+
+ bool IsFiltered (Unity::GameObject& gameObject) const;
+ void SetFilterMode (int filterMode) { m_FilterMode = filterMode; }
+ int GetFilterMode () const { return m_FilterMode; }
+
+ #endif
+
+// Texture* GetUmbraOcclusionBufferTexture ();
+
+private:
+
+ typedef void PerformRenderFunction (Camera& camera, RenderLoop& loop, CullResults& contents);
+ void DoRender( CullResults& cullResults, PerformRenderFunction* customRender, int renderFlags ); // Render all objects
+
+ void DoRenderPostLayers(); // Render any post-layers (before image effects)
+ void DoRenderGUILayer();
+
+ void DoClear (UInt32 gfxClearFlags);
+
+ // Behaviour stuff
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+
+ void SetCameraShaderProps();
+
+ void UpdateDepthTextures (const CullResults& cullResults);
+ void RenderDepthTexture (const CullResults& cullResults, RenderTexture** rt, RenderTextureFormat format, Shader* shader, const ColorRGBAf& clearColor, ShaderLab::FastPropertyName name);
+
+ void CleanupDepthTextures ();
+
+ void DisplayHDRWarnings() const;
+
+ Rectf GetCameraRect (bool zeroOrigin) const;
+
+ bool GetRenderImmediateObjects () const;
+
+private:
+
+ mutable Matrix4x4f m_WorldToCameraMatrix;
+ mutable Matrix4x4f m_ProjectionMatrix;
+ mutable Matrix4x4f m_WorldToClipMatrix;
+
+
+ // NOTE: whenever adding new camera properties, make sure they are copied as
+ // appropriate in CopyFrom(), and extend CameraCopyFromWorks runtime test
+ // to cover it.
+
+
+ RenderLoop* m_RenderLoop;
+
+ PPtr<RenderTexture> m_TargetTexture; ///< The texture to render this camera into
+
+ RenderSurfaceHandle m_TargetColorBuffer[kMaxSupportedRenderTargets];
+ int m_TargetColorBufferCount;
+ RenderSurfaceHandle m_TargetDepthBuffer;
+ // this is here to set as active RT along with render buffers
+ // dx uses current rt to check if we render onscreen (and tweak projection/offsets/etc)
+ // image filters use target RT in calculations of rt to draw to
+ // while ideally we want simple IsRenderingOnscreen + image effects to use render buffers
+ // we want it to work naow
+ RenderTexture* m_TargetBuffersOriginatedFrom;
+
+ RenderTexture* m_DepthTexture;
+ RenderTexture* m_DepthNormalsTexture;
+
+ RenderTexture* m_CurrentTargetTexture; // The texture we're rendering into _right now_
+
+ PPtr<Shader> m_ReplacementShader;
+ std::string m_ReplacementTag;
+
+ IntermediateRenderers* m_IntermediateRenderers;
+
+ unsigned int m_ClearFlags; ///< enum { Skybox = 1, Solid Color = 2, Depth only = 3, Don't Clear = 4 }
+ ColorRGBAf m_BackGroundColor; ///< The color to which camera clears the screen
+ Rectf m_NormalizedViewPortRect;
+
+ BitField m_CullingMask; ///< Which layers the camera does render
+ BitField m_EventMask; ///< Which layers receive events
+
+ float m_Depth; ///< A camera with a larger depth is drawn on top of a camera with a smaller depth range {-100, 100}
+ Vector3f m_Velocity;
+ Vector3f m_LastPosition;
+
+ float m_OrthographicSize;
+ float m_FieldOfView; ///< Field of view of the camera range { 0.00001, 179 }
+ float m_NearClip; ///< Near clipping plane
+ float m_FarClip; ///< Far clipping plane
+ int m_RenderingPath; ///< enum { Use Player Settings = -1, Vertex Lit=0, Forward=1, Deferred Lighting=2 } Rendering path to use.
+
+ float m_LayerCullDistances[kNumLayers];
+ float m_Aspect;
+ SortMode m_SortMode;
+
+ CubemapFace m_CurrentTargetFace; // current cubemap face we're rendering into (only used while rendering into a cubemap)
+
+ UInt32 m_DepthTextureMode;
+
+ mutable bool m_DirtyWorldToCameraMatrix;
+ mutable bool m_DirtyProjectionMatrix;
+ mutable bool m_DirtyWorldToClipMatrix;
+ bool m_ImplicitWorldToCameraMatrix;
+ bool m_ImplicitProjectionMatrix;
+ bool m_ImplicitAspect;
+ bool m_Orthographic; ///< Is camera orthographic?
+ bool m_OcclusionCulling;
+ bool m_LayerCullSpherical;
+ bool m_HDR;
+ bool m_UsingHDR;
+ bool m_IsRendering;
+ bool m_ClearStencilAfterLightingPass;
+
+
+ // NOTE: whenever adding new camera properties, make sure they are copied as
+ // appropriate in CopyFrom(), and extend CameraCopyFromWorks runtime test
+ // to cover it.
+
+
+ void SetTargetTextureBuffers(RenderTexture* tex, int colorCount, RenderSurfaceHandle* color, RenderSurfaceHandle depth, RenderTexture* rbOrigin);
+
+ #if UNITY_EDITOR
+ int m_FilterMode;
+ bool m_OnlyRenderIntermediateObjects;
+ bool m_IsSceneCamera;
+ CameraRenderOldState m_OldCameraState;
+ bool m_AnimateMaterials;
+ float m_AnimateMaterialsTime;
+ #endif
+};
+
+void StoreRenderState (CameraRenderOldState& state);
+void RestoreRenderState (CameraRenderOldState& state);
+
+Shader* GetCameraDepthTextureShader ();
+Shader* GetCameraDepthNormalsTextureShader ();
+
+void ClearWithSkybox (bool clearDepth, Camera const* camera);
+
+Rectf GetCameraOrWindowRect (const Camera* camera);
+
+#endif
diff --git a/Runtime/Camera/CameraCullingParameters.h b/Runtime/Camera/CameraCullingParameters.h
new file mode 100644
index 0000000..5936432
--- /dev/null
+++ b/Runtime/Camera/CameraCullingParameters.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "ShaderReplaceData.h"
+#include "Runtime/Utilities/EnumFlags.h"
+#include "UnityPrefix.h"
+#include "Camera.h"
+#include "External/shaderlab/Library/shaderlab.h"
+
+
+namespace Umbra { class DebugRenderer; }
+
+enum CullFlag
+{
+ kCullFlagForceEvenIfCameraIsNotActive = 1 << 0,
+ kCullFlagOcclusionCull = 1 << 1,
+ kCullFlagNeedsLighting = 1 << 2,
+};
+
+struct CameraCullingParameters
+{
+ Camera* cullingCamera;
+ ShaderReplaceData explicitShaderReplace;
+ CullFlag cullFlag;
+ Umbra::DebugRenderer* umbraDebugRenderer;
+ UInt32 umbraDebugFlags;
+
+ CameraCullingParameters (Camera& cam, CullFlag flag)
+ {
+ cullingCamera = &cam;
+ cullFlag = flag;
+ umbraDebugRenderer = NULL;
+ umbraDebugFlags = 0;
+ }
+};
+
+ENUM_FLAGS(CullFlag);
diff --git a/Runtime/Camera/CameraUtil.cpp b/Runtime/Camera/CameraUtil.cpp
new file mode 100644
index 0000000..1bdcd4b
--- /dev/null
+++ b/Runtime/Camera/CameraUtil.cpp
@@ -0,0 +1,452 @@
+#include "UnityPrefix.h"
+#include "CameraUtil.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Math/Rect.h"
+#include "RenderManager.h"
+#include "Runtime/Graphics/ScreenManager.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Graphics/RenderTexture.h"
+#include "Runtime/Graphics/ScreenManager.h"
+
+void FlipScreenRectIfNeeded( const GfxDevice& device, int screenviewcoord[4] )
+{
+ // Flip viewport rect vertically for D3D or dashboard widgets.
+ // But only do it when rendering to screen, not a texture!
+ if( (!device.UsesOpenGLTextureCoords() || device.GetInvertProjectionMatrix()) && device.GetActiveRenderTexture() == NULL )
+ {
+ int height;
+ #if GFX_SUPPORTS_D3D9
+ // On D3D, always use height of current actual render target. Others may be off, particularly in editor's game view
+ if (device.GetRenderer() == kGfxRendererD3D9 || device.GetRenderer() == kGfxRendererD3D11)
+ height = device.GetCurrentTargetHeight();
+ else
+ #endif
+ // Use screen height, not render manager's window height. In editor's GameView, the window height might be smaller.
+ height = GetScreenManager().GetHeight();
+
+ int miny = height - screenviewcoord[1];
+ int maxy = height - (screenviewcoord[3] + screenviewcoord[1]);
+ if (maxy < miny)
+ std::swap(maxy, miny);
+ miny = std::max(miny, 0);
+ screenviewcoord[1] = miny;
+ screenviewcoord[3] = maxy - miny;
+ DebugAssertIf( screenviewcoord[1] < 0 || screenviewcoord[3] < 0 );
+ }
+ #if GFX_USES_VIEWPORT_OFFSET
+ if( device.GetActiveRenderTexture() == NULL )
+ {
+ float xOffs, yOffs;
+ device.GetViewportOffset(xOffs, yOffs);
+ screenviewcoord[0] += xOffs;
+ screenviewcoord[1] += yOffs;
+ }
+ #endif
+ #if UNITY_WP8
+ ScreenOrientation const screenOrientation = GetScreenManager().GetScreenOrientation();
+ bool renderToTexture = device.GetActiveRenderTexture() != NULL;
+ bool rotated = (screenOrientation == ScreenOrientation::kLandscapeLeft) || (screenOrientation == ScreenOrientation::kLandscapeRight);
+ if (rotated && !renderToTexture)
+ {
+ float x = screenviewcoord[0];
+ float y = screenviewcoord[1];
+
+ if (screenOrientation == ScreenOrientation::kLandscapeRight)
+ {
+ float width = screenviewcoord[2];
+ float targetWidth = device.GetActiveRenderTexture() ? device.GetCurrentTargetWidth() : GetScreenManager().GetWidth();
+ screenviewcoord[0] = y;
+ screenviewcoord[1] = targetWidth - x - width;
+ }
+ else if (screenOrientation == ScreenOrientation::kLandscapeLeft)
+ {
+ float height = screenviewcoord[3];
+ float targetHeight = device.GetActiveRenderTexture() ? device.GetCurrentTargetHeight() : GetScreenManager().GetHeight();
+ screenviewcoord[0] = targetHeight - y - height;
+ screenviewcoord[1] = x;
+ }
+
+ std::swap(screenviewcoord[2], screenviewcoord[3]);
+ }
+ #endif
+}
+
+#if UNITY_WP8
+
+void RotateScreenIfNeeded(Matrix4x4f& projection)
+{
+ ScreenOrientation const screenOrientation = GetScreenManager().GetScreenOrientation();
+ if (screenOrientation == ScreenOrientation::kLandscapeLeft)
+ {
+ Vector4f const x = projection.GetRow(0);
+ Vector4f const y = projection.GetRow(1);
+ projection.SetRow(0, y);
+ projection.SetRow(1, -x);
+ }
+ else if (screenOrientation == ScreenOrientation::kLandscapeRight)
+ {
+ Vector4f const x = projection.GetRow(0);
+ Vector4f const y = projection.GetRow(1);
+ projection.SetRow(0, -y);
+ projection.SetRow(1, x);
+ }
+}
+
+static void RotatePointIfNeeded(Vector3f& point, bool unrotate)
+{
+ ScreenOrientation const screenOrientation = GetScreenManager().GetScreenOrientation();
+ if (screenOrientation == ScreenOrientation::kLandscapeLeft)
+ {
+ auto const x = point.x;
+ auto const y = point.y;
+
+ if (unrotate)
+ {
+ point.x = y;
+ point.y = -x;
+ }
+ else
+ {
+ point.x = -y;
+ point.y = x;
+ }
+ }
+ else if (screenOrientation == ScreenOrientation::kLandscapeRight)
+ {
+ auto const x = point.x;
+ auto const y = point.y;
+
+ if (unrotate)
+ {
+ point.x = -y;
+ point.y = x;
+ }
+ else
+ {
+ point.x = y;
+ point.y = -x;
+ }
+ }
+}
+
+#endif
+
+void CalcPixelMatrix (const Rectf& screenRect, Matrix4x4f &out)
+{
+ out.SetOrtho( screenRect.x, screenRect.GetRight(), screenRect.y, screenRect.GetBottom(), -1.0f, 100.0f );
+ #if UNITY_WP8
+ RotateScreenIfNeeded(out);
+ #endif
+}
+
+void ApplyTexelOffsetsToPixelMatrix( bool invertYTexelOffset, Matrix4x4f& matrix )
+{
+ float offsetX, offsetY;
+ GetHalfTexelOffsets( offsetX, offsetY );
+ if( invertYTexelOffset )
+ offsetY = -offsetY;
+
+ matrix.Get(0,3) -= offsetX * matrix.Get(0,0);
+ matrix.Get(1,3) -= offsetY * matrix.Get(1,1);
+}
+
+
+void LoadPixelMatrix( const Rectf& screenRect, GfxDevice& device, bool setMatrix, bool invertYTexelOffset )
+{
+ Matrix4x4f ortho;
+ CalcPixelMatrix( screenRect, ortho );
+ ApplyTexelOffsetsToPixelMatrix( invertYTexelOffset, ortho );
+ device.SetProjectionMatrix (ortho);
+ if( setMatrix )
+ device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity
+}
+
+void GetHalfTexelOffsets( float& outx, float& outy )
+{
+ GfxDevice& device = GetGfxDevice();
+ if( device.UsesHalfTexelOffset() )
+ {
+ outx = 0.5f;
+ outy = 0.5f;
+ if( device.GetActiveRenderTexture() == NULL )
+ outy = -outy;
+ }
+ else
+ {
+ outx = outy = 0.0f;
+ }
+}
+
+void LoadFullScreenOrthoMatrix( float nearPlane, float farPlane, bool forceNoHalfTexelOffset )
+{
+ GfxDevice& device = GetGfxDevice();
+ float offsetX, offsetY;
+ if( device.UsesHalfTexelOffset() && !forceNoHalfTexelOffset)
+ {
+ // viewport of the device should always have the correct size of
+ // the render target (both when rendering to screen and to a
+ // render texture), so calc half texel offset from that.
+ int viewport[4];
+ device.GetViewport(viewport);
+ int width = viewport[2];
+ int height = viewport[3];
+ offsetX = width ? (0.5f / width) : 0.0f;
+ offsetY = height ? (0.5f / height) : 0.0f;
+ if( device.GetActiveRenderTexture() == NULL )
+ offsetY = -offsetY;
+ }
+ else
+ {
+ offsetX = offsetY = 0.0f;
+ }
+ Matrix4x4f matrix;
+ matrix.SetOrtho( offsetX, 1.0f + offsetX, offsetY, 1.0f + offsetY, nearPlane, farPlane );
+ device.SetProjectionMatrix (matrix);
+ device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity
+}
+
+void SetupPixelCorrectCoordinates()
+{
+ GfxDevice& device = GetGfxDevice();
+
+ int viewcoords[4];
+ Rectf r = GetRenderManager ().GetWindowRect();
+ RectfToViewport( r, viewcoords );
+ FlipScreenRectIfNeeded( device, viewcoords );
+ device.SetViewport( viewcoords[0], viewcoords[1], viewcoords[2], viewcoords[3] );
+
+ LoadPixelMatrix( r, device, true, false );
+}
+
+void RectfToViewport( const Rectf& r, int viewPort[4] )
+{
+ // We have to take care that the viewport doesn't exceed the buffer size (case 569703).
+ // Bad rounding to integer makes D3D11 crash and burn.
+ viewPort[0] = RoundfToInt (r.x);
+ viewPort[1] = RoundfToInt (r.y);
+ viewPort[2] = RoundfToIntPos (r.GetRight ()) - viewPort[0];
+ viewPort[3] = RoundfToIntPos (r.GetBottom ()) - viewPort[1];
+}
+
+
+bool CameraProject( const Vector3f& p, const Matrix4x4f& cameraToWorld, const Matrix4x4f& worldToClip, const int viewport[4], Vector3f& outP )
+{
+ Vector3f clipPoint;
+ if( worldToClip.PerspectiveMultiplyPoint3( p, clipPoint ) )
+ {
+ Vector3f cameraPos = cameraToWorld.GetPosition();
+ Vector3f dir = p - cameraPos;
+ // The camera/projection matrices follow OpenGL convention: positive Z is towards the viewer.
+ // So negate it to get into Unity convention.
+ Vector3f forward = -cameraToWorld.GetAxisZ();
+ float dist = Dot( dir, forward );
+
+ #if UNITY_WP8
+ RotatePointIfNeeded(clipPoint, false);
+ #endif
+
+ outP.x = viewport[0] + (1.0f + clipPoint.x) * viewport[2] * 0.5f;
+ outP.y = viewport[1] + (1.0f + clipPoint.y) * viewport[3] * 0.5f;
+ //outP.z = (1.0f + clipPoint.z) * 0.5f;
+ outP.z = dist;
+
+ return true;
+ }
+
+ outP.Set( 0.0f, 0.0f, 0.0f );
+ return false;
+}
+
+bool CameraUnProject( const Vector3f& p, const Matrix4x4f& cameraToWorld, const Matrix4x4f& clipToWorld, const int viewport[4], Vector3f& outP )
+{
+ // pixels to -1..1
+ Vector3f in;
+ in.x = (p.x - viewport[0]) * 2.0f / viewport[2] - 1.0f;
+ in.y = (p.y - viewport[1]) * 2.0f / viewport[3] - 1.0f;
+ // It does not matter where the point we unproject lies in depth; so we choose 0.95, which
+ // is further than near plane and closer than far plane, for precision reasons.
+ // In a perspective camera setup (near=0.1, far=1000), a point at 0.95 projected depth is about
+ // 5 units from the camera.
+ in.z = 0.95f;
+
+ #if UNITY_WP8
+ RotatePointIfNeeded(in, true);
+ #endif
+
+ Vector3f pointOnPlane;
+ if( clipToWorld.PerspectiveMultiplyPoint3( in, pointOnPlane ) )
+ {
+ // Now we have a point on the plane perpendicular to the viewing direction. We need to return the one that is on the line
+ // towards this point, and at p.z distance along camera's viewing axis.
+ Vector3f cameraPos = cameraToWorld.GetPosition();
+ Vector3f dir = pointOnPlane - cameraPos;
+
+ // The camera/projection matrices follow OpenGL convention: positive Z is towards the viewer.
+ // So negate it to get into Unity convention.
+ Vector3f forward = -cameraToWorld.GetAxisZ();
+ float distToPlane = Dot( dir, forward );
+ if( Abs(distToPlane) >= 1.0e-6f )
+ {
+ bool isPerspective = (clipToWorld.m_Data[3] != 0.0f || clipToWorld.m_Data[7] != 0.0f || clipToWorld.m_Data[11] != 0.0f || clipToWorld.m_Data[15] != 1.0f);
+ if( isPerspective )
+ {
+ dir *= p.z / distToPlane;
+ outP = cameraPos + dir;
+ }
+ else
+ {
+ outP = pointOnPlane - forward * (distToPlane - p.z);
+ }
+ return true;
+ }
+ }
+
+ outP.Set( 0.0f, 0.0f, 0.0f );
+ return false;
+}
+
+void SetGLViewport (const Rectf& pixelRect)
+{
+ Rectf tempPixelRect (pixelRect);
+ int viewport[4];
+
+ GfxDevice& device = GetGfxDevice();
+
+#if UNITY_EDITOR
+ // Handle game view's aspect ratio dropdown, but only if we're not rendering into a render
+ // texture.
+ if( device.GetActiveRenderTexture() == NULL )
+ {
+ Rectf renderRect = GetRenderManager().GetWindowRect();
+ tempPixelRect.x += renderRect.x;
+ tempPixelRect.y += renderRect.y;
+ tempPixelRect.Clamp (renderRect);
+ }
+#endif
+
+ viewport[0] = RoundfToInt( tempPixelRect.x );
+ viewport[1] = RoundfToInt( tempPixelRect.y );
+ viewport[2] = RoundfToIntPos( tempPixelRect.Width() );
+ viewport[3] = RoundfToIntPos( tempPixelRect.Height() );
+ FlipScreenRectIfNeeded( device, viewport );
+ device.SetViewport( viewport[0], viewport[1], viewport[2], viewport[3] );
+
+}
+
+void ExtractProjectionPlanes( const Matrix4x4f& finalMatrix, Plane* outPlanes )
+{
+ float tmpVec[4];
+ float otherVec[4];
+
+ tmpVec[0] = finalMatrix.Get (3, 0);
+ tmpVec[1] = finalMatrix.Get (3, 1);
+ tmpVec[2] = finalMatrix.Get (3, 2);
+ tmpVec[3] = finalMatrix.Get (3, 3);
+
+ otherVec[0] = finalMatrix.Get (0, 0);
+ otherVec[1] = finalMatrix.Get (0, 1);
+ otherVec[2] = finalMatrix.Get (0, 2);
+ otherVec[3] = finalMatrix.Get (0, 3);
+
+ // left & right
+ outPlanes[kPlaneFrustumLeft].SetABCD ( otherVec[0] + tmpVec[0], otherVec[1] + tmpVec[1], otherVec[2] + tmpVec[2], otherVec[3] + tmpVec[3]);
+ outPlanes[kPlaneFrustumLeft].NormalizeUnsafe();
+ outPlanes[kPlaneFrustumRight].SetABCD (-otherVec[0] + tmpVec[0], -otherVec[1] + tmpVec[1], -otherVec[2] + tmpVec[2], -otherVec[3] + tmpVec[3]);
+ outPlanes[kPlaneFrustumRight].NormalizeUnsafe();
+
+ // bottom & top
+ otherVec[0] = finalMatrix.Get (1, 0);
+ otherVec[1] = finalMatrix.Get (1, 1);
+ otherVec[2] = finalMatrix.Get (1, 2);
+ otherVec[3] = finalMatrix.Get (1, 3);
+
+ outPlanes[kPlaneFrustumBottom].SetABCD ( otherVec[0] + tmpVec[0], otherVec[1] + tmpVec[1], otherVec[2] + tmpVec[2], otherVec[3] + tmpVec[3]);
+ outPlanes[kPlaneFrustumBottom].NormalizeUnsafe();
+ outPlanes[kPlaneFrustumTop].SetABCD (-otherVec[0] + tmpVec[0], -otherVec[1] + tmpVec[1], -otherVec[2] + tmpVec[2], -otherVec[3] + tmpVec[3]);
+ outPlanes[kPlaneFrustumTop].NormalizeUnsafe();
+
+ otherVec[0] = finalMatrix.Get (2, 0);
+ otherVec[1] = finalMatrix.Get (2, 1);
+ otherVec[2] = finalMatrix.Get (2, 2);
+ otherVec[3] = finalMatrix.Get (2, 3);
+
+ // near & far
+ outPlanes[kPlaneFrustumNear].SetABCD ( otherVec[0] + tmpVec[0], otherVec[1] + tmpVec[1], otherVec[2] + tmpVec[2], otherVec[3] + tmpVec[3]);
+ outPlanes[kPlaneFrustumNear].NormalizeUnsafe();
+ outPlanes[kPlaneFrustumFar].SetABCD (-otherVec[0] + tmpVec[0], -otherVec[1] + tmpVec[1], -otherVec[2] + tmpVec[2], -otherVec[3] + tmpVec[3]);
+ outPlanes[kPlaneFrustumFar].NormalizeUnsafe();
+}
+
+void ExtractProjectionNearPlane( const Matrix4x4f& finalMatrix, Plane* outPlane )
+{
+ float tmpVec[4];
+ float otherVec[4];
+
+ tmpVec[0] = finalMatrix.Get (3, 0);
+ tmpVec[1] = finalMatrix.Get (3, 1);
+ tmpVec[2] = finalMatrix.Get (3, 2);
+ tmpVec[3] = finalMatrix.Get (3, 3);
+
+ otherVec[0] = finalMatrix.Get (2, 0);
+ otherVec[1] = finalMatrix.Get (2, 1);
+ otherVec[2] = finalMatrix.Get (2, 2);
+ otherVec[3] = finalMatrix.Get (2, 3);
+
+ // near
+ outPlane->SetABCD ( otherVec[0] + tmpVec[0], otherVec[1] + tmpVec[1], otherVec[2] + tmpVec[2], otherVec[3] + tmpVec[3]);
+ outPlane->NormalizeUnsafe();
+}
+
+
+void SetClippingPlaneShaderProps()
+{
+ GfxDevice& device = GetGfxDevice();
+ BuiltinShaderParamValues& params = device.GetBuiltinParamValues();
+
+ const Matrix4x4f* viewMatrix = (const Matrix4x4f*)device.GetViewMatrix();
+ const Matrix4x4f* deviceProjMatrix = (const Matrix4x4f*)device.GetDeviceProjectionMatrix();
+ Matrix4x4f viewProj;
+ MultiplyMatrices4x4 (deviceProjMatrix, viewMatrix, &viewProj);
+ Plane planes[6];
+ ExtractProjectionPlanes (viewProj, planes);
+ params.SetVectorParam (kShaderVecCameraWorldClipPlanes0, (const Vector4f&)planes[0]);
+ params.SetVectorParam (kShaderVecCameraWorldClipPlanes1, (const Vector4f&)planes[1]);
+ params.SetVectorParam (kShaderVecCameraWorldClipPlanes2, (const Vector4f&)planes[2]);
+ params.SetVectorParam (kShaderVecCameraWorldClipPlanes3, (const Vector4f&)planes[3]);
+ params.SetVectorParam (kShaderVecCameraWorldClipPlanes4, (const Vector4f&)planes[4]);
+ params.SetVectorParam (kShaderVecCameraWorldClipPlanes5, (const Vector4f&)planes[5]);
+}
+
+
+DeviceMVPMatricesState::DeviceMVPMatricesState()
+{
+ GfxDevice& device = GetGfxDevice();
+ CopyMatrix(device.GetViewMatrix(), m_View.GetPtr());
+ CopyMatrix(device.GetWorldMatrix(), m_World.GetPtr());
+ CopyMatrix(device.GetProjectionMatrix(), m_Proj.GetPtr());
+}
+
+DeviceMVPMatricesState::~DeviceMVPMatricesState()
+{
+ GfxDevice& device = GetGfxDevice();
+ device.SetViewMatrix(m_View.GetPtr());
+ device.SetWorldMatrix(m_World.GetPtr());
+ device.SetProjectionMatrix(m_Proj);
+ SetClippingPlaneShaderProps();
+}
+
+DeviceViewProjMatricesState::DeviceViewProjMatricesState()
+{
+ GfxDevice& device = GetGfxDevice();
+ CopyMatrix(device.GetViewMatrix(), m_View.GetPtr());
+ CopyMatrix(device.GetProjectionMatrix(), m_Proj.GetPtr());
+}
+
+DeviceViewProjMatricesState::~DeviceViewProjMatricesState()
+{
+ GfxDevice& device = GetGfxDevice();
+ device.SetViewMatrix(m_View.GetPtr());
+ device.SetProjectionMatrix(m_Proj);
+ SetClippingPlaneShaderProps();
+}
diff --git a/Runtime/Camera/CameraUtil.h b/Runtime/Camera/CameraUtil.h
new file mode 100644
index 0000000..ee747c2
--- /dev/null
+++ b/Runtime/Camera/CameraUtil.h
@@ -0,0 +1,61 @@
+#ifndef CAMERA_UTIL_H
+#define CAMERA_UTIL_H
+
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Modules/ExportModules.h"
+
+class GfxDevice;
+class Vector3f;
+class Plane;
+
+void LoadPixelMatrix( const Rectf& screenRect, GfxDevice& device, bool setMatrix, bool invertYTexelOffset );
+void CalcPixelMatrix (const Rectf& screenRect, Matrix4x4f &out);
+void ApplyTexelOffsetsToPixelMatrix( bool invertYTexelOffset, Matrix4x4f& matrix );
+void LoadFullScreenOrthoMatrix( float nearPlane = -1.0f, float farPlane = 100.0f, bool forceNoHalfTexelOffset = false );
+void GetHalfTexelOffsets( float& outx, float& outy );
+void SetupPixelCorrectCoordinates();
+
+void RectfToViewport( const Rectf& r, int viewPort[4] );
+void FlipScreenRectIfNeeded( const GfxDevice& device, int screenviewcoord[4] );
+void SetGLViewport (const Rectf& pixelRect);
+
+// World point to screen point
+// p = world point
+// outP = result (x, y = in pixels inside the viewport, z = world space distance from the camera)
+//
+// sets outP to (0,0,0) if fails.
+bool CameraProject( const Vector3f& p, const Matrix4x4f& cameraToWorld, const Matrix4x4f& worldToClip, const int viewport[4], Vector3f& outP );
+
+// Screen point to world point
+// p = screen point (x, y = in pixels inside the viewport, z = world space distance from the camera)
+//
+// sets outP to (0,0,0) if fails.
+bool CameraUnProject( const Vector3f& p, const Matrix4x4f& cameraToWorld, const Matrix4x4f& clipToWorld, const int viewport[4], Vector3f& outP );
+
+// Extract frustum planes from Projection matrix
+void EXPORT_COREMODULE ExtractProjectionPlanes (const Matrix4x4f& projection, Plane* planes);
+void EXPORT_COREMODULE ExtractProjectionNearPlane (const Matrix4x4f& projection, Plane* outPlane);
+
+void SetClippingPlaneShaderProps();
+
+
+class DeviceMVPMatricesState {
+public:
+ DeviceMVPMatricesState ();
+ ~DeviceMVPMatricesState();
+ const Matrix4x4f& GetView() const { return m_View; }
+ const Matrix4x4f& GetProj() const { return m_Proj; }
+private:
+ Matrix4x4f m_World, m_View, m_Proj;
+};
+
+class DeviceViewProjMatricesState {
+public:
+ DeviceViewProjMatricesState ();
+ ~DeviceViewProjMatricesState();
+private:
+ Matrix4x4f m_View, m_Proj;
+};
+
+#endif
diff --git a/Runtime/Camera/CullResults.cpp b/Runtime/Camera/CullResults.cpp
new file mode 100644
index 0000000..7b345b1
--- /dev/null
+++ b/Runtime/Camera/CullResults.cpp
@@ -0,0 +1,81 @@
+#include "UnityPrefix.h"
+#include "CullResults.h"
+#include "ShadowCulling.h"//@TODO: Remove
+#include "UmbraBackwardsCompatibility.h"
+
+CullResults::CullResults() : shadowCullData(NULL), treeSceneNodes(kMemTempAlloc), treeBoundingBoxes(kMemTempAlloc)
+{
+}
+
+CullResults::~CullResults()
+{
+ Umbra::Visibility* umbra = sceneCullingOutput.umbraVisibility;
+ if (umbra != NULL)
+ {
+ int* clusterBuffer = (int*)umbra->getOutputClusters()->getPtr();
+ UNITY_FREE(kMemTempAlloc, clusterBuffer);
+
+ Umbra::IndexList* outputObjects = umbra->getOutputObjects();
+ Umbra::OcclusionBuffer* outputBuffer = umbra->getOutputBuffer();
+ Umbra::IndexList* outputCluster = umbra->getOutputClusters();
+
+ UNITY_DELETE(outputObjects, kMemTempAlloc);
+ UNITY_DELETE(outputBuffer, kMemTempAlloc);
+ UNITY_DELETE(outputCluster, kMemTempAlloc);
+ UNITY_DELETE(umbra, kMemTempAlloc);
+ }
+
+ DestroyCullingOutput(sceneCullingOutput);
+
+ UNITY_DELETE(shadowCullData, kMemTempAlloc);
+}
+
+void InitIndexList (IndexList& list, size_t count)
+{
+ int* array = (int*)UNITY_MALLOC (kMemTempAlloc, count * sizeof(int));
+ list = IndexList (array, 0, count);
+}
+
+void DestroyIndexList (IndexList& list)
+{
+ UNITY_FREE(kMemTempAlloc, list.indices);
+ list.indices = NULL;
+}
+
+void CreateCullingOutput (const RendererCullData* rendererCullData, CullingOutput& cullingOutput)
+{
+ for(int i=0;i<kVisibleListCount;i++)
+ InitIndexList(cullingOutput.visible[i], rendererCullData[i].rendererCount);
+}
+
+void DestroyCullingOutput (CullingOutput& cullingOutput)
+{
+ for (int i=0;i<kVisibleListCount;i++)
+ DestroyIndexList (cullingOutput.visible[i]);
+}
+
+void CullResults::Init (const UmbraTomeData& tomeData, const RendererCullData* rendererCullData)
+{
+ CreateCullingOutput(rendererCullData, sceneCullingOutput);
+
+ if (tomeData.tome != NULL)
+ {
+ size_t staticCount = rendererCullData[kStaticRenderers].rendererCount;
+ Assert(staticCount == 0 || staticCount == UMBRA_TOME_METHOD(tomeData, getObjectCount()));
+ size_t clusterCount = UMBRA_TOME_METHOD(tomeData, getClusterCount());
+
+ int* clusterArray = (int*)UNITY_MALLOC(kMemTempAlloc, clusterCount * sizeof(int));
+
+ Umbra::IndexList* staticList = UNITY_NEW(Umbra::IndexList (sceneCullingOutput.visible[kStaticRenderers].indices, staticCount), kMemTempAlloc);
+ Umbra::OcclusionBuffer* occlusionBuffer = UNITY_NEW(Umbra::OcclusionBuffer, kMemTempAlloc);
+ Umbra::Visibility* umbraVisibility = UNITY_NEW(Umbra::Visibility(staticList, occlusionBuffer), kMemTempAlloc);
+ Umbra::IndexList* clusterList = UNITY_NEW(Umbra::IndexList (clusterArray, clusterCount), kMemTempAlloc);
+ umbraVisibility->setOutputClusters(clusterList);
+
+ sceneCullingOutput.umbraVisibility = umbraVisibility;
+ }
+ else
+ {
+ sceneCullingOutput.umbraVisibility = NULL;
+ }
+}
diff --git a/Runtime/Camera/CullResults.h b/Runtime/Camera/CullResults.h
new file mode 100644
index 0000000..4905de6
--- /dev/null
+++ b/Runtime/Camera/CullResults.h
@@ -0,0 +1,153 @@
+#pragma once
+
+#include "BaseRenderer.h"
+#include "Lighting.h"
+#include "ShaderReplaceData.h"
+#include "CullingParameters.h"
+#include "Runtime/Math/Rect.h"
+#include "UmbraTomeData.h"
+#include "SceneNode.h"
+
+class Light;
+struct ShadowCullData;
+struct ShadowedLight;
+namespace Umbra { class Visibility; class Tome; }
+
+
+struct VisibleNode : public TransformInfo
+{
+ void SetTransformInfo(const TransformInfo& src) { TransformInfo::operator=(src); }
+ BaseRenderer* renderer;
+ float lodFade;
+};
+
+struct ActiveLight
+{
+ Light* light;
+ ShadowedLight* shadowedLight;
+
+#if ENABLE_SHADOWS
+ bool insideShadowRange;
+#endif
+
+ // For vertex lights we have to keep all lights around when doing per-object culling.
+ // This is because vertex lights can still affect objects (vertex interpolation on large triangles) although the light itself might be completely invisible.
+ bool isVisibleInPrepass;
+ int lightmappingForRender;
+ UInt32 cullingMask;
+ bool intersectsNear;
+ bool intersectsFar;
+ AABB boundingBox;
+ Rectf screenRect;
+ bool hasCookie;
+ int lightRenderMode;
+ int lightType;
+
+ // Some lights are offscreen
+ bool isOffscreenVertexLight;
+ float visibilityFade;
+};
+
+template <typename T>
+struct CullingDynamicArray : public dynamic_array<T, AlignOfType<T>::align, kMemTempAllocId> {};
+
+struct ActiveLights
+{
+ typedef CullingDynamicArray<ActiveLight> Array;
+ Array lights;
+
+ // If there is a main directional light, it will be the first one in lights.
+ bool hasMainLight;
+
+ // Lights are sorted by type in the following order
+ size_t numDirLights;
+ size_t numSpotLights;
+ size_t numPointLights;
+ size_t numOffScreenSpotLights;
+ size_t numOffScreenPointLights;
+};
+
+struct ShadowedLight
+{
+ // Index into CullResults.activeLights array
+ int lightIndex;
+};
+
+typedef CullingDynamicArray<VisibleNode> VisibleNodes;
+typedef CullingDynamicArray<UInt32> ObjectLightIndices;
+typedef CullingDynamicArray<UInt32> ObjectLightOffsets;
+typedef dynamic_array<ShadowedLight> ShadowedLights;
+
+/// CullResults lives during the entire duration of rendering one frame.
+/// Shadow culling uses data from the frustum/occlusion cull pass
+/// The render loop uses CullResults to render the scene.
+struct CullResults
+{
+ CullResults();
+ ~CullResults();
+
+ void Init (const UmbraTomeData& tome, const RendererCullData* rendererCullData);
+
+ CullingOutput sceneCullingOutput;
+ VisibleNodes nodes;
+
+ // All lights that might affect any visible objects
+ ActiveLights activeLights;
+
+ // Forward rendering needs to know on a per renderer basis which lights affect it.
+ ObjectLightIndices forwardLightIndices; // index array for all objects
+ ObjectLightOffsets forwardLightOffsets; // offset for each object (in the forwardLightIndices array)
+
+ // All lights that cast shadows on any objects in the scene
+ ShadowedLights shadowedLights;
+
+ SceneCullingParameters sceneCullParameters;
+
+ CullingDynamicArray<UInt8> lodMasks;
+ CullingDynamicArray<float> lodFades;
+
+ dynamic_array<SceneNode> treeSceneNodes;
+ dynamic_array<AABB> treeBoundingBoxes;
+
+ ///@TODO: Whats up with this thing? Does seem related...
+ ShadowCullData* shadowCullData;
+
+ ShaderReplaceData shaderReplaceData;
+};
+
+void InitIndexList (IndexList& list, size_t count);
+void DestroyIndexList (IndexList& list);
+
+void CreateCullingOutput (const RendererCullData* rendererCullData, CullingOutput& cullingOutput);
+void DestroyCullingOutput (CullingOutput& cullingOutput);
+
+inline const ActiveLight* GetMainActiveLight(const ActiveLights& activeLights)
+{
+ return activeLights.hasMainLight ? &activeLights.lights[0] : NULL;
+}
+
+inline const UInt32* GetObjectLightIndices(const CullResults& cullResults, UInt32 roIndex)
+{
+ if (cullResults.forwardLightOffsets.size() == 0)
+ return 0;
+
+ DebugAssert(roIndex < (cullResults.forwardLightOffsets.size()-1));
+ // Get beginning of light indices for object
+ UInt32 lightOffset = cullResults.forwardLightOffsets[roIndex];
+ return cullResults.forwardLightIndices.data() + lightOffset;
+}
+
+inline UInt32 GetObjectLightCount(const CullResults& cullResults, UInt32 visibleNodeIndex)
+{
+ if (cullResults.forwardLightOffsets.size() == 0)
+ return 0;
+
+ DebugAssert(visibleNodeIndex < (cullResults.forwardLightOffsets.size()-1));
+ // We store an extra offset at the end of forwardLightOffsets
+ // This means it's always safe to check next offset to get the size
+ UInt32 lightOffset = cullResults.forwardLightOffsets[visibleNodeIndex];
+ UInt32 nextLightOffset = cullResults.forwardLightOffsets[visibleNodeIndex + 1];
+ DebugAssert(nextLightOffset >= lightOffset);
+ DebugAssert(nextLightOffset <= cullResults.forwardLightIndices.size());
+ return nextLightOffset - lightOffset;
+}
diff --git a/Runtime/Camera/Culler.cpp b/Runtime/Camera/Culler.cpp
new file mode 100644
index 0000000..5fc5305
--- /dev/null
+++ b/Runtime/Camera/Culler.cpp
@@ -0,0 +1,261 @@
+#include "UnityPrefix.h"
+#include "Culler.h"
+#include "SceneCulling.h"
+#include "UnityScene.h"
+#include "CullResults.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/BaseClasses/GameObject.h"
+#include "BaseRenderer.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Camera/IntermediateRenderer.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Graphics/Transform.h"
+#include "LightCulling.h"
+#include "External/shaderlab/Library/intshader.h"
+
+PROFILER_INFORMATION(gCulling, "Culling", kProfilerRender);
+PROFILER_INFORMATION(gCullActiveLights, "CullAllVisibleLights", kProfilerRender);
+PROFILER_INFORMATION(gCullPerObjectLights, "CullPerObjectLights", kProfilerRender);
+PROFILER_INFORMATION(gCullScene, "CullSceneObjects", kProfilerRender);
+PROFILER_INFORMATION(gCacheTransformInfo, "CacheTransformInfo.", kProfilerRender);
+
+
+#if UNITY_EDITOR
+
+inline bool IsRendererFiltered (BaseRenderer* r, CullFiltering filtering)
+{
+ if (filtering == kFilterModeOff)
+ return true;
+
+ if (r->GetRendererType() == kRendererIntermediate)
+ return true;
+
+ Renderer* casted = static_cast<Renderer*> (r);
+ return IsGameObjectFiltered (casted->GetGameObject(), filtering);
+}
+
+bool IsGameObjectFiltered (Unity::GameObject& go, CullFiltering filtering)
+{
+ if (filtering == kFilterModeOff)
+ return true;
+
+ if (go.IsMarkedVisible())
+ return filtering == kFilterModeShowFiltered;
+ else
+ {
+ Transform& trs = go.GetComponent (Transform);
+ if (trs.GetParent())
+ {
+ bool isFiltered = IsGameObjectFiltered(trs.GetParent()->GetGameObject(), filtering);
+ go.SetMarkedVisible((isFiltered == (filtering == kFilterModeShowFiltered))?GameObject::kVisibleAsChild:GameObject::kNotVisible);
+ return isFiltered;
+ }
+ else
+ return filtering == kFilterModeShowRest;
+ }
+}
+#endif
+
+void InvokeOnWillRenderObject (const dynamic_array<BaseRenderer*>& renderers)
+{
+ // Invoke OnWillRenderObject callbacks.
+ // These can only ever happen on non intermediate Renderers.
+ // Scene needs to know we are calling scripts (case 445226).
+ GetScene().SetPreventAddRemoveRenderer(true);
+ for( int i = 0; i < renderers.size(); ++i )
+ {
+ Assert (renderers[i]->GetRendererType() != kRendererIntermediate);
+ Renderer* r = static_cast<Renderer*>(renderers[i]);
+ r->SendMessage (kOnWillRenderObject);
+ }
+ GetScene().SetPreventAddRemoveRenderer(false);
+}
+
+
+static void PrepareSceneNodes (const IndexList& visible, CullFiltering filtering, const SceneNode* nodes, VisibleNodes& output, dynamic_array<BaseRenderer*>& needsCullCallback)
+{
+ // Generate Visible nodes from all static & dynamic objects
+ for (int i=0;i<visible.size;i++)
+ {
+ const SceneNode& node = nodes[visible.indices[i]];
+ if (node.needsCullCallback)
+ needsCullCallback.push_back(node.renderer);
+
+ #if UNITY_EDITOR
+ if (!IsRendererFiltered (node.renderer, filtering))
+ continue;
+ #endif
+
+ VisibleNode& visibleNode = output.push_back ();
+ visibleNode.renderer = node.renderer;
+ visibleNode.lodFade = 0.0F;
+ }
+}
+
+static void CacheTransformInfo (VisibleNodes& results)
+{
+ PROFILER_AUTO(gCacheTransformInfo, NULL)
+
+ for( int i = 0; i < results.size(); ++i )
+ {
+ VisibleNode& node = results[i];
+ node.SetTransformInfo (node.renderer->GetTransformInfo());
+ }
+}
+
+
+///////////@TODO: Move this shit to the right places....
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "Runtime/Camera/LightManager.h"
+#include "Runtime/Camera/Light.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Camera/ShadowCulling.h"
+
+void CullShadowCasters (const Light& light, const ShadowCullData& cullData, bool excludeLightmapped, CullingOutput& cullingOutput );
+
+static void CullAllPerObjectLights (const VisibleNodes& visibleNodes, const ActiveLights& lights, const int renderPath, ObjectLightIndices& forwardLightIndices, ObjectLightIndices& forwardLightOffsets)
+{
+ PROFILER_AUTO(gCullPerObjectLights, NULL);
+
+ forwardLightIndices.reserve(visibleNodes.size() * 3);
+ forwardLightOffsets.resize_uninitialized(visibleNodes.size() + 1);
+
+ const bool dualLightmapsMode = (GetLightmapSettings().GetLightmapsMode() == LightmapSettings::kDualLightmapsMode);
+ const bool areLightProbesBaked = LightProbes::AreBaked();
+
+ for( int i = 0; i < visibleNodes.size(); ++i )
+ {
+ const VisibleNode& node = visibleNodes[i];
+ ///@TODO: Should use SceneNode for this???
+ Renderer* renderer = static_cast<Renderer*>(node.renderer);
+ UInt32 layerMask = renderer->GetLayerMask();
+ const bool isLightmapped = renderer->IsLightmappedForRendering();
+ const bool isUsingLightProbes = renderer->GetRendererType() != kRendererIntermediate && renderer->GetUseLightProbes() && areLightProbesBaked;
+ const bool directLightFromLightProbes = (renderPath == kRenderPathExtVertex) ? false : isUsingLightProbes && !dualLightmapsMode;
+
+ forwardLightOffsets[i] = forwardLightIndices.size();
+ CullPerObjectLights (lights, node.worldAABB, node.localAABB, node.worldMatrix, node.invScale, layerMask, isLightmapped || directLightFromLightProbes, dualLightmapsMode, forwardLightIndices);
+ }
+
+ forwardLightOffsets[visibleNodes.size()] = forwardLightIndices.size();
+}
+
+static void FindShadowCastingLights (ActiveLights& activeLights, ShadowedLights& shadowedLights)
+{
+ size_t index = 0;
+ size_t endIndex = activeLights.numDirLights + activeLights.numSpotLights + activeLights.numPointLights;
+
+ // Must reserve here otherwise the shadowlight pointer in activelight might become invalid...
+ shadowedLights.reserve(endIndex);
+
+ for ( ; index < endIndex; index++)
+ {
+ ActiveLight& light = activeLights.lights[index];
+ if (light.isVisibleInPrepass && light.insideShadowRange && light.light->GetShadows() != kShadowNone)
+ {
+ ShadowedLight& shadowedLight = shadowedLights.push_back();
+ shadowedLight.lightIndex = index;
+ light.shadowedLight = &shadowedLight;
+ }
+ else
+ light.shadowedLight = NULL;
+
+ }
+}
+
+static void CullLights (const SceneCullingParameters& cullingParameters, CullResults& results)
+{
+ PROFILER_BEGIN(gCullActiveLights, NULL)
+ FindAndCullActiveLights(cullingParameters, *results.shadowCullData, results.activeLights);
+ PROFILER_END
+
+ ///@TODO: This is not very awesome, we now cache transform info twice...
+ // Figure out how to not do that...
+ CacheTransformInfo (results.nodes);
+
+ if (cullingParameters.cullPerObjectLights)
+ CullAllPerObjectLights (results.nodes, results.activeLights, cullingParameters.renderPath, results.forwardLightIndices, results.forwardLightOffsets);
+
+ // Shadows might be disabled in quality setting
+ if (GetQualitySettings().GetCurrent().shadows == QualitySettings::kShadowsDisable)
+ return;
+
+ FindShadowCastingLights (results.activeLights, results.shadowedLights);
+}
+
+
+static void CullSendEvents (CullResults& results, dynamic_array<BaseRenderer*>& needsCullCallback)
+{
+ // Send OnBecameVisible / OnBecameInvisible callback
+ GetScene().NotifyVisible (results.sceneCullingOutput);
+
+ // Invoke renderer callbacks
+ // Invoke renderer callbacks (Only scene visible objects)
+ InvokeOnWillRenderObject(needsCullCallback);
+
+ // Cache the transform info.
+ // We do this after OnWillRenderObject since OnWillRenderObject might modify the transform positions.
+ // For example the pre-mecanim animation system will sample animations in OnWillRenderObject when using animation culling.
+ // Which can result in a change to transform. pos / rot / scale
+ CacheTransformInfo(results.nodes);
+}
+
+
+void CullScene (SceneCullingParameters& cullingParameters, CullResults& results)
+{
+ PROFILER_AUTO(gCulling, NULL);
+
+ CullingOutput& cullingOutput = results.sceneCullingOutput;
+
+ if (cullingParameters.useOcclusionCulling)
+ {
+ PROFILER_AUTO(gCullScene, NULL);
+ // Cull the scene, outputs index lists of visible renderers
+ CullSceneWithUmbra( cullingParameters, cullingOutput );
+ }
+ else
+ {
+ PROFILER_AUTO(gCullScene, NULL);
+ // Cull the scene, outputs index lists of visible renderers
+ CullSceneWithoutUmbra( cullingParameters, cullingOutput );
+ }
+
+ dynamic_array<BaseRenderer*> needsCullCallback (kMemTempAlloc);
+
+
+ // Extract visible nodes & the objects needing culling callbacks from the visible indices
+ CullFiltering cullFiltering = cullingParameters.filterMode;
+ for (int i=0;i<kVisibleListCount;i++)
+ PrepareSceneNodes(cullingOutput.visible[i], cullFiltering, cullingParameters.renderers[i].nodes, results.nodes, needsCullCallback);
+
+ if (cullingParameters.cullLights)
+ CullLights(cullingParameters, results);
+
+ CullSendEvents(results, needsCullCallback);
+}
+
+void CullIntermediateRenderersOnly (const SceneCullingParameters& cullingParameters, CullResults& results)
+{
+ Assert(cullingParameters.renderers[kDynamicRenderer].nodes == NULL);
+
+ const RendererCullData& cullData = cullingParameters.renderers[kCameraIntermediate];
+ for( size_t i = 0; i < cullData.rendererCount; ++i )
+ {
+ BaseRenderer* r = cullData.nodes[i].renderer;
+ Assert (r);
+
+ VisibleNode& visibleNode = results.nodes.push_back ();
+ visibleNode.renderer = r;
+ visibleNode.lodFade = 0.0F;
+
+ visibleNode.SetTransformInfo (r->GetTransformInfo());
+ }
+
+ if (cullingParameters.cullLights)
+ CullLights(cullingParameters, results);
+}
+
+///* Fix Lightmanager::FindActiveLights to not be ugly
+///* Move actual culling functions out of Lightmanager into their own LightCulling.cpp
+///* Make Terrains and terrain shadow culling work... \ No newline at end of file
diff --git a/Runtime/Camera/Culler.h b/Runtime/Camera/Culler.h
new file mode 100644
index 0000000..19eafbb
--- /dev/null
+++ b/Runtime/Camera/Culler.h
@@ -0,0 +1,16 @@
+#ifndef CULLER_H
+#define CULLER_H
+
+#include "CullingParameters.h"
+
+struct CullResults;
+struct SceneCullingParameters;
+class IntermediateRenderers;
+namespace Unity { class GameObject; }
+
+void CullScene (SceneCullingParameters& cullingParameters, CullResults& cullResults);
+void CullIntermediateRenderersOnly (const SceneCullingParameters& cullingParameters, CullResults& results);
+
+bool IsGameObjectFiltered (Unity::GameObject& go, CullFiltering cullFilterMode);
+
+#endif
diff --git a/Runtime/Camera/CullingParameters.h b/Runtime/Camera/CullingParameters.h
new file mode 100644
index 0000000..df4f43e
--- /dev/null
+++ b/Runtime/Camera/CullingParameters.h
@@ -0,0 +1,140 @@
+#ifndef CULLING_PARAMETERS_H
+#define CULLING_PARAMETERS_H
+
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/BaseClasses/Tags.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Math/Simd/math.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "UmbraTomeData.h"
+
+struct SceneNode;
+class AABB;
+
+namespace Umbra { class DebugRenderer; class Visibility; class Tome; class QueryExt; }
+
+enum CullingType { kNoOcclusionCulling = 0, kOcclusionCulling = 1, kShadowCasterOcclusionCulling = 2 };
+enum CullFiltering { kFilterModeOff = 0, kFilterModeShowFiltered = 1, kFilterModeShowRest = 2 };
+
+struct IndexList
+{
+ int* indices;
+ int size;
+ int reservedSize;
+
+ int& operator [] (int index)
+ {
+ DebugAssert(index < reservedSize);
+ return indices[index];
+ }
+
+ int operator [] (int index) const
+ {
+ DebugAssert(index < reservedSize);
+ return indices[index];
+ }
+
+ IndexList ()
+ {
+ indices = NULL;
+ reservedSize = size = 0;
+ }
+
+ IndexList (int* i, int sz, int rs)
+ {
+ indices = i;
+ size = sz;
+ reservedSize = rs;
+ }
+
+};
+
+enum
+{
+ kStaticRenderers = 0,
+ kDynamicRenderer,
+ kSceneIntermediate,
+ kCameraIntermediate,
+#if ENABLE_TERRAIN
+ kTreeRenderer,
+#endif
+ kVisibleListCount
+};
+
+// Output for all culling operations.
+// simple index list indexing into the different places where renderers can be found.
+struct CullingOutput
+{
+ IndexList visible[kVisibleListCount];
+
+ Umbra::Visibility* umbraVisibility;
+
+ CullingOutput () : umbraVisibility (NULL) { }
+};
+
+struct RendererCullData
+{
+ const AABB* bounds;
+ const SceneNode* nodes;
+ size_t rendererCount;
+
+ RendererCullData () { bounds = NULL; nodes = NULL; rendererCount = 0; }
+};
+
+struct CullingParameters
+{
+ enum { kMaxPlanes = 10 };
+ enum LayerCull
+ {
+ kLayerCullNone,
+ kLayerCullPlanar,
+ kLayerCullSpherical
+ };
+
+ Vector3f lodPosition;
+ float lodFieldOfView;
+ float orthoSize;
+ int cameraPixelHeight;
+ Plane cullingPlanes[kMaxPlanes];
+ int cullingPlaneCount;
+ UInt32 cullingMask;
+ float layerFarCullDistances[kNumLayers];
+ LayerCull layerCull;
+ bool isOrthographic;
+ Vector3f lightDir;
+
+ // Used for Umbra
+ Matrix4x4f worldToClipMatrix;
+ Vector3f position;
+};
+
+struct SceneCullingParameters : CullingParameters
+{
+ RendererCullData renderers[kVisibleListCount];
+
+ UInt8* lodMasks;
+ size_t lodGroupCount;
+
+ bool useOcclusionCulling;
+ bool useShadowCasterCulling;
+ bool useLightOcclusionCulling;
+ bool excludeLightmappedShadowCasters;
+ bool cullPerObjectLights;
+ bool cullLights;
+ int renderPath;
+ CullFiltering filterMode;
+
+ /// This stores the visibility of previous culling operations.
+ /// For example, shadow caster culling uses the visibility of the visible objects from the camera.
+ const CullingOutput* sceneVisbilityForShadowCulling;
+
+ UmbraTomeData umbraTome;
+ Umbra::DebugRenderer* umbraDebugRenderer;
+ Umbra::QueryExt* umbraQuery;
+ UInt32 umbraDebugFlags;
+};
+
+
+
+#endif
diff --git a/Runtime/Camera/Flare.cpp b/Runtime/Camera/Flare.cpp
new file mode 100644
index 0000000..1053f05
--- /dev/null
+++ b/Runtime/Camera/Flare.cpp
@@ -0,0 +1,611 @@
+#include "UnityPrefix.h"
+#include "Flare.h"
+#include "Runtime/Interfaces/IRaycast.h"
+#include "Camera.h"
+#include "RenderSettings.h"
+#include "RenderManager.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/Graphics/Transform.h"
+#include "External/shaderlab/Library/properties.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/BaseClasses/Tags.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Shaders/ShaderNameRegistry.h"
+#include "Runtime/Profiler/Profiler.h"
+
+static Material *s_FlareMaterial;
+
+static SHADERPROP (FlareTexture);
+
+
+static void CalculateFlareGetUVCoords (int layout, int imageIndex, const Vector2f& pixOffset, Vector2f& outUV0, Vector2f& outUV1 );
+
+
+Flare::Flare (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+
+}
+
+Flare::~Flare ()
+{
+}
+
+void Flare::Reset ()
+{
+ Super::Reset();
+ resize_trimmed (m_Elements, 1);
+ m_Elements[0].m_ImageIndex = 0;
+ m_Elements[0].m_Rotate = false;
+ m_Elements[0].m_Position = 0.0f;
+ m_Elements[0].m_Size = 0.5f;
+ m_Elements[0].m_Color = ColorRGBAf (1,1,1,0);
+ m_Elements[0].m_Zoom = true;
+ m_Elements[0].m_Fade = true;
+ m_Elements[0].m_UseLightColor = true;
+ m_UseFog = true;
+ m_TextureLayout = 0;
+}
+
+template<class TransferFunc>
+void Flare::Transfer (TransferFunc& transfer) {
+ Super::Transfer (transfer);
+ TRANSFER_SIMPLE (m_FlareTexture);
+ TRANSFER_SIMPLE (m_TextureLayout);
+ TRANSFER_SIMPLE (m_Elements);
+ TRANSFER (m_UseFog);
+}
+
+void Flare::AwakeFromLoad (AwakeFromLoadMode awakeMode) {
+ Super::AwakeFromLoad(awakeMode);
+
+ // mark pixel offset as "need to figure out"
+ m_PixOffset.Set( -1.0f, -1.0f );
+}
+
+struct FlareVertex {
+ Vector3f vert;
+ ColorRGBA32 color;
+ Vector2f uv;
+};
+PROFILER_INFORMATION(gSubmitVBOProfileFlare, "Mesh.SubmitVBO", kProfilerRender)
+
+void Flare::Render (Vector3f &pos, float visibility, const ColorRGBAf &tintColor, const ChannelAssigns& channels)
+{
+ // Figure out pixel offset if we have to.
+ // Have to do this just before rendering, because at load time texel sizes might not
+ // be set up for some textures yet (render textures in particular).
+ if( m_PixOffset.x == -1.0f )
+ {
+ Texture *tex = m_FlareTexture;
+ // Get the UV offsets to address the texture on texel boundaries
+ // Use this to work around OpenGL's feature that UVs are in the middle of a texel.
+ if( tex )
+ {
+ m_PixOffset.x = tex->GetTexelSizeX() * 0.5f;
+ m_PixOffset.y = tex->GetTexelSizeY() * 0.5f;
+ }
+ else
+ {
+ m_PixOffset.Set(0,0);
+ }
+ }
+
+ Vector2f p (pos.x, pos.y);
+ if (SqrMagnitude (p) > Vector2f::epsilon)
+ p = Normalize (p);
+ else
+ p = Vector2f (1,0);
+
+ if (m_UseFog)
+ visibility *= 1.0F-GetRenderSettings().CalcFogFactor (pos.z);
+
+ // Get VBO chunk
+ const int elemCount = m_Elements.size();
+ GfxDevice& device = GetGfxDevice();
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ FlareVertex* vbPtr;
+ if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor),
+ elemCount * 4, 0,
+ DynamicVBO::kDrawQuads,
+ (void**)&vbPtr, NULL ) )
+ {
+ return;
+ }
+
+ std::vector<FlareElement>::const_iterator it, itEnd = m_Elements.end();
+ for (it = m_Elements.begin(); it != itEnd; ++it)
+ {
+ Vector2f uv0, uv1;
+ CalculateFlareGetUVCoords (m_TextureLayout, it->m_ImageIndex, m_PixOffset, uv0, uv1);
+
+ // Had to invert uv's when we flipped all our textures to conform to opengl
+ // the actual code using the uv's should flip uv's instead of this fix.
+ uv0.y = 1.0F - uv0.y;
+ uv1.y = 1.0F - uv1.y;
+
+ float s = it->m_Size * pos.z * (it->m_Zoom ? visibility * .01f : .01f);
+ Vector2f size;
+ if (it->m_Rotate) {
+ size = p * (s * 1.4f);
+ } else {
+ size.x = size.y = s;
+ }
+ Vector3f v = Lerp (pos, Vector3f (0,0,pos.z), it->m_Position);
+ ColorRGBA32 color;
+
+ if (!it->m_UseLightColor) {
+ color = (it->m_Color * visibility);
+ } else {
+ ColorRGBAf col = it->m_Color;
+ col.r *= tintColor.r;
+ col.g *= tintColor.g;
+ col.b *= tintColor.b;
+ col.a *= tintColor.a;
+ if( it->m_Fade )
+ col = col * (visibility);
+ color = col;
+ }
+ // Swizzle color of the renderer requires it
+ color = device.ConvertToDeviceVertexColor(color);
+
+ // vertices
+ vbPtr[0].vert.Set( v.x - size.x, v.y - size.y, v.z );
+ vbPtr[0].color = color;
+ vbPtr[0].uv.Set( uv1.x, uv0.y );
+
+ vbPtr[1].vert.Set( v.x + size.y, v.y - size.x, v.z );
+ vbPtr[1].color = color;
+ vbPtr[1].uv.Set( uv0.x, uv0.y );
+
+ vbPtr[2].vert.Set( v.x + size.x, v.y + size.y, v.z );
+ vbPtr[2].color = color;
+ vbPtr[2].uv.Set( uv0.x, uv1.y );
+
+ vbPtr[3].vert.Set( v.x - size.y, v.y + size.x, v.z );
+ vbPtr[3].color = color;
+ vbPtr[3].uv.Set( uv1.x, uv1.y );
+
+ vbPtr += 4;
+ }
+
+ vbo.ReleaseChunk( elemCount * 4, 0 );
+
+ PROFILER_BEGIN(gSubmitVBOProfileFlare, this)
+
+ vbo.DrawChunk (channels);
+ GPU_TIMESTAMP();
+
+ PROFILER_END
+}
+
+// Matches the modes used in the inspector for texture layout:
+enum FlareLayout {
+ kLayoutLargeRestSmall = 0,
+ kLayoutMixed,
+ kLayout1x1,
+ kLayout2x2,
+ kLayout3x3,
+ kLayout4x4,
+};
+
+
+// Get the UV coords for a flare element.
+// Returns: outUV0 is top-left UV, outUV1 is bottom-right UV.
+static void CalculateFlareGetUVCoords (int layout, int imageIndex, const Vector2f& pixOffset, Vector2f& outUV0, Vector2f& outUV1 )
+{
+ switch (layout)
+ {
+ case kLayoutLargeRestSmall:
+ if (imageIndex != 0) {
+ imageIndex -= 1;
+ int xImg = (imageIndex & 1);
+ int yImg = (imageIndex >> 1);
+ float imgSize = .5f;
+ outUV0 = Vector2f( (float)(xImg+0) * imgSize, (float)(yImg+0) * imgSize * 0.5f + 0.5f) + pixOffset;
+ outUV1 = Vector2f( (float)(xImg+1) * imgSize, (float)(yImg+1) * imgSize * 0.5f + 0.5f) - pixOffset;
+ } else {
+ outUV0 = Vector2f( 0.0f, 0.0f ) + pixOffset;
+ outUV1 = Vector2f( 1.0f, 0.5f ) - pixOffset;
+ }
+ break;
+
+ case kLayoutMixed:
+ switch (imageIndex) {
+ case 0:
+ outUV0 = Vector2f( 0.0f, 0.0f ); // + pixOffset;
+ outUV1 = Vector2f( 1.0f, 0.5f ) - pixOffset;
+ break;
+ case 1:
+ outUV0 = Vector2f( 0.0f, 0.50f ); // + pixOffset;
+ outUV1 = Vector2f( 0.5f, 0.75f ) - pixOffset;
+ break;
+ case 2:
+ outUV0 = Vector2f( 0.0f, 0.75f ); // + pixOffset;
+ outUV1 = Vector2f( 0.5f, 1.00f ) - pixOffset;
+ break;
+ default:
+ imageIndex -= 3;
+ int xImg = (imageIndex & 1);
+ int yImg = (imageIndex >> 1);
+ const float imgSize = 0.25f;
+ outUV0 = Vector2f( (float)(xImg+0) * imgSize + 0.5f, (float)(yImg+0) * imgSize * 0.5f + 0.5f ) + pixOffset;
+ outUV1 = Vector2f( (float)(xImg+1) * imgSize + 0.5f, (float)(yImg+1) * imgSize * 0.5f + 0.5f ) - pixOffset;
+ break;
+ }
+ break;
+
+ default:
+ {
+ // the rest of layouts are regular grids
+ const int grid = layout - 1;
+ int xImg = imageIndex % grid;
+ int yImg = imageIndex / grid;
+ float imgSize = 1.0f / (float)grid;
+ outUV0 = Vector2f( (float)(xImg+0) * imgSize, (float)(yImg+0) * imgSize ) + pixOffset;
+ outUV1 = Vector2f( (float)(xImg+1) * imgSize, (float)(yImg+1) * imgSize ) - pixOffset;
+ }
+ break;
+ }
+}
+
+
+FlareManager &FlareManager::Get ()
+{
+ static FlareManager* s_FlareManager = NULL;
+
+ // Create the material used for the flares
+ if (!s_FlareMaterial)
+ {
+ Shader* shader = GetScriptMapper ().FindShader ("Hidden/Internal-Flare");
+ if(shader)
+ s_FlareMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ }
+
+ if (!s_FlareManager)
+ s_FlareManager = new FlareManager();
+
+ return *s_FlareManager;
+}
+
+int FlareManager::AddFlare ()
+{
+ // Find unused flare, and put it there if there is one
+ FlareList::iterator it, itEnd = m_Flares.end();
+ int index = 0;
+ for (it = m_Flares.begin(); it != itEnd; ++it, ++index)
+ {
+ if (!it->used)
+ {
+ it->used = true;
+ // set brightness of this to zero in all cameras
+ for (RendererList::iterator rit = m_Renderers.begin(); rit != m_Renderers.end(); ++rit)
+ {
+ DebugAssert (rit->second.size() == m_Flares.size());
+ rit->second[index] = 0.0f;
+ }
+ return index;
+ }
+ }
+
+ // No unused flare found; add new one
+ index = m_Flares.size();
+ m_Flares.push_back (FlareEntry());
+ // add zero brightness to all cameras
+ for (RendererList::iterator rit = m_Renderers.begin(); rit != m_Renderers.end(); ++rit)
+ {
+ rit->second.push_back (0.0f);
+ DebugAssert (rit->second.size() == m_Flares.size());
+ }
+ return index;
+}
+
+void FlareManager::UpdateFlare( int handle, Flare *flare, const Vector3f &position,
+ bool infinite, float brightness, const ColorRGBAf &color,
+ float fadeSpeed, UInt32 layers, UInt32 ignoredLayers
+ )
+{
+ Assert (handle >= 0 && handle < m_Flares.size());
+ FlareEntry& i = m_Flares[handle];
+ i.position = position;
+ i.flare = flare;
+ i.infinite = infinite;
+ i.brightness = brightness;
+ i.color = color;
+ i.fadeSpeed = fadeSpeed;
+ i.layers = layers;
+ i.ignoredLayers = ignoredLayers;
+}
+
+void FlareManager::DeleteFlare (int handle)
+{
+ Assert (handle >= 0 && handle < m_Flares.size());
+ // mark it as unused
+ FlareEntry& flare = m_Flares[handle];
+ flare.used = false;
+}
+
+void FlareManager::AddCamera (Camera &camera) {
+ Assert (m_Renderers.find (&camera) == m_Renderers.end());
+ m_Renderers[&camera] = std::vector<float> ();
+ std::vector<float> &vec = m_Renderers[&camera];
+ vec.resize (m_Flares.size(), 0.0f);
+ Assert (vec.size() == m_Flares.size());
+}
+
+void FlareManager::RemoveCamera (Camera &camera) {
+ RendererList::iterator i = m_Renderers.find (&camera);
+ AssertIf (i == m_Renderers.end());
+ m_Renderers.erase (i);
+}
+
+void FlareManager::Update () {
+ Camera &cam = GetCurrentCamera();
+ RendererList::iterator it = m_Renderers.find (&cam);
+ if (it == m_Renderers.end())
+ {
+ AssertString ("Flare renderer to update not found");
+ return;
+ }
+
+ Assert (it->second.size() == m_Flares.size());
+ float *brightness = (it->second.size() ? &it->second[0] : NULL);
+
+ float camFar = cam.GetFar();
+
+ for (FlareList::iterator i = m_Flares.begin(); i != m_Flares.end(); ++i, ++brightness)
+ {
+ if (!i->used)
+ continue;
+
+ int layers = ~i->ignoredLayers;
+ if (!(i->layers & layers))
+ continue;
+
+ float fadeAmt = i->fadeSpeed * (IsWorldPlaying() ? GetDeltaTime() : 1.0f);
+ // We want flares to fade out at half speed of which they fade in
+ float fadeOutAmt = fadeAmt * .5f;
+
+ float targetVisible;
+ Vector3f pos;
+ if (!i->infinite)
+ {
+ pos = cam.WorldToViewportPoint (i->position);
+ if( pos.z < camFar && (pos.x > 0.0f && pos.x < 1.0f) && (pos.y > 0.0f && pos.y < 1.0f) )
+ targetVisible = 1.0f;
+ else
+ targetVisible = 0.0f;
+ }
+ else
+ {
+ pos = cam.WorldToViewportPoint( GetCurrentCamera().GetPosition () + i->position );
+ if( pos.x > 0.0F && pos.x < 1.0F && pos.y > 0.0F && pos.y < 1.0F )
+ targetVisible = 1.0f;
+ else
+ targetVisible = 0.0f;
+ }
+
+ if (targetVisible)
+ {
+ float t = 10000;
+ Ray r;
+ r.SetOrigin (cam.GetPosition());
+ if (!i->infinite) {
+ t = Magnitude (cam.GetPosition() - i->position);
+ r.SetDirection ((i->position - cam.GetPosition()) / t);
+ } else
+ r.SetDirection (-i->position);
+
+ IRaycast* raycast = GetRaycastInterface ();
+ HitInfo hit;
+ if (raycast && raycast->Raycast (r, t, layers, hit))
+ targetVisible = 0.0f;
+ }
+
+ if (targetVisible > *brightness) {
+ *brightness += fadeAmt;
+ if (*brightness > 1.0f)
+ *brightness = 1.0f;
+ } else if (targetVisible < *brightness) {
+ *brightness -= fadeOutAmt;
+ if (*brightness < 0.0f)
+ *brightness = 0.0f;
+ }
+ }
+}
+
+void FlareManager::RenderFlares ()
+{
+ if(!s_FlareMaterial)
+ return;
+ Shader* shader = s_FlareMaterial->GetShader();
+ if (!shader)
+ return;
+ if(!GetCurrentCameraPtr())
+ return;
+
+ Camera &cam = GetCurrentCamera();
+ float doubleNearDistance = cam.GetNear() * 2.0F;
+
+ Update ();
+
+ GfxDevice& device = GetGfxDevice();
+ float matWorld[16], matView[16];
+ CopyMatrix(device.GetViewMatrix(), matView);
+ CopyMatrix(device.GetWorldMatrix(), matWorld);
+ device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity
+
+ RendererList::iterator it = m_Renderers.find (&cam);
+ Assert (it->second.size() == m_Flares.size());
+ float *brightness = (it->second.size() ? &it->second[0] : NULL);
+
+ Texture *lastTex = NULL;
+ const ChannelAssigns* channels = NULL;
+ Matrix4x4f cameraMatrix = GetCurrentCamera().GetWorldToCameraMatrix ();
+ for (FlareList::iterator i = m_Flares.begin(); i != m_Flares.end(); ++i, ++brightness)
+ {
+ if (!i->used)
+ continue;
+
+ if ((*brightness) <= 0.0f)
+ continue;
+
+ Flare *fl = i->flare;
+ if (!fl)
+ continue;
+
+ Vector3f pos;
+ if (!i->infinite)
+ pos = cameraMatrix.MultiplyPoint3 (i->position);
+ else
+ pos = cameraMatrix.MultiplyVector3 (-i->position * doubleNearDistance);
+
+ Texture* flareTex = fl->GetTexture();
+
+ if (!flareTex)
+ continue;
+
+ if (lastTex != flareTex)
+ {
+ lastTex = flareTex;
+ ShaderLab::g_GlobalProperties->SetTexture(kSLPropFlareTexture, lastTex);
+ channels = s_FlareMaterial->SetPassWithShader( 0, shader, 0 );
+ }
+
+ fl->Render (pos, *brightness * i->brightness, i->color, *channels);
+ }
+
+ device.SetViewMatrix(matView);
+ device.SetWorldMatrix(matWorld);
+}
+
+
+IMPLEMENT_CLASS_HAS_INIT (LensFlare)
+IMPLEMENT_OBJECT_SERIALIZE (LensFlare)
+
+LensFlare::LensFlare (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+ m_Handle = -1;
+ m_FadeSpeed = 3.0f;
+}
+
+LensFlare::~LensFlare ()
+{
+}
+
+void LensFlare::Reset () {
+ Super::Reset();
+ m_Brightness = 1.0f;
+ m_Color = ColorRGBAf (1,1,1,0);
+ m_Directional = false;
+ m_FadeSpeed = 3.0f;
+ m_IgnoreLayers.m_Bits = kNoFXLayerMask | kIgnoreRaycastMask;
+}
+
+void LensFlare::InitializeClass () {
+ REGISTER_MESSAGE_VOID (LensFlare, kTransformChanged, TransformChanged);
+}
+
+template<class TransferFunc>
+void LensFlare::Transfer (TransferFunc& transfer) {
+ Super::Transfer (transfer);
+ TRANSFER_SIMPLE (m_Flare);
+ TRANSFER_SIMPLE (m_Color);
+ TRANSFER (m_Brightness);
+ TRANSFER (m_FadeSpeed);
+ TRANSFER (m_IgnoreLayers);
+ TRANSFER (m_Directional);
+}
+
+inline void LensFlare::UpdateFlare () {
+ Vector3f pos;
+ if (!m_Directional)
+ pos = GetComponent(Transform).GetPosition();
+ else
+ pos = GetComponent(Transform).TransformDirection (Vector3f (0,0,1));
+
+ GetFlareManager().UpdateFlare ( m_Handle, m_Flare, pos, m_Directional,
+ m_Brightness, m_Color, m_FadeSpeed,
+ GetGameObject().GetLayerMask(), m_IgnoreLayers.m_Bits
+ );
+}
+
+void LensFlare::AwakeFromLoad (AwakeFromLoadMode awakeMode) {
+ Super::AwakeFromLoad (awakeMode);
+ if ((awakeMode & kDidLoadFromDisk) == 0 && m_Handle != -1)
+ UpdateFlare ();
+}
+
+/// Update the flare in the FlareManager when the GO moves.
+void LensFlare::TransformChanged () {
+ if (m_Handle != -1)
+ UpdateFlare ();
+}
+
+void LensFlare::SetBrightness (float brightness) {
+ m_Brightness = brightness;
+ SetDirty ();
+ if (m_Handle != -1)
+ UpdateFlare ();
+}
+
+void LensFlare::SetFadeSpeed (float fadeSpeed) {
+ m_FadeSpeed = fadeSpeed;
+ SetDirty ();
+ if (m_Handle != -1)
+ UpdateFlare ();
+}
+
+void LensFlare::SetColor (const ColorRGBAf& color) {
+ m_Color = color;
+ SetDirty ();
+ if (m_Handle != -1)
+ UpdateFlare ();
+}
+
+
+void LensFlare::SetFlare (Flare *flare) {
+ m_Flare = flare;
+ SetDirty ();
+ if (m_Handle != -1)
+ UpdateFlare ();
+}
+
+void LensFlare::AddToManager () {
+ m_Handle = GetFlareManager().AddFlare ();
+ UpdateFlare ();
+}
+
+void LensFlare::RemoveFromManager () {
+ GetFlareManager().DeleteFlare (m_Handle);
+ m_Handle = -1;
+}
+
+IMPLEMENT_CLASS (FlareLayer)
+
+FlareLayer::FlareLayer (MemLabelId label, ObjectCreationMode mode)
+ : Super(label, mode)
+{}
+
+FlareLayer::~FlareLayer ()
+{
+}
+
+void FlareLayer::AddToManager ()
+{
+ Camera &cam = GetComponent (Camera);
+ GetFlareManager().AddCamera (cam);
+}
+
+void FlareLayer::RemoveFromManager ()
+{
+ Camera &cam = GetComponent (Camera);
+ GetFlareManager().RemoveCamera (cam);
+}
+
+IMPLEMENT_CLASS (Flare)
+IMPLEMENT_OBJECT_SERIALIZE (Flare)
diff --git a/Runtime/Camera/Flare.h b/Runtime/Camera/Flare.h
new file mode 100644
index 0000000..8c8dc9e
--- /dev/null
+++ b/Runtime/Camera/Flare.h
@@ -0,0 +1,169 @@
+#ifndef FLARE_H
+#define FLARE_H
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/GameCode/Behaviour.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Graphics/Texture.h"
+
+class Camera;
+class ChannelAssigns;
+
+// Source asset for a lens flare.
+// Essentially it has some standard settings and a list of flare elements that make up the flare.
+// All per-camera visibility is handled by the FlareManager, which is also responsible for rendering this flare.
+class Flare : public NamedObject {
+public:
+ REGISTER_DERIVED_CLASS (Flare, NamedObject)
+ DECLARE_OBJECT_SERIALIZE (Flare)
+
+ Flare(MemLabelId label, ObjectCreationMode mode);
+
+ virtual void Reset ();
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ // Render this flare with the center at pos, a visibility factor and a tint Color.
+ void Render (Vector3f &pos, float visibility, const ColorRGBAf &tintColor, const ChannelAssigns& channels);
+
+ // Set/Get the texture to use as a source for this flare
+ void SetTexture (Texture *texture) { m_FlareTexture = texture; }
+ Texture *GetTexture () const { return m_FlareTexture; }
+
+private:
+ struct FlareElement {
+ unsigned int m_ImageIndex; ///< The image index from the flare texture
+ float m_Position; ///< The position of the element (0 = light, 1 = screen center)
+ float m_Size; ///< The size of the element
+ ColorRGBAf m_Color; ///< Element color tint
+ bool m_UseLightColor; ///< Pick up the color from a light source?
+ bool m_Rotate; ///< Rotate the flare in respect to light angle?
+ bool m_Zoom; ///< Make the flare size dependent on visibility?
+ bool m_Fade; ///< Make the flare fade dependent on visibility?
+
+ FlareElement() {m_Fade=true;}
+
+ DECLARE_SERIALIZE (FlareElement)
+ };
+ std::vector<FlareElement> m_Elements; ///< The individual flare elements.
+ PPtr<Texture> m_FlareTexture; ///< The texture used for the flare elements.
+
+ int m_TextureLayout; ///< enum { 1 Large 4 Small = 0, 1 Large 2 Medium 8 Small, 1 Texture, 2x2 Grid, 3x3 Grid, 4x4 Grid } Flare element layout in the texture.
+
+ bool m_UseFog;
+ Vector2f m_PixOffset;
+};
+
+template<class TransferFunc>
+void Flare::FlareElement::Transfer (TransferFunc& transfer) {
+ TRANSFER_SIMPLE (m_ImageIndex);
+ TRANSFER_SIMPLE (m_Position);
+ TRANSFER_SIMPLE (m_Size);
+ TRANSFER_SIMPLE (m_Color);
+ TRANSFER (m_UseLightColor);
+ TRANSFER (m_Rotate);
+ TRANSFER (m_Zoom);
+ TRANSFER (m_Fade);
+}
+
+/// \todo Show flare outside screen option
+/// \todo fade non-inf flares dependant on fog settings
+class FlareManager {
+public:
+ struct FlareEntry {
+ ColorRGBAf color;
+ Vector3f position; // The world-space position of the flare, OR the direction vector if inf
+ PPtr<Flare> flare;
+ UInt32 layers;
+ UInt32 ignoredLayers;
+ float brightness;
+ float fadeSpeed;
+ bool infinite;
+ bool used;
+ FlareEntry ()
+ : position (Vector3f (0,0,0)), layers (-1), ignoredLayers(-1), brightness(0.0f), infinite (false), used (true), fadeSpeed (3.0f) {}
+ };
+ static FlareManager &Get ();
+
+ // Add a flare entry. returns the handle to the flare element.
+ int AddFlare ();
+ void UpdateFlare ( int handle, Flare *flare, const Vector3f &position,
+ bool infinite, float brightness, const ColorRGBAf &color,
+ float fadeSpeed, UInt32 layers, UInt32 ignoredLayers
+ );
+ void DeleteFlare (int handle);
+
+ /// Add and remove a camera from the list of cameras to track
+ /// Used by the flarelayer
+ void AddCamera (Camera &camera);
+ void RemoveCamera (Camera &camera);
+
+ void RenderFlares ();
+
+private:
+ /// The brightness for each camera for each flare.
+ typedef std::map <const Camera *, std::vector<float> > RendererList;
+ RendererList m_Renderers;
+
+ typedef std::vector<FlareEntry> FlareList;
+ FlareList m_Flares;
+ void Update ();
+};
+
+inline FlareManager &GetFlareManager () {
+ return FlareManager::Get();
+}
+
+class LensFlare : public Behaviour {
+public:
+ REGISTER_DERIVED_CLASS (LensFlare, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (LensFlare)
+
+ LensFlare(MemLabelId label, ObjectCreationMode mode);
+
+ static void InitializeClass ();
+ static void CleanupClass () {}
+
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+ virtual void Reset ();
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+ inline void UpdateFlare ();
+
+ void TransformChanged ();
+
+ void SetBrightness (float brightness);
+ float GetBrightness () const { return m_Brightness; }
+
+ void SetFadeSpeed (float m_FadeSpeed);
+ float GetFadeSpeed () const { return m_FadeSpeed; }
+
+ void SetColor (const ColorRGBAf& color);
+ const ColorRGBAf &GetColor () const {return m_Color; }
+
+
+ void SetFlare (Flare *flare);
+ Flare *GetFlare () { return m_Flare; }
+private:
+ PPtr<Flare> m_Flare; ///< Source flare asset to render.
+ ColorRGBAf m_Color; ///< Color of the flare.
+ float m_Brightness; ///< Brightness scale of the flare.
+ float m_FadeSpeed; ///< Fade speed of the flare.
+ BitField m_IgnoreLayers; ///< mask for layers that cannot hide flare
+ int m_Handle;
+ bool m_Directional; ///< Is this lensflare directional (true) or positional (false)
+};
+
+class FlareLayer : public Behaviour {
+public:
+ REGISTER_DERIVED_CLASS (FlareLayer, Behaviour)
+
+ FlareLayer (MemLabelId label, ObjectCreationMode mode);
+
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+};
+
+
+#endif
diff --git a/Runtime/Camera/GraphicsSettings.cpp b/Runtime/Camera/GraphicsSettings.cpp
new file mode 100644
index 0000000..004f6ed
--- /dev/null
+++ b/Runtime/Camera/GraphicsSettings.cpp
@@ -0,0 +1,79 @@
+#include "UnityPrefix.h"
+#include "GraphicsSettings.h"
+#include "RenderManager.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#if UNITY_EDITOR
+#include "Runtime/Misc/ResourceManager.h"
+#endif
+
+
+GraphicsSettings::GraphicsSettings (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_NeedToInitializeDefaultShaders(false)
+{
+}
+
+GraphicsSettings::~GraphicsSettings ()
+{
+}
+
+
+void GraphicsSettings::InitializeClass ()
+{
+ RenderManager::InitializeClass();
+}
+
+void GraphicsSettings::CleanupClass ()
+{
+ RenderManager::CleanupClass();
+}
+
+
+void GraphicsSettings::SetDefaultAlwaysIncludedShaders()
+{
+ #if UNITY_EDITOR
+ m_AlwaysIncludedShaders.clear();
+ if (BuiltinResourceManager::AreResourcesInitialized())
+ m_AlwaysIncludedShaders.push_back(GetBuiltinExtraResource<Shader> ("Normal-Diffuse.shader"));
+ else
+ m_NeedToInitializeDefaultShaders = true;
+ SetDirty();
+ #endif
+}
+
+bool GraphicsSettings::IsAlwaysIncludedShader (PPtr<Shader> shader) const
+{
+ for (int i = 0; i < m_AlwaysIncludedShaders.size (); ++i)
+ if (m_AlwaysIncludedShaders[i] == shader)
+ return true;
+
+ return false;
+}
+
+#if UNITY_EDITOR
+
+void GraphicsSettings::AddAlwaysIncludedShader (PPtr<Shader> shader)
+{
+ m_AlwaysIncludedShaders.push_back (shader);
+ SetDirty ();
+}
+
+#endif
+
+void GraphicsSettings::Reset ()
+{
+ Super::Reset ();
+ SetDefaultAlwaysIncludedShaders ();
+}
+
+template<class TransferFunction>
+void GraphicsSettings::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ transfer.Transfer (m_AlwaysIncludedShaders, "m_AlwaysIncludedShaders");
+}
+
+IMPLEMENT_CLASS_HAS_INIT (GraphicsSettings)
+IMPLEMENT_OBJECT_SERIALIZE (GraphicsSettings)
+GET_MANAGER (GraphicsSettings)
diff --git a/Runtime/Camera/GraphicsSettings.h b/Runtime/Camera/GraphicsSettings.h
new file mode 100644
index 0000000..bfb95ce
--- /dev/null
+++ b/Runtime/Camera/GraphicsSettings.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "Runtime/BaseClasses/GameManager.h"
+#include "Runtime/Shaders/Shader.h"
+#include <vector>
+
+
+class GraphicsSettings : public GlobalGameManager
+{
+public:
+ typedef UNITY_VECTOR(kMemRenderer, PPtr<Shader>) ShaderArray;
+
+ REGISTER_DERIVED_CLASS (GraphicsSettings, GlobalGameManager)
+ DECLARE_OBJECT_SERIALIZE (GraphicsSettings)
+
+ GraphicsSettings (MemLabelId label, ObjectCreationMode mode);
+ // ~GraphicsSettings (); declared-by-macro
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ virtual void Reset ();
+
+ #if UNITY_EDITOR
+ bool DoesNeedToInitializeDefaultShaders() const { return m_NeedToInitializeDefaultShaders; }
+ #endif
+ void SetDefaultAlwaysIncludedShaders();
+
+ /// Return true if the given shader is in the list of shaders
+ /// that should always be included in builds.
+ bool IsAlwaysIncludedShader (PPtr<Shader> shader) const;
+
+#if UNITY_EDITOR
+
+ const ShaderArray& GetAlwaysIncludedShaders () const { return m_AlwaysIncludedShaders; }
+
+ /// Add a shader to the list of shaders that are aways included in builds.
+ /// NOTE: Does not check whether the shader is already on the list.
+ void AddAlwaysIncludedShader (PPtr<Shader> shader);
+
+#endif
+
+private:
+ ShaderArray m_AlwaysIncludedShaders;
+ bool m_NeedToInitializeDefaultShaders;
+};
+
+GraphicsSettings& GetGraphicsSettings();
diff --git a/Runtime/Camera/HaloManager.cpp b/Runtime/Camera/HaloManager.cpp
new file mode 100644
index 0000000..30feb4f
--- /dev/null
+++ b/Runtime/Camera/HaloManager.cpp
@@ -0,0 +1,282 @@
+#include "UnityPrefix.h"
+#include "HaloManager.h"
+#include "Runtime/Shaders/Material.h"
+#include "RenderManager.h"
+#include "Camera.h"
+#include "CullResults.h"
+#include "RenderLoops/RenderLoop.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Shaders/ShaderNameRegistry.h"
+#include "Runtime/Profiler/Profiler.h"
+
+IMPLEMENT_CLASS_HAS_INIT (Halo)
+IMPLEMENT_OBJECT_SERIALIZE (Halo)
+
+static Material *s_HaloMaterial = NULL;
+
+Halo::Halo (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+ m_Handle = 0;
+}
+
+Halo::~Halo ()
+{
+}
+
+void Halo::Reset ()
+{
+ Super::Reset();
+ m_Color = ColorRGBA32 (128, 128, 128, 255);
+ m_Size = 5.0f;
+}
+
+void Halo::InitializeClass () {
+ REGISTER_MESSAGE_VOID (Halo, kTransformChanged, TransformChanged);
+}
+
+void Halo::CleanupClass () {
+// s_HaloMaterial is clean up by UnloadAllObjects()
+}
+
+template<class TransferFunc>
+void Halo::Transfer (TransferFunc& transfer) {
+ Super::Transfer (transfer);
+ TRANSFER_SIMPLE (m_Color);
+ TRANSFER_SIMPLE (m_Size);
+}
+
+static void LoadHaloMaterial()
+{
+ if (s_HaloMaterial)
+ return;
+
+ SET_ALLOC_OWNER(NULL);
+ Shader* shader = GetScriptMapper ().FindShader ("Hidden/Internal-Halo");
+ if (shader)
+ s_HaloMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+}
+
+void Halo::AwakeFromLoad (AwakeFromLoadMode awakeMode) {
+ Super::AwakeFromLoad (awakeMode);
+ if ((awakeMode & kDidLoadFromDisk) == 0 && m_Handle)
+ GetHaloManager().UpdateHalo (m_Handle, GetComponent (Transform).GetPosition(), m_Color, m_Size, GetGameObject ().GetLayerMask());
+
+ LoadHaloMaterial();
+}
+
+void Halo::TransformChanged () {
+ if (m_Handle)
+ GetHaloManager().UpdateHalo (m_Handle, GetComponent (Transform).GetPosition(), m_Color, m_Size, GetGameObject ().GetLayerMask());
+}
+void Halo::AddToManager () {
+ m_Handle = GetHaloManager().AddHalo ();
+ GetHaloManager().UpdateHalo (m_Handle, GetComponent (Transform).GetPosition(), m_Color, m_Size, GetGameObject ().GetLayerMask());
+}
+void Halo::RemoveFromManager () {
+ GetHaloManager().DeleteHalo (m_Handle);
+ m_Handle = 0;
+}
+
+
+HaloManager::HaloManager(MemLabelId label, ObjectCreationMode mode)
+ : Super(label, mode)
+{
+}
+
+void HaloManager::AwakeFromLoad(AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad(mode);
+
+ GetRenderManager().AddCameraRenderable (this, kTransparentRenderQueue);
+}
+
+HaloManager::~HaloManager() {
+ RenderManager* mgr = GetRenderManagerPtr();
+ if (mgr) // render manager can be already gone
+ mgr->RemoveCameraRenderable (this);
+}
+
+HaloManager::Halo::Halo (int hdl)
+ : position (Vector3f (0,0,0)), color (ColorRGBAf(0,0,0)), size(1), handle(hdl), layers (1) {
+}
+HaloManager::Halo::Halo (const Vector3f &pos, const ColorRGBA32 &col, float s, int h, UInt32 _layers)
+ : position (pos), color (col), size(s), handle(h), layers (_layers) {
+}
+
+IMPLEMENT_CLASS (HaloManager)
+GET_MANAGER (HaloManager)
+
+struct HaloVertex {
+ Vector3f vert;
+ ColorRGBA32 color;
+ Vector2f uv;
+};
+PROFILER_INFORMATION(gHaloRenderProfile, "Halo.Render", kProfilerRender)
+PROFILER_INFORMATION(gSubmitVBOProfileHalo, "Mesh.SubmitVBO", kProfilerRender)
+
+void HaloManager::RenderRenderable (const CullResults& cullResults)
+{
+ // Just bail if we have no visible halos or using shader replace
+ if( m_Halos.empty() || !s_HaloMaterial || cullResults.shaderReplaceData.replacementShader != NULL )
+ return;
+
+ LoadHaloMaterial();
+ if (!s_HaloMaterial)
+ return;
+
+ Shader* shader = s_HaloMaterial->GetShader();
+
+ GfxDevice& device = GetGfxDevice();
+ #if UNITY_EDITOR
+ // don't draw when wireframe mode
+ if( device.GetWireframe() )
+ return;
+ #endif
+
+ PROFILER_AUTO(gHaloRenderProfile, this)
+
+
+ const int kHaloVertices = 21;
+ const int kMaxHalos = 65535 / kHaloVertices; // cap max halos to be rendered so we work on older DX hardware
+ int haloCount = m_Halos.size();
+ if( haloCount > kMaxHalos )
+ haloCount = kMaxHalos;
+
+ // Get VBO chunk
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ HaloVertex* vbPtr;
+ if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor),
+ haloCount * kHaloVertices, 0,
+ DynamicVBO::kDrawTriangleStrip,
+ (void**)&vbPtr, NULL ) )
+ {
+ return;
+ }
+
+
+ // Write halos into VBO
+ Camera &cam = GetCurrentCamera();
+ UInt32 layers = cam.GetCullingMask();
+ Matrix4x4f mat (cam.GetWorldToCameraMatrix());
+ int halosToRender = 0;
+ for( int i = 0; i < haloCount; ++i )
+ {
+ const Halo& halo = m_Halos[i];
+ Vector3f v = mat.MultiplyPoint3( halo.position );
+ const float s = halo.size;
+
+ // Skip this halo if behind the camera or layers don't match
+ if( v.z > -s || !(halo.layers & layers) )
+ continue;
+
+ // Dim this halo if near the camera (to avoid ugly intersection thingies).
+ ColorRGBA32 c;
+ if (v.z <= -s * 2.0f ) {
+ c = halo.color;
+ } else {
+ int fac = RoundfToInt((-v.z * 255.0f / s) - 255.0f);
+ c = halo.color * fac;
+ }
+ // Swizzle color of the renderer requires it
+ c = device.ConvertToDeviceVertexColor(c);
+
+ float z2 = v.z + s * 0.333f;
+
+ // Output 21 vertices
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x-s,v.y ,v.z); vbPtr->color = c; vbPtr->uv.Set( 0.0f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x-s,v.y-s,v.z); vbPtr->color = c; vbPtr->uv.Set( 0, 0 ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y-s,v.z); vbPtr->color = c; vbPtr->uv.Set( .5f, 0 ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x+s,v.y-s,v.z); vbPtr->color = c; vbPtr->uv.Set( 1, 0 ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x+s,v.y ,v.z); vbPtr->color = c; vbPtr->uv.Set( 1, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x+s,v.y+s,v.z); vbPtr->color = c; vbPtr->uv.Set( 1, 1 ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y+s,v.z); vbPtr->color = c; vbPtr->uv.Set( .5f, 1 ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x-s,v.y+s,v.z); vbPtr->color = c; vbPtr->uv.Set( 0, 1 ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x-s,v.y ,v.z); vbPtr->color = c; vbPtr->uv.Set( 0.0f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+ vbPtr->vert.Set( v.x ,v.y , z2); vbPtr->color = c; vbPtr->uv.Set( .5f, .5f ); ++vbPtr;
+
+ ++halosToRender;
+ }
+
+ vbo.ReleaseChunk( halosToRender * kHaloVertices, 0 );
+
+ float matWorld[16], matView[16];
+ CopyMatrix(device.GetViewMatrix(), matView);
+ CopyMatrix(device.GetWorldMatrix(), matWorld);
+ device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity
+
+ // Output halos
+ const ChannelAssigns* channels = s_HaloMaterial->SetPassWithShader( 0, shader, 0 );
+
+ PROFILER_BEGIN(gSubmitVBOProfileHalo, this)
+ vbo.DrawChunk (*channels);
+ GPU_TIMESTAMP();
+ PROFILER_END
+
+ device.SetViewMatrix(matView);
+ device.SetWorldMatrix(matWorld);
+}
+
+int HaloManager::AddHalo () {
+ int handle;
+ if (!m_Halos.empty())
+ handle = m_Halos.back().handle + 1;
+ else
+ handle = 1;
+ m_Halos.push_back (Halo (handle));
+ return handle;
+}
+
+void HaloManager::UpdateHalo (int h, Vector3f position,ColorRGBA32 color,float size, UInt32 layers)
+{
+ for (HaloList::iterator i = m_Halos.begin(); i != m_Halos.end(); i++) {
+ if (i->handle == h) {
+ i->position = position;
+ i->color = color;
+ i->size = size;
+ i->layers = layers;
+ return;
+ }
+ }
+ AssertString ("Unable to find Halo to update");
+}
+
+void HaloManager::DeleteHalo (int h)
+{
+ for (HaloList::iterator i = m_Halos.begin(); i != m_Halos.end(); i++) {
+ if (i->handle == h) {
+ m_Halos.erase (i);
+ return;
+ }
+ }
+ AssertString ("Unable to find Halo to be deleted");
+}
+
+IMPLEMENT_CLASS (HaloLayer)
+
+HaloLayer::HaloLayer (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+}
+
+HaloLayer::~HaloLayer ()
+{
+}
diff --git a/Runtime/Camera/HaloManager.h b/Runtime/Camera/HaloManager.h
new file mode 100644
index 0000000..9be7395
--- /dev/null
+++ b/Runtime/Camera/HaloManager.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "Renderable.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/GameCode/Behaviour.h"
+
+class Halo : public Behaviour {
+public:
+ REGISTER_DERIVED_CLASS (Halo, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (Halo)
+ Halo (MemLabelId label, ObjectCreationMode mode);
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+ virtual void Reset ();
+ void TransformChanged ();
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+private:
+ ColorRGBA32 m_Color;
+ float m_Size;
+ int m_Handle;
+};
+
+
+class HaloManager : public LevelGameManager, public Renderable {
+public:
+ REGISTER_DERIVED_CLASS (HaloManager, LevelGameManager)
+
+ int AddHalo ();
+ void UpdateHalo (int h, Vector3f position,ColorRGBA32 color,float size, UInt32 layers);
+ void DeleteHalo (int h);
+
+ // Renderable
+ virtual void RenderRenderable (const CullResults& CullResults);
+
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+
+private:
+ HaloManager (MemLabelId label, ObjectCreationMode mode);
+ // ~HaloManager (); declared-by-macro
+ struct Halo {
+ Vector3f position;
+ ColorRGBA32 color;
+ float size;
+ int handle;
+ UInt32 layers;
+ Halo (int hdl);
+ Halo (const Vector3f &pos, const ColorRGBA32 &col, float s, int h, UInt32 _layers);
+ };
+
+ typedef std::vector<Halo> HaloList;
+ HaloList m_Halos;
+};
+
+HaloManager& GetHaloManager();
+
+
+// DEPRECATED
+class HaloLayer : public Behaviour {
+public:
+ REGISTER_DERIVED_CLASS (HaloLayer, Behaviour)
+ HaloLayer (MemLabelId label, ObjectCreationMode mode);
+ virtual void AddToManager () {};
+ virtual void RemoveFromManager () {};
+};
diff --git a/Runtime/Camera/ImageFilters.cpp b/Runtime/Camera/ImageFilters.cpp
new file mode 100644
index 0000000..5c866c2
--- /dev/null
+++ b/Runtime/Camera/ImageFilters.cpp
@@ -0,0 +1,613 @@
+#include "UnityPrefix.h"
+#include "ImageFilters.h"
+#include "Renderable.h"
+#include "Runtime/Graphics/RenderTexture.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Shaders/Material.h"
+#include "CameraUtil.h"
+#include "Runtime/Graphics/RenderBufferManager.h"
+#include "Runtime/Graphics/RenderSurface.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Shaders/ShaderNameRegistry.h"
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+
+PROFILER_INFORMATION(gImageFxProfile, "Camera.ImageEffects", kProfilerRender);
+PROFILER_INFORMATION(gGraphicsBlitProfile, "Graphics.Blit", kProfilerRender);
+
+namespace ImageFilters_Static
+{
+
+static SHADERPROP(MainTex);
+
+} // namespace ImageFilters_Static
+
+static bool s_InsideFilterChain = false;
+static RenderTexture* s_CurrentSrcRT;
+static RenderTexture* s_CurrentFinalRT;
+
+
+// -----------------------------------------------------------------------------
+
+static int GetImageFilterSortIndex (Unity::Component* component)
+{
+ GameObject* go = component->GetGameObjectPtr();
+ int count = go ? go->GetComponentCount() : 0;
+ for (int i = 0; i < count; ++i)
+ {
+ if (&go->GetComponentAtIndex(i) == component)
+ return i;
+ }
+ return -1;
+}
+
+
+void ImageFilters::AddImageFilter (const ImageFilter& filter)
+{
+ // When importing a package over a live image filter, it does not get removed.
+ // So remove them explicitly instead of adding it multiple times.
+ #if UNITY_EDITOR
+ RemoveImageFilter (filter);
+ #endif
+
+ Filters& filters = filter.afterOpaque ? m_AfterOpaque : m_AfterEverything;
+
+ // Insert the image filter by sort index.
+ // Search backwards because in most cases the filters are added in sorted order.
+ int insertIndex = GetImageFilterSortIndex(filter.component);
+ for( int i = filters.size()-1; i >= 0; --i )
+ {
+ if (insertIndex >= GetImageFilterSortIndex(filters[i].component))
+ {
+ Filters::iterator insertion = filters.begin() + i + 1;
+ filters.insert (insertion, filter);
+ return;
+ }
+ }
+ filters.insert (filters.begin(), filter);
+}
+
+void ImageFilters::RemoveImageFilter (const ImageFilter& filter)
+{
+ for (Filters::iterator i = m_AfterOpaque.begin(); i != m_AfterOpaque.end(); /**/)
+ {
+ if (*i == filter)
+ i = m_AfterOpaque.erase (i);
+ else
+ ++i;
+ }
+ for (Filters::iterator i = m_AfterEverything.begin(); i != m_AfterEverything.end(); /**/)
+ {
+ if (*i == filter)
+ i = m_AfterEverything.erase (i);
+ else
+ ++i;
+ }
+}
+
+RenderTexture* ImageFilters::GetTargetBeforeOpaque ()
+{
+ return m_FirstTargetTexture;
+}
+
+RenderTexture* ImageFilters::GetTargetAfterOpaque (bool forceIntoRT, bool usingScreenToComposite)
+{
+ if (m_AfterOpaque.empty())
+ return m_FirstTargetTexture;
+ if (m_AfterEverything.empty() && !forceIntoRT)
+ return m_FirstTargetTexture;
+ if (usingScreenToComposite && !forceIntoRT)
+ return m_FirstTargetTexture;
+ return m_SecondTargetTexture;
+}
+
+RenderTexture* ImageFilters::GetTargetFinal ()
+{
+ return m_FinalTargetTexture;
+}
+
+static RenderTexture* GetTemporaryRT (bool depthBuffer, bool requestHDR = false, bool requestLinear = false, int antiAliasing = 1)
+{
+ RenderBufferManager& rbm = GetRenderBufferManager ();
+ RenderTexture* rt = rbm.GetTempBuffer (
+ RenderBufferManager::kFullSize,
+ RenderBufferManager::kFullSize,
+ depthBuffer ? kDepthFormat24 : kDepthFormatNone,
+ // by gl/gles spec blitting won't work if dst have components missing from src
+ // which is the case often on mobiles
+ requestHDR ? GetGfxDevice().GetDefaultHDRRTFormat() : GetGfxDevice().GetDefaultRTFormat(),
+ 0,
+ (requestLinear && !requestHDR) ? kRTReadWriteSRGB : kRTReadWriteLinear,
+ antiAliasing);
+
+ if (rt) rt->CorrectVerticalTexelSize(true);
+ return rt;
+}
+
+RenderTexture* ImageFilters::SwitchTargetToLDR (RenderTexture* oldRt, bool requestLinear)
+{
+ if(!oldRt)
+ return NULL;
+
+ RenderTexture* newRt = GetTemporaryRT (false, false, requestLinear);
+ if (oldRt == m_FirstTargetTexture) {
+ GetRenderBufferManager().ReleaseTempBuffer (oldRt);
+ m_FirstTargetTexture = newRt;
+ }
+ else if (oldRt == m_SecondTargetTexture) {
+ GetRenderBufferManager().ReleaseTempBuffer (oldRt);
+ m_SecondTargetTexture = newRt;
+ }
+ else {
+ GetRenderBufferManager().ReleaseTempBuffer (oldRt);
+ }
+
+ return newRt;
+}
+
+void ImageFilters::ReleaseTargetForLDR (RenderTexture** oldRt)
+{
+ if(!(*oldRt))
+ return;
+
+ RenderTexture* rt = *oldRt;
+
+ GetRenderBufferManager().ReleaseTempBuffer (rt);
+ if(rt == m_FirstTargetTexture)
+ m_FirstTargetTexture = NULL;
+ if(rt == m_SecondTargetTexture)
+ m_SecondTargetTexture = NULL;
+
+ *oldRt = NULL;
+}
+
+void ImageFilters::Prepare (bool forceIntoRT, bool hdr, int antiAliasing)
+{
+ Assert (!m_FirstTargetTexture && !m_SecondTargetTexture);
+
+ // Nothing to do if we have no image filters
+ if (!HasImageFilter() && !forceIntoRT)
+ return;
+
+ // Ignore image filters if we can't use them
+ if (!RenderTexture::IsEnabled() || (gGraphicsCaps.npotRT == kNPOTNone))
+ {
+ static bool errorShown = false;
+ if( !errorShown )
+ {
+ ErrorString("can't use image filters (npot-RT are not supported or RT are disabled completely)");
+ errorShown = true;
+ }
+ return;
+ }
+
+ bool linearColorSpace = GetActiveColorSpace() == kLinearColorSpace;
+
+ m_FirstTargetTexture = GetTemporaryRT (true, hdr, linearColorSpace, antiAliasing);
+
+ // find out if we're still rendering HDR after opaque
+ bool hdrAfterOpaque = hdr;
+ Filters& filters = m_AfterOpaque;
+ size_t n = filters.size();
+ for (size_t i = 0; i < n; ++i)
+ if (filters[i].transformsToLDR)
+ hdrAfterOpaque = false;
+
+ // we need second target only if have both after-opaque
+ //if (!m_AfterOpaque.empty())
+ m_SecondTargetTexture = GetTemporaryRT (false, hdrAfterOpaque, linearColorSpace, antiAliasing);
+}
+
+
+static void GetDestRenderTargetSurfaces (RenderTexture* dest, RenderSurfaceHandle& outColor, RenderSurfaceHandle& outDepth)
+{
+ if (dest && !dest->IsCreated())
+ dest->Create();
+
+ // Ugly hack: when we have image filters that are between opaque & transparent geometry,
+ // we really want to share the depth buffer for before/after rendering of that. However
+ // the current scripting API does not allow doing that!
+ //
+ // So try to detect this situation: if we're inside of image filters loop and destination
+ // is not the first target: use depth from the first one.
+ if (s_InsideFilterChain && dest && s_CurrentSrcRT != NULL && dest == s_CurrentFinalRT && dest->GetWidth()==s_CurrentSrcRT->GetWidth() && dest->GetHeight()==s_CurrentSrcRT->GetHeight())
+ {
+ // one more ugly hack (do we even need to mark ugly hacks?)
+ // RenderBufferManager returns non-created RT, so at this point we can end up with non-created s_CurrentSrcRT
+ // e.g. first run of image filters
+ // so create it before getting depth surface
+ if(!s_CurrentSrcRT->IsCreated())
+ s_CurrentSrcRT->Create();
+
+ outColor = dest->GetColorSurfaceHandle();
+ outDepth = s_CurrentSrcRT->GetDepthSurfaceHandle();
+ }
+ else if (dest)
+ {
+ outColor = dest->GetColorSurfaceHandle();
+ outDepth = dest->GetDepthSurfaceHandle();
+ }
+ else
+ {
+ outColor = GetGfxDevice().GetBackBufferColorSurface();
+ outDepth = GetGfxDevice().GetBackBufferDepthSurface();
+ }
+}
+
+
+void ImageFilters::DoRender (RenderTexture* finalRT, bool forceIntoRT, bool afterOpaque, bool usingScreenToComposite, bool hdr)
+{
+ // Ignore image filters if we can't use them
+ if (!RenderTexture::IsEnabled() || (gGraphicsCaps.npotRT == kNPOTNone))
+ return;
+
+ PROFILER_AUTO_GFX(gImageFxProfile, NULL)
+ GPU_AUTO_SECTION(kGPUSectionPostProcess);
+
+ bool buffersInHDR = hdr;
+ bool linearColorSpace = GetActiveColorSpace() == kLinearColorSpace;
+
+ RenderBufferManager& rbm = GetRenderBufferManager();
+ RenderTexture* srcRT = NULL;
+ RenderTexture* dstRT = NULL;
+
+ m_FinalTargetTexture = GetGfxDevice().GetActiveRenderTexture();
+
+ if (afterOpaque)
+ {
+ srcRT = m_FirstTargetTexture;
+ if ((!m_AfterEverything.empty() && !usingScreenToComposite) || forceIntoRT)
+ finalRT = m_SecondTargetTexture;
+ }
+ else
+ {
+ srcRT = GetTargetAfterOpaque(forceIntoRT, usingScreenToComposite);
+ }
+
+ // store current values of global state (to make re-entrancy work)
+ bool oldInside = s_InsideFilterChain;
+ RenderTexture *oldSrcRT = s_CurrentSrcRT;
+ RenderTexture *oldFinalRT = s_CurrentFinalRT;
+ s_InsideFilterChain = false;
+ s_CurrentSrcRT = srcRT;
+ s_CurrentFinalRT = finalRT;
+ GfxDevice& device = GetGfxDevice();
+
+ Filters& filters = afterOpaque ? m_AfterOpaque : m_AfterEverything;
+ size_t n = filters.size();
+ for (size_t i = 0; i < n; ++i)
+ {
+ RenderTexture* dst;
+ if (i == n-1)
+ dst = finalRT;
+ else
+ {
+ if (filters[i].transformsToLDR && buffersInHDR) {
+ buffersInHDR = false;
+ dstRT = SwitchTargetToLDR(dstRT, linearColorSpace);
+ }
+ if (!dstRT)
+ {
+ dstRT = GetTemporaryRT(false, buffersInHDR, linearColorSpace);
+ }
+ dst = dstRT;
+ }
+
+
+ // Render one image effect
+
+ s_InsideFilterChain = true;
+ PROFILER_AUTO_GFX(gImageFxProfile, filters[i].component);
+
+ // Discard any destination RT contents before rendering the effect into it
+ // NB: do not discard back buffer here
+ RenderSurfaceHandle dstRsColor, dstRsDepth;
+ GetDestRenderTargetSurfaces (dst, dstRsColor, dstRsDepth);
+ if(!dstRsColor.object->backBuffer)
+ device.DiscardContents (dstRsColor);
+ // However, do not discard depth if we're in the opaque image effects
+ // stage and it's our final destination depth - we will still need
+ // it for later alpha rendering.
+ if (dstRsDepth != s_CurrentSrcRT->GetDepthSurfaceHandle() && !dstRsDepth.object->backBuffer)
+ device.DiscardContents (dstRsDepth);
+ else
+ device.IgnoreNextUnresolveOnRS (dstRsDepth); // we'll have to un-resolve it, so silence up the warning
+
+ // Invoke actual image effect function
+ filters[i].renderFunc (filters[i].component, srcRT, dst);
+ s_InsideFilterChain = false;
+
+
+ // if we have just converted to LDR, we need to completely switch to LDR, so let's release src
+ if (filters[i].transformsToLDR && hdr && !buffersInHDR) {
+ //srcRT = SwitchTargetToLDR(srcRT, linearColorSpace);
+ if(srcRT)
+ ReleaseTargetForLDR(&srcRT);
+ }
+
+ // We are ping-ponging between textures when there are more than 2 image filters.
+ // If the very first one was AA-resolved, it's upside down flip is already handled.
+ if (srcRT) srcRT->CorrectVerticalTexelSize(true);
+
+ std::swap (srcRT, dstRT);
+ }
+
+ bool needsBlitIntoFinalRT = !afterOpaque && forceIntoRT && filters.empty();
+
+ if (needsBlitIntoFinalRT)
+ {
+ ImageFilters::Blit (srcRT, finalRT);
+ }
+
+ if (n > 0 || needsBlitIntoFinalRT)
+ {
+ // we actually rendered into finalRT
+ m_FinalTargetTexture = finalRT;
+ }
+
+ if (dstRT && dstRT != m_FirstTargetTexture && dstRT != m_SecondTargetTexture)
+ rbm.ReleaseTempBuffer (dstRT);
+
+ if (srcRT && srcRT != m_FirstTargetTexture && srcRT != m_SecondTargetTexture)
+ rbm.ReleaseTempBuffer (srcRT);
+
+ if (!afterOpaque)
+ {
+ if (m_FirstTargetTexture)
+ {
+ rbm.ReleaseTempBuffer (m_FirstTargetTexture);
+ m_FirstTargetTexture = NULL;
+ }
+ if (m_SecondTargetTexture)
+ {
+ rbm.ReleaseTempBuffer (m_SecondTargetTexture);
+ if (m_SecondTargetTexture == m_FinalTargetTexture)
+ m_FinalTargetTexture = NULL;
+ m_SecondTargetTexture = NULL;
+ }
+ }
+
+ GetGfxDevice().SetSRGBWrite(false);
+
+ // resstore old values of global state
+ s_InsideFilterChain = oldInside;
+ s_CurrentSrcRT = oldSrcRT;
+ s_CurrentFinalRT = oldFinalRT;
+}
+
+// -----------------------------------------------------------------------------
+
+void ImageFilters::Blit (Texture* source, RenderTexture* dest)
+{
+ static Material* s_BlitMaterial = NULL;
+ if (!s_BlitMaterial){
+ Shader* shader = GetScriptMapper().FindShader ("Hidden/BlitCopy");
+ s_BlitMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ }
+ Blit (source, dest, s_BlitMaterial, -1, true);
+}
+
+void ImageFilters::DrawQuadNoGPUTimestamp (GfxDevice& device, bool invertY, float uvX, float uvY)
+{
+ device.ImmediateBegin (kPrimitiveQuads);
+ float y1, y2;
+ if (invertY) {
+ y1 = uvY; y2 = 0.0f;
+ } else {
+ y1 = 0.0f; y2 = uvY;
+ }
+
+ // set the vertex color to white, otherwise shader doing the blit might get some random color
+ device.ImmediateColor(1.0f, 1.0f, 1.0f, 1.0f);
+
+ device.ImmediateTexCoordAll (0.0f, y1, 0.0f); device.ImmediateVertex (0.0f, 0.0f, 0.1f);
+ device.ImmediateTexCoordAll (0.0f, y2, 0.0f); device.ImmediateVertex (0.0f, 1.0f, 0.1f);
+ device.ImmediateTexCoordAll (uvX, y2, 0.0f); device.ImmediateVertex (1.0f, 1.0f, 0.1f);
+ device.ImmediateTexCoordAll (uvX, y1, 0.0f); device.ImmediateVertex (1.0f, 0.0f, 0.1f);
+ device.ImmediateEnd ();
+}
+
+void ImageFilters::DrawQuad (GfxDevice& device, bool invertY, float uvX, float uvY)
+{
+ DrawQuadNoGPUTimestamp(device, invertY, uvX, uvY);
+ GPU_TIMESTAMP();
+}
+
+
+static void SetMultiTapTexCoords (GfxDevice& device, float invSourceSizeX, float invSourceSizeY, float x, float y, bool invertY, int count, const Vector2f* offsets)
+{
+ for (int i = 0; i < count; ++i)
+ {
+ Vector2f offset = offsets[i];
+ if (invertY)
+ offset.y = -offset.y;
+ offset.x *= invSourceSizeX;
+ offset.y *= invSourceSizeY;
+ device.ImmediateTexCoord (i, x + offset.x, y + offset.y, 0.0f);
+ }
+}
+
+void ImageFilters::SetCurrentRenderTarget (RenderTexture* dest, UInt32 flags)
+{
+ RenderSurfaceHandle rsColor, rsDepth;
+ GetDestRenderTargetSurfaces (dest, rsColor, rsDepth);
+ RenderTexture::SetActive (1, &rsColor, rsDepth, dest, 0, kCubeFaceUnknown, flags);
+ RenderTexture::FindAndSetSRGBWrite (dest);
+}
+
+static bool IsActiveRenderTextureMSAA ()
+{
+ RenderTexture* rt = RenderTexture::GetActive();
+ return (rt && rt->IsAntiAliased());
+}
+
+void ImageFilters::Blit (Texture* source, RenderTexture* dest, Unity::Material* mat, int pass, bool setRT)
+{
+ using namespace ImageFilters_Static;
+ PROFILER_AUTO(gGraphicsBlitProfile, mat->GetShader())
+
+ GfxDevice& device = GetGfxDevice();
+
+ UInt32 rtFlags = 0;
+#if UNITY_XENON
+ // Xbox 360 must resolve a render target before using it as a texture.
+ if (source == dest)
+ {
+ rtFlags |= RenderTexture::kFlagForceResolve;
+ }
+ else if (!setRT)
+ {
+ // Render target was set previously. Get it and compare.
+ if (source == device.GetActiveRenderTexture())
+ {
+ setRT = true;
+ rtFlags |= RenderTexture::kFlagForceResolve;
+ }
+ }
+#endif
+ // MSAA render targets must be resolved before they are used.
+ if (IsActiveRenderTextureMSAA())
+ {
+ setRT = true;
+ rtFlags |= RenderTexture::kFlagForceResolve;
+ }
+ if (setRT)
+ SetCurrentRenderTarget (dest, rtFlags);
+
+
+ bool setTexture = source && mat->HasProperty(kSLPropMainTex);
+ if (setTexture)
+ mat->SetTexture (kSLPropMainTex, source);
+ bool invertY = source && source->GetTexelSizeY() < 0.0f;
+
+ float uvX = 1.0f, uvY = 1.0f;
+ #if GFX_EMULATES_NPOT_RENDERTEXTURES
+ if (source)
+ {
+ int texWidth = source->GetGLWidth();
+ int texHeight = source->GetGLHeight();
+ uvX = (float)texWidth / (float)NextPowerOfTwo(texWidth);
+ uvY = (float)texHeight / (float)NextPowerOfTwo(texHeight);
+ }
+ #endif
+
+ DeviceMVPMatricesState preserveMVP;
+
+ LoadFullScreenOrthoMatrix();
+
+ int npasses = mat->GetPassCount ();
+ if (pass == -1)
+ {
+ for (int i = 0; i < npasses; ++i)
+ {
+ mat->SetPass (i);
+ DrawQuad (device, invertY, uvX, uvY);
+ }
+ }
+ else
+ {
+ if (pass >= 0 && pass < npasses)
+ {
+ mat->SetPass (pass);
+ DrawQuad (device, invertY, uvX, uvY);
+ }
+ else
+ {
+ ErrorString ("Invalid pass number for Graphics.Blit");
+ }
+ }
+
+ if (setTexture)
+ mat->SetTexture (kSLPropMainTex, NULL);
+}
+
+void ImageFilters::BlitMultiTap (Texture* source, RenderTexture* dest, Material* mat, int count, const Vector2f* offsets)
+{
+ using namespace ImageFilters_Static;
+
+ PROFILER_AUTO(gGraphicsBlitProfile, mat->GetShader())
+
+ UInt32 rtFlags = 0;
+#if UNITY_XENON
+ // Xbox 360 must resolve a render target before using it as a texture.
+ // MSAA render targets also need to be resolved before they are used.
+ if (source == dest)
+ rtFlags |= RenderTexture::kFlagForceResolve;
+#endif
+ // MSAA render targets must be resolved before they are used.
+ if (IsActiveRenderTextureMSAA())
+ {
+ rtFlags |= RenderTexture::kFlagForceResolve;
+ }
+ SetCurrentRenderTarget (dest, rtFlags);
+
+ bool setTexture = source && mat->HasProperty(kSLPropMainTex);
+ if (setTexture)
+ mat->SetTexture (kSLPropMainTex, source);
+ bool invertY = source && source->GetTexelSizeY() < 0.0f;
+
+ float uvX = 1.0f, uvY = 1.0f;
+ int texWidth = 0, texHeight = 0;
+ if (source)
+ {
+ texWidth = source->GetGLWidth();
+ texHeight = source->GetGLHeight();
+ #if GFX_EMULATES_NPOT_RENDERTEXTURES
+ int potWidth = NextPowerOfTwo(texWidth);
+ int potHeight = NextPowerOfTwo(texHeight);
+ uvX = (float)texWidth / (float)potWidth;
+ uvY = (float)texHeight / (float)potHeight;
+ texWidth = potWidth;
+ texHeight = potHeight;
+ #endif
+ }
+
+ GfxDevice& device = GetGfxDevice();
+ DeviceMVPMatricesState preserveMVP;
+ LoadFullScreenOrthoMatrix();
+
+ int npasses = mat->GetPassCount ();
+ for (int i = 0; i < npasses; ++i)
+ {
+ float y1, y2;
+ if (invertY)
+ {
+ y1 = uvY; y2 = 0.0f;
+ }
+ else
+ {
+ y1 = 0.0f; y2 = uvY;
+ }
+ float invSizeX = source ? 1.0f / texWidth : 0.0f;
+ float invSizeY = source ? 1.0f / texHeight : 0.0f;
+
+ mat->SetColor(ShaderLab::Property("_BlurOffsets"), ColorRGBAf(offsets[0].x, offsets[0].y, 0.0f, y1));
+
+ mat->SetPass (i);
+
+ device.ImmediateBegin (kPrimitiveQuads);
+
+ SetMultiTapTexCoords( device, invSizeX, invSizeY, 0.0f, y1, invertY, count, offsets );
+ device.ImmediateVertex (0.0f, 0.0f, 0.1f);
+
+ SetMultiTapTexCoords( device, invSizeX, invSizeY, 0.0f, y2, invertY, count, offsets );
+ device.ImmediateVertex (0.0f, 1.0f, 0.1f);
+
+ SetMultiTapTexCoords( device, invSizeX, invSizeY, uvX, y2, invertY, count, offsets );
+ device.ImmediateVertex (1.0f, 1.0f, 0.1f);
+
+ SetMultiTapTexCoords( device, invSizeX, invSizeY, uvX, y1, invertY, count, offsets );
+ device.ImmediateVertex (1.0f, 0.0f, 0.1f);
+
+ device.ImmediateEnd ();
+ GPU_TIMESTAMP();
+ }
+
+ if (setTexture)
+ mat->SetTexture (kSLPropMainTex, NULL);
+
+}
+
diff --git a/Runtime/Camera/ImageFilters.h b/Runtime/Camera/ImageFilters.h
new file mode 100644
index 0000000..36439af
--- /dev/null
+++ b/Runtime/Camera/ImageFilters.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "Renderable.h"
+
+class RenderTexture;
+class Texture;
+class Vector2f;
+class GfxDevice;
+namespace Unity { class Material; }
+
+
+// Image filters functionality. Only used internally by the camera (and other minor places).
+class ImageFilters
+{
+public:
+ ImageFilters() : m_FirstTargetTexture(NULL), m_SecondTargetTexture(NULL), m_FinalTargetTexture(NULL) { }
+
+ void AddImageFilter (const ImageFilter& filter);
+ void RemoveImageFilter (const ImageFilter& filter);
+ bool HasImageFilter() const { return !(m_AfterOpaque.empty() && m_AfterEverything.empty()); }
+ bool HasAfterOpaqueFilters() const { return !m_AfterOpaque.empty(); }
+
+ void DoRender (RenderTexture* finalRT, bool forceIntoRT, bool afterOpaque, bool usingScreenToComposite, bool hdr = false);
+ void Prepare (bool forceIntoRT, bool hdr = false, int antiAliasing = 1);
+ RenderTexture* GetTargetBeforeOpaque ();
+ RenderTexture* GetTargetAfterOpaque (bool forceIntoRT, bool usingScreenToComposite);
+ RenderTexture* GetTargetFinal ();
+ RenderTexture* SwitchTargetToLDR (RenderTexture* oldRt, bool requestLinear);
+ void ReleaseTargetForLDR (RenderTexture** oldRt);
+
+ static void Blit (Texture* source, RenderTexture* dest);
+ static void Blit (Texture* source, RenderTexture* dest, Unity::Material* mat, int pass, bool setRT);
+ static void BlitMultiTap (Texture* source, RenderTexture* dest, Unity::Material* mat, int count, const Vector2f* offsets);
+ static void DrawQuadNoGPUTimestamp (GfxDevice& device, bool invertY, float uvX, float uvY);
+ static void DrawQuad (GfxDevice& device, bool invertY, float uvX, float uvY);
+private:
+ static void SetCurrentRenderTarget (RenderTexture* dest, UInt32 flags);
+
+private:
+ typedef std::vector<ImageFilter> Filters;
+ Filters m_AfterOpaque;
+ Filters m_AfterEverything;
+ RenderTexture* m_FirstTargetTexture; // has color & depth
+ RenderTexture* m_SecondTargetTexture; // has color only, reuses depth from first
+ RenderTexture* m_FinalTargetTexture;
+};
diff --git a/Runtime/Camera/IntermediateRenderer.cpp b/Runtime/Camera/IntermediateRenderer.cpp
new file mode 100644
index 0000000..cb2520e
--- /dev/null
+++ b/Runtime/Camera/IntermediateRenderer.cpp
@@ -0,0 +1,284 @@
+#include "UnityPrefix.h"
+#include "IntermediateRenderer.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Filters/Mesh/SpriteRenderer.h"
+#include "Camera.h"
+#include "Runtime/Graphics/DrawUtil.h"
+#include "Runtime/Graphics/SpriteFrame.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Utilities/InitializeAndCleanup.h"
+#include "UnityScene.h"
+
+
+IntermediateRenderer::IntermediateRenderer()
+: BaseRenderer(kRendererIntermediate)
+, m_Node(this)
+{
+}
+
+void IntermediateRenderer::Initialize(const Matrix4x4f& matrix, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows)
+{
+ #if UNITY_EDITOR
+ m_InstanceID = 0;
+ #endif
+ m_Material = material;
+
+ if (layer < 0 || layer >= 32)
+ {
+ AssertString ("DrawMesh layer has to be from in [0..31] range!");
+ layer = 0;
+ }
+ m_Layer = layer;
+
+ // TODO: check if render 2 texture required for this material
+ m_CastShadows = castShadows;
+ m_ReceiveShadows = receiveShadows;
+
+ m_TransformInfo.worldMatrix = matrix;
+ // detect uniform and non-uniform scale (ignoring non-affine)
+ float uniformScale = 1.0f;
+ m_TransformInfo.transformType = ComputeTransformType(matrix, uniformScale);
+ m_TransformInfo.invScale = 1.0f / uniformScale;
+ m_TransformInfo.localAABB = localAABB;
+ TransformAABB (localAABB, matrix, m_TransformInfo.worldAABB);
+
+ Assert (m_TransformInfo.localAABB.IsValid());
+ Assert (m_TransformInfo.worldAABB.IsValid());
+
+ #if UNITY_EDITOR
+ m_ScaleInLightmap = -1.0f;
+ #endif
+
+ RendererBecameVisible ();
+
+ m_TransformDirty = false;
+ m_BoundsDirty = false;
+}
+
+IntermediateRenderer::~IntermediateRenderer()
+{
+ RendererBecameInvisible ();
+}
+
+void IntermediateRenderer::OnAssetBoundsChanged()
+{
+ // Not supported. IntermediateRenderer live only for one frame.
+}
+
+
+// --------------------------------------------------------------------------
+
+
+DEFINE_POOLED_ALLOC(MeshIntermediateRenderer, 64 * 1024);
+
+void MeshIntermediateRenderer::StaticInitialize()
+{
+ STATIC_INITIALIZE_POOL(MeshIntermediateRenderer);
+}
+
+void MeshIntermediateRenderer::StaticDestroy()
+{
+ STATIC_DESTROY_POOL(MeshIntermediateRenderer);
+}
+
+static RegisterRuntimeInitializeAndCleanup s_MeshIntermediateRendererCallbacks(MeshIntermediateRenderer::StaticInitialize, MeshIntermediateRenderer::StaticDestroy);
+
+
+MeshIntermediateRenderer::MeshIntermediateRenderer()
+{
+}
+
+MeshIntermediateRenderer::~MeshIntermediateRenderer()
+{
+}
+
+void MeshIntermediateRenderer::OnAssetDeleted()
+{
+ m_Mesh = NULL;
+}
+
+void MeshIntermediateRenderer::Render(int subsetIndex, const ChannelAssigns& channels)
+{
+ if (m_Mesh == NULL)
+ return;
+ if (m_CustomProperties)
+ GetGfxDevice().SetMaterialProperties (*m_CustomProperties);
+ DrawUtil::DrawMeshRaw (channels, *m_Mesh, m_SubMeshIndex); //@TODO: why not use subsetIndex here?
+}
+
+void MeshIntermediateRenderer::Initialize( const Matrix4x4f& matrix, Mesh* mesh, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows, int submeshIndex )
+{
+ m_Mesh = mesh;
+ if (m_Mesh)
+ {
+ m_Mesh->AddIntermediateUser(m_Node);
+
+ if (submeshIndex < 0 || submeshIndex >= m_Mesh->GetSubMeshCount())
+ {
+ AssertString("Submesh index in intermediate renderer is out of bounds");
+ submeshIndex = 0;
+ }
+ }
+
+ m_SubMeshIndex = submeshIndex;
+
+ IntermediateRenderer::Initialize(matrix, localAABB, material, layer, castShadows, receiveShadows);
+}
+
+
+// --------------------------------------------------------------------------
+
+#if ENABLE_SPRITES
+
+DEFINE_POOLED_ALLOC(SpriteIntermediateRenderer, 64 * 1024);
+
+void SpriteIntermediateRenderer::StaticInitialize()
+{
+ STATIC_INITIALIZE_POOL(SpriteIntermediateRenderer);
+}
+
+void SpriteIntermediateRenderer::StaticDestroy()
+{
+ STATIC_DESTROY_POOL(SpriteIntermediateRenderer);
+}
+
+static RegisterRuntimeInitializeAndCleanup s_SpriteIntermediateRendererCallbacks(SpriteIntermediateRenderer::StaticInitialize, SpriteIntermediateRenderer::StaticDestroy);
+
+
+SpriteIntermediateRenderer::SpriteIntermediateRenderer()
+{
+}
+
+SpriteIntermediateRenderer::~SpriteIntermediateRenderer()
+{
+}
+
+void SpriteIntermediateRenderer::OnAssetDeleted()
+{
+ m_Sprite = NULL;
+}
+
+void SpriteIntermediateRenderer::Render(int subsetIndex, const ChannelAssigns& channels)
+{
+ if (m_Sprite == NULL)
+ return;
+ if (m_CustomProperties)
+ GetGfxDevice().SetMaterialProperties(*m_CustomProperties);
+ DrawUtil::DrawSpriteRaw(channels, *m_Sprite, m_Color);
+}
+
+void SpriteIntermediateRenderer::Initialize(const Matrix4x4f& matrix, Sprite* sprite, const AABB& localAABB, PPtr<Material> material, int layer, const ColorRGBA32& color)
+{
+ m_Sprite = sprite;
+ if (m_Sprite)
+ m_Sprite->AddIntermediateUser(m_Node);
+
+ m_Color = color;
+
+ if (!material)
+ material = SpriteRenderer::GetDefaultSpriteMaterial();
+
+ // Patch sprite texture and apply material property block
+ PPtr<Texture2D> spriteTexture = m_Sprite->GetRenderData(false).texture; // Use non-atlased RenderData as input.
+ MaterialPropertyBlock block;
+ SpriteRenderer::SetupMaterialPropertyBlock(block, spriteTexture);
+ SetPropertyBlock(block);
+
+ IntermediateRenderer::Initialize(matrix, localAABB, material, layer, false, false);
+}
+
+#endif
+
+// --------------------------------------------------------------------------
+
+
+void IntermediateRenderers::Clear( size_t startIndex )
+{
+ size_t n = m_SceneNodes.size();
+ AssertIf( startIndex > n );
+
+ for( size_t i = startIndex; i < n; ++i )
+ {
+ IntermediateRenderer* renderer = static_cast<IntermediateRenderer*> (m_SceneNodes[i].renderer);
+ delete renderer;
+ }
+ m_SceneNodes.resize_uninitialized( startIndex );
+ m_BoundingBoxes.resize_uninitialized( startIndex );
+}
+
+const AABB* IntermediateRenderers::GetBoundingBoxes () const
+{
+ return m_BoundingBoxes.begin();
+}
+
+const SceneNode* IntermediateRenderers::GetSceneNodes () const
+{
+ return m_SceneNodes.begin();
+}
+
+void IntermediateRenderers::Add(IntermediateRenderer* renderer, int layer)
+{
+ m_SceneNodes.push_back(SceneNode ());
+
+ SceneNode& node = m_SceneNodes.back();
+ node.renderer = renderer;
+ node.layer = layer;
+
+ renderer->GetWorldAABB(m_BoundingBoxes.push_back());
+}
+
+IntermediateRenderer* AddMeshIntermediateRenderer( const Matrix4x4f& matrix, Mesh* mesh, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows, int submeshIndex, Camera* camera )
+{
+ AABB bounds;
+ if (mesh)
+ bounds = mesh->GetBounds();
+ else
+ bounds.SetCenterAndExtent( Vector3f::zero, Vector3f::zero );
+
+ return AddMeshIntermediateRenderer (matrix, mesh, bounds, material, layer, castShadows, receiveShadows, submeshIndex, camera);
+}
+
+IntermediateRenderer* AddMeshIntermediateRenderer( const Matrix4x4f& matrix, Mesh* mesh, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows, int submeshIndex , Camera* camera )
+{
+ MeshIntermediateRenderer* renderer = new MeshIntermediateRenderer();
+ renderer->Initialize(matrix, mesh, localAABB, material, layer, castShadows, receiveShadows, submeshIndex);
+
+ IntermediateRenderers* renderers;
+ if (camera != NULL)
+ renderers = &camera->GetIntermediateRenderers();
+ else
+ renderers = &GetScene().GetIntermediateRenderers();
+ renderers->Add(renderer, layer);
+
+ return renderer;
+}
+
+#if ENABLE_SPRITES
+IntermediateRenderer* AddSpriteIntermediateRenderer(const Matrix4x4f& matrix, Sprite* sprite, PPtr<Material> material, int layer, const ColorRGBA32& color, Camera* camera)
+{
+ AABB bounds;
+ if (sprite)
+ bounds = sprite->GetBounds();
+ else
+ bounds.SetCenterAndExtent( Vector3f::zero, Vector3f::zero );
+
+ return AddSpriteIntermediateRenderer (matrix, sprite, bounds, material, layer, color, camera);
+}
+
+IntermediateRenderer* AddSpriteIntermediateRenderer(const Matrix4x4f& matrix, Sprite* sprite, const AABB& localAABB, PPtr<Material> material, int layer, const ColorRGBA32& color, Camera* camera)
+{
+ SpriteIntermediateRenderer* renderer = new SpriteIntermediateRenderer();
+ renderer->Initialize(matrix, sprite, localAABB, material, layer, color);
+
+ IntermediateRenderers* renderers;
+ if (camera != NULL)
+ renderers = &camera->GetIntermediateRenderers();
+ else
+ renderers = &GetScene().GetIntermediateRenderers();
+ renderers->Add(renderer, layer);
+
+ return renderer;
+}
+#endif
diff --git a/Runtime/Camera/IntermediateRenderer.h b/Runtime/Camera/IntermediateRenderer.h
new file mode 100644
index 0000000..799cf47
--- /dev/null
+++ b/Runtime/Camera/IntermediateRenderer.h
@@ -0,0 +1,148 @@
+#ifndef INTERMEDIATE_RENDERER_H
+#define INTERMEDIATE_RENDERER_H
+
+#include "BaseRenderer.h"
+#include "Runtime/Shaders/MaterialProperties.h"
+#include "Runtime/Utilities/MemoryPool.h"
+#include "SceneNode.h"
+#include "IntermediateUsers.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Modules/ExportModules.h"
+
+class Mesh;
+class Camera;
+class Matrix4x4f;
+class Vector3f;
+class Quaternionf;
+class Sprite;
+
+class EXPORT_COREMODULE IntermediateRenderer : public BaseRenderer
+{
+public:
+ IntermediateRenderer ();
+ virtual ~IntermediateRenderer();
+
+ void Initialize(const Matrix4x4f& matrix, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows);
+
+ // BaseRenderer
+ virtual UInt32 GetLayerMask() const { return 1<<m_Layer; }
+ virtual int GetLayer() const { return m_Layer; }
+ virtual int GetMaterialCount() const { return 1; }
+ virtual PPtr<Material> GetMaterial(int i) const { return m_Material; }
+
+ virtual void OnAssetDeleted() = 0;
+ virtual void OnAssetBoundsChanged();
+
+ void SetPropertyBlock( const MaterialPropertyBlock& block )
+ {
+ m_Properties = block;
+ m_CustomProperties = &m_Properties;
+ ComputeCustomPropertiesHash();
+ }
+
+ #if UNITY_EDITOR
+ SInt32 GetInstanceID() const { return m_InstanceID; }
+ void SetInstanceID (SInt32 id) { m_InstanceID = id; }
+ #endif
+
+ virtual void UpdateTransformInfo() {};
+ virtual void UpdateAABB() {Assert(false);}
+
+ const AABB& GetCachedWorldAABB () const { return m_TransformInfo.worldAABB; }
+
+protected:
+ ListNode<IntermediateRenderer> m_Node;
+ PPtr<Material> m_Material;
+ MaterialPropertyBlock m_Properties;
+ int m_Layer;
+
+ #if UNITY_EDITOR
+ SInt32 m_InstanceID;
+ #endif
+};
+
+
+
+class EXPORT_COREMODULE MeshIntermediateRenderer : public IntermediateRenderer
+{
+public:
+ MeshIntermediateRenderer();
+ virtual ~MeshIntermediateRenderer();
+
+ void Initialize(const Matrix4x4f& matrix, Mesh* mesh, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows, int submeshIndex);
+
+ // BaseRenderer
+ virtual void Render(int materialIndex, const ChannelAssigns& channels);
+
+ virtual void OnAssetDeleted();
+
+ static void StaticInitialize ();
+ static void StaticDestroy ();
+
+private:
+ // Note: not using per-frame linear allocator, because in the editor
+ // it can render multiple frames using single player loop run (e.g. when editor is paused).
+ // Clearing per-frame data and then trying to use it later leads to Bad Things.
+ DECLARE_POOLED_ALLOC(MeshIntermediateRenderer);
+
+ Mesh* m_Mesh;
+ int m_SubMeshIndex;
+};
+
+
+
+#if ENABLE_SPRITES
+class EXPORT_COREMODULE SpriteIntermediateRenderer : public IntermediateRenderer
+{
+public:
+ SpriteIntermediateRenderer();
+ virtual ~SpriteIntermediateRenderer();
+
+ void Initialize(const Matrix4x4f& matrix, Sprite* sprite, const AABB& localAABB, PPtr<Material> material, int layer, const ColorRGBA32& color);
+
+ // BaseRenderer
+ virtual void Render(int materialIndex, const ChannelAssigns& channels);
+
+ virtual void OnAssetDeleted();
+
+ static void StaticInitialize ();
+ static void StaticDestroy ();
+
+private:
+ // Note: not using per-frame linear allocator, because in the editor
+ // it can render multiple frames using single player loop run (e.g. when editor is paused).
+ // Clearing per-frame data and then trying to use it later leads to Bad Things.
+ DECLARE_POOLED_ALLOC(SpriteIntermediateRenderer);
+
+ Sprite* m_Sprite;
+ ColorRGBAf m_Color;
+};
+#endif
+
+
+
+class IntermediateRenderers
+{
+public:
+ void Clear( size_t startIndex = 0 );
+
+ const AABB* GetBoundingBoxes () const;
+ const SceneNode* GetSceneNodes () const;
+ size_t GetRendererCount () const { return m_BoundingBoxes.size(); }
+
+ void Add(IntermediateRenderer* renderer, int layer);
+
+private:
+ dynamic_array<SceneNode> m_SceneNodes;
+ dynamic_array<AABB> m_BoundingBoxes;
+};
+
+IntermediateRenderer* AddMeshIntermediateRenderer( const Matrix4x4f& matrix, Mesh* mesh, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows, int submeshIndex, Camera* camera );
+IntermediateRenderer* AddMeshIntermediateRenderer( const Matrix4x4f& matrix, Mesh* mesh, const AABB& localAABB, PPtr<Material> material, int layer, bool castShadows, bool receiveShadows, int submeshIndex , Camera* camera );
+
+#if ENABLE_SPRITES
+IntermediateRenderer* AddSpriteIntermediateRenderer(const Matrix4x4f& matrix, Sprite* sprite, PPtr<Material> material, int layer, const ColorRGBA32& color, Camera* camera);
+IntermediateRenderer* AddSpriteIntermediateRenderer(const Matrix4x4f& matrix, Sprite* sprite, const AABB& localAABB, PPtr<Material> material, int layer, const ColorRGBA32& color, Camera* camera);
+#endif
+
+#endif
diff --git a/Runtime/Camera/IntermediateUsers.cpp b/Runtime/Camera/IntermediateUsers.cpp
new file mode 100644
index 0000000..2422376
--- /dev/null
+++ b/Runtime/Camera/IntermediateUsers.cpp
@@ -0,0 +1,21 @@
+#include "UnityPrefix.h"
+#include "IntermediateUsers.h"
+#include "IntermediateRenderer.h"
+
+void IntermediateUsers::Notify(IntermediateNotify notify)
+{
+ IntermediateRendererList::iterator i;
+ switch (notify)
+ {
+ case kImNotifyAssetDeleted:
+ for (i = m_IntermediateUsers.begin(); i != m_IntermediateUsers.end(); ++i)
+ (*i)->OnAssetDeleted();
+ break;
+ case kImNotifyBoundsChanged:
+ for (i = m_IntermediateUsers.begin(); i != m_IntermediateUsers.end(); ++i)
+ (*i)->OnAssetBoundsChanged();
+ break;
+ default:
+ AssertString("unknown notification");
+ }
+}
diff --git a/Runtime/Camera/IntermediateUsers.h b/Runtime/Camera/IntermediateUsers.h
new file mode 100644
index 0000000..273d6e0
--- /dev/null
+++ b/Runtime/Camera/IntermediateUsers.h
@@ -0,0 +1,26 @@
+#ifndef INTERMEDIATE_USERS_H
+#define INTERMEDIATE_USERS_H
+
+#include "BaseRenderer.h"
+#include "Runtime/Utilities/LinkedList.h"
+
+class IntermediateRenderer;
+
+enum IntermediateNotify
+{
+ kImNotifyAssetDeleted,
+ kImNotifyBoundsChanged,
+};
+
+class IntermediateUsers
+{
+public:
+ void Notify(IntermediateNotify notify);
+ void AddUser(ListNode<IntermediateRenderer>& node) { m_IntermediateUsers.push_back(node); }
+
+protected:
+ typedef List< ListNode<IntermediateRenderer> > IntermediateRendererList;
+ IntermediateRendererList m_IntermediateUsers; // IntermediateRenderer users of this data
+};
+
+#endif
diff --git a/Runtime/Camera/LODGroup.cpp b/Runtime/Camera/LODGroup.cpp
new file mode 100644
index 0000000..c95b8e5
--- /dev/null
+++ b/Runtime/Camera/LODGroup.cpp
@@ -0,0 +1,395 @@
+#include "UnityPrefix.h"
+#include "LODGroup.h"
+#include "LODGroupManager.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Filters/AABBUtility.h"
+#include "UnityScene.h"
+
+/*
+// @TODO:
+
+Ask aras:
+ RenderQueue.cpp
+ Create( "LIGHTMAP_OFF" );
+ Create( "LIGHTMAP_ON" );
+HUH???
+
+PRI 1
+ * Integrate with lightmaps? Probably need an option to reuse lightmap data if the uv's match up exactly between LOD's. Maybe we can automate it?
+ * multi_compile doesn't work. Seems like using more properties in a multicompile causes some shaders to drop.
+
+ ///@TODO: This should probably be 0. But for now we don't have proper ifdef support for switching to a different subshader.
+ #define LOD_FADE_DISABLED 0.999F
+
+ /////****** SHADER LOD FADING ****
+ ///@TODO: Make it so that fading is automatically disabled based on a shader tag or some shit like that.
+ ///@TODO: Expose the fade distance and visualize in inspector
+ ///@TODO: Add easy support for LOD fade in surface shaders
+ ///@TODO: Switch to a shader when it is not fading to reduce shader complexity
+
+PRI 2
+ ///@TODO: IntegrationTest: create lodgroup, attach renderer, delete lod group. Enable / disable renderer
+ ///@TODO: IntegrationTest: Make sure that m_LODs is never bigger than 8 (because of the lodIndex bitmask)
+
+ ///@TODO: GraphicsFunctionalTest / FunctionalTest: Write graphics functional test for LOD & layer based culling and projector especially when an object is being culled by the camera (Make sure it is also culled by projector)
+
+
+ ///@TODO: Does SceneManager really have to be recreated on every level load?
+ // This is probably related to the super weird behaviour of PlayerLoadLevel deactivate / activate ...
+
+
+
+PRI 3
+ * When calculating static objects pvs data. We could precalculate which static objects can be visible.
+ Have to be careful with runtime tweakable distance fudge...
+
+///@TODO: Use case:
+// "I assume a higher LOD can be triggered, for example, for an explosion effect,
+// swapping out the unbroken model for a broken one and passing the pieces to the physics engine with random velocities.."
+
+
+*/
+
+LODGroup::LODGroup (MemLabelId label, ObjectCreationMode mode)
+ : Super(label, mode)
+ , m_LODGroup (-1)
+{
+ m_Enabled = true;
+}
+
+LODGroup::~LODGroup ()
+{
+ Assert(m_LODGroup == kInvalidLODGroup);
+ Assert(m_CachedRenderers.empty());
+}
+
+void LODGroup::Reset ()
+{
+ Super::Reset();
+ m_LocalReferencePoint = Vector3f(0, 0, 0);
+ m_Size = 1.0F;
+ m_ScreenRelativeTransitionHeight = 0.0;
+ m_LODs.clear();
+}
+
+void LODGroup::SmartReset ()
+{
+ Super::SmartReset();
+
+ LODGroup::LOD lod;
+ lod.screenRelativeHeight = 0.6f;
+ m_LODs.push_back( lod );
+ lod.screenRelativeHeight = 0.3f;
+ m_LODs.push_back( lod );
+ lod.screenRelativeHeight = 0.1f;
+ m_LODs.push_back( lod );
+}
+
+void LODGroup::CheckConsistency ()
+{
+ Super::CheckConsistency();
+ m_LODs.resize(std::min<size_t>(m_LODs.size(), kMaximumLODLevels));
+}
+
+void LODGroup::AwakeFromLoad (AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad (mode);
+ UpdateEnabledState(IsActive());
+ SyncLODGroupManager();
+}
+
+void LODGroup::Deactivate (DeactivateOperation operation)
+{
+ UpdateEnabledState(false);
+ Super::Deactivate (operation);
+}
+
+void LODGroup::SetLocalReferencePoint (const Vector3f& ref)
+{
+ m_LocalReferencePoint = ref;
+ SyncLODGroupManager();
+ SetDirty();
+}
+
+void LODGroup::SetSize (float size)
+{
+ m_Size = size;
+ SyncLODGroupManager();
+ SetDirty();
+}
+
+void LODGroup::SyncLODGroupManager ()
+{
+ // Super inefficient...
+ if (m_LODGroup != kInvalidLODGroup)
+ {
+ Cleanup ();
+ Create ();
+ }
+}
+
+void LODGroup::NotifyLODGroupManagerIndexChange (int newIndex)
+{
+ m_LODGroup = newIndex;
+ for (int i=0;i<m_CachedRenderers.size();i++)
+ {
+ SceneHandle handle = m_CachedRenderers[i]->GetSceneHandle();
+ if (handle != kInvalidSceneHandle)
+ GetScene().SetRendererLODGroup(handle, newIndex);
+ }
+}
+
+bool DoesRendererSupportLODFade (Renderer& renderer)
+{
+ //@TODO:
+// MaterialArray& materials = renderer.GetMaterialArray();
+ return false;
+}
+
+// Goes through Renderers in the LODArray and sets up their LODGroup pointers & group indices and masks
+
+void LODGroup::RegisterCachedRenderers ()
+{
+ Assert(m_CachedRenderers.empty());
+ Assert(m_LODGroup != kInvalidLODGroup);
+
+ bool supportsLODFade = false;
+
+ Unity::Scene& scene = GetScene();
+
+ for (int i=0;i<m_LODs.size();i++)
+ {
+ LODRenderers& renderers = m_LODs[i].renderers;
+
+ for (int r=0;r<renderers.size();r++)
+ {
+ Renderer* renderer = renderers[r].renderer;
+ if (renderer == NULL)
+ continue;
+
+ supportsLODFade |= DoesRendererSupportLODFade (*renderer);
+
+ SceneHandle handle = renderer->GetSceneHandle();
+
+ // If the renderer has no LODGroup attached yet, then this is the first time that specific Renderer is used in this LODGroup.
+ // Thus we initialize the Group index & LODIndexMask with the current LOD Level
+ if (renderer->GetLODGroup () == NULL)
+ {
+ renderer->SetLODGroup (this);
+
+ // Initialize cull node lodgroup values
+ if (handle != kInvalidSceneHandle)
+ {
+ scene.SetRendererLODGroup(handle, m_LODGroup);
+ scene.SetRendererLODIndexMask(handle, 1 << i);
+ }
+ m_CachedRenderers.push_back(renderer);
+ }
+ // The renderer is attached to the same LOD group in a previous LOD level.
+ // Thus we add the current LOD level to the LODIndexMask
+ else if (renderer->GetLODGroup () == this)
+ {
+ if (handle != kInvalidSceneHandle)
+ {
+ UInt32 lodIndexMask = GetScene().GetRendererNode(handle).lodIndexMask;
+ lodIndexMask |= 1 << i;
+ scene.SetRendererLODIndexMask(handle, lodIndexMask);
+ }
+ }
+ // Fail (renderer is used in multiple LODGroups...)
+ else
+ {
+ string warningString = Format("Renderer '%s' is registered with more than one LODGroup ('%s' and '%s').", renderer->GetName(), GetName(), renderer->GetLODGroup ()->GetName());
+ WarningStringObject(warningString, renderer);
+ }
+ }
+ }
+}
+
+void LODGroup::ClearCachedRenderers ()
+{
+ for (int i=0;i<m_CachedRenderers.size();i++)
+ {
+ m_CachedRenderers[i]->SetLODGroup (NULL);
+ SceneHandle handle = m_CachedRenderers[i]->GetSceneHandle();
+ if (handle != kInvalidSceneHandle)
+ {
+ Unity::Scene& scene = GetScene();
+ scene.SetRendererLODGroup(handle, 0);
+ scene.SetRendererLODIndexMask(handle, 0);
+ }
+ }
+ m_CachedRenderers.resize_uninitialized(0);
+}
+
+void LODGroup::RemoveFromCachedRenderers (Renderer* renderer)
+{
+ for (int i=0;i<m_CachedRenderers.size();i++)
+ {
+ if (m_CachedRenderers[i] == renderer)
+ {
+ m_CachedRenderers[i] = m_CachedRenderers.back();
+ m_CachedRenderers.pop_back();
+ return;
+ }
+ }
+}
+
+void LODGroup::GetLODGroupIndexAndMask (Renderer* renderer, UInt32* outGroup, UInt32* outMask)
+{
+ Assert(m_LODGroup != kInvalidLODGroup);
+
+ PPtr<Renderer> rendererPPtr (renderer);
+
+ // Compute mask of which LOD
+ UInt32 mask = 0;
+ for (int i=0;i<m_LODs.size();i++)
+ {
+ LODRenderers& renderers = m_LODs[i].renderers;
+ for (int r=0;r<renderers.size();r++)
+ {
+ if (renderers[r].renderer == rendererPPtr)
+ mask |= 1 << i;
+ }
+ }
+
+ *outMask = mask;
+ *outGroup = m_LODGroup;
+}
+
+void LODGroup::SetLODArray (const LODArray& lodArray)
+{
+ m_LODs = lodArray;
+ SyncLODGroupManager();
+ SetDirty();
+}
+
+const LODGroup::LOD& LODGroup::GetLOD (int index)
+{
+ Assert (index < GetLODCount());
+ return m_LODs[index];
+}
+
+
+Vector3f LODGroup::GetWorldReferencePoint ()
+{
+ return GetComponent(Transform).TransformPoint(m_LocalReferencePoint);
+}
+
+float LODGroup::GetWorldSpaceScale ()
+{
+ Vector3f scale = GetComponent(Transform).GetWorldScaleLossy();
+ float largestAxis;
+ largestAxis = Abs(scale.x);
+ largestAxis = std::max (largestAxis, Abs(scale.y));
+ largestAxis = std::max (largestAxis, Abs(scale.z));
+ return largestAxis;
+}
+
+float LODGroup::GetWorldSpaceSize ()
+{
+ return GetWorldSpaceScale () * m_Size;
+}
+
+void LODGroup::UpdateEnabledState (bool active)
+{
+ Cleanup();
+ if (active)
+ Create();
+}
+
+void LODGroup::Create()
+{
+ if (m_Enabled)
+ {
+ GetLODGroupManager().AddLODGroup(*this, GetWorldReferencePoint(), GetWorldSpaceSize());
+ }
+ else
+ {
+ m_LODGroup = kDisabledLODGroup;
+ }
+
+ RegisterCachedRenderers();
+}
+
+
+bool LODGroup::GetEnabled()
+{
+ return m_Enabled;
+}
+
+void LODGroup::SetEnabled(bool enabled)
+{
+ if ((bool)m_Enabled == enabled)
+ return;
+ m_Enabled = enabled;
+ UpdateEnabledState (IsActive ());
+ SetDirty ();
+}
+
+void LODGroup::Cleanup ()
+{
+ if (m_LODGroup != kInvalidLODGroup)
+ {
+ ClearCachedRenderers();
+ if (m_LODGroup == kDisabledLODGroup)
+ m_LODGroup = kInvalidLODGroup;
+ else
+ GetLODGroupManager().RemoveLODGroup(*this);
+ }
+}
+
+void LODGroup::OnTransformChanged (int options)
+{
+ if (m_LODGroup != kInvalidLODGroup)
+ {
+ // Scale changed: update all parameters
+ if (options & Transform::kScaleChanged)
+ GetLODGroupManager().UpdateLODGroupParameters(m_LODGroup, *this, GetWorldReferencePoint(), GetWorldSpaceSize());
+ // rotation or position changed: fastpath for just changing the reference point
+ else
+ {
+ GetLODGroupManager().UpdateLODGroupPosition(m_LODGroup, GetWorldReferencePoint());
+ }
+ }
+}
+
+template<class TransferFunction> inline
+void LODGroup::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ TRANSFER (m_LocalReferencePoint);
+ TRANSFER (m_Size);
+ TRANSFER (m_ScreenRelativeTransitionHeight);
+ TRANSFER (m_LODs);
+ transfer.Transfer (m_Enabled, "m_Enabled", kHideInEditorMask);
+}
+
+template<class TransferFunction> inline
+void LODGroup::LODRenderer::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (renderer);
+}
+
+template<class TransferFunction> inline
+void LODGroup::LOD::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (screenRelativeHeight);
+ TRANSFER (renderers);
+}
+
+void LODGroup::InitializeClass ()
+{
+ REGISTER_MESSAGE (LODGroup, kTransformChanged, OnTransformChanged, int);
+
+ InitializeLODGroupManager();
+}
+
+void LODGroup::CleanupClass ()
+{
+ if (GetLODGroupManagerPtr())
+ CleanupLODGroupManager();
+}
+
+IMPLEMENT_CLASS_HAS_INIT(LODGroup)
+IMPLEMENT_OBJECT_SERIALIZE(LODGroup)
diff --git a/Runtime/Camera/LODGroup.h b/Runtime/Camera/LODGroup.h
new file mode 100644
index 0000000..5def8d1
--- /dev/null
+++ b/Runtime/Camera/LODGroup.h
@@ -0,0 +1,105 @@
+#pragma once
+
+#include "Runtime/GameCode/Behaviour.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Math/Vector3.h"
+
+class LODGroup : public Unity::Component
+{
+public:
+
+ struct LODRenderer
+ {
+ PPtr<Renderer> renderer;
+
+ DECLARE_SERIALIZE (LODRenderer)
+ };
+
+ typedef dynamic_array<LODRenderer> LODRenderers;
+ struct LOD
+ {
+ float screenRelativeHeight;
+ LODRenderers renderers;
+
+ LOD ()
+ : screenRelativeHeight (0.0F)
+ { }
+
+
+ DECLARE_SERIALIZE (LOD)
+ };
+ typedef std::vector<LOD> LODArray;
+
+ REGISTER_DERIVED_CLASS (LODGroup, Component)
+ DECLARE_OBJECT_SERIALIZE(LODGroup)
+
+ LODGroup (MemLabelId label, ObjectCreationMode mode);
+ // virtual ~LODGroup (); declared-by-macro
+
+ virtual void Reset ();
+ virtual void SmartReset ();
+ virtual void CheckConsistency ();
+ virtual void AwakeFromLoad (AwakeFromLoadMode mode);
+ virtual void Deactivate (DeactivateOperation operation);
+
+ // Property get / set
+ Vector3f GetLocalReferencePoint () { return m_LocalReferencePoint; }
+ void SetLocalReferencePoint (const Vector3f& ref);
+ Vector3f GetWorldReferencePoint ();
+
+ float GetWorldSpaceSize ();
+
+
+ void UpdateEnabledState (bool active);
+ bool GetEnabled();
+ void SetEnabled(bool enabled);
+
+ float GetSize () const { return m_Size; }
+ void SetSize (float size);
+
+ int GetLODCount () const { return m_LODs.size(); }
+ const LOD& GetLOD (int index);
+ void SetLODArray (const LODArray& lodArray);
+ void GetLODArray (LODArray& lodArray) const { lodArray = m_LODs; }
+ int GetLODGroup () const { return m_LODGroup; }
+
+ // Interface for Renderer / Scene
+ void ClearCachedRenderers ();
+ void RegisterCachedRenderers ();
+ void RemoveFromCachedRenderers (Renderer* renderer);
+ void NotifyLODGroupManagerIndexChange (int newIndex);
+ void GetLODGroupIndexAndMask (Renderer* renderer, UInt32* outLODGroupIndex, UInt32* outActiveLODMask);
+
+ static void InitializeClass();
+ static void CleanupClass();
+
+ // Supported messages
+ void OnTransformChanged (int options);
+ float GetWorldSpaceScale ();
+
+private:
+ void Create();
+ void Cleanup();
+
+ void SyncLODGroupManager ();
+
+ Vector3f m_LocalReferencePoint;
+ float m_Size;
+ LODArray m_LODs;
+ int m_LODGroup;
+ bool m_Enabled;
+ float m_ScreenRelativeTransitionHeight;
+
+
+ typedef dynamic_array<Renderer*> CachedRenderers;
+ CachedRenderers m_CachedRenderers;
+
+ friend class LODGroupManager;
+};
+
+
+struct MonoLOD
+{
+ float screenRelativeTransitionHeight;
+ ScriptingArrayPtr renderers;
+}; \ No newline at end of file
diff --git a/Runtime/Camera/LODGroupManager.cpp b/Runtime/Camera/LODGroupManager.cpp
new file mode 100644
index 0000000..0463cf3
--- /dev/null
+++ b/Runtime/Camera/LODGroupManager.cpp
@@ -0,0 +1,462 @@
+#include "UnityPrefix.h"
+#include "LODGroupManager.h"
+#include "LODGroup.h"
+#include "CullingParameters.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h"
+#include "Runtime/Misc/BuildSettings.h"
+
+PROFILER_INFORMATION (gComputeLOD, "LOD.ComputeLOD", kProfilerRender)
+
+LODGroupManager* gLODGroupManager = NULL;
+
+LODGroupManager& GetLODGroupManager ()
+{
+ Assert(gLODGroupManager != NULL);
+ return *gLODGroupManager;
+}
+
+LODGroupManager* GetLODGroupManagerPtr ()
+{
+ return gLODGroupManager;
+}
+void CleanupLODGroupManager ()
+{
+ Assert(gLODGroupManager != NULL);
+ UNITY_DELETE (gLODGroupManager, kMemRenderer);
+}
+
+void InitializeLODGroupManager ()
+{
+ Assert(gLODGroupManager == NULL);
+ gLODGroupManager = UNITY_NEW_AS_ROOT(LODGroupManager(), kMemRenderer, "LODGroupManager", "");
+}
+
+
+LODGroupManager::LODGroupManager ()
+{
+ m_LODBias = 1.0F;
+ m_MaximumLOD = 0;
+
+ memset(&m_SelectionData.push_back(), 0, sizeof(LODSelectionData));
+}
+
+// The basic LOD distance check in orthomode:
+// * Pixel size check: if (array[i].pixelHeight < lodGroup.m_Size * 0.5F / parameters.orthoSize * parameters.cameraPixelHeight)
+// * Relative height check: if (array[i].relativeHeight < lodGroup.m_Size * 0.5F / parameters.orthoSize)
+
+// The basic LOD distance check in perspective:
+// ...
+
+// All LOD calculations are distance based "reference point in LOD group" to camera position.
+// - Rotating a camera never switches LOD
+// - Point based means it's very predictable behaviour that is easy to visualize accurately
+// - Fast to calculate
+// float distance = CalculateFOVDistanceFudge () * CaclulateLODDistance();
+
+
+float CalculateFOVHalfAngle (const CullingParameters& parameters)
+{
+ return tan(Deg2Rad (parameters.lodFieldOfView) * 0.5F);
+}
+
+enum { kScreenRelativeMetric = 0, kPixelMetric = 1, kMetricCount = 2 };
+
+void CalculateLODFudge (const CullingParameters& parameters, float* fudge)
+{
+ float screenRelativeMetric;
+ if (parameters.isOrthographic)
+ {
+ screenRelativeMetric = 2.0F * parameters.orthoSize;
+ }
+ else
+ {
+ // Half angle at 90 degrees is 1.0 (So we skip halfAngle / 1.0 calculation)
+ float halfAngle = CalculateFOVHalfAngle(parameters);
+ screenRelativeMetric = 2.0 * halfAngle;
+ }
+
+ fudge[kScreenRelativeMetric] = screenRelativeMetric;
+ fudge[kPixelMetric] = screenRelativeMetric / parameters.cameraPixelHeight;
+}
+
+float CalculateLODDistance (float relativeScreenHeight, float size)
+{
+ return size / relativeScreenHeight;
+}
+
+float DistanceToRelativeHeight (const CullingParameters& parameters, float distance, float size)
+{
+ if (parameters.isOrthographic)
+ {
+ return size * 0.5F / parameters.orthoSize;
+ }
+ else
+ {
+ float halfAngle = CalculateFOVHalfAngle(parameters);
+ return size * 0.5F / (distance * halfAngle);
+ }
+}
+
+
+void LODGroupManager::CalculatePerspectiveLODMask (const LODSelectionData& selection, const Vector3f& position, int maximumLOD, int currentMask, const float* fieldOfViewFudge, UInt8* output, float* fade)
+{
+ if (selection.forceLODLevelMask != 0)
+ {
+ *output = selection.forceLODLevelMask;
+ *fade = 1.0F;
+ return;
+ }
+
+ Vector3f offset = selection.worldReferencePoint - position;
+
+ float sqrDistance = SqrMagnitude(offset);
+ sqrDistance *= fieldOfViewFudge[kScreenRelativeMetric] * fieldOfViewFudge[kScreenRelativeMetric];
+
+ // Early out if the object is getting culled because it is too far away.
+ *output = 0;
+ *fade = 0.0F;
+
+ // Must use the same metric for everything.... Otherwise this will fail
+ if (sqrDistance > selection.maxDistanceSqr)
+ return;
+
+ int maxDistancesCount = selection.maxDistancesCount;
+ const float* distances = selection.maxDistances;
+
+ bool supportsFade = selection.fadeDistance != 0.0F;
+
+ for (int i=maximumLOD;i<maxDistancesCount;i++)
+ {
+ // Is camera closer than maximum LOD distance?
+ float lodMaxDistance = distances[i];
+ float lodMaxDistanceSqr = lodMaxDistance * lodMaxDistance;
+
+ if (sqrDistance < lodMaxDistanceSqr)
+ {
+ // @TODO: This if could be optimized out of the inner loop
+ if (supportsFade)
+ {
+ // Is the next LOD in the transition range?
+ float dif = lodMaxDistance - sqrt(sqrDistance);
+ if (dif < selection.fadeDistance)
+ {
+ currentMask |= currentMask << 1;
+ *output = currentMask;
+ *fade = dif / selection.fadeDistance;
+ }
+ else
+ {
+ ///@TODO: this should be zero, because when you are not fading it shouldn't use a shader that does fading.
+ *output = currentMask;
+ *fade = 1.0F;
+ }
+ }
+ else
+ {
+ ///@TODO: this should be zero, because when you are not fading it shouldn't use a shader that does fading.
+ *output = currentMask;
+ *fade = 1.0F;
+ }
+
+ return;
+ }
+
+ currentMask <<= 1;
+ }
+}
+
+void LODGroupManager::CalculateOrthoLODMask (const LODSelectionData& selection, int maximumLOD, int currentMask, const float* fudge, UInt8* output, float* fade)
+{
+ if (selection.forceLODLevelMask != 0)
+ {
+ *output = selection.forceLODLevelMask;
+ *fade = 1.0F;
+ return;
+ }
+
+ ///@TODO: DO IT
+ *output = 0;
+ *fade = 0.0F;
+
+ int maxDistancesCount = selection.maxDistancesCount;
+ const float* distances = selection.maxDistances;
+
+ float distance = fudge[kScreenRelativeMetric];
+
+ for (int i=maximumLOD;i<maxDistancesCount;i++)
+ {
+ if (distance < distances[i])
+ {
+ // Is the next LOD in the transition range?
+ float dif = distances[i] - distance;
+ if (dif < selection.fadeDistance)
+ {
+ currentMask |= currentMask << 1;
+ *output = currentMask;
+ *fade = dif / selection.fadeDistance;
+ }
+ else
+ {
+ ///@TODO: this should be zero, because when you are not fading it shouldn't use a shader that does fading.
+ *output = currentMask;
+ *fade = 1.0F;
+ }
+
+ return;
+ }
+
+ currentMask <<= 1;
+ }
+}
+
+void LODGroupManager::CalculateLODMasks (const CullingParameters& parameters, UInt8* outMasks, float* outFades)
+{
+ PROFILER_AUTO(gComputeLOD, NULL)
+
+ // Get field of view / pixel fudge values and sqr it so the inner loop doesn't have to do it.
+ float fieldOfViewFudge[kMetricCount];
+ CalculateLODFudge (parameters, fieldOfViewFudge);
+ for (int i=0;i<kMetricCount;i++)
+ fieldOfViewFudge[i] = fieldOfViewFudge[i] / m_LODBias;
+
+ int lodGroupCount = m_SelectionData.size();
+ DebugAssert(lodGroupCount > 0);
+ outMasks[0] = 0;
+ outFades[0] = 0;
+ int baseMask = 1 << m_MaximumLOD;
+ if (parameters.isOrthographic)
+ {
+ for (int i=1;i<lodGroupCount;i++)
+ CalculateOrthoLODMask(m_SelectionData[i], m_MaximumLOD, baseMask, fieldOfViewFudge, &outMasks[i], &outFades[i]);
+ }
+ else
+ {
+ for (int i=1;i<lodGroupCount;i++)
+ CalculatePerspectiveLODMask(m_SelectionData[i], parameters.lodPosition, m_MaximumLOD, baseMask, fieldOfViewFudge, &outMasks[i], &outFades[i]);
+ }
+}
+
+inline UInt32 LowestBit2Consecutive8Bit (UInt32 v)
+{
+ Assert(v == (v & 0xff));
+ UInt32 extra = (v & (v - 1)) == 0;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v++;
+
+ UInt32 table[] = { v >> 2, v >> 1 };
+ return table[extra];
+}
+
+// The shader isn't using clamp on the z value of the 3D dither texture.
+// Thus we have to ensure that it never actually becomes 1.0
+inline float ClampForGPURepeat (float fade)
+{
+ return clamp(fade, 0.0F, LOD_FADE_DISABLED);
+}
+
+float LODGroupManager::CalculateLODFade (UInt32 lodGroupIndex, UInt32 rendererActiveLODMask, const UInt8* lodMasks, const float* lodFades)
+{
+ if (rendererActiveLODMask == 0)
+ return LOD_FADE_DISABLED;
+
+ // rendererActiveLODMask:
+ // The mask of all LOD levels that this renderer participates in.
+
+ // The mask of all active LOD levels for this group.
+ // Eg. LOD 0 and LOD1 enabled -> 1 | 2
+ UInt8 activeMaskOfLODGroup = lodMasks[lodGroupIndex];
+
+ // If renderer is part of all active LOD groups, then it should be completely visible.
+ if ((rendererActiveLODMask & activeMaskOfLODGroup) == activeMaskOfLODGroup)
+ return LOD_FADE_DISABLED;
+
+ // If the renderer is part of the lowest bit then it is part of the highest LOD (duh!)
+ bool isPartOfHighLOD = LowestBit2Consecutive8Bit (activeMaskOfLODGroup) & rendererActiveLODMask;
+
+ // The highest LOD Level uses the computed fade value
+ if (isPartOfHighLOD)
+ {
+ return ClampForGPURepeat(lodFades[lodGroupIndex]);
+ }
+ // The lower lod level uses the inverse
+ else
+ {
+ return ClampForGPURepeat(1.0F - lodFades[lodGroupIndex]);
+ }
+}
+
+void LODGroupManager::ClearAllForceLODMask ()
+{
+ for (int i=0;i<m_SelectionData.size();i++)
+ m_SelectionData[i].forceLODLevelMask = 0;
+
+}
+
+#if UNITY_EDITOR
+static void AddRenderersToVisualizationStats (const LODGroup::LODRenderers& renderers, LODVisualizationInformation& information)
+{
+ // Calculate triangle & vertex & mesh count for the renderers in this LOD
+ for (int i=0;i<renderers.size();i++)
+ {
+ Renderer* renderer = renderers[i].renderer;
+ if (renderer)
+ {
+ RenderStats stats;
+ renderer->GetRenderStats (stats);
+
+ information.triangleCount += stats.triangleCount;
+ information.vertexCount += stats.vertexCount;
+ information.rendererCount += 1;
+ information.submeshCount += stats.submeshCount;
+ }
+ }
+}
+
+LODVisualizationInformation LODGroupManager::CalculateVisualizationData (const CullingParameters& cullingParameters, LODGroup& lodGroup, int lodLevel)
+{
+ LODVisualizationInformation information;
+ memset(&information, 0, sizeof(information));
+ information.activeLODLevel = kInvalidLODGroup;
+
+ // Calculate switch distance
+ float fudge[kMetricCount];
+ CalculateLODFudge (cullingParameters, fudge);
+ for (int i=0;i<kMetricCount;i++)
+ fudge[i] = fudge[i] / m_LODBias;
+
+ float sqrFudge[kMetricCount];
+ for (int i=0;i<kMetricCount;i++)
+ sqrFudge[i] = fudge[i];
+ float lodFade = 0.0F;
+
+ if (lodGroup.m_LODGroup != kInvalidLODGroup && lodGroup.m_LODGroup != kDisabledLODGroup)
+ {
+ UInt8 activeLODMask;
+ if (cullingParameters.isOrthographic)
+ CalculateOrthoLODMask (m_SelectionData[lodGroup.m_LODGroup], m_MaximumLOD, 1 << m_MaximumLOD, sqrFudge, &activeLODMask, &lodFade);
+ else
+ CalculatePerspectiveLODMask (m_SelectionData[lodGroup.m_LODGroup], cullingParameters.lodPosition, m_MaximumLOD, 1 << m_MaximumLOD, sqrFudge, &activeLODMask, &lodFade);
+
+ if (activeLODMask != 0)
+ information.activeLODLevel = LowestBit(activeLODMask);
+ else
+ information.activeLODLevel = -1;
+ }
+
+ if (lodLevel == -1)
+ lodLevel = information.activeLODLevel;
+
+ // Calculate current distances & bounding volume sizes
+ information.activeDistance = Magnitude(lodGroup.GetWorldReferencePoint() - cullingParameters.lodPosition);
+ information.activeRelativeScreenSize = DistanceToRelativeHeight(cullingParameters, information.activeDistance, lodGroup.GetWorldSpaceSize()) * m_LODBias;
+ information.activePixelSize = information.activeRelativeScreenSize * cullingParameters.cameraPixelHeight;
+ information.activeWorldSpaceSize = lodGroup.GetWorldSpaceSize ();
+ information.activeLODFade = lodFade;
+
+ if (lodLevel != -1)
+ {
+ // Calculate switch distance for the lod
+ const LODGroup::LOD& lod = lodGroup.m_LODs[lodLevel];
+
+ // Calculate triangle & vertex & mesh count for the renderers in this LOD
+ AddRenderersToVisualizationStats(lod.renderers, information);
+ }
+
+ return information;
+}
+#endif //UNITY_EDITOR
+
+void LODGroupManager::AddLODGroup (LODGroup& group, const Vector3f& position, float worldSpaceSize)
+{
+ // Add Group
+ int index = m_SelectionData.size();
+ m_SelectionData.push_back();
+ group.m_LODGroup = index;
+
+ // Initialize parameters
+ UpdateLODGroupParameters(index, group, position, worldSpaceSize);
+
+ m_SelectionData.back().forceLODLevelMask = 0;
+}
+
+void LODGroupManager::UpdateLODGroupParameters (int index, LODGroup& group, const Vector3f& position, float worldSpaceSize)
+{
+ LODSelectionData& data = m_SelectionData[index];
+
+ data.worldReferencePoint = position;
+ data.lodGroup = &group;
+ data.maxDistancesCount = group.m_LODs.size();
+ data.maxDistanceSqr = 0.0F;
+
+ Assert(group.m_LODs.size() <= kMaximumLODLevels);
+
+ float totalMaxDistance = 0.0F;
+
+ if (!GetBuildSettings ().hasAdvancedVersion)
+ {
+ data.maxDistancesCount = std::max (1, data.maxDistancesCount);
+ totalMaxDistance = data.maxDistances[0] = CalculateLODDistance(0.0001F, worldSpaceSize);
+ }
+ else
+ {
+
+ for (int i=0;i<group.m_LODs.size();i++)
+ {
+ float maxDistance = CalculateLODDistance(group.m_LODs[i].screenRelativeHeight, worldSpaceSize);
+
+ totalMaxDistance = std::max(maxDistance, totalMaxDistance);
+ data.maxDistances[i] = maxDistance;
+ }
+ }
+
+ data.maxDistanceSqr = totalMaxDistance * totalMaxDistance;
+
+ bool useLODFade = group.m_ScreenRelativeTransitionHeight > 0.00001F && !group.m_LODs.empty();
+ if (useLODFade)
+ {
+ float baseRelativeHeight = group.m_LODs.front().screenRelativeHeight;
+ data.fadeDistance = CalculateLODDistance (baseRelativeHeight - group.m_ScreenRelativeTransitionHeight, worldSpaceSize) - CalculateLODDistance (baseRelativeHeight, worldSpaceSize);
+ }
+ else
+ data.fadeDistance = 0.0F;
+}
+
+void LODGroupManager::RemoveLODGroup (LODGroup& group)
+{
+ // Remove from array by replacing with the last element.
+ Assert(group.m_CachedRenderers.empty());
+
+ int index = group.m_LODGroup;
+ Assert(index != 0);
+
+ // Update LODGroup index of the LODGroup we moved from the back
+ m_SelectionData.back().lodGroup->NotifyLODGroupManagerIndexChange(index);
+
+ m_SelectionData[index] = m_SelectionData.back();
+ m_SelectionData.pop_back();
+
+ group.m_LODGroup = kInvalidLODGroup;
+}
+
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+SUITE (LODGroupManagerTests)
+{
+TEST (LODGroupManagerTests_PrevPowerOfTwoUInt8)
+{
+ for (int i=0;i<7;i++)
+ {
+ CHECK_EQUAL (1 << i, LowestBit2Consecutive8Bit(1 << i));
+ CHECK_EQUAL (1 << i, LowestBit2Consecutive8Bit((1 << i) | (1 << (i+1))));
+ }
+}
+}
+
+#endif
diff --git a/Runtime/Camera/LODGroupManager.h b/Runtime/Camera/LODGroupManager.h
new file mode 100644
index 0000000..7623b63
--- /dev/null
+++ b/Runtime/Camera/LODGroupManager.h
@@ -0,0 +1,116 @@
+#pragma once
+
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Utilities/BitUtility.h"
+
+class LODGroup;
+struct CullingParameters;
+
+enum
+{
+ kMaximumLODLevels = 8,
+ kInvalidLODGroup = -1,
+
+ // When an LOD group is disabled, we want to make all renderers be disabled.
+ // For this purpose we have a single shared LODGroup index, with a mask that has all renderers always disabled.
+ kDisabledLODGroup = 0
+};
+
+#if UNITY_EDITOR
+// LOD Visualization
+// (NOTE: Keep LODUtilityBindings.txt struct in sync)
+struct LODVisualizationInformation
+{
+ int triangleCount;
+ int vertexCount;
+ int rendererCount;
+ int submeshCount;
+
+ int activeLODLevel;
+ float activeLODFade;
+ float activeDistance;
+ float activeRelativeScreenSize;
+ float activePixelSize;
+ float activeWorldSpaceSize;
+};
+#endif
+
+class LODGroupManager
+{
+ struct LODSelectionData
+ {
+ // The point we measure the LOD distance against
+ Vector3f worldReferencePoint;
+ float maxDistanceSqr;
+
+ // LOD maximum distance values
+ float maxDistances[kMaximumLODLevels];
+ int maxDistancesCount;
+ float fadeDistance;
+
+ // The associated lod group
+ LODGroup* lodGroup;
+
+ UInt32 forceLODLevelMask;
+ };
+
+ dynamic_array<LODSelectionData> m_SelectionData;
+ float m_LODBias;
+ UInt32 m_MaximumLOD;
+
+public:
+
+ LODGroupManager ();
+
+ void AddLODGroup (LODGroup& group, const Vector3f& position, float worldSpaceSize);
+ void RemoveLODGroup (LODGroup& group);
+
+ void UpdateLODGroupParameters (int index, LODGroup& group, const Vector3f& position, float worldSpaceSize);
+ void UpdateLODGroupPosition (int index, const Vector3f& position) { m_SelectionData[index].worldReferencePoint = position; }
+
+ void SetLODBias (float b) { m_LODBias = b; }
+ float GetLODBias () const { return m_LODBias; }
+
+ void SetMaximumLODLevel (UInt32 b) { m_MaximumLOD = b; }
+ UInt32 GetMaximumLODLevel () const { return m_MaximumLOD; }
+
+ void ResetLODBias () { SetLODBias(1.0F); }
+
+ int GetLODGroupCount () const { return m_SelectionData.size(); }
+
+
+ // Used by scene culling to determine
+ // /lodGroupIndex/ is the index of LODGroup into m_SelectionData & m_ActiveLOD
+ // /activeLODMask/ is the LOD mask of the renderer.
+ // m_ActiveLOD[lodGroupIndex].activeMask is a bitmask specifying which LOD levels should be rendered.
+ // When cross-fading between two LOD's multiple LOD's might be active in the same LODGroup
+ static bool IsLODVisible (UInt32 lodGroupIndex, UInt32 activeLODMask, const UInt8* activeLOD)
+ {
+ if (activeLODMask == 0)
+ return true;
+
+ return activeLOD[lodGroupIndex] & activeLODMask;
+ }
+
+ void CalculateLODMasks (const CullingParameters& parameters, UInt8* outMasks, float* outFades);
+ static float CalculateLODFade (UInt32 lodGroupIndex, UInt32 rendererActiveLODMask, const UInt8* lodMasks, const float* lodFades);
+
+ static void CalculatePerspectiveLODMask (const LODSelectionData& selection, const Vector3f& position, int maximumLOD, int currentMask, const float* fieldOfViewFudge, UInt8* output, float* outputFade);
+ static void CalculateOrthoLODMask (const LODSelectionData& selection, int maximumLOD, int currentMask, const float* fudge, UInt8* output, float* outputFade);
+
+#if UNITY_EDITOR
+ LODVisualizationInformation CalculateVisualizationData (const CullingParameters& cullingParameters, LODGroup& lodGroup, int lod);
+
+#endif
+
+ void SetForceLODMask (int index, UInt32 forceEditorLODMask) { m_SelectionData[index].forceLODLevelMask = forceEditorLODMask; }
+ UInt32 GetForceLODMask (int index) { return m_SelectionData[index].forceLODLevelMask; }
+ void ClearAllForceLODMask ();
+
+};
+
+LODGroupManager* GetLODGroupManagerPtr ();
+LODGroupManager& GetLODGroupManager ();
+void CleanupLODGroupManager ();
+void InitializeLODGroupManager ();
diff --git a/Runtime/Camera/Light.cpp b/Runtime/Camera/Light.cpp
new file mode 100644
index 0000000..a17c209
--- /dev/null
+++ b/Runtime/Camera/Light.cpp
@@ -0,0 +1,668 @@
+#include "UnityPrefix.h"
+#include "Light.h"
+#include "Shadows.h"
+#include "RenderSettings.h"
+#include "HaloManager.h"
+#include "Runtime/BaseClasses/Tags.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Graphics/CubemapTexture.h"
+#include "Runtime/Graphics/Image.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "External/shaderlab/Library/properties.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Shaders/ShaderKeywords.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Camera/LightManager.h"
+#if UNITY_EDITOR
+#include "Runtime/Misc/BuildSettings.h"
+#endif
+
+
+
+using namespace Unity;
+
+
+static SHADERPROP (LightTexture0);
+
+const UInt64 kAllLightKeywordsMask = 0x1F;
+
+
+// constants for opengl attenuation
+static const float kConstantFac = 1.000f;
+static const float kQuadraticFac = 25.0f;
+// where the falloff down to zero should start
+static const float kToZeroFadeStart = 0.8f * 0.8f;
+
+float Light::CalcQuadFac (float range)
+{
+ return kQuadraticFac / (range * range);
+}
+
+float Light::AttenuateNormalized(float distSqr)
+{
+ // match the vertex lighting falloff
+ float atten = 1 / (kConstantFac + CalcQuadFac (1.0f) * distSqr);
+
+ // ...but vertex one does not falloff to zero at light's range; it falls off to 1/26 which
+ // is then doubled by our shaders, resulting in 19/255 difference!
+ // So force it to falloff to zero at the edges.
+ if( distSqr >= kToZeroFadeStart )
+ {
+ if( distSqr > 1 )
+ atten = 0;
+ else
+ atten *= 1 - (distSqr - kToZeroFadeStart) / (1 - kToZeroFadeStart);
+ }
+
+ return atten;
+}
+
+Light::Light(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_GfxLightValid(false)
+, m_ActuallyLightmapped(false)
+{
+ m_KeywordMode = kLightKeywordDirectional;
+ m_HaloHandle = 0;
+ m_FlareHandle = -1;
+ m_World2Local = Matrix4x4f::identity;
+ m_WorldPosition = Vector3f::zero;
+}
+
+Light::~Light ()
+{
+}
+
+void Light::InitializeClass () {
+ REGISTER_MESSAGE_VOID (Light, kTransformChanged, TransformChanged);
+}
+
+void Light::CleanupClass ()
+{
+}
+
+float Light::AttenuateApprox (float sqrDist) const
+{
+ return 1.0f / (kConstantFac + CalcQuadFac(m_Range) * sqrDist);
+}
+
+
+void Light::TransformChanged ()
+{
+ if (IsAddedToManager ())
+ {
+ const Transform& transform = GetComponent(Transform);
+ m_World2Local = transform.GetWorldToLocalMatrixNoScale ();
+ m_WorldPosition = transform.GetPosition ();
+ Precalc ();
+ }
+ m_GfxLightValid = false;
+}
+
+Light::Lightmapping Light::GetLightmappingForBake() const
+{
+ if (m_Type == kLightArea)
+ return kLightmappingBakedOnly;
+
+ return static_cast<Lightmapping>(m_Lightmapping);
+}
+
+void Light::Reset ()
+{
+ Super::Reset();
+ m_Shadows.Reset();
+
+ m_Color = ColorRGBAf (1,1,1,1);
+ m_Intensity = 1.0f;
+ m_Range = 10.0f;
+ m_SpotAngle = 30.0f;
+ m_CookieSize = 10.0f;
+ m_Lightmapping = kLightmappingAuto;
+ UpdateSpotAngleValues ();
+ m_RenderMode = kRenderAuto;
+ m_DrawHalo = false;
+ m_Type = kLightPoint;
+ m_CullingMask.m_Bits = -1;
+ #if UNITY_EDITOR
+ m_ShadowSamples = 1;
+ m_ShadowRadius = 0.0f;
+ m_ShadowAngle = 0.0f;
+ m_IndirectIntensity = 1.0f;
+ m_AreaSize.Set (1,1);
+ #endif
+}
+
+void Light::CheckConsistency ()
+{
+ Texture *cookie = m_Cookie;
+ // If this is a point light and cookie is not a cubemap, remove the cookie
+ if( m_Type == kLightPoint && cookie && cookie->GetClassID () != ClassID (Cubemap) )
+ {
+ m_Cookie = NULL;
+ cookie = NULL;
+ }
+
+ // If this is not a point light and cookie is a cubemap, remove the cookie
+ if( m_Type != kLightPoint && cookie && cookie->GetClassID () == ClassID (Cubemap) )
+ {
+ m_Cookie = NULL;
+ cookie = NULL;
+ }
+
+ // I think this is to get cookie-to-cubemap working on Radeon 7000 path. Enforcing cookies
+ // to be square makes constructing cubemap much easier.
+ if( m_Type == kLightSpot && cookie && cookie->GetDataHeight() != cookie->GetDataWidth() )
+ {
+ ErrorStringObject ("Spotlight cookies must be square (width and height must be equal)", this);
+ m_Cookie = 0;
+ }
+
+ m_Range = std::max (m_Range, 0.0f);
+ m_SpotAngle = std::min (m_SpotAngle, 179.0f);
+ m_SpotAngle = std::max (m_SpotAngle, 1.0f);
+ m_CookieSize = std::max (m_CookieSize, 0.0f);
+ m_Shadows.m_Bias = clamp (m_Shadows.m_Bias, 0.0f, 10.0f);
+ m_Shadows.m_Softness = clamp (m_Shadows.m_Softness, 1.0f, 8.0f);
+ m_Shadows.m_SoftnessFade = clamp (m_Shadows.m_SoftnessFade, 0.1f, 5.0f);
+ #if UNITY_EDITOR
+ m_ShadowSamples = std::max<int> (m_ShadowSamples, 1);
+ m_IndirectIntensity = std::max (m_IndirectIntensity, 0.0f);
+ m_AreaSize.x = std::max(m_AreaSize.x, 0.0f);
+ m_AreaSize.y = std::max(m_AreaSize.y, 0.0f);
+ #endif
+}
+
+void Light::AddToManager ()
+{
+ DebugAssert (!IsInList());
+ const Transform& transform = GetComponent(Transform);
+ m_World2Local = transform.GetWorldToLocalMatrixNoScale ();
+ m_WorldPosition = transform.GetPosition ();
+ GetLightManager().AddLight(this);
+
+ SetupHalo ();
+ SetupFlare ();
+}
+
+void Light::RemoveFromManager ()
+{
+ if (IsInList())
+ {
+ GetLightManager().RemoveLight (this);
+ }
+ if (m_HaloHandle) {
+ GetHaloManager().DeleteHalo (m_HaloHandle);
+ m_HaloHandle = 0;
+ }
+ if (m_FlareHandle != -1)
+ {
+ GetFlareManager ().DeleteFlare (m_FlareHandle);
+ m_FlareHandle = -1;
+ }
+}
+
+void Light::Precalc ()
+{
+ // setup the light cookie/attenuation textures
+ Texture *cookie = m_Cookie;
+ switch (m_Type)
+ {
+ case kLightSpot:
+ if (!cookie)
+ cookie = GetRenderSettings().GetDefaultSpotCookie();
+
+ m_AttenuationTexture = cookie;
+ m_AttenuationMode = kSpotCookie;
+ m_KeywordMode = kLightKeywordSpot;
+ break;
+
+ case kLightPoint:
+ if (cookie) {
+ m_AttenuationTexture = cookie;
+ m_AttenuationMode = kPointFalloff;
+ m_KeywordMode = kLightKeywordPointCookie;
+ } else {
+ m_AttenuationTexture = builtintex::GetAttenuationTexture();
+ m_AttenuationMode = kPointFalloff;
+ m_KeywordMode = kLightKeywordPoint;
+ }
+ break;
+
+ case kLightDirectional:
+ if (cookie)
+ {
+ m_AttenuationTexture = cookie;
+ m_AttenuationMode = kDirectionalCookie;
+ m_KeywordMode = kLightKeywordDirectionalCookie;
+ }
+ else
+ {
+ m_AttenuationTexture = NULL;
+ m_AttenuationMode = kUnused;
+ m_KeywordMode = kLightKeywordDirectional;
+ }
+ break;
+ }
+
+ m_ConvertedFinalColor = GammaToActiveColorSpace (m_Color) * m_Intensity;
+
+ UpdateSpotAngleValues ();
+
+ SetupHalo();
+ SetupFlare();
+}
+
+
+
+
+void Light::SetPropsToShaderLab (float blend) const
+{
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+
+ params.SetVectorParam(kShaderVecLightColor0, Vector4f((m_ConvertedFinalColor * blend).GetPtr()));
+ Texture* attenTex = m_AttenuationTexture;
+ if (attenTex)
+ {
+ ShaderLab::PropertySheet* probs = ShaderLab::g_GlobalProperties;
+ probs->SetTexture (kSLPropLightTexture0, attenTex);
+ }
+}
+
+void Light::SetLightKeyword()
+{
+ UInt64 mask = g_ShaderKeywords.GetMask();
+ mask &= ~kAllLightKeywordsMask;
+ mask |= 1ULL << m_KeywordMode;
+ g_ShaderKeywords.SetMask (mask);
+}
+
+void Light::GetMatrix (const Matrix4x4f* __restrict object2light, Matrix4x4f* __restrict outMatrix) const
+{
+ Matrix4x4f temp1, temp2, temp3;
+ float scale;
+
+ switch (m_AttenuationMode) {
+ case kSpotCookie:
+ // we want out.w = 2.0 * in.z / m_CotanHalfSpotAngle
+ // c = m_CotanHalfSpotAngle
+ // 1 0 0 0
+ // 0 1 0 0
+ // 0 0 1 0
+ // 0 0 2/c 0
+ // the "2" will be used to scale .xy for the cookie as in .xy/2 + 0.5
+ temp3.SetIdentity();
+ temp3.Get(3,2) = 2.0f / m_CotanHalfSpotAngle;
+ temp3.Get(3,3) = 0;
+
+ scale = 1.0f / m_Range;
+ temp1.SetScale (Vector3f(scale,scale,scale));
+
+ // temp3 * temp1 * object2Light
+ MultiplyMatrices4x4 (&temp3, &temp1, &temp2);
+ MultiplyMatrices4x4 (&temp2, object2light, outMatrix);
+ break;
+ case kPointFalloff:
+ scale = 1.0f / m_Range;
+ temp1.SetScale (Vector3f(scale,scale,scale));
+ MultiplyMatrices4x4 (&temp1, object2light, outMatrix);
+ break;
+ case kDirectionalCookie:
+ scale = 1.0f / m_CookieSize;
+ temp1.SetScale (Vector3f (scale, scale, 0));
+ temp2.SetTranslate (Vector3f (.5f, .5f, 0));
+ // temp2 * temp1 * object2Light
+ MultiplyMatrices4x4 (&temp2, &temp1, &temp3);
+ MultiplyMatrices4x4 (&temp3, object2light, outMatrix);
+ break;
+ case kUnused:
+ break;
+ }
+}
+
+
+
+void Light::ComputeGfxLight (GfxVertexLight& gfxLight) const
+{
+ gfxLight.type = static_cast<LightType>(m_Type);
+
+ const Transform& tr = GetComponent(Transform);
+ switch( m_Type ) {
+ case kLightPoint:
+ {
+ Vector3f lightPos = tr.GetPosition();
+ gfxLight.position.Set( lightPos.x, lightPos.y, lightPos.z, 1.0f );
+ gfxLight.spotAngle = -1.0f;
+ gfxLight.quadAtten = CalcQuadFac(m_Range);
+ gfxLight.spotDirection.Set( 1.0f, 0.0f, 0.0f, 0.0f );
+ }
+ break;
+ case kLightDirectional:
+ {
+ Vector3f lightDir = tr.TransformDirection( Vector3f (0,0,1) );
+ gfxLight.position.Set( lightDir.x, lightDir.y, lightDir.z, 0.0f );
+ gfxLight.quadAtten = 0.0f;
+ gfxLight.spotAngle = -1.0f;
+ gfxLight.spotDirection.Set( 1.0f, 0.0f, 0.0f, 0.0f );
+ }
+ break;
+ case kLightSpot:
+ {
+ Vector3f lightPos = tr.GetPosition();
+ gfxLight.position.Set( lightPos.x, lightPos.y, lightPos.z, 1.0f );
+ Vector3f lightDir = tr.TransformDirection (Vector3f (0,0,1));
+ gfxLight.spotDirection.Set( lightDir.x, lightDir.y, lightDir.z, 0.0f );
+ gfxLight.spotAngle = m_SpotAngle;
+ gfxLight.quadAtten = CalcQuadFac(m_Range);
+ }
+ break;
+ case kLightArea:
+ break;
+ default:
+ ErrorStringObject( "Unsupported light type", this );
+ }
+
+ // Light color & range
+ gfxLight.color.Set( m_ConvertedFinalColor.GetPtr() );
+ gfxLight.range = m_Range;
+}
+
+void Light::SetupVertexLight (int lightNo, float visibilityFade)
+{
+ if (!m_GfxLightValid)
+ {
+ ComputeGfxLight (m_CachedGfxLight);
+ m_GfxLightValid = true;
+ }
+ GfxDevice& device = GetGfxDevice();
+
+ const ColorRGBAf color = GetConvertedFinalColor();
+ Vector4f fadedColor;
+ fadedColor.x = color.r * visibilityFade;
+ fadedColor.y = color.g * visibilityFade;
+ fadedColor.z = color.b * visibilityFade;
+
+ m_CachedGfxLight.color = fadedColor;
+ device.SetLight (lightNo, m_CachedGfxLight);
+}
+
+
+void Light::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad (awakeMode);
+ if ((awakeMode & kDidLoadFromDisk) == 0 && GetEnabled () && IsActive ())
+ {
+ const Transform& transform = GetComponent(Transform);
+ m_World2Local = transform.GetWorldToLocalMatrixNoScale ();
+ m_WorldPosition = transform.GetPosition ();
+ SetupHalo ();
+ SetupFlare ();
+ }
+ m_GfxLightValid = false;
+ Precalc ();
+}
+
+void Light::SetFlare (Flare *flare)
+{
+ if (m_Flare == PPtr<Flare> (flare))
+ return;
+ m_Flare = flare;
+ if (GetEnabled () && IsActive ())
+ SetupFlare();
+}
+
+void Light::SetType (LightType type)
+{
+ m_Type = type;
+ SetDirty(); m_GfxLightValid = false; Precalc();
+}
+
+void Light::SetColor (const ColorRGBAf& c)
+{
+ m_Color = c;
+ SetDirty(); m_GfxLightValid = false; Precalc();
+}
+
+void Light::SetIntensity( float i )
+{
+ m_Intensity = clamp(i, 0.0f, 8.0f);
+ SetDirty(); m_GfxLightValid = false; Precalc();
+}
+
+int Light::GetFinalShadowResolution() const
+{
+ int lightShadowResolution = GetShadowResolution();
+ if (lightShadowResolution == -1) // use global resolution?
+ {
+ const QualitySettings::QualitySetting& quality = GetQualitySettings().GetCurrent();
+ lightShadowResolution = quality.shadowResolution;;
+ }
+ return lightShadowResolution;
+}
+
+void Light::SetShadows( int v )
+{
+ m_Shadows.m_Type = v;
+ SetDirty();
+}
+
+void Light::SetActuallyLightmapped (bool v)
+{
+ if (m_ActuallyLightmapped != v)
+ {
+ m_ActuallyLightmapped = v;
+ SetDirty();
+ m_GfxLightValid = false;
+ }
+}
+
+void Light::SetCookie (Texture *tex)
+{
+ if (m_Cookie == PPtr<Texture> (tex))
+ return;
+
+ m_Cookie = tex;
+ SetDirty();
+ CheckConsistency ();
+ Precalc ();
+}
+
+template<class TransferFunc>
+void Light::Transfer (TransferFunc& transfer) {
+ Super::Transfer (transfer);
+ transfer.SetVersion(3);
+
+ TRANSFER_SIMPLE (m_Type);
+ TRANSFER_SIMPLE (m_Color);
+ TRANSFER (m_Intensity);
+ TRANSFER_SIMPLE (m_Range);
+ TRANSFER_SIMPLE (m_SpotAngle);
+ if (transfer.IsVersionSmallerOrEqual(2))
+ {
+ m_CookieSize = m_SpotAngle * 2.0f;
+ }
+ else
+ {
+ TRANSFER (m_CookieSize);
+ }
+
+ #if UNITY_EDITOR
+ if (transfer.IsVersionSmallerOrEqual(1))
+ {
+ transfer.Transfer(m_Shadows.m_Type, "m_Shadows");
+ transfer.Transfer(m_Shadows.m_Resolution, "m_ShadowResolution");
+ transfer.Transfer(m_Shadows.m_Strength, "m_ShadowStrength");
+ }
+ else
+ {
+ transfer.Transfer(m_Shadows, "m_Shadows");
+ }
+ #else
+ transfer.Transfer(m_Shadows, "m_Shadows");
+ #endif
+
+ TRANSFER (m_Cookie);
+ transfer.Transfer (m_DrawHalo, "m_DrawHalo", kSimpleEditorMask);
+ transfer.Transfer (m_ActuallyLightmapped, "m_ActuallyLightmapped", kDontAnimate);
+ transfer.Align();
+ TRANSFER (m_Flare);
+ TRANSFER (m_RenderMode);
+ TRANSFER (m_CullingMask);
+ TRANSFER (m_Lightmapping);
+
+ TRANSFER_EDITOR_ONLY (m_ShadowSamples);
+ TRANSFER_EDITOR_ONLY (m_ShadowRadius);
+ TRANSFER_EDITOR_ONLY (m_ShadowAngle);
+ TRANSFER_EDITOR_ONLY (m_IndirectIntensity);
+ TRANSFER_EDITOR_ONLY (m_AreaSize);
+}
+
+void Light::SetupHalo () {
+ if( m_DrawHalo && IsActive() && GetEnabled() )
+ {
+ float haloStr = GetRenderSettings().GetHaloStrength();
+ if (!m_HaloHandle)
+ m_HaloHandle = GetHaloManager().AddHalo();
+
+ if (m_HaloHandle) {
+ ///@TODO: Handle color conversion.
+ GetHaloManager().UpdateHalo( m_HaloHandle, GetComponent(Transform).GetPosition(), m_Color * (haloStr * m_Intensity * m_Color.a), haloStr * m_Range, GetGameObject().GetLayerMask() );
+ }
+ } else {
+ if (m_HaloHandle) {
+ GetHaloManager().DeleteHalo (m_HaloHandle);
+ m_HaloHandle = 0;
+ }
+ }
+}
+
+void Light::SetupFlare ()
+{
+ Flare *flare = m_Flare;
+ if (!flare || !IsActive() || !GetEnabled())
+ {
+ if (m_FlareHandle != -1)
+ {
+ GetFlareManager().DeleteFlare (m_FlareHandle);
+ m_FlareHandle = -1;
+ }
+ return;
+ }
+
+ bool inf;
+ Vector3f pos;
+
+ if (m_Type != kLightDirectional)
+ {
+ pos = GetComponent(Transform).GetPosition();
+ inf = false;
+ }
+ else
+ {
+ pos = GetComponent(Transform).TransformDirection (Vector3f (0,0,1));
+ inf = true;
+ }
+
+ if (m_FlareHandle == -1)
+ m_FlareHandle = GetFlareManager().AddFlare ();
+ GetFlareManager().UpdateFlare(
+ m_FlareHandle,
+ flare,
+ pos,
+ inf,
+ GetRenderSettings().GetFlareStrength(),
+ m_ConvertedFinalColor,
+ GetRenderSettings().GetFlareFadeSpeed(),
+ GetGameObject().GetLayerMask(),
+ kNoFXLayerMask|kIgnoreRaycastMask
+ );
+}
+
+
+bool Light::IsValidToRender() const
+{
+ // Spot lights with range lower than a pretty high value of 0.001f have to be culled already,
+ // as the code extracting projection planes for culling isn't smart enough to handle smaller values.
+ return !((m_Type == kLightSpot && (m_Range < 0.001f || m_SpotAngle < 0.001f)) ||
+ (m_Type == kLightPoint && m_Range < 0.00000001f));
+}
+
+
+IMPLEMENT_CLASS_HAS_INIT (Light)
+IMPLEMENT_OBJECT_SERIALIZE (Light)
+
+/// @TODO: Hack and should be removed before 2.0
+void SetupVertexLights(const std::vector<Light*>& lights)
+{
+ GfxDevice& device = GetGfxDevice();
+
+ /// @TODO: .a is multiplied differently only here. This is very inconsistent
+ /// Someone with guts please fix it or get rid of SetupVertexLights codepath completely.
+ ColorRGBAf ambient = GetRenderSettings().GetAmbientLightInActiveColorSpace();
+ ambient *= ColorRGBAf(0.5F, 0.5F, 0.5F, 1.0F);
+ device.SetAmbient( ambient.GetPtr() );
+
+
+ int lightNumber = 0;
+ for (int i = 0, size = lights.size(); i < size; ++i)
+ {
+ Light* light = lights[i];
+ if (light)
+ {
+ light->SetupVertexLight(lightNumber, 1.0f); // @TODO: Visibility fade does not work with this vertex light setup path
+ lightNumber++;
+ }
+ }
+ device.DisableLights (lightNumber);
+}
+
+
+void SetLightScissorRect (const Rectf& lightRect, const Rectf& viewPort, bool intoRT, GfxDevice& device)
+{
+ Rectf rect = lightRect;
+ rect.Scale (viewPort.width, viewPort.height);
+ if (!intoRT)
+ rect.Move (viewPort.x, viewPort.y);
+ int scissorRect[4];
+ RectfToViewport (rect, scissorRect);
+ FlipScreenRectIfNeeded (device, scissorRect);
+ device.SetScissorRect (scissorRect[0], scissorRect[1], scissorRect[2], scissorRect[3]);
+}
+
+void ClearScissorRect (bool oldScissor, const int oldRect[4], GfxDevice& device)
+{
+ if (oldScissor)
+ device.SetScissorRect (oldRect[0],oldRect[1],oldRect[2],oldRect[3]);
+ else
+ device.DisableScissor();
+}
+
+
+// --------------------------------------------------------------------------
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+SUITE (LightTests)
+{
+ TEST(LightKeywordsHaveExpectedValues)
+ {
+ CHECK_EQUAL (kLightKeywordSpot, keywords::Create("SPOT"));
+ CHECK_EQUAL (kLightKeywordDirectional, keywords::Create("DIRECTIONAL"));
+ CHECK_EQUAL (kLightKeywordDirectionalCookie, keywords::Create("DIRECTIONAL_COOKIE"));
+ CHECK_EQUAL (kLightKeywordPoint, keywords::Create("POINT"));
+ CHECK_EQUAL (kLightKeywordPointCookie, keywords::Create("POINT_COOKIE"));
+ UInt32 mask = 0;
+ for (int i = 0; i < kLightKeywordCount; ++i)
+ mask += 1<<i;
+ CHECK_EQUAL (kAllLightKeywordsMask, mask);
+ }
+} // SUITE
+#endif // ENABLE_UNIT_TESTS
diff --git a/Runtime/Camera/Light.h b/Runtime/Camera/Light.h
new file mode 100644
index 0000000..c14a59c
--- /dev/null
+++ b/Runtime/Camera/Light.h
@@ -0,0 +1,254 @@
+#ifndef LIGHT_H
+#define LIGHT_H
+
+#include "Lighting.h"
+#include "Flare.h"
+#include "ShadowSettings.h"
+#include "Runtime/GameCode/Behaviour.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Geometry/AABB.h"
+#include "Runtime/GfxDevice/GfxDeviceObjects.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+#include "Runtime/Utilities/LinkedList.h"
+#if UNITY_EDITOR
+#include "Editor/Src/LightmapVisualization.h"
+#endif
+#include "Runtime/Math/Rect.h"
+
+class Texture;
+class GfxDevice;
+
+class Light : public Behaviour, public ListElement
+{
+public:
+
+ REGISTER_DERIVED_CLASS (Light, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (Light)
+
+ Light (MemLabelId label, ObjectCreationMode mode);
+ // ~Light(); declared-by-macro
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ // Tag class as sealed, this makes QueryComponent faster.
+ static bool IsSealedClass () { return true; }
+
+ virtual void Reset ();
+
+ // System interaction
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+ virtual void CheckConsistency ();
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+
+ void TransformChanged ();
+
+
+ LightType GetType() const { return static_cast<LightType>( m_Type ); }
+ void SetType (LightType type);
+
+
+ /// How to render this light.
+ enum RenderMode
+ {
+ kRenderAuto, ///< Automatic
+ kRenderImportant, ///< This light is important
+ kRenderNotImportant, ///< This light is not very important
+ kRenderModeCount // keep this last!
+ };
+ /// Get the render mode hint for this light
+ int GetRenderMode() const { return m_RenderMode; }
+ void SetRenderMode (int mode) { m_RenderMode = mode; SetDirty(); m_GfxLightValid = false; }
+
+ enum Lightmapping
+ {
+ kLightmappingRealtimeOnly = 0, // light is not baked, realtime only
+ kLightmappingAuto = 1, // light is baked in the dual lightmap style
+ kLightmappingBakedOnly = 2 // light is fully baked, not rendering in realtime at all
+ };
+
+ void SetLightmapping (int mode) { m_Lightmapping = mode; SetDirty(); m_GfxLightValid = false; }
+ inline Lightmapping GetLightmappingForRender() const;
+ Lightmapping GetLightmappingForBake() const;
+
+ void SetActuallyLightmapped (bool v);
+ bool GetActuallyLightmapped() const { return m_ActuallyLightmapped; }
+
+ /// Get the full range of the light.
+ float GetRange() const { return m_Range; }
+ void SetRange (float range) { m_Range = std::max(0.0F, range); SetDirty (); m_GfxLightValid = false; Precalc (); }
+
+ /// Get the spot angle in degrees.
+ float GetSpotAngle() const { return m_SpotAngle; }
+ void SetSpotAngle (float angle) { m_SpotAngle = angle; CheckConsistency(); SetDirty (); m_GfxLightValid = false; Precalc (); }
+
+ float GetCookieSize() const { return m_CookieSize; }
+ void SetCookieSize (float size) { m_CookieSize = size; CheckConsistency(); SetDirty (); m_GfxLightValid = false; Precalc (); }
+
+ // range * this value = end side length
+ float GetCotanHalfSpotAngle () const { return m_CotanHalfSpotAngle; }
+ // range * this value = diagonal side length
+ float GetInvCosHalfSpotAngle () const { return m_InvCosHalfSpotAngle; }
+
+ /// Get/set the cookie used for the light
+ void SetCookie (Texture *tex);
+ Texture *GetCookie () const { return m_Cookie; }
+
+ /// Get/set the lens flare used
+ void SetFlare (Flare *flare);
+ Flare *GetFlare () { return m_Flare; }
+
+ const ColorRGBAf& GetColor () const { return m_Color; }
+ void SetColor (const ColorRGBAf& c);
+ ColorRGBAf GetConvertedFinalColor() const { return m_ConvertedFinalColor; }
+
+ float GetIntensity() const { return m_Intensity; }
+ void SetIntensity( float i );
+
+ float GetShadowStrength() const { return m_Shadows.m_Strength; }
+ void SetShadowStrength( float f ) { m_Shadows.m_Strength = f; SetDirty(); }
+ ShadowType GetShadows() const { return static_cast<ShadowType>(m_Shadows.m_Type); }
+ void SetShadows( int v );
+ int GetShadowResolution() const { return m_Shadows.m_Resolution; }
+ void SetShadowResolution( int v ) { m_Shadows.m_Resolution = v; SetDirty(); }
+ float GetShadowBias() const { return m_Shadows.m_Bias; }
+ void SetShadowBias( float v ) { m_Shadows.m_Bias = v; SetDirty(); }
+ float GetShadowSoftness() const { return m_Shadows.m_Softness; }
+ void SetShadowSoftness (float v) { m_Shadows.m_Softness = v; SetDirty(); }
+ float GetShadowSoftnessFade() const { return m_Shadows.m_SoftnessFade; }
+ void SetShadowSoftnessFade (float v) { m_Shadows.m_SoftnessFade = v; SetDirty(); }
+
+ int GetFinalShadowResolution() const;
+
+ #if UNITY_EDITOR
+ int GetShadowSamples() const { return m_ShadowSamples; }
+ void SetShadowSamples (int samples) { m_ShadowSamples = samples; }
+ float GetShadowRadius() const { return m_ShadowRadius; }
+ void SetShadowRadius (float radius) { m_ShadowRadius = radius; }
+ float GetShadowAngle() const { return m_ShadowAngle; }
+ void SetShadowAngle (float angle) { m_ShadowAngle = angle; }
+ float GetIndirectIntensity() const { return m_IndirectIntensity; }
+ void SetIndirectIntensity (float intensity) { m_IndirectIntensity = intensity; }
+ Vector2f GetAreaSize() const { return m_AreaSize; }
+ void SetAreaSize(const Vector2f& areaSize) { m_AreaSize = areaSize; }
+ #endif
+
+ /// Calculate the quadratic attenuation factor for a light with a specified range
+ /// @param range the range of the light
+ /// @return the quadratic attenuation factor
+ static float CalcQuadFac (float range);
+
+ // Attenuation function: inverse quadratic. distSqr is over 0..1 range
+ static float AttenuateNormalized(float distSqr);
+
+ // Get the result of attenuation for a squared distance from the light
+ float AttenuateApprox (float sqrDist) const;
+
+ // Set up this light as GfxDevice fixed function per-vertex light.
+ // IMPORTANT: Assumes the modelview matrix has been set up to be the world2camera matrix.
+ void SetupVertexLight (int lightNo, float visibilityFade);
+
+ void SetLightKeyword();
+ void SetPropsToShaderLab (float blend) const;
+
+
+ // Set up the halo for the light.
+ void SetupHalo ();
+ // Set up the flare for the light.
+ void SetupFlare ();
+
+ void SetCullingMask (int mask) { m_CullingMask.m_Bits = mask; SetDirty(); }
+ int GetCullingMask () const { return m_CullingMask.m_Bits; }
+
+ // Precalc all non-changing shaderlab values.
+ void Precalc ();
+
+ const Vector3f& GetWorldPosition() const { return m_WorldPosition; }
+ const Matrix4x4f& GetWorldToLocalMatrix() const { return m_World2Local; }
+
+ enum AttenuationMode
+ {
+ kSpotCookie, // 2D cookie projected in spot light's cone
+ kPointFalloff, // Attenuation for a point light
+ kDirectionalCookie, // Cookie projected from a directional light
+ kUnused // The attenuation texture is not used
+ };
+ void GetMatrix (const Matrix4x4f* __restrict object2light, Matrix4x4f* __restrict outMatrix) const;
+
+ bool IsValidToRender() const;
+
+private:
+ void UpdateSpotAngleValues ()
+ {
+ float halfSpotRad = Deg2Rad(m_SpotAngle * 0.5f);
+ float cs = cosf(halfSpotRad);
+ float ss = sinf(halfSpotRad);
+ m_CotanHalfSpotAngle = cs / ss;
+ m_InvCosHalfSpotAngle = 1.0f / cs;
+ }
+
+ void ComputeGfxLight (GfxVertexLight& gfxLight) const;
+
+private:
+ Matrix4x4f m_World2Local;
+ Vector3f m_WorldPosition;
+ ShadowSettings m_Shadows; ///< Shadow settings.
+
+ ColorRGBAf m_Color;
+ ColorRGBAf m_ConvertedFinalColor;
+
+ PPtr<Flare> m_Flare; ///< Does the light have a flare?
+ PPtr<Texture> m_Cookie; ///< Custom cookie (optional).
+ BitField m_CullingMask; ///< The mask used for selectively lighting objects in the scene.
+ float m_Intensity; ///< Light intensity range {0.0, 8.0}
+ float m_Range; ///< Light range
+ float m_SpotAngle; ///< Angle of the spotlight cone.
+ float m_CookieSize; ///< Cookie size for directional lights.
+ float m_CotanHalfSpotAngle; // cotangent of half of the spot angle
+ float m_InvCosHalfSpotAngle; // 1/cos of half of the spot angle
+ int m_RenderMode; ///< enum { Auto, Important, Not Important } Rendering mode for the light.
+ int m_Lightmapping; ///< enum { RealtimeOnly, Auto, BakedOnly } Is light baked into lightmaps?
+ int m_Type; ///< enum { Spot, Directional, Point, Area (baked only) } Light type
+ bool m_DrawHalo; ///< Does the light have a halo?
+ bool m_ActuallyLightmapped; // Is it actually lightmapped already?
+ #if UNITY_EDITOR
+ int m_ShadowSamples; // number of samples for lightmapper shadow calculations
+ float m_ShadowRadius; // radius of the light source for lightmapper shadow calculations (point and spot lights)
+ float m_ShadowAngle; // angle of the cone for lightmapper shadow rays (directional lights)
+ float m_IndirectIntensity; // aka bounce intensity - a multiplier for the indirect light
+ Vector2f m_AreaSize; // size of area light's rectangle
+ #endif
+
+ PPtr<Texture> m_AttenuationTexture;
+ AttenuationMode m_AttenuationMode;
+ LightKeywordMode m_KeywordMode; // The current keyword used by the light
+
+ int m_HaloHandle, m_FlareHandle;
+
+ GfxVertexLight m_CachedGfxLight;
+ bool m_GfxLightValid;
+};
+
+void SetupVertexLights(const std::vector<Light*>& lights);
+void SetLightScissorRect (const Rectf& lightRect, const Rectf& viewPort, bool intoRT, GfxDevice& device);
+void ClearScissorRect (bool oldScissor, const int oldRect[4], GfxDevice& device);
+
+
+// Inline this; called a lot in light culling.
+inline Light::Lightmapping Light::GetLightmappingForRender() const
+{
+ if (m_Type == kLightArea)
+ return kLightmappingBakedOnly;
+
+ // all lights behave as realtime only if no lightmaps have been baked yet or if lightmaps are disabled
+ return m_ActuallyLightmapped
+ #if UNITY_EDITOR
+ && GetLightmapVisualization().GetUseLightmapsForRendering()
+ #endif
+ ? static_cast<Lightmapping>(m_Lightmapping) : kLightmappingRealtimeOnly;
+}
+
+
+#endif
diff --git a/Runtime/Camera/LightCulling.cpp b/Runtime/Camera/LightCulling.cpp
new file mode 100644
index 0000000..771ebc3
--- /dev/null
+++ b/Runtime/Camera/LightCulling.cpp
@@ -0,0 +1,669 @@
+#include "UnityPrefix.h"
+#include "LightManager.h"
+#include "CullResults.h"
+#include "Light.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Geometry/Sphere.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Geometry/BoundingUtils.h"
+#include "Runtime/Math/Simd/math.h"
+#include "ShadowCulling.h"
+#include "Runtime/Profiler/Profiler.h"
+
+#include "External/Umbra/builds/interface/runtime/umbraTome.hpp"
+#include "External/Umbra/builds/interface/runtime/umbraQuery.hpp"
+
+PROFILER_INFORMATION(gCullLightConnectivity, "CullLightConnectivity", kProfilerRender);
+PROFILER_INFORMATION(gOcclusionCullLight, "OcclusionCullLight", kProfilerRender);
+
+struct LocalLightCullingParameters
+{
+ Plane eyePlane;
+ float farDistance;
+ bool enableShadows;
+ UInt32 cullingMask;
+};
+
+
+
+static float CalculateIntensityForMainLight (const Light& source)
+{
+ DebugAssert(source.GetType() == kLightDirectional);
+ if (source.GetRenderMode() == Light::kRenderNotImportant || source.GetLightmappingForRender() == Light::kLightmappingBakedOnly)
+ return 0.0f;
+
+ float lum = source.GetColor().GreyScaleValue() * source.GetIntensity();
+ if (source.GetShadows() != kShadowNone)
+ lum *= 16.0f;
+ return lum;
+}
+
+static void OcclusionCullLocalLights (const SceneCullingParameters& cullingParams, const Vector4f* lightBSpheres, IndexList& visible)
+{
+ // Umbra light culling is not supported. Just output
+ const Umbra::Visibility* umbraVisibility = cullingParams.sceneVisbilityForShadowCulling->umbraVisibility;
+ if (!cullingParams.useLightOcclusionCulling)
+ return;
+
+ PROFILER_BEGIN(gOcclusionCullLight, NULL)
+
+ Umbra::OcclusionBuffer* occlusionBuffer = umbraVisibility->getOutputBuffer();
+
+ // Mark lights visible and add them to lightVisibleBits
+ size_t visibleCount = 0;
+ for (int l = 0; l < visible.size; l++)
+ {
+ int lightNdx = visible[l];
+
+ const float* lightFloatParams = reinterpret_cast<const float*> (lightBSpheres + lightNdx);
+
+ float r = lightFloatParams[3];
+ Vector3f r3(r, r, r);
+ Vector3f lc = Vector3f(lightFloatParams[0], lightFloatParams[1], lightFloatParams[2]);
+
+ Vector3f mn = lc - r3;
+ Vector3f mx = lc + r3;
+
+ bool isLightVisible = occlusionBuffer->isAABBVisible((const Umbra::Vector3&)mn, (const Umbra::Vector3&)mx);
+ if (isLightVisible)
+ visible.indices[visibleCount++] = lightNdx;
+ }
+ visible.size = visibleCount;
+
+ PROFILER_END
+
+ PROFILER_BEGIN(gCullLightConnectivity, NULL)
+
+
+ ///@TODO: This doesn't make sense for non-shadowed lights...
+
+ // Use connectivity data to detect which lights are can touch any visible geometry
+ Umbra::IndexList visibleLightList(visible.indices, visible.size, visible.size);
+ Umbra::QueryExt* umbraQuery = (Umbra::QueryExt*)cullingParams.umbraQuery;
+
+ umbraQuery->queryLocalLights(
+ visibleLightList,
+ 0,
+ (const Umbra::SphereLight*)lightBSpheres,
+ visible.reservedSize,
+ *umbraVisibility->getOutputClusters(),
+ &visibleLightList);
+
+ visible.size = visibleLightList.getSize();
+
+ PROFILER_END
+}
+
+static void FrustumCullLocalLights (const CullingParameters& cullingParameters, const Vector4f* lightBSpheres, IndexList& visibleLights, IndexList& offScreenLights, float* visibilityFades)
+{
+ int visibleLightCount = 0;
+ int offScreenLightCount = 0;
+
+ for (int i=0;i<visibleLights.reservedSize;i++)
+ {
+ float distance = PointDistanceToFrustum(lightBSpheres[i], cullingParameters.cullingPlanes, cullingParameters.cullingPlaneCount);
+
+ // Light is inside or intersecting the frustum
+ if (distance < lightBSpheres[i].w)
+ {
+ // lights that intersect or are inside the frustum
+ DebugAssert(visibleLightCount < visibleLights.reservedSize);
+ visibleLights.indices[visibleLightCount++] = i;
+ }
+ // Light is outside of the frustum and must be faded out
+ else if (distance < lightBSpheres[i].w + lightBSpheres[i].w)
+ {
+ //off screen lights whose distance from frustum is less than light radius
+ DebugAssert(offScreenLightCount < offScreenLights.reservedSize);
+
+ offScreenLights.indices[offScreenLightCount] = i;
+
+ distance -= lightBSpheres[i].w;
+
+ distance = distance / lightBSpheres[i].w;
+ visibilityFades[offScreenLightCount++] = 1.0F - distance;
+ DebugAssert(distance > 0.0f);
+ DebugAssert(distance < 1.0f);
+ }
+ }
+ visibleLights.size = visibleLightCount;
+ offScreenLights.size = offScreenLightCount;
+}
+
+static bool IsValidRenderingLight (const Light& light, LightType lightType, UInt32 cullingMask)
+{
+ const Light::Lightmapping lightmappingMode = light.GetLightmappingForRender();
+ // If light is lightmap only - just skip it
+ if (lightmappingMode == Light::kLightmappingBakedOnly)
+ return false;
+
+ // If light not visible in camera's culling mask - just skip it
+ if ((light.GetCullingMask() & cullingMask) == 0)
+ return false;
+
+ // Light with zero intensity - just skip it
+ if (light.GetIntensity() < 0.01f)
+ return false;
+
+ // Check if light has valid properties
+ return light.IsValidToRender();
+}
+
+static void SetupActiveDirectionalLight (const Light& light, ActiveLight& outLight)
+{
+ const Light::Lightmapping lightmappingMode = light.GetLightmappingForRender();
+
+ outLight.light = const_cast<Light*> (&light);
+#if ENABLE_SHADOWS
+ outLight.insideShadowRange = true;
+#endif
+ outLight.boundingBox = AABB(Vector3f::zero, Vector3f::infinityVec);
+
+ outLight.lightmappingForRender = lightmappingMode;
+ outLight.isVisibleInPrepass = true;
+ outLight.screenRect = Rectf(0,0,1,1);
+ outLight.cullingMask = light.GetCullingMask();
+ outLight.hasCookie = light.GetCookie() ? true : false;
+ outLight.lightRenderMode = light.GetRenderMode();
+ outLight.lightType = light.GetType();
+ outLight.isOffscreenVertexLight = false;
+ outLight.visibilityFade = 1.0;
+}
+
+static int FindBestMainDirectionalLight (const Light** lights, size_t count)
+{
+ // Find main directional light based on intensity
+ int mainLightIndex = -1;
+ float bestMainLightIntensity = 0.0f;
+
+ for (int i=0;i<count;i++)
+ {
+ const Light& light = *lights[i];
+ float mainLightIntensity = CalculateIntensityForMainLight(light);
+ if (mainLightIntensity > bestMainLightIntensity)
+ {
+ mainLightIndex = i;
+ bestMainLightIntensity = mainLightIntensity;
+ }
+ }
+
+ return mainLightIndex;
+}
+
+static void AddDirectionalLights (const Light** lights, size_t count, ActiveLights& outLights)
+{
+ Assert(outLights.lights.size() == 0);
+
+ // Add main light as the first light!
+ int mainLightIndex = FindBestMainDirectionalLight(lights, count);
+ if (mainLightIndex != -1)
+ {
+ SetupActiveDirectionalLight (*lights[mainLightIndex], outLights.lights.push_back());
+ outLights.hasMainLight = true;
+ }
+ else
+ outLights.hasMainLight = false;
+
+ // Add any other lights
+ for (int i=0;i<count;i++)
+ {
+ if (i == mainLightIndex)
+ continue;
+
+ SetupActiveDirectionalLight (*lights[i], outLights.lights.push_back());
+ }
+ outLights.numDirLights = outLights.lights.size();
+}
+
+static void SetupActiveLocalLight (const LocalLightCullingParameters& params, const ShadowCullData& shadowCullData, const Light& light, const Vector4f& lightBSpheres, const Rectf lightScreenRectangle, bool isVisible, float visibilityFade, ActiveLight& outLight)
+{
+ const Transform& trans = light.GetComponent(Transform);
+ Matrix4x4f lightMatrix = trans.GetLocalToWorldMatrixNoScale();
+ float radius = lightBSpheres.w;
+ Vector3f center = Vector3f(lightBSpheres.x, lightBSpheres.y, lightBSpheres.z);
+ float nearDistanceFudged = shadowCullData.camera->GetNear() * 1.001f;
+ float farDistanceFudged = shadowCullData.camera->GetFar() * 0.999f;
+
+ // Add to spot or point lights
+ outLight.light = const_cast<Light*> (&light);
+ float viewDistance = params.eyePlane.GetDistanceToPoint(center);
+ float closestDistance = std::numeric_limits<float>::infinity();
+ float farthestDistance = -closestDistance;
+
+ outLight.isVisibleInPrepass = isVisible;
+ outLight.screenRect = lightScreenRectangle;
+ outLight.visibilityFade = visibilityFade;
+
+ // If light survived from culling, but is not visible
+ outLight.isOffscreenVertexLight = !isVisible;
+
+ // Baked-only lights are already rejected, so lightmaps are either off or auto
+ const Light::Lightmapping lightmappingMode = light.GetLightmappingForRender();
+ outLight.lightmappingForRender = lightmappingMode;
+
+ // Keep cached copy of culling mask for efficiency
+ outLight.cullingMask = light.GetCullingMask();
+ outLight.hasCookie = light.GetCookie() ? true : false;
+ outLight.lightRenderMode = light.GetRenderMode();
+
+ LightType lightType = light.GetType();
+ outLight.lightType = lightType;
+
+ if (lightType == kLightSpot)
+ {
+ // Find nearest point
+ SpotLightBounds spotBounds;
+ CalculateSpotLightBounds (light.GetRange(), light.GetCotanHalfSpotAngle(), lightMatrix, spotBounds);
+ const Vector3f* points = spotBounds.points;
+
+ for (int i = 0; i < SpotLightBounds::kPointCount; i++)
+ {
+ float dist = params.eyePlane.GetDistanceToPoint(points[i]);
+ closestDistance = std::min (closestDistance, dist);
+ farthestDistance = std::max (farthestDistance, dist);
+ }
+ outLight.intersectsNear = closestDistance <= nearDistanceFudged;
+ outLight.intersectsFar = farthestDistance >= farDistanceFudged;
+
+ // Nearest point is also bounded by light radius (cull by far plane distance)
+ float dist = viewDistance - radius;
+ closestDistance = std::max(closestDistance, dist);
+ if (closestDistance > params.farDistance)
+ {
+ outLight.isVisibleInPrepass = false;
+ outLight.screenRect = Rectf(0,0,0,0);
+ }
+
+ // Compute bounding box
+ MinMaxAABB bounds(points[0], points[0]);
+ for (int i = 1; i < SpotLightBounds::kPointCount; i++)
+ bounds.Encapsulate(points[i]);
+ outLight.boundingBox = AABB(bounds);
+ }
+ else
+ {
+ DebugAssert(lightType == kLightPoint);
+ closestDistance = viewDistance - radius;
+ Vector3f boxSize(radius, radius, radius);
+ outLight.boundingBox = AABB(center, boxSize);
+
+ #if GFX_USE_SPHERE_FOR_POINT_LIGHT
+ // If we're drawing an icosphere or icosahedron, check for the radius of a sphere
+ // circumscribed on an icosahedron, which was circumscribed on a unit sphere.
+ const float proxyMeshSize = 1.27f;
+ #else
+ // If we're drawing a bounding cube, check if the farthest corner would not cross the near plane
+ const float proxyMeshSize = 1.7321f;
+ #endif
+ const float intersectionRadius = radius * proxyMeshSize;
+ outLight.intersectsNear = (viewDistance - intersectionRadius) <= nearDistanceFudged;
+ outLight.intersectsFar = (viewDistance + intersectionRadius) >= farDistanceFudged;
+ }
+
+#if ENABLE_SHADOWS
+ // TODO: tighter shadow culling for spot lights
+ outLight.insideShadowRange = (closestDistance < shadowCullData.shadowDistance) && params.enableShadows;
+ if (outLight.insideShadowRange && shadowCullData.useSphereCulling)
+ {
+ float sumRadii = shadowCullData.shadowCullRadius + radius;
+ if (SqrMagnitude(center - shadowCullData.shadowCullCenter) > Sqr(sumRadii))
+ outLight.insideShadowRange = false;
+ else if (!IsObjectWithinShadowRange(shadowCullData, outLight.boundingBox))
+ outLight.insideShadowRange = false;
+ }
+
+ // If light is auto but behind shadow distance (so dual lightmaps normally) - just skip it
+ if ((lightmappingMode == Light::kLightmappingAuto) && !outLight.insideShadowRange)
+ {
+ outLight.isVisibleInPrepass = false;
+ outLight.screenRect = Rectf(0,0,0,0);
+ }
+#endif
+}
+
+// Function returns true if screen rectangle (outRect) is inside camera viewport
+// Returned rectangle is 0..1 coordinates
+bool CalculateLightScreenBounds (const Matrix4x4f& cameraWorldToClip, const Light& light, const Matrix4x4f& lightMatrix, Rectf& outRect)
+{
+ Assert ( light.GetType() != kLightDirectional );
+
+ // Compute the hull of light's bounds
+ Vector3f lightPos = lightMatrix.GetPosition();
+
+ UInt8 hullFaces;
+ UInt8 hullCounts[6]; // 6 faces
+ Vector3f hullPoints[24]; // this input hull has maximum of 6 faces x 4 points hence 24 vectors
+
+ switch( light.GetType() )
+ {
+ case kLightSpot:
+ // Spot light's hull is the light position and four points on the plane at Range
+ {
+ SpotLightBounds spotBounds;
+ CalculateSpotLightBounds(light.GetRange(), light.GetCotanHalfSpotAngle(), lightMatrix, spotBounds);
+ const Vector3f* points = spotBounds.points;
+
+ hullFaces = 5;
+ hullCounts[0] = 4;
+ hullCounts[1] = hullCounts[2] = hullCounts[3] = hullCounts[4] = 3;
+
+ // far plane
+ hullPoints[0] = points[4]; hullPoints[1] = points[3]; hullPoints[2] = points[2]; hullPoints[3] = points[1];
+
+ // sides
+ hullPoints[ 4] = points[0]; hullPoints[ 5] = points[1]; hullPoints[ 6] = points[2];
+ hullPoints[ 7] = points[0]; hullPoints[ 8] = points[2]; hullPoints[ 9] = points[3];
+ hullPoints[10] = points[0]; hullPoints[11] = points[3]; hullPoints[12] = points[4];
+ hullPoints[13] = points[0]; hullPoints[14] = points[4]; hullPoints[15] = points[1];
+ }
+ break;
+
+ case kLightPoint:
+ // Point light's hull is the cube at position with half-size Range
+ {
+ float r = light.GetRange();
+ Vector3f points[8];
+ points[0].Set( lightPos.x - r, lightPos.y - r, lightPos.z - r );
+ points[1].Set( lightPos.x + r, lightPos.y - r, lightPos.z - r );
+ points[2].Set( lightPos.x + r, lightPos.y + r, lightPos.z - r );
+ points[3].Set( lightPos.x - r, lightPos.y + r, lightPos.z - r );
+ points[4].Set( lightPos.x - r, lightPos.y - r, lightPos.z + r );
+ points[5].Set( lightPos.x + r, lightPos.y - r, lightPos.z + r );
+ points[6].Set( lightPos.x + r, lightPos.y + r, lightPos.z + r );
+ points[7].Set( lightPos.x - r, lightPos.y + r, lightPos.z + r );
+
+ hullFaces = 6;
+ hullCounts[0] = hullCounts[1] = hullCounts[2] = hullCounts[3] = hullCounts[4] = hullCounts[5] = 4;
+
+ hullPoints[ 0] = points[0]; hullPoints[ 1] = points[1]; hullPoints[ 2] = points[2]; hullPoints[ 3] = points[3];
+ hullPoints[ 4] = points[7]; hullPoints[ 5] = points[6]; hullPoints[ 6] = points[5]; hullPoints[ 7] = points[4];
+ hullPoints[ 8] = points[0]; hullPoints[ 9] = points[3]; hullPoints[10] = points[7]; hullPoints[11] = points[4];
+ hullPoints[12] = points[1]; hullPoints[13] = points[5]; hullPoints[14] = points[6]; hullPoints[15] = points[2];
+ hullPoints[16] = points[4]; hullPoints[17] = points[5]; hullPoints[18] = points[1]; hullPoints[19] = points[0];
+ hullPoints[20] = points[6]; hullPoints[21] = points[7]; hullPoints[22] = points[3]; hullPoints[23] = points[2];
+
+ }
+ break;
+
+ default:
+ hullFaces = 0;
+ AssertString( "Unknown light type" );
+ break;
+ }
+
+ // Clip hull by camera's near plane - needed because point behind near plane don't have
+ // proper projection on the screen.
+ Plane nearPlane;
+ ExtractProjectionNearPlane( cameraWorldToClip, &nearPlane );
+ // Push near plane forward a bit, by a small number proportional to plane's distance from
+ // the origin (precision gets worse at larger numbers).
+ nearPlane.d() = nearPlane.d() - Abs(nearPlane.d())*0.0001f;
+ DebugAssertIf(!IsNormalized(nearPlane.GetNormal()));
+
+ MinMaxAABB aabb;
+ CalcHullBounds(hullPoints, hullCounts, hullFaces, nearPlane, cameraWorldToClip, aabb);
+ outRect.Set (
+ (aabb.m_Min.x + 1.0f) * 0.5f,
+ (aabb.m_Min.y + 1.0f) * 0.5f,
+ (aabb.m_Max.x - aabb.m_Min.x) * 0.5f,
+ (aabb.m_Max.y - aabb.m_Min.y) * 0.5f
+ );
+
+ // Is screen rect inside viewport [0,1]
+ return ((aabb.m_Max.x > aabb.m_Min.x) || (aabb.m_Max.y > aabb.m_Min.y)) ? true : false;
+}
+
+void AddActiveLocalLights (const LocalLightCullingParameters& params, const ShadowCullData& shadowCullData, const Vector4f* lightBSpheres, const Light** lights, const IndexList& visibleLocalLights, float* visibilityFades, IndexList& offScreenLocalLights, ActiveLights& outLights)
+{
+ int offScreenLightCount = offScreenLocalLights.size;
+
+ //Add spot lights first, and point lights second
+ int lightTypes[2] = {kLightSpot, kLightPoint};
+ int lightCount[2] = {0, 0};
+ for (int j=0; j<2; j++)
+ for (int i=0;i<visibleLocalLights.size;i++)
+ {
+ int lightIndex = visibleLocalLights[i];
+ const Light &light = *lights[lightIndex];
+ if (light.GetType() == lightTypes[j])
+ {
+ // Calculate local light screen rectangle
+ const Transform& trans = lights[lightIndex]->GetComponent(Transform);
+ Matrix4x4f lightMatrix = trans.GetLocalToWorldMatrixNoScale();
+
+ Rectf lightScreenRect;
+ bool isInside = CalculateLightScreenBounds (shadowCullData.cameraWorldToClip, *lights[lightIndex], lightMatrix, lightScreenRect);
+
+ // Setup visible local light if it is inside camera viewport and has a valid screen rectangle
+ if (isInside && !lightScreenRect.IsEmpty())
+ {
+ SetupActiveLocalLight (params, shadowCullData, *lights[lightIndex], lightBSpheres[lightIndex], lightScreenRect, true, 1.0f, outLights.lights.push_back());
+ lightCount[j]++;
+ }
+ else if (!isInside) // change visible light to off screen light if it is outside camera viewport
+ {
+ visibilityFades[offScreenLightCount] = 1.0f; // don't fade as the local light is close to the frustum
+ offScreenLocalLights[offScreenLightCount++] = lightIndex;
+ }
+ }
+ }
+
+ outLights.numSpotLights = lightCount[0];
+ outLights.numPointLights = lightCount[1];
+
+ //Add off screen spot lights third, and off screen point lights fourth
+ lightCount[0] = lightCount[1] = 0;
+ for (int j=0; j<2; j++)
+ for (int i=0;i<offScreenLightCount;i++)
+ {
+ int lightIndex = offScreenLocalLights[i];
+ const Light &light = *lights[lightIndex];
+ if (light.GetType() == lightTypes[j])
+ {
+ SetupActiveLocalLight (params, shadowCullData, *lights[lightIndex], lightBSpheres[lightIndex], Rectf(0,0,0,0), false, visibilityFades[i], outLights.lights.push_back());
+ lightCount[j]++;
+ }
+ }
+
+ outLights.numOffScreenSpotLights = lightCount[0];
+ outLights.numOffScreenPointLights = lightCount[1];
+}
+
+void FindAndCullActiveLights (const SceneCullingParameters& sceneCullParameters, const ShadowCullData& cullData, ActiveLights& outLights)
+{
+ const List<Light>& allLights = GetLightManager().GetAllLights();
+
+ LocalLightCullingParameters localLightCullParameters;
+ localLightCullParameters.eyePlane.SetNormalAndPosition(cullData.viewDir, cullData.eyePos);
+ localLightCullParameters.farDistance = cullData.camera->GetFar();
+ localLightCullParameters.enableShadows = cullData.shadowDistance > cullData.camera->GetNear();
+ localLightCullParameters.cullingMask = cullData.camera->GetCullingMask();
+
+ size_t lightCount = allLights.size_slow();
+
+ dynamic_array<Vector4f> localLightBSpheres (kMemTempAlloc);
+ dynamic_array<const Light*> localLights (kMemTempAlloc);
+ dynamic_array<const Light*> directionalLights (kMemTempAlloc);
+ dynamic_array<float> offScreenLocalLightvisibilityFades (kMemTempAlloc);
+
+ localLightBSpheres.reserve(lightCount);
+ localLights.reserve(lightCount);
+ directionalLights.reserve(lightCount);
+ offScreenLocalLightvisibilityFades.reserve(lightCount);
+
+ LightManager::Lights::const_iterator it, itEnd = allLights.end();
+ for( it = allLights.begin(); it != itEnd; ++it)
+ {
+ const Light& light = *it;
+ LightType lightType = light.GetType();
+
+ if (!IsValidRenderingLight (light, lightType, localLightCullParameters.cullingMask))
+ continue;
+
+ // Add directional light
+ if (lightType == kLightDirectional)
+ {
+ directionalLights.push_back(&light);
+ }
+ // Setup data necessary for culling point / spot lights
+ else if (lightType == kLightPoint || lightType == kLightSpot)
+ {
+ float radius = light.GetRange();
+
+ if (lightType == kLightSpot)
+ radius *= light.GetInvCosHalfSpotAngle();
+
+ // Set radius to negative by default, use it to skip lights in the next loop
+ Vector3f lightPos = light.GetWorldPosition();
+ localLightBSpheres.push_back ( Vector4f(lightPos.x, lightPos.y, lightPos.z, radius) );
+ localLights.push_back (&light);
+ }
+ else
+ {
+ ErrorStringObject("Unsupported light type", &light);
+ }
+ }
+
+ IndexList visibleLocalLightIndices;
+ InitIndexList(visibleLocalLightIndices, localLightBSpheres.size());
+ IndexList offScreenLocalLightIndices;
+ InitIndexList(offScreenLocalLightIndices, localLightBSpheres.size());
+
+ // 1x and 2x light radius frustum culling for local lights
+ FrustumCullLocalLights (sceneCullParameters, localLightBSpheres.begin(), visibleLocalLightIndices, offScreenLocalLightIndices, offScreenLocalLightvisibilityFades.begin());
+
+ // Occlusion cull 1x local lights
+ OcclusionCullLocalLights (sceneCullParameters, localLightBSpheres.begin(), visibleLocalLightIndices);
+
+ // Reserve memory for lights...
+ outLights.lights.reserve (visibleLocalLightIndices.size + offScreenLocalLightIndices.size + directionalLights.size());
+
+ // Add directional lights to the outLights...
+ AddDirectionalLights (directionalLights.begin(), directionalLights.size(), outLights);
+
+ // Add visible local lights and off screen local lights to the outlights...
+ AddActiveLocalLights (localLightCullParameters, cullData, localLightBSpheres.begin(), localLights.begin(), visibleLocalLightIndices, offScreenLocalLightvisibilityFades.begin(), offScreenLocalLightIndices, outLights);
+
+ DestroyIndexList(visibleLocalLightIndices);
+ DestroyIndexList(offScreenLocalLightIndices);
+}
+
+
+static bool IsLightCulled (const ActiveLight& light, int layerMask, bool lightmappedObject, bool dualLightmapsMode)
+{
+ // Skip light if has lightmap for object
+ if ( (lightmappedObject && light.lightmappingForRender == Light::kLightmappingAuto) && (dualLightmapsMode == false) )
+ return true;
+
+ // Cull by layer mask
+ if ((layerMask & light.cullingMask) == 0)
+ return true;
+
+ return false;
+}
+
+static bool IsDirectionalLightCulled (const ActiveLight& light, int layerMask, bool lightmappedObject, bool dualLightmapsMode)
+{
+ DebugAssert(light.light->GetType() == kLightDirectional);
+
+ return IsLightCulled (light, layerMask, lightmappedObject, dualLightmapsMode);
+}
+
+static bool IsSpotLightCulled (const ActiveLight& light, int layerMask, bool lightmappedObject, bool dualLightmapsMode, const AABB& globalObjectAABB, const AABB& localObjectAABB, const Matrix4x4f& objectTransform)
+{
+ DebugAssert(light.light->GetType() == kLightSpot);
+
+ // Common code
+ if (IsLightCulled (light, layerMask, lightmappedObject, dualLightmapsMode))
+ return true;
+
+ // AABB vs AABB
+ if (!IntersectAABBAABB (globalObjectAABB, light.boundingBox))
+ return true;
+
+ const Light& source = *light.light;
+
+ // Detailed culling: frustum vs local AABB
+ Plane planes[6];
+ Matrix4x4f zscale, objectToLightMatrix, projectionMatrix;
+ zscale.SetScale (Vector3f (1.0F, 1.0F, -1.0F));
+
+ const float minNearDist = 0.0001F;
+ const float minNearFarRatio = 0.00001F;
+ float nearDist = std::max(minNearDist, source.GetRange() * minNearFarRatio);
+ projectionMatrix.SetPerspectiveCotan( source.GetCotanHalfSpotAngle(), nearDist, source.GetRange() );
+
+ // objectToLightMatrix = zscale * GetWorldToLocalMatrix * objectTransform
+ Matrix4x4f temp;
+ MultiplyMatrices4x4 (&zscale, &source.GetWorldToLocalMatrix(), &temp);
+ MultiplyMatrices4x4 (&temp, &objectTransform, &objectToLightMatrix);
+
+ // finalProjMatrix = projectionMatrix * objectToLightMatrix
+ Matrix4x4f finalProjMatrix;
+ MultiplyMatrices4x4 (&projectionMatrix, &objectToLightMatrix, &finalProjMatrix);
+ ExtractProjectionPlanes (finalProjMatrix, planes);
+
+ if (!IntersectAABBFrustumFull (localObjectAABB, planes))
+ return true;
+
+ return false;
+}
+
+static bool IsPointLightCulled (const ActiveLight& light, int layerMask, bool lightmappedObject, bool dualLightmapsMode, const AABB& globalObjectAABB, const AABB& localObjectAABB, const Matrix4x4f& objectTransform, float invScale)
+{
+ DebugAssert(light.light->GetType() == kLightPoint);
+
+ // Common code
+ if (IsLightCulled (light, layerMask, lightmappedObject, dualLightmapsMode))
+ return true;
+
+ // AABB vs AABB
+ if (!IntersectAABBAABB (globalObjectAABB, light.boundingBox))
+ return true;
+
+ const Light& source = *light.light;
+
+ // Detailed culling
+ // Test light sphere transformed into the object's local space
+ // against local aabb of the object
+ Vector3f objectRelativeLightPos = objectTransform.InverseMultiplyPoint3Affine (source.GetWorldPosition()) * invScale;
+ Sphere objectRelLightSphere (objectRelativeLightPos * invScale, source.GetRange () * invScale);
+
+ if (!IntersectAABBSphere (localObjectAABB, objectRelLightSphere))
+ return true;
+
+ return false;
+}
+
+void CullPerObjectLights (const ActiveLights& activeLights, const AABB& globalObjectAABB, const AABB& localObjectAABB, const Matrix4x4f& objectTransform, float invScale, int layerMask, bool lightmappedObject, bool dualLightmapsMode, ObjectLightIndices& outIndices)
+{
+ size_t index = 0;
+ size_t endIndex = activeLights.numDirLights;
+ for ( ; index < endIndex; index++)
+ if (!IsDirectionalLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode))
+ outIndices.push_back (index);
+
+ endIndex += activeLights.numSpotLights;
+ for ( ; index < endIndex; index++)
+ if (!IsSpotLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode, globalObjectAABB, localObjectAABB, objectTransform))
+ outIndices.push_back (index);
+
+ endIndex += activeLights.numPointLights;
+ for ( ; index < endIndex; index++)
+ if (!IsPointLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode, globalObjectAABB, localObjectAABB, objectTransform, invScale))
+ outIndices.push_back (index);
+
+ endIndex += activeLights.numOffScreenSpotLights;
+ for ( ; index < endIndex; index++)
+ if (!IsSpotLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode, globalObjectAABB, localObjectAABB, objectTransform))
+ outIndices.push_back (index);
+
+ endIndex += activeLights.numOffScreenPointLights;
+ for ( ; index < endIndex; index++)
+ if (!IsPointLightCulled (activeLights.lights[index], layerMask, lightmappedObject, dualLightmapsMode, globalObjectAABB, localObjectAABB, objectTransform, invScale))
+ outIndices.push_back (index);
+} \ No newline at end of file
diff --git a/Runtime/Camera/LightCulling.h b/Runtime/Camera/LightCulling.h
new file mode 100644
index 0000000..a55de69
--- /dev/null
+++ b/Runtime/Camera/LightCulling.h
@@ -0,0 +1,7 @@
+#pragma once
+
+// Figures out all lights that are visible in the scene
+void FindAndCullActiveLights (const SceneCullingParameters& sceneCullParameters, const ShadowCullData& cullData, ActiveLights& outLights);
+
+// Figures out lights for given object, no sorting.
+void CullPerObjectLights (const ActiveLights& activeLights, const AABB& globalObjectAABB, const AABB& localObjectAABB, const Matrix4x4f& objectTransform, float invScale, int layerMask, bool lightmappedObject, bool dualLightmapsMode, ObjectLightIndices& outIndices); \ No newline at end of file
diff --git a/Runtime/Camera/LightManager.cpp b/Runtime/Camera/LightManager.cpp
new file mode 100644
index 0000000..eaf5d89
--- /dev/null
+++ b/Runtime/Camera/LightManager.cpp
@@ -0,0 +1,635 @@
+#include "UnityPrefix.h"
+#include "LightManager.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Light.h"
+#include "UnityScene.h"
+#include "CullResults.h"
+#include "BaseRenderer.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Math/SphericalHarmonics.h"
+#include "Runtime/Shaders/ShaderKeywords.h"
+#include "Runtime/Camera/LightProbes.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Misc/BuildSettings.h"
+
+static ShaderKeyword kKeywordVertexLight = keywords::Create("VERTEXLIGHT_ON");
+
+static LightManager* s_LightManager = NULL;
+
+LightManager& GetLightManager()
+{
+ DebugAssert (s_LightManager != NULL);
+ return *s_LightManager;
+}
+
+void LightManager::InitializeClass()
+{
+ DebugAssert (s_LightManager == NULL);
+ s_LightManager = new LightManager();
+}
+
+void LightManager::CleanupClass()
+{
+ DebugAssert (s_LightManager != NULL);
+ delete s_LightManager;
+ s_LightManager = NULL;
+}
+
+
+
+
+static float CalculateLightIntensityAtPoint (const Light& light, const Vector3f& position)
+{
+ float lum = light.GetColor().GreyScaleValue() * light.GetIntensity();
+
+ if (light.GetType () == kLightDirectional)
+ {
+ if (light.GetShadows() != kShadowNone)
+ return lum * 16.0f;
+ else
+ return lum;
+ }
+ else
+ {
+ float sqrDistance = SqrMagnitude (position - light.GetWorldPosition());
+ float atten = light.AttenuateApprox (sqrDistance);
+ return lum * atten;
+ }
+}
+
+
+static ColorRGBAf CalculateLightColorAtPointForSH (const Light& light, const AABB& bounds, const AABB& localBounds, float scale)
+{
+ float lum = light.GetIntensity();
+
+ if (light.GetType () == kLightDirectional)
+ {
+ return lum * GammaToActiveColorSpace (light.GetColor());
+ }
+ else
+ {
+ // objects larger than lights should be affected less
+ float objectSizeSqr = SqrMagnitude(localBounds.GetExtent() * scale);
+ float lightSizeSqr = light.GetRange() * light.GetRange();
+ if (objectSizeSqr > lightSizeSqr)
+ {
+ lum *= lightSizeSqr / objectSizeSqr;
+ }
+
+ Vector3f pos = bounds.GetCenter();
+ // don't ever treat SH lights as coming closer to center than at object bounds
+ float sqrDistance = std::max (SqrMagnitude (pos - light.GetWorldPosition()), objectSizeSqr);
+ float atten = light.AttenuateApprox (sqrDistance);
+ return (lum * atten) * GammaToActiveColorSpace (light.GetColor());
+ }
+}
+
+
+LightManager::LightManager ()
+: m_LastMainLight(NULL)
+{
+}
+
+
+LightManager::~LightManager () {
+}
+
+
+
+
+void LightManager::SetupVertexLights (int lightCount, const ActiveLight* const* lights)
+{
+ GfxDevice& device = GetGfxDevice();
+
+ int maxVertexLights = gGraphicsCaps.maxLights;
+ AssertIf(lightCount > maxVertexLights);
+ lightCount = std::min(lightCount, maxVertexLights); // just a safeguard, should not normally happen
+
+ // setup vertex lights
+ for( int i = 0; i < lightCount; ++i )
+ lights[i]->light->SetupVertexLight (i, lights[i]->visibilityFade);
+
+ // disable rest of lights
+ device.DisableLights( lightCount );
+}
+
+void SetSHConstants (const float sh[9][3], BuiltinShaderParamValues& params)
+{
+ Vector4f vCoeff[3];
+
+ static const float s_fSqrtPI = ((float)sqrtf(kPI));
+ const float fC0 = 1.0f/(2.0f*s_fSqrtPI);
+ const float fC1 = (float)sqrt(3.0f)/(3.0f*s_fSqrtPI);
+ const float fC2 = (float)sqrt(15.0f)/(8.0f*s_fSqrtPI);
+ const float fC3 = (float)sqrt(5.0f)/(16.0f*s_fSqrtPI);
+ const float fC4 = 0.5f*fC2;
+
+ int iC;
+ for( iC=0; iC<3; iC++ )
+ {
+ vCoeff[iC].x = -fC1*sh[3][iC];
+ vCoeff[iC].y = -fC1*sh[1][iC];
+ vCoeff[iC].z = fC1*sh[2][iC];
+ vCoeff[iC].w = fC0*sh[0][iC] - fC3*sh[6][iC];
+ }
+
+ params.SetVectorParam(kShaderVecSHAr, vCoeff[0]);
+ params.SetVectorParam(kShaderVecSHAg, vCoeff[1]);
+ params.SetVectorParam(kShaderVecSHAb, vCoeff[2]);
+
+ for( iC=0; iC<3; iC++ )
+ {
+ vCoeff[iC].x = fC2*sh[4][iC];
+ vCoeff[iC].y = -fC2*sh[5][iC];
+ vCoeff[iC].z = 3.0f*fC3*sh[6][iC];
+ vCoeff[iC].w = -fC2*sh[7][iC];
+ }
+
+ params.SetVectorParam(kShaderVecSHBr, vCoeff[0]);
+ params.SetVectorParam(kShaderVecSHBg, vCoeff[1]);
+ params.SetVectorParam(kShaderVecSHBb, vCoeff[2]);
+
+ vCoeff[0].x = fC4*sh[8][0];
+ vCoeff[0].y = fC4*sh[8][1];
+ vCoeff[0].z = fC4*sh[8][2];
+ vCoeff[0].w = 1.0f;
+
+ params.SetVectorParam(kShaderVecSHC, vCoeff[0]);
+}
+
+void LightManager::SetupForwardBaseLights (const ForwardLightsBlock& lights)
+{
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+
+ // Setup SH constants
+ SetSHConstants (lights.sh, params);
+
+ // Setup per-vertex light constants
+ Assert (lights.vertexLightCount <= 4);
+ Vector4f packedPosX;
+ Vector4f packedPosY;
+ Vector4f packedPosZ;
+ Vector4f packedAtten;
+ Vector4f lightColors[4];
+
+ const ActiveLight* const* vertexLights = lights.GetLights() + lights.addLightCount;
+ for (int i = 0; i < lights.vertexLightCount; ++i)
+ {
+ Light& light = *vertexLights[i]->light;
+ Vector3f pos = light.GetWorldPosition();
+ float intensity = light.GetIntensity() * 2.0f;
+ if (i == 0 && lights.lastAddLightBlend != 1.0f)
+ intensity *= (1.0f-lights.lastAddLightBlend);
+ else if (i == lights.vertexLightCount-1)
+ intensity *= lights.lastVertexLightBlend;
+ ColorRGBAf color = GammaToActiveColorSpace (light.GetColor()) * intensity;
+ float attenSq = Light::CalcQuadFac (light.GetRange());
+
+ packedPosX.GetPtr()[i] = pos.x;
+ packedPosY.GetPtr()[i] = pos.y;
+ packedPosZ.GetPtr()[i] = pos.z;
+ packedAtten.GetPtr()[i] = attenSq;
+ lightColors[i].Set (color.r, color.g, color.b, color.a);
+ }
+ for (int i = lights.vertexLightCount; i < kMaxForwardVertexLights; ++i)
+ {
+ packedPosX.GetPtr()[i] = 0;
+ packedPosY.GetPtr()[i] = 0;
+ packedPosZ.GetPtr()[i] = 0;
+ packedAtten.GetPtr()[i] = 1;
+ lightColors[i].Set (0,0,0,0);
+ }
+ if (lights.vertexLightCount)
+ {
+ params.SetVectorParam(kShaderVecVertexLightPosX0, packedPosX);
+ params.SetVectorParam(kShaderVecVertexLightPosY0, packedPosY);
+ params.SetVectorParam(kShaderVecVertexLightPosZ0, packedPosZ);
+ params.SetVectorParam(kShaderVecVertexLightAtten0, packedAtten);
+ params.SetVectorParam(kShaderVecLight0Diffuse, lightColors[0]);
+ params.SetVectorParam(kShaderVecLight1Diffuse, lightColors[1]);
+ params.SetVectorParam(kShaderVecLight2Diffuse, lightColors[2]);
+ params.SetVectorParam(kShaderVecLight3Diffuse, lightColors[3]);
+
+ g_ShaderKeywords.Enable (kKeywordVertexLight);
+ }
+ else
+ {
+ g_ShaderKeywords.Disable (kKeywordVertexLight);
+ }
+
+
+ // Setup per-pixel main light constants
+ if (lights.mainLight == NULL)
+ {
+ params.SetVectorParam(kShaderVecLightColor0, Vector4f(0,0,0,0));
+ return;
+ }
+
+ Light* light = lights.mainLight->light;
+ Assert (light->GetType() == kLightDirectional);
+
+ const Transform& transform = light->GetComponent(Transform);
+
+ Vector3f lightDir = transform.TransformDirection (Vector3f(0,0,-1));
+ params.SetVectorParam(kShaderVecWorldSpaceLightPos0, Vector4f(lightDir.x, lightDir.y, lightDir.z, 0.0f));
+
+ Matrix4x4f world2Light = light->GetWorldToLocalMatrix();
+ light->GetMatrix (&world2Light, &params.GetWritableMatrixParam(kShaderMatLightMatrix));
+
+ light->SetLightKeyword ();
+ light->SetPropsToShaderLab (1.0f);
+}
+
+void LightManager::SetupForwardAddLight (Light* light, float blend)
+{
+ Assert (light);
+
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+ Vector4f lightInfo;
+ if (light->GetType() != kLightDirectional)
+ {
+ Vector3f worldPos = light->GetWorldPosition();
+ lightInfo.Set (worldPos.x, worldPos.y, worldPos.z, 1.0f);
+ }
+ else
+ {
+ const Transform& transform = light->GetComponent(Transform);
+ Vector3f worldDir = transform.TransformDirection (Vector3f(0,0,-1));
+ lightInfo.Set (worldDir.x, worldDir.y, worldDir.z, 0.0f);
+ }
+ params.SetVectorParam(kShaderVecWorldSpaceLightPos0, lightInfo);
+
+ Matrix4x4f world2Light = light->GetWorldToLocalMatrix();
+ light->GetMatrix (&world2Light, &params.GetWritableMatrixParam(kShaderMatLightMatrix));
+
+ light->SetLightKeyword ();
+ light->SetPropsToShaderLab (blend);
+}
+
+
+
+void LightManager::AddLight (Light* source)
+{
+ DebugAssert (source);
+
+ m_Lights.push_back(*source);
+}
+
+void LightManager::RemoveLight (Light* source)
+{
+ DebugAssert (source && source->IsInList());
+
+ m_Lights.erase(source);
+
+ // If this was the last main light, clear it
+ if (m_LastMainLight == source)
+ m_LastMainLight = NULL;
+}
+
+static float kRenderModeSortBias[Light::kRenderModeCount] = {
+ 0.0f,
+ 1000.0f,
+ -1000.0f,
+};
+
+
+
+
+// Figures out lights for given object, sorts them by intensity.
+// Fills them into an array of CulledLight.
+// Returns number of lights filled.
+
+struct CulledLight
+{
+ UInt32 lightIndex;
+ float sortIntensity;
+ friend bool operator< (const CulledLight& lhs, const CulledLight& rhs)
+ {
+ return lhs.sortIntensity > rhs.sortIntensity;
+ }
+};
+
+static void SortLights (dynamic_array<CulledLight>& outLights, const UInt32* lightIndices, UInt32 lightCount, const ActiveLights& activeLights, const Vector3f& objectCenter)
+{
+ outLights.resize_uninitialized(lightCount);
+ for (size_t i = 0; i < lightCount; i++)
+ {
+ // Light passed culling, add it
+ CulledLight& outLight = outLights[i];
+ outLight.lightIndex = lightIndices[i];
+ const Light& light = *activeLights.lights[outLight.lightIndex].light;
+ int lightRenderMode = light.GetRenderMode ();
+ float blend = CalculateLightIntensityAtPoint (light, objectCenter);
+ outLight.sortIntensity = blend + kRenderModeSortBias[lightRenderMode];
+ }
+
+ // Sort culled lights by intensity (most intensive lights first)
+ std::sort( outLights.begin(), outLights.end() );
+}
+
+UNITY_VECTOR(kMemRenderer, Light*) LightManager::GetLights(LightType type, int layer)
+{
+ UNITY_VECTOR(kMemRenderer, Light*) lights;
+ layer = 1 << layer;
+ for (LightManager::Lights::iterator i= m_Lights.begin();i != m_Lights.end();i++)
+ {
+ Light* light = &*i;
+ if (!light)
+ continue;
+ if (light->GetType() == type && (light->GetCullingMask() & layer) != 0)
+ lights.push_back(light);
+ }
+
+ return lights;
+}
+
+void LightManager::FindVertexLightsForObject (dynamic_array<UInt8>& dest, const UInt32* lightIndices, UInt32 lightCount, const ActiveLights& activeLights, const VisibleNode& node)
+{
+ DebugAssert (node.renderer);
+
+ dynamic_array<CulledLight> culledLights(kMemTempAlloc);
+ SortLights (culledLights, lightIndices, lightCount, activeLights, node.worldAABB.GetCenter ());
+
+ int resultLightCount = std::min<int> (lightCount, std::min<int>(gGraphicsCaps.maxLights,kMaxSupportedVertexLights));
+
+ // allocate block to hold light count & light pointers
+ size_t resultOffset = dest.size();
+ size_t requiredSize = sizeof(VertexLightsBlock) + resultLightCount * sizeof(Light*);
+ dest.resize_uninitialized(resultOffset + requiredSize);
+ VertexLightsBlock* outBlock = reinterpret_cast<VertexLightsBlock*>(&dest[resultOffset]);
+ const ActiveLight** outLights = reinterpret_cast<const ActiveLight**>(outBlock + 1);
+ outBlock->lightCount = resultLightCount;
+ for (int i = 0; i < resultLightCount; ++i)
+ outLights[i] = &activeLights.lights[culledLights[i].lightIndex];
+}
+
+
+
+static void AddLightToSH (const VisibleNode& node, const Light& source, ForwardLightsBlock* lights, float blend)
+{
+ Vector3f lightDir;
+ if (source.GetType() != kLightDirectional)
+ {
+ lightDir = NormalizeSafe(source.GetWorldPosition() - node.worldAABB.GetCenter());
+ }
+ else
+ {
+ const Transform& lightTransform = source.GetComponent (Transform);
+ lightDir = lightTransform.TransformDirection (Vector3f(0,0,-1));
+ }
+
+ ColorRGBAf color = CalculateLightColorAtPointForSH (source, node.worldAABB, node.localAABB, 1.0f/node.invScale) * (blend * 2.0f);
+
+ float shR[9], shG[9], shB[9];
+ SHEvalDirectionalLight9 (
+ lightDir.x, lightDir.y, lightDir.z,
+ color.r, color.g, color.b,
+ shR, shG, shB);
+ for (int i = 0; i < 9; ++i) {
+ lights->sh[i][0] += shR[i];
+ lights->sh[i][1] += shG[i];
+ lights->sh[i][2] += shB[i];
+ }
+}
+
+
+// Light we want to blend is L1 (with L0 the brighter one before it, and L2 the dimmer one after it).
+// Blend is: (L1-L2)/(L0-L2)
+static inline bool CalculateLightBlend (const CulledLight* lights, int lightsCount, int index, float* outBlend)
+{
+ if (index <= 0 || index >= lightsCount-1)
+ return false;
+
+ float l0 = lights[index-1].sortIntensity;
+ float l1 = lights[index ].sortIntensity;
+ float l2 = lights[index+1].sortIntensity;
+ if (l0 - l2 >= kRenderModeSortBias[Light::kRenderImportant])
+ return false;
+
+ *outBlend = clamp01 ((l1 - l2) / (l0 - l2 + 0.001f));
+ return true;
+}
+
+
+static void CrossBlendForwardLights (
+ CulledLight* culledLights,
+ const ActiveLights& activeLights,
+ int culledLightsCount,
+ int lastAutoAddLightIndex,
+ bool lightmappedObject,
+ dynamic_array<UInt8>& dest,
+ size_t resultOffset,
+ const VisibleNode& node
+ )
+{
+ ForwardLightsBlock* lights = reinterpret_cast<ForwardLightsBlock*>(&dest[resultOffset]);
+ int lastVertexLightIndex = lights->addLightCount + lights->vertexLightCount-1;
+
+ // How much we need to blend the last additive light?
+ lights->lastAddLightBlend = 1.0f;
+ bool blendLastAddLight = CalculateLightBlend (culledLights, culledLightsCount, lastAutoAddLightIndex, &lights->lastAddLightBlend);
+ if (blendLastAddLight && !lightmappedObject)
+ {
+ // For non-lightmapped objects, we need to add oppositely blended
+ // vertex or SH light.
+ UInt32 blendIndex = culledLights[lastAutoAddLightIndex].lightIndex;
+ const Light& blendLight = *activeLights.lights[blendIndex].light;
+ if (blendLight.GetType() != kLightDirectional)
+ {
+ // Non-directional light: insert one vertex light
+ dest.resize_uninitialized(dest.size() + sizeof(Light*));
+ lights = reinterpret_cast<ForwardLightsBlock*>(&dest[resultOffset]); // could result in reallocation; recalculate block pointer
+ const ActiveLight** lightPtrs = reinterpret_cast<const ActiveLight**>(lights + 1); // ActiveLight array begins at end of struct
+ // move all vertex lights
+ for (int i = lights->addLightCount+lights->vertexLightCount-1; i >= lights->addLightCount-1; --i)
+ lightPtrs[i+1] = lightPtrs[i];
+ Assert (lights->vertexLightCount <= kMaxForwardVertexLights);
+ ++lights->vertexLightCount;
+ if (lights->vertexLightCount > kMaxForwardVertexLights)
+ {
+ --lastVertexLightIndex;
+ lights->vertexLightCount = kMaxForwardVertexLights;
+ }
+ }
+ else
+ {
+ // Directional light: add to SH
+ AddLightToSH (node, blendLight, lights, 1.0f-lights->lastAddLightBlend);
+ }
+ }
+
+ // If we have vertex lights, we might want to blend last one
+ if (lights->vertexLightCount > 0)
+ {
+ float vertexBlend;
+ lights->lastVertexLightBlend = 1.0f;
+ bool blendLastVertexLight = CalculateLightBlend (culledLights, culledLightsCount, lastVertexLightIndex, &vertexBlend);
+ if (blendLastVertexLight)
+ lights->lastVertexLightBlend = vertexBlend;
+ }
+}
+
+static size_t CountNonOffscreenVertexLights (const UInt32* lightIndices, UInt32 lightCount, const ActiveLights& activeLights)
+{
+ size_t visibleLightCount = 0;
+ for (visibleLightCount=0;visibleLightCount<lightCount;visibleLightCount++)
+ {
+ if (activeLights.lights[lightIndices[visibleLightCount]].isOffscreenVertexLight)
+ break;
+ }
+
+ for (int j=visibleLightCount;j<lightCount;j++)
+ {
+ DebugAssert(activeLights.lights[lightIndices[j]].isOffscreenVertexLight);
+ }
+
+
+ return visibleLightCount;
+}
+
+void LightManager::FindForwardLightsForObject (dynamic_array<UInt8>& dest, const UInt32* lightIndices, UInt32 lightCount, const ActiveLights& activeLights, const VisibleNode& node, bool lightmappedObject, bool dualLightmapsMode, bool useVertexLights, int maxAutoAddLights, bool disableAddLights, const ColorRGBAf& ambient)
+{
+ DebugAssert (node.renderer);
+
+ // cull and sort lights into temporary memory block
+ Renderer* renderer = static_cast<Renderer*>(node.renderer);
+ UInt32 layerMask = renderer->GetLayerMask();
+ const bool isUsingLightProbes = node.renderer->GetRendererType() != kRendererIntermediate && renderer->GetUseLightProbes() && LightProbes::AreBaked();
+ const bool directLightFromLightProbes = isUsingLightProbes && !dualLightmapsMode;
+
+ dynamic_array<CulledLight> culledLights(kMemTempAlloc);
+
+ // If we don't support vertex lights we can skip rendering any offscreen lights completely (offscreen lights always come after visible lights in the index list)
+ if (!useVertexLights)
+ lightCount = CountNonOffscreenVertexLights (lightIndices, lightCount, activeLights);
+
+
+ SortLights (culledLights, lightIndices, lightCount, activeLights, node.worldAABB.GetCenter ());
+
+ // put ForwardLightsBlock header structure into buffer
+ size_t resultOffset = dest.size();
+ size_t arrayOffset = resultOffset + sizeof(ForwardLightsBlock);
+ dest.resize_uninitialized(arrayOffset);
+ ForwardLightsBlock* lights = reinterpret_cast<ForwardLightsBlock*>(&dest[resultOffset]);
+ lights->addLightCount = 0;
+ lights->vertexLightCount = 0;
+ lights->mainLight = NULL;
+ lights->lastAddLightBlend = 1.0f;
+ lights->lastVertexLightBlend = 1.0f;
+
+ if (useVertexLights)
+ {
+ // if we want vertex lights as result, just take N brightest ones
+ int resultLightCount = std::min<int> (lightCount, std::min<int>(gGraphicsCaps.maxLights,kMaxSupportedVertexLights));
+
+ // set SH to zero (not really used for rendering, but having garbage there would break batches)
+ memset (lights->sh, 0, sizeof(lights->sh));
+
+ // allocate block to hold light pointers
+ dest.resize_uninitialized(arrayOffset + sizeof(ActiveLight*) * resultLightCount);
+ // could result in reallocation; recalculate block pointer
+ lights = reinterpret_cast<ForwardLightsBlock*>(&dest[resultOffset]);
+ const ActiveLight** lightPtrs = reinterpret_cast<const ActiveLight**>(lights + 1);
+ lights->vertexLightCount = resultLightCount;
+ for (int i = 0; i < resultLightCount; ++i)
+ lightPtrs[i] = &activeLights.lights[culledLights[i].lightIndex];
+ }
+ else
+ {
+ // we want main light + SH + additive lights as result
+
+ // Main light if any will be first one in activeLights
+ const ActiveLight* mainLight = NULL;
+ if (activeLights.hasMainLight )
+ mainLight = &activeLights.lights[0];
+
+ // Take globally main light if that fits our layer mask, it does not have a cookie, and it's not an auto light while we're using probes
+ if (mainLight && (mainLight->cullingMask & layerMask) != 0 && !mainLight->hasCookie &&
+ !(directLightFromLightProbes && mainLight->lightmappingForRender == Light::kLightmappingAuto))
+ lights->mainLight = mainLight;
+
+ // put ambient into SH
+ SHEvalAmbientLight(ambient, &lights->sh[0][0]);
+ for (int i = 1; i < 9; ++i) {
+ lights->sh[i][0] = 0.0f;
+ lights->sh[i][1] = 0.0f;
+ lights->sh[i][2] = 0.0f;
+ }
+
+ // go over result lights and place them
+ int lastAutoAddLightIndex = -1;
+ for (int i = 0; i < lightCount; ++i)
+ {
+ UInt32 lightIndex = culledLights[i].lightIndex;
+ const ActiveLight& activeLight = activeLights.lights[lightIndex];
+ const int lightRenderMode = activeLight.lightRenderMode;
+ const int lightmappingMode = activeLight.lightmappingForRender;
+
+ Assert(!activeLight.isOffscreenVertexLight);
+
+ // BasePass for lightmapped objects has no code for run-time lighting
+ // therefore we have to promote RuntimeOnly directional lights to Additive pass
+ // (This bug was fixed in 3.5. Thus we keep behaviour in the webplayer)
+ bool forceAdditive = lightmappedObject && lightmappingMode == Light::kLightmappingRealtimeOnly;
+ forceAdditive &= IS_CONTENT_NEWER_OR_SAME(kUnityVersion3_5_a1);
+
+ // If this is already set as main light: skip it
+ if (lights->mainLight && lightIndex == 0 && !forceAdditive)
+ {
+ // nothing
+ }
+ // If we don't have main light yet and this could be it: do it
+ else if (lights->mainLight == NULL && activeLight.lightType == kLightDirectional && lightRenderMode != Light::kRenderNotImportant && !activeLight.hasCookie && !forceAdditive)
+ {
+ lights->mainLight = &activeLight;
+ }
+ // If it's an important light, add it to additive lights
+ else if (((lightRenderMode == Light::kRenderImportant) || (lightRenderMode!=Light::kRenderNotImportant && lights->addLightCount < maxAutoAddLights)) && !disableAddLights)
+ {
+ // now that we know it will not be rendered as a vertex light, we can check if it's actually visible;
+ // can't do that for vertex lights, as they influence object past the range
+ size_t curOffset = dest.size();
+ dest.resize_uninitialized(curOffset + sizeof(ActiveLight*));
+ *reinterpret_cast<const ActiveLight**>(&dest[curOffset]) = &activeLight;
+ // could result in reallocation; recalculate block pointer
+ lights = reinterpret_cast<ForwardLightsBlock*>(&dest[resultOffset]);
+ ++lights->addLightCount;
+ if (lights->addLightCount == maxAutoAddLights && lightRenderMode != Light::kRenderImportant)
+ lastAutoAddLightIndex = i;
+ }
+ // All Vertex/SH lights for non-lightmapped objects
+ else if (!lightmappedObject)
+ {
+ // Add some non-directional lights as vertex lights
+ if (activeLight.lightType != kLightDirectional && lights->vertexLightCount < kMaxForwardVertexLights)
+ {
+ size_t curOffset = dest.size();
+ dest.resize_uninitialized(curOffset + sizeof(Light*));
+ *reinterpret_cast<const ActiveLight**>(&dest[curOffset]) = &activeLight;
+ // could result in reallocation; recalculate block pointer
+ lights = reinterpret_cast<ForwardLightsBlock*>(&dest[resultOffset]);
+ ++lights->vertexLightCount;
+ }
+ // Otherwise, add light to SH
+ else
+ {
+ AddLightToSH (node, *activeLight.light, lights, 1.0f);
+ }
+ }
+ }
+
+ // Blend light transitions: full to vertex lit; and vertex lit to SH.
+ CrossBlendForwardLights (culledLights.data(), activeLights, lightCount, lastAutoAddLightIndex, lightmappedObject, dest, resultOffset, node);
+
+ if (isUsingLightProbes)
+ {
+ float coefficients[kLightProbeCoefficientCount];
+ GetLightProbes()->GetInterpolatedLightProbe(renderer->GetLightProbeInterpolationPosition(node.worldAABB), renderer, &coefficients[0]);
+ for (int i = 0; i < kLightProbeCoefficientCount; i++)
+ {
+ lights->sh[0][i] += coefficients[i];
+ }
+ }
+ }
+}
diff --git a/Runtime/Camera/LightManager.h b/Runtime/Camera/LightManager.h
new file mode 100644
index 0000000..1a154f8
--- /dev/null
+++ b/Runtime/Camera/LightManager.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include "Runtime/Geometry/AABB.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Runtime/Camera/CullResults.h"
+#include "Runtime/Camera/LightTypes.h"
+
+class Light;
+struct ShadowCullData;
+class ColorRGBAf;
+class BuiltinShaderParamValues;
+namespace Umbra { class OcclusionBuffer; }
+
+const int kMaxForwardVertexLights = 4;
+
+// We reserve light buffers for this number of lights per object (in forward/vertex lit)
+const int kEstimatedLightsPerObject = 3;
+
+
+class LightManager
+{
+public:
+ LightManager ();
+ ~LightManager ();
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ // Figures out and sorts lights for the object
+ // Puts results in dest as VertexLightsBlock + variable number of lights
+ void FindVertexLightsForObject (dynamic_array<UInt8>& dest, const UInt32* lightIndices, UInt32 lightCount, const ActiveLights& activeLights, const VisibleNode& node);
+
+ // Figures out and sorts lights for the object
+ // Puts results in dest as ForwardLightsBlock + variable number of lights
+ void FindForwardLightsForObject (dynamic_array<UInt8>& dest, const UInt32* lightIndices, UInt32 lightCount, const ActiveLights& activeLights, const VisibleNode& node, bool lightmappedObject, bool dualLightmapsMode, bool useVertexLights, int maxAutoAddLights, bool disableAddLights, const ColorRGBAf& ambient);
+
+ static void SetupVertexLights (int lightCount, const ActiveLight* const* lights);
+
+ static void SetupForwardBaseLights (const ForwardLightsBlock& lights);
+ static void SetupForwardAddLight (Light* light, float blend);
+
+ void AddLight (Light* source);
+ void RemoveLight (Light* source);
+
+ typedef List<Light> Lights;
+ Lights& GetAllLights () { return m_Lights; }
+ const Lights& GetAllLights () const { return m_Lights; }
+
+ ///Terrain engine only
+ UNITY_VECTOR(kMemRenderer, Light*) GetLights (LightType type, int layer);
+
+ Light* GetLastMainLight() { return m_LastMainLight; }
+
+
+private:
+ Lights m_Lights;
+ Light* m_LastMainLight; // brightest directional light found in last render
+};
+
+void SetSHConstants (const float sh[9][3], BuiltinShaderParamValues& params);
+LightManager& GetLightManager();
diff --git a/Runtime/Camera/LightProbes.cpp b/Runtime/Camera/LightProbes.cpp
new file mode 100644
index 0000000..43f338c
--- /dev/null
+++ b/Runtime/Camera/LightProbes.cpp
@@ -0,0 +1,350 @@
+#include "UnityPrefix.h"
+#include "LightProbes.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Math/Polynomials.h"
+#if UNITY_EDITOR
+#include "Editor/Src/LightmapperLightProbes.h"
+#endif
+
+template<class T>
+void LightProbeCoefficients::Transfer (T& transfer)
+{
+ TRANSFER(sh[0]);
+ TRANSFER(sh[1]);
+ TRANSFER(sh[2]);
+ TRANSFER(sh[3]);
+ TRANSFER(sh[4]);
+ TRANSFER(sh[5]);
+ TRANSFER(sh[6]);
+ TRANSFER(sh[7]);
+ TRANSFER(sh[8]);
+ TRANSFER(sh[9]);
+ TRANSFER(sh[10]);
+ TRANSFER(sh[11]);
+ TRANSFER(sh[12]);
+ TRANSFER(sh[13]);
+ TRANSFER(sh[14]);
+ TRANSFER(sh[15]);
+ TRANSFER(sh[16]);
+ TRANSFER(sh[17]);
+ TRANSFER(sh[18]);
+ TRANSFER(sh[19]);
+ TRANSFER(sh[20]);
+ TRANSFER(sh[21]);
+ TRANSFER(sh[22]);
+ TRANSFER(sh[23]);
+ TRANSFER(sh[24]);
+ TRANSFER(sh[25]);
+ TRANSFER(sh[26]);
+}
+
+template<class T>
+void Tetrahedron::Transfer (T& transfer)
+{
+ TRANSFER(indices[0]);
+ TRANSFER(indices[1]);
+ TRANSFER(indices[2]);
+ TRANSFER(indices[3]);
+ TRANSFER(neighbors[0]);
+ TRANSFER(neighbors[1]);
+ TRANSFER(neighbors[2]);
+ TRANSFER(neighbors[3]);
+ TRANSFER(matrix);
+}
+
+template<class T>
+void LightProbes::Transfer (T& transfer)
+{
+ Super::Transfer(transfer);
+ transfer.Transfer(m_Data.positions, "bakedPositions", kHideInEditorMask);
+ transfer.Transfer(m_Data.coefficients, "bakedCoefficients");
+ transfer.Transfer(m_Data.tetrahedra, "tetrahedra", kHideInEditorMask);
+ transfer.Transfer(m_Data.hullRays, "hullRays", kHideInEditorMask);
+}
+
+LightProbes::LightProbes(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+}
+
+LightProbes::~LightProbes ()
+{
+}
+
+void LightProbes::AwakeFromLoad(AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad(awakeMode);
+}
+
+#if UNITY_EDITOR
+void LightProbes::SetBakedData( const dynamic_array<Vector3f>& positions, const dynamic_array<LightProbeCoefficients>& coefficents )
+{
+ m_Data.coefficients = coefficents;
+ m_Data.positions = positions;
+ LightProbeUtils::Tetrahedralize(m_Data);
+ SetDirty();
+}
+#endif
+
+LightProbes* GetLightProbes ()
+{
+ return GetLightmapSettings().GetLightProbes();
+}
+
+static inline float TriArea2D(float x1, float y1, float x2, float y2, float x3, float y3)
+{
+ return (x1-x2)*(y2-y3) - (x2-x3)*(y1-y2);
+}
+
+// Taken from Real-Time Collision Detection
+static inline void BarycentricCoordinates3DTriangle(const Vector3f tri[3], const Vector3f& p, Vector4f& coords)
+{
+ // Unnormalized(!) triangle normal
+ Vector3f normal = Cross(tri[1] - tri[0], tri[2] - tri[0]);
+ // Nominators and one-over-denominator for u and v ratios
+ float nu, nv, ood;
+ // Absolute components for determining projection plane
+ float x = Abs(normal.x), y = Abs(normal.y), z = Abs(normal.z);
+
+ // Compute areas in plane of largest projection
+ if (x >= y && x >= z) {
+ // x is largest, project to the yz plane
+ nu = TriArea2D(p.y, p.z, tri[1].y, tri[1].z, tri[2].y, tri[2].z); // Area of PBC in yz plane
+ nv = TriArea2D(p.y, p.z, tri[2].y, tri[2].z, tri[0].y, tri[0].z); // Area of PCA in yz plane
+ ood = 1.0f / normal.x; // 1/(2*area of ABC in yz plane)
+ } else if (y >= x && y >= z) {
+ // y is largest, project to the xz plane
+ nu = TriArea2D(p.x, p.z, tri[1].x, tri[1].z, tri[2].x, tri[2].z);
+ nv = TriArea2D(p.x, p.z, tri[2].x, tri[2].z, tri[0].x, tri[0].z);
+ ood = 1.0f / -normal.y;
+ } else {
+ // z is largest, project to the xy plane
+ nu = TriArea2D(p.x, p.y, tri[1].x, tri[1].y, tri[2].x, tri[2].y);
+ nv = TriArea2D(p.x, p.y, tri[2].x, tri[2].y, tri[0].x, tri[0].y);
+ ood = 1.0f / normal.z;
+ }
+ coords.x = nu * ood;
+ coords.y = nv * ood;
+ coords.z = 1.0f - coords.x - coords.y;
+}
+
+// Warped prism is an outer open cell (a.k.a. outer tetrahedron) formed by a tri face of the convex hull and
+// 3 vertex normals. There are no additional constraints on the normals other than they have point in the
+// half-space above the face, so the prism can be warped and the function will still project correctly.
+// This is done by solving the equation
+// P = a*(V0*t+P0) + b*(V1*t+P1) + (1-a-b)*(V2*t+P2)
+// where V_ is the vertex normal, P_ the hull vertex, so V_*t+P_ is a hull ray for t >= 0
+// P is the point we want to find the three barycentric coordinates for, so a, b and c=1-a-b
+// If we substitute
+// A = P0 - P2, Ap = V0 - V2, B = P1 - P2, Bp = V1 - V2, C = P - P2, Cp = -V2,
+// we can rewrite as:
+// a*(A + t*Ap) + b*(B + t*Bp) = C + t*Cp
+// or, as a matrix equation:
+// A.x + t*Ap.x, B.x + t*Bp.x, C.x + t*Cp.x a 0
+// [ A.y + t*Ap.y, B.y + t*Bp.y, C.y + t*Cp.y ] [ b ] = [ 0 ]
+// A.z + t*Ap.z, B.z + t*Bp.z, C.z + t*Cp.z -1 0
+// which only has a solution, if the determinant is 0, which gives a cubic in t.
+// The geometrical interpretation shows that there should be exactly one positive root for that cubic
+// and that's our t. (There might be two real negative roots too, but we don't care).
+// Finally we can find a and b either by substituting t into the two equations, or plug t
+// into the hull rays V_*t+P_ and use BarycentricCoordinates3DTriangle()
+inline void GetBarycentricCoordinatesForOuterCell (const dynamic_array<Vector3f>& bakedPositions, const dynamic_array<Vector3f>& hullRays, const Vector3f& p, const Tetrahedron& tet, Vector4f& coords, float& t)
+{
+ // It's an outer tetrahedron;
+ // Take the position relative to one of the triangle points (say, 0th) and dot it with normal
+ const int (&ind)[4] = tet.indices;
+ const Vector3f& v0 = bakedPositions[ind[0]];
+ const Vector3f edge0 = bakedPositions[ind[1]] - v0;
+ const Vector3f edge1 = bakedPositions[ind[2]] - v0;
+ // We could cache the normal... or not.
+ const Vector3f normal = Cross(edge1, edge0);
+ t = Dot(p - v0, normal);
+ if (t < 0)
+ {
+ // p is below the hull surface of this tetrahedron, so let's just return the 4th barycentric coordinate
+ // as the lowest (and negative), so that the tetrahedron adjacent at the base gets tested next
+ coords.Set(0, 0, 0, -1);
+ return;
+ }
+
+ // CalculateOuterTetrahedraMatrices() prepares the Tetrahedron.matrix, so that
+ // the coefficients of the cubic can be found just like that:
+ Vector3f polyCoeffs = tet.matrix.MultiplyPoint3(p);
+ // If the polynomial degenerated to quadratic, the unused ind[3] will be set to -2 instead of -1
+ t = ind[3] == -1 ? CubicPolynomialRoot(polyCoeffs.x, polyCoeffs.y, polyCoeffs.z) : QuadraticPolynomialRoot(polyCoeffs.x, polyCoeffs.y, polyCoeffs.z);
+
+ // We could directly calculate the barycentric coords by plugging t into
+ // a*(A + t*Ap) + b*(B + t*Bp) = C + t*Cp, checking which coord to ignore
+ // and using the two other equations, but it's actually almost the same
+ // as using BarycentricCoordinates3DTriangle()
+
+ Vector3f tri[3];
+ tri[0] = bakedPositions[ind[0]] + hullRays[ind[0]]*t;
+ tri[1] = bakedPositions[ind[1]] + hullRays[ind[1]]*t;
+ tri[2] = bakedPositions[ind[2]] + hullRays[ind[2]]*t;
+ BarycentricCoordinates3DTriangle(tri, p, coords);
+ coords.w = 0;
+}
+
+inline void GetBarycentricCoordinatesForInnerTetrahedron (const dynamic_array<Vector3f>& bakedPositions, const Vector3f& p, const Tetrahedron& tet, Vector4f& coords)
+{
+ // It's an inner tetrahedron, just use the precalculated matrix to find the barycentric coordinates
+ Vector3f mult = tet.matrix.MultiplyVector3(p - bakedPositions[tet.indices[3]]);
+ coords.x = mult.x;
+ coords.y = mult.y;
+ coords.z = mult.z;
+ coords.w = 1.0f - mult.x - mult.y - mult.z;
+}
+
+void GetBarycentricCoordinates (const dynamic_array<Vector3f>& bakedPositions, const dynamic_array<Vector3f>& hullRays, const Vector3f& p, const Tetrahedron& tet, Vector4f& coords, float& t)
+{
+ if (tet.indices[3] >= 0)
+ GetBarycentricCoordinatesForInnerTetrahedron (bakedPositions, p, tet, coords);
+ else
+ GetBarycentricCoordinatesForOuterCell (bakedPositions, hullRays, p, tet, coords, t);
+}
+
+void GetLightProbeInterpolationWeights (const LightProbeData& data, const Vector3f& position, int& tetIndex, Vector4f& weights, float& t, int& steps)
+{
+ // If we don't have an initial guess, always start from tetrahedron 0.
+ // Tetrahedron 0 is picked to be roughly in the center of the probe cloud,
+ // to minimize the number of steps to any other tetrahedron.
+ const int tetCount = data.tetrahedra.size();
+ if (tetIndex < 0 || tetIndex >= tetCount)
+ tetIndex = 0;
+
+ steps = 0;
+ int prev = -1, prevprev = -1;
+ for (; steps < tetCount; steps++)
+ {
+ // Check if we're in the current "best guess" tetrahedron
+ const Tetrahedron& tet = data.tetrahedra[tetIndex];
+ GetBarycentricCoordinates(data.positions, data.hullRays, position, tet, weights, t);
+ if (weights.x >= 0.0f && weights.y >= 0.0f && weights.z >= 0.0f && weights.w >= 0.0f)
+ {
+ // Success!
+ return;
+ }
+
+
+ // There's a chance the position lies "between" two tetrahedra, i.e. both return a slightly negative weight
+ // due to numerical errors and we ping-pong between them.
+ if (tetIndex == prevprev)
+ return;
+
+ prevprev = prev;
+ prev = tetIndex;
+
+ // Otherwise find the smallest barycentric coord and move in that direction
+ if (weights.x < weights.y && weights.x < weights.z && weights.x < weights.w)
+ tetIndex = tet.neighbors[0];
+ else if (weights.y < weights.z && weights.y < weights.w)
+ tetIndex = tet.neighbors[1];
+ else if (weights.z < weights.w)
+ tetIndex = tet.neighbors[2];
+ else
+ tetIndex = tet.neighbors[3];
+ }
+}
+
+void InterpolateLightProbeCoefficients(const dynamic_array<LightProbeCoefficients>& bakedCoefficients, const Tetrahedron& tet, const Vector4f& weights, float* coefficients)
+{
+ // Outer tetrahedra don't have a fourth probe
+ int probeCount = tet.indices[3] < 0 ? 3 : 4;
+
+ for (int i = 0; i < probeCount; i++)
+ {
+ int probeIndex = tet.indices[i];
+ float probeWeight = weights[i];
+ for (int j = 0; j < kLightProbeCoefficientCount; j++)
+ {
+ coefficients[j] += bakedCoefficients[probeIndex].sh[j] * probeWeight;
+ }
+ }
+}
+
+void LightProbes::GetInterpolatedLightProbe (const Vector3f& position, Renderer* renderer, float* coefficients, int& tetIndex, Vector4f& weights, float& t)
+{
+ // Init to black
+ memset (coefficients, 0, sizeof(float)*kLightProbeCoefficientCount);
+
+ // If there are no probes baked
+ const int tetCount = m_Data.tetrahedra.size();
+ if (tetCount == 0)
+ {
+ tetIndex = -1;
+ weights.Set(0,0,0,0);
+ return;
+ }
+
+ // Use tetIndex as an initial guess; if it's not set, check for it in the renderer
+ if ((tetIndex < 0 || tetIndex >= tetCount) && renderer != NULL)
+ tetIndex = renderer->GetLastLightProbeTetIndex();
+
+ int steps;
+ GetLightProbeInterpolationWeights(m_Data, position, tetIndex, weights, t, steps);
+
+ // Return black if we're not within any tetrahedron. It should never happen.
+ if (tetIndex < 0 || tetIndex >= tetCount)
+ return;
+
+ // Tetrahedron found, set it's index to the renderer, to be used as a good guess for the next frame
+ if (renderer)
+ renderer->SetLastLightProbeTetIndex(tetIndex);
+
+ // Use the weights to actually interpolate the probes and get the coefficients
+ InterpolateLightProbeCoefficients(m_Data.coefficients, m_Data.tetrahedra[tetIndex], weights, coefficients);
+}
+
+void LightProbes::SetCoefficients( float* data, int size )
+{
+ int count = m_Data.positions.size();
+ if ( count * kLightProbeCoefficientCount != size || !data)
+ {
+ ErrorString(Format("Number of coefficient sets (%i) has to be equal to current light probe count (%i).", size/kLightProbeCoefficientCount, count));
+ return;
+ }
+ LightProbeCoefficients* lpData = reinterpret_cast<LightProbeCoefficients*>(data);
+ m_Data.coefficients.assign(lpData, lpData + count);
+ SetDirty();
+}
+
+IMPLEMENT_CLASS_HAS_INIT (LightProbes)
+IMPLEMENT_OBJECT_SERIALIZE (LightProbes)
+
+Matrix3x4f::Matrix3x4f( const Matrix3x3f& other )
+{
+ memcpy(&m_Data[0], &other.m_Data[0], sizeof(Matrix3x3f));
+ m_Data[9] = m_Data[10] = m_Data[11] = 0.0f;
+}
+
+inline Vector3f Matrix3x4f::MultiplyVector3( const Vector3f& v ) const
+{
+ Vector3f res;
+ res.x = m_Data[0] * v.x + m_Data[3] * v.y + m_Data[6] * v.z;
+ res.y = m_Data[1] * v.x + m_Data[4] * v.y + m_Data[7] * v.z;
+ res.z = m_Data[2] * v.x + m_Data[5] * v.y + m_Data[8] * v.z;
+ return res;
+}
+
+inline Vector3f Matrix3x4f::MultiplyPoint3( const Vector3f& v ) const
+{
+ Vector3f res;
+ res.x = m_Data[0] * v.x + m_Data[3] * v.y + m_Data[6] * v.z + m_Data[9];
+ res.y = m_Data[1] * v.x + m_Data[4] * v.y + m_Data[7] * v.z + m_Data[10];
+ res.z = m_Data[2] * v.x + m_Data[5] * v.y + m_Data[8] * v.z + m_Data[11];
+ return res;
+}
+
+template<class TransferFunction> inline
+void Matrix3x4f::Transfer (TransferFunction& t)
+{
+ t.Transfer (m_Data[0], "e00"); t.Transfer (m_Data[3], "e01"); t.Transfer (m_Data[6], "e02"); t.Transfer (m_Data[ 9], "e03");
+ t.Transfer (m_Data[1], "e10"); t.Transfer (m_Data[4], "e11"); t.Transfer (m_Data[7], "e12"); t.Transfer (m_Data[10], "e13");
+ t.Transfer (m_Data[2], "e20"); t.Transfer (m_Data[5], "e21"); t.Transfer (m_Data[8], "e22"); t.Transfer (m_Data[11], "e23");
+}
diff --git a/Runtime/Camera/LightProbes.h b/Runtime/Camera/LightProbes.h
new file mode 100644
index 0000000..cd59f9b
--- /dev/null
+++ b/Runtime/Camera/LightProbes.h
@@ -0,0 +1,98 @@
+#pragma once
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Vector4.h"
+#include "Runtime/Math/Matrix3x3.h"
+
+class Renderer;
+
+// column-major
+// m0 m3 m6 m9
+// m1 m4 m7 m10
+// m2 m5 m8 m11
+// So far only used for light probes, move it to Matrix3x4f.h if it ever grows beyond that
+class Matrix3x4f
+{
+public:
+ float m_Data[12];
+ ///@todo: Can't be Transfer optimized because Transfer doesn't write the same as memory layout
+ DECLARE_SERIALIZE_NO_PPTR (Matrix3x4f)
+ Matrix3x4f () {}
+ Matrix3x4f (const Matrix3x3f& other);
+ float& operator [] (int index) { return m_Data[index];}
+ Vector3f MultiplyVector3 (const Vector3f& v) const;
+ Vector3f MultiplyPoint3 (const Vector3f& v) const;
+};
+
+enum { kLightProbeBasisCount = 9};
+enum { kLightProbeCoefficientCount = kLightProbeBasisCount*3};
+
+struct LightProbeCoefficients
+{
+ float sh[kLightProbeBasisCount*3];
+ float& operator [] (int i) { return sh[i]; }
+ DECLARE_SERIALIZE(LightmapData)
+};
+
+struct Tetrahedron
+{
+ int indices[4];
+ int neighbors[4];
+ // For an inner tetrahedron: the matrix is the cached inverted matrix used for calculating barycentric coordinates
+ // For an outer tetrahedron: it's a matrix allowing the calculation of the cubic coefficients for the
+ // t parameter polynomial, used for calculating barycentric coordinates as well
+ Matrix3x4f matrix;
+ DECLARE_SERIALIZE(Tetrahedron)
+};
+
+struct LightProbeData
+{
+ dynamic_array<Vector3f> positions;
+ dynamic_array<LightProbeCoefficients> coefficients;
+ dynamic_array<Tetrahedron> tetrahedra;
+ // TODO: Sort probes so that all outer probes are at the beginning of m_BakedPositions. Thanks to that m_HullRays will be only
+ // as long as there are outer probes, not all probes.
+ dynamic_array<Vector3f> hullRays;
+};
+
+class LightProbes;
+LightProbes* GetLightProbes ();
+
+class LightProbes : public NamedObject
+{
+public:
+ REGISTER_DERIVED_CLASS (LightProbes, NamedObject)
+ DECLARE_OBJECT_SERIALIZE(LightProbes)
+
+ LightProbes(MemLabelId label, ObjectCreationMode mode);
+
+ void AwakeFromLoad(AwakeFromLoadMode mode);
+ Vector3f* GetPositions() { return m_Data.positions.size() > 0 ? &m_Data.positions[0] : NULL; }
+ int GetPositionsSize() { return m_Data.positions.size(); }
+ LightProbeCoefficients* GetCoefficients() { return m_Data.coefficients.size() > 0 ? &m_Data.coefficients[0] : NULL; }
+ void SetCoefficients( float* data, int size );
+ int GetTetrahedraSize() { return m_Data.tetrahedra.size();}
+#if UNITY_EDITOR
+ Tetrahedron* GetTetrahedra() { return m_Data.tetrahedra.size() > 0 ? &m_Data.tetrahedra[0] : NULL;}
+ Vector3f* GetHullRays() { return m_Data.hullRays.size() > 0 ? &m_Data.hullRays[0] : NULL; }
+ void SetBakedData (const dynamic_array<Vector3f>& positions, const dynamic_array<LightProbeCoefficients>& coefficents);
+#endif
+ inline void GetInterpolatedLightProbe(const Vector3f& position, Renderer* renderer, float* coefficients)
+ {
+ int tetIndex = -1;
+ Vector4f weights;
+ float t;
+ GetInterpolatedLightProbe(position, renderer, coefficients, tetIndex, weights, t);
+ }
+ void GetInterpolatedLightProbe(const Vector3f& position, Renderer* renderer, float* coefficients, int& tetIndex, Vector4f& weights, float& t);
+
+ static void InitializeClass() {}
+ static void CleanupClass() {}
+ inline static bool AreBaked() { LightProbes* lp = GetLightProbes(); return lp && lp->GetTetrahedraSize() > 0; }
+
+private:
+ LightProbeData m_Data;
+};
+
diff --git a/Runtime/Camera/LightTypes.h b/Runtime/Camera/LightTypes.h
new file mode 100644
index 0000000..189236d
--- /dev/null
+++ b/Runtime/Camera/LightTypes.h
@@ -0,0 +1,28 @@
+#pragma once
+
+struct ActiveLight;
+
+struct ForwardLightsBlock
+{
+ float sh[9][3];
+ const ActiveLight* mainLight;
+ int addLightCount;
+ int vertexLightCount;
+ float lastAddLightBlend;
+ float lastVertexLightBlend;
+ // followed by ActiveLight pointers; additive lights first, then vertex lights
+
+ const ActiveLight* const* GetLights() const {
+ return reinterpret_cast<const ActiveLight* const*>( reinterpret_cast<const UInt8*>(this) + sizeof(ForwardLightsBlock) );
+ }
+};
+
+struct VertexLightsBlock
+{
+ int lightCount;
+ // followed by ActiveLight pointers
+
+ const ActiveLight* const* GetLights() const {
+ return reinterpret_cast<const ActiveLight* const*>( reinterpret_cast<const UInt8*>(this) + sizeof(VertexLightsBlock) );
+ }
+};
diff --git a/Runtime/Camera/Lighting.h b/Runtime/Camera/Lighting.h
new file mode 100644
index 0000000..dfe491b
--- /dev/null
+++ b/Runtime/Camera/Lighting.h
@@ -0,0 +1,34 @@
+#ifndef LIGHTING_H
+#define LIGHTING_H
+
+// Light type
+enum LightType {
+ kLightSpot,
+ kLightDirectional,
+ kLightPoint,
+ kLightArea,
+ kLightTypeCount // keep this last
+};
+
+// Pixel lighting mode (keyword to use)
+enum LightKeywordMode {
+ kLightKeywordSpot,
+ kLightKeywordDirectional,
+ kLightKeywordDirectionalCookie,
+ kLightKeywordPoint,
+ kLightKeywordPointCookie,
+ kLightKeywordCount // keep this last
+};
+
+enum ShadowType {
+ kShadowNone = 0,
+ kShadowHard,
+ kShadowSoft,
+};
+
+inline bool IsSoftShadow(ShadowType shadowType)
+{
+ return (shadowType == kShadowSoft);
+};
+
+#endif
diff --git a/Runtime/Camera/OcclusionArea.cpp b/Runtime/Camera/OcclusionArea.cpp
new file mode 100644
index 0000000..c00d5b8
--- /dev/null
+++ b/Runtime/Camera/OcclusionArea.cpp
@@ -0,0 +1,67 @@
+#include "UnityPrefix.h"
+#include "OcclusionArea.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+
+OcclusionArea::OcclusionArea (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+}
+
+OcclusionArea::~OcclusionArea ()
+{
+}
+
+void OcclusionArea::Reset ()
+{
+ Super::Reset();
+
+ m_Center = Vector3f::zero;
+ m_Size = Vector3f::one;
+ m_IsViewVolume = true;
+// m_OverrideResolution = false;
+// m_SmallestVoxelSize = 1.0F;
+// m_SmallestHoleSize = 0.1F;
+// m_BackfaceCulling = 0.0F;
+}
+
+Vector3f OcclusionArea::GetGlobalExtents () const
+{
+ Vector3f extents = GetComponent (Transform).GetWorldScaleLossy ();
+ extents.Scale (m_Size);
+ extents *= 0.5F;
+ extents = Abs (extents);
+ return extents;
+}
+
+Vector3f OcclusionArea::GetGlobalCenter () const
+{
+ return GetComponent (Transform).TransformPoint (m_Center);
+}
+
+using namespace Unity;
+
+template<class TransferFunction>
+void OcclusionArea::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ transfer.SetVersion (2);
+
+ TRANSFER(m_Size);
+ TRANSFER(m_Center);
+ TRANSFER(m_IsViewVolume);
+// TRANSFER(m_OverrideResolution);
+ transfer.Align();
+
+// TRANSFER(m_SmallestVoxelSize);
+// TRANSFER(m_SmallestHoleSize);
+// TRANSFER(m_BackfaceCulling);
+}
+
+IMPLEMENT_OBJECT_SERIALIZE (OcclusionArea)
+IMPLEMENT_CLASS_HAS_INIT (OcclusionArea)
+
+void OcclusionArea::InitializeClass ()
+{
+}
diff --git a/Runtime/Camera/OcclusionArea.h b/Runtime/Camera/OcclusionArea.h
new file mode 100644
index 0000000..6610cb4
--- /dev/null
+++ b/Runtime/Camera/OcclusionArea.h
@@ -0,0 +1,46 @@
+#ifndef OCCLUSION_AREA_H
+#define OCCLUSION_AREA_H
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Math/Vector3.h"
+
+class OcclusionArea : public Unity::Component {
+public:
+ OcclusionArea ();
+
+ REGISTER_DERIVED_CLASS (OcclusionArea, Component)
+ DECLARE_OBJECT_SERIALIZE (OcclusionArea)
+
+ OcclusionArea (MemLabelId label, ObjectCreationMode mode);
+
+ virtual void Reset();
+
+ void SetCenter (const Vector3f& center) { m_Center = center; SetDirty(); }
+ const Vector3f& GetCenter () const { return m_Center; }
+
+ void SetSize (const Vector3f& size) { m_Size = size; SetDirty(); }
+ const Vector3f& GetSize () const { return m_Size; }
+
+ void SetViewVolume (bool isViewVolume) { m_IsViewVolume = isViewVolume; SetDirty(); }
+ bool GetViewVolume () const { return m_IsViewVolume; }
+
+ Vector3f GetGlobalExtents () const;
+ Vector3f GetGlobalCenter () const;
+
+ static void InitializeClass ();
+ static void CleanupClass () { }
+
+private:
+
+ Vector3f m_Size;
+ Vector3f m_Center;
+
+ bool m_IsViewVolume;
+
+// bool m_OverrideResolution;
+// float m_SmallestVoxelSize;
+// float m_SmallestHoleSize;
+// float m_BackfaceCulling;
+};
+
+#endif
diff --git a/Runtime/Camera/OcclusionPortal.cpp b/Runtime/Camera/OcclusionPortal.cpp
new file mode 100644
index 0000000..ab741bd
--- /dev/null
+++ b/Runtime/Camera/OcclusionPortal.cpp
@@ -0,0 +1,60 @@
+#include "UnityPrefix.h"
+#include "OcclusionPortal.h"
+#include "UnityScene.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+OcclusionPortal::OcclusionPortal (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+ m_Open = true;
+ m_PortalIndex = -1;
+ m_Center = Vector3f(0,0,0);
+ m_Size = Vector3f(1,1,1);
+}
+
+OcclusionPortal::~OcclusionPortal ()
+{
+}
+
+bool OcclusionPortal::CalculatePortalEnabled ()
+{
+ if (!IsActive())
+ return true;
+
+ return m_Open;
+}
+
+void OcclusionPortal::SetIsOpen (bool open)
+{
+ m_Open = open;
+
+ if (m_PortalIndex != -1)
+ GetScene().SetOcclusionPortalEnabled (m_PortalIndex, CalculatePortalEnabled());
+}
+
+void OcclusionPortal::Deactivate (DeactivateOperation operation)
+{
+ if (m_PortalIndex != -1)
+ GetScene().SetOcclusionPortalEnabled (m_PortalIndex, true);
+}
+
+void OcclusionPortal::AwakeFromLoad (AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad(mode);
+
+ if (m_PortalIndex != -1)
+ GetScene().SetOcclusionPortalEnabled (m_PortalIndex, CalculatePortalEnabled());
+}
+
+template<class TransferFunction>
+void OcclusionPortal::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ TRANSFER (m_Open);
+ transfer.Align();
+ TRANSFER (m_Center);
+ TRANSFER (m_Size);
+}
+
+IMPLEMENT_OBJECT_SERIALIZE (OcclusionPortal)
+IMPLEMENT_CLASS (OcclusionPortal) \ No newline at end of file
diff --git a/Runtime/Camera/OcclusionPortal.h b/Runtime/Camera/OcclusionPortal.h
new file mode 100644
index 0000000..48bface
--- /dev/null
+++ b/Runtime/Camera/OcclusionPortal.h
@@ -0,0 +1,37 @@
+#ifndef OCCLUSION_PORTAL_H
+#define OCCLUSION_PORTAL_H
+
+#include "Runtime/GameCode/Behaviour.h"
+#include "Runtime/Math/Vector3.h"
+
+class OcclusionPortal : public Unity::Component
+{
+public:
+ REGISTER_DERIVED_CLASS (OcclusionPortal, Component)
+ DECLARE_OBJECT_SERIALIZE (OcclusionPortal)
+
+ OcclusionPortal (MemLabelId label, ObjectCreationMode mode);
+
+ void SetPortalIndex (int portalIndex) { m_PortalIndex = portalIndex; }
+
+ void SetIsOpen (bool opened);
+ bool GetIsOpen () { return m_Open; }
+
+
+ Vector3f GetCenter () { return m_Center; }
+ Vector3f GetSize () { return m_Size; }
+
+ bool CalculatePortalEnabled ();
+
+ virtual void AwakeFromLoad (AwakeFromLoadMode mode);
+ virtual void Deactivate (DeactivateOperation operation);
+
+ private:
+
+ Vector3f m_Center;
+ Vector3f m_Size;
+ int m_PortalIndex;
+ bool m_Open;
+};
+
+#endif \ No newline at end of file
diff --git a/Runtime/Camera/Projector.cpp b/Runtime/Camera/Projector.cpp
new file mode 100644
index 0000000..d60ea62
--- /dev/null
+++ b/Runtime/Camera/Projector.cpp
@@ -0,0 +1,313 @@
+#include "UnityPrefix.h"
+#include "Projector.h"
+#include "BaseRenderer.h"
+#include "RenderManager.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Camera/Camera.h"
+#include "Renderqueue.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "External/shaderlab/Library/properties.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Camera/CullResults.h"
+//#include "Runtime/Camera/RenderLoops/RenderLoopEnums.h"
+
+void Projector::InitializeClass ()
+{
+ RegisterAllowNameConversion (Projector::GetClassStringStatic(), "m_IsOrthoGraphic", "m_Orthographic");
+ RegisterAllowNameConversion (Projector::GetClassStringStatic(), "m_OrthoGraphicSize", "m_OrthographicSize");
+}
+
+Projector::Projector (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+}
+
+Projector::~Projector ()
+{
+}
+
+void Projector::Reset ()
+{
+ Super::Reset();
+ m_NearClipPlane = .1F;
+ m_FarClipPlane = 100.0F;
+ m_FieldOfView = 60;
+ m_AspectRatio = 1.0F;
+ m_OrthographicSize = 10;
+ m_Orthographic = false;
+ m_IgnoreLayers.m_Bits = 0;
+}
+
+// chosen not to blow up when the user has set fov, aspect ratio and ortho size to 0 at the same time
+static const float kSmallValue = 1.0e-8f;
+
+// should probably be scaled with m_NearClipPlane, but it's fine for reasonable near clip values
+static const float kSmallestNearClipMargin = 1.0e-2f;
+
+void Projector::CheckConsistency ()
+{
+ Super::CheckConsistency ();
+
+ if (m_Orthographic)
+ {
+ float clipPlaneDiff = m_FarClipPlane - m_NearClipPlane;
+ if (Abs (clipPlaneDiff) < kSmallestNearClipMargin)
+ m_FarClipPlane = m_NearClipPlane + Sign (clipPlaneDiff) * kSmallestNearClipMargin;
+ }
+ else
+ {
+ if (m_NearClipPlane < kSmallestNearClipMargin)
+ m_NearClipPlane = kSmallestNearClipMargin;
+
+ if (m_FarClipPlane < m_NearClipPlane + kSmallestNearClipMargin)
+ m_FarClipPlane = m_NearClipPlane + kSmallestNearClipMargin;
+ }
+
+
+ if (Abs (m_FieldOfView) < kSmallValue)
+ m_FieldOfView = kSmallValue * Sign (m_FieldOfView);
+
+ if (Abs (m_AspectRatio) < kSmallValue)
+ m_AspectRatio = kSmallValue * Sign (m_AspectRatio);
+
+ if (Abs (m_OrthographicSize) < kSmallValue)
+ m_OrthographicSize = kSmallValue * Sign (m_OrthographicSize);
+}
+
+using namespace Unity;
+
+struct ProjectorRenderSettings
+{
+ Matrix4x4f projection;
+ Matrix4x4f distance;
+ Matrix4x4f clipping;
+ Matrix4x4f frustumMatrix;
+
+ Material* material;
+ Shader* shader;
+ int subshaderIndex;
+ int passCount;
+};
+
+static void RenderProjectorForRenderer ( BaseRenderer* renderer, const ProjectorRenderSettings& settings)
+{
+ const TransformInfo& xformInfo = renderer->GetTransformInfo ();
+ const Matrix4x4f& transform = xformInfo.worldMatrix;
+
+ // texture projection matrices
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+ MultiplyMatrices4x4 (&settings.projection, &transform, &params.GetWritableMatrixParam(kShaderMatProjector));
+ MultiplyMatrices4x4 (&settings.distance, &transform, &params.GetWritableMatrixParam(kShaderMatProjectorDistance));
+ MultiplyMatrices4x4 (&settings.clipping, &transform, &params.GetWritableMatrixParam(kShaderMatProjectorClip));
+
+ SetupObjectMatrix (transform, xformInfo.transformType);
+
+ for (int i=0;i<settings.passCount;i++)
+ {
+ const ChannelAssigns* channels = settings.material->SetPassWithShader( i, settings.shader, settings.subshaderIndex );
+ if (!channels) // pass should not be rendered
+ continue;
+
+ int rendererMatCount = renderer->GetMaterialCount();
+ for( int m = 0; m < rendererMatCount; ++m )
+ {
+ Material* rendererMat = renderer->GetMaterial(m);
+ if( rendererMat )
+ {
+ Shader* rendererShader = rendererMat->GetShader();
+ if( rendererShader && rendererShader->GetShaderLabShader()->GetNoProjector() )
+ continue;
+ }
+ renderer->Render (renderer->GetSubsetIndex(m), *channels);
+ }
+ }
+}
+
+void Projector::AddToManager ()
+{
+ RenderManager& renderManager = GetRenderManager();
+
+ // Remove first to make sure we don't have duplicates
+ // @todo: Eventually Behaviour should be fixed to always close a AddToManager with a RemoveFromManager
+ renderManager.RemoveCameraRenderable (this);
+
+ // The projector should default to "after all geometry" render queue.
+ // Unless the material defines a non-default render queue, in which case
+ // use that.
+ int renderQueue = kGeometryQueueIndexMax + 1;
+ Material* projectorMaterial = m_Material;
+ if (projectorMaterial)
+ {
+ int rq = projectorMaterial->GetActualRenderQueue();
+ if (rq != kGeometryRenderQueue)
+ renderQueue = rq;
+ }
+
+ renderManager.AddCameraRenderable (this, renderQueue);
+}
+
+
+void Projector::RemoveFromManager ()
+{
+ GetRenderManager ().RemoveCameraRenderable (this);
+}
+
+void Projector::SetupProjectorSettings (Material* projectorMaterial, ProjectorRenderSettings& projectorSettings)
+{
+ Matrix4x4f projectionMatrix = CalculateProjectionMatrix ();
+
+ Matrix4x4f zscale;
+ zscale.SetScale (Vector3f (1.0F, 1.0F, -1.0F));
+
+ Matrix4x4f projectorToWorld;
+ projectorToWorld = GetComponent (Transform).GetWorldToLocalMatrixNoScale ();
+
+ Matrix4x4f temp1, temp2,temp3, temp4;
+
+ // Setup the functor
+ // projection matrix
+ temp1.SetScale (Vector3f (.5f, .5f, 1.0f));
+ temp2.SetTranslate (Vector3f (.5f, .5f, 0.0f));
+ // functor.projection = temp2 * projectionMatrix * zscale * temp1 * projectorToWorld
+ MultiplyMatrices4x4 (&temp2, &projectionMatrix, &temp3);
+ MultiplyMatrices4x4 (&temp3, &zscale, &temp4);
+ MultiplyMatrices4x4 (&temp4, &temp1, &temp2);
+ MultiplyMatrices4x4 (&temp2, &projectorToWorld, &projectorSettings.projection);
+
+ // X-axis fadeout matrix
+ float scale = 1.0f / m_FarClipPlane;
+ temp1.SetScale (Vector3f (scale, scale, scale));
+ temp2.SetIdentity ();
+ temp2.Get(0,0) = 0; temp2.Get(0,1) = 0; temp2.Get(0,2) = 1; temp2.Get(0,0) = 0;
+ // functor.distance = temp2 * temp1 * projectorToWorld
+ MultiplyMatrices4x4 (&temp2, &temp1, &temp3);
+ MultiplyMatrices4x4 (&temp3, &projectorToWorld, &projectorSettings.distance);
+
+ // X-axis texture cull (use with an alpha map to do alpha-tested clip planes)
+ scale = 1.0f / (m_FarClipPlane - m_NearClipPlane);
+ temp1.SetScale (Vector3f (scale, scale, scale));
+ temp2.SetIdentity ();
+ temp3.SetTranslate (Vector3f (-m_NearClipPlane, -m_NearClipPlane, -m_NearClipPlane));
+ temp2.Get(0,0) = 0; temp2.Get(0,1) = 0; temp2.Get(0,2) = 1; temp2.Get(0,0) = 0;
+ // functor.clipping = temp2 * temp1 * temp3 * projectorToWorld
+ MultiplyMatrices4x4 (&temp2, &temp1, &temp4);
+ MultiplyMatrices4x4 (&temp4, &temp3, &temp1);
+ MultiplyMatrices4x4 (&temp1, &projectorToWorld, &projectorSettings.clipping);
+
+ Shader* shader = projectorMaterial->GetShader();
+ int subshaderIndex = 0;
+ projectorSettings.material = projectorMaterial;
+ projectorSettings.shader = shader;
+ projectorSettings.subshaderIndex = subshaderIndex;
+ const ShaderLab::SubShader& ss = shader->GetShaderLabShader()->GetSubShader(subshaderIndex);
+ projectorSettings.passCount = ss.GetValidPassCount();
+
+ /// Setup culling planes to be the projector area without any layer based distance culling.
+ // finalProj = projectionMatrix * zscale * projectorToWorld
+ MultiplyMatrices4x4 (&projectionMatrix, &zscale, &temp1);
+ MultiplyMatrices4x4 (&temp1, &projectorToWorld, &projectorSettings.frustumMatrix);
+}
+
+
+void Projector::RenderRenderable (const CullResults& cullResults)
+{
+ // early out if we have no material
+ Material* projectorMaterial = m_Material;
+ if( !projectorMaterial )
+ return;
+
+ // We dont't support projectors when doing shader replacement
+ if ( cullResults.shaderReplaceData.replacementShader != NULL)
+ return;
+
+ Camera& camera = GetCurrentCamera();
+ if( !(camera.GetCullingMask() & GetGameObject().GetLayerMask()) )
+ return; // current camera does not render our layer - exit
+
+ // save current view/world matrices
+ GfxDevice& device = GetGfxDevice();
+ float matWorld[16], matView[16];
+ CopyMatrix(device.GetViewMatrix(), matView);
+ CopyMatrix(device.GetWorldMatrix(), matWorld);
+
+
+ ProjectorRenderSettings projectorSettings;
+ SetupProjectorSettings (projectorMaterial, projectorSettings);
+
+ UInt32 projectorCullingMask = ~(m_IgnoreLayers.m_Bits);
+
+ // From the objects that we are rendering from this camera.
+ // Cull it further to the objects that overlap with the Projector frustum
+ Plane cullingPlanes[6];
+ ExtractProjectionPlanes (projectorSettings.frustumMatrix, cullingPlanes);
+
+ const VisibleNodes& nodes = cullResults.nodes;
+ for (int i=0;i<nodes.size();i++)
+ {
+ if (IntersectAABBFrustumFull (nodes[i].worldAABB, cullingPlanes))
+ {
+ UInt32 mask = nodes[i].renderer->GetLayerMask();
+ if (mask & projectorCullingMask)
+ RenderProjectorForRenderer (nodes[i].renderer, projectorSettings);
+ }
+ }
+
+ // restore view/world matrices
+ device.SetViewMatrix(matView);
+ device.SetWorldMatrix(matWorld);
+}
+
+Matrix4x4f Projector::GetProjectorToPerspectiveMatrix() const
+{
+ Matrix4x4f zscale;
+ zscale.SetScale (Vector3f (1.0F, 1.0F, -1.0F));
+
+ Matrix4x4f projectorToWorld = GetComponent (Transform).GetWorldToLocalMatrixNoScale ();
+
+ // CalculateProjectionMatrix() * zscale * projectorToWorld
+ Matrix4x4f proj, temp, res;
+ proj = CalculateProjectionMatrix();
+ MultiplyMatrices4x4 (&proj, &zscale, &temp);
+ MultiplyMatrices4x4 (&temp, &projectorToWorld, &res);
+ return res;
+}
+
+Matrix4x4f Projector::CalculateProjectionMatrix() const
+{
+ Matrix4x4f projection;
+ if( m_Orthographic )
+ projection.SetOrtho(-m_OrthographicSize * m_AspectRatio, m_OrthographicSize * m_AspectRatio, -m_OrthographicSize, m_OrthographicSize, m_NearClipPlane, m_FarClipPlane);
+ else
+ projection.SetPerspective(m_FieldOfView, m_AspectRatio, m_NearClipPlane, m_FarClipPlane);
+ return projection;
+}
+
+template<class TransferFunction>
+void Projector::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ // Note: transfer code for version 1 was just removed. It was around Unity 1.2 times,
+ // and now we're fine with losing project folder compatibility with that.
+ transfer.SetVersion (2);
+
+ TRANSFER_SIMPLE(m_NearClipPlane);
+ TRANSFER_SIMPLE(m_FarClipPlane);
+ TRANSFER_SIMPLE(m_FieldOfView);
+ TRANSFER(m_AspectRatio);
+ TRANSFER(m_Orthographic);
+ transfer.Align();
+ TRANSFER(m_OrthographicSize);
+ TRANSFER_SIMPLE(m_Material);
+ TRANSFER(m_IgnoreLayers);
+}
+
+IMPLEMENT_OBJECT_SERIALIZE (Projector)
+IMPLEMENT_CLASS_HAS_INIT (Projector)
diff --git a/Runtime/Camera/Projector.h b/Runtime/Camera/Projector.h
new file mode 100644
index 0000000..f805e1e
--- /dev/null
+++ b/Runtime/Camera/Projector.h
@@ -0,0 +1,74 @@
+#ifndef PROJECTOR_H
+#define PROJECTOR_H
+
+#include "Runtime/GameCode/Behaviour.h"
+#include "Renderable.h"
+#include "Runtime/Math/Matrix4x4.h"
+
+namespace Unity { class Material; }
+struct CullResults;
+struct ProjectorRenderSettings;
+
+class Projector : public Behaviour, Renderable {
+public:
+ Projector ();
+
+ REGISTER_DERIVED_CLASS (Projector, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (Projector)
+
+ Projector (MemLabelId label, ObjectCreationMode mode);
+
+ // Renderable
+ virtual void RenderRenderable (const CullResults& cullResults);
+
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+
+ virtual void Reset();
+ virtual void CheckConsistency ();
+
+ void SetNearClipPlane (float inNear) { m_NearClipPlane = inNear; SetDirty(); }
+ float GetNearClipPlane () const { return m_NearClipPlane; }
+
+ void SetFarClipPlane (float farPlane) { m_FarClipPlane = farPlane; SetDirty(); }
+ float GetFarClipPlane () const { return m_FarClipPlane; }
+
+ void SetFieldOfView (float angle) { m_FieldOfView = angle; SetDirty(); }
+ float GetFieldOfView () const { return m_FieldOfView; }
+
+ void SetAspectRatio (float aspect) { m_AspectRatio = aspect; SetDirty(); }
+ float GetAspectRatio () const { return m_AspectRatio; }
+
+ void SetOrthographic (bool isOrtho) { m_Orthographic = isOrtho; SetDirty(); }
+ bool GetOrthographic () const { return m_Orthographic; }
+
+ void SetOrthographicSize (float size) { m_OrthographicSize = size; SetDirty(); }
+ float GetOrthographicSize () const { return m_OrthographicSize; }
+
+ PPtr<Material> GetMaterial () const { return m_Material; }
+ void SetMaterial (PPtr<Material> material) { m_Material = material; }
+
+ int GetIgnoreLayers () { return m_IgnoreLayers.m_Bits; }
+ void SetIgnoreLayers(int layers) { m_IgnoreLayers.m_Bits = layers; SetDirty(); }
+
+ Matrix4x4f GetProjectorToPerspectiveMatrix() const;
+ static void InitializeClass();
+ static void CleanupClass() { }
+
+private:
+
+ void SetupProjectorSettings (Material* material, ProjectorRenderSettings& settings);
+ Matrix4x4f CalculateProjectionMatrix() const;
+
+ float m_NearClipPlane;
+ float m_FarClipPlane;
+ float m_FieldOfView;
+ float m_AspectRatio;
+ bool m_Orthographic;
+ float m_OrthographicSize;
+
+ BitField m_IgnoreLayers;
+ PPtr<Material> m_Material; ///< Custom material to apply. If set it overrides, texture & blend mode settings
+};
+
+#endif
diff --git a/Runtime/Camera/RenderLayers/GUIElement.cpp b/Runtime/Camera/RenderLayers/GUIElement.cpp
new file mode 100644
index 0000000..73ff0e0
--- /dev/null
+++ b/Runtime/Camera/RenderLayers/GUIElement.cpp
@@ -0,0 +1,31 @@
+#include "UnityPrefix.h"
+#include "GUIElement.h"
+#include "GUILayer.h"
+#include "Runtime/Math/Vector2.h"
+
+GUIElement::GUIElement (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+}
+
+GUIElement::~GUIElement ()
+{
+}
+
+void GUIElement::AddToManager ()
+{
+ GUILayer::ms_GUIElements->add_delayed (this);
+}
+
+void GUIElement::RemoveFromManager ()
+{
+ GUILayer::ms_GUIElements->remove_delayed (this);
+}
+
+bool GUIElement::HitTest (const Vector2f& screenSpacePosition, const Rectf& cameraRect)
+{
+ Rectf rect = GetScreenRect (cameraRect);
+ return rect.Contains (screenSpacePosition.x, screenSpacePosition.y);
+}
+
+IMPLEMENT_CLASS (GUIElement)
diff --git a/Runtime/Camera/RenderLayers/GUIElement.h b/Runtime/Camera/RenderLayers/GUIElement.h
new file mode 100644
index 0000000..0092da9
--- /dev/null
+++ b/Runtime/Camera/RenderLayers/GUIElement.h
@@ -0,0 +1,30 @@
+#ifndef GUIELEMENT_H
+#define GUIELEMENT_H
+
+#include "Runtime/GameCode/Behaviour.h"
+#include "Runtime/Math/Rect.h"
+class Vector2f;
+
+
+// Base class for all GUI elements.
+// Registers itself with the GUILayer when enabled.
+class GUIElement : public Behaviour
+{
+public:
+
+ REGISTER_DERIVED_ABSTRACT_CLASS (GUIElement, Behaviour)
+
+ GUIElement (MemLabelId label, ObjectCreationMode mode);
+
+ virtual void RenderGUIElement (const Rectf& cameraRect) = 0;
+ virtual Rectf GetScreenRect (const Rectf& cameraRect) = 0;
+
+ bool HitTest (const Vector2f& screenSpaceCoordinates, const Rectf& cameraRect);
+
+private:
+
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+};
+
+#endif
diff --git a/Runtime/Camera/RenderLayers/GUILayer.cpp b/Runtime/Camera/RenderLayers/GUILayer.cpp
new file mode 100644
index 0000000..69deeb8
--- /dev/null
+++ b/Runtime/Camera/RenderLayers/GUILayer.cpp
@@ -0,0 +1,104 @@
+#include "UnityPrefix.h"
+#include "GUILayer.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/BaseClasses/Tags.h"
+#include "Runtime/Allocator/MemoryMacros.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+
+PROFILER_INFORMATION(gGUILayerProfile, "Camera.GUILayer", kProfilerRender);
+
+GUILayer::GUIElements* GUILayer::ms_GUIElements = NULL;
+
+GUILayer::GUILayer(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+}
+
+GUILayer::~GUILayer ()
+{
+}
+
+inline bool SortGUIByDepth (GUIElement* lhs, GUIElement* rhs)
+{
+ return lhs->GetComponent (Transform).GetLocalPosition ().z < rhs->GetComponent (Transform).GetLocalPosition ().z;
+}
+
+void GUILayer::InitializeClass ()
+{
+ ms_GUIElements = new GUIElements;
+}
+
+void GUILayer::CleanupClass()
+{
+ delete ms_GUIElements;
+}
+
+
+void GUILayer::RenderGUILayer ()
+{
+ PROFILER_AUTO_GFX(gGUILayerProfile, this);
+
+ ms_GUIElements->apply_delayed ();
+ if (ms_GUIElements->empty())
+ return;
+
+ typedef UNITY_TEMP_VECTOR(GUIElement*) TempElements;
+ TempElements elements (ms_GUIElements->begin (), ms_GUIElements->end ());
+
+ std::sort (elements.begin (), elements.end (), SortGUIByDepth);
+
+ Camera& camera = GetComponent(Camera);
+ UInt32 cullingMask = camera.GetCullingMask ();
+ Rectf cameraRect = camera.GetScreenViewportRect();
+
+ for (TempElements::iterator it=elements.begin(), itEnd = elements.end(); it != itEnd; ++it)
+ {
+ GUIElement& element = **it;
+ if (element.GetGameObject ().GetLayerMask () & cullingMask)
+ element.RenderGUIElement (cameraRect);
+ }
+}
+
+GUIElement* GUILayer::HitTest (const Vector2f& screenPosition)
+{
+ Camera& camera = GetComponent (Camera);
+ Vector3f viewportPos3D = camera.ScreenToViewportPoint (Vector3f (screenPosition.x, screenPosition.y, camera.GetNear()));
+ Vector2f viewportPos (viewportPos3D.x, viewportPos3D.y);
+ Rectf normalized (0.0F,0.0F,1.0F,1.0F);
+
+ if (!normalized.Contains (viewportPos.x, viewportPos.y))
+ return NULL;
+
+ Rectf cameraRect = camera.GetScreenViewportRect();
+
+ Rectf windowRect = GetRenderManager ().GetWindowRect ();
+ viewportPos.x *= windowRect.Width ();
+ viewportPos.y *= windowRect.Height ();
+
+ GUIElement* topmost = NULL;
+ float topmostZ = -std::numeric_limits<float>::infinity ();
+
+ // GUI hit testing always ignores IgnoreRaycast layer
+ UInt32 cullingMask = camera.GetCullingMask() & ~(kIgnoreRaycastMask);
+
+ for (GUIElements::iterator it=ms_GUIElements->begin (), itEnd = ms_GUIElements->end (); it != itEnd; ++it)
+ {
+ GUIElement* element = *it;
+ if (element && (element->GetGameObject ().GetLayerMask () & cullingMask) && element->HitTest (viewportPos, cameraRect))
+ {
+ float z = element->GetComponent (Transform).GetLocalPosition ().z;
+ if (z > topmostZ)
+ {
+ topmost = element;
+ topmostZ = z;
+ }
+ }
+ }
+
+ return topmost;
+}
+
+IMPLEMENT_CLASS_HAS_INIT(GUILayer)
diff --git a/Runtime/Camera/RenderLayers/GUILayer.h b/Runtime/Camera/RenderLayers/GUILayer.h
new file mode 100644
index 0000000..925b070
--- /dev/null
+++ b/Runtime/Camera/RenderLayers/GUILayer.h
@@ -0,0 +1,33 @@
+#ifndef GUILAYER_H
+#define GUILAYER_H
+
+#include "GUIElement.h"
+#include "Runtime/GameCode/Behaviour.h"
+#include "Runtime/Utilities/delayed_set.h"
+
+/// A GUI Layer is attached to the camera.
+/// It tracks all enabled GUIElements (eg. Text, GUITexture) and renders them
+class GUILayer : public Behaviour
+{
+public:
+ REGISTER_DERIVED_CLASS (GUILayer, Behaviour)
+
+ GUILayer (MemLabelId label, ObjectCreationMode mode);
+
+ void RenderGUILayer();
+ GUIElement* HitTest (const Vector2f& screenPosition);
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+ // Behaviour
+ virtual void AddToManager() { };
+ virtual void RemoveFromManager() { };
+
+private:
+ typedef delayed_set <PPtr<GUIElement> > GUIElements;
+ static GUIElements* ms_GUIElements;
+ friend class GUIElement;
+};
+
+#endif
diff --git a/Runtime/Camera/RenderLayers/GUIText.cpp b/Runtime/Camera/RenderLayers/GUIText.cpp
new file mode 100644
index 0000000..9602bdd
--- /dev/null
+++ b/Runtime/Camera/RenderLayers/GUIText.cpp
@@ -0,0 +1,303 @@
+#include "UnityPrefix.h"
+#include "GUIText.h"
+#include "Runtime/Filters/Misc/Font.h"
+#include "Runtime/Misc/ResourceManager.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/IMGUI/TextMeshGenerator2.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Filters/Mesh/Mesh.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Profiler/Profiler.h"
+
+static Font* gDefaultFont = NULL;
+
+GUIText::GUIText (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+ m_FontSize = 0;
+ m_FontStyle = 0;
+ m_RichText = true;
+ m_Color = 0xffffffff;
+}
+
+GUIText::~GUIText ()
+{
+}
+
+Font * GUIText::GetFont () const {
+ Font *f = m_Font;
+ if (!f) {
+ if (!gDefaultFont)
+ gDefaultFont = GetBuiltinResource<Font> (kDefaultFontName);
+ return gDefaultFont;
+ }
+ else {
+ return f;
+ }
+
+}
+
+PROFILER_INFORMATION(gRenderGUIText, "GUIText.Render", kProfilerGUI)
+PROFILER_INFORMATION(gSubmitVBOProfileGUIText, "Mesh.SubmitVBO", kProfilerRender)
+
+void GUIText::RenderGUIElement (const Rectf& cameraRect)
+{
+ if (m_Text.empty ())
+ return;
+
+ PROFILER_AUTO(gRenderGUIText, this)
+
+ pair<Font*, Material*> temp = GetFontAndMaterial ();
+ Font* font = temp.first;
+ Material* material = temp.second;
+ if (font == NULL || material == NULL)
+ return;
+
+ TextMeshGenerator2 &tmgen = TextMeshGenerator2::Get (UTF16String(m_Text.c_str()), font, (TextAnchor)m_Anchor, (TextAlignment)m_Alignment, 0, m_TabSize, m_LineSpacing, m_RichText, m_PixelCorrect, m_Color, m_FontSize, m_FontStyle);
+
+ GfxDevice& device = GetGfxDevice();
+ DeviceMVPMatricesState preserveMVP; // save MVP matrices, restore when returning from this function!
+
+ Vector2f size = tmgen.GetSize ();
+ Vector2f offset = tmgen.GetTextOffset (Rectf (0, 0, -size.x, size.y * 2));
+ switch (m_Alignment)
+ {
+ case kRight: offset.x += size.x; break;
+ case kCenter:
+ if (m_PixelCorrect)
+ offset.x += Roundf(size.x * 0.5f);
+ else
+ offset.x += size.x * 0.5f;
+ break;
+ }
+ Matrix4x4f textMatrix;
+ if (!m_PixelCorrect)
+ {
+ Matrix4x4f ortho;
+ ortho.SetOrtho( 0, 1, 0, 1, -1, 100 );
+ device.SetProjectionMatrix (ortho);
+
+ Transform& transform = GetComponent (Transform);
+ Vector3f position = transform.GetPosition ();
+ position.z = 0.0F;
+ Vector3f scale = transform.GetWorldScaleLossy ();
+ scale.x *= 0.05F * font->GetDeprecatedPixelScale ();
+ scale.y *= -0.05F * font->GetDeprecatedPixelScale ();
+ scale.z = 1.0F;
+
+ position.x += offset.x * scale.x;
+ position.y -= offset.y * scale.y;
+
+ textMatrix.SetTranslate( position );
+ textMatrix.Scale( scale );
+ }
+ else {
+ // Find out how large a rect the font should be rendered into, so that it is pixel correct. call the generator with this size.
+ Rectf rectNoOffset = cameraRect;
+ rectNoOffset.Move( -rectNoOffset.x, -rectNoOffset.y );
+ LoadPixelMatrix( rectNoOffset, device, true, false );
+
+ Transform& transform = GetComponent (Transform);
+ Vector3f position = transform.GetPosition ();
+ position.z = 0.0F;
+
+ textMatrix.SetTranslate( Vector3f( Roundf (position.x * cameraRect.Width() + m_PixelOffset.x), Roundf(position.y * cameraRect.Height() + m_PixelOffset.y), 0.0f ) );
+ textMatrix.Scale( Vector3f( 1.0F, -1.0F, 1.0f ) );
+
+ textMatrix.Translate( Vector3f( offset.x, -offset.y, 0.0f ) );
+ }
+ device.SetViewMatrix( textMatrix.GetPtr() );
+
+ int passCount = material->GetPassCount ();
+ for (int i=0;i < passCount ;i++)
+ {
+ const ChannelAssigns* channels = material->SetPass (i);
+ tmgen.RenderRaw (*channels);
+ }
+}
+
+
+void DrawGUIText (const std::string& text, Font* font, Material* material)
+{
+ if (text.empty())
+ return;
+ if (font == NULL || material == NULL)
+ return;
+
+ PROFILER_AUTO(gRenderGUIText, NULL)
+
+ TextMeshGenerator2 &tmgen = TextMeshGenerator2::Get (UTF16String(text.c_str()), font, kUpperLeft, kAuto, 0, 0, 1.0f, false, true, 0xffffffff, 0, 0);
+
+ static SHADERPROP (MainTex);
+ Texture* fontTexture = font->GetTexture();
+ // In the case when font is a custum font, GetTexture() will return NULL, so in this case take the main texture from font's material
+ if (fontTexture == NULL && font->GetMaterial())
+ {
+ fontTexture = font->GetMaterial()->GetTexture(kSLPropMainTex);
+ }
+ material->SetTexture(kSLPropMainTex, fontTexture);
+
+ int passCount = material->GetPassCount ();
+ for (int i=0;i < passCount ;i++)
+ {
+ const ChannelAssigns* channels = material->SetPass (i);
+ tmgen.RenderRaw (*channels);
+ }
+}
+
+Rectf GUIText::GetScreenRect (const Rectf& cameraRect)
+{
+ if (m_Text.empty ())
+ return Rectf();
+
+ Font* font = GetFontAndMaterial ().first;
+ if (font == NULL)
+ return Rectf();
+
+ TextMeshGenerator2 &tmgen = TextMeshGenerator2::Get (UTF16String(m_Text.c_str()), font, (TextAnchor)m_Anchor, (TextAlignment)m_Alignment, 0, m_TabSize, m_LineSpacing, m_RichText, m_PixelCorrect, m_Color, m_FontSize, m_FontStyle);
+ Vector2f size = tmgen.GetSize ();
+ Vector2f offset = tmgen.GetTextOffset (Rectf (0, 0, -size.x, size.y * 2));
+ Rectf rect (offset.x, -offset.y, size.x, size.y);
+
+ Transform& transform = GetComponent (Transform);
+ if (!m_PixelCorrect)
+ {
+ Vector3f position = transform.GetPosition ();
+ position.z = 0.0F;
+ Vector3f scale = transform.GetWorldScaleLossy ();
+ scale.x *= 0.05F * font->GetDeprecatedPixelScale ();
+ scale.y *= -0.05F * font->GetDeprecatedPixelScale ();
+ scale.z = 1.0F;
+
+ rect.Scale (scale.x, scale.y);
+ rect.Move (position.x, position.y);
+ Rectf windowRect = GetRenderManager ().GetWindowRect ();
+ rect.Scale (windowRect.Width (), windowRect.Height ());
+ }
+ else
+ {
+ Rectf windowRect = GetRenderManager ().GetWindowRect ();
+
+ Vector3f position = transform.GetPosition ();
+ position.x = Roundf (position.x * windowRect.Width() + m_PixelOffset.x);
+ position.y = Roundf (position.y * windowRect.Height() + m_PixelOffset.y);
+
+ Vector3f scale = Vector3f (1.0F ,-1.0F,1);
+
+ rect.Scale (scale.x, scale.y);
+ rect.Move (position.x, position.y);
+ }
+ if (rect.height < 0)
+ {
+ rect.height = -rect.height;
+ rect.y -= rect.height;
+ }
+ return rect;
+}
+
+pair<Font*, Material*> GUIText::GetFontAndMaterial ()
+{
+ Font* font = m_Font;
+ Material* material = m_Material;
+ if (font != NULL && material == NULL)
+ material = font->GetMaterial ();
+
+ // Use default resource instead!
+ if (font == NULL || material == NULL)
+ {
+ if (gDefaultFont == NULL)
+ {
+ gDefaultFont = GetBuiltinResource<Font> (kDefaultFontName);
+ if (!gDefaultFont)
+ {
+ LogString ("Couldn't load default font!");
+ return std::make_pair<Font*, Material*> (NULL, NULL);
+ }
+ if (!gDefaultFont->GetMaterial())
+ {
+ LogString ("Couldn't load default font material!");
+ return std::make_pair<Font*, Material*> (NULL, NULL);
+ }
+ }
+
+ if (font == NULL)
+ font = gDefaultFont;
+ if (material == NULL)
+ material = gDefaultFont->GetMaterial();
+ }
+
+ return std::make_pair (font, material);
+}
+
+void GUIText::Reset ()
+{
+ Super::Reset ();
+ m_PixelCorrect = true;
+ m_Anchor = kUpperLeft;
+ m_Alignment = kLeft;
+ m_LineSpacing = 1.0F;
+ m_TabSize = 4.0F;
+ m_PixelOffset = Vector2f(0,0);
+}
+
+Material* GUIText::GetMaterial ()
+{
+ return GetFontAndMaterial ().second;
+}
+
+void GUIText::SetMaterial (Material* material)
+{
+ m_Material = material;
+ SetDirty ();
+}
+
+void GUIText::SetFont (PPtr<Font> font)
+{
+ m_Font = font;
+ SetDirty();
+}
+
+
+template<class TransferFunction> inline
+void GUIText::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ transfer.SetVersion (3);
+ TRANSFER_SIMPLE(m_Text);
+ TRANSFER_SIMPLE(m_Anchor);
+ TRANSFER_SIMPLE(m_Alignment);
+ TRANSFER(m_PixelOffset);
+ TRANSFER(m_LineSpacing);
+ TRANSFER(m_TabSize);
+ TRANSFER(m_Font);
+ TRANSFER(m_Material);
+ TRANSFER(m_FontSize);
+ TRANSFER(m_FontStyle);
+ TRANSFER(m_Color);
+
+ TRANSFER(m_PixelCorrect);
+ TRANSFER(m_RichText);
+
+ #if UNITY_EDITOR
+ // Explanation: in verson 1.2.2 we added pixel correct drawing. By default it is on.
+ // for backwards compatbility we disable it
+ if(transfer.IsOldVersion(1))
+ m_PixelCorrect = false;
+
+ // In version 1.5.0 line spacing is multiplicative instead of additive
+ if(transfer.IsOldVersion(1) || transfer.IsOldVersion(2))
+ {
+ Font* font = GetFont();
+ m_LineSpacing = (font->GetLineSpacing() + m_LineSpacing) / font->GetLineSpacing();
+ }
+ #endif
+}
+
+IMPLEMENT_CLASS (GUIText)
+IMPLEMENT_OBJECT_SERIALIZE (GUIText)
diff --git a/Runtime/Camera/RenderLayers/GUIText.h b/Runtime/Camera/RenderLayers/GUIText.h
new file mode 100644
index 0000000..fdfeeb9
--- /dev/null
+++ b/Runtime/Camera/RenderLayers/GUIText.h
@@ -0,0 +1,93 @@
+#ifndef GUITEXT_H
+#define GUITEXT_H
+
+#include <string>
+#include "GUIElement.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Vector2.h"
+
+class Font;
+namespace Unity { class Material; }
+
+/// Can be Attached to any game object in the scene.
+/// Registers with GUILayer, GUILayer renders it.
+/// Position comes from transform.position.x,y
+/// size comes from transform.scale.x,y
+class GUIText : public GUIElement
+{
+ public:
+
+ REGISTER_DERIVED_CLASS (GUIText, GUIElement)
+ DECLARE_OBJECT_SERIALIZE (GUIText)
+
+ GUIText (MemLabelId label, ObjectCreationMode mode);
+ virtual void Reset ();
+
+ const UnityStr& GetText () const { return m_Text; }
+ void SetText (const std::string& text) { m_Text = text; }
+
+ // GUIElement
+ virtual void RenderGUIElement (const Rectf& cameraRect);
+ virtual Rectf GetScreenRect (const Rectf& cameraRect);
+
+ void SetFont (PPtr<Font> font);
+ Font * GetFont () const;
+
+ Material* GetMaterial ();
+ void SetMaterial (Material* material);
+
+ void SetPixelOffset (const Vector2f& offset) { m_PixelOffset = offset; SetDirty(); }
+ Vector2f GetPixelOffset () { return m_PixelOffset; }
+
+ void SetLineSpacing (float space) { m_LineSpacing = space; SetDirty(); }
+ float GetLineSpacing() const { return m_LineSpacing; }
+
+ void SetTabSize (float size) { m_TabSize = size; SetDirty(); }
+ float GetTabSize() const { return m_TabSize; }
+
+ void SetAlignment (int align) { m_Alignment = align; SetDirty(); }
+ int GetAlignment() const { return m_Alignment; }
+
+ void SetAnchor (int size) { m_Anchor = size; SetDirty(); }
+ int GetAnchor() const { return m_Anchor; }
+
+ void SetFontSize (int size) { m_FontSize = size; SetDirty(); }
+ int GetFontSize() const { return m_FontSize; }
+
+ void SetFontStyle (int style) { m_FontStyle = style; SetDirty(); }
+ int GetFontStyle() const { return m_FontStyle; }
+
+ void SetRichText (bool richText) { m_RichText = richText; SetDirty(); }
+ bool GetRichText() const { return m_RichText; }
+
+ void SetColor (ColorRGBA32 color) { m_Color = color; SetDirty(); }
+ ColorRGBA32 GetColor() const { return m_Color; }
+
+ private:
+
+ std::pair<Font*, Material*> GetFontAndMaterial ();
+
+ UnityStr m_Text;
+
+ short m_Alignment; ///< enum { left, center, right }
+ short m_Anchor; ///< Where the text-mesh is anchored related to local origo. enum { upper left, upper center, upper right, middle left, middle center, middle right, lower left, lower center, lower right }
+
+ float m_LineSpacing; ///< Spacing between lines as multiplum of height of a character.
+ float m_TabSize; ///< Length of one tab
+ bool m_PixelCorrect; ///< Place & scale the text to a pixel-correct position.
+ bool m_RichText;///< Enable HTML-style tags for text formatting.
+ Vector2f m_PixelOffset;
+
+ int m_FontSize; ///<The font size to use. Set to 0 to use default font size. Only applicable for dynamic fonts.
+ int m_FontStyle; ///<The font style to use. Only applicable for dynamic fonts. enum { Normal, Bold, Italic, Bold and Italic }
+
+ ColorRGBA32 m_Color;
+
+ PPtr<Font> m_Font;
+ PPtr<Material> m_Material;
+};
+
+
+void DrawGUIText (const std::string& text, Font* font, Material* material);
+
+#endif
diff --git a/Runtime/Camera/RenderLayers/GUITexture.cpp b/Runtime/Camera/RenderLayers/GUITexture.cpp
new file mode 100644
index 0000000..b30d7e4
--- /dev/null
+++ b/Runtime/Camera/RenderLayers/GUITexture.cpp
@@ -0,0 +1,524 @@
+#include "UnityPrefix.h"
+#include "GUITexture.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Shaders/Material.h"
+#include "External/shaderlab/Library/properties.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Misc/ResourceManager.h"
+
+extern bool IsNPOTTextureAllowed(bool hasMipMap);
+
+static SHADERPROP (MainTex);
+static Shader* gGUI2DShader = NULL;
+static Material* gGUI2DMaterial = NULL;
+
+
+// can't do in InitializeClass because graphics might not be created yet
+static void InitializeGUIShaders()
+{
+ if( !gGUI2DMaterial )
+ {
+ Shader* shader = GetBuiltinResource<Shader> ("Internal-GUITexture.shader");
+ gGUI2DMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ gGUI2DShader = gGUI2DMaterial->GetShader ();
+ }
+}
+
+void GUITexture::InitializeClass ()
+{
+}
+
+void GUITexture::CleanupClass ()
+{
+ gGUI2DShader = NULL;
+ gGUI2DMaterial = NULL;
+}
+
+GUITexture::GUITexture (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+ m_Sheet = NULL;
+ m_PrevTextureWidth = 0;
+ m_PrevTextureHeight = 0;
+ m_PrevTextureBaseLevel = Texture::GetMasterTextureLimit();
+}
+
+GUITexture::~GUITexture ()
+{
+ SAFE_RELEASE_LABEL(m_Sheet, kMemShader);
+}
+
+void GUITexture::BuildSheet()
+{
+ InitializeGUIShaders();
+
+ Texture* texture = m_Texture;
+ if( !texture )
+ return;
+
+ SAFE_RELEASE_LABEL(m_Sheet, kMemShader);
+ bool is2D = (texture->GetDimension () == kTexDim2D);
+ m_Sheet = gGUI2DShader->MakeProperties();
+ m_Sheet->SetTexture (kSLPropMainTex, texture);
+
+ ShaderLab::PropertySheet::TextureProperty* prop = m_Sheet->GetTextureProperty (kSLPropMainTex);
+ if (prop && prop->scaleOffsetValue)
+ {
+ // for NPOT textures (in case it is not supported) override the GL texture name and texture scale
+ // so that we use unscaled texture and ignore the padded dummy portion
+ bool isNPOT = !IsPowerOfTwo(m_PrevTextureWidth) || !IsPowerOfTwo(m_PrevTextureHeight);
+ if( is2D && isNPOT && !IsNPOTTextureAllowed(texture->HasMipMap()) )
+ {
+ // Need to take master texture limit into account because that
+ // changes scaling...
+ int baseMipLevel = Texture::GetMasterTextureLimit();
+ if( !texture->HasMipMap() )
+ baseMipLevel = 0;
+ int texWidth = texture->GetDataWidth() >> baseMipLevel;
+ int texHeight = texture->GetDataHeight() >> baseMipLevel;
+ int actualWidth = texture->GetGLWidth() >> baseMipLevel;
+ int actualHeight = texture->GetGLHeight() >> baseMipLevel;
+ // ...and shifting above might produce zeros
+ float scaleX = (actualWidth > 0) ? float(texWidth) / float(actualWidth) : 1.0f;
+ float scaleY = (actualHeight > 0) ? float(texHeight) / float(actualHeight) : 1.0f;
+ scaleX *= texture->GetUVScaleX();
+ scaleY *= texture->GetUVScaleY();
+ prop->texEnv->OverrideTextureInfo( texture->GetUnscaledTextureID(), scaleX, scaleY );
+ prop->scaleOffsetValue->Set (scaleX, scaleY, 0,0);
+ }
+ else
+ {
+ prop->scaleOffsetValue->Set (1,1,0,0);
+ }
+ }
+}
+
+void GUITexture::AwakeFromLoad (AwakeFromLoadMode awakeMode) {
+ Super::AwakeFromLoad (awakeMode);
+ BuildSheet ();
+}
+
+void GUITexture::SetTexture (Texture* tex)
+{
+ m_Texture = tex;
+ if( tex )
+ {
+ m_PrevTextureWidth = tex->GetDataWidth();
+ m_PrevTextureHeight = tex->GetDataHeight();
+ }
+ m_PrevTextureBaseLevel = Texture::GetMasterTextureLimit();
+ if( tex && !tex->HasMipMap() )
+ m_PrevTextureBaseLevel = 0;
+ BuildSheet ();
+}
+
+Texture* GUITexture::GetTexture ()
+{
+ return m_Texture;
+}
+
+void GUITexture::Reset ()
+{
+ Super::Reset ();
+ m_Color = ColorRGBAf (.5, .5, .5, .5);
+ m_LeftBorder = m_RightBorder = m_TopBorder = m_BottomBorder = 0;
+ m_PixelInset = Rectf (0,0,0,0);
+}
+
+void GUITexture::SetColor (const ColorRGBAf& color) {
+ m_Color = color;
+ BuildSheet ();
+ SetDirty ();
+}
+
+struct GUITextureVertex {
+ Vector3f vert;
+ ColorRGBA32 color;
+ Vector2f uv;
+};
+
+static bool FillGUITextureVBOChunk( const Rectf &screenRect, Texture* tex, const Rectf &sourceRect, int leftBorder, int rightBorder, int topBorder, int bottomBorder, ColorRGBA32 color )
+{
+ DebugAssertIf( !tex );
+ Vector2f texScale (1.0f / (float)tex->GetDataWidth(), 1.0f / (float)tex->GetDataHeight());
+
+ float x0 = RoundfToInt(screenRect.x);
+ float x3 = RoundfToInt(screenRect.GetRight());
+
+ int y0 = RoundfToInt(screenRect.y);
+ int y3 = RoundfToInt(screenRect.GetBottom());
+
+ float x1 = x0 + (float)leftBorder;
+ float x2 = x3 - (float)rightBorder;
+ int y1 = int(y0 + (float)bottomBorder);
+ int y2 = int(y3 - (float)topBorder);
+
+ float tx0 = sourceRect.x;
+ float tx1 = tx0 + texScale.x * (float)leftBorder;
+ float tx3 = sourceRect.GetRight();
+ float tx2 = tx3 - texScale.x * (float)rightBorder;
+
+ float ty0 = sourceRect.y;
+ float ty1 = ty0 + texScale.y * (float)bottomBorder;
+ float ty3 = sourceRect.GetBottom();
+ float ty2 = ty3 - texScale.y * (float)topBorder;
+
+ GUITextureVertex* vbPtr;
+ unsigned short* ibPtr;
+ DynamicVBO& vbo = GetGfxDevice().GetDynamicVBO();
+ if( !vbo.GetChunk(
+ (1<<kShaderChannelVertex) | (1<<kShaderChannelColor) | (1<<kShaderChannelTexCoord0),
+ 16, // 16 vertices
+ 9*6, // 9 quads
+ DynamicVBO::kDrawIndexedTriangles,
+ (void**)&vbPtr, (void**)&ibPtr ) )
+ {
+ return false;
+ }
+
+ vbPtr[ 0].vert.Set( x0, y0, 0.0f ); vbPtr[ 0].color = color; vbPtr[ 0].uv.Set( tx0, ty0 );
+ vbPtr[ 1].vert.Set( x1, y0, 0.0f ); vbPtr[ 1].color = color; vbPtr[ 1].uv.Set( tx1, ty0 );
+ vbPtr[ 2].vert.Set( x2, y0, 0.0f ); vbPtr[ 2].color = color; vbPtr[ 2].uv.Set( tx2, ty0 );
+ vbPtr[ 3].vert.Set( x3, y0, 0.0f ); vbPtr[ 3].color = color; vbPtr[ 3].uv.Set( tx3, ty0 );
+ vbPtr[ 4].vert.Set( x0, y1, 0.0f ); vbPtr[ 4].color = color; vbPtr[ 4].uv.Set( tx0, ty1 );
+ vbPtr[ 5].vert.Set( x1, y1, 0.0f ); vbPtr[ 5].color = color; vbPtr[ 5].uv.Set( tx1, ty1 );
+ vbPtr[ 6].vert.Set( x2, y1, 0.0f ); vbPtr[ 6].color = color; vbPtr[ 6].uv.Set( tx2, ty1 );
+ vbPtr[ 7].vert.Set( x3, y1, 0.0f ); vbPtr[ 7].color = color; vbPtr[ 7].uv.Set( tx3, ty1 );
+ vbPtr[ 8].vert.Set( x0, y2, 0.0f ); vbPtr[ 8].color = color; vbPtr[ 8].uv.Set( tx0, ty2 );
+ vbPtr[ 9].vert.Set( x1, y2, 0.0f ); vbPtr[ 9].color = color; vbPtr[ 9].uv.Set( tx1, ty2 );
+ vbPtr[10].vert.Set( x2, y2, 0.0f ); vbPtr[10].color = color; vbPtr[10].uv.Set( tx2, ty2 );
+ vbPtr[11].vert.Set( x3, y2, 0.0f ); vbPtr[11].color = color; vbPtr[11].uv.Set( tx3, ty2 );
+ vbPtr[12].vert.Set( x0, y3, 0.0f ); vbPtr[12].color = color; vbPtr[12].uv.Set( tx0, ty3 );
+ vbPtr[13].vert.Set( x1, y3, 0.0f ); vbPtr[13].color = color; vbPtr[13].uv.Set( tx1, ty3 );
+ vbPtr[14].vert.Set( x2, y3, 0.0f ); vbPtr[14].color = color; vbPtr[14].uv.Set( tx2, ty3 );
+ vbPtr[15].vert.Set( x3, y3, 0.0f ); vbPtr[15].color = color; vbPtr[15].uv.Set( tx3, ty3 );
+
+ static UInt16 ib[9*6] = {
+ 0, 4, 1, 1, 4, 5,
+ 1, 5, 2, 2, 5, 6,
+ 2, 6, 3, 3, 6, 7,
+ 4, 8, 5, 5, 8, 9,
+ 5, 9, 6, 6, 9, 10,
+ 6, 10, 7, 7, 10, 11,
+ 8, 12, 9, 9, 12, 13,
+ 9, 13, 10, 10, 13, 14,
+ 10, 14, 11, 11, 14, 15,
+ };
+ memcpy( ibPtr, ib, sizeof(ib) );
+ vbo.ReleaseChunk( 16, 9*6 );
+
+ return true;
+}
+
+PROFILER_INFORMATION(gRenderGUITexture, "GUITexture.Render", kProfilerGUI)
+PROFILER_INFORMATION(gSubmitVBOProfileGUITexture, "Mesh.SubmitVBO", kProfilerRender)
+
+void GUITexture::DrawGUITexture (const Rectf &bounds)
+{
+ PROFILER_AUTO(gRenderGUITexture, NULL)
+
+ InitializeGUIShaders();
+
+ Shader* shader = gGUI2DShader;
+
+ GfxDevice& device = GetGfxDevice();
+ DynamicVBO& vbo = device.GetDynamicVBO();
+
+ ColorRGBA32 color = m_Color;
+ color = device.ConvertToDeviceVertexColor(color);
+
+ if( !FillGUITextureVBOChunk( bounds, m_Texture, Rectf(0,0,1,1), m_LeftBorder, m_RightBorder, m_TopBorder, m_BottomBorder, color ) )
+ return;
+ const ShaderLab::SubShader& ss = shader->GetShaderLabShader()->GetActiveSubShader();
+ const int passCount = ss.GetValidPassCount();
+ for( int i = 0; i != passCount; ++i )
+ {
+ const ChannelAssigns* channels = shader->SetPass (0, i, 0, m_Sheet);
+
+ PROFILER_BEGIN(gSubmitVBOProfileGUITexture, this)
+ vbo.DrawChunk (*channels);
+ GPU_TIMESTAMP();
+ PROFILER_END
+ }
+}
+
+void GUITexture::RenderGUIElement (const Rectf& cameraRect)
+{
+ Texture* tex = m_Texture;
+ if( !tex )
+ return;
+
+ // Before rendering check whether something serious has changed:
+ // * texture could have changed from POT to NPOT, or the size may have changed (at least in the editor)
+ // * global texture limit might have changed
+ int texWidth = tex->GetDataWidth();
+ int texHeight = tex->GetDataHeight();
+ int masterTexLimit = Texture::GetMasterTextureLimit();
+ if( !tex->HasMipMap() )
+ masterTexLimit = 0;
+ if( texWidth != m_PrevTextureWidth || texHeight != m_PrevTextureHeight || m_PrevTextureBaseLevel != masterTexLimit )
+ {
+ m_PrevTextureWidth = texWidth;
+ m_PrevTextureHeight = texHeight;
+ m_PrevTextureBaseLevel = masterTexLimit;
+ BuildSheet();
+ }
+
+ GfxDevice& device = GetGfxDevice();
+ DeviceMVPMatricesState preserveMVP;
+
+ Rectf rectNoOffset = cameraRect;
+ rectNoOffset.Move( -rectNoOffset.x, -rectNoOffset.y );
+ LoadPixelMatrix( rectNoOffset, device, true, false );
+
+ Rectf drawBox = CalculateDrawBox (cameraRect);
+ DrawGUITexture (drawBox);
+}
+
+Rectf GUITexture::CalculateDrawBox (const Rectf& screenViewportRect)
+{
+ Transform& transform = GetComponent (Transform);
+ Rectf drawBox;
+
+ Vector3f position = transform.GetPosition ();
+ Vector3f scale = transform.GetWorldScaleLossy ();
+
+ float xmin = position.x - scale.x * 0.5F;
+ float xmax = position.x + scale.x * 0.5F;
+
+ float ymin = position.y - scale.y * 0.5F;
+ float ymax = position.y + scale.y * 0.5F;
+
+ drawBox.x = screenViewportRect.Width() * xmin + m_PixelInset.x;
+ drawBox.SetRight( screenViewportRect.Width() * xmax + m_PixelInset.GetRight() );
+ drawBox.y = screenViewportRect.Height() * ymin + m_PixelInset.y;
+ drawBox.SetBottom( screenViewportRect.Height() * ymax + m_PixelInset.GetBottom() );
+
+ return drawBox;
+}
+
+Rectf GUITexture::GetScreenRect (const Rectf& cameraRect)
+{
+ return CalculateDrawBox (cameraRect);
+}
+
+template<class TransferFunction>
+void GUITexture::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ TRANSFER_SIMPLE (m_Texture);
+ TRANSFER_SIMPLE (m_Color);
+
+ TRANSFER (m_PixelInset);
+
+ TRANSFER (m_LeftBorder);
+ TRANSFER (m_RightBorder);
+ TRANSFER (m_TopBorder);
+ TRANSFER (m_BottomBorder);
+}
+
+IMPLEMENT_CLASS_HAS_INIT (GUITexture)
+IMPLEMENT_OBJECT_SERIALIZE (GUITexture)
+
+void DrawGUITexture (const Rectf &screenRect, Texture* texture, ColorRGBA32 color, Material* material) {
+ DrawGUITexture (screenRect, texture, 0,0,0,0, color, material);
+}
+void DrawGUITexture (const Rectf &screenRect, Texture* texture, int leftBorder, int rightBorder, int topBorder, int bottomBorder, ColorRGBA32 color, Material* material) {
+ DrawGUITexture (screenRect, texture, Rectf (0,0,1,1), leftBorder, rightBorder, topBorder, bottomBorder, color, material);
+}
+
+void HandleGUITextureProps( ShaderLab::PropertySheet *sheet, Texture *texture )
+{
+ sheet->SetTexture (kSLPropMainTex, texture);
+
+ int texWidth = texture->GetDataWidth();
+ int texHeight = texture->GetDataHeight();
+
+ ShaderLab::PropertySheet::TextureProperty* prop = sheet->GetTextureProperty (kSLPropMainTex);
+ if (prop && prop->scaleOffsetValue)
+ {
+ float uvScaleX = texture->GetUVScaleX();
+ float uvScaleY = texture->GetUVScaleY();
+
+ // for NPOT textures (in case it is not supported) override the GL texture name and texture scale
+ // so that we use unscaled texture and ignore the padded dummy portion
+ bool isNPOT = !IsPowerOfTwo(texWidth) || !IsPowerOfTwo(texHeight);
+ if( texture->GetDimension() == kTexDim2D && isNPOT && !IsNPOTTextureAllowed(texture->HasMipMap()) )
+ {
+ // Need to take master texture limit into account because that
+ // changes scaling...
+ int baseMipLevel = Texture::GetMasterTextureLimit();
+ if (!texture->HasMipMap())
+ baseMipLevel = 0;
+ texWidth = texWidth >> baseMipLevel;
+ texHeight = texHeight >> baseMipLevel;
+ int actualWidth = texture->GetGLWidth() >> baseMipLevel;
+ int actualHeight = texture->GetGLHeight() >> baseMipLevel;
+ // ...and shifting above might produce zeros
+ float scaleX = (actualWidth > 0) ? float(texWidth) / float(actualWidth) : 1.0f;
+ float scaleY = (actualHeight > 0) ? float(texHeight) / float(actualHeight) : 1.0f;
+ scaleX *= uvScaleX;
+ scaleY *= uvScaleY;
+ prop->texEnv->OverrideTextureInfo( texture->GetUnscaledTextureID(), scaleX, scaleY );
+ prop->scaleOffsetValue->Set (scaleX, scaleY, 0, 0);
+ }
+ else
+ {
+ prop->scaleOffsetValue->Set (uvScaleX,uvScaleY,0,0);
+ }
+ }
+}
+
+// Fills out the VBO with the new inverted-coordinate GUI rectangle
+
+static bool FillGUITextureVBOChunkInverted( const Rectf &screenRect, Texture* tex, const Rectf &sourceRect, int leftBorder, int rightBorder, int topBorder, int bottomBorder, ColorRGBA32 color, UInt32* outTriangles )
+{
+ DebugAssertIf( !tex );
+ Vector2f texScale (1.0f / (float)tex->GetDataWidth(), 1.0f / (float)tex->GetDataHeight());
+
+ float x0 = RoundfToInt(screenRect.x);
+ float x3 = RoundfToInt(screenRect.GetRight());
+ float y0 = RoundfToInt(screenRect.GetBottom());
+ float y3 = RoundfToInt(screenRect.y);
+
+ float tx0 = sourceRect.x;
+ float tx3 = sourceRect.GetRight();
+ float ty0 = sourceRect.y;
+ float ty3 = sourceRect.GetBottom();
+
+ GUITextureVertex* vbPtr;
+ unsigned short* ibPtr;
+ DynamicVBO& vbo = GetGfxDevice().GetDynamicVBO();
+
+ if ((leftBorder|rightBorder|topBorder|bottomBorder) == 0)
+ {
+ // no borders, texture is just a quad
+ if( !vbo.GetChunk(
+ (1<<kShaderChannelVertex) | (1<<kShaderChannelColor) | (1<<kShaderChannelTexCoord0),
+ 4, // 4 vertices
+ 6, // 1 quad
+ DynamicVBO::kDrawIndexedTriangles,
+ (void**)&vbPtr, (void**)&ibPtr ) )
+ {
+ return false;
+ }
+
+ vbPtr[ 0].vert.Set( x0, y0, 0.0f ); vbPtr[ 0].color = color; vbPtr[ 0].uv.Set( tx0, ty0 );
+ vbPtr[ 1].vert.Set( x3, y0, 0.0f ); vbPtr[ 1].color = color; vbPtr[ 1].uv.Set( tx3, ty0 );
+ vbPtr[ 2].vert.Set( x0, y3, 0.0f ); vbPtr[ 2].color = color; vbPtr[ 2].uv.Set( tx0, ty3 );
+ vbPtr[ 3].vert.Set( x3, y3, 0.0f ); vbPtr[ 3].color = color; vbPtr[ 3].uv.Set( tx3, ty3 );
+ static UInt16 ib[1*6] = {
+ 0, 2, 1, 1, 2, 3,
+ };
+ memcpy( ibPtr, ib, sizeof(ib) );
+ vbo.ReleaseChunk( 4, 6 );
+ *outTriangles = 2;
+ }
+ else
+ {
+ // with borders, texture is a 3x3 quad grid
+ float x1 = x0 + (float)leftBorder;
+ float x2 = x3 - (float)rightBorder;
+ float y1 = y0 - (float)topBorder;
+ float y2 = y3 + (float)bottomBorder;
+
+ float tx1 = tx0 + texScale.x * (float)leftBorder;
+ float tx2 = tx3 - texScale.x * (float)rightBorder;
+ float ty1 = ty0 + texScale.y * (float)topBorder;
+ float ty2 = ty3 - texScale.y * (float)bottomBorder;
+
+ if( !vbo.GetChunk(
+ (1<<kShaderChannelVertex) | (1<<kShaderChannelColor) | (1<<kShaderChannelTexCoord0),
+ 16, // 16 vertices
+ 9*6, // 9 quads
+ DynamicVBO::kDrawIndexedTriangles,
+ (void**)&vbPtr, (void**)&ibPtr ) )
+ {
+ return false;
+ }
+
+ vbPtr[ 0].vert.Set( x0, y0, 0.0f ); vbPtr[ 0].color = color; vbPtr[ 0].uv.Set( tx0, ty0 );
+ vbPtr[ 1].vert.Set( x1, y0, 0.0f ); vbPtr[ 1].color = color; vbPtr[ 1].uv.Set( tx1, ty0 );
+ vbPtr[ 2].vert.Set( x2, y0, 0.0f ); vbPtr[ 2].color = color; vbPtr[ 2].uv.Set( tx2, ty0 );
+ vbPtr[ 3].vert.Set( x3, y0, 0.0f ); vbPtr[ 3].color = color; vbPtr[ 3].uv.Set( tx3, ty0 );
+ vbPtr[ 4].vert.Set( x0, y1, 0.0f ); vbPtr[ 4].color = color; vbPtr[ 4].uv.Set( tx0, ty1 );
+ vbPtr[ 5].vert.Set( x1, y1, 0.0f ); vbPtr[ 5].color = color; vbPtr[ 5].uv.Set( tx1, ty1 );
+ vbPtr[ 6].vert.Set( x2, y1, 0.0f ); vbPtr[ 6].color = color; vbPtr[ 6].uv.Set( tx2, ty1 );
+ vbPtr[ 7].vert.Set( x3, y1, 0.0f ); vbPtr[ 7].color = color; vbPtr[ 7].uv.Set( tx3, ty1 );
+ vbPtr[ 8].vert.Set( x0, y2, 0.0f ); vbPtr[ 8].color = color; vbPtr[ 8].uv.Set( tx0, ty2 );
+ vbPtr[ 9].vert.Set( x1, y2, 0.0f ); vbPtr[ 9].color = color; vbPtr[ 9].uv.Set( tx1, ty2 );
+ vbPtr[10].vert.Set( x2, y2, 0.0f ); vbPtr[10].color = color; vbPtr[10].uv.Set( tx2, ty2 );
+ vbPtr[11].vert.Set( x3, y2, 0.0f ); vbPtr[11].color = color; vbPtr[11].uv.Set( tx3, ty2 );
+ vbPtr[12].vert.Set( x0, y3, 0.0f ); vbPtr[12].color = color; vbPtr[12].uv.Set( tx0, ty3 );
+ vbPtr[13].vert.Set( x1, y3, 0.0f ); vbPtr[13].color = color; vbPtr[13].uv.Set( tx1, ty3 );
+ vbPtr[14].vert.Set( x2, y3, 0.0f ); vbPtr[14].color = color; vbPtr[14].uv.Set( tx2, ty3 );
+ vbPtr[15].vert.Set( x3, y3, 0.0f ); vbPtr[15].color = color; vbPtr[15].uv.Set( tx3, ty3 );
+ static UInt16 ib[9*6] = {
+ 0, 4, 1, 1, 4, 5, // Top-left
+ 1, 5, 2, 2, 5, 6, // Top-mid
+ 2, 6, 3, 3, 6, 7, // Top-right
+ 4, 8, 5, 5, 8, 9, // mid-left
+ 5, 9, 6, 6, 9, 10, // mid-center
+ 6, 10, 7, 7, 10, 11, // mid-right
+ 8, 12, 9, 9, 12, 13, // bottom-left
+ 9, 13, 10, 10, 13, 14, // bottom-mid
+ 10, 14, 11, 11, 14, 15, // bottom-right
+ };
+ memcpy( ibPtr, ib, sizeof(ib) );
+ vbo.ReleaseChunk( 16, 9*6 );
+ *outTriangles = 9 * 2;
+ }
+
+ return true;
+}
+
+void DrawGUITexture (const Rectf &screenRect, Texture* tex, const Rectf &sourceRect, int leftBorder, int rightBorder, int topBorder, int bottomBorder, ColorRGBA32 color, Material* material)
+{
+ InitializeGUIShaders();
+
+ if( !tex )
+ {
+ ErrorString ("DrawGUITexture: texture is null");
+ return;
+ }
+
+ GfxDevice& device = GetGfxDevice();
+ color = device.ConvertToDeviceVertexColor( color );
+ UInt32 triCount;
+
+ // temp workaround to flip Y-axis. should probably be done in FillGUITextureVBOChunk - but for now we do it here
+ if( !FillGUITextureVBOChunkInverted( screenRect, tex, sourceRect, leftBorder, rightBorder, bottomBorder, topBorder, color, &triCount ) )
+ return;
+
+ if (material)
+ {
+ HandleGUITextureProps( &material->GetWritableProperties(), tex );
+ }
+ else
+ {
+ HandleGUITextureProps( &gGUI2DMaterial->GetWritableProperties(), tex );
+ material = gGUI2DMaterial;
+ }
+
+ int passCount = material->GetPassCount();
+
+ DynamicVBO& vbo = device.GetDynamicVBO();
+
+ for( int i = 0; i < passCount; ++i)
+ {
+ const ChannelAssigns* channels = material->SetPass (i, 0, false);
+
+ PROFILER_BEGIN(gSubmitVBOProfileGUITexture, NULL);
+ vbo.DrawChunk (*channels);
+ GPU_TIMESTAMP();
+ PROFILER_END
+ }
+}
diff --git a/Runtime/Camera/RenderLayers/GUITexture.h b/Runtime/Camera/RenderLayers/GUITexture.h
new file mode 100644
index 0000000..55988ae
--- /dev/null
+++ b/Runtime/Camera/RenderLayers/GUITexture.h
@@ -0,0 +1,73 @@
+#ifndef GUITEXTURE_H
+#define GUITEXTURE_H
+
+#include "GUIElement.h"
+#include "Runtime/Graphics/Texture.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Rect.h"
+
+
+namespace Unity { class Material; }
+namespace ShaderLab { class PropertySheet; }
+
+// Attached to any game object in the scene.
+// Registers with GUILayer, GUILayer renders it.
+// Position comes from transform.position.x,y
+// size comes from transform.scale.x,y
+class GUITexture : public GUIElement
+{
+public:
+
+ REGISTER_DERIVED_CLASS (GUITexture, GUIElement)
+ DECLARE_OBJECT_SERIALIZE (GUITexture)
+
+ GUITexture (MemLabelId label, ObjectCreationMode mode);
+
+ virtual void Reset ();
+
+ // GUIElement
+ virtual void RenderGUIElement (const Rectf& cameraRect);
+ virtual Rectf GetScreenRect (const Rectf& cameraRect);
+
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+
+ void SetColor (const ColorRGBAf& color);
+ inline ColorRGBAf GetColor () { return m_Color; }
+
+ void SetTexture (Texture* tex);
+ Texture* GetTexture ();
+
+ int m_LeftBorder; ///< The border pixels - this part of texture is never scaled.
+ int m_RightBorder; ///< The border pixels - this part of texture is never scaled.
+ int m_TopBorder; ///< The border pixels - this part of texture is never scaled.
+ int m_BottomBorder; ///< The border pixels - this part of texture is never scaled.
+
+ PPtr<Texture> m_Texture; ///< The texture to use.
+ ColorRGBAf m_Color; ///< Tint color.
+
+ static void InitializeClass();
+ static void CleanupClass();
+
+ const Rectf &GetPixelInset() const { return m_PixelInset; }
+ void SetPixelInset(const Rectf &r) { m_PixelInset = r; SetDirty(); }
+
+private:
+ void DrawGUITexture (const Rectf& bounds);
+ void BuildSheet ();
+ Rectf CalculateDrawBox (const Rectf& screenViewportRect);
+
+ Rectf m_PixelInset;
+
+ ShaderLab::PropertySheet *m_Sheet;
+ int m_PrevTextureWidth;
+ int m_PrevTextureHeight;
+ int m_PrevTextureBaseLevel;
+};
+
+// Immediate mode DrawGUITexture API
+void DrawGUITexture (const Rectf &screenRect, Texture* texture, const Rectf &sourceRect, int leftBorder, int rightBorder, int topBorder, int bottomBorder, ColorRGBA32 color, Material* material = NULL);
+void DrawGUITexture (const Rectf &screenRect, Texture* texture, int leftBorder, int rightBorder, int topBorder, int bottomBorder, ColorRGBA32 color, Material* material = NULL);
+void DrawGUITexture (const Rectf &screenRect, Texture* texture, ColorRGBA32 color, Material* material = NULL);
+void HandleGUITextureProps (ShaderLab::PropertySheet *sheet, Texture *texture);
+
+#endif
diff --git a/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.cpp b/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.cpp
new file mode 100644
index 0000000..99e5d22
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.cpp
@@ -0,0 +1,19 @@
+#include "UnityPrefix.h"
+#include "BuiltinShaderParamUtility.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/ShaderKeywords.h"
+
+static ShaderKeyword gSupportedLODFadeKeyword = keywords::Create("ENABLE_LOD_FADE");
+
+void SetObjectScale (GfxDevice& device, float lodFade, float invScale)
+{
+ device.SetInverseScale(invScale);
+
+ /////@TODO: Figure out why inverse scale is implemented in gfxdevice, and decide if we should do the same for lodFade?
+ device.GetBuiltinParamValues().SetInstanceVectorParam(kShaderInstanceVecScale, Vector4f(0,0,lodFade, invScale));
+
+ if (lodFade == LOD_FADE_DISABLED)
+ g_ShaderKeywords.Disable(gSupportedLODFadeKeyword);
+ else
+ g_ShaderKeywords.Enable(gSupportedLODFadeKeyword);
+}
diff --git a/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h b/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h
new file mode 100644
index 0000000..7b480ed
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/BuiltinShaderParamUtility.h
@@ -0,0 +1,11 @@
+#pragma once
+
+
+///@TODO: This should probably be 0. But for now we don't have proper ifdef support for switching to a different subshader.
+#define LOD_FADE_DISABLED 0.999F
+
+#define LOD_FADE_BATCH_EPSILON 0.0625 // 1/16
+
+class GfxDevice;
+
+void SetObjectScale (GfxDevice& device, float lodFade, float invScale);
diff --git a/Runtime/Camera/RenderLoops/ForwardShaderRenderLoop.cpp b/Runtime/Camera/RenderLoops/ForwardShaderRenderLoop.cpp
new file mode 100644
index 0000000..44e91b8
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/ForwardShaderRenderLoop.cpp
@@ -0,0 +1,1403 @@
+#include "UnityPrefix.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+
+#include "RenderLoopPrivate.h"
+#include "RenderLoop.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/Renderable.h"
+#include "Runtime/Camera/Light.h"
+#include "Runtime/Camera/RenderSettings.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Camera/Shadows.h"
+#include "Runtime/Camera/LODGroupManager.h"
+#include "Runtime/Graphics/RenderBufferManager.h"
+#include "Runtime/Graphics/GraphicsHelper.h"
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "Runtime/Graphics/Transform.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "External/shaderlab/Library/properties.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/GfxDevice/BatchRendering.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "BuiltinShaderParamUtility.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+#include "Runtime/Camera/LightManager.h"
+#include "External/MurmurHash/MurmurHash2.h"
+
+
+// Enable/disable hash based forward shader render loop sorting functionality.
+#define ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING 0
+
+PROFILER_INFORMATION(gFwdOpaquePrepare, "RenderForwardOpaque.Prepare", kProfilerRender)
+PROFILER_INFORMATION(gFwdOpaqueSort, "RenderForwardOpaque.Sort", kProfilerRender)
+PROFILER_INFORMATION(gFwdOpaqueCollectShadows, "RenderForwardOpaque.CollectShadows", kProfilerRender)
+PROFILER_INFORMATION(gFwdOpaqueRender, "RenderForwardOpaque.Render", kProfilerRender)
+PROFILER_INFORMATION(gFwdAlphaPrepare, "RenderForwardAlpha.Prepare", kProfilerRender)
+PROFILER_INFORMATION(gFwdAlphaSort, "RenderForwardAlpha.Sort", kProfilerRender)
+PROFILER_INFORMATION(gFwdAlphaRender, "RenderForwardAlpha.Render", kProfilerRender)
+
+static SHADERPROP (ShadowMapTexture);
+
+
+static inline bool CompareLights (ForwardLightsBlock const* a, ForwardLightsBlock const* b)
+{
+ if (!a || !b)
+ return false;
+
+ if (a->mainLight != b->mainLight)
+ return false;
+ if (a->vertexLightCount != b->vertexLightCount)
+ return false;
+ if (a->addLightCount != b->addLightCount)
+ return false;
+
+ int totalLightCount = a->vertexLightCount + a->addLightCount;
+ const ActiveLight* const* lightsA = a->GetLights();
+ const ActiveLight* const* lightsB = b->GetLights();
+ for (int i = 0; i < totalLightCount; ++i)
+ if (lightsA[i] != lightsB[i])
+ return false;
+
+ if (memcmp(a->sh, b->sh, sizeof(a->sh)) != 0)
+ return false;
+
+ if (!CompareApproximately(a->lastAddLightBlend, b->lastAddLightBlend))
+ return false;
+ if (!CompareApproximately(a->lastVertexLightBlend, b->lastVertexLightBlend))
+ return false;
+
+ return true;
+}
+
+struct RenderObjectDataCold {
+ float invScale; // 4
+ float lodFade; // 4
+ size_t lightsDataOffset; // 4 into memory block with all light data chunks
+ int subshaderIndex; // 4
+ // 16 bytes
+};
+
+
+namespace ForwardShaderRenderLoop_Enum
+{
+// Render pass data here is 8 bytes each; an index of the render object and "the rest" packed
+// into 4 bytes.
+enum {
+ kPackPassShift = 0,
+ kPackPassMask = 0xFF,
+ kPackTypeShift = 8,
+ kPackTypeMask = 0xFF,
+ kPackFirstPassFlag = (1<<24),
+ kPackMultiPassFlag = (1<<25),
+};
+
+} // namespace ForwardShaderRenderLoop_Enum
+
+struct RenderPassData {
+ int roIndex;
+ // Packed into UInt32: pass number, pass type, first pass flag, multipass flag
+ UInt32 data;
+#if ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+ // state hash for optimizing render object sorter
+ UInt32 hash;
+#endif
+};
+typedef dynamic_array<RenderPassData> RenderPasses;
+
+
+struct ForwardShaderRenderState
+{
+ int rendererType;
+ int transformType;
+
+ float invScale;
+ float lodFade;
+
+ Material* material;
+ Shader* shader;
+ int subshaderIndex;
+ ShaderPassType passType;
+ int passIndex;
+
+ const ForwardLightsBlock* lights;
+ int receiveShadows;
+
+ int lightmapIndex;
+ Vector4f lightmapST;
+
+ UInt32 customPropsHash;
+
+
+ void Invalidate()
+ {
+ rendererType = -1;
+ transformType = -1;
+ invScale = 0.0f;
+ lodFade = 0.0F;
+ material = 0; shader = 0; subshaderIndex = -1; passType = kShaderPassTypeCount; passIndex = -1;
+ lights = 0;
+ lightmapIndex = -1; lightmapST = Vector4f(0,0,0,0);
+ receiveShadows = -1;
+ customPropsHash = 0;
+ }
+
+ bool operator == (const ForwardShaderRenderState& rhs) const
+ {
+ if (this == &rhs)
+ return true;
+
+ return (
+ rendererType == rhs.rendererType &&
+ transformType == rhs.transformType &&
+ material == rhs.material &&
+ shader == rhs.shader &&
+ CompareLights(lights, rhs.lights) &&
+ subshaderIndex == rhs.subshaderIndex &&
+ passType == rhs.passType &&
+ passIndex == rhs.passIndex &&
+ CompareApproximately(invScale,rhs.invScale) &&
+ CompareApproximately(lodFade,rhs.lodFade, LOD_FADE_BATCH_EPSILON) &&
+ #if ENABLE_SHADOWS
+ receiveShadows == rhs.receiveShadows &&
+ #endif
+ lightmapIndex == rhs.lightmapIndex &&
+ lightmapST == rhs.lightmapST &&
+ customPropsHash == rhs.customPropsHash
+ );
+ }
+
+ bool operator != (const ForwardShaderRenderState& rhs) const
+ {
+ return !(rhs == *this);
+ }
+};
+
+
+struct ForwardShadowMap
+{
+ ForwardShadowMap() : light(NULL), texture(NULL) {}
+ const ActiveLight* light;
+ RenderTexture* texture;
+ Matrix4x4f shadowMatrix;
+ MinMaxAABB receiverBounds;
+};
+typedef dynamic_array<ForwardShadowMap> ForwardShadowMaps;
+
+struct CompactShadowCollectorSortData;
+
+struct ForwardShaderRenderLoop
+{
+ const RenderLoopContext* m_Context;
+ RenderObjectDataContainer* m_Objects;
+
+ dynamic_array<RenderObjectDataCold> m_RenderObjectsCold;
+ dynamic_array<UInt8> m_RenderObjectsLightData;
+
+ RenderPasses m_PlainRenderPasses;
+ #if ENABLE_SHADOWS
+ ForwardShadowMap m_MainShadowMap;
+ ForwardShadowMaps m_ShadowMaps;
+ // Render object indices of shadow receivers.
+ // This includes both shadow receivers and objects that have shadows off, but
+ // are within shadow distance. They should still participate in screenspace shadow
+ // gathering, otherwise shadows will be visible through them.
+ dynamic_array<int> m_ReceiverObjects;
+ #endif
+
+ BatchRenderer m_BatchRenderer;
+
+ ForwardShaderRenderLoop()
+ : m_RenderObjectsCold (kMemTempAlloc)
+ , m_RenderObjectsLightData (kMemTempAlloc)
+ , m_PlainRenderPasses (kMemTempAlloc)
+ #if ENABLE_SHADOWS
+ , m_ShadowMaps (kMemTempAlloc)
+ , m_ReceiverObjects (kMemTempAlloc)
+ #endif
+ { }
+
+ void PerformRendering (const ActiveLight* mainDirShadowLight, RenderTexture* existingShadowMap, const ShadowCullData& shadowCullData, bool disableDynamicBatching, bool sRGBrenderTarget, bool clearFrameBuffer);
+ #if ENABLE_SHADOWS
+ RenderTexture* CollectShadows (RenderTexture* inputShadowMap, const Light* light, const Matrix4x4f* shadowMatrices, const float* splitDistances, const Vector4f* splitSphereCentersAndSquaredRadii, bool enableSoftShadows, bool useDualInForward, bool clearFrameBuffer);
+ void RenderLightShadowMaps (ForwardShadowMap& shadowMap, ShadowCameraData& camData, bool enableSoftShadows, bool useDualInForward, bool clearFrameBuffer);
+ int SortShadowCollectorsCompact(CompactShadowCollectorSortData* _resultOrder);
+ #endif
+
+ template <bool opaque>
+ struct RenderObjectSorter
+ {
+ bool operator()( const RenderPassData& ra, const RenderPassData& rb ) const;
+ const ForwardShaderRenderLoop* queue;
+ };
+
+ template <bool opaque>
+ void SortRenderPassData( RenderPasses& passes )
+ {
+ RenderObjectSorter<opaque> sorter;
+ sorter.queue = this;
+ std::sort( passes.begin(), passes.end(), sorter );
+ }
+};
+
+
+template <bool opaque>
+bool ForwardShaderRenderLoop::RenderObjectSorter<opaque>::operator() (const RenderPassData& ra, const RenderPassData& rb) const
+{
+ using namespace ForwardShaderRenderLoop_Enum;
+
+ const RenderObjectData& dataa = (*queue->m_Objects)[ra.roIndex];
+ const RenderObjectData& datab = (*queue->m_Objects)[rb.roIndex];
+
+ // Sort by layering depth.
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(dataa.globalLayeringData, datab.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+#if ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+
+ if (!opaque)
+ {
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+#if DEBUGMODE
+ DebugAssertIf (dataa.queueIndex >= kQueueIndexMin && dataa.queueIndex <= kGeometryQueueIndexMax); // this is alpha loop!
+#endif
+
+ // Sort strictly by distance unless they are equal
+ if( dataa.distance != datab.distance )
+ return dataa.distance < datab.distance;
+ }
+
+ UInt64 keya = (0x0000ffff-((dataa.queueIndex)&0x0000ffff))<<16;
+ UInt64 keyb = (0x0000ffff-((datab.queueIndex)&0x0000ffff))<<16;
+
+ keya |= (ra.data & kPackFirstPassFlag)>>(24-8);
+ keyb |= (rb.data & kPackFirstPassFlag)>>(24-8);
+ keya |= (0x000000ff-((dataa.lightmapIndex)&0x000000ff));
+ keyb |= (0x000000ff-((datab.lightmapIndex)&0x000000ff));
+ keya = keya << 32;
+ keyb = keyb << 32;
+ keya |= ra.hash;
+ keyb |= rb.hash;
+
+ //Sort keys, TODO try to move the key generation outside the sorting loop
+ if( keya != keyb )
+ return (keya > keyb);
+
+#if DEBUGMODE
+ if (opaque)
+ {
+ DebugAssertIf (dataa.queueIndex < kQueueIndexMin || dataa.queueIndex > kGeometryQueueIndexMax); // this is opaque loop!
+ }
+#endif
+
+ //fall though distance, TODO insert distance into the key
+ return dataa.distance > datab.distance;
+
+#else
+
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+#if DEBUGMODE
+ if (opaque) {
+ DebugAssertIf (dataa.queueIndex < kQueueIndexMin || dataa.queueIndex > kGeometryQueueIndexMax); // this is opaque loop!
+ } else {
+ DebugAssertIf (dataa.queueIndex >= kQueueIndexMin && dataa.queueIndex <= kGeometryQueueIndexMax); // this is alpha loop!
+ }
+#endif
+
+ if (!opaque)
+ {
+ // Sort strictly by distance unless they are equal
+ if( dataa.distance != datab.distance )
+ return dataa.distance < datab.distance;
+ }
+
+ UInt32 flagsa = ra.data;
+ UInt32 flagsb = rb.data;
+
+ // render all first passes first
+ if( (flagsa & kPackFirstPassFlag) != (flagsb & kPackFirstPassFlag) )
+ return (flagsa & kPackFirstPassFlag) > (flagsb & kPackFirstPassFlag);
+
+ // sort by lightmap index (fine to do it before source material index
+ // since every part of same mesh will have the same lightmap index)
+ if( dataa.lightmapIndex != datab.lightmapIndex )
+ return dataa.lightmapIndex < datab.lightmapIndex;
+
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ // if part of predefined static batch, then sort by static batch index
+ // prefer static batched first as they usually cover quite a lot
+ if( dataa.staticBatchIndex != datab.staticBatchIndex )
+ return dataa.staticBatchIndex > datab.staticBatchIndex;
+
+ // otherwise sort by material index. Some people are using multiple materials
+ // on a single mesh and expect them to be rendered in order.
+ if( dataa.staticBatchIndex == 0 && dataa.sourceMaterialIndex != datab.sourceMaterialIndex )
+ return dataa.sourceMaterialIndex < datab.sourceMaterialIndex;
+#else
+ // Sort by material index. Some people are using multiple materials
+ // on a single mesh and expect them to be rendered in order.
+ if( dataa.sourceMaterialIndex != datab.sourceMaterialIndex )
+ return dataa.sourceMaterialIndex < datab.sourceMaterialIndex;
+#endif
+
+ // sort by shader
+ if( dataa.shader != datab.shader )
+ return dataa.shader->GetInstanceID() < datab.shader->GetInstanceID(); // just compare instance IDs
+
+ // then sort by material
+ if( dataa.material != datab.material )
+ return dataa.material->GetInstanceID() < datab.material->GetInstanceID(); // just compare instance IDs
+
+ // inside same material: by pass
+ UInt32 passa = (flagsa >> kPackPassShift) & kPackPassMask;
+ UInt32 passb = (flagsb >> kPackPassShift) & kPackPassMask;
+ if( passa != passb )
+ return passa < passb;
+
+ if (opaque)
+ {
+ // Sort by distance in reverse order.
+ // That way we get consistency in render order, and more pixels not rendered due to z-testing,
+ // which benefits performance.
+ if( dataa.distance != datab.distance )
+ return dataa.distance > datab.distance;
+ }
+
+ // fall through: roIndex
+ return ra.roIndex < rb.roIndex;
+
+#endif // ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+}
+
+#if ENABLE_SHADOWS
+static void SetLightShadowProps (const Camera& camera, const Light& light, Texture* shadowMap, const Matrix4x4f& shadowMatrix, bool useDualInForward)
+{
+ const float shadowStrength = light.GetShadowStrength();
+ DebugAssert (shadowMap);
+
+ ShaderLab::PropertySheet *props = ShaderLab::g_GlobalProperties;
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+
+ // shadow matrix
+ CopyMatrix (shadowMatrix.GetPtr(), params.GetWritableMatrixParam(kShaderMatWorldToShadow).GetPtr());
+
+ props->SetTexture( kSLPropShadowMapTexture, shadowMap );
+
+ if (light.GetType() == kLightPoint)
+ {
+ const Vector3f lightPos = light.GetWorldPosition();
+ params.SetVectorParam(kShaderVecLightPositionRange, Vector4f(lightPos.x, lightPos.y, lightPos.z, 1.0f/light.GetRange()));
+ }
+
+ // ambient & shadow fade out
+ Vector4f lightFade;
+ Vector4f fadeCenterAndType;
+ CalculateLightShadowFade (camera, shadowStrength, lightFade, fadeCenterAndType);
+ params.SetVectorParam(kShaderVecLightmapFade, lightFade);
+ if (useDualInForward)
+ lightFade.z = lightFade.w = 0.0f;
+ params.SetVectorParam(kShaderVecLightShadowData, lightFade);
+ params.SetVectorParam(kShaderVecShadowFadeCenterAndType, fadeCenterAndType);
+ // texel offsets for PCF
+ Vector4f offsets;
+ float offX = 0.5f / shadowMap->GetGLWidth();
+ float offY = 0.5f / shadowMap->GetGLHeight();
+ offsets.z = 0.0f; offsets.w = 0.0f;
+ offsets.x = -offX; offsets.y = -offY; params.SetVectorParam(kShaderVecShadowOffset0, offsets);
+ offsets.x = offX; offsets.y = -offY; params.SetVectorParam(kShaderVecShadowOffset1, offsets);
+ offsets.x = -offX; offsets.y = offY; params.SetVectorParam(kShaderVecShadowOffset2, offsets);
+ offsets.x = offX; offsets.y = offY; params.SetVectorParam(kShaderVecShadowOffset3, offsets);
+}
+static void SetLightShadowCollectProps (const Camera& camera, const Light& light, Texture* shadowMap, const Matrix4x4f* shadowMatrices, const float* splitDistances, const Vector4f* splitSphereCentersAndSquaredRadii, bool useDualInForward)
+{
+ DebugAssert (shadowMatrices && shadowMap);
+ SetLightShadowProps (camera, light, shadowMap, shadowMatrices[0], useDualInForward);
+ SetCascadedShadowShaderParams (shadowMatrices, splitDistances, splitSphereCentersAndSquaredRadii);
+}
+#endif // ENABLE_SHADOWS
+
+
+
+void ForwardShaderRenderLoop::PerformRendering (const ActiveLight* mainDirShadowLight, RenderTexture* existingShadowMap, const ShadowCullData& shadowCullData, bool disableDynamicBatching, bool sRGBrenderTarget, bool clearFrameBuffer)
+{
+ using namespace ForwardShaderRenderLoop_Enum;
+
+ const RenderManager::Renderables& renderables = GetRenderManager ().GetRenderables ();
+ RenderManager::Renderables::const_iterator renderablesBegin = renderables.begin(), renderablesEnd = renderables.end();
+
+ SetNoShadowsKeywords();
+
+ GfxDevice& device = GetGfxDevice();
+ // save current scissor params
+ int oldScissorRect[4];
+ device.GetScissorRect(oldScissorRect);
+ const bool oldScissor = device.IsScissorEnabled();
+
+ #if ENABLE_SHADOWS
+ const bool enableSoftShadows = GetSoftShadowsEnabled();
+ ShadowCameraData camData(shadowCullData);
+ ForwardShadowMap mainLightShadowMap;
+ const bool hasAnyShadows = (mainDirShadowLight != 0 || !m_ShadowMaps.empty());
+ const bool useDualInForward = GetLightmapSettings().GetUseDualLightmapsInForward();
+
+ // shadow map of main directional light
+ if (mainDirShadowLight != 0)
+ {
+ // Render shadow map
+ if (!existingShadowMap)
+ {
+ // Prevent receiver bounds to be zero size in any dimension;
+ // causes trouble with calculating intersection of frustum and bounds.
+ mainLightShadowMap.receiverBounds = m_MainShadowMap.receiverBounds;
+ mainLightShadowMap.receiverBounds.Expand (0.01f);
+ mainLightShadowMap.light = mainDirShadowLight;
+
+ // One directional light can have shadows in free version, so temporarily
+ // enable render textures just for that.
+ RenderTexture::SetTemporarilyAllowIndieRenderTexture (true);
+ RenderLightShadowMaps (mainLightShadowMap, camData, enableSoftShadows, useDualInForward, clearFrameBuffer);
+ RenderTexture::SetTemporarilyAllowIndieRenderTexture (false);
+
+ // There were no shadow casters - no shadowmap is produced
+ if (!mainLightShadowMap.texture)
+ mainDirShadowLight = 0;
+ }
+ else
+ {
+ mainLightShadowMap.texture = existingShadowMap;
+ }
+ }
+
+ // shadow maps of other lights
+ for (ForwardShadowMaps::iterator it = m_ShadowMaps.begin(), itEnd = m_ShadowMaps.end(); it != itEnd; ++it)
+ {
+ ForwardShadowMap& shadowMap = *it;
+
+ // Prevent receiver bounds to be zero size in any dimension;
+ // causes trouble with calculating intersection of frustum and bounds.
+ shadowMap.receiverBounds.Expand (0.01f);
+
+ RenderLightShadowMaps (shadowMap, camData, enableSoftShadows, false, clearFrameBuffer);
+ }
+
+ if (hasAnyShadows)
+ {
+ m_Context->m_Camera->SetupRender (Camera::kRenderFlagSetRenderTarget);
+ SetNoShadowsKeywords ();
+ }
+ #endif
+
+ const RenderSettings& renderSettings = GetRenderSettings();
+ const LightmapSettings& lightmapper = GetLightmapSettings();
+ size_t npasses = m_PlainRenderPasses.size();
+
+ int currentQueueIndex = m_Context->m_RenderQueueStart;
+
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+ ForwardShaderRenderState prevRenderState;
+ prevRenderState.Invalidate();
+
+ //If we are in linear lighting enable sRGB writes here...
+ device.SetSRGBWrite(sRGBrenderTarget);
+ if (clearFrameBuffer)
+ m_Context->m_Camera->ClearNoSkybox(false);
+ else
+ device.IgnoreNextUnresolveOnCurrentRenderTarget();
+
+ const ChannelAssigns* channels = NULL;
+
+ for( size_t i = 0; i < npasses; ++i )
+ {
+ const RenderPassData& rpData = m_PlainRenderPasses[i];
+ const RenderObjectData& roDataH = (*m_Objects)[rpData.roIndex];
+ const RenderObjectDataCold& roDataC = m_RenderObjectsCold[rpData.roIndex];
+ const ForwardLightsBlock& roDataL = *reinterpret_cast<ForwardLightsBlock*>(&m_RenderObjectsLightData[roDataC.lightsDataOffset]);
+
+ // We're going over all things that need to be rendered in increasing
+ // render queue order. Whenever we switch to the new queue, we must
+ // invoke all "camera renderables" (halos, flares and so on).
+ const int roQueueIndex = roDataH.queueIndex;
+ DebugAssert (roQueueIndex >= currentQueueIndex);
+ if( roQueueIndex > currentQueueIndex )
+ {
+ m_BatchRenderer.Flush();
+
+ // Draw required renderables
+ if (!m_Context->m_DontRenderRenderables)
+ {
+ while( renderablesBegin != renderablesEnd && renderablesBegin->first <= roQueueIndex )
+ {
+ renderablesBegin->second->RenderRenderable(*m_Context->m_CullResults);
+ ++renderablesBegin;
+ }
+ }
+
+ currentQueueIndex = roQueueIndex;
+ }
+
+ const VisibleNode *node = roDataH.visibleNode;
+ const UInt16 subsetIndex = roDataH.subsetIndex;
+
+ ForwardShaderRenderState rs;
+ {
+ rs.rendererType = node->renderer->GetRendererType();
+ rs.transformType = node->transformType;
+ rs.invScale = roDataC.invScale;
+ rs.lodFade = roDataC.lodFade;
+
+ rs.material = roDataH.material;
+ rs.shader = roDataH.shader;
+ rs.subshaderIndex = roDataC.subshaderIndex;
+ rs.passType = (ShaderPassType)((rpData.data >> kPackTypeShift) & kPackTypeMask);
+ rs.passIndex = (rpData.data >> kPackPassShift) & kPackPassMask;
+
+ rs.lights = &roDataL;
+ #if ENABLE_SHADOWS
+ rs.receiveShadows = hasAnyShadows && node->renderer->GetReceiveShadows() && IsObjectWithinShadowRange (*m_Context->m_ShadowCullData, node->worldAABB);
+ #endif
+
+ rs.lightmapIndex = roDataH.lightmapIndex;
+ DebugAssert(rs.lightmapIndex == node->renderer->GetLightmapIndex());
+ rs.lightmapST = node->renderer->GetLightmapSTForRendering();
+ rs.customPropsHash = node->renderer->GetCustomPropertiesHash();
+ }
+
+
+ // multi-pass requires vertex position values to be EXACTLY the same for all passes
+ // therefore do NOT batch dynamic multi-pass nodes
+ // same for shadow casters
+ const bool multiPass = (rpData.data & kPackMultiPassFlag) == kPackMultiPassFlag;
+ const bool dynamicShouldNotBatch = (node->renderer->GetStaticBatchIndex() == 0) && (multiPass || disableDynamicBatching);
+
+ #if ENABLE_SHADOWS
+ const bool dynamicAndShadowCaster = (node->renderer->GetStaticBatchIndex() == 0) && (mainDirShadowLight != 0) && node->renderer->GetCastShadows();
+ #else
+ const bool dynamicAndShadowCaster = false;
+ #endif
+
+ bool shouldResetPass;
+ if (rs.passType == kPassForwardAdd || // rendering multiple different lights in a row - impossible to batch
+ prevRenderState != rs)
+ {
+ // break the batch
+ m_BatchRenderer.Flush();
+ prevRenderState = rs;
+ shouldResetPass = true;
+ }
+ // We can not use dynamic batching for shadow casting renderers or multipass renderers,
+ // because that will lead to zfighting due to slightly different vertex positions
+ else if (dynamicAndShadowCaster || dynamicShouldNotBatch)
+ {
+ m_BatchRenderer.Flush();
+ shouldResetPass = false;
+ }
+ else
+ shouldResetPass = false;
+
+ renderSettings.SetupAmbient();
+ SetObjectScale(device, roDataC.lodFade, roDataC.invScale);
+
+ node->renderer->ApplyCustomProperties(*roDataH.material, rs.shader, rs.subshaderIndex);
+
+ // non batchable and generally inefficient multi-pass path
+ if (rs.passType == kPassForwardAdd)
+ {
+ const int lightCount = rs.lights->addLightCount;
+ const ActiveLight* const* addLights = rs.lights->GetLights();
+ for( int lightNo = 0; lightNo < lightCount; ++lightNo )
+ {
+ const ActiveLight& activeLight = *addLights[lightNo];
+ Light* light = activeLight.light;
+ LightManager::SetupForwardAddLight (light, lightNo==lightCount-1 ? rs.lights->lastAddLightBlend : 1.0f);
+
+ if (light->GetType() != kLightDirectional)
+ SetLightScissorRect (activeLight.screenRect, m_Context->m_CameraViewport, false, device);
+
+
+ #if ENABLE_SHADOWS
+ if (rs.receiveShadows && light->GetShadows() != kShadowNone)
+ {
+ // find light among additional shadow lights
+ ForwardShadowMaps::iterator sl, slEnd = m_ShadowMaps.end();
+ for (sl = m_ShadowMaps.begin(); sl != slEnd; ++sl)
+ {
+ if (sl->light == &activeLight && sl->texture)
+ {
+ const Light& light = *activeLight.light;
+ SetLightShadowProps (*m_Context->m_Camera, light, sl->texture, sl->shadowMatrix, false);
+ SetShadowsKeywords (light.GetType(), light.GetShadows(), light.GetType()==kLightDirectional, enableSoftShadows);
+ break;
+ }
+ }
+ }
+ #endif
+
+ channels = rs.material->SetPassWithShader(rs.passIndex, rs.shader, rs.subshaderIndex);
+ if (channels)
+ {
+ SetupObjectMatrix (node->worldMatrix, rs.transformType);
+ node->renderer->Render( subsetIndex, *channels );
+ }
+
+ #if ENABLE_SHADOWS
+ if (rs.receiveShadows && light->GetShadows() != kShadowNone)
+ {
+ SetNoShadowsKeywords ();
+ }
+ #endif
+
+ if (light->GetType() != kLightDirectional)
+ ClearScissorRect (oldScissor, oldScissorRect, device);
+ }
+ }
+ else
+ {
+ // only setup lights & pass state when they're differ from previous
+ if (shouldResetPass)
+ {
+ // only setup lights & pass state when they're differ from previous
+ switch( rs.passType )
+ {
+ case kPassAlways:
+ {
+ // Disable all fixed function lights for consistency (so if user
+ // has accidentally Lighting On in an Always pass, it will not produce
+ // random results)
+ device.DisableLights (0);
+
+ // Reset SH lighting
+ float blackSH [9][3];
+ memset (blackSH, 0, (9 * 3)* sizeof(float));
+ SetSHConstants (blackSH, GetGfxDevice().GetBuiltinParamValues());
+
+ SetupObjectLightmaps (lightmapper, rs.lightmapIndex, rs.lightmapST, false);
+ }
+ break;
+
+ case kPassForwardBase:
+ {
+ // NOTE: identity matrix has to be set for GLSL & OpenGLES before vertex lights are set
+ // as lighting is specified in World space
+ device.SetWorldMatrix( Matrix4x4f::identity.GetPtr() );
+
+ LightManager::SetupForwardBaseLights (*rs.lights);
+ SetupObjectLightmaps (lightmapper, rs.lightmapIndex, rs.lightmapST, false);
+
+ #if ENABLE_SHADOWS
+ if (rs.receiveShadows && mainDirShadowLight && rs.lights->mainLight == mainDirShadowLight)
+ {
+ const Light& light = *mainDirShadowLight->light;
+ SetLightShadowProps (*m_Context->m_Camera, light, mainLightShadowMap.texture, mainLightShadowMap.shadowMatrix, false);
+ SetShadowsKeywords (light.GetType(), light.GetShadows(), true, enableSoftShadows);
+ }
+ #endif
+ }
+ break;
+
+ case kPassVertex:
+ case kPassVertexLM:
+ case kPassVertexLMRGBM:
+ {
+ // NOTE: identity matrix has to be set for GLSL & OpenGLES before vertex lights are set
+ // as lighting is specified in World space
+ device.SetWorldMatrix( Matrix4x4f::identity.GetPtr() );
+
+ SetupObjectLightmaps (lightmapper, rs.lightmapIndex, rs.lightmapST, true);
+ LightManager::SetupVertexLights( rs.lights->vertexLightCount, rs.lights->GetLights() );
+ }
+ break;
+
+ default:
+ {
+ AssertString ("This pass type should not happen");
+ break;
+ }
+ }
+
+ channels = roDataH.material->SetPassWithShader(rs.passIndex, rs.shader, rs.subshaderIndex);
+ }
+
+ if (channels)
+ m_BatchRenderer.Add(node->renderer, subsetIndex, channels, node->worldMatrix, rs.transformType);
+
+ if (ENABLE_SHADOWS && rs.passType == kPassForwardBase)
+ SetNoShadowsKeywords ();
+ }
+ }
+
+ m_BatchRenderer.Flush();
+
+ SetNoShadowsKeywords ();
+
+ // restore scissor
+ ClearScissorRect (oldScissor, oldScissorRect, device);
+
+ #if ENABLE_SHADOWS
+ if (mainLightShadowMap.texture && mainLightShadowMap.texture != existingShadowMap)
+ GetRenderBufferManager().ReleaseTempBuffer( mainLightShadowMap.texture );
+ for (ForwardShadowMaps::iterator it = m_ShadowMaps.begin(), itEnd = m_ShadowMaps.end(); it != itEnd; ++it)
+ {
+ ForwardShadowMap& sl = *it;
+ if (sl.texture)
+ GetRenderBufferManager().ReleaseTempBuffer (sl.texture);
+ }
+ #endif
+
+ // After everything we might still have renderables that should be drawn and the
+ // very end. Do it.
+ if (!m_Context->m_DontRenderRenderables)
+ {
+ while (renderablesBegin != renderablesEnd && renderablesBegin->first < m_Context->m_RenderQueueStart)
+ ++renderablesBegin;
+ while( renderablesBegin != renderablesEnd && renderablesBegin->first < m_Context->m_RenderQueueEnd )
+ {
+ renderablesBegin->second->RenderRenderable(*m_Context->m_CullResults);
+ ++renderablesBegin;
+ }
+ }
+ GetGfxDevice().SetSRGBWrite(false);
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+}
+
+
+// ------------------------------------------------------------------------
+// collect cascaded shadows into screen-space texture; apply blur
+
+#if ENABLE_SHADOWS
+
+struct ShadowCollectorSorter
+{
+ bool operator() (int raIndex, int rbIndex) const;
+ const ForwardShaderRenderLoop* queue;
+};
+
+bool ShadowCollectorSorter::operator()(int raIndex, int rbIndex) const
+{
+ const RenderObjectData& ra = (*queue->m_Objects)[raIndex];
+ const RenderObjectData& rb = (*queue->m_Objects)[rbIndex];
+
+ // Sort by layering depth. //@TODO:should this be here?
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(ra.globalLayeringData, rb.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+ // Sort front to back
+ return ra.distance > rb.distance;
+}
+
+struct CompactShadowCollectorSortData
+{
+ UInt64 key; // 64b key, stores full 32b material instance ID, 16b internal static batch ID, 2b for transform type, and 14b depth
+ int collectorIndex;
+
+ CompactShadowCollectorSortData(UInt32 _smallMeshIndex, UInt32 _instanceID, TransformType _transformType, float _depth, int _collectorIndex )
+ {
+ key=0;
+ UInt32 transformType = static_cast<UInt32>(_transformType);
+ UInt32 z = (UInt32)(16383.0f*_depth);
+
+ key |= (_instanceID);
+ key = key << 32;
+ key |= ((_smallMeshIndex&0x0000ffff)<<16)|((transformType&0x00000003)<<14)|(z&0x00003fff);
+
+ collectorIndex = _collectorIndex;
+ }
+};
+
+struct CompactShadowCollectorKeySorter
+{
+ inline bool operator()(const CompactShadowCollectorSortData& a, const CompactShadowCollectorSortData& b)
+ {
+ return a.key < b.key;
+ }
+};
+
+// Shadow collector sorting
+// Sorted shadow collector order is stored into m_ReceiverObjects
+// Output:
+// _resultOrder - Sorted shadow caster sort data
+// Returns:
+// Number of active collectors
+int ForwardShaderRenderLoop::SortShadowCollectorsCompact(CompactShadowCollectorSortData* _resultOrder)
+{
+ int activeShadowCollectors = 0;
+
+ // Generate key array for sorting
+ for( int i = 0; i < m_ReceiverObjects.size(); ++i )
+ {
+ int roIndex = m_ReceiverObjects[i];
+ const RenderObjectData& roDataH = (*m_Objects)[roIndex];
+ Shader* shader = roDataH.shader;
+
+ if( shader->HasShadowCollectorPass() )
+ {
+ const TransformInfo& xformInfo = roDataH.visibleNode->renderer->GetTransformInfo();
+
+ Matrix4x4f worldToClipMatrix = m_Context->m_Camera->GetWorldToClipMatrix();
+ const Vector3f& worldPos = roDataH.visibleNode->worldAABB.GetCenter();
+ float z = worldToClipMatrix.Get (2, 0) * worldPos.x + worldToClipMatrix.Get (2, 1) * worldPos.y + worldToClipMatrix.Get (2, 2) * worldPos.z + worldToClipMatrix.Get (2, 3);
+ float w = worldToClipMatrix.Get (3, 0) * worldPos.x + worldToClipMatrix.Get (3, 1) * worldPos.y + worldToClipMatrix.Get (3, 2) * worldPos.z + worldToClipMatrix.Get (3, 3);
+ float z_proj = z/w;
+ z_proj = max(z_proj,0.0f);
+ z_proj = min(z_proj,1.0f);
+
+ _resultOrder[activeShadowCollectors++] = CompactShadowCollectorSortData( roDataH.visibleNode->renderer->GetMeshIDSmall(), roDataH.material->GetShadowCollectorHash(),
+ xformInfo.transformType, z_proj, roIndex );
+
+ }
+ }
+
+ std::sort( _resultOrder, _resultOrder + activeShadowCollectors, CompactShadowCollectorKeySorter() );
+
+ return activeShadowCollectors;
+}
+
+RenderTexture* ForwardShaderRenderLoop::CollectShadows (RenderTexture* inputShadowMap, const Light* light, const Matrix4x4f* shadowMatrices, const float* splitDistances, const Vector4f* splitSphereCentersAndSquaredRadii, bool enableSoftShadows, bool useDualInForward, bool clearFrameBuffer)
+{
+ PROFILER_AUTO_GFX(gFwdOpaqueCollectShadows, m_Context->m_Camera)
+ GPU_AUTO_SECTION(kGPUSectionShadowPass);
+
+ DebugAssert (shadowMatrices && inputShadowMap && light && splitDistances);
+
+ //Sort shadow collectors
+#if GFX_ENABLE_SHADOW_BATCHING
+ CompactShadowCollectorSortData* sortOrder;
+ ALLOC_TEMP(sortOrder, CompactShadowCollectorSortData, m_ReceiverObjects.size());
+ int shadowColectors = SortShadowCollectorsCompact(sortOrder);
+#else
+ ShadowCollectorSorter sorter;
+ sorter.queue = this;
+ std::sort (m_ReceiverObjects.begin(), m_ReceiverObjects.end(), sorter);
+#endif
+
+ // If camera is rendering into a texture, we can share its depth buffer while collecting shadows.
+ // This doesn't apply if the target texture is antialiased (case 559079).
+ bool shareDepthBuffer = false;
+ RenderTexture* cameraRT = m_Context->m_Camera->GetCurrentTargetTexture();
+ if (cameraRT && cameraRT->GetDepthFormat() != kDepthFormatNone && !cameraRT->IsAntiAliased())
+ {
+ shareDepthBuffer = true;
+ if (!cameraRT->IsCreated())
+ cameraRT->Create();
+ }
+
+ // create screen-space render texture and collect shadows into it
+ RenderTexture* screenShadowMap = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, shareDepthBuffer ? kDepthFormatNone : kDepthFormat24, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ if (shareDepthBuffer)
+ {
+ if (!screenShadowMap->IsCreated())
+ screenShadowMap->Create();
+ RenderSurfaceHandle rtSurfaceColor = screenShadowMap->GetColorSurfaceHandle();
+ RenderSurfaceHandle rtSurfaceDepth = cameraRT->GetDepthSurfaceHandle();
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, screenShadowMap);
+ }
+ else
+ {
+ RenderTexture::SetActive (screenShadowMap);
+ }
+
+ GfxDevice& device = GetGfxDevice();
+ // (case 555375)
+ // Clear all, expect in cases where depth buffer is shared and forward path is used to render deferred path shadow receiving objects
+ // (clearFrameBuffer variable is false in those cases)
+ bool clearColorOnly = shareDepthBuffer && !clearFrameBuffer;
+ device.Clear (clearColorOnly ? kGfxClearColor : kGfxClearAll, ColorRGBAf(1,1,1,0).GetPtr(), 1.0f, 0);
+ if (clearColorOnly)
+ device.IgnoreNextUnresolveOnCurrentRenderTarget();
+ GPU_TIMESTAMP();
+ m_Context->m_Camera->SetupRender ();
+
+ SetLightShadowCollectProps (*m_Context->m_Camera, *light, inputShadowMap, shadowMatrices, splitDistances, splitSphereCentersAndSquaredRadii, useDualInForward);
+ light->SetPropsToShaderLab (1.0f);
+
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+#if GFX_ENABLE_SHADOW_BATCHING
+
+ device.SetInverseScale(1.0f);
+ m_BatchRenderer.Flush();
+
+ if (shadowColectors > 0)
+ {
+ UInt64 previousKey = ((sortOrder[0].key)&0xFFFFFFFFFFFFC000ULL); // depth component does not affect state change boundaries
+ UInt32 previousHash = 0;
+ int roIndex = sortOrder[0].collectorIndex;
+ const ChannelAssigns* channels = (*m_Objects)[roIndex].material->SetShadowCollectorPassWithShader ((*m_Objects)[roIndex].shader, m_RenderObjectsCold[roIndex].subshaderIndex);
+
+ for(int i=0; i<shadowColectors;i++)
+ {
+ UInt64 currentKey = ((sortOrder[i].key)&0xFFFFFFFFFFFFC000ULL);
+
+ roIndex = sortOrder[i].collectorIndex;
+ const RenderObjectData& roDataH = (*m_Objects)[roIndex];
+ Shader* shader = roDataH.shader;
+ const TransformInfo& xformInfo = roDataH.visibleNode->renderer->GetTransformInfo ();
+ const RenderObjectDataCold& roDataC = m_RenderObjectsCold[roIndex];
+
+ roDataH.visibleNode->renderer->ApplyCustomProperties(*roDataH.material, shader, roDataC.subshaderIndex);
+
+ UInt32 currentHash = roDataH.visibleNode->renderer->GetCustomPropertiesHash();
+
+ // different property hasah or shared depth buffer cause Flush(), state setup, and one non-batched draw call
+ if (currentHash != previousHash || shareDepthBuffer)
+ {
+ m_BatchRenderer.Flush(); // empty BatchRenderer
+ channels = roDataH.material->SetShadowCollectorPassWithShader(shader, roDataC.subshaderIndex);
+ SetupObjectMatrix(xformInfo.worldMatrix, xformInfo.transformType);
+ roDataH.visibleNode->renderer->Render( roDataH.subsetIndex, *channels );
+ }
+ else
+ {
+ if (previousKey != currentKey) // Flush() and update state when key changes
+ {
+ m_BatchRenderer.Flush();
+ channels = roDataH.material->SetShadowCollectorPassWithShader (shader, roDataC.subshaderIndex);
+ }
+
+ // if this pass needs to be rendered
+ if (channels)
+ m_BatchRenderer.Add(roDataH.visibleNode->renderer, roDataH.subsetIndex, channels, xformInfo.worldMatrix, xformInfo.transformType);
+ }
+ previousKey = currentKey;
+ previousHash = currentHash;
+ }
+ m_BatchRenderer.Flush();
+ }
+
+#else // GFX_ENABLE_SHADOW_BATCHING
+
+ size_t npasses = m_ReceiverObjects.size();
+ for( size_t i = 0; i < npasses; ++i )
+ {
+ int roIndex = m_ReceiverObjects[i];
+ const RenderObjectData& roDataH = (*m_Objects)[roIndex];
+ const RenderObjectDataCold& roDataC = m_RenderObjectsCold[roIndex];
+
+ Shader* shader = roDataH.shader;
+ if( !shader->HasShadowCollectorPass() )
+ continue;
+
+ const VisibleNode* node = roDataH.visibleNode;
+ BaseRenderer* renderer = node->renderer;
+ SetObjectScale(device, roDataC.lodFade, roDataC.invScale);
+
+ renderer->ApplyCustomProperties(*roDataH.material, shader, roDataC.subshaderIndex);
+
+ const ChannelAssigns* channels = roDataH.material->SetShadowCollectorPassWithShader(shader, roDataC.subshaderIndex);
+ SetupObjectMatrix (node->worldMatrix, node->transformType);
+ renderer->Render( roDataH.subsetIndex, *channels );
+ }
+
+#endif // GFX_ENABLE_SHADOW_BATCHING
+
+ GetRenderBufferManager().ReleaseTempBuffer( inputShadowMap );
+
+ //
+ // possibly blur into another screen-space render texture
+
+ if( IsSoftShadow(light->GetShadows()) && enableSoftShadows )
+ {
+ return BlurScreenShadowMap (screenShadowMap, light->GetShadows(), m_Context->m_Camera->GetFar(), light->GetShadowSoftness(), light->GetShadowSoftnessFade());
+ }
+
+ return screenShadowMap;
+}
+#endif // ENABLE_SHADOWS
+
+// ------------------------------------------------------------------------
+// render shadow maps for a single light
+
+
+#if ENABLE_SHADOWS
+
+void ForwardShaderRenderLoop::RenderLightShadowMaps (ForwardShadowMap& shadowMap, ShadowCameraData& camData, bool enableSoftShadows, bool useDualInForward, bool clearFrameBuffer)
+{
+ // Set correct keywords before rendering casters (caster passes use keywords for shader selection)
+ const Light* light = shadowMap.light->light;
+ SetShadowsKeywords( light->GetType(), light->GetShadows(), false, enableSoftShadows );
+ GetGfxDevice().SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+ Matrix4x4f shadowMatrices[kMaxShadowCascades];
+
+#if UNITY_EDITOR
+ bool useLightmaps = GetLightmapVisualization ().GetUseLightmapsForRendering ();
+#else
+ bool useLightmaps = true;
+#endif
+
+ bool excludeLightmapped = !useDualInForward && useLightmaps;
+ shadowMap.texture = RenderShadowMaps (camData, *shadowMap.light, shadowMap.receiverBounds, excludeLightmapped, shadowMatrices);
+ CopyMatrix (shadowMatrices[0].GetPtr(), shadowMap.shadowMatrix.GetPtr());
+
+ // Shadow map can be null if out of memory; or no shadow casters present
+ if (gGraphicsCaps.hasShadowCollectorPass && shadowMap.texture && light->GetType() == kLightDirectional)
+ {
+ SetShadowsKeywords( light->GetType(), light->GetShadows(), false, enableSoftShadows );
+ shadowMap.texture = CollectShadows (shadowMap.texture, light, shadowMatrices, camData.splitDistances, camData.splitSphereCentersAndSquaredRadii, enableSoftShadows, useDualInForward, clearFrameBuffer);
+ }
+ else
+ {
+ // If shadow map could not actually be created (out of VRAM, whatever), set the no shadows
+ // keywords and proceed. So there will be no shadows, but otherwise it will be ok.
+ SetNoShadowsKeywords();
+ }
+}
+
+#endif // ENABLE_SHADOWS
+
+
+// ------------------------------------------------------------------------
+// rendering entry points
+
+ForwardShaderRenderLoop* CreateForwardShaderRenderLoop()
+{
+ return new ForwardShaderRenderLoop();
+}
+
+void DeleteForwardShaderRenderLoop (ForwardShaderRenderLoop* queue)
+{
+ delete queue;
+}
+
+static bool IsPassSuitable (UInt32 currentRenderOptions, UInt32 passRenderOptions, ShaderPassType passType,
+ bool isLightmapped, bool useRGBM, bool useVertexLights, bool hasAddLights)
+{
+ // All options that a pass requires must be on
+ if( (currentRenderOptions & passRenderOptions) != passRenderOptions )
+ return false; // some options are off, skip this pass
+
+ if (useVertexLights)
+ {
+ if (passType != kPassAlways && passType != kPassVertex &&
+ passType != kPassVertexLM && passType != kPassVertexLMRGBM)
+ return false;
+
+ // Use either lightmapped or non-lightmapped pass
+ if ((passType == kPassVertex && isLightmapped) ||
+ ((passType == kPassVertexLM || passType == kPassVertexLMRGBM) && !isLightmapped))
+ return false;
+
+ // Use pass that can properly decode the lightmap
+ if ((passType == kPassVertexLM && useRGBM) ||
+ (passType == kPassVertexLMRGBM && !useRGBM))
+ return false;
+ }
+ else
+ {
+ if (passType != kPassAlways && passType != kPassForwardBase && passType != kPassForwardAdd)
+ return false; // pass does not belong to forward loop
+
+ if (!hasAddLights && passType == kPassForwardAdd)
+ return false; // additive pass but have no additive lights
+ }
+ return true;
+}
+
+// A point or spot light might be completely behind shadow distance,
+// so there's no point in doing shadows on them.
+static bool IsLightBeyondShadowDistance (const Light& light, const Matrix4x4f& cameraMatrix, float shadowDistance)
+{
+ if (light.GetType() == kLightDirectional)
+ return false;
+ const Vector3f lightPos = light.GetComponent(Transform).GetPosition();
+ float distanceToLight = -cameraMatrix.MultiplyPoint3 (lightPos).z;
+ if (distanceToLight - light.GetRange() > shadowDistance)
+ return true;
+ return false;
+}
+
+
+static void PutAdditionalShadowLights (const AABB& bounds, ForwardLightsBlock& lights, const Matrix4x4f& cameraMatrix, float shadowDistance, ForwardShadowMaps& outShadowMaps)
+{
+ const int lightCount = lights.addLightCount;
+ const ActiveLight* const* addLights = lights.GetLights();
+ for (int lightNo = 0; lightNo < lightCount; ++lightNo)
+ {
+ const ActiveLight* light = addLights[lightNo];
+ if (light->light->GetShadows() == kShadowNone)
+ continue;
+
+ // Find this light's shadow data
+ ForwardShadowMaps::iterator sl, slEnd = outShadowMaps.end();
+ ForwardShadowMap* found = NULL;
+ for (sl = outShadowMaps.begin(); sl != slEnd; ++sl)
+ {
+ if (sl->light == light)
+ {
+ found = &(*sl);
+ break;
+ }
+ }
+ if (sl == slEnd)
+ {
+ // Point/Spot light beyond shadow distance: no need to add
+ if (IsLightBeyondShadowDistance (*light->light, cameraMatrix, shadowDistance))
+ continue;
+
+ ForwardShadowMap& shadowMap = outShadowMaps.push_back ();
+ shadowMap.light = light;
+ shadowMap.receiverBounds = bounds;
+ shadowMap.texture = NULL;
+ }
+ else
+ {
+ found->receiverBounds.Encapsulate (bounds);
+ }
+ }
+}
+
+#if ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+template<typename T>
+static UInt8* InsertIntoHashBuffer(const T* p, UInt8* buffer)
+{
+ Assert((sizeof(T) % 4) == 0); // unaligned write
+ *reinterpret_cast<T*>(buffer) = *p;
+ return buffer + sizeof(T);
+}
+#endif
+
+void DoForwardShaderRenderLoop (
+ RenderLoopContext& ctx,
+ RenderObjectDataContainer& objects,
+ bool opaque,
+ bool disableDynamicBatching,
+ RenderTexture* mainShadowMap,
+ ActiveLights& activeLights,
+ bool linearLighting,
+ bool clearFrameBuffer)
+{
+ GPU_AUTO_SECTION(opaque ? kGPUSectionOpaquePass : kGPUSectionTransparentPass);
+ using namespace ForwardShaderRenderLoop_Enum;
+
+ const QualitySettings::QualitySetting& quality = GetQualitySettings().GetCurrent();
+
+ // figure out hardware supports shadows
+ #if ENABLE_SHADOWS
+ float shadowDistance = QualitySettings::GetShadowDistanceForRendering();
+ const bool receiveShadows =
+ opaque &&
+ GetBuildSettings().hasShadows &&
+ CheckPlatformSupportsShadows() &&
+ (quality.shadows != QualitySettings::kShadowsDisable) &&
+ (shadowDistance > 0.0f);
+ const bool localLightShadows = receiveShadows && GetBuildSettings().hasLocalLightShadows;
+ #endif
+
+ bool useRGBM = gGraphicsCaps.SupportsRGBM();
+
+ // Allocated on the stack each time, uses temp allocators
+ ForwardShaderRenderLoop queue;
+ queue.m_Context = &ctx;
+ queue.m_Objects = &objects;
+ queue.m_RenderObjectsCold.reserve(objects.size());
+ const int kEstimatedLightDataPerObject = sizeof(ForwardLightsBlock) + kEstimatedLightsPerObject * sizeof(Light*);
+ queue.m_RenderObjectsLightData.reserve(objects.size() * kEstimatedLightDataPerObject);
+
+ const ActiveLight* mainDirShadowLight = NULL;
+
+ // figure out current rendering options
+ UInt32 currentRenderOptions = GetCurrentRenderOptions ();
+
+ RenderSettings& renderSettings = GetRenderSettings();
+ LightManager& lightManager = GetLightManager();
+ const int pixelLightCount = quality.pixelLightCount;
+ const bool dualLightmapsMode = (GetLightmapSettings().GetLightmapsMode() == LightmapSettings::kDualLightmapsMode);
+
+#if UNITY_EDITOR
+ bool useLightmaps = GetLightmapVisualization ().GetUseLightmapsForRendering ();
+#endif
+
+ const CullResults& cullResults = *ctx.m_CullResults;
+
+ // Figure everything out
+ {
+ PROFILER_AUTO((opaque?gFwdOpaquePrepare:gFwdAlphaPrepare), ctx.m_Camera);
+
+ RenderObjectDataContainer::iterator itEnd = objects.end();
+ size_t roIndex = 0;
+ for (RenderObjectDataContainer::iterator it = objects.begin(); it != itEnd; ++it, ++roIndex)
+ {
+ RenderObjectData& odata = *it;
+ const VisibleNode *node = odata.visibleNode;
+ size_t visibleNodeIndex = node - cullResults.nodes.begin();
+
+ BaseRenderer* renderer = node->renderer;
+
+#if UNITY_EDITOR
+ const bool isLightmapped = renderer->IsLightmappedForRendering() && useLightmaps;
+#else
+ const bool isLightmapped = renderer->IsLightmappedForRendering();
+#endif
+
+ ShaderLab::IntShader& slshader = *odata.shader->GetShaderLabShader();
+ RenderObjectDataCold& roDataC = queue.m_RenderObjectsCold.push_back();
+ bool useVertexLights = false;
+ if (odata.subShaderIndex == -1)
+ {
+ int ss = slshader.GetDefaultSubshaderIndex (kRenderPathExtForward);
+ if (ss == -1)
+ {
+ ss = slshader.GetDefaultSubshaderIndex (isLightmapped ? kRenderPathExtVertexLM : kRenderPathExtVertex);
+ useVertexLights = true;
+ }
+ if (ss == -1)
+ continue;
+ roDataC.subshaderIndex = ss;
+ }
+ else
+ {
+ roDataC.subshaderIndex = odata.subShaderIndex;
+ }
+ ShaderLab::SubShader& subshader = slshader.GetSubShader(roDataC.subshaderIndex);
+
+ bool disableAddLights = false;
+ if (!useVertexLights)
+ {
+ // If we only have ForwardBase pass and no ForwardAdd,
+ // disable additive lights completely. Only support main directional,
+ // vertex & SH.
+ disableAddLights = !subshader.GetSupportsForwardAddLights();
+ }
+
+ size_t objectLightsOffset = queue.m_RenderObjectsLightData.size();
+ roDataC.lightsDataOffset = objectLightsOffset;
+
+ lightManager.FindForwardLightsForObject (
+ queue.m_RenderObjectsLightData,
+ GetObjectLightIndices(cullResults, visibleNodeIndex),
+ GetObjectLightCount(cullResults, visibleNodeIndex),
+ activeLights,
+ *node,
+ isLightmapped,
+ dualLightmapsMode,
+ useVertexLights,
+ pixelLightCount,
+ disableAddLights,
+ renderSettings.GetAmbientLightInActiveColorSpace());
+
+ ForwardLightsBlock& roDataL = *reinterpret_cast<ForwardLightsBlock*>(&queue.m_RenderObjectsLightData[objectLightsOffset]);
+ const bool hasAddLights = (roDataL.addLightCount != 0);
+
+ #if ENABLE_SHADOWS
+ bool objectReceivesShadows = renderer->GetReceiveShadows();
+ bool withinShadowDistance = IsObjectWithinShadowRange (*ctx.m_ShadowCullData, node->worldAABB);
+ if (receiveShadows && withinShadowDistance)
+ {
+ queue.m_ReceiverObjects.push_back (roIndex);
+
+ if (objectReceivesShadows)
+ {
+ // deal with main directional shadow light
+ if (roDataL.mainLight && roDataL.mainLight->light->GetShadows() != kShadowNone)
+ {
+ if (!mainDirShadowLight)
+ mainDirShadowLight = roDataL.mainLight;
+ if (mainDirShadowLight == roDataL.mainLight)
+ queue.m_MainShadowMap.receiverBounds.Encapsulate (node->worldAABB);
+ }
+
+ // deal with additive shadow lights if needed
+ if (localLightShadows && subshader.GetSupportsFullForwardShadows())
+ {
+ PutAdditionalShadowLights (node->worldAABB, roDataL, ctx.m_CurCameraMatrix, shadowDistance, queue.m_ShadowMaps);
+ }
+ }
+ }
+ #endif
+
+ roDataC.invScale = node->invScale;
+ roDataC.lodFade = node->lodFade;
+
+ int shaderPassCount = subshader.GetValidPassCount();
+
+ // Determine if we will need more than a single pass
+ int suitablePasses = 0;
+ for( int pass = 0; pass < shaderPassCount && suitablePasses < 2; ++pass )
+ {
+ ShaderPassType passType; UInt32 passRenderOptions;
+ subshader.GetPass(pass)->GetPassOptions( passType, passRenderOptions );
+
+ if (IsPassSuitable (currentRenderOptions, passRenderOptions, passType, isLightmapped, useRGBM, useVertexLights, hasAddLights))
+ ++suitablePasses;
+ }
+
+ // Go over all passes in the shader
+ UInt32 firstPassFlag = kPackFirstPassFlag;
+ const UInt32 multiPassFlag = (suitablePasses > 1)? kPackMultiPassFlag: 0;
+ for( int pass = 0; pass < shaderPassCount; ++pass )
+ {
+ ShaderPassType passType; UInt32 passRenderOptions;
+ subshader.GetPass(pass)->GetPassOptions( passType, passRenderOptions );
+
+ if (!IsPassSuitable (currentRenderOptions, passRenderOptions, passType, isLightmapped, useRGBM, useVertexLights, hasAddLights))
+ continue; // skip this pass
+
+ RenderPassData rpData;
+ rpData.roIndex = roIndex;
+ rpData.data =
+ ((pass & kPackPassMask) << kPackPassShift) |
+ (passType << kPackTypeShift) |
+ firstPassFlag |
+ multiPassFlag;
+
+#if ENABLE_FORWARD_SHADER_LOOP_HASH_SORTING
+
+ //hash state information for render object sorter
+ const int kHashBufferSize = 64;
+ UInt8 hashBuffer[kHashBufferSize];
+ UInt8* hashPtr = hashBuffer;
+
+ // Always write 32b granularity into the hash buffer to avoid unaligned writes
+ if (opaque)
+ hashPtr = InsertIntoHashBuffer(&node->invScale, hashPtr);
+ int materialID = odata.material->GetInstanceID();
+ hashPtr = InsertIntoHashBuffer(&materialID, hashPtr);
+ hashPtr = InsertIntoHashBuffer(&roDataC.subshaderIndex, hashPtr);
+ UInt32 shaderPassType = (ShaderPassType)((rpData.data >> kPackTypeShift) & kPackTypeMask);
+ hashPtr = InsertIntoHashBuffer(&shaderPassType, hashPtr);
+ UInt32 passIndex = (rpData.data >> kPackPassShift) & kPackPassMask;
+ hashPtr = InsertIntoHashBuffer(&passIndex, hashPtr);
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ hashPtr = InsertIntoHashBuffer(&odata.staticBatchIndex, hashPtr);
+#endif
+ Assert(hashPtr-hashBuffer <= kHashBufferSize);
+
+ rpData.hash = MurmurHash2A(hashBuffer, hashPtr-hashBuffer, 0x9747b28c);
+#endif
+ queue.m_PlainRenderPasses.push_back( rpData );
+
+ firstPassFlag = 0;
+ }
+ }
+ }
+
+ // sort everything
+ {
+ PROFILER_AUTO((opaque?gFwdOpaqueSort:gFwdAlphaSort), ctx.m_Camera);
+ if (opaque)
+ queue.SortRenderPassData<true> (queue.m_PlainRenderPasses);
+ else
+ queue.SortRenderPassData<false> (queue.m_PlainRenderPasses);
+ }
+
+ // Render everything. When transitioning to render queues,
+ // it will invoke camera renderables (halos, and so on)
+ {
+ PROFILER_AUTO_GFX((opaque?gFwdOpaqueRender:gFwdAlphaRender), ctx.m_Camera);
+ RenderTexture* rtMain = ctx.m_Camera->GetCurrentTargetTexture ();
+ queue.PerformRendering (mainDirShadowLight, mainShadowMap, *ctx.m_ShadowCullData, disableDynamicBatching, linearLighting && (!rtMain || rtMain->GetSRGBReadWrite()), clearFrameBuffer);
+ }
+}
diff --git a/Runtime/Camera/RenderLoops/ForwardVertexRenderLoop.cpp b/Runtime/Camera/RenderLoops/ForwardVertexRenderLoop.cpp
new file mode 100644
index 0000000..60889ad
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/ForwardVertexRenderLoop.cpp
@@ -0,0 +1,637 @@
+#include "UnityPrefix.h"
+#include "RenderLoopPrivate.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Graphics/Transform.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Camera/Renderable.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Camera/RenderSettings.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "RenderLoop.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "Runtime/GfxDevice/BatchRendering.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Camera/LightManager.h"
+#if UNITY_EDITOR
+#include "Editor/Src/LightmapVisualization.h"
+#endif
+#include "BuiltinShaderParamUtility.h"
+#include "External/MurmurHash/MurmurHash2.h"
+
+// Enable/disable hash based forward shader render loop sorting functionality.
+#define ENABLE_VERTEX_LOOP_HASH_SORTING 0
+
+static inline bool CompareLights (VertexLightsBlock const* a, VertexLightsBlock const* b)
+{
+ if (!a || !b)
+ return false;
+
+ if (a->lightCount != b->lightCount)
+ return false;
+
+ const ActiveLight* const* lightsA = a->GetLights();
+ const ActiveLight* const* lightsB = b->GetLights();
+ for (int i = 0; i < a->lightCount; ++i)
+ if (lightsA[i] != lightsB[i])
+ return false;
+
+ return true;
+}
+
+
+struct RODataVLit {
+ // help the compiler here a bit...
+ RODataVLit() { }
+ RODataVLit( const RODataVLit& rhs ) { memcpy(this, &rhs, sizeof(*this)); }
+
+ float invScale; // 4
+ float lodFade; // 4
+ size_t lightsDataOffset; // 4 into memory block with all light data chunks
+ int subshaderIndex; // 4
+
+ // 16 bytes
+};
+
+namespace ForwardVertexRenderLoop_Enum
+{
+// Render pass data here is 8 bytes each; an index of the render object and "the rest" packed
+// into 4 bytes.
+enum {
+ kPackPassShift = 0,
+ kPackPassMask = 0xFF,
+ kPackFirstPassFlag = (1<<16),
+ kPackMultiPassFlag = (1<<17),
+};
+} // namespace ForwardVertexRenderLoop_Enum
+
+struct RPDataVLit {
+ int roIndex;
+ // Packed into UInt32: pass number, first pass flag
+ UInt32 data;
+#if ENABLE_VERTEX_LOOP_HASH_SORTING
+ UInt32 hash;
+#endif
+};
+typedef dynamic_array<RPDataVLit> RenderPassesVLit;
+
+
+struct ForwardVertexRenderState
+{
+ int rendererType;
+ int transformType;
+ float invScale;
+ float lodFade;
+
+ Material* material;
+ Shader* shader;
+ int subshaderIndex;
+ int passIndex;
+
+ const VertexLightsBlock* lights;
+
+ int lightmapIndex;
+ Vector4f lightmapST;
+
+ UInt32 customPropsHash;
+
+ void Invalidate()
+ {
+ rendererType = -1;
+ transformType = -1;
+ invScale = 0.0f;
+ lodFade = 0.0f;
+ material = 0; shader = 0; subshaderIndex = -1; passIndex = -1;
+ lights = 0;
+ lightmapIndex = -1; lightmapST = Vector4f(0,0,0,0);
+ customPropsHash = 0;
+ }
+
+ bool operator == (const ForwardVertexRenderState& rhs) const
+ {
+ if (this == &rhs)
+ return true;
+
+ return (
+ rendererType == rhs.rendererType &&
+ transformType == rhs.transformType &&
+ material == rhs.material &&
+ shader == rhs.shader &&
+ CompareLights(lights, rhs.lights) &&
+ subshaderIndex == rhs.subshaderIndex &&
+ passIndex == rhs.passIndex &&
+ CompareApproximately(invScale,rhs.invScale) &&
+ CompareApproximately(lodFade,rhs.lodFade, LOD_FADE_BATCH_EPSILON) &&
+ lightmapIndex == rhs.lightmapIndex &&
+ CompareMemory(lightmapST, rhs.lightmapST) &&
+ customPropsHash == rhs.customPropsHash);
+ }
+
+ bool operator != (const ForwardVertexRenderState& rhs) const
+ {
+ return !(rhs == *this);
+ }
+};
+
+
+struct ForwardVertexRenderLoop
+{
+ ForwardVertexRenderLoop()
+ : m_RenderObjectsCold (kMemTempAlloc)
+ , m_RenderObjectsLightData (kMemTempAlloc)
+ , m_PlainRenderPasses (kMemTempAlloc)
+ { }
+
+ const RenderLoopContext* m_Context;
+ RenderObjectDataContainer* m_Objects;
+ dynamic_array<RODataVLit> m_RenderObjectsCold;
+ dynamic_array<UInt8> m_RenderObjectsLightData;
+ RenderPassesVLit m_PlainRenderPasses;
+ BatchRenderer m_BatchRenderer;
+
+ void PerformRendering (bool sSRGBRenderTarget, bool clearFrameBuffer);
+
+ template<bool opaque>
+ struct RenderObjectSorter
+ {
+ bool operator()( const RPDataVLit& ra, const RPDataVLit& rb ) const;
+ const ForwardVertexRenderLoop* queue;
+ };
+ template<bool opaque>
+ void SortRenderPassData( RenderPassesVLit& passes )
+ {
+ RenderObjectSorter<opaque> sorter;
+ sorter.queue = this;
+ std::sort( passes.begin(), passes.end(), sorter );
+ }
+};
+
+
+template<bool opaque>
+bool ForwardVertexRenderLoop::RenderObjectSorter<opaque>::operator() (const RPDataVLit& ra, const RPDataVLit& rb) const
+{
+ using namespace ForwardVertexRenderLoop_Enum;
+
+ const RenderObjectData& dataa = (*queue->m_Objects)[ra.roIndex];
+ const RenderObjectData& datab = (*queue->m_Objects)[rb.roIndex];
+
+ // Sort by layering depth.
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(dataa.globalLayeringData, datab.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+#if ENABLE_VERTEX_LOOP_HASH_SORTING
+
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+ if (opaque) {
+ DebugAssertIf (dataa.queueIndex < kQueueIndexMin || dataa.queueIndex > kGeometryQueueIndexMax); // this is opaque loop!
+ } else {
+ DebugAssertIf (dataa.queueIndex >= kQueueIndexMin && dataa.queueIndex <= kGeometryQueueIndexMax); // this is alpha loop!
+ }
+
+ if (!opaque)
+ {
+ if( dataa.distance != datab.distance )
+ return dataa.distance < datab.distance;
+ }
+
+ UInt32 flagsa = ra.data;
+ UInt32 flagsb = rb.data;
+
+ // render all first passes first
+ if( (flagsa & kPackFirstPassFlag) != (flagsb & kPackFirstPassFlag) )
+ return (flagsa & kPackFirstPassFlag) > (flagsb & kPackFirstPassFlag);
+
+ if (ra.hash != rb.hash)
+ return ra.hash < rb.hash;
+
+ // then sort by material
+ if( dataa.material != datab.material )
+ return dataa.material->GetInstanceID() < datab.material->GetInstanceID(); // just compare instance IDs
+
+ // inside same material: by pass
+ UInt32 passa = (flagsa >> kPackPassShift) & kPackPassMask;
+ UInt32 passb = (flagsb >> kPackPassShift) & kPackPassMask;
+ if( passa != passb )
+ return passa < passb;
+
+ // Sort by distance in reverse order.
+ // That way we get consistency in render order, and more pixels not rendered due to z-testing,
+ // which benefits performance.
+ if (opaque)
+ {
+ if( dataa.distance != datab.distance )
+ return dataa.distance > datab.distance;
+ }
+
+ // fall through: roIndex
+ return ra.roIndex < rb.roIndex;
+
+#else
+
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+ if (opaque) {
+ DebugAssertIf (dataa.queueIndex < kQueueIndexMin || dataa.queueIndex > kGeometryQueueIndexMax); // this is opaque loop!
+ } else {
+ DebugAssertIf (dataa.queueIndex >= kQueueIndexMin && dataa.queueIndex <= kGeometryQueueIndexMax); // this is alpha loop!
+ }
+
+ if (!opaque)
+ {
+ if( dataa.distance != datab.distance )
+ return dataa.distance < datab.distance;
+ }
+
+ UInt32 flagsa = ra.data;
+ UInt32 flagsb = rb.data;
+
+ // render all first passes first
+ if( (flagsa & kPackFirstPassFlag) != (flagsb & kPackFirstPassFlag) )
+ return (flagsa & kPackFirstPassFlag) > (flagsb & kPackFirstPassFlag);
+
+ // sort by lightmap index (fine to do it before source material index
+ // since every part of same mesh will have the same lightmap index)
+ if( dataa.lightmapIndex != datab.lightmapIndex )
+ return dataa.lightmapIndex < datab.lightmapIndex;
+
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ // if part of predefined static batch, then sort by static batch index
+ if( dataa.staticBatchIndex != datab.staticBatchIndex )
+ return dataa.staticBatchIndex < datab.staticBatchIndex;
+
+ // otherwise sort by material index. Some people are using multiple materials
+ // on a single mesh and expect them to be rendered in order.
+ if( dataa.staticBatchIndex == 0 && dataa.sourceMaterialIndex != datab.sourceMaterialIndex )
+ return dataa.sourceMaterialIndex < datab.sourceMaterialIndex;
+#else
+ // Sort by material index. Some people are using multiple materials
+ // on a single mesh and expect them to be rendered in order.
+ if( dataa.sourceMaterialIndex != datab.sourceMaterialIndex )
+ return dataa.sourceMaterialIndex < datab.sourceMaterialIndex;
+#endif
+
+ // then sort by material
+ if( dataa.material != datab.material )
+ return dataa.material->GetInstanceID() < datab.material->GetInstanceID(); // just compare instance IDs
+
+ // inside same material: by pass
+ UInt32 passa = (flagsa >> kPackPassShift) & kPackPassMask;
+ UInt32 passb = (flagsb >> kPackPassShift) & kPackPassMask;
+ if( passa != passb )
+ return passa < passb;
+
+ // Sort by distance in reverse order.
+ // That way we get consistency in render order, and more pixels not rendered due to z-testing,
+ // which benefits performance.
+ if (opaque)
+ {
+ if( dataa.distance != datab.distance )
+ return dataa.distance > datab.distance;
+ }
+
+ // fall through: roIndex
+ return ra.roIndex < rb.roIndex;
+
+#endif
+}
+
+void ForwardVertexRenderLoop::PerformRendering (bool sSRGBRenderTarget, bool clearFrameBuffer)
+{
+ using namespace ForwardVertexRenderLoop_Enum;
+
+ GfxDevice& device = GetGfxDevice();
+ const RenderSettings& renderSettings = GetRenderSettings();
+
+ const RenderManager::Renderables& renderables = GetRenderManager ().GetRenderables ();
+ RenderManager::Renderables::const_iterator renderablesBegin = renderables.begin(), renderablesEnd = renderables.end();
+
+ const LightmapSettings& lightmapper = GetLightmapSettings();
+
+ size_t npasses = m_PlainRenderPasses.size();
+
+ int currentQueueIndex = m_Context->m_RenderQueueStart;
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+ ForwardVertexRenderState prevRenderState;
+ prevRenderState.Invalidate();
+
+ // SRGB read/write for vertexRenderLoop
+ device.SetSRGBWrite(sSRGBRenderTarget);
+ if (clearFrameBuffer)
+ m_Context->m_Camera->ClearNoSkybox(false);
+
+ const ChannelAssigns* channels = NULL;
+ int canBatch = 0;
+ StartRenderLoop();
+ for( size_t i = 0; i < npasses; ++i )
+ {
+ const RPDataVLit& rpData = m_PlainRenderPasses[i];
+ DebugAssertIf (rpData.roIndex < 0 || rpData.roIndex >= m_Objects->size() || rpData.roIndex >= m_RenderObjectsCold.size());
+ const RenderObjectData& roDataH = (*m_Objects)[rpData.roIndex];
+ const RODataVLit& roDataC = m_RenderObjectsCold[rpData.roIndex];
+
+ const VertexLightsBlock& roDataL = *reinterpret_cast<VertexLightsBlock*>(&m_RenderObjectsLightData[roDataC.lightsDataOffset]);
+
+ const int roQueueIndex = roDataH.queueIndex;
+ DebugAssertIf( roQueueIndex < currentQueueIndex );
+ if( roQueueIndex > currentQueueIndex )
+ {
+ m_BatchRenderer.Flush();
+ canBatch = 0;
+ EndRenderLoop();
+ // Draw required renderables
+ if (!m_Context->m_DontRenderRenderables)
+ {
+ while( renderablesBegin != renderablesEnd && renderablesBegin->first <= roQueueIndex )
+ {
+ renderablesBegin->second->RenderRenderable(*m_Context->m_CullResults);
+
+ ++renderablesBegin;
+ }
+ }
+
+ currentQueueIndex = roQueueIndex;
+ StartRenderLoop();
+ }
+
+ const VisibleNode* node = roDataH.visibleNode;
+ const UInt16 subsetIndex = roDataH.subsetIndex;
+
+ ForwardVertexRenderState rs;
+ {
+ rs.rendererType = node->renderer->GetRendererType();
+ rs.transformType = node->transformType;
+ rs.invScale = roDataC.invScale;
+ rs.lodFade = roDataC.lodFade;
+
+ rs.material = roDataH.material;
+ rs.shader = roDataH.shader;
+ rs.subshaderIndex = roDataC.subshaderIndex;
+ rs.passIndex = (rpData.data >> kPackPassShift) & kPackPassMask;
+
+ rs.lights = &roDataL;
+
+ rs.lightmapIndex = roDataH.lightmapIndex;
+ DebugAssert(rs.lightmapIndex == node->renderer->GetLightmapIndex());
+ rs.lightmapST = node->renderer->GetLightmapSTForRendering();
+ rs.customPropsHash = node->renderer->GetCustomPropertiesHash();
+ }
+
+ // multi-pass requires vertex position values to be EXACTLY the same for all passes
+ // therefore do NOT batch dynamic multi-pass nodes
+ const bool multiPass = (rpData.data & kPackMultiPassFlag) == kPackMultiPassFlag;
+ const bool dynamicAndMultiPass = (node->renderer->GetStaticBatchIndex() == 0) && multiPass;
+
+ if (dynamicAndMultiPass ||
+ prevRenderState != rs)
+ {
+ m_BatchRenderer.Flush();
+ prevRenderState = rs;
+ canBatch = 0;
+ }
+ else
+ ++canBatch;
+
+ // NOTE: identity matrix has to be set on OpenGLES before lights are set
+ // as lighting is specified in World space
+ device.SetWorldMatrix( Matrix4x4f::identity.GetPtr() );
+
+ renderSettings.SetupAmbient ();
+ SetObjectScale(device, roDataC.lodFade, roDataC.invScale);
+
+ node->renderer->ApplyCustomProperties(*rs.material, rs.shader, rs.subshaderIndex);
+
+ // only setup lights & pass when not batching
+ if (canBatch < 1)
+ {
+ SetupObjectLightmaps (lightmapper, rs.lightmapIndex, rs.lightmapST, true);
+
+ LightManager::SetupVertexLights(rs.lights->lightCount, rs.lights->GetLights());
+ channels = rs.material->SetPassWithShader(rs.passIndex, rs.shader, rs.subshaderIndex);
+ }
+ if (channels)
+ {
+ m_BatchRenderer.Add(node->renderer, subsetIndex, channels, node->worldMatrix, rs.transformType);
+ }
+ }
+
+ m_BatchRenderer.Flush();
+ EndRenderLoop();
+ device.SetSRGBWrite(false);
+ device.SetViewMatrix( m_Context->m_CurCameraMatrix.GetPtr() );
+
+
+ // After everything we might still have renderables that should be drawn at the
+ // very end. Do it.
+ if (!m_Context->m_DontRenderRenderables)
+ {
+ while (renderablesBegin != renderablesEnd && renderablesBegin->first < m_Context->m_RenderQueueStart)
+ ++renderablesBegin;
+ while( renderablesBegin != renderablesEnd && renderablesBegin->first < m_Context->m_RenderQueueEnd )
+ {
+ renderablesBegin->second->RenderRenderable(*m_Context->m_CullResults);
+
+ ++renderablesBegin;
+ }
+ }
+}
+
+
+ForwardVertexRenderLoop* CreateForwardVertexRenderLoop()
+{
+ return new ForwardVertexRenderLoop();
+}
+
+void DeleteForwardVertexRenderLoop (ForwardVertexRenderLoop* queue)
+{
+ delete queue;
+}
+
+
+static bool IsPassSuitable (UInt32 currentRenderOptions, UInt32 passRenderOptions, ShaderPassType passType,
+ bool isLightmapped, bool useRGBM)
+{
+ // All options that a pass requires must be on
+ if( (currentRenderOptions & passRenderOptions) != passRenderOptions )
+ return false; // some options are off, skip this pass
+
+ if (passType != kPassAlways && passType != kPassVertex &&
+ passType != kPassVertexLM && passType != kPassVertexLMRGBM)
+ return false; // unsuitable pass type
+
+ // Use either lightmapped or non-lightmapped pass
+ if ((passType == kPassVertex && isLightmapped) ||
+ ((passType == kPassVertexLM || passType == kPassVertexLMRGBM) && !isLightmapped))
+ return false;
+
+ // Use pass that can properly decode the lightmap
+ if ((passType == kPassVertexLM && useRGBM) ||
+ (passType == kPassVertexLMRGBM && !useRGBM))
+ return false;
+
+ return true;
+}
+
+#if ENABLE_VERTEX_LOOP_HASH_SORTING
+template<typename T>
+static UInt8* InsertIntoHashBufferVtx(const T* p, UInt8* buffer)
+{
+ Assert((sizeof(T) % 4) == 0); // unaligned write
+ *reinterpret_cast<T*>(buffer) = *p;
+ return buffer + sizeof(T);
+}
+#endif
+
+void DoForwardVertexRenderLoop (RenderLoopContext& ctx, RenderObjectDataContainer& objects, bool opaque, ActiveLights& activeLights, bool linearLighting, bool clearFrameBuffer)
+{
+ GPU_AUTO_SECTION(opaque ? kGPUSectionOpaquePass : kGPUSectionTransparentPass);
+
+ using namespace ForwardVertexRenderLoop_Enum;
+
+ // Allocated on the stack each time, uses temp allocators
+ ForwardVertexRenderLoop queue;
+ queue.m_Context = &ctx;
+ queue.m_Objects = &objects;
+ queue.m_RenderObjectsCold.reserve(objects.size());
+ queue.m_PlainRenderPasses.reserve(objects.size());
+ const int kEstimatedLightDataPerObject = sizeof(VertexLightsBlock) + kEstimatedLightsPerObject * sizeof(Light*);
+ queue.m_RenderObjectsLightData.reserve(objects.size() * kEstimatedLightDataPerObject);
+
+ const CullResults& cullResults = *ctx.m_CullResults;
+
+ // figure out current rendering options
+ UInt32 currentRenderOptions = GetCurrentRenderOptions ();
+
+ //RenderSettings& renderSettings = GetRenderSettings();
+ const LightmapSettings& lightmapper = GetLightmapSettings();
+#if UNITY_EDITOR
+ bool useLightmaps = GetLightmapVisualization().GetUseLightmapsForRendering();
+#endif
+
+ bool useRGBM = gGraphicsCaps.SupportsRGBM();
+
+ // Figure everything out
+ RenderObjectDataContainer::iterator itEnd = objects.end();
+ size_t roIndex = 0;
+ for (RenderObjectDataContainer::iterator it = objects.begin(); it != itEnd; ++it, ++roIndex)
+ {
+ RenderObjectData& odata = *it;
+
+ const VisibleNode* node = odata.visibleNode;
+ RODataVLit& roDataC = queue.m_RenderObjectsCold.push_back();
+ size_t visibleNodeIndex = node - cullResults.nodes.begin();
+
+ LightmapSettings::TextureTriple lmTextures = lightmapper.GetLightmapTexture (node->renderer->GetLightmapIndex());
+#if UNITY_EDITOR
+ bool isLightmapped = useLightmaps && lmTextures.first.m_ID;
+#else
+ bool isLightmapped = lmTextures.first.m_ID;
+#endif
+ ShaderLab::IntShader& slshader = *odata.shader->GetShaderLabShader();
+ int vlitSS = odata.subShaderIndex;
+ if (vlitSS == -1)
+ {
+ vlitSS = slshader.GetDefaultSubshaderIndex (isLightmapped ? kRenderPathExtVertexLM : kRenderPathExtVertex);
+ if (vlitSS == -1)
+ continue;
+ }
+ roDataC.subshaderIndex = vlitSS;
+
+ size_t objectLightsOffset = queue.m_RenderObjectsLightData.size();
+ roDataC.lightsDataOffset = objectLightsOffset;
+
+ GetLightManager().FindVertexLightsForObject (
+ queue.m_RenderObjectsLightData,
+ GetObjectLightIndices(cullResults, visibleNodeIndex),
+ GetObjectLightCount(cullResults, visibleNodeIndex),
+ activeLights, *node);
+
+ roDataC.invScale = node->invScale;
+ roDataC.lodFade = node->lodFade;
+
+ // Go over all passes in the shader and add suitable ones for rendering
+ ShaderLab::SubShader& subshader = slshader.GetSubShader(roDataC.subshaderIndex);
+ int shaderPassCount = subshader.GetValidPassCount();
+
+ // Determine if we will need more than a single pass
+ int suitablePasses = 0;
+ for( int pass = 0; pass < shaderPassCount && suitablePasses < 2; ++pass )
+ {
+ ShaderPassType passType; UInt32 passRenderOptions;
+ subshader.GetPass(pass)->GetPassOptions( passType, passRenderOptions );
+
+ if (IsPassSuitable (currentRenderOptions, passRenderOptions, passType, isLightmapped, useRGBM))
+ ++suitablePasses;
+ }
+
+ // Go over all passes in the shader
+ UInt32 firstPassFlag = kPackFirstPassFlag;
+ const UInt32 multiPassFlag = (suitablePasses > 1)? kPackMultiPassFlag: 0;
+ for (int pass = 0; pass < shaderPassCount; ++pass)
+ {
+ ShaderPassType passType;
+ UInt32 passRenderOptions;
+ subshader.GetPass(pass)->GetPassOptions( passType, passRenderOptions );
+
+ if (!IsPassSuitable (currentRenderOptions, passRenderOptions, passType, isLightmapped, useRGBM))
+ continue;
+
+ RPDataVLit& rpData = queue.m_PlainRenderPasses.push_back();
+ rpData.roIndex = roIndex;
+ rpData.data =
+ ((pass & kPackPassMask) << kPackPassShift) |
+ firstPassFlag |
+ multiPassFlag;
+ firstPassFlag = 0;
+
+#if ENABLE_VERTEX_LOOP_HASH_SORTING
+
+ //hash state information for render object sorter
+ const int kHashBufferSize = 64;
+ UInt8 hashBuffer[kHashBufferSize];
+ UInt8* hashPtr = hashBuffer;
+
+ // Always write 32b granularity into the hash buffer to avoid unaligned writes
+ UInt32 rendererType = static_cast<UInt32>(node->renderer->GetRendererType());
+ hashPtr = InsertIntoHashBufferVtx(&rendererType, hashPtr);
+ UInt32 lightmapIndex = odata.lightmapIndex;
+ hashPtr = InsertIntoHashBufferVtx(&lightmapIndex, hashPtr);
+ UInt32 sourceMaterialIndex = 0;
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ hashPtr = InsertIntoHashBufferVtx(&odata.staticBatchIndex, hashPtr);
+ if (odata.staticBatchIndex == 0)
+ sourceMaterialIndex = odata.sourceMaterialIndex;
+#else
+ sourceMaterialIndex = odata.sourceMaterialIndex;
+#endif
+ hashPtr = InsertIntoHashBufferVtx(&sourceMaterialIndex, hashPtr);
+
+ Assert(hashPtr-hashBuffer <= kHashBufferSize);
+
+ Assert(hashPtr-hashBuffer <= kHashBufferSize);
+
+ rpData.hash = MurmurHash2A(hashBuffer, hashPtr-hashBuffer, 0x9747b28c);
+#endif
+ }
+ }
+
+ // sort everything
+ if (opaque)
+ queue.SortRenderPassData<true> (queue.m_PlainRenderPasses);
+ else
+ queue.SortRenderPassData<false> (queue.m_PlainRenderPasses);
+
+ // Render everything. When transitioning to render queues,
+ // it will invoke camera renderables (halos, and so on).
+ RenderTexture* rtMain = ctx.m_Camera->GetCurrentTargetTexture ();
+ queue.PerformRendering (linearLighting && (!rtMain || rtMain->GetSRGBReadWrite()), clearFrameBuffer);
+}
diff --git a/Runtime/Camera/RenderLoops/GlobalLayeringData.h b/Runtime/Camera/RenderLoops/GlobalLayeringData.h
new file mode 100644
index 0000000..176edde
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/GlobalLayeringData.h
@@ -0,0 +1,26 @@
+#pragma once
+
+struct GlobalLayeringData
+{
+ // Per-renderer sorting data.
+ SInt16 layer; // Layer order.
+ SInt16 order; // In-layer order.
+};
+
+inline GlobalLayeringData GlobalLayeringDataCleared () { GlobalLayeringData data = {0,0}; return data; }
+
+inline bool CompareGlobalLayeringData(const GlobalLayeringData& lhs, const GlobalLayeringData& rhs, bool& result)
+{
+ if (lhs.layer != rhs.layer)
+ {
+ result = lhs.layer < rhs.layer;
+ return true;
+ }
+ else if (lhs.order != rhs.order)
+ {
+ result = lhs.order < rhs.order;
+ return true;
+ }
+
+ return false;
+}
diff --git a/Runtime/Camera/RenderLoops/PrePassRenderLoop.cpp b/Runtime/Camera/RenderLoops/PrePassRenderLoop.cpp
new file mode 100644
index 0000000..8aa3ab6
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/PrePassRenderLoop.cpp
@@ -0,0 +1,1958 @@
+#include "UnityPrefix.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+
+#if GFX_SUPPORTS_RENDERLOOP_PREPASS
+#include "RenderLoopPrivate.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Camera/BaseRenderer.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/ImageFilters.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "External/shaderlab/Library/properties.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Shaders/ShaderNameRegistry.h"
+#include "Runtime/Camera/RenderSettings.h"
+#include "Runtime/Misc/ResourceManager.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Graphics/RenderBufferManager.h"
+#include "Runtime/Graphics/GraphicsHelper.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Camera/Light.h"
+#include "Runtime/Camera/Shadows.h"
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Graphics/GeneratedTextures.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/GfxDevice/BatchRendering.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "ReplacementRenderLoop.h"
+#if UNITY_EDITOR
+#include "Runtime/BaseClasses/Tags.h"
+#endif
+#include "BuiltinShaderParamUtility.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+#include "Runtime/Math/SphericalHarmonics.h"
+#include "Runtime/Camera/LightManager.h"
+#include "External/MurmurHash/MurmurHash2.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Graphics/DrawUtil.h"
+
+// Enable/disable hash based pre pass render loop sorting functionality.
+#define ENABLE_PRE_PASS_LOOP_HASH_SORTING 0
+
+#define SEPERATE_PREPASS_SPECULAR UNITY_XENON
+
+PROFILER_INFORMATION(gPrepassSort, "RenderPrePass.Sort", kProfilerRender)
+PROFILER_INFORMATION(gPrepassGeom, "RenderPrePass.GeometryPass", kProfilerRender)
+PROFILER_INFORMATION(gPrepassLighting, "RenderPrePass.Lighting", kProfilerRender)
+PROFILER_INFORMATION(gPrepassLight, "RenderPrePass.Light", kProfilerRender)
+PROFILER_INFORMATION(gPrepassFinal, "RenderPrePass.FinalPass", kProfilerRender)
+PROFILER_INFORMATION(gPrepassFwdDepth, "RenderPrePass.ForwardObjectsToDepth", kProfilerRender)
+PROFILER_INFORMATION(gPrepassCombineDepthNormals, "RenderPrePass.CombineDepthNormals", kProfilerRender)
+
+
+static SHADERPROP (LightPos);
+static SHADERPROP (LightDir);
+static SHADERPROP (LightColor);
+static SHADERPROP (LightTexture0);
+static SHADERPROP (LightBuffer);
+static SHADERPROP (LightAsQuad);
+
+// ShadowMapTexture must be in namespace or otherwise it conflicts with property in
+// ForwardShaderRenderLoop.cpp in batched Android build.
+namespace PrePassPrivate
+{
+static SHADERPROP (ShadowMapTexture);
+}
+
+#if SEPERATE_PREPASS_SPECULAR
+static SHADERPROP (LightSpecBuffer);
+#endif
+
+static Material* s_LightMaterial = NULL;
+static Material* s_CollectMaterial = NULL;
+
+static ShaderKeyword kKeywordHDRLightPrepassOn = keywords::Create ("HDR_LIGHT_PREPASS_ON");
+
+static PPtr<Mesh> s_Icosahedron = NULL;
+static PPtr<Mesh> s_Icosphere = NULL;
+static PPtr<Mesh> s_Pyramid = NULL;
+
+
+enum {
+ kLightingLayerCount = 4, // bits of stencil used for lighting layers
+
+ // 3 highest bits used for excluding lights for other reasons.
+ kStencilMaskSomething = (1<<7), // any object (i.e. not background)
+ kStencilMaskNonLightmapped = (1<<6), // non-lightmapped object
+ kStencilMaskBeyondShadowDistace = (1<<5), // beyond shadow distance
+ kStencilMaskLightBackface = (1<<4), // don't render light where it's backface passes z test
+
+ // Next 4 highest bits (3 down to 0) used for lighting layers.
+ kStencilBitLayerStart = 0, // start of lighting layer bits
+ kStencilMaskLayers = ((1<<kLightingLayerCount)-1) << kStencilBitLayerStart,
+
+ kStencilGeomWriteMask = kStencilMaskSomething | kStencilMaskNonLightmapped | kStencilMaskBeyondShadowDistace | kStencilMaskLayers,
+};
+
+
+
+// Lights can illuminate arbitrary layer masks. Say we have several lights:
+// La = XXXXXXXX
+// Lb = XXXXXXX-
+// Lc = XXXX-XXX
+// Ld = XXXX-X--
+// Layers used for excluding lights are then:
+// ----O-OO (3 in total)
+// In stencil buffer, we allocate 3 consecutive bits to handle this:
+// LaS = ---
+// LbS = --O
+// LcS = O--
+// LdS = OOO
+//
+// When rendering an object, set that bit if object belongs to one of light layers.
+//
+// When drawing a light, set stencil mask to light layer stencil mask, and stencil
+// test should be equal to zero in those bits.
+
+struct LightingLayers
+{
+ enum { kLayerCount = 32 };
+
+ LightingLayers (UInt32 lightMask)
+ : lightingLayerMask(lightMask)
+ {
+ for (int i = 0; i < kLayerCount; ++i)
+ layerToStencil[i] = -1;
+
+ int bit = kStencilBitLayerStart + kLightingLayerCount - 1;
+ lightLayerCount = 0;
+ UInt32 mask = 1;
+ for (int i = 0; i < kLayerCount; ++i, mask<<=1)
+ {
+ if (lightMask & mask)
+ {
+ if (lightLayerCount < kLightingLayerCount)
+ layerToStencil[i] = bit;
+ --bit;
+ ++lightLayerCount;
+ }
+ }
+ }
+
+ UInt32 lightingLayerMask;
+ int layerToStencil[kLayerCount];
+ int lightLayerCount;
+};
+
+struct PrePassRenderData {
+ int roIndex;
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+ UInt32 hash;
+#endif
+};
+typedef dynamic_array<PrePassRenderData> PreRenderPasses;
+
+
+struct PrePassRenderLoop
+{
+ const RenderLoopContext* m_Context;
+ RenderObjectDataContainer* m_Objects;
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ BatchRenderer m_BatchRenderer;
+ #endif
+
+ PreRenderPasses m_PlainRenderPasses;
+
+ RenderTexture* RenderBasePass (RenderTexture* rtMain, const LightingLayers& lightingLayers, RenderObjectDataContainer& outRemainingObjects, MinMaxAABB& receiverBounds);
+
+ void RenderLighting (
+ ActiveLights& activeLights,
+ RenderTexture* rtMain,
+ TextureID depthTextureID,
+ RenderTexture* rtNormalsSpec,
+ RenderTexture*& rtLight,
+
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture*& rtLightSpec,
+#endif
+ const Vector4f& lightFade,
+ const LightingLayers& lightingLayers,
+ MinMaxAABB& receiverBounds,
+ RenderTexture** outMainShadowMap);
+
+ void RenderFinalPass (RenderTexture* rtMain,
+ RenderTexture* rtLight,
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture* rtLightSpec,
+#endif
+ bool hdr,
+ bool linearLighting);
+
+ struct RenderPrePassObjectSorterHash
+ {
+ bool operator()( const PrePassRenderData& ra, const PrePassRenderData& rb ) const;
+ const PrePassRenderLoop* queue;
+ };
+
+ void SortPreRenderPassData( PreRenderPasses& passes )
+ {
+ RenderPrePassObjectSorterHash sorter;
+ sorter.queue = this;
+ std::sort( passes.begin(), passes.end(), sorter );
+ }
+};
+
+
+struct RenderPrePassObjectSorter {
+ bool operator()( const RenderObjectData& ra, const RenderObjectData& rb ) const;
+};
+
+
+
+
+bool RenderPrePassObjectSorter::operator()( const RenderObjectData& ra, const RenderObjectData& rb ) const
+{
+ // Sort by layering depth.
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(ra.globalLayeringData, rb.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+ // Sort by render queues first
+ if( ra.queueIndex != rb.queueIndex )
+ return ra.queueIndex < rb.queueIndex;
+
+ // sort by lightmap index
+ if( ra.lightmapIndex != rb.lightmapIndex )
+ return ra.lightmapIndex < rb.lightmapIndex;
+
+#if GFX_ENABLE_DRAW_CALL_BATCHING
+ // if part of predefined static batch, then sort by static batch index
+ if( ra.staticBatchIndex != rb.staticBatchIndex )
+ return ra.staticBatchIndex > rb.staticBatchIndex; // assuming that statically batched geometry occlude more - render it first
+#endif
+
+ // then sort by material (maybe better sort by shader?)
+ if( ra.material != rb.material )
+ return ra.material->GetInstanceID() < rb.material->GetInstanceID(); // just compare instance IDs
+
+ // Sort front to back
+ return ra.distance > rb.distance;
+}
+
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+bool PrePassRenderLoop::RenderPrePassObjectSorterHash::operator()( const PrePassRenderData& ra, const PrePassRenderData& rb ) const
+{
+ const RenderObjectData& dataa = (*queue->m_Objects)[ra.roIndex];
+ const RenderObjectData& datab = (*queue->m_Objects)[rb.roIndex];
+
+ // Sort by layering depth.
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(dataa.globalLayeringData, datab.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+ // Sort by render queues first
+ if( dataa.queueIndex != datab.queueIndex )
+ return dataa.queueIndex < datab.queueIndex;
+
+ // sort by hash
+ if( ra.hash != rb.hash )
+ return ra.hash < rb.hash;
+
+ // Sort front to back
+ return dataa.distance > datab.distance;
+}
+#endif
+
+static Texture* defaultSpotCookie = NULL;
+
+static void AssignCookieToMaterial(const Light& light, Material* lightMaterial)
+{
+ //@TODO: when computing positions from screen space, mipmapping of cookie will really play against
+ // us, when some adjacent pixels will happen to have very similar UVs. It will sample high levels which
+ // will be mostly black!
+ // Proper fix would be manual derivatives based on something else in the shader, but that needs SM3.0 on D3D
+ // and GLSL in GL. So just use bad mip bias for now.
+
+ Texture* cookie = light.GetCookie();
+
+ if(cookie)
+ {
+ lightMaterial->SetTexture (kSLPropLightTexture0, cookie);
+ }
+ else if(light.GetType() == kLightSpot)
+ {
+ if(!defaultSpotCookie)
+ {
+ defaultSpotCookie = (Texture*)GetRenderSettings().GetDefaultSpotCookie();
+ }
+ lightMaterial->SetTexture (kSLPropLightTexture0, defaultSpotCookie);
+ }
+}
+
+
+// To properly collect & blur directional light's screen space shadow map,
+// we need to have shadow receivers that are forward-rendered in the depth buffer.
+// Also, if camera needs a depth texture, forward-rendered objects should be there
+// as well.
+static void RenderForwardObjectsIntoDepth (
+ const RenderLoopContext& ctx,
+ RenderTexture* rt,
+ RenderObjectDataContainer* forwardRenderedObjects,
+ RenderSurfaceHandle rtColorSurface,
+ RenderSurfaceHandle rtDepthSurface,
+ int width, int height,
+ bool cameraNeedsDepthTexture)
+{
+ Assert (rt);
+
+ if (!forwardRenderedObjects || forwardRenderedObjects->size() == 0)
+ return; // nothing to do
+
+ PROFILER_AUTO_GFX(gPrepassFwdDepth, ctx.m_Camera);
+ GPU_AUTO_SECTION(kGPUSectionOpaquePass);
+
+ Shader* depthShader = GetCameraDepthTextureShader ();
+ if (!depthShader)
+ return;
+
+ // If we do not need the depth texture, leave only the objects that will possibly receive shadows;
+ // no need to render all forward objects.
+ RenderObjectDataContainer forwardRenderedShadowReceivers;
+ if (!cameraNeedsDepthTexture)
+ {
+ size_t n = forwardRenderedObjects->size();
+ forwardRenderedShadowReceivers.reserve (n / 4);
+ for (size_t i = 0; i < n; ++i)
+ {
+ RenderObjectData& roData = (*forwardRenderedObjects)[i];
+ DebugAssert (roData.visibleNode);
+ BaseRenderer* renderer = roData.visibleNode->renderer;
+ DebugAssert (renderer);
+ if (!renderer->GetReceiveShadows())
+ continue; // does not receive shadows
+ Shader* shader = roData.shader;
+ int ss = shader->GetShaderLabShader()->GetDefaultSubshaderIndex (kRenderPathExtForward);
+ if (ss == -1)
+ continue; // is not forward rendered
+ forwardRenderedShadowReceivers.push_back (roData);
+ }
+
+ if (forwardRenderedShadowReceivers.size() == 0)
+ return; // nothing left to render
+ forwardRenderedObjects = &forwardRenderedShadowReceivers;
+ }
+
+ RenderTexture::SetActive (1, &rtColorSurface, rtDepthSurface, rt);
+ RenderSceneShaderReplacement (*forwardRenderedObjects, depthShader, "RenderType");
+}
+
+static RenderTexture* ComputeScreenSpaceShadowMap (
+ const RenderLoopContext& ctx,
+ RenderTexture* shadowMap,
+ float blurWidth,
+ float blurFade,
+ ShadowType shadowType)
+{
+ Assert (shadowMap);
+
+ GfxDevice& device = GetGfxDevice();
+
+ if (!s_CollectMaterial)
+ {
+ Shader* shader = GetScriptMapper().FindShader ("Hidden/Internal-PrePassCollectShadows");
+ s_CollectMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ }
+
+ SetShadowsKeywords (kLightDirectional, shadowType, false, false);
+ RenderBufferManager& rbm = GetRenderBufferManager ();
+
+ RenderTexture* screenShadowMap = rbm.GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormatNone, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ RenderTexture::SetActive (screenShadowMap);
+
+ // Clear so that tiled and multi-GPU systems don't do a RT unresolve
+ float clearColor[4] = {1,0,1,0};
+ device.Clear(kGfxClearColor, clearColor, 1.0f, 0);
+
+ LoadFullScreenOrthoMatrix ();
+ s_CollectMaterial->SetTexture (PrePassPrivate::kSLPropShadowMapTexture, shadowMap);
+ s_CollectMaterial->SetPass (0);
+
+ Vector3f ray;
+ device.ImmediateBegin (kPrimitiveQuads);
+
+ float x1 = 0.0f;
+ float x2 = 1.0f;
+ float y1 = 0.0f;
+ float y2 = 1.0f;
+ float f = ctx.m_Camera->GetProjectionFar();
+
+ const Transform& camtr = ctx.m_Camera->GetComponent(Transform);
+ Matrix4x4f cameraWorldToLocalNoScale = camtr.GetWorldToLocalMatrixNoScale();
+
+ device.ImmediateTexCoord (0, x1, y1, 0.0f);
+ ray = cameraWorldToLocalNoScale.MultiplyPoint3(ctx.m_Camera->ViewportToWorldPoint (Vector3f(x1, y1, f)));
+ device.ImmediateNormal (ray.x, ray.y, ray.z);
+ device.ImmediateVertex (x1, y1, 0.1f);
+
+ device.ImmediateTexCoord (0, x2, y1, 0.0f);
+ ray = cameraWorldToLocalNoScale.MultiplyPoint3(ctx.m_Camera->ViewportToWorldPoint (Vector3f(x2, y1, f)));
+ device.ImmediateNormal (ray.x, ray.y, ray.z);
+ device.ImmediateVertex (x2, y1, 0.1f);
+
+ device.ImmediateTexCoord (0, x2, y2, 0.0f);
+ ray = cameraWorldToLocalNoScale.MultiplyPoint3(ctx.m_Camera->ViewportToWorldPoint (Vector3f(x2, y2, f)));
+ device.ImmediateNormal (ray.x, ray.y, ray.z);
+ device.ImmediateVertex (x2, y2, 0.1f);
+
+ device.ImmediateTexCoord (0, x1, y2, 0.0f);
+ ray = cameraWorldToLocalNoScale.MultiplyPoint3(ctx.m_Camera->ViewportToWorldPoint (Vector3f(x1, y2, f)));
+ device.ImmediateNormal (ray.x, ray.y, ray.z);
+ device.ImmediateVertex (x1, y2, 0.1f);
+
+ device.ImmediateEnd ();
+ GPU_TIMESTAMP();
+
+ rbm.ReleaseTempBuffer (shadowMap);
+
+ // possibly blur into another screen-space render texture
+ SetShadowsKeywords (kLightDirectional, shadowType, true, true);
+ if (IsSoftShadow(shadowType) && GetSoftShadowsEnabled())
+ return BlurScreenShadowMap (screenShadowMap, shadowType, f, blurWidth, blurFade);
+
+ return screenShadowMap;
+}
+
+static void RenderLightGeom (const RenderLoopContext& ctx, const ActiveLight& light, const Vector3f& lightPos, const Matrix4x4f& lightMatrix, const bool renderAsQuad)
+{
+ // Spot and point lights: render as tight geometry. If it doesn't intersect near or far, stencil optimisation will be used
+ // (rendering the z tested back faces into stencil and then front faces will only pass for these pixels).
+ // If it intersects near, back faces with z test greater will be rendered (shouldn't use that when not intersecting near, because
+ // then there could be objects between the cam and the light, not touching the light).
+ // If it intersects far, render front faces without any gimmicks.
+ // If it intersects both near and far, render as a quad.
+
+ GfxDevice& device = GetGfxDevice();
+ Light& l = *light.light;
+ float r = l.GetRange();
+ float n = ctx.m_Camera->GetProjectionNear() * 1.001f;
+
+ if (l.GetType() == kLightPoint && !renderAsQuad)
+ {
+ #if GFX_USE_SPHERE_FOR_POINT_LIGHT
+ ChannelAssigns ch;
+ ch.Bind (kShaderChannelVertex, kVertexCompVertex);
+
+ // Older content might have included/overriden old Internal-PrePassLighting.shader,
+ // which relied on normals being zeros here. Light .fbx files have zero normals just for that.
+ if (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1))
+ ch.Bind (kShaderChannelNormal, kVertexCompNormal);
+
+ Matrix4x4f m;
+ m.SetTranslate (lightPos);
+ m.Get (0, 0) = r;
+ m.Get (1, 1) = r;
+ m.Get (2, 2) = r;
+ // Point lights bigger than 0.25 of the screen height can be rendered with high-poly, but tighter geometry.
+ DrawUtil::DrawMesh (ch, light.screenRect.height > 0.25f ? *s_Icosphere : *s_Icosahedron, m, -1);
+ #else
+ // PS3 is not the best at vertex processing, so stick to low-poly meshes
+ device.ImmediateShape(lightPos.x, lightPos.y, lightPos.z, r, GfxDevice::kShapeCube);
+ #endif
+ }
+ else if (l.GetType() == kLightSpot && !renderAsQuad)
+ {
+ Matrix4x4f m (lightMatrix);
+ ChannelAssigns ch;
+ ch.Bind (kShaderChannelVertex, kVertexCompVertex);
+ if (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_3_a1))
+ ch.Bind (kShaderChannelNormal, kVertexCompNormal);
+ float sideLength = r / l.GetCotanHalfSpotAngle ();
+ m.Scale (Vector3f(sideLength, sideLength, r));
+ DrawUtil::DrawMesh (ch, *s_Pyramid, m, -1);
+ }
+ else // Directional light or spot/point that needs to be rendered as a quad
+ {
+ DeviceViewProjMatricesState preserveViewProj;
+
+ const Camera* camera = ctx.m_Camera;
+ float nearPlane = 0;
+
+ float x1 = light.screenRect.x;
+ float x2 = light.screenRect.x + light.screenRect.width;
+ float y1 = light.screenRect.y;
+ float y2 = light.screenRect.y + light.screenRect.height;
+
+ // Calculate rays pointing from the camera to the near plane's corners in camera space
+ Vector3f ray1 = camera->ViewportToCameraPoint (Vector3f(x1, y1, n));
+ Vector3f ray2 = camera->ViewportToCameraPoint (Vector3f(x1, y2, n));
+ Vector3f ray3 = camera->ViewportToCameraPoint (Vector3f(x2, y2, n));
+ Vector3f ray4 = camera->ViewportToCameraPoint (Vector3f(x2, y1, n));
+
+ // Set up orthographic projection not to have to deal with precision problems
+ // that show up when drawing a full screen quad in perspective projection in world space.
+ LoadFullScreenOrthoMatrix (nearPlane, camera->GetProjectionFar(), true);
+
+ // Draw the fullscreen quad on the near plane
+ device.ImmediateBegin (kPrimitiveQuads);
+
+ device.ImmediateNormal (ray1.x, ray1.y, ray1.z);
+ device.ImmediateVertex (x1, y1, nearPlane);
+
+ device.ImmediateNormal (ray2.x, ray2.y, ray2.z);
+ device.ImmediateVertex (x1, y2, nearPlane);
+
+ device.ImmediateNormal (ray3.x, ray3.y, ray3.z);
+ device.ImmediateVertex (x2, y2, nearPlane);
+
+ device.ImmediateNormal (ray4.x, ray4.y, ray4.z);
+ device.ImmediateVertex (x2, y1, nearPlane);
+
+ device.ImmediateEnd ();
+ GPU_TIMESTAMP();
+ }
+}
+
+static UInt32 LightMask (const Light& l, const LightingLayers& lightingLayers)
+{
+ UInt32 mask = 0U;
+ UInt32 lightExcludeLayers = ~l.GetCullingMask();
+ int bit = 0;
+ while (lightExcludeLayers)
+ {
+ if (lightExcludeLayers & 1)
+ {
+ int layerStencilBit = lightingLayers.layerToStencil[bit];
+ if (layerStencilBit != -1)
+ mask |= 1 << layerStencilBit;
+ }
+ lightExcludeLayers >>= 1;
+ ++bit;
+ }
+ return mask;
+}
+
+static RenderTexture* RenderLight (
+ const RenderLoopContext& ctx,
+ const ShadowCullData& shadowCullData,
+ QualitySettings::ShadowQuality shadowQuality,
+ const LightmapSettings::LightmapsMode lightmapsMode,
+ RenderTexture*& rtLight,
+ RenderTexture* rtMain,
+ int width, int height,
+ DeviceStencilState* devStDisabled,
+ const MinMaxAABB& receiverBounds,
+ const DeviceMVPMatricesState& mvpState,
+ const Vector4f& lightFade,
+ const LightingLayers& lightingLayers,
+ const ActiveLight& light,
+#if SEPERATE_PREPASS_SPECULAR
+ bool specularPass,
+#endif
+ bool returnShadowMap)
+{
+ Light& l = *light.light;
+
+ PROFILER_AUTO_GFX(gPrepassLight, &l);
+
+ const Light::Lightmapping lightmappingMode = l.GetLightmappingForRender();
+ const Transform& trans = l.GetComponent(Transform);
+ Matrix4x4f lightMatrix = trans.GetLocalToWorldMatrixNoScale();
+ Vector3f lightPos = lightMatrix.GetPosition();
+
+ Assert(light.isVisibleInPrepass);
+ Assert(!light.screenRect.IsEmpty());
+
+ ShadowType lightShadows = l.GetShadows();
+ // Shadows on local lights are Pro only
+ if (lightShadows != kShadowNone && l.GetType() != kLightDirectional &&
+ !GetBuildSettings().hasLocalLightShadows)
+ lightShadows = kShadowNone;
+
+ // Check if soft shadows are allowed by license, quality settings etc.
+ if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_1_a1) &&
+ lightShadows > kShadowHard && !GetSoftShadowsEnabled())
+ lightShadows = kShadowHard;
+
+ GfxDevice& device = GetGfxDevice();
+ BuiltinShaderParamValues& params = device.GetBuiltinParamValues();
+
+ RenderSurfaceHandle rtSurfaceColor;
+ RenderSurfaceHandle rtSurfaceDepth = rtMain->GetDepthSurfaceHandle(); // re-use depth from final target
+ RenderSurfaceHandle rtSurfaceMainColor = rtMain->GetColorSurfaceHandle(); // will allocate color later (if any lights will actually be present)
+
+ bool hdr = ctx.m_Camera->GetUsingHDR();
+ float white[] = {1,1,1,1};
+ float black[] = {0,0,0,0};
+ UInt32 rtFlags = RenderTexture::kFlagDontRestoreColor;
+
+ if (!rtLight)
+ {
+ rtLight = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormatNone, hdr ? GetGfxDevice().GetDefaultHDRRTFormat() : kRTFormatARGB32, 0, kRTReadWriteLinear);
+
+ if (!rtLight->IsCreated())
+ rtLight->Create();
+
+ rtLight->SetFilterMode (kTexFilterNearest);
+ rtSurfaceColor = rtLight->GetColorSurfaceHandle();
+
+
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtLight, 0, kCubeFaceUnknown, rtFlags);
+ GraphicsHelper::Clear(kGfxClearColor, hdr ? black : white, 1.0f, 0);
+ GPU_TIMESTAMP();
+ }
+
+ rtSurfaceColor = rtLight->GetColorSurfaceHandle();
+
+ l.SetLightKeyword();
+
+ Vector3f lightDir = lightMatrix.GetAxisZ();
+ ColorRGBAf lightCol = GammaToActiveColorSpace (l.GetColor()) * l.GetIntensity() * 2.0f;
+
+ Matrix4x4f temp1, temp2, temp3;
+ if (l.GetType() == kLightSpot)
+ {
+ Matrix4x4f worldToLight = l.GetWorldToLocalMatrix();
+ {
+ temp1.SetScale (Vector3f (-.5f, -.5f, 1.0f));
+ temp2.SetTranslate (Vector3f (.5f, .5f, 0.0f));
+ temp3.SetPerspectiveCotan( l.GetCotanHalfSpotAngle(), 0.0f, l.GetRange() );
+ // temp2 * temp3 * temp1 * worldToLight
+ Matrix4x4f temp4;
+ MultiplyMatrices4x4 (&temp2, &temp3, &temp4);
+ MultiplyMatrices4x4 (&temp4, &temp1, &temp2);
+ MultiplyMatrices4x4 (&temp2, &worldToLight, &params.GetWritableMatrixParam(kShaderMatLightMatrix));
+ }
+ }
+ else if (l.GetCookie())
+ {
+ if (l.GetType() == kLightPoint)
+ {
+ params.SetMatrixParam(kShaderMatLightMatrix, l.GetWorldToLocalMatrix());
+ }
+ else if (l.GetType() == kLightDirectional)
+ {
+ float scale = 1.0f / l.GetCookieSize();
+ temp1.SetScale (Vector3f (scale, scale, 0));
+ temp2.SetTranslate (Vector3f (.5f, .5f, 0));
+ // temp2 * temp1 * l.GetWorldToLocalMatrix()
+ MultiplyMatrices4x4 (&temp2, &temp1, &temp3);
+ MultiplyMatrices4x4 (&temp3, &l.GetWorldToLocalMatrix(), &params.GetWritableMatrixParam(kShaderMatLightMatrix));
+ }
+ }
+
+ AssignCookieToMaterial(l, s_LightMaterial);
+
+ const bool renderAsQuad = light.intersectsNear && light.intersectsFar || l.GetType() == kLightDirectional;
+ ShaderLab::g_GlobalProperties->SetFloat(kSLPropLightAsQuad, renderAsQuad ? 1.0f : 0.0f);
+ ShaderLab::g_GlobalProperties->SetVector (kSLPropLightPos, lightPos.x, lightPos.y, lightPos.z, 1.0f / (l.GetRange() * l.GetRange()));
+ ShaderLab::g_GlobalProperties->SetVector (kSLPropLightDir, lightDir.x, lightDir.y, lightDir.z, 0.0f);
+ ShaderLab::g_GlobalProperties->SetVector (kSLPropLightColor, lightCol.GetPtr());
+ ///@TODO: cleanup, remove this from Internal-PrePassLighting shader
+ s_LightMaterial->SetTexture (ShaderLab::Property("_LightTextureB0"), builtintex::GetAttenuationTexture());
+
+ RenderTexture* shadowMap = NULL;
+ ShadowCameraData camData(shadowCullData);
+
+
+ if (light.shadowedLight != NULL && receiverBounds.IsValid() && shadowQuality != QualitySettings::kShadowsDisable)
+ {
+ Assert(light.insideShadowRange);
+
+ ShadowType lightShadows = l.GetShadows();
+
+ if (IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_1_a1))
+ {
+ if (shadowQuality == QualitySettings::kShadowsHardOnly && lightShadows != kShadowNone)
+ lightShadows = kShadowHard;
+ }
+
+ SetShadowsKeywords (l.GetType(), lightShadows, false, true);
+
+ Matrix4x4f shadowMatrices[kMaxShadowCascades];
+ device.SetViewMatrix (ctx.m_CurCameraMatrix.GetPtr());
+ device.SetStencilState (devStDisabled, 0);
+
+ // Rendering shadowmaps will switch away from the lighting buffer and then will switch back.
+ // Nothing we can do about it, so don't produce the warning.
+ device.IgnoreNextUnresolveOnCurrentRenderTarget();
+
+ shadowMap = RenderShadowMaps (camData, light, receiverBounds, false, shadowMatrices);
+
+ if (!shadowMap)
+ {
+ // If shadow map could not actually be created (no casters, out of VRAM, whatever),
+ // set the no shadows keywords and proceed. So there will be no shadows,
+ // but otherwise it will be ok.
+ SetNoShadowsKeywords();
+ }
+ else
+ {
+ Vector4f data;
+
+ // ambient & shadow fade out
+ data.x = 1.0f - l.GetShadowStrength(); // R = 1-strength
+ data.y = data.z = data.w = 0.0f;
+ params.SetVectorParam(kShaderVecLightShadowData, data);
+
+ if (l.GetType() == kLightDirectional)
+ {
+ params.SetMatrixParam(kShaderMatWorldToShadow, shadowMatrices[0]);
+ SetCascadedShadowShaderParams (shadowMatrices, camData.splitDistances, camData.splitSphereCentersAndSquaredRadii);
+
+ shadowMap = ComputeScreenSpaceShadowMap (
+ ctx,
+ shadowMap,
+ l.GetShadowSoftness(),
+ l.GetShadowSoftnessFade(),
+ lightShadows);
+ }
+ else if (l.GetType() == kLightSpot)
+ {
+ params.SetMatrixParam(kShaderMatWorldToShadow, shadowMatrices[0]);
+ }
+
+ // texel offsets for PCF
+ float offX = 0.5f / shadowMap->GetGLWidth();
+ float offY = 0.5f / shadowMap->GetGLHeight();
+ data.z = 0.0f; data.w = 0.0f;
+ data.x = -offX; data.y = -offY; params.SetVectorParam(kShaderVecShadowOffset0, data);
+ data.x = offX; data.y = -offY; params.SetVectorParam(kShaderVecShadowOffset1, data);
+ data.x = -offX; data.y = offY; params.SetVectorParam(kShaderVecShadowOffset2, data);
+ data.x = offX; data.y = offY; params.SetVectorParam(kShaderVecShadowOffset3, data);
+ s_LightMaterial->SetTexture (PrePassPrivate::kSLPropShadowMapTexture, shadowMap);
+
+ if (rtLight != NULL)
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtLight);
+ else
+ RenderTexture::SetActive (1, &rtSurfaceMainColor, rtSurfaceDepth, rtMain);
+ }
+ device.SetViewMatrix(mvpState.GetView().GetPtr());
+ device.SetProjectionMatrix(mvpState.GetProj());
+ SetClippingPlaneShaderProps();
+
+ // restore the cull mode, since it could be changed by a shadow caster with odd-negative scale
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+ }
+ else
+ {
+ SetNoShadowsKeywords ();
+ }
+
+ // Draw each light in two passes: illuminate non-lightmapped objects; illuminate lightmapped objects.
+ int lightPassCount = 2;
+ int lightPassAddBits[2] = { kStencilMaskNonLightmapped, 0 };
+ if (lightmappingMode == Light::kLightmappingRealtimeOnly)
+ {
+ // If light is realtime only, it's enough to draw one pass; that illuminates any object
+ // be it lightmapped or not.
+ lightPassCount = 1;
+ lightPassAddBits[0] = 0;
+ }
+ else if (lightmappingMode == Light::kLightmappingAuto && lightmapsMode != LightmapSettings::kDualLightmapsMode)
+ {
+ // If it's an auto light but we're in single lightmaps mode, draw in one pass only to illuminate
+ // non-lightmapped objects
+ // TODO: realtime shadows from auto lights won't be received by lightmapped objects. Do we want to fix it?
+ lightPassCount = 1;
+ lightPassAddBits[0] = kStencilMaskNonLightmapped;
+ }
+
+ //TODO: skip if smaller than certain size
+ const bool useStencilMask = !light.intersectsNear && !light.intersectsFar &&
+ (lightmappingMode == Light::kLightmappingRealtimeOnly) &&
+ (l.GetType() == kLightSpot || l.GetType() == kLightPoint );
+
+ const UInt32 lightmask = LightMask (l, lightingLayers);
+
+ // Render stencil mask, to discard all light pixels, at which the light is fully in front of scene geometry.
+ if (useStencilMask)
+ {
+ Material::GetDefault ()->SetPass (0);
+ #if UNITY_XENON
+ device.SetNullPixelShader ();
+ #endif
+
+ GfxBlendState blendstate;
+ blendstate.renderTargetWriteMask = 0U;
+ device.SetBlendState (device.CreateBlendState(blendstate), 0);
+
+ GfxRasterState rasterstate;
+ rasterstate.cullMode = kCullOff;
+ device.SetRasterState (device.CreateRasterState(rasterstate));
+
+ GfxDepthState depthstate;
+ depthstate.depthWrite = false;
+ depthstate.depthFunc = kFuncLEqual;
+ device.SetDepthState (device.CreateDepthState(depthstate));
+
+ GfxStencilState lightStencil;
+ lightStencil.stencilEnable = true;
+ lightStencil.readMask = 0xFFU;
+ lightStencil.writeMask = kStencilMaskLightBackface;
+ lightStencil.stencilZFailOpBack = kStencilOpInvert;
+ lightStencil.stencilZFailOpFront = kStencilOpInvert;
+ lightStencil.stencilPassOpBack = kStencilOpKeep;
+ lightStencil.stencilPassOpFront = kStencilOpKeep;
+ lightStencil.stencilFuncBack = (lightmask != 0 ) ? kFuncNotEqual : kFuncAlways;
+ lightStencil.stencilFuncFront = (lightmask != 0 ) ? kFuncNotEqual : kFuncAlways;
+ device.SetStencilState (device.CreateStencilState(lightStencil), lightmask|kStencilMaskSomething|kStencilMaskNonLightmapped);
+
+ #if UNITY_XENON
+ // Clear within light-geom, sets all HiS to cull.
+ // Set to cull where equal to background (to deal with lightmasks), unoptimal but works
+ if (useStencilMask)
+ device.SetHiStencilState (false, true, kStencilMaskSomething|kStencilMaskNonLightmapped, kFuncEqual);
+ #endif
+
+ RenderLightGeom (ctx, light, lightPos, lightMatrix, renderAsQuad);
+
+ blendstate.renderTargetWriteMask = KColorWriteAll;
+ device.SetBlendState (device.CreateBlendState(blendstate), 0);
+
+ #if UNITY_XENON
+ device.HiStencilFlush (kHiSflush_sync);
+ #endif
+ }
+
+ for (int pp = 0; pp < lightPassCount; ++pp)
+ {
+ Vector4f lightingFade = lightFade;
+ Vector4f shadowFade = lightFade;
+ shadowFade.x = 1.0f - l.GetShadowStrength();
+ if (pp == 0 || lightmappingMode == Light::kLightmappingRealtimeOnly)
+ lightingFade.z = lightingFade.w = 0.0f;
+ else
+ shadowFade.z = shadowFade.w = 0.0f;
+ params.SetVectorParam(kShaderVecLightmapFade, lightingFade);
+ params.SetVectorParam(kShaderVecLightShadowData, shadowFade);
+
+ // Disable mipmapping on light cookies
+ ShaderLab::TexEnv* cookieEnv = s_LightMaterial->GetProperties().GetTexEnv(kSLPropLightTexture0);
+ if (cookieEnv)
+ {
+ cookieEnv->TextureMipBiasChanged (-8);
+ }
+
+ #if SEPERATE_PREPASS_SPECULAR
+ if (s_LightMaterial->GetPassCount () > 2 && ctx.m_Camera->GetUsingHDR() && specularPass)
+ s_LightMaterial->SetPass (2);
+ else
+ #endif
+ if (s_LightMaterial->GetPassCount () > 1 && ctx.m_Camera->GetUsingHDR())
+ s_LightMaterial->SetPass (1);
+ else
+ s_LightMaterial->SetPass (0);
+
+ // Construct stencil read mask
+ GfxStencilState stencil;
+ stencil.stencilEnable = true;
+ stencil.stencilFuncFront = stencil.stencilFuncBack = kFuncEqual;
+ stencil.readMask = kStencilMaskSomething;
+ // Check lightmapped vs. non-lightmapped unless it's a realtime light that
+ // only cares about not illuminating non-something.
+ if (lightmappingMode != Light::kLightmappingRealtimeOnly)
+ stencil.readMask |= kStencilMaskNonLightmapped;
+
+ if (pp != 0 && lightmappingMode != Light::kLightmappingRealtimeOnly)
+ stencil.readMask |= kStencilMaskBeyondShadowDistace;
+
+ stencil.readMask |= lightmask;
+ int stencilRef = kStencilMaskSomething + lightPassAddBits[pp];
+
+ if (useStencilMask)
+ {
+ // Clear stencil while rendering
+ stencil.writeMask = kStencilMaskLightBackface;
+ stencil.stencilZFailOpBack = kStencilOpZero;
+ stencil.stencilZFailOpFront = kStencilOpZero;
+ stencil.stencilPassOpBack = kStencilOpZero;
+ stencil.stencilPassOpFront = kStencilOpZero;
+ // Clear the kStencilMaskLightBackface bit even if rejecting pixel due to stencil layer mask
+ stencil.stencilFailOpBack = kStencilOpZero;
+ stencil.stencilFailOpFront = kStencilOpZero;
+
+ stencil.readMask |= kStencilMaskLightBackface;
+ stencilRef |= kStencilMaskLightBackface;
+ }
+
+ DeviceStencilState* devStCheck = device.CreateStencilState (stencil);
+ device.SetStencilState (devStCheck, stencilRef);
+
+ #if UNITY_XENON
+ // Set to cull when all == background (to deal with lightmasks), unoptimal but works
+ if (useStencilMask)
+ device.SetHiStencilState (true, true, kStencilMaskSomething|kStencilMaskNonLightmapped, kFuncEqual);
+ #endif
+
+ // Draw light shape
+ GfxRasterState rasterstate;
+ GfxDepthState depthstate;
+ depthstate.depthWrite = false;
+ if (light.intersectsNear && !light.intersectsFar && (l.GetType() == kLightSpot || l.GetType() == kLightPoint))
+ {
+ // When near (but not far) plane intersects the light, render back faces (tighter than rendering a bounding quad).
+ // Can't use this when not intersecting, since it would waste processing for objects between
+ // the light and the cam, even when they don't touch the light.
+ rasterstate.cullMode = kCullFront;
+ depthstate.depthFunc = kFuncGreater;
+ }
+ else
+ {
+ depthstate.depthFunc = kFuncLEqual;
+ #if UNITY_XENON
+ device.SetHiZEnable (kHiZEnable);
+ #endif
+ }
+ device.SetRasterState (device.CreateRasterState (rasterstate));
+ device.SetDepthState (device.CreateDepthState (depthstate));
+
+ RenderLightGeom (ctx, light, lightPos, lightMatrix, renderAsQuad);
+
+ #if UNITY_XENON
+ device.SetHiZEnable (kHiZAuto);
+ if (useStencilMask)
+ device.HiStencilFlush (kHiSflush_async);
+ #endif
+ }
+
+ if (shadowMap && !returnShadowMap)
+ GetRenderBufferManager().ReleaseTempBuffer (shadowMap);
+
+ return returnShadowMap ? shadowMap : NULL;
+}
+
+
+void PrePassRenderLoop::RenderLighting (
+ ActiveLights& activeLights,
+ RenderTexture* rtMain,
+ TextureID depthTextureID,
+ RenderTexture* rtNormalsSpec,
+ RenderTexture*& rtLight,
+
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture*& rtLightSpec,
+#endif
+ const Vector4f& lightFade,
+ const LightingLayers& lightingLayers,
+ MinMaxAABB& receiverBounds,
+ RenderTexture** outMainShadowMap)
+{
+ PROFILER_AUTO_GFX(gPrepassLighting, m_Context->m_Camera);
+ GPU_AUTO_SECTION(kGPUSectionDeferedLighting);
+ *outMainShadowMap = NULL;
+
+ Assert(rtLight == NULL);
+#if SEPERATE_PREPASS_SPECULAR
+ Assert(rtLightSpec == NULL);
+#endif
+ const QualitySettings::ShadowQuality shadowQuality = static_cast<QualitySettings::ShadowQuality>(GetQualitySettings().GetCurrent().shadows);
+ const LightmapSettings::LightmapsMode lightmapsMode = static_cast<LightmapSettings::LightmapsMode>(GetLightmapSettings().GetLightmapsMode());
+
+ ShadowCameraData camData(*m_Context->m_ShadowCullData);
+
+ // Prevent receiver bounds to be zero size in any dimension;
+ // causes trouble with calculating intersection of frustum and bounds.
+ receiverBounds.Expand( 0.01f );
+
+ const Rectf screenRect = m_Context->m_Camera->GetScreenViewportRect();
+
+ if (!s_LightMaterial) {
+ Shader* shader = GetScriptMapper().FindShader ("Hidden/Internal-PrePassLighting");
+ s_LightMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ }
+
+ if (s_Icosahedron.IsNull ())
+ s_Icosahedron = GetBuiltinResource<Mesh> ("icosahedron.fbx");
+ if (s_Icosphere.IsNull ())
+ s_Icosphere = GetBuiltinResource<Mesh> ("icosphere.fbx");
+ if (s_Pyramid.IsNull ())
+ s_Pyramid = GetBuiltinResource<Mesh> ("pyramid.fbx");
+
+ static SHADERPROP (CameraDepthTexture);
+ static SHADERPROP (CameraNormalsTexture);
+ const int width = rtNormalsSpec->GetGLWidth();
+ const int height = rtNormalsSpec->GetGLHeight();
+ if (gGraphicsCaps.hasStencilInDepthTexture)
+ {
+ ShaderLab::g_GlobalProperties->SetRectTextureID (
+ kSLPropCameraDepthTexture,
+ depthTextureID,
+ width,
+ height,
+ rtMain->GetTexelSizeX(),
+ rtMain->GetTexelSizeY(),
+ rtMain->GetUVScaleX(),
+ rtMain->GetUVScaleY()
+ );
+ }
+
+ // set as _CameraNormalsTexture for external access
+ ShaderLab::g_GlobalProperties->SetTexture (kSLPropCameraNormalsTexture, rtNormalsSpec);
+
+ GfxDevice& device = GetGfxDevice();
+
+ SetAndRestoreWireframeMode setWireframeOff(false); // turn off wireframe; will restore old value in destructor
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+
+ DeviceStencilState* devStDisabled = device.CreateStencilState (GfxStencilState());
+
+
+ DeviceMVPMatricesState preserveMVP;
+
+ device.SetWorldMatrix (Matrix4x4f::identity.GetPtr());
+
+ RenderTexture** currentLightTex = &rtLight;
+#if SEPERATE_PREPASS_SPECULAR
+ //Do 2 passes for HDR prepass lighting on xenon
+ for (int lp = 0; lp < (m_Context->m_Camera->GetUsingHDR() ? 2 : 1); ++lp)
+ {
+ if (lp == 0)
+ currentLightTex = &rtLight;
+ else
+ currentLightTex = &rtLightSpec;
+#endif
+
+ const ActiveLight* mainActiveLight = GetMainActiveLight(activeLights);
+ ActiveLights::Array::iterator it, itEnd = activeLights.lights.end();
+ for (it = activeLights.lights.begin(); it != itEnd; ++it)
+ {
+ if (!it->isVisibleInPrepass)
+ continue;
+ if (&*it == mainActiveLight)
+ {
+ // skip main light now; will render it last
+ continue;
+ }
+ RenderLight (*m_Context, camData, shadowQuality, lightmapsMode,
+ *currentLightTex,
+ rtMain,
+ width, height, devStDisabled, receiverBounds,
+ preserveMVP, lightFade, lightingLayers, *it,
+#if SEPERATE_PREPASS_SPECULAR
+ lp == 1,
+#endif
+ false);
+ }
+
+ #if UNITY_XENON
+ device.SetStencilState (devStDisabled, 0);
+ device.SetHiStencilState (false, false, 0, kFuncEqual);
+ #endif
+
+ // render main light
+ if (mainActiveLight)
+ {
+ RenderTexture* shadowMap = RenderLight (
+ *m_Context, camData, shadowQuality, lightmapsMode,
+ *currentLightTex,
+ rtMain,
+ width, height, devStDisabled, receiverBounds,
+ preserveMVP, lightFade, lightingLayers, *mainActiveLight,
+#if SEPERATE_PREPASS_SPECULAR
+ lp == 1,
+#endif
+ true);
+ if (shadowMap)
+ {
+ AddRenderLoopTempBuffer (m_Context->m_RenderLoop, shadowMap);
+ *outMainShadowMap = shadowMap;
+ }
+ }
+#if SEPERATE_PREPASS_SPECULAR
+ }
+#endif
+ SetNoShadowsKeywords ();
+
+ Vector4f lightmapFade = lightFade;
+ // if we're not in dual lightmaps mode, always use the far lightmap, i.e. lightmapFade = 1
+ if (GetLightmapSettings().GetLightmapsMode() != LightmapSettings::kDualLightmapsMode)
+ lightmapFade.z = lightmapFade.w = 1.0f;
+
+ device.GetBuiltinParamValues().SetVectorParam(kShaderVecLightmapFade, lightmapFade);
+
+ device.SetStencilState (devStDisabled, 0);
+
+ #if !UNITY_XENON
+ // Ok, we didn't really have any lights worth rendering.
+ // Create a small render texture and clear it to white and pass it as the lighting buffer.
+ // Don't do that on 360; pointless and saves a resolve.
+ if (!rtLight)
+ {
+ rtLight = GetRenderBufferManager().GetTempBuffer (16, 16, kDepthFormatNone, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ RenderTexture::SetActive (rtLight);
+ float white[] = {1,1,1,1};
+ float black[] = {0,0,0,0};
+ GraphicsHelper::Clear (kGfxClearColor, m_Context->m_Camera->GetUsingHDR() ? black : white, 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ // We just switched away from a Z buffer (only in case when no lights were there!),
+ // and we'll switch back to it. So ignore the unresolve warning on it.
+ device.IgnoreNextUnresolveOnRS(rtMain->GetDepthSurfaceHandle());
+ }
+ #endif
+}
+
+
+static RenderTexture* CombineDepthNormalsTexture (const RenderLoopContext& ctx, RenderObjectDataContainer& remainingObjects)
+{
+ PROFILER_AUTO_GFX(gPrepassCombineDepthNormals, ctx.m_Camera);
+
+ static Material* s_CombineMaterial = NULL;
+ if (!s_CombineMaterial)
+ {
+ Shader* shader = GetScriptMapper ().FindShader ("Hidden/Internal-CombineDepthNormals");
+ if (shader)
+ s_CombineMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ if (!s_CombineMaterial) {
+ AssertString ("Coult not find depth+normals combine shader");
+ return NULL;
+ }
+ }
+
+ RenderTexture* depthNormals = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormatNone, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ RenderTexture::SetActive (depthNormals);
+ GraphicsHelper::Clear (kGfxClearColor, ColorRGBAf(0.5f,0.5f,1.0f,1.0f).GetPtr(), 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ // Combine depth & normals into single texture
+ ImageFilters::Blit (NULL, depthNormals, s_CombineMaterial, 0, false);
+
+ AddRenderLoopTempBuffer (ctx.m_RenderLoop, depthNormals);
+
+ static SHADERPROP (CameraDepthNormalsTexture);
+ ShaderLab::g_GlobalProperties->SetTexture (kSLPropCameraDepthNormalsTexture, depthNormals);
+
+ return depthNormals;
+}
+
+
+
+// Separate pass to render depth into a separate target. Only used on Macs with Radeon HDs, since
+// only there doing it the regular way is broken.
+#if GFX_SUPPORTS_OPENGL
+static RenderTexture* RenderBasePassDepth (const RenderLoopContext& ctx, RenderObjectDataContainer& renderData, PreRenderPasses& plainRenderPasses)
+{
+ GPU_AUTO_SECTION(kGPUSectionDeferedPrePass);
+
+ GfxDevice& device = GetGfxDevice();
+
+ RenderTexture* rt = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormat24, kRTFormatDepth, 0, kRTReadWriteLinear);
+ rt->SetFilterMode (kTexFilterNearest);
+ if (!rt->IsCreated())
+ rt->Create();
+ RenderTexture::SetActive (rt);
+ AddRenderLoopTempBuffer (ctx.m_RenderLoop, rt);
+
+ float black[] = {0,0,0,0};
+ GraphicsHelper::Clear (kGfxClearAll, black, 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ device.SetViewMatrix (ctx.m_CurCameraMatrix.GetPtr());
+
+ size_t ndata = renderData.size();
+
+ for( size_t i = 0; i < ndata; ++i )
+ {
+ const PrePassRenderData& rpData = plainRenderPasses[i];
+ const RenderObjectData& roData = renderData[rpData.roIndex];
+ Shader* shader = roData.shader;
+ int ss = shader->GetShaderLabShader()->GetDefaultSubshaderIndex(kRenderPathExtPrePass);
+ if (ss == -1)
+ continue;
+
+ const VisibleNode *node = roData.visibleNode;
+
+ SetObjectScale (device, node->lodFade, node->invScale);
+
+ //@TODO: if this returns true and we have any sort of batching, we'd have to break batches here
+ node->renderer->ApplyCustomProperties(*roData.material, shader, ss);
+
+ ShaderLab::SubShader& subshader = roData.shader->GetShaderLabShader()->GetSubShader (ss);
+ int shaderPassCount = subshader.GetValidPassCount();
+ for (int p = 0; p < shaderPassCount; ++p)
+ {
+ ShaderPassType passType;
+ UInt32 passRenderOptions;
+ subshader.GetPass(p)->GetPassOptions (passType, passRenderOptions);
+ if (passType != kPassLightPrePassBase)
+ continue;
+
+ const ChannelAssigns* channels = roData.material->SetPassWithShader(p, shader, ss);
+ if (channels)
+ {
+ SetupObjectMatrix (node->worldMatrix, node->transformType);
+ node->renderer->Render( roData.subsetIndex, *channels );
+ }
+ }
+ }
+
+ return rt;
+}
+#endif
+
+inline float MultiplyAbsVectorZ (const Matrix4x4f& m, const Vector3f& v)
+{
+ return Abs(m.m_Data[2]) * v.x + Abs(m.m_Data[6]) * v.y + Abs(m.m_Data[10]) * v.z;
+}
+
+
+RenderTexture* PrePassRenderLoop::RenderBasePass (
+ RenderTexture* rtMain,
+ const LightingLayers& lightingLayers,
+ RenderObjectDataContainer& outRemainingObjects,
+ MinMaxAABB& receiverBounds
+ )
+{
+ PROFILER_AUTO_GFX(gPrepassGeom, m_Context->m_Camera);
+ GPU_AUTO_SECTION(kGPUSectionDeferedPrePass);
+
+ const float shadowDistance = m_Context->m_ShadowCullData->shadowDistance;
+
+ GfxDevice& device = GetGfxDevice();
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+
+ GfxStencilState stRender;
+ stRender.writeMask = kStencilGeomWriteMask;
+ stRender.stencilEnable = true;
+ stRender.stencilPassOpFront = stRender.stencilPassOpBack = kStencilOpReplace;
+ DeviceStencilState* devStRender = device.CreateStencilState (stRender);
+
+ RenderTexture* rtNormalsSpec = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormatNone, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ rtNormalsSpec->SetFilterMode (kTexFilterNearest);
+ if (!rtNormalsSpec->IsCreated())
+ rtNormalsSpec->Create();
+ RenderSurfaceHandle rtSurfaceColor = rtNormalsSpec->GetColorSurfaceHandle();
+ RenderSurfaceHandle rtSurfaceDepth = rtMain->GetDepthSurfaceHandle(); // reuse depth buffer from final pass
+
+ UInt32 rtFlags = RenderTexture::kFlagDontRestoreColor;
+ UInt32 gfxClearFlags = kGfxClearAll;
+ // do not clear depth/stencil if camera set to DontClear
+ if (m_Context->m_Camera->GetClearFlags() == Camera::kDontClear)
+ {
+ gfxClearFlags &= ~kGfxClearDepthStencil;
+ }
+ else
+ {
+ rtFlags |= RenderTexture::kFlagDontRestoreDepth;
+ }
+
+ // set base pass render texture
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtNormalsSpec, 0, kCubeFaceUnknown, rtFlags);
+
+ AddRenderLoopTempBuffer (m_Context->m_RenderLoop, rtNormalsSpec);
+
+ float black[] = {0,0,0,0};
+ GraphicsHelper::Clear (gfxClearFlags, black, 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ device.SetViewMatrix (m_Context->m_CurCameraMatrix.GetPtr());
+
+ const ChannelAssigns* channels = NULL;
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ int prevTransformType = -1;
+ Material* prevMaterial = 0;
+ Shader* prevShader = 0;
+ int prevSubshaderIndex = -1;
+ float prevInvScale = 0.0f;
+ float prevLodFade = 0.0f;
+ UInt32 prevCustomPropsHash = 0;
+ int prevPassIndex = -1;
+ int prevStencilRef = 0;
+
+ int canBatch = 0;
+
+ #endif
+
+ const bool directLightBakedInLightProbes = LightProbes::AreBaked() && GetLightmapSettings().GetLightmapsMode() != LightmapSettings::kDualLightmapsMode;
+
+ size_t ndata = m_Objects->size();
+ outRemainingObjects.reserve (ndata / 16);
+
+ for( size_t i = 0; i < ndata; ++i )
+ {
+ const PrePassRenderData& rpData = m_PlainRenderPasses[i];
+ const RenderObjectData& roData = (*m_Objects)[rpData.roIndex];
+ Shader* shader = roData.shader;
+
+ int ss = roData.subShaderIndex;
+ if (ss == -1)
+ ss = shader->GetShaderLabShader()->GetDefaultSubshaderIndex(kRenderPathExtPrePass);
+
+ const VisibleNode *node = roData.visibleNode;
+
+ bool withinShadowDistance = true;
+ float distanceAlongView = roData.distanceAlongView;
+ if (distanceAlongView > shadowDistance)
+ {
+ // check whether its bounds is actually further than shadow distance
+ // project extents onto camera forward axis
+ float z = MultiplyAbsVectorZ (m_Context->m_CurCameraMatrix, node->worldAABB.GetExtent());
+ Assert(z >= 0.0f);
+ if (distanceAlongView - z > shadowDistance)
+ withinShadowDistance = false;
+ }
+
+ if (ss == -1)
+ {
+ if (withinShadowDistance && node->renderer->GetReceiveShadows())
+ receiverBounds.Encapsulate (node->worldAABB);
+ outRemainingObjects.push_back() = roData;
+ continue;
+ }
+
+
+ const float invScale = node->invScale;
+ const float lodFade = node->lodFade;
+ const int transformType = node->transformType;
+ const UInt32 customPropsHash = node->renderer->GetCustomPropertiesHash();
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ if (
+ node->renderer->GetStaticBatchIndex() == 0 ||
+ prevTransformType != transformType ||
+ prevMaterial != roData.material ||
+ prevShader != shader ||
+ prevSubshaderIndex != ss ||
+ !CompareApproximately(prevInvScale,invScale) ||
+ !CompareApproximately(prevLodFade,lodFade, LOD_FADE_BATCH_EPSILON) ||
+ prevCustomPropsHash != customPropsHash)
+ {
+ m_BatchRenderer.Flush();
+
+ prevTransformType = transformType;
+ prevMaterial = roData.material;
+ prevShader = shader;
+ prevSubshaderIndex = ss;
+ prevInvScale = invScale;
+ prevLodFade = lodFade;
+ prevCustomPropsHash = customPropsHash;
+
+ canBatch = 0;
+ }
+ else
+ ++canBatch;
+
+ #endif
+
+ SetObjectScale (device, lodFade, invScale);
+
+ node->renderer->ApplyCustomProperties(*roData.material, shader, ss);
+
+ const bool lightmapped = node->renderer->IsLightmappedForRendering();
+ const Renderer* renderer = static_cast<Renderer*>(node->renderer);
+ const bool directLightFromLightProbes = directLightBakedInLightProbes && node->renderer->GetRendererType() != kRendererIntermediate && renderer->GetUseLightProbes();
+
+ ShaderLab::SubShader& subshader = shader->GetShaderLabShader()->GetSubShader (ss);
+ int shaderPassCount = subshader.GetValidPassCount();
+ for (int p = 0; p < shaderPassCount; ++p)
+ {
+ ShaderPassType passType;
+ UInt32 passRenderOptions;
+ subshader.GetPass(p)->GetPassOptions (passType, passRenderOptions);
+ if (passType != kPassLightPrePassBase)
+ continue;
+
+ int stencilRef = kStencilMaskSomething;
+ if (!lightmapped && !directLightFromLightProbes)
+ {
+ stencilRef += kStencilMaskNonLightmapped;
+ }
+
+ if (!withinShadowDistance)
+ stencilRef += kStencilMaskBeyondShadowDistace;
+
+ int layerStencilBit = lightingLayers.layerToStencil[node->renderer->GetLayer()];
+ if (layerStencilBit != -1)
+ stencilRef |= 1<<layerStencilBit;
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ if ((p != prevPassIndex) ||
+ (stencilRef != prevStencilRef))
+ {
+ m_BatchRenderer.Flush();
+ prevPassIndex = p;
+ prevStencilRef = stencilRef;
+ canBatch = 0;
+ }
+
+ if (canBatch <= 1)
+ #endif
+ {
+ device.SetStencilState (devStRender, stencilRef);
+ channels = roData.material->SetPassWithShader(p, shader, ss);
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ prevPassIndex = p;
+ prevStencilRef = stencilRef;
+ #endif
+ }
+
+ receiverBounds.Encapsulate (node->worldAABB);
+
+ if (channels)
+ {
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ m_BatchRenderer.Add (node->renderer, roData.subsetIndex, channels, node->worldMatrix, transformType);
+ #else
+ SetupObjectMatrix (node->worldMatrix, transformType);
+ node->renderer->Render (roData.subsetIndex, *channels);
+ #endif
+ }
+ }
+ }
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ m_BatchRenderer.Flush();
+ #endif
+
+ return rtNormalsSpec;
+}
+
+void PrePassRenderLoop::RenderFinalPass (RenderTexture* rtMain,
+ RenderTexture* rtLight,
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture* rtLightSpec,
+#endif
+ bool hdr,
+ bool linearLighting)
+{
+ PROFILER_AUTO_GFX(gPrepassFinal, m_Context->m_Camera);
+ GPU_AUTO_SECTION(kGPUSectionOpaquePass);
+
+ GfxDevice& device = GetGfxDevice();
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+
+ RenderTexture::SetActive (rtMain);
+
+ // Clear with background. Do not clear depth since we need the already
+ // filled one from the base pass.
+ device.SetSRGBWrite(!hdr && linearLighting && (!rtMain || rtMain->GetSRGBReadWrite()) );
+ m_Context->m_Camera->ClearNoSkybox(true);
+
+ if(rtLight)
+ rtLight->SetGlobalProperty (kSLPropLightBuffer);
+ else
+ {
+ ShaderLab::TexEnv *te = ShaderLab::g_GlobalProperties->SetTexture (kSLPropLightBuffer, hdr ? builtintex::GetBlackTexture() : builtintex::GetWhiteTexture());
+ te->ClearMatrix();
+ }
+
+#if SEPERATE_PREPASS_SPECULAR
+ if(rtLightSpec)
+ rtLightSpec->SetGlobalProperty (kSLPropLightSpecBuffer);
+ else
+ {
+ ShaderLab::TexEnv *te = ShaderLab::g_GlobalProperties->SetTexture (kSLPropLightSpecBuffer, hdr ? builtintex::GetBlackTexture() : builtintex::GetWhiteTexture());
+ te->ClearMatrix();
+ }
+#endif
+
+ const ChannelAssigns* channels = NULL;
+ const LightmapSettings& lightmapper = GetLightmapSettings();
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ int prevPassIndex = -1;
+
+ int prevLightmapIndex = -1;
+ Vector4f prevLightmapST (0,0,0,0);
+ int prevTransformType = -1;
+ Material* prevMaterial = 0;
+ Shader* prevShader = 0;
+ int prevSubshaderIndex = -1;
+ float prevInvScale = 0.0f;
+ float prevLodFade = 0.0f;
+ UInt32 prevCustomPropsHash = 0;
+
+ int canBatch = 0;
+
+ #endif
+
+ if (hdr)
+ g_ShaderKeywords.Enable (kKeywordHDRLightPrepassOn);
+ else
+ g_ShaderKeywords.Disable (kKeywordHDRLightPrepassOn);
+
+ LightProbes* lightProbes = GetLightProbes();
+ const bool areLightProbesBaked = LightProbes::AreBaked();
+ BuiltinShaderParamValues& builtinParamValues = GetGfxDevice().GetBuiltinParamValues();
+ Vector3f ambientSH;
+ SHEvalAmbientLight(GetRenderSettings().GetAmbientLightInActiveColorSpace(), &ambientSH[0]);
+
+ size_t ndata = m_Objects->size();
+ for( size_t i = 0; i < ndata; ++i )
+ {
+ const PrePassRenderData& rpData = m_PlainRenderPasses[i];
+ const RenderObjectData& roData = (*m_Objects)[rpData.roIndex];
+
+ const VisibleNode *node = roData.visibleNode;
+ Shader* shader = roData.shader;
+
+ int ss = roData.subShaderIndex;
+ if (ss == -1)
+ ss = shader->GetShaderLabShader()->GetDefaultSubshaderIndex(kRenderPathExtPrePass);
+ if (ss == -1)
+ continue;
+
+ const Vector4f lightmapST = node->renderer->GetLightmapSTForRendering();
+ const int lightmapIndex = roData.lightmapIndex;
+ DebugAssert(lightmapIndex == node->renderer->GetLightmapIndex());
+
+ const float invScale = node->invScale;
+ const float lodFade = node->lodFade;
+ const int transformType = node->transformType;
+ const UInt32 customPropsHash = node->renderer->GetCustomPropertiesHash();
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+
+ if (
+ node->renderer->GetStaticBatchIndex() == 0 ||
+ prevTransformType != transformType ||
+ prevMaterial != roData.material ||
+ prevShader != shader ||
+ prevSubshaderIndex != ss ||
+ prevLightmapIndex != lightmapIndex ||
+ !CompareMemory(prevLightmapST, lightmapST) ||
+ !CompareApproximately(prevInvScale,invScale) ||
+ !CompareApproximately(prevLodFade,lodFade) ||
+ prevCustomPropsHash != customPropsHash)
+ {
+ m_BatchRenderer.Flush();
+
+ prevLightmapIndex = lightmapIndex;
+ prevLightmapST = lightmapST;
+ prevTransformType = transformType;
+ prevMaterial = roData.material;
+ prevShader = shader;
+ prevSubshaderIndex = ss;
+ prevInvScale = invScale;
+ prevLodFade = lodFade;
+ prevCustomPropsHash = customPropsHash;
+
+ canBatch = 0;
+ }
+ else
+ ++canBatch;
+
+ #endif
+
+ SetObjectScale (device, lodFade, invScale);
+
+ node->renderer->ApplyCustomProperties(*roData.material, roData.shader, ss);
+
+ ShaderLab::SubShader& subshader = roData.shader->GetShaderLabShader()->GetSubShader (ss);
+ int shaderPassCount = subshader.GetValidPassCount();
+ for (int p = 0; p < shaderPassCount; ++p)
+ {
+ ShaderPassType passType;
+ UInt32 passRenderOptions;
+ subshader.GetPass(p)->GetPassOptions (passType, passRenderOptions);
+ if (passType != kPassLightPrePassFinal)
+ continue;
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ if (p != prevPassIndex)
+ {
+ m_BatchRenderer.Flush();
+ canBatch = 0;
+ }
+
+ if (canBatch <= 1)
+ #endif
+ {
+ // lightmap
+ SetupObjectLightmaps (lightmapper, lightmapIndex, lightmapST, false);
+
+ // light probes
+ // TODO: figure how does that interact with lightmaps and with batching;
+ // if we are about to use light probes and the renderer gets different coeffs (maybe a simpler check?) => can't batch
+ float lightProbeCoeffs[9][3];
+ memset (lightProbeCoeffs, 0, sizeof(lightProbeCoeffs));
+ if (areLightProbesBaked && node->renderer->GetRendererType() != kRendererIntermediate)
+ {
+ Renderer* renderer = static_cast<Renderer*>(node->renderer);
+ if (renderer && renderer->GetUseLightProbes())
+ lightProbes->GetInterpolatedLightProbe(renderer->GetLightProbeInterpolationPosition(node->worldAABB), renderer, &(lightProbeCoeffs[0][0]));
+ }
+ lightProbeCoeffs[0][0] += ambientSH[0];
+ lightProbeCoeffs[0][1] += ambientSH[1];
+ lightProbeCoeffs[0][2] += ambientSH[2];
+ SetSHConstants (lightProbeCoeffs, builtinParamValues);
+
+ // set pass
+ channels = roData.material->SetPassWithShader(p, shader, ss);
+ }
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ prevPassIndex = p;
+ #endif
+
+ if (channels)
+ {
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ m_BatchRenderer.Add (node->renderer, roData.subsetIndex, channels, node->worldMatrix, transformType);
+ #else
+ SetupObjectMatrix (node->worldMatrix, transformType);
+ node->renderer->Render (roData.subsetIndex, *channels);
+ #endif
+ }
+ }
+ }
+
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ m_BatchRenderer.Flush();
+ #endif
+
+ GetGfxDevice().SetSRGBWrite(false);
+}
+
+
+PrePassRenderLoop* CreatePrePassRenderLoop()
+{
+ return new PrePassRenderLoop();
+}
+
+void DeletePrePassRenderLoop (PrePassRenderLoop* queue)
+{
+ delete queue;
+}
+
+
+static UInt32 CalculateLightingLayers ()
+{
+ // TODO: Use active lights instead
+ const LightManager::Lights& lights = GetLightManager().GetAllLights();
+ LightManager::Lights::const_iterator it, itEnd = lights.end();
+ UInt32 layers = ~0;
+ for (it = lights.begin(); it != itEnd; ++it)
+ {
+ UInt32 mask = it->GetCullingMask();
+ if (mask == 0)
+ continue;
+ layers &= mask;
+ }
+ return ~layers;
+}
+
+
+#if UNITY_EDITOR
+static void CheckLightLayerUsage (const LightingLayers& layers)
+{
+ static bool s_UsageWasOK = true;
+ bool usageIsOK = (layers.lightLayerCount <= kLightingLayerCount);
+
+ // Only log/remove warning message when broken vs. okay has changed
+ if (usageIsOK == s_UsageWasOK)
+ return;
+
+ s_UsageWasOK = usageIsOK;
+
+ // Remove any previous error
+ // Use instanceID of QualitySettings as log identifier
+ RemoveErrorWithIdentifierFromConsole (GetQualitySettings().GetInstanceID());
+
+ if (!usageIsOK)
+ {
+ std::string msg = Format(
+ "Too many layers used to exclude objects from lighting. Up to %i layers can be used to exclude lights, while your lights use %i:",
+ kLightingLayerCount,
+ layers.lightLayerCount);
+ for (int i = 0; i < LightingLayers::kLayerCount; ++i)
+ {
+ if (layers.lightingLayerMask & (1<<i))
+ {
+ std::string layerName = LayerToString (i);
+ if (layerName.empty())
+ layerName = "Unnamed " + IntToString (i);
+ msg += " '" + layerName + "'";
+ }
+ }
+ // Use instanceID of QualitySettings as log identifier
+ DebugStringToFile (msg, 0, __FILE__, __LINE__, kScriptingWarning, 0, GetQualitySettings().GetInstanceID());
+ }
+}
+#endif
+
+static void ResolveDepthIntoTextureIfNeeded (
+ GfxDevice& device,
+ RenderLoop& renderLoop,
+ DepthBufferFormat depthFormat,
+ RenderTexture*& outDepthRT,
+ TextureID* outDepthTextureID,
+ bool* outDepthWasCopied)
+{
+ // TODO FIXME!! Should add GLES20 here as well, but it's missing GfxDevice::ResolveDepthIntoTexture!
+
+#if GFX_SUPPORTS_D3D9 || GFX_SUPPORTS_D3D11 || GFX_SUPPORTS_OPENGL || GFX_SUPPORTS_OPENGLES30
+ bool needsDepthResolve = false;
+#if GFX_SUPPORTS_D3D9
+ // If doing depth tests & sampling as INTZ is very slow,
+ // do a depth resolve into a separate texture first.
+ needsDepthResolve |= (device.GetRenderer() == kGfxRendererD3D9 && gGraphicsCaps.hasStencilInDepthTexture && gGraphicsCaps.d3d.hasDepthResolveRESZ && gGraphicsCaps.d3d.slowINTZSampling);
+#endif
+#if GFX_SUPPORTS_D3D11
+ // Always needs resolve on D3D11.
+ needsDepthResolve |= (device.GetRenderer() == kGfxRendererD3D11);
+#endif
+#if GFX_SUPPORTS_OPENGL
+ // Needs resolve on OpenGL, unless we did the slow RenderBasePassDepth().
+ // TODO: get rid of buggyPackedDepthStencil
+ needsDepthResolve |= (device.GetRenderer() == kGfxRendererOpenGL) && !gGraphicsCaps.gl.buggyPackedDepthStencil;
+#endif
+#if GFX_SUPPORTS_OPENGLES30
+ // Always needs resolve on GLES30.
+ needsDepthResolve |= (device.GetRenderer() == kGfxRendererOpenGLES30);
+#endif
+
+ if (needsDepthResolve)
+ {
+ DebugAssert (depthFormat != kDepthFormatNone);
+ RenderTexture* depthCopy = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, depthFormat, kRTFormatDepth, RenderBufferManager::kRBSampleOnlyDepth, kRTReadWriteLinear);
+ depthCopy->SetFilterMode (kTexFilterNearest);
+ if (!depthCopy->IsCreated())
+ depthCopy->Create();
+ AddRenderLoopTempBuffer (&renderLoop, depthCopy);
+
+ device.ResolveDepthIntoTexture (depthCopy->GetColorSurfaceHandle (), depthCopy->GetDepthSurfaceHandle ());
+
+ outDepthRT = depthCopy;
+ *outDepthTextureID = depthCopy->GetTextureID ();
+ *outDepthWasCopied = true;
+ }
+
+#endif
+}
+
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+template<typename T>
+static UInt8* InstertToHashBufferPreLoop(const T* p, UInt8* buffer)
+{
+ Assert((sizeof(T) % 4) == 0); // unaligned write
+ *reinterpret_cast<T*>(buffer) = *p;
+ return buffer + sizeof(T);
+ }
+#endif
+
+void DoPrePassRenderLoop (
+ RenderLoopContext& ctx,
+ RenderObjectDataContainer& objects,
+ RenderObjectDataContainer& outRemainingObjects,
+ RenderTexture*& outDepthRT,
+ RenderTexture*& outDepthNormalsRT,
+ RenderTexture*& outMainShadowMap,
+ ActiveLights& activeLights,
+ bool linearLighting,
+ bool* outDepthWasCopied)
+{
+ outDepthRT = NULL;
+ outDepthNormalsRT = NULL;
+ *outDepthWasCopied = false;
+
+ // Allocated on the stack each time, uses temp allocators
+ PrePassRenderLoop loop;
+ loop.m_Context = &ctx;
+ loop.m_Objects = &objects;
+
+ loop.m_PlainRenderPasses.resize_uninitialized(0);
+
+ RenderObjectDataContainer::iterator itEnd = objects.end();
+ size_t roIndex = 0;
+ for (RenderObjectDataContainer::iterator it = objects.begin(); it != itEnd; ++it, ++roIndex)
+ {
+ RenderObjectData& odata = *it;
+ const VisibleNode *node = odata.visibleNode;
+ BaseRenderer* renderer = node->renderer;
+
+ PrePassRenderData rpData;
+ rpData.roIndex = roIndex;
+
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+
+ //hash state information for render object sorter
+ const int kHashBufferSize = 64;
+ UInt8 hashBuffer[kHashBufferSize];
+ UInt8* hashPtr = hashBuffer;
+
+ // Always write 32b granularity into the hash buffer to avoid unaligned writes
+ UInt32 transformType = static_cast<UInt32>(renderer->GetTransformInfo().transformType);
+ hashPtr = InstertToHashBufferPreLoop(&transformType, hashPtr);
+ hashPtr = InstertToHashBufferPreLoop(&node->invScale, hashPtr);
+ hashPtr = InstertToHashBufferPreLoop(&node->lodFade, hashPtr);
+ int materialID = odata.material->GetInstanceID();
+ hashPtr = InstertToHashBufferPreLoop(&materialID, hashPtr);
+ int shaderID = odata.shader->GetInstanceID();
+ hashPtr = InstertToHashBufferPreLoop(&shaderID, hashPtr);
+ int ss = odata.shader->GetShaderLabShader()->GetDefaultSubshaderIndex(kRenderPathExtPrePass);
+ hashPtr = InstertToHashBufferPreLoop(&ss, hashPtr);
+ #if GFX_ENABLE_DRAW_CALL_BATCHING
+ hashPtr = InstertToHashBufferPreLoop(&odata.staticBatchIndex, hashPtr);
+ #endif
+ Assert(hashPtr-hashBuffer <= kHashBufferSize);
+
+ rpData.hash = MurmurHash2A(hashBuffer, hashPtr-hashBuffer, 0x9747b28c);
+#endif
+ loop.m_PlainRenderPasses.push_back( rpData );
+ }
+
+ // Sort objects
+ {
+ PROFILER_AUTO(gPrepassSort, ctx.m_Camera);
+#if ENABLE_PRE_PASS_LOOP_HASH_SORTING
+ loop.SortPreRenderPassData(loop.m_PlainRenderPasses);
+#else
+ std::sort (objects.begin(), objects.end(), RenderPrePassObjectSorter());
+#endif
+ }
+
+ // Setup shadow distance, fade and ambient parameters
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+ Vector4f lightFade;
+ Vector4f fadeCenterAndType;
+ CalculateLightShadowFade (*ctx.m_Camera, 1.0f, lightFade, fadeCenterAndType);
+ params.SetVectorParam(kShaderVecLightmapFade, lightFade);
+ params.SetVectorParam(kShaderVecShadowFadeCenterAndType, fadeCenterAndType);
+ params.SetVectorParam(kShaderVecUnityAmbient, Vector4f(GetRenderSettings().GetAmbientLightInActiveColorSpace().GetPtr()));
+
+ GfxDevice& device = GetGfxDevice();
+
+ // Prepare for rendering
+ RenderTexture* rtMain = ctx.m_Camera->GetCurrentTargetTexture ();
+ Assert (rtMain);
+ if (!rtMain->IsCreated())
+ rtMain->Create();
+
+ LightingLayers lightingLayers (CalculateLightingLayers ());
+ #if UNITY_EDITOR
+ CheckLightLayerUsage (lightingLayers);
+ #endif
+
+ // Don't allow shaders to set their own stencil state from base pass until
+ // the end of light pass, since it would screw them up.
+ ShaderLab::g_GlobalAllowShaderStencil = false;
+
+ // Render Geometry base pass
+ MinMaxAABB receiverBounds;
+ RenderTexture* rtNormalsSpec = loop.RenderBasePass (rtMain, lightingLayers, outRemainingObjects, receiverBounds);
+ outDepthRT = rtNormalsSpec;
+
+ RenderSurfaceHandle colorSurfaceHandle = rtNormalsSpec->GetColorSurfaceHandle();
+ RenderSurfaceHandle depthTextureHandle = rtMain->GetDepthSurfaceHandle();
+ TextureID depthTextureID = rtMain->GetSecondaryTextureID();
+ DepthBufferFormat depthFormat = rtMain->GetDepthFormat();
+
+ #if GFX_SUPPORTS_OPENGL
+ if (device.GetRenderer() == kGfxRendererOpenGL && gGraphicsCaps.gl.buggyPackedDepthStencil)
+ {
+ // Separate pass to render depth into a separate target. And then use that texture to read depth
+ // in the lighting pass.
+ RenderTexture* rtDepth = RenderBasePassDepth (ctx, objects, loop.m_PlainRenderPasses);
+ depthTextureID = rtDepth->GetTextureID();
+ outDepthRT = rtDepth;
+ colorSurfaceHandle = rtDepth->GetColorSurfaceHandle();
+ depthTextureHandle = rtDepth->GetDepthSurfaceHandle();
+ *outDepthWasCopied = true;
+ }
+ #endif
+
+ if (gGraphicsCaps.hasStencilInDepthTexture)
+ {
+ const ActiveLight* mainActiveLight = GetMainActiveLight(activeLights);
+ Light* mainLight = mainActiveLight ? mainActiveLight->light : NULL;
+ const bool mainLightHasShadows = mainLight && mainLight->GetType() == kLightDirectional && mainLight->GetShadows() != kShadowNone;
+ const bool cameraNeedsDepthTexture = (ctx.m_Camera->GetDepthTextureMode() & Camera::kDepthTexDepthBit);
+ if (mainLightHasShadows || cameraNeedsDepthTexture)
+ {
+ RenderForwardObjectsIntoDepth (
+ ctx,
+ rtMain,
+ &outRemainingObjects,
+ colorSurfaceHandle,
+ depthTextureHandle,
+ rtMain->GetWidth(),
+ rtMain->GetHeight(),
+ cameraNeedsDepthTexture
+ );
+ }
+ }
+
+ ResolveDepthIntoTextureIfNeeded (device, *(ctx.m_RenderLoop), depthFormat, outDepthRT, &depthTextureID, outDepthWasCopied);
+
+ // Render Lighting pass
+ RenderTexture* rtLight = NULL;
+#if SEPERATE_PREPASS_SPECULAR
+ RenderTexture* rtLightSpec = NULL;
+#endif
+ loop.RenderLighting (activeLights,
+ rtMain,
+ depthTextureID,
+ rtNormalsSpec,
+ rtLight,
+#if SEPERATE_PREPASS_SPECULAR
+ rtLightSpec,
+#endif
+ lightFade,
+ lightingLayers,
+ receiverBounds,
+ &outMainShadowMap);
+
+ // It's again ok for shaders to set their stencil state now.
+ ShaderLab::g_GlobalAllowShaderStencil = true;
+
+ if (ctx.m_Camera->GetClearStencilAfterLightingPass())
+ {
+ float black[] = {0,0,0,0};
+ device.Clear (kGfxClearStencil, black, 1.0f, 0);
+ }
+
+ // Render final Geometry pass
+ loop.RenderFinalPass (rtMain,
+ rtLight,
+#if SEPERATE_PREPASS_SPECULAR
+ rtLightSpec,
+#endif
+ ctx.m_Camera->GetUsingHDR(),
+ linearLighting);
+
+ if (rtLight)
+ {
+ // Do not release the light buffer yet; so that image effects or whatever can access it later
+ // if needed (via _LightBuffer)
+ device.SetSurfaceFlags(rtLight->GetColorSurfaceHandle(), GfxDevice::kSurfaceDefault, ~GfxDevice::kSurfaceRestoreMask);
+ device.SetSurfaceFlags(rtLight->GetDepthSurfaceHandle(), GfxDevice::kSurfaceDefault, ~GfxDevice::kSurfaceRestoreMask);
+ AddRenderLoopTempBuffer (ctx.m_RenderLoop, rtLight);
+ }
+
+#if SEPERATE_PREPASS_SPECULAR
+ if (rtLightSpec)
+ {
+ device.SetSurfaceFlags(rtLightSpec->GetColorSurfaceHandle(), GfxDevice::kSurfaceDefault, ~GfxDevice::kSurfaceRestoreMask);
+ device.SetSurfaceFlags(rtLightSpec->GetDepthSurfaceHandle(), GfxDevice::kSurfaceDefault, ~GfxDevice::kSurfaceRestoreMask);
+ AddRenderLoopTempBuffer (ctx.m_RenderLoop, rtLightSpec);
+ }
+#endif
+
+ // Combine depth+normals if needed
+ if (ctx.m_Camera->GetDepthTextureMode() & Camera::kDepthTexDepthNormalsBit)
+ {
+ outDepthNormalsRT = CombineDepthNormalsTexture (ctx, outRemainingObjects);
+ RenderTexture::SetActive (rtMain);
+ }
+
+ device.SetViewMatrix( ctx.m_CurCameraMatrix.GetPtr() );
+ device.SetNormalizationBackface( kNormalizationDisabled, false );
+}
+
+#endif // GFX_SUPPORTS_RENDERLOOP_PREPASS
diff --git a/Runtime/Camera/RenderLoops/RenderLoop.h b/Runtime/Camera/RenderLoops/RenderLoop.h
new file mode 100644
index 0000000..8c54ed8
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/RenderLoop.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "RenderLoopEnums.h"
+
+class Shader;
+struct RenderLoop;
+class Camera;
+class ImageFilters;
+class RenderTexture;
+struct ShadowCullData;
+struct CullResults;
+
+
+RenderLoop* CreateRenderLoop (Camera& camera);
+void DeleteRenderLoop (RenderLoop* loop);
+void DoRenderLoop (
+ RenderLoop& loop,
+ RenderingPath renderPath,
+ CullResults& contents,
+ // used in the editor for material previews - those should not render projectors, halos etc.
+ bool dontRenderRenderables
+);
+void CleanupAfterRenderLoop (RenderLoop& loop);
+ImageFilters& GetRenderLoopImageFilters (RenderLoop& loop);
+void RenderImageFilters (RenderLoop& loop, RenderTexture* targetTexture, bool afterOpaque);
diff --git a/Runtime/Camera/RenderLoops/RenderLoopEnums.h b/Runtime/Camera/RenderLoops/RenderLoopEnums.h
new file mode 100644
index 0000000..e6ec07e
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/RenderLoopEnums.h
@@ -0,0 +1,29 @@
+#pragma once
+
+enum RenderingPath {
+ kRenderPathVertex = 0,
+ kRenderPathForward,
+ kRenderPathPrePass,
+ kRenderPathCount
+};
+
+enum OcclusionQueryType {
+ kOcclusionQueryTypeMostAccurate = 0,
+ kOcclusionQueryTypeFastest,
+ kOcclusionQueryTypeCount
+};
+
+enum
+{
+ kBackgroundRenderQueue = 1000,
+ kGeometryRenderQueue = 2000,
+ kAlphaTestRenderQueue = 2450, // we want it to be in the end of geometry queue
+ kTransparentRenderQueue = 3000,
+ kOverlayRenderQueue = 4000,
+
+ kQueueIndexMin = 0,
+ kQueueIndexMax = 5000,
+
+ kGeometryQueueIndexMin = kGeometryRenderQueue-500,
+ kGeometryQueueIndexMax = kGeometryRenderQueue+500,
+};
diff --git a/Runtime/Camera/RenderLoops/RenderLoopPrivate.cpp b/Runtime/Camera/RenderLoops/RenderLoopPrivate.cpp
new file mode 100644
index 0000000..823c8fa
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/RenderLoopPrivate.cpp
@@ -0,0 +1,469 @@
+#include "UnityPrefix.h"
+#include "RenderLoopPrivate.h"
+#include "RenderLoop.h"
+#include "Runtime/Camera/UnityScene.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/ShadowCulling.h"
+#include "Runtime/Camera/RenderSettings.h"
+#include "Runtime/Camera/ImageFilters.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Camera/BaseRenderer.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Graphics/RenderTexture.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+#include "Runtime/Shaders/Shader.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Graphics/RenderBufferManager.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "ReplacementRenderLoop.h"
+
+#if UNITY_EDITOR
+ #include "Editor/Src/EditorUserBuildSettings.h"
+#endif
+
+PROFILER_INFORMATION(gRenderPrepareObjects, "Render.Prepare", kProfilerRender)
+PROFILER_INFORMATION(gRenderOpaque, "Render.OpaqueGeometry", kProfilerRender)
+PROFILER_INFORMATION(gRenderTransparent, "Render.TransparentGeometry", kProfilerRender)
+PROFILER_INFORMATION(gPrePassFwdDepthTex, "RenderPrePass.FwdObjectsIntoDepth", kProfilerRender)
+PROFILER_INFORMATION(gPrePassFwdDepthNormalsTex, "RenderPrePass.FwdObjectsIntoDepthNormals", kProfilerRender)
+PROFILER_INFORMATION(gCameraResolveProfile, "Camera.AAResolve", kProfilerRender)
+
+
+namespace ShaderLab { void ClearGrabPassFrameState (); } // pass.cpp
+
+
+struct RenderLoop {
+public:
+ RenderLoop (Camera& camera);
+ ~RenderLoop ();
+
+ void PrepareFrame (bool dontRenderRenderables, bool renderingShaderReplace);
+
+public:
+ RenderLoopContext m_Context;
+ ShadowCullData m_ShadowCullData;
+ RenderObjectDataContainer m_Objects[kPartCount];
+ ImageFilters m_ImageFilters;
+
+ enum { kMaxCreatedTempBuffers = 8 };
+ RenderTexture* m_TempBuffers[kMaxCreatedTempBuffers];
+ int m_TempBufferCount;
+};
+
+
+RenderLoop* CreateRenderLoop (Camera& camera)
+{
+ return new RenderLoop(camera);
+}
+
+void DeleteRenderLoop (RenderLoop* loop)
+{
+ delete loop;
+}
+
+ImageFilters& GetRenderLoopImageFilters (RenderLoop& loop)
+{
+ return loop.m_ImageFilters;
+}
+
+
+RenderLoop::RenderLoop(Camera& camera)
+{
+ m_Context.m_Camera = &camera;
+ m_Context.m_RenderLoop = this;
+
+ for (int i = 0; i < kMaxCreatedTempBuffers; ++i) {
+ m_TempBuffers[i] = NULL;
+ }
+ m_TempBufferCount = 0;
+}
+
+RenderLoop::~RenderLoop()
+{
+ Assert (m_TempBufferCount == 0);
+}
+
+inline float MultiplyPointZ (const Matrix4x4f& m, const Vector3f& v)
+{
+ return m.m_Data[2] * v.x + m.m_Data[6] * v.y + m.m_Data[10] * v.z + m.m_Data[14];
+}
+
+// Both distances become smaller (more negative) when moving forward from the camera.
+// outDistanceForSort is for sorting only, and it can be square of the actual distance, and so on.
+// outDistnaceAlongView is projection of the center along camera's view.
+static void EvaluateObjectDepth (const RenderLoopContext& ctx, const TransformInfo& info, float& outDistanceForSort, float& outDistanceAlongView)
+{
+ Vector3f center = info.worldAABB.GetCenter();
+ if (ctx.m_SortOrthographic)
+ {
+ const float d = MultiplyPointZ (ctx.m_CurCameraMatrix, center);
+ outDistanceForSort = d;
+ outDistanceAlongView = d;
+ }
+ else
+ {
+ outDistanceAlongView = MultiplyPointZ (ctx.m_CurCameraMatrix, center);
+ center -= ctx.m_CurCameraPos;
+ outDistanceForSort = -SqrMagnitude(center);
+ }
+
+ // A distance of NaN can cause inconsistent sorting results, if input order is inconsistent.
+ Assert(IsFinite(outDistanceForSort));
+ Assert(IsFinite(outDistanceAlongView));
+}
+
+
+void RenderLoop::PrepareFrame (bool dontRenderRenderables, bool renderingShaderReplace)
+{
+ Camera& camera = *m_Context.m_Camera;
+ m_Context.m_CurCameraMatrix = camera.GetWorldToCameraMatrix();
+ m_Context.m_CurCameraPos = camera.GetComponent(Transform).GetPosition();
+ m_Context.m_CameraViewport = camera.GetRenderRectangle();
+ switch (camera.GetSortMode())
+ {
+ case Camera::kSortPerspective: m_Context.m_SortOrthographic = false; break;
+ case Camera::kSortOrthographic: m_Context.m_SortOrthographic = true; break;
+ default: m_Context.m_SortOrthographic = camera.GetOrthographic(); break;
+ }
+ m_Context.m_DontRenderRenderables = dontRenderRenderables;
+ m_Context.m_RenderingShaderReplace = renderingShaderReplace;
+
+ for (int i = 0; i < kPartCount; ++i)
+ m_Objects[i].resize_uninitialized(0);
+
+ #if DEBUGMODE
+ for (int i = 0; i < kMaxCreatedTempBuffers; ++i) {
+ Assert (m_TempBuffers[i] == NULL);
+ }
+ #endif
+ m_TempBufferCount = 0;
+}
+
+
+static RenderTexture* ResolveScreenToTextureIfNeeded (RenderLoop& loop, bool forceIntoRT, bool beforeOpaqueImageFx)
+{
+ // If we use screen to composite image effects, resolve screen into the render texture now
+ bool usingScreenToComposite = loop.m_ImageFilters.HasImageFilter() && loop.m_Context.m_Camera->GetUsesScreenForCompositing(forceIntoRT);
+ RenderTexture* rt = NULL;
+ if (usingScreenToComposite)
+ {
+ // Do a screen to RT resolve here.
+ rt = beforeOpaqueImageFx ? loop.m_ImageFilters.GetTargetBeforeOpaque () : loop.m_ImageFilters.GetTargetAfterOpaque (forceIntoRT, usingScreenToComposite);
+ if (!rt)
+ return NULL;
+
+ PROFILER_AUTO_GFX(gCameraResolveProfile, loop.m_Context.m_Camera)
+ GPU_AUTO_SECTION(kGPUSectionPostProcess);
+
+ // We should insert proper discard/clear/... on backbuffer when doing MSAA
+ // resolved off it. However that's for the future (case 549705),
+ // for now just silence the RT unresolve warning.
+ GetGfxDevice().IgnoreNextUnresolveOnCurrentRenderTarget();
+
+ Rectf r = loop.m_Context.m_Camera->GetPhysicalViewportRect();
+ int rect[4];
+ RectfToViewport( r, rect );
+ Assert (rect[2] == rt->GetGLWidth() && rect[3] == rt->GetGLHeight());
+ rt->GrabPixels (rect[0], rect[1], rect[2], rect[3]);
+
+ // D3D and GL use different notions of how Y texture coordinates go.
+ // In effect, we have to flip any sampling from the first texture in the image filters
+ // stack on D3D.
+ rt->CorrectVerticalTexelSize(false);
+ }
+
+ return rt;
+}
+
+
+void RenderImageFilters (RenderLoop& loop, RenderTexture* targetTexture, bool afterOpaque)
+{
+ bool forceIntoRT = loop.m_Context.m_Camera->CalculateNeedsToRenderIntoRT();
+ ResolveScreenToTextureIfNeeded (loop, forceIntoRT, afterOpaque);
+ bool usingScreenToComposite = loop.m_ImageFilters.HasImageFilter() && loop.m_Context.m_Camera->GetUsesScreenForCompositing(forceIntoRT);
+ loop.m_ImageFilters.DoRender (targetTexture, forceIntoRT, afterOpaque, usingScreenToComposite, loop.m_Context.m_Camera->GetUsingHDR());
+ if (afterOpaque && !usingScreenToComposite)
+ loop.m_Context.m_Camera->SetCurrentTargetTexture (loop.m_ImageFilters.GetTargetAfterOpaque(forceIntoRT,usingScreenToComposite));
+}
+
+
+static void UpdateCameraDepthTextures (Camera& camera, RenderTexture* rtDepth, RenderTexture* rtDepthNormals, RenderObjectDataContainer& objects, bool depthWasCopied, bool skipDepthTexture, bool afterOpaque)
+{
+ if (!rtDepth || objects.size() == 0)
+ return;
+
+ // use depth buffer from final target
+ RenderTexture* rtFinal = camera.GetCurrentTargetTexture();
+ Assert (rtFinal);
+ RenderSurfaceHandle rtSurfaceDepth = rtFinal->GetDepthSurfaceHandle();
+
+ int renderFlags = Camera::kRenderFlagSetRenderTarget;
+ if (!afterOpaque)
+ renderFlags |= Camera::kRenderFlagSetRenderTargetFinal;
+
+ if (!skipDepthTexture && gGraphicsCaps.hasStencilInDepthTexture && (camera.GetDepthTextureMode() & Camera::kDepthTexDepthBit))
+ {
+ Shader* shader = GetCameraDepthTextureShader ();
+ if (shader)
+ {
+ PROFILER_AUTO_GFX(gPrePassFwdDepthTex, &camera);
+ // If we did separate pass or depth resolve in deferred to work around depth+stencil texture bugs,
+ // render into the copy in that case.
+ if (depthWasCopied)
+ {
+ RenderTexture::SetActive (rtDepth);
+ }
+ else
+ {
+ RenderSurfaceHandle rtSurfaceColor = rtDepth->GetColorSurfaceHandle();
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtDepth);
+ }
+
+ RenderSceneShaderReplacement (objects, shader, "RenderType");
+ camera.SetupRender (renderFlags);
+ }
+ }
+
+ if (rtDepthNormals && (camera.GetDepthTextureMode() & Camera::kDepthTexDepthNormalsBit))
+ {
+ Shader* shader = GetCameraDepthNormalsTextureShader ();
+ if (shader)
+ {
+ PROFILER_AUTO_GFX(gPrePassFwdDepthNormalsTex, &camera);
+ RenderSurfaceHandle rtSurfaceColor = rtDepthNormals->GetColorSurfaceHandle();
+ RenderTexture::SetActive (1, &rtSurfaceColor, rtSurfaceDepth, rtDepthNormals);
+
+ RenderSceneShaderReplacement (objects, shader, "RenderType");
+ camera.SetupRender (renderFlags);
+ }
+ }
+}
+
+bool gInsideRenderLoop = false;
+void StartRenderLoop()
+{
+ Assert (!gInsideRenderLoop);
+ gInsideRenderLoop = true;
+}
+void EndRenderLoop()
+{
+ Assert (gInsideRenderLoop);
+ gInsideRenderLoop = false;
+}
+bool IsInsideRenderLoop()
+{
+ return gInsideRenderLoop;
+}
+
+void DoRenderLoop (
+ RenderLoop& loop,
+ RenderingPath renderPath,
+ CullResults& contents,
+ bool dontRenderRenderables)
+{
+ Assert (loop.m_TempBufferCount == 0);
+ Assert (contents.shadowCullData);
+
+ loop.m_Context.m_ShadowCullData = contents.shadowCullData;
+ loop.m_Context.m_CullResults = &contents;
+
+ // save wireframe state, restore at exit
+ SetAndRestoreWireframeMode saveAndRestoreWireframe;
+
+ const bool licenseAllowsStaticBatching = GetBuildSettings().hasAdvancedVersion;
+ Camera& camera = *loop.m_Context.m_Camera;
+
+ Shader* replacementShader = contents.shaderReplaceData.replacementShader;
+ const bool replacementTagSet = contents.shaderReplaceData.replacementTagSet;
+ const int replacementTagID = contents.shaderReplaceData.replacementTagID;
+
+
+ {
+ PROFILER_AUTO(gRenderPrepareObjects, &camera);
+
+ loop.PrepareFrame (dontRenderRenderables, replacementShader);
+
+ const bool useOldRenderQueueLogic = !IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_2_a1);
+
+ // sort out objects into opaque & alpha parts
+ VisibleNodes::iterator itEnd = contents.nodes.end();
+ for (VisibleNodes::iterator it = contents.nodes.begin(); it != itEnd; ++it)
+ {
+ if (!IsFinite(it->invScale))
+ continue;
+
+ BaseRenderer* renderer = it->renderer;
+
+ float distanceForSort, distanceAlongView;
+ EvaluateObjectDepth (loop.m_Context, *it, distanceForSort, distanceAlongView);
+ distanceForSort -= renderer->GetSortingFudge ();
+ distanceAlongView = -distanceAlongView; // make that so increases with distance
+
+ const int matCount = renderer->GetMaterialCount();
+ const int batchIndex = (licenseAllowsStaticBatching)? renderer->GetStaticBatchIndex(): 0;
+ const UInt16 lightmapIndex = renderer->GetLightmapIndex();
+
+ for (int mi = 0; mi < matCount; ++mi)
+ {
+ Material* mat = renderer->GetMaterial (mi);
+ if( mat == NULL )
+ mat = Material::GetDefault();
+ Shader* shader = mat->GetShader();
+
+ int usedSubshaderIndex = -1;
+ if (replacementShader)
+ {
+ if (replacementTagSet)
+ {
+ int subshaderTypeID = shader->GetShaderLabShader()->GetTag (replacementTagID, true);
+ if (subshaderTypeID < 0)
+ continue; // skip rendering
+ usedSubshaderIndex = replacementShader->GetSubShaderWithTagValue (replacementTagID, subshaderTypeID);
+ if (usedSubshaderIndex == -1)
+ continue; // skip rendering
+ }
+ else
+ {
+ usedSubshaderIndex = 0;
+ }
+ }
+
+ const int matIndex = renderer->GetSubsetIndex(mi);
+
+ // Figure out rendering queue to use
+ int queueIndex = mat->GetCustomRenderQueue(); // any per-material overriden queue takes priority
+ if (queueIndex < 0)
+ {
+ // When no shader replacement or old content, take queue from the shader
+ if (!replacementShader || useOldRenderQueueLogic)
+ {
+ queueIndex = shader->GetShaderLabShader()->GetRenderQueue();
+ }
+ // Otherwise take from replacement shader
+ else
+ {
+ queueIndex = replacementShader->GetShaderLabShader()->GetRenderQueue(usedSubshaderIndex);
+ }
+ }
+
+ RenderPart part;
+ if (queueIndex <= kGeometryQueueIndexMax)
+ part = kPartOpaque;
+ else
+ part = kPartAfterOpaque;
+
+ RenderObjectData& odata = loop.m_Objects[part].push_back ();
+ DebugAssertIf (!mat);
+ odata.material = mat;
+ odata.queueIndex = queueIndex;
+ odata.subsetIndex = matIndex;
+ odata.subShaderIndex = usedSubshaderIndex;
+ odata.sourceMaterialIndex = (UInt16)mi;
+ odata.lightmapIndex = lightmapIndex;
+ odata.staticBatchIndex = batchIndex;
+ odata.distance = distanceForSort;
+ odata.distanceAlongView = distanceAlongView;
+ odata.visibleNode = &*it;
+ odata.shader = replacementShader ? replacementShader : shader;
+ odata.globalLayeringData = renderer->GetGlobalLayeringData();
+ }
+ }
+ }
+
+ // want linear lighting?
+ bool linearLighting = GetActiveColorSpace() == kLinearColorSpace;
+
+ // opaque: deferred or forward
+ RenderTexture *rtDepth = NULL, *rtDepthNormals = NULL;
+ bool prepassDepthWasCopied = false;
+ {
+ PROFILER_AUTO_GFX(gRenderOpaque, &camera);
+
+ loop.m_Context.m_RenderQueueStart = kQueueIndexMin; loop.m_Context.m_RenderQueueEnd = kGeometryQueueIndexMax+1;
+ if (renderPath == kRenderPathPrePass)
+ {
+ #if GFX_SUPPORTS_RENDERLOOP_PREPASS
+ RenderTexture *rtShadowMap = NULL;
+ RenderObjectDataContainer remainingObjects;
+ DoPrePassRenderLoop (loop.m_Context, loop.m_Objects[kPartOpaque], remainingObjects, rtDepth, rtDepthNormals, rtShadowMap, contents.activeLights, linearLighting, &prepassDepthWasCopied);
+ if (remainingObjects.size() != 0)
+ {
+ // Objects/shaders that don't handle deferred: render with forward path, and pass main shadowmap to it
+ // Also disable dynamic batching of those objects. They are already rendered into
+ // the depth buffer, and dynamic batching would make them be rendered at slightly
+ // different positions, failing depth test at places.
+ DoForwardShaderRenderLoop (loop.m_Context, remainingObjects, true, true, rtShadowMap, contents.activeLights, linearLighting, false);
+
+ UpdateCameraDepthTextures (camera, rtDepth, rtDepthNormals, remainingObjects, prepassDepthWasCopied, true, true);
+ }
+ #else
+ ErrorString ("Pre-pass rendering loop should never happen on this platform!");
+ #endif
+ }
+ else if (renderPath == kRenderPathForward)
+ {
+ DoForwardShaderRenderLoop (loop.m_Context, loop.m_Objects[kPartOpaque], true, false, NULL, contents.activeLights, linearLighting, true);
+ }
+ else
+ {
+ DoForwardVertexRenderLoop (loop.m_Context, loop.m_Objects[kPartOpaque], true, contents.activeLights, linearLighting, true);
+ }
+ }
+
+ // render skybox after opaque (sRGB conversions needed if using linear rendering)
+ {
+ GetGfxDevice().SetSRGBWrite(linearLighting);
+ camera.RenderSkybox();
+ GetGfxDevice().SetSRGBWrite(false);
+ }
+
+ RenderImageFilters (loop, camera.GetTargetTexture(), true);
+
+ // after opaque: forward
+ {
+ PROFILER_AUTO_GFX(gRenderTransparent, &camera);
+
+ loop.m_Context.m_RenderQueueStart = kGeometryQueueIndexMax+1; loop.m_Context.m_RenderQueueEnd = kQueueIndexMax;
+ if (renderPath != kRenderPathVertex)
+ {
+ DoForwardShaderRenderLoop (loop.m_Context, loop.m_Objects[kPartAfterOpaque], false, false, NULL, contents.activeLights, linearLighting, false);
+ }
+ else
+ {
+ DoForwardVertexRenderLoop (loop.m_Context, loop.m_Objects[kPartAfterOpaque], false, contents.activeLights, linearLighting, false);
+ }
+
+ UpdateCameraDepthTextures (camera, rtDepth, rtDepthNormals, loop.m_Objects[kPartAfterOpaque], prepassDepthWasCopied, false, false);
+ }
+
+ loop.m_Context.m_ShadowCullData = NULL;
+ loop.m_Context.m_CullResults = NULL;
+}
+
+void CleanupAfterRenderLoop (RenderLoop& loop)
+{
+ Assert (loop.m_TempBufferCount >= 0 && loop.m_TempBufferCount < RenderLoop::kMaxCreatedTempBuffers);
+ RenderBufferManager& rbm = GetRenderBufferManager();
+ for (int i = 0; i < loop.m_TempBufferCount; ++i) {
+ Assert (loop.m_TempBuffers[i]);
+ rbm.ReleaseTempBuffer (loop.m_TempBuffers[i]);
+ loop.m_TempBuffers[i] = NULL;
+ }
+ loop.m_TempBufferCount = 0;
+ ShaderLab::ClearGrabPassFrameState();
+}
+
+void AddRenderLoopTempBuffer (RenderLoop* loop, RenderTexture* rt)
+{
+ Assert (loop && rt);
+ Assert (loop->m_TempBufferCount < RenderLoop::kMaxCreatedTempBuffers);
+
+ loop->m_TempBuffers[loop->m_TempBufferCount++] = rt;
+}
diff --git a/Runtime/Camera/RenderLoops/RenderLoopPrivate.h b/Runtime/Camera/RenderLoops/RenderLoopPrivate.h
new file mode 100644
index 0000000..c64927c
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/RenderLoopPrivate.h
@@ -0,0 +1,86 @@
+#pragma once
+
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Camera/CullResults.h"
+#include "GlobalLayeringData.h"
+
+namespace Unity { class Material; }
+class Camera;
+class Shader;
+class RenderTexture;
+struct ShadowCullData;
+struct RenderLoop;
+
+struct RenderObjectData {
+ Unity::Material* material; // 4
+ SInt16 queueIndex; // 2
+ UInt16 subsetIndex; // 2
+ SInt16 subShaderIndex; // 2
+ UInt16 sourceMaterialIndex;// 2
+ UInt16 lightmapIndex; // 2
+ int staticBatchIndex; // 4
+ float distance; // 4
+
+ //@TODO: cold?
+ float distanceAlongView; // 4
+ VisibleNode* visibleNode; // 4
+ Shader* shader; // 4 shader to use
+ GlobalLayeringData
+ globalLayeringData; // 4
+ // 36 bytes
+};
+
+enum RenderPart { kPartOpaque, kPartAfterOpaque, kPartCount };
+
+typedef dynamic_array<RenderObjectData> RenderObjectDataContainer;
+
+struct RenderLoopContext
+{
+ Camera* m_Camera;
+
+ const CullResults* m_CullResults;
+ const ShadowCullData* m_ShadowCullData;
+ Matrix4x4f m_CurCameraMatrix;
+ Rectf m_CameraViewport;
+ Vector3f m_CurCameraPos;
+ bool m_SortOrthographic;
+ bool m_DontRenderRenderables;
+ bool m_RenderingShaderReplace;
+
+ int m_RenderQueueStart;
+ int m_RenderQueueEnd;
+
+ RenderLoop* m_RenderLoop;
+};
+
+void AddRenderLoopTempBuffer (RenderLoop* loop, RenderTexture* rt);
+
+void DoForwardVertexRenderLoop (RenderLoopContext& ctx, RenderObjectDataContainer& objects, bool opaque, ActiveLights& activeLights, bool linearLighting, bool clearFrameBuffer);
+void DoForwardShaderRenderLoop (
+ RenderLoopContext& ctx,
+ RenderObjectDataContainer& objects,
+ bool opaque,
+ bool disableDynamicBatching,
+ RenderTexture* mainShadowMap,
+ ActiveLights& activeLights,
+ bool linearLighting,
+ bool clearFrameBuffer);
+
+void DoPrePassRenderLoop (
+ RenderLoopContext& ctx,
+ RenderObjectDataContainer& objects,
+ RenderObjectDataContainer& outRemainingObjects,
+ RenderTexture*& outDepthRT,
+ RenderTexture*& outDepthNormalsRT,
+ RenderTexture*& outMainShadowMap,
+ ActiveLights& activeLights,
+ bool linearLighting,
+ bool* outDepthWasCopied);
+
+// This is only usable by GfxDeviceGLES, because GfxDeviceGLES only supports ForwardVertexRenderLoop, you'll only see these functions there
+// If IsInsideRenderLoop() == true, no state caching will be performed by GfxDeviceGLES
+void StartRenderLoop();
+void EndRenderLoop();
+bool IsInsideRenderLoop();
diff --git a/Runtime/Camera/RenderLoops/ReplacementRenderLoop.cpp b/Runtime/Camera/RenderLoops/ReplacementRenderLoop.cpp
new file mode 100644
index 0000000..fcba391
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/ReplacementRenderLoop.cpp
@@ -0,0 +1,245 @@
+#include "UnityPrefix.h"
+#include "ReplacementRenderLoop.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Camera/BaseRenderer.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Camera/Camera.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Camera/UnityScene.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+
+
+struct RODataReplacement {
+ float distance;
+ int subshaderIndex;
+ Material* material;
+ const VisibleNode* visibleNode;
+ Shader* shader;
+ int materialIndex;
+ GlobalLayeringData globalLayeringData;
+};
+
+typedef UNITY_TEMP_VECTOR(RODataReplacement) RenderObjects;
+
+struct ROSorterReplacement {
+ bool operator()( const RODataReplacement& ra, const RODataReplacement& rb ) const;
+};
+
+
+bool ROSorterReplacement::operator()( const RODataReplacement& ra, const RODataReplacement& rb ) const
+{
+ // Sort by layering depth. //@TODO:should this be here?
+ bool globalLayeringResult;
+ if (CompareGlobalLayeringData(ra.globalLayeringData, rb.globalLayeringData, globalLayeringResult))
+ return globalLayeringResult;
+
+ // Sort by subshader index used
+ if (ra.subshaderIndex != rb.subshaderIndex)
+ return ra.subshaderIndex < rb.subshaderIndex;
+
+ // Sort front to back
+ return ra.distance > rb.distance;
+}
+
+
+static inline float EvaluateObjectDepth (const Matrix4x4f& cameraMatrix, const TransformInfo& info)
+{
+ Vector3f center = info.worldAABB.GetCenter();
+ float d = cameraMatrix.MultiplyPoint3( center ).z;
+ Assert(IsFinite(d));
+ return d;
+}
+
+
+static void PerformRenderingReplacement (Camera& camera, const Matrix4x4f& curCameraMatrix, RenderObjects& renderData)
+{
+ // Sort
+ std::sort (renderData.begin(), renderData.end(), ROSorterReplacement());
+
+
+ GfxDevice& device = GetGfxDevice();
+ size_t ndata = renderData.size();
+ device.SetViewMatrix (curCameraMatrix.GetPtr());
+
+ for( size_t i = 0; i < ndata; ++i )
+ {
+ const RODataReplacement& roData = renderData[i];
+
+ const VisibleNode* node = roData.visibleNode;
+ Assert (node);
+ BaseRenderer* renderer = node->renderer;
+ Assert (renderer);
+ Shader* shader = roData.shader;
+
+ device.SetInverseScale(1.0f);
+
+ //@TODO: if this returns true and we have any sort of batching, we'd have to break batches here
+ renderer->ApplyCustomProperties(*roData.material, shader, roData.subshaderIndex);
+
+ ShaderLab::SubShader& subshader = roData.shader->GetShaderLabShader()->GetSubShader (roData.subshaderIndex);
+ int shaderPassCount = subshader.GetValidPassCount();
+ for (int p = 0; p < shaderPassCount; ++p)
+ {
+ const ChannelAssigns* channels = roData.material->SetPassWithShader(p, shader, roData.subshaderIndex);
+ if (channels)
+ {
+ SetupObjectMatrix (node->worldMatrix, node->transformType);
+ renderer->Render( renderer->GetSubsetIndex(roData.materialIndex), *channels );
+ }
+ }
+ }
+}
+
+static void AddReplacementObject (
+ RenderObjects& renderObjects,
+ Material* mat,
+ Shader* replacementShader,
+ bool noReplacementTag,
+ int replacementTagID,
+ const VisibleNode* visibleNode,
+ float distanceForSort,
+ int materialIndex,
+ GlobalLayeringData globalLayeringData
+
+ )
+{
+ if( mat == NULL )
+ mat = Material::GetDefault();
+ Shader *shader = mat->GetShader();
+
+ // Note: do not check whether object is in geometry queue range,
+ // let shader replacement handle that. E.g. terrain billboard shaders are actually
+ // beyond geometry queue, but still can output meaningful depth/normals information.
+
+ // Handle shader replacement
+ // Given a replacement shader and tag name:
+ // 1. if tag name is empty, then all objects are just rendered with replacement shader's first subshader
+ // 2. if tag name is given:
+ // * real object's subshader is queried for tag value.
+ // * if it does not have that tag, the object is not rendered.
+ // * subshader is found in the replacement shader, that has given tag with the given value. If no subshader found, object is not rendered.
+ // * that subshader is used instead to render the object.
+ int usedSubshaderIndex;
+ if (noReplacementTag)
+ {
+ usedSubshaderIndex = 0;
+ }
+ else
+ {
+ int subshaderTypeID = shader->GetShaderLabShader()->GetTag (replacementTagID, true);
+ if (subshaderTypeID < 0)
+ return; // skip rendering
+ usedSubshaderIndex = replacementShader->GetSubShaderWithTagValue (replacementTagID, subshaderTypeID);
+ if (usedSubshaderIndex == -1)
+ return; // skip rendering
+ }
+
+ renderObjects.push_back(RODataReplacement());
+ RODataReplacement& roData = renderObjects.back();
+ roData.visibleNode = visibleNode;
+ roData.distance = distanceForSort;
+
+ DebugAssertIf( !mat );
+ roData.material = mat;
+ roData.materialIndex = materialIndex;
+
+ roData.shader = replacementShader;
+ roData.subshaderIndex = usedSubshaderIndex;
+
+ roData.globalLayeringData = globalLayeringData;
+}
+
+void RenderSceneShaderReplacement (const VisibleNodes& contents, Shader* shader, const std::string& shaderReplaceTag)
+{
+ ShaderReplaceData replaceData;
+ replaceData.replacementShader = shader;
+ replaceData.replacementTagSet = !shaderReplaceTag.empty();
+ replaceData.replacementTagID = ShaderLab::GetShaderTagID(shaderReplaceTag);
+
+ RenderSceneShaderReplacement(contents, replaceData);
+}
+
+
+void RenderSceneShaderReplacement (const VisibleNodes& contents, const ShaderReplaceData& shaderReplace)
+{
+ Assert (shaderReplace.replacementShader != NULL);
+
+ const bool noReplacementTag = !shaderReplace.replacementTagSet;
+ const int replacementTagID = shaderReplace.replacementTagID;
+ Shader* replacementShader = shaderReplace.replacementShader;
+ Camera& camera = GetRenderManager().GetCurrentCamera();
+ Matrix4x4f curCameraMatrix = camera.GetWorldToCameraMatrix();
+
+ RenderObjects renderObjects;
+ renderObjects.reserve (contents.size()/4);
+
+ // Go over the objects
+ for( VisibleNodes::const_iterator i = contents.begin(); i != contents.end(); ++i )
+ {
+ float distanceForSort = EvaluateObjectDepth (curCameraMatrix, *i);
+
+ const BaseRenderer* renderer = i->renderer;
+
+ int matCount = renderer->GetMaterialCount();
+ for (int mi = 0; mi < matCount; ++mi)
+ {
+ Material* mat = renderer->GetMaterial(mi);
+ AddReplacementObject (
+ renderObjects,
+ mat,
+ replacementShader,
+ noReplacementTag,
+ replacementTagID,
+ &*i,
+ distanceForSort,
+ mi,
+ renderer->GetGlobalLayeringData()
+ );
+ }
+ }
+
+ // Render
+ PerformRenderingReplacement (camera, curCameraMatrix, renderObjects);
+}
+
+void RenderSceneShaderReplacement (const RenderObjectDataContainer& contents, Shader* replacementShader, const std::string& replacementTag)
+{
+ Assert (replacementShader);
+
+ const bool noReplacementTag = replacementTag.empty();
+ const int replacementTagID = ShaderLab::GetShaderTagID(replacementTag);
+
+ Camera& camera = GetRenderManager().GetCurrentCamera();
+ Matrix4x4f curCameraMatrix = camera.GetWorldToCameraMatrix();
+
+ RenderObjects renderObjects;
+ renderObjects.reserve (contents.size()/4);
+
+ // Go over the objects
+ for (RenderObjectDataContainer::const_iterator i = contents.begin(); i != contents.end(); ++i)
+ {
+ const RenderObjectData& ro = *i;
+ const BaseRenderer* renderer = ro.visibleNode->renderer;
+ Assert (renderer);
+ Material* mat = renderer->GetMaterial(ro.sourceMaterialIndex);
+ AddReplacementObject (
+ renderObjects,
+ mat,
+ replacementShader,
+ noReplacementTag,
+ replacementTagID,
+ ro.visibleNode,
+ ro.distance,
+ ro.sourceMaterialIndex,
+ renderer->GetGlobalLayeringData()
+ );
+ }
+
+ // Render
+ PerformRenderingReplacement (camera, curCameraMatrix, renderObjects);
+}
+
diff --git a/Runtime/Camera/RenderLoops/ReplacementRenderLoop.h b/Runtime/Camera/RenderLoops/ReplacementRenderLoop.h
new file mode 100644
index 0000000..0adce81
--- /dev/null
+++ b/Runtime/Camera/RenderLoops/ReplacementRenderLoop.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "Runtime/Camera/CullResults.h"
+#include "RenderLoopPrivate.h"
+#include <string>
+
+class Shader;
+
+void RenderSceneShaderReplacement (const VisibleNodes& contents, const ShaderReplaceData& shaderReplace);
+void RenderSceneShaderReplacement (const VisibleNodes& contents, Shader* shader, const std::string& shaderReplaceTag);
+void RenderSceneShaderReplacement (const RenderObjectDataContainer& contents, Shader* shader, const std::string& shaderReplaceTag);
diff --git a/Runtime/Camera/RenderManager.cpp b/Runtime/Camera/RenderManager.cpp
new file mode 100644
index 0000000..65301e5
--- /dev/null
+++ b/Runtime/Camera/RenderManager.cpp
@@ -0,0 +1,272 @@
+#include "UnityPrefix.h"
+#include "RenderManager.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Camera.h"
+#include "Renderable.h"
+#include <vector>
+#include "UnityScene.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystem.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "Runtime/Camera/CullResults.h"
+#include "Runtime/Camera/CameraCullingParameters.h"
+
+static RenderManager* gRenderManager = NULL;
+
+
+// NOTE: AddCamera/RemoveCamera defer the actual addition/removal when called from inside Culling/Rendering
+// loop. Reason: add/remove may invalidate iterators; and that happens in cases like disabling self (or some other)
+// camera from OnPostRender etc.
+
+RenderManager::RenderManager ()
+: m_InsideRenderOrCull(false)
+{
+ m_WindowRect = Rectf(0.0f, 0.0f, 128.0f, 128.0f );
+ m_CurrentViewPort[0] = m_CurrentViewPort[1] = 0;
+ m_CurrentViewPort[2] = m_CurrentViewPort[3] = 128;
+ m_CurrentCamera = NULL;
+}
+
+RenderManager::~RenderManager () {
+ Assert (m_Cameras.empty ());
+}
+
+
+PROFILER_INFORMATION(gCameraRenderManagerProfile, "Camera.Render", kProfilerRender)
+PROFILER_INFORMATION(gCameraUpdateRenderersProfile, "Rendering.UpdateDirtyRenderers", kProfilerRender)
+
+
+void RenderManager::RenderOffscreenCameras()
+{
+ m_InsideRenderOrCull = true;
+
+ // Make sure current viewport is fully preserved after rendering offscreen cameras
+ int savedViewport[4];
+ for (int i = 0; i < 4; ++i)
+ savedViewport[i] = m_CurrentViewPort[i];
+
+ // Render all offscreen cameras so they are ready for the real cameras.
+ for (CameraContainer::iterator i = m_OffScreenCameras.begin(); i != m_OffScreenCameras.end(); ++i )
+ {
+ Camera* cam = *i;
+ PROFILER_AUTO_GFX(gCameraRenderManagerProfile, cam)
+
+ m_CurrentCamera = cam;
+ CullResults cullResults;
+ if( cam && cam->GetEnabled() ) // might become NULL or disabled in OnPreCull
+ cam->Cull(cullResults);
+ if( cam && cam->GetEnabled() ) // check again, might get disabled in culling
+ cam->Render( cullResults, Camera::kRenderFlagSetRenderTarget);
+ }
+
+ for (int i = 0; i < 4; ++i)
+ m_CurrentViewPort[i] = savedViewport[i];
+
+ m_InsideRenderOrCull = false;
+ AddRemoveCamerasDelayed();
+}
+
+void RenderManager::RenderCameras()
+{
+ m_InsideRenderOrCull = true;
+
+ Unity::Scene& scene = GetScene();
+
+ // Render on-screen cameras.
+ for (CameraContainer::iterator i = m_Cameras.begin(); i != m_Cameras.end(); ++i )
+ {
+ Camera* cam = *i;
+ PROFILER_AUTO_GFX(gCameraRenderManagerProfile, cam)
+
+ /////@TODO: This is not reflected in the standalone render function...
+ scene.BeginCameraRender();
+ m_CurrentCamera = cam;
+ CullResults cullResults;
+ if( cam && cam->GetEnabled() ) // might become NULL or disabled in OnPreCull
+ cam->Cull(cullResults);
+ if( cam && cam->GetEnabled() ) // check again, might get disabled in culling
+ cam->Render( cullResults, Camera::kRenderFlagSetRenderTarget );
+ scene.EndCameraRender();
+ }
+
+ m_InsideRenderOrCull = false;
+ AddRemoveCamerasDelayed();
+}
+
+
+/** Get a render callback for each Camera */
+void RenderManager::AddCameraRenderable (Renderable *r, int depth) {
+// Assert (depth <= Camera::kRenderQueueCount);
+ #if DEBUGMODE
+ for (Renderables::iterator i = m_Renderables.begin (); i != m_Renderables.end();i++) {
+ if (i->second == r && i->first == depth)
+ AssertString ("RenderManager: renderable with same depth already added");
+ }
+ #endif
+
+ m_Renderables.insert (std::make_pair (depth, r));
+}
+
+void RenderManager::RemoveCameraRenderable (Renderable *r) {
+ Renderables::iterator next;
+ for (Renderables::iterator i = m_Renderables.begin (); i != m_Renderables.end();i=next) {
+ next = i;
+ next++;
+ if (i->second == r)
+ {
+ m_Renderables.erase (i);
+ }
+ }
+}
+
+
+void RenderManager::InvokeOnRenderObjectCallbacks ()
+{
+ if (m_OnRenderObjectCallbacks.empty())
+ return;
+
+#if ENABLE_MONO || UNITY_WINRT
+ SafeIterator<MonoBehaviourList> it (m_OnRenderObjectCallbacks);
+ while (it.Next())
+ {
+ MonoBehaviour& beh = **it;
+ beh.InvokeOnRenderObject ();
+ }
+#endif
+}
+
+
+#if UNITY_EDITOR
+
+bool RenderManager::HasFullscreenCamera () const
+{
+ for (CameraContainer::const_iterator i = m_Cameras.begin(); i != m_Cameras.end(); i++) {
+ Rectf viewRect = (*i)->GetNormalizedViewportRect ();
+ if (CompareApproximately(Rectf(0,0,1,1), viewRect))
+ return true;
+ }
+ return false;
+}
+
+#endif // UNITY_EDITOR
+
+
+void RenderManager::AddCamera (Camera *c)
+{
+ Assert (c != NULL);
+
+ PPtr<Camera> cam(c);
+ if( m_InsideRenderOrCull )
+ {
+ m_CamerasToRemove.remove( cam );
+ m_CamerasToAdd.push_back( cam );
+ return;
+ }
+
+ m_CamerasToAdd.remove(c);
+ m_CamerasToRemove.remove(c);
+
+ m_Cameras.remove( cam );
+ m_OffScreenCameras.remove( cam );
+ CameraContainer &queue = (c->GetTargetTexture() == NULL) ? m_Cameras : m_OffScreenCameras;
+
+ for (CameraContainer::iterator i=queue.begin ();i != queue.end ();i++)
+ {
+ Camera* curCamera = *i;
+ if (curCamera && curCamera->GetDepth () > c->GetDepth ())
+ {
+ queue.insert (i, c);
+ return;
+ }
+ }
+ queue.push_back (c);
+}
+
+void RenderManager::RemoveCamera (Camera *c)
+{
+ PPtr<Camera> cam(c);
+
+ m_CamerasToAdd.remove(c);
+ m_CamerasToRemove.remove(c);
+
+ if( m_InsideRenderOrCull )
+ {
+ m_CamerasToRemove.push_back( cam );
+ }
+ else
+ {
+ m_Cameras.remove( cam );
+ m_OffScreenCameras.remove( cam );
+ }
+
+ Camera* currentCamera = m_CurrentCamera;
+ if (currentCamera == c)
+ {
+ if (m_Cameras.empty ())
+ m_CurrentCamera = NULL;
+ else
+ m_CurrentCamera = m_Cameras.front (); // ??? maybe better choose next
+ }
+}
+
+void RenderManager::AddRemoveCamerasDelayed()
+{
+ DebugAssertIf( m_InsideRenderOrCull );
+ for( CameraContainer::iterator i = m_CamerasToRemove.begin(); i != m_CamerasToRemove.end(); /**/ )
+ {
+ Camera* cam = *i;
+ ++i; // increment iterator before removing camera; as it changes the list
+ RemoveCamera( cam );
+ }
+ m_CamerasToRemove.clear();
+ for( CameraContainer::iterator i = m_CamerasToAdd.begin(); i != m_CamerasToAdd.end(); /**/ )
+ {
+ Camera* cam = *i;
+ ++i; // increment iterator before adding camera; as it changes the list
+ AddCamera( cam );
+ }
+ m_CamerasToAdd.clear();
+}
+
+
+void RenderManager::SetWindowRect (const Rectf& r)
+{
+ m_WindowRect = r;
+ for( CameraContainer::iterator i=m_Cameras.begin ();i != m_Cameras.end (); ++i )
+ (**i).WindowSizeHasChanged();
+}
+
+
+void RenderManager::UpdateAllRenderers()
+{
+ ParticleSystem::SyncJobs();
+
+ PROFILER_AUTO(gCameraUpdateRenderersProfile, NULL)
+ Renderer::UpdateAllRenderersInternal();
+}
+
+
+RenderManager& GetRenderManager ()
+{
+ return *gRenderManager;
+}
+
+RenderManager* GetRenderManagerPtr ()
+{
+ return gRenderManager;
+}
+
+void RenderManager::InitializeClass ()
+{
+ Assert(gRenderManager == NULL);
+ gRenderManager = new RenderManager ();
+}
+
+void RenderManager::CleanupClass ()
+{
+ Assert(gRenderManager != NULL);
+ delete gRenderManager;
+ gRenderManager = NULL;
+}
diff --git a/Runtime/Camera/RenderManager.h b/Runtime/Camera/RenderManager.h
new file mode 100644
index 0000000..78c3e38
--- /dev/null
+++ b/Runtime/Camera/RenderManager.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#include "Runtime/Utilities/LinkedList.h"
+#include <list>
+#include <map>
+#include "Runtime/Math/Rect.h"
+#include "Camera.h"
+
+class Renderable;
+class MonoBehaviour;
+typedef ListNode<MonoBehaviour> MonoBehaviourListNode;
+
+// Camera handling
+class RenderManager {
+public:
+ typedef std::list<PPtr<Camera> > CameraContainer;
+ typedef std::multimap<int, Renderable *> Renderables;
+
+ RenderManager ();
+ ~RenderManager ();
+
+ void RenderOffscreenCameras();
+ void RenderCameras();
+
+ Camera &GetCurrentCamera () { Assert (!m_CurrentCamera.IsNull ()); return *m_CurrentCamera; }
+ Camera* GetCurrentCameraPtr () { return m_CurrentCamera; }
+ void SetCurrentCamera (Camera *c) { m_CurrentCamera = c; }
+
+ void AddCamera (Camera *c);
+ void RemoveCamera (Camera *c);
+
+ // Add/Remove Renderable. The Renderable.Render() method is called before the RenderQueue with index beforeRenderqueueIndex
+ // is rendered in Camera.Render()
+ void AddCameraRenderable (Renderable *r, int beforeRenderqueueIndex);
+ void RemoveCameraRenderable (Renderable *r);
+ const Renderables& GetRenderables () { return m_Renderables; }
+
+ void AddOnRenderObject (MonoBehaviourListNode& beh) { m_OnRenderObjectCallbacks.push_back(beh); }
+ void InvokeOnRenderObjectCallbacks ();
+
+ CameraContainer& GetOnscreenCameras () { return m_Cameras; }
+ CameraContainer& GetOffscreenCameras () { return m_OffScreenCameras; }
+
+ // The window we're rendering into.
+ // Most often xmin/xmax of this window are zero, except when using fixed aspect
+ // in the game view.
+ const Rectf &GetWindowRect() const { return m_WindowRect; }
+ void SetWindowRect (const Rectf& r);
+
+ const int* GetCurrentViewPort() const { return m_CurrentViewPort; }
+ int* GetCurrentViewPortWriteable() { return m_CurrentViewPort; }
+
+ #if UNITY_EDITOR
+ bool HasFullscreenCamera () const;
+ #endif
+
+ static void UpdateAllRenderers();
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+private:
+ void AddRemoveCamerasDelayed();
+
+private:
+ PPtr<Camera> m_CurrentCamera;
+ CameraContainer m_Cameras, m_OffScreenCameras;
+ CameraContainer m_CamerasToAdd;
+ CameraContainer m_CamerasToRemove;
+ bool m_InsideRenderOrCull;
+
+ Rectf m_WindowRect;
+ int m_CurrentViewPort[4]; // left, bottom, width, height; in pixels into current render target
+
+ Renderables m_Renderables;
+ typedef List<MonoBehaviourListNode> MonoBehaviourList;
+ MonoBehaviourList m_OnRenderObjectCallbacks;
+};
+
+RenderManager& GetRenderManager ();
+RenderManager* GetRenderManagerPtr ();
+inline Camera &GetCurrentCamera () { return GetRenderManager().GetCurrentCamera(); }
+inline Camera* GetCurrentCameraPtr () { return GetRenderManager().GetCurrentCameraPtr(); }
diff --git a/Runtime/Camera/RenderSettings.cpp b/Runtime/Camera/RenderSettings.cpp
new file mode 100644
index 0000000..8c4d4ee
--- /dev/null
+++ b/Runtime/Camera/RenderSettings.cpp
@@ -0,0 +1,262 @@
+#include "UnityPrefix.h"
+#include "RenderSettings.h"
+#include "External/shaderlab/Library/properties.h"
+#include "Runtime/Graphics/Texture2D.h"
+#include "Light.h"
+#include "Runtime/Misc/ResourceManager.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "Runtime/Camera/LightManager.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+
+static SHADERPROP (LightTextureB0);
+static SHADERPROP (HaloFalloff);
+
+
+RenderSettings::RenderSettings (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+ m_LinearFogStart = 0; m_LinearFogEnd = 300; m_FogDensity = .01f;
+ m_Fog = false;
+ m_FogMode = kFogExp2;
+ m_FlareFadeSpeed = 3.0f;
+}
+
+
+void RenderSettings::Reset ()
+{
+ Super::Reset();
+
+ m_FogColor.Set (.5,.5,.5,1);
+ m_LinearFogStart = 0; m_LinearFogEnd = 300; m_FogDensity = .01f;
+ m_Fog = false;
+ m_FogMode = kFogExp2;
+ m_AmbientLight.Set (.2f, .2f, .2f, 1);
+ m_FlareStrength = 1.0f;
+ m_FlareFadeSpeed = 3.0f;
+ m_HaloStrength = .5f;
+}
+
+float RenderSettings::CalcFogFactor (float distance) const
+{
+ if (m_Fog)
+ return 1.0F-exp(-(m_FogDensity * m_FogDensity * distance * distance));
+
+ return 0.0F;
+}
+
+Texture2D* RenderSettings::GetDefaultSpotCookie()
+{
+ Texture2D* tex = m_SpotCookie;
+ if (tex)
+ return tex;
+ else
+ {
+ static PPtr<Texture2D> fallback;
+ if (!fallback)
+ fallback = GetBuiltinResource<Texture2D> ("Soft.psd");
+ return fallback;
+ }
+}
+
+RenderSettings::~RenderSettings ()
+{
+}
+
+void RenderSettings::SetupAmbient () const
+{
+ ColorRGBAf amb = GetAmbientLightInActiveColorSpace() * 0.5F;
+ GetGfxDevice().SetAmbient( amb.GetPtr() );
+}
+
+void RenderSettings::AwakeFromLoad (AwakeFromLoadMode awakeMode) {
+ Super::AwakeFromLoad (awakeMode);
+
+ ShaderLab::g_GlobalProperties->SetTexture (kSLPropLightTextureB0, builtintex::GetAttenuationTexture());
+
+ ApplyFog();
+ ApplyHaloTexture();
+
+ // disable light 0 because GL state has it on by default (or something like that)
+ // This handles the wacky case where we have 0 vertex lights and a vertex lit shader (DOH!).
+ GetGfxDevice().DisableLights (0);
+
+ if ((awakeMode & kDidLoadFromDisk) == 0)
+ ApplyFlareAndHaloStrength ();
+}
+
+void RenderSettings::ApplyFlareAndHaloStrength ()
+{
+ // if we are editing from inside the editor, the Halo values might have changed,
+ // So all lights need to update their halos
+ std::vector<SInt32> obj;
+ Object::FindAllDerivedObjects(ClassID (Light), &obj);
+ for (std::vector<SInt32>::iterator i = obj.begin(); i != obj.end(); i++) {
+ Light *l = PPtr<Light> (*i);
+ l->SetupHalo ();
+ l->SetupFlare ();
+ }
+}
+
+void RenderSettings::CheckConsistency () {
+ m_FogDensity = std::min (std::max (m_FogDensity, 0.0f), 1.0f);
+ m_HaloStrength = std::min (std::max (m_HaloStrength, 0.0f), 1.0f);
+}
+
+void RenderSettings::ApplyHaloTexture()
+{
+ Texture2D *tex = m_HaloTexture;
+ ShaderLab::g_GlobalProperties->SetTexture (kSLPropHaloFalloff, tex?tex:builtintex::GetHaloTexture());
+}
+
+void RenderSettings::ApplyFog ()
+{
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+ ShaderLab::g_GlobalFogMode = m_Fog ? static_cast<FogMode>(m_FogMode) : kFogDisabled;
+ float fogDensity = m_FogDensity;
+ float fogStart = m_LinearFogStart;
+ float fogEnd = m_LinearFogEnd;
+ if (ShaderLab::g_GlobalFogMode == kFogDisabled)
+ {
+ fogDensity = 0.0f;
+ fogStart = 10000.0f;
+ fogEnd = 20000.0f;
+ }
+
+ params.SetVectorParam(kShaderVecUnityFogStart, Vector4f(fogStart, fogStart, fogStart, fogStart));
+ params.SetVectorParam(kShaderVecUnityFogEnd, Vector4f(fogEnd, fogEnd, fogEnd, fogEnd));
+ params.SetVectorParam(kShaderVecUnityFogDensity, Vector4f(fogDensity, fogDensity, fogDensity, fogDensity));
+
+ ColorRGBAf fogColor = GammaToActiveColorSpace(m_FogColor);
+ params.SetVectorParam(kShaderVecUnityFogColor, Vector4f(fogColor.GetPtr()));
+
+}
+
+template<class TransferFunc>
+void RenderSettings::Transfer (TransferFunc& transfer) {
+ TRANSFER_SIMPLE (m_Fog);
+ transfer.Align();
+ TRANSFER_SIMPLE (m_FogColor);
+ TRANSFER_SIMPLE (m_FogMode);
+ TRANSFER_SIMPLE (m_FogDensity);
+ TRANSFER_SIMPLE (m_LinearFogStart);
+ TRANSFER_SIMPLE (m_LinearFogEnd);
+ TRANSFER_SIMPLE (m_AmbientLight);
+ TRANSFER_SIMPLE (m_SkyboxMaterial);
+ TRANSFER (m_HaloStrength);
+ TRANSFER (m_FlareStrength);
+ TRANSFER (m_FlareFadeSpeed);
+ TRANSFER (m_HaloTexture);
+ TRANSFER (m_SpotCookie);
+ Super::Transfer (transfer);
+}
+
+void RenderSettings::InitializeClass ()
+{
+ RegisterAllowNameConversion(GetClassStringStatic (), "m_Ambient", "m_AmbientLight");
+ LightManager::InitializeClass();
+}
+
+void RenderSettings::PostInitializeClass ()
+{
+ builtintex::GenerateBuiltinTextures();
+}
+
+void RenderSettings::CleanupClass()
+{
+ LightManager::CleanupClass();
+}
+
+void RenderSettings::SetSkyboxMaterial (Material *mat) {
+ m_SkyboxMaterial = mat;
+ SetDirty();
+}
+
+void RenderSettings::SetFogColor (const ColorRGBAf& color)
+{
+ m_FogColor = color;
+ SetDirty();
+ ApplyFog ();
+}
+
+void RenderSettings::SetUseFog (bool fog)
+{
+ m_Fog = fog;
+ SetDirty();
+ ApplyFog ();
+}
+
+void RenderSettings::SetUseFogNoDirty (bool fog)
+{
+ m_Fog = fog;
+ ApplyFog ();
+}
+
+void RenderSettings::SetFogMode (FogMode fm)
+{
+ m_FogMode = fm;
+ SetDirty();
+ ApplyFog ();
+}
+
+void RenderSettings::SetFogDensity (float fog)
+{
+ m_FogDensity = fog;
+ SetDirty();
+ ApplyFog ();
+}
+
+void RenderSettings::SetLinearFogStart (float d)
+{
+ m_LinearFogStart = d;
+ SetDirty();
+ ApplyFog ();
+}
+
+void RenderSettings::SetLinearFogEnd (float d)
+{
+ m_LinearFogEnd = d;
+ SetDirty();
+ ApplyFog ();
+}
+
+void RenderSettings::SetAmbientLight (const ColorRGBAf &col)
+{
+ m_AmbientLight = col;
+ ApplyFog ();
+ SetDirty();
+}
+
+#if UNITY_EDITOR
+void RenderSettings::SetAmbientLightNoDirty (const ColorRGBAf &col)
+{
+ m_AmbientLight = col;
+ ApplyFog ();
+}
+#endif
+
+void RenderSettings::SetFlareStrength (float flare)
+{
+ m_FlareStrength = flare;
+ SetDirty();
+ ApplyFlareAndHaloStrength();
+}
+
+void RenderSettings::SetFlareFadeSpeed (float time)
+{
+ m_FlareFadeSpeed = time;
+ SetDirty();
+ ApplyFlareAndHaloStrength();
+}
+
+void RenderSettings::SetHaloStrength (float halo)
+{
+ m_HaloStrength = halo;
+ SetDirty();
+ ApplyFlareAndHaloStrength();
+}
+
+IMPLEMENT_CLASS_HAS_POSTINIT (RenderSettings)
+IMPLEMENT_OBJECT_SERIALIZE (RenderSettings)
+GET_MANAGER (RenderSettings)
diff --git a/Runtime/Camera/RenderSettings.h b/Runtime/Camera/RenderSettings.h
new file mode 100644
index 0000000..ac9f791
--- /dev/null
+++ b/Runtime/Camera/RenderSettings.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#include "Runtime/BaseClasses/GameManager.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Graphics/GeneratedTextures.h"
+#include "Runtime/Modules/ExportModules.h"
+
+class Texture2D;
+
+class EXPORT_COREMODULE RenderSettings : public LevelGameManager
+{
+public:
+ RenderSettings (MemLabelId label, ObjectCreationMode mode);
+ // virtual ~RenderSettings(); declared-by-macro
+ REGISTER_DERIVED_CLASS (RenderSettings, LevelGameManager)
+ DECLARE_OBJECT_SERIALIZE (RenderSettings)
+
+ void CheckConsistency ();
+ virtual void Reset ();
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ /// Get the scene ambient light
+ const ColorRGBAf& GetAmbientLight () const { return m_AmbientLight; }
+
+ /// Get the ambient light in active color space
+ ColorRGBAf GetAmbientLightInActiveColorSpace () const { return GammaToActiveColorSpace(m_AmbientLight); }
+
+ void SetAmbientLight (const ColorRGBAf &col);
+ #if UNITY_EDITOR
+ void SetAmbientLightNoDirty (const ColorRGBAf &col);
+ #endif
+
+ /// Get the default spotlight cookie.
+ /// This is used if no cookies have been assigned to a spotlight.
+ Texture2D *GetDefaultSpotCookie();
+
+ void SetupAmbient () const;
+
+ // Fog
+ const ColorRGBAf& GetFogColor() const { return m_FogColor; }
+ void SetFogColor (const ColorRGBAf& color);
+ bool GetUseFog () const { return m_Fog; }
+ void SetUseFog (bool fog);
+ void SetUseFogNoDirty (bool fog);
+ FogMode GetFogMode () const { return static_cast<FogMode>(m_FogMode); }
+ void SetFogMode (FogMode fm);
+ float GetFogDensity () const { return m_FogDensity; }
+ void SetFogDensity (float fog);
+ float GetLinearFogStart() const { return m_LinearFogStart; }
+ void SetLinearFogStart (float d);
+ float GetLinearFogEnd() const { return m_LinearFogEnd; }
+ void SetLinearFogEnd (float d);
+
+ Material *GetSkyboxMaterial () const { return m_SkyboxMaterial; }
+ void SetSkyboxMaterial (Material *mat);
+ float GetHaloStrength () const { return m_HaloStrength; }
+ void SetHaloStrength (float halo);
+
+ float GetFlareStrength () const { return m_FlareStrength; }
+ void SetFlareStrength (float flare);
+ float GetFlareFadeSpeed () const { return m_FlareFadeSpeed; }
+ void SetFlareFadeSpeed (float flare);
+
+ float CalcFogFactor (float distance) const;
+
+ static void InitializeClass ();
+ static void PostInitializeClass ();
+ static void CleanupClass ();
+
+private:
+ void ApplyFlareAndHaloStrength ();
+ void ApplyHaloTexture();
+ void ApplyFog ();
+
+private:
+ ColorRGBAf m_AmbientLight; ///< Scene ambient color.
+ float m_HaloStrength; ///< Strength of light halos range {0, 1}
+ float m_FlareStrength; ///< Strength of light flares range {0, 1}
+ float m_FlareFadeSpeed; ///< Fade time for a flare
+ bool m_Fog; ///< Use fog in the scene?
+ int m_FogMode; ///< enum { Linear=1, Exponential, Exp2 } Fog mode to use.
+ ColorRGBAf m_FogColor; ///< Fog color.
+ float m_LinearFogStart; ///< Starting distance for linear fog.
+ float m_LinearFogEnd; ///< End distance for linear fog.
+ float m_FogDensity; ///< Density for exponential fog.
+ PPtr<Texture2D> m_SpotCookie; ///< The default spotlight cookie
+ PPtr<Texture2D> m_HaloTexture; ///< The light halo texture
+ PPtr<Material> m_HaloMaterial;
+ PPtr<Material> m_SkyboxMaterial; ///< The material used to render the skybox
+};
+
+EXPORT_COREMODULE RenderSettings& GetRenderSettings ();
diff --git a/Runtime/Camera/Renderable.h b/Runtime/Camera/Renderable.h
new file mode 100644
index 0000000..ed56577
--- /dev/null
+++ b/Runtime/Camera/Renderable.h
@@ -0,0 +1,30 @@
+#ifndef RENDERABLE_H
+#define RENDERABLE_H
+
+class RenderTexture;
+namespace Unity { class Component; }
+struct CullResults;
+
+class Renderable {
+public:
+ virtual void RenderRenderable (const CullResults& cullResults) = 0;
+};
+
+typedef void RenderImageFilterFunc (Unity::Component* component, RenderTexture* source, RenderTexture* dest);
+
+struct ImageFilter
+{
+ Unity::Component* component;
+ RenderImageFilterFunc* renderFunc;
+
+ bool transformsToLDR;
+ bool afterOpaque;
+
+ ImageFilter (Unity::Component* inComponent, RenderImageFilterFunc* inRenderFunc, bool inLDR, bool inAfterOpaque)
+ : component(inComponent), renderFunc(inRenderFunc), afterOpaque(inAfterOpaque), transformsToLDR(inLDR){ }
+
+ bool operator==(const ImageFilter& o) const { return component==o.component && renderFunc==o.renderFunc; }
+ bool operator!=(const ImageFilter& o) const { return component!=o.component || renderFunc!=o.renderFunc; }
+};
+
+#endif
diff --git a/Runtime/Camera/Renderqueue.cpp b/Runtime/Camera/Renderqueue.cpp
new file mode 100644
index 0000000..7e1baf2
--- /dev/null
+++ b/Runtime/Camera/Renderqueue.cpp
@@ -0,0 +1,268 @@
+#include "UnityPrefix.h"
+#include "Renderqueue.h"
+#include "BaseRenderer.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Camera.h"
+#include "External/shaderlab/Library/properties.h"
+#include "External/shaderlab/Library/shaderstate.h"
+#include "UnityScene.h"
+#include "RenderSettings.h"
+#include "RenderManager.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/Shader.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Graphics/LightmapSettings.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "Runtime/Shaders/VBO.h"
+#include "CameraUtil.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Profiler/Profiler.h"
+#if UNITY_EDITOR
+#include "Editor/Src/LightmapVisualization.h"
+#endif
+
+PROFILER_INFORMATION(gWarmupShadersProfile, "Shader.WarmupAllShaders", kProfilerRender)
+
+
+static ShaderKeyword kLightmapOffKeyword = keywords::Create("LIGHTMAP_OFF");
+static ShaderKeyword kLightmapOnKeyword = keywords::Create("LIGHTMAP_ON");
+
+static ShaderKeyword kDirLightmapOffKeyword = keywords::Create("DIRLIGHTMAP_OFF");
+static ShaderKeyword kDirLightmapOnKeyword = keywords::Create("DIRLIGHTMAP_ON");
+
+
+void SetupObjectMatrix (const Matrix4x4f& m, int transformType)
+{
+ GfxDevice& device = GetGfxDevice();
+ device.SetWorldMatrixAndType( m.GetPtr(), TransformType(transformType) );
+}
+
+
+UInt32 GetCurrentRenderOptions ()
+{
+ UInt32 currentRenderOptions = 0;
+ if( GetQualitySettings().GetCurrent().softVegetation )
+ currentRenderOptions |= (1 << kShaderOptionSoftVegetation);
+ return currentRenderOptions;
+}
+
+bool CheckShouldRenderPass (int pass, Material& material)
+{
+ UInt32 currentRenderOptions = GetCurrentRenderOptions ();
+
+ ShaderPassType passType;
+ UInt32 passRenderOptions;
+ ShaderLab::Pass* slpass = material.GetShader()->GetShaderLabShader()->GetActiveSubShader().GetPass(pass);
+ slpass->GetPassOptions (passType, passRenderOptions);
+
+ // All options that a pass requires must be on
+ if( (currentRenderOptions & passRenderOptions) != passRenderOptions )
+ return false; // some options are off, skip this pass
+
+ return true;
+}
+
+bool SetupObjectLightmaps (const LightmapSettings& lightmapper, UInt32 lightmapIndex, const Vector4f& lightmapST, bool setMatrix)
+{
+ // setup object's lightmap info
+ LightmapSettings::TextureTriple lmTextures = lightmapper.GetLightmapTexture (lightmapIndex);
+#if UNITY_EDITOR
+ if (lmTextures.first.m_ID && GetLightmapVisualization().GetUseLightmapsForRendering())
+#else
+ if (lmTextures.first.m_ID)
+#endif
+ {
+ g_ShaderKeywords.Enable( kLightmapOnKeyword );
+ g_ShaderKeywords.Disable( kLightmapOffKeyword );
+
+ if (lightmapper.GetLightmapsMode() == LightmapSettings::kDirectionalLightmapsMode)
+ {
+ g_ShaderKeywords.Enable( kDirLightmapOnKeyword );
+ g_ShaderKeywords.Disable( kDirLightmapOffKeyword );
+ }
+ else
+ {
+ g_ShaderKeywords.Enable( kDirLightmapOffKeyword );
+ g_ShaderKeywords.Disable( kDirLightmapOnKeyword );
+ }
+
+
+ #define INIT_LIGHTMAP_TEX(texI, tex) GetGfxDevice().GetBuiltinParamValues().GetWritableTexEnvParam(texI).SetTextureInfo(tex, kTexDim2D, NULL)
+
+ //@TODO: optimize
+ TextureID indTex = lmTextures.second.m_ID ? lmTextures.second : builtintex::GetBlackTextureID();
+ TextureID thirdTex = lmTextures.third.m_ID ? lmTextures.third : builtintex::GetBlackTextureID();
+ INIT_LIGHTMAP_TEX(kShaderTexEnvUnityLightmap, lmTextures.first);
+ INIT_LIGHTMAP_TEX(kShaderTexEnvUnityLightmapInd, indTex);
+ INIT_LIGHTMAP_TEX(kShaderTexEnvUnityLightmapThird, thirdTex);
+
+ #undef INIT_LIGHTMAP_TEX
+
+ GetGfxDevice().GetBuiltinParamValues().SetVectorParam(kShaderVecUnityLightmapST, lightmapST);
+ if (setMatrix)
+ {
+ Matrix4x4f mat;
+ mat.SetIdentity();
+ mat.Get(0,0) = lightmapST[0];
+ mat.Get(1,1) = lightmapST[1];
+ mat.Get(0,3) = lightmapST[2];
+ mat.Get(1,3) = lightmapST[3];
+ GetGfxDevice().GetBuiltinParamValues().SetMatrixParam(kShaderMatLightmapMatrix, mat);
+ }
+ return true;
+ }
+ else
+ {
+ g_ShaderKeywords.Enable( kLightmapOffKeyword );
+ g_ShaderKeywords.Disable( kLightmapOnKeyword );
+
+ g_ShaderKeywords.Enable( kDirLightmapOffKeyword );
+ g_ShaderKeywords.Disable( kDirLightmapOnKeyword );
+ return false;
+ }
+}
+
+
+typedef std::set<const void*> PointerSet;
+
+
+static int WarmupOnePass (ShaderLab::Pass& pass, const ShaderLab::PropertySheet* props, DynamicVBO& vbo, PointerSet& passes, PointerSet& programs)
+{
+ if (pass.GetPassType() != ShaderLab::Pass::kPassNormal)
+ return 0;
+
+ // check if this pass already done (we clone quite a few passes via Fallback/UsePass)
+ if (passes.find(&pass) != passes.end())
+ return 0;
+ passes.insert (&pass);
+
+
+ // If we have vertex or fragment program, pick the one with larger permutation count.
+ const ShaderLab::Program* vp = pass.GetState().GetProgram(kShaderVertex);
+ const ShaderLab::Program* fp = pass.GetState().GetProgram(kShaderFragment);
+
+ const ShaderLab::Program* prog = NULL;
+ if (vp && fp)
+ prog = vp->GetSubProgramCount() > fp->GetSubProgramCount() ? vp : fp;
+ else if (vp)
+ prog = vp;
+ else if (fp)
+ prog = fp;
+
+ if (!prog)
+ {
+ // Fixed function; just apply shader state
+ const ChannelAssigns* channels = pass.ApplyPass (0, props);
+ vbo.DrawChunk (*channels);
+ GPU_TIMESTAMP();
+ return 1;
+ }
+ else
+ {
+ // Check if this program already done (we cache identical programs)
+ if (programs.find(prog) != programs.end())
+ return 0;
+ programs.insert (prog);
+
+ // Go over all shader permutations in the program
+ int combos = 0;
+ for (int i = 0; i < prog->GetSubProgramCount(); ++i)
+ {
+ g_ShaderKeywords = prog->GetSubProgramKeywords (i);
+ const ChannelAssigns* channels = pass.ApplyPass (0, props);
+ vbo.DrawChunk (*channels);
+ GPU_TIMESTAMP();
+ ++combos;
+ }
+ return combos;
+ }
+}
+
+static int WarmupOneShader (ShaderLab::IntShader& shader, DynamicVBO& vbo, PointerSet& passes, PointerSet& programs)
+{
+ ShaderKeywordSet savedKeywords = g_ShaderKeywords;
+
+ int combos = 0;
+
+ const ShaderLab::PropertySheet* props = shader.GetDefaultProperties();
+ for (size_t is = 0; is < shader.GetSubShaders().size(); ++is)
+ {
+ ShaderLab::SubShader& ss = shader.GetSubShader(is);
+ for (size_t ip = 0; ip < ss.GetValidPassCount(); ++ip)
+ {
+ combos += WarmupOnePass (*ss.GetPass(ip), props, vbo, passes, programs);
+ }
+ }
+
+ g_ShaderKeywords = savedKeywords;
+
+ return combos;
+}
+
+void WarmupAllShaders ()
+{
+ PROFILER_AUTO(gWarmupShadersProfile, NULL);
+
+ double t0 = GetTimeSinceStartup();
+ int shaderCount = 0;
+ int comboCount = 0;
+
+ // Get a dynamic VBO chunk with one triangle
+ struct FullVertex {
+ Vector3f vert;
+ Vector3f normal;
+ ColorRGBA32 color;
+ Vector2f uv1;
+ Vector2f uv2;
+ Vector4f tangent;
+ };
+
+ GfxDevice& device = GetGfxDevice ();
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ FullVertex* vbPtr;
+ if (!vbo.GetChunk(
+ (1<<kShaderChannelVertex) |
+ (1<<kShaderChannelNormal) |
+ (1<<kShaderChannelColor) |
+ (1<<kShaderChannelTexCoord0) |
+ (1<<kShaderChannelTexCoord1) |
+ (1<<kShaderChannelTangent),
+ 3, 0,
+ DynamicVBO::kDrawTriangleStrip,
+ (void**)&vbPtr, NULL))
+ {
+ return;
+ }
+ // Set all vertex data to zero; this will ensure nothing is changed on screen
+ // but is enough for the driver to actually issue a draw call with a fully
+ // prepared shader/state.
+ memset (vbPtr, 0, 3*sizeof(FullVertex));
+ vbo.ReleaseChunk (3, 0);
+
+ DeviceMVPMatricesState saveMVPMatrices;
+ LoadFullScreenOrthoMatrix ();
+
+ PointerSet passes, programs;
+
+ // Go over all currently loaded shaders
+ std::vector<SInt32> allShaderObjects;
+ Object::FindAllDerivedObjects (ClassID (Shader), &allShaderObjects);
+ for (std::vector<SInt32>::iterator i = allShaderObjects.begin(); i != allShaderObjects.end(); ++i)
+ {
+ Shader* s = PPtr<Shader>(*i);
+ if (!s)
+ continue;
+ ShaderLab::IntShader* shader = s->GetShaderLabShader();
+ if (!shader)
+ continue;
+
+ int combos = WarmupOneShader (*shader, vbo, passes, programs);
+ ++shaderCount;
+ comboCount += combos;
+ }
+
+ double t1 = GetTimeSinceStartup();
+ printf_console ("Shader warmup: %i shaders %i combinations %.3fs\n", shaderCount, comboCount, (t1-t0));
+}
diff --git a/Runtime/Camera/Renderqueue.h b/Runtime/Camera/Renderqueue.h
new file mode 100644
index 0000000..766caac
--- /dev/null
+++ b/Runtime/Camera/Renderqueue.h
@@ -0,0 +1,23 @@
+#ifndef RENDERQUEUE_H
+#define RENDERQUEUE_H
+
+class Transform;
+class Vector4f;
+class Matrix4x4f;
+class LightmapSettings;
+namespace Unity { class Material; }
+
+
+void SetupObjectMatrix (const Matrix4x4f& m, int transformType);
+
+UInt32 GetCurrentRenderOptions ();
+bool CheckShouldRenderPass (int pass, Unity::Material& material);
+
+bool SetupObjectLightmaps (const LightmapSettings& lightmapper, UInt32 lightmapIndex, const Vector4f& lightmapST, bool setMatrix);
+
+
+// does a dummy render with all shaders & their combinations, so that driver actually creates them
+void WarmupAllShaders ();
+
+
+#endif
diff --git a/Runtime/Camera/SceneCulling.cpp b/Runtime/Camera/SceneCulling.cpp
new file mode 100644
index 0000000..ad5e4ed
--- /dev/null
+++ b/Runtime/Camera/SceneCulling.cpp
@@ -0,0 +1,412 @@
+#include "UnityPrefix.h"
+#include "SceneCulling.h"
+#include "CullingParameters.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "UmbraBackwardsCompatibility.h"
+#include "SceneNode.h"
+#include "Runtime/Geometry/AABB.h"
+
+
+using Umbra::OcclusionBuffer;
+
+static bool IsLayerDistanceCulled (UInt32 layer, const AABB& aabb, const SceneCullingParameters& params);
+static void ExtractUmbraCameraTransformFromCullingParameters (const CullingParameters& params, Umbra::CameraTransform& output);
+static void CullSceneWithLegacyUmbraDeprecated (SceneCullingParameters& cullingParams, CullingOutput& output, Umbra::Query* q, const Umbra_3_0::Tome* t);
+
+// Reduce the index list using Scene::IsNodeVisible.
+// Takes the index list as input and output, after the function the indices that are not visible will have been removed.
+static void ProcessIndexListIsNodeVisible (IndexList& list, const SceneNode* nodes, const AABB* bounds, const SceneCullingParameters& params)
+{
+ int size = list.size;
+ // Check layers and cull distances
+ int visibleCountNodes = 0;
+ for (int i = 0; i < size; ++i)
+ {
+ int index = list.indices[i];
+ const SceneNode& node = nodes[index];
+ const AABB& aabb = bounds[index];
+ if (IsNodeVisible(node, aabb, params))
+ list.indices[visibleCountNodes++] = index;
+ }
+
+ list.size = visibleCountNodes;
+}
+
+
+static void CullDynamicObjectsWithoutUmbra (IndexList& visibleObjects, const SceneCullingParameters& cullingParams, const AABB* aabbs, size_t count)
+{
+ int visibleIndex = 0;
+ for (int i=0;i<count;i++)
+ {
+ const AABB& bounds = aabbs[i];
+ if (IntersectAABBPlaneBounds(bounds, cullingParams.cullingPlanes, cullingParams.cullingPlaneCount))
+ visibleObjects[visibleIndex++] = i;
+ }
+
+ visibleObjects.size = visibleIndex;
+}
+
+static void CullDynamicObjectsUmbra (IndexList& visibleObjects, const AABB* aabbs, size_t count, OcclusionBuffer* occlusionBuffer)
+{
+ // TODO dynamic objects with legacy Tomes properly
+ int visibleIndex = 0;
+ for (int i=0;i<count;i++)
+ {
+ const AABB& bounds = aabbs[i];
+
+ Vector3f mn = bounds.GetMin();
+ Vector3f mx = bounds.GetMax();
+ Umbra::OcclusionBuffer::VisibilityTestResult result = occlusionBuffer->testAABBVisibility((Umbra::Vector3&)mn, (Umbra::Vector3&)mx);
+
+ if (result != OcclusionBuffer::OCCLUDED)
+ visibleObjects[visibleIndex++] = i;
+ }
+
+ visibleObjects.size = visibleIndex;
+}
+
+void CullSceneWithoutUmbra (const SceneCullingParameters& cullingParams, CullingOutput& output)
+{
+ for (int i=0;i<kVisibleListCount;i++)
+ {
+ CullDynamicObjectsWithoutUmbra (output.visible[i], cullingParams, cullingParams.renderers[i].bounds, cullingParams.renderers[i].rendererCount);
+ ProcessIndexListIsNodeVisible (output.visible[i], cullingParams.renderers[i].nodes, cullingParams.renderers[i].bounds, cullingParams);
+ }
+}
+
+void CullSceneWithUmbra (SceneCullingParameters& cullingParams, CullingOutput& output)
+{
+#if SUPPORT_BACKWARDS_COMPATIBLE_UMBRA
+ if (cullingParams.umbraTome.IsLegacyTome())
+ {
+ CullSceneWithLegacyUmbraDeprecated(cullingParams, output, cullingParams.umbraQuery, cullingParams.umbraTome.legacyTome);
+ return;
+ }
+#endif
+
+ DebugAssert(cullingParams.useOcclusionCulling);
+
+ const Umbra::Tome* tome = cullingParams.umbraTome.tome;
+ Umbra::QueryExt* query = cullingParams.umbraQuery;
+
+ Assert(tome != NULL);
+
+ Umbra::CameraTransform camera;
+ ExtractUmbraCameraTransformFromCullingParameters(cullingParams, camera);
+
+ Umbra::Query::ErrorCode e;
+
+ Umbra::UINT32 umbraFlags = cullingParams.umbraDebugFlags;
+ query->setDebugRenderer(cullingParams.umbraDebugRenderer);
+
+ // Legacy Umbra queries for PVS Tomes
+ Assert(tome->getStatistic(Umbra::Tome::STAT_PORTAL_DATA_SIZE) != 0);
+ e = query->queryPortalVisibility(umbraFlags, *output.umbraVisibility, camera);
+
+ //If camera is out of generated view volumes (case 554981), fall back to view frustum culling
+ if (e == Umbra::Query::ERROR_OUTSIDE_SCENE)
+ {
+ cullingParams.useOcclusionCulling = false;
+ cullingParams.useShadowCasterCulling = false;
+ CullSceneWithoutUmbra( cullingParams, output );
+ return;
+ }
+
+ // Process static objects with Unity specific culling (cullingMask / LOD etc)
+ IndexList& staticObjects = output.visible[kStaticRenderers];
+ staticObjects.size = output.umbraVisibility->getOutputObjects()->getSize();
+
+ ProcessIndexListIsNodeVisible(output.visible[kStaticRenderers], cullingParams.renderers[kStaticRenderers].nodes, cullingParams.renderers[kStaticRenderers].bounds, cullingParams);
+ output.umbraVisibility->getOutputObjects()->setSize(staticObjects.size);
+
+ // Process dynamic objects
+ for (int i=kDynamicRenderer;i<kVisibleListCount;i++)
+ {
+ CullDynamicObjectsUmbra (output.visible[i], cullingParams.renderers[i].bounds, cullingParams.renderers[i].rendererCount, output.umbraVisibility->getOutputBuffer());
+ ProcessIndexListIsNodeVisible(output.visible[i], cullingParams.renderers[i].nodes, cullingParams.renderers[i].bounds, cullingParams);
+ }
+}
+
+static void SplitCombinedDynamicList (Umbra::IndexList& indices, CullingOutput& sceneCullingOutput)
+{
+ int startIndices[kVisibleListCount];
+ startIndices[kStaticRenderers] = -1;
+ int offset = 0;
+ for (int t=kDynamicRenderer;t<kVisibleListCount;t++)
+ {
+ startIndices[t] = offset;
+ offset += sceneCullingOutput.visible[t].reservedSize;
+ }
+
+ int *indexPtr = indices.getPtr();
+ for (int i=0;i<indices.getSize();i++)
+ {
+ int index = indexPtr[i];
+
+ for (int t=kVisibleListCount-1; t>= 0 ;t--)
+ {
+ DebugAssert(t != kStaticRenderers);
+ if (index >= startIndices[t])
+ {
+ IndexList& indices = sceneCullingOutput.visible[t];
+ indices[indices.size++] = index - startIndices[t];
+ break;
+ }
+ }
+ }
+}
+
+// Unity has multiple dynamic bounding volume arrays.
+// Umbra's shadow caster culling needs a single list of all dynamic renderers
+static void GenerateCombinedDynamicList (const CullingOutput& sceneCullingOutput, const RendererCullData* renderers, dynamic_array<int>& indexList, dynamic_array<Vector3f>& minMaxBounds)
+{
+ size_t visibleSize = 0;
+ size_t totalSize = 0;
+ for (int t=kDynamicRenderer;t<kVisibleListCount;t++)
+ {
+ visibleSize += sceneCullingOutput.visible[t].size;
+ totalSize += sceneCullingOutput.visible[t].reservedSize;
+ }
+
+ indexList.resize_uninitialized(visibleSize);
+ minMaxBounds.resize_uninitialized(totalSize * 2);
+
+ // Make one big index list indexing into the combined bounding volume array
+ int index = 0;
+ int baseOffset = 0;
+ for (int t=kDynamicRenderer;t<kVisibleListCount;t++)
+ {
+ int* visibleDynamic = sceneCullingOutput.visible[t].indices;
+ for (int i=0;i<sceneCullingOutput.visible[t].size;i++)
+ indexList[index++] = visibleDynamic[i] + baseOffset;
+
+ baseOffset += sceneCullingOutput.visible[t].reservedSize;
+ }
+
+ // Create one big bounding volume array
+ index = 0;
+ for (int t=kDynamicRenderer;t<kVisibleListCount;t++)
+ {
+ const AABB* aabbs = renderers[t].bounds;
+
+ Assert(renderers[t].rendererCount == sceneCullingOutput.visible[t].reservedSize);
+ for (int i=0;i<renderers[t].rendererCount;i++)
+ {
+ minMaxBounds[index++] = aabbs[i].GetMin();
+ minMaxBounds[index++] = aabbs[i].GetMax();
+ }
+ }
+}
+
+void CullShadowCastersWithUmbra (const SceneCullingParameters& cullingParams, CullingOutput& output)
+{
+ DebugAssert(cullingParams.useShadowCasterCulling && cullingParams.useOcclusionCulling);
+
+ Assert( cullingParams.umbraTome.tome != NULL);
+ Umbra::QueryExt* query = (Umbra::QueryExt*)cullingParams.umbraQuery;
+
+ Umbra::CameraTransform camera;
+ ExtractUmbraCameraTransformFromCullingParameters(cullingParams, camera);
+
+ const Umbra::Visibility* inputSceneVisibility = cullingParams.sceneVisbilityForShadowCulling->umbraVisibility;
+
+ dynamic_array<int> visibleSceneIndexListCombined (kMemTempAlloc);
+ dynamic_array<Vector3f> dynamicBounds (kMemTempAlloc);
+
+ ////@TODO: This extra copying could maybe be removed or moved to not be per light?
+ GenerateCombinedDynamicList (*cullingParams.sceneVisbilityForShadowCulling, cullingParams.renderers, visibleSceneIndexListCombined, dynamicBounds);
+
+ size_t totalDynamicCount = dynamicBounds.size() / 2;
+ size_t totalStaticCount = cullingParams.renderers[kStaticRenderers].rendererCount;
+
+ int* dynamicCasterIndices;
+ ALLOC_TEMP(dynamicCasterIndices, int, totalDynamicCount);
+
+ // The output visible static & dynamic caster index lists
+ Umbra::IndexList staticCasterList (output.visible[kStaticRenderers].indices, totalStaticCount);
+ Umbra::IndexList dynamicCasterList(dynamicCasterIndices, totalDynamicCount);
+
+ // The current scene visibility (input for umbra occlusion culling)
+ const Umbra::IndexList sceneVisibleDynamicIndexList (visibleSceneIndexListCombined.begin(), visibleSceneIndexListCombined.size(), visibleSceneIndexListCombined.size());
+
+
+ query->setDebugRenderer(cullingParams.umbraDebugRenderer);
+
+ // TODO: this can be done right after the visibility query and only needs to be done once
+ // per camera position
+ // TODO: store shadowCuller so that it can be shared across threads
+ Umbra::ShadowCullerExt* shadowCuller;
+ ALLOC_TEMP(shadowCuller, Umbra::ShadowCullerExt, 1);
+ new (shadowCuller) Umbra::ShadowCullerExt(
+ cullingParams.umbraTome.tome,
+ inputSceneVisibility,
+ (Umbra::Vector3&)cullingParams.lightDir,
+ (const Umbra::Vector3*)dynamicBounds.data(),
+ totalDynamicCount,
+ &sceneVisibleDynamicIndexList);
+
+ // The last two parameters: jobIdx, numTotalJobs. These can be called from mulitple threads
+ query->queryStaticShadowCasters (*shadowCuller, staticCasterList, 0, 1);
+ query->queryDynamicShadowCasters(*shadowCuller, dynamicCasterList, (const Umbra::Vector3*)dynamicBounds.data(), totalDynamicCount);
+
+ output.visible[kStaticRenderers].size = staticCasterList.getSize();
+ SplitCombinedDynamicList (dynamicCasterList, output);
+
+ for (int i=0;i<kVisibleListCount;i++)
+ ProcessIndexListIsNodeVisible (output.visible[i], cullingParams.renderers[i].nodes, cullingParams.renderers[i].bounds, cullingParams);
+}
+
+
+bool IsNodeVisible (const SceneNode& node, const AABB& aabb, const SceneCullingParameters& params)
+{
+ UInt32 layermask = 1 << node.layer;
+ if ((layermask & params.cullingMask) == 0)
+ return false;
+
+ // Static renderers are not deleted from the renderer objects list.
+ // This is because we ensure that static objects come first and the pvxIndex in umbra matches the index in m_RendererNodes array.
+ // However the layerMask is set to 0 thus it should be culled at this point
+ if (node.renderer == NULL)
+ return false;
+
+ // Check if node was deleted but not removed from list yet.
+ if (node.disable)
+ return false;
+
+ if (node.lodIndexMask != 0)
+ {
+ DebugAssert(node.lodGroup < params.lodGroupCount);
+ if ((params.lodMasks[node.lodGroup] & node.lodIndexMask) == 0)
+ return false;
+ }
+
+ if (IsLayerDistanceCulled(node.layer, aabb, params))
+ return false;
+
+ return true;
+}
+
+
+static bool IsLayerDistanceCulled (UInt32 layer, const AABB& aabb, const SceneCullingParameters& params)
+{
+ switch (params.layerCull)
+ {
+ case CullingParameters::kLayerCullPlanar:
+ {
+ Assert(params.cullingPlaneCount == kPlaneFrustumNum);
+ Plane farPlane = params.cullingPlanes[kPlaneFrustumFar];
+ farPlane.distance = params.layerFarCullDistances[layer];
+ return !IntersectAABBPlaneBounds (aabb, &farPlane, 1);
+ }
+ case CullingParameters::kLayerCullSpherical:
+ {
+ float cullDist = params.layerFarCullDistances[layer];
+ if (cullDist == 0.0f)
+ return false;
+ return (SqrMagnitude(aabb.GetCenter() - params.position) > Sqr(cullDist));
+ }
+ default:
+ return false;
+ }
+}
+
+void ExtractUmbraCameraTransformFromCullingParameters (const CullingParameters& params, Umbra::CameraTransform& output)
+{
+ if (params.cullingPlaneCount > 6)
+ output.setUserClipPlanes((Umbra::Vector4*)&params.cullingPlanes[6], params.cullingPlaneCount-6);
+ output.set((Umbra::Matrix4x4&)params.worldToClipMatrix, (Umbra::Vector3&)params.position);
+}
+
+#if SUPPORT_BACKWARDS_COMPATIBLE_UMBRA
+
+static void CullDynamicObjectsUmbraDeprecated (IndexList& visibleObjects, const AABB* aabbs, size_t count, Umbra_3_0::Region* region)
+{
+ // TODO dynamic objects with legacy Tomes properly
+ int visibleIndex = 0;
+ for (int i=0;i<count;i++)
+ {
+ const AABB& bounds = aabbs[i];
+
+ Vector3f mn = bounds.GetMin();
+ Vector3f mx = bounds.GetMax();
+
+ bool isVisible = region->isAABBVisible((Umbra_3_0::Vector3&)mn, (Umbra_3_0::Vector3&)mx);
+
+ if (isVisible)
+ visibleObjects[visibleIndex++] = i;
+ }
+
+ visibleObjects.size = visibleIndex;
+}
+
+static void CullDynamicObjectsWithoutUmbraInOut (IndexList& visibleObjects, const SceneCullingParameters& cullingParams, const AABB* aabbs)
+{
+ int visibleIndex = 0;
+ for (int i=0;i<visibleObjects.size;i++)
+ {
+ int index = visibleObjects[i];
+ const AABB& bounds = aabbs[index];
+ if (IntersectAABBPlaneBounds(bounds, cullingParams.cullingPlanes, cullingParams.cullingPlaneCount))
+ visibleObjects[visibleIndex++] = index;
+ }
+
+ visibleObjects.size = visibleIndex;
+}
+
+static void CullSceneWithLegacyUmbraDeprecated (SceneCullingParameters& cullingParams, CullingOutput& output, Umbra::Query* q, const Umbra_3_0::Tome* tome)
+{
+ ///@TODO: Review if output.umbraVisibility makes any sense????
+
+ Umbra_3_0::QueryExt* query = (Umbra_3_0::QueryExt*)q;
+
+ query->init(tome);
+
+ Umbra_3_0::Region* region;
+ ALLOC_TEMP(region, Umbra_3_0::Region, 1);
+ region->init();
+ query->setVisibilityRegionOutput(region);
+
+
+ Umbra_3_0::CameraTransform camera;
+ if (cullingParams.cullingPlaneCount > 6)
+ camera.setUserClipPlanes((Umbra_3_0::Vector4*)&cullingParams.cullingPlanes[6], cullingParams.cullingPlaneCount-6);
+
+ camera.set((Umbra_3_0::Matrix4x4&)cullingParams.worldToClipMatrix, (Umbra_3_0::Vector3&)cullingParams.position);
+
+ Umbra_3_0::IndexList staticObjs(output.visible[kStaticRenderers].indices, output.visible[kStaticRenderers].reservedSize);
+
+ Umbra_3_0::Query::ErrorCode e;
+
+ int flags = 0;
+ if (tome->getStatistic(Umbra_3_0::Tome::STAT_PORTAL_DATA_SIZE))
+ flags |= Umbra_3_0::Query::QUERYFLAG_PORTAL;
+ else // TODO: handle other cases?
+ flags |= Umbra_3_0::Query::QUERYFLAG_PVS;
+
+ e = query->queryCameraVisibility(flags, &staticObjs, NULL, camera);
+ //Fallback to view frustum culling if queryCameraVisibility failed
+ if (e != Umbra_3_0::Query::ERROR_OK)
+ {
+ cullingParams.useOcclusionCulling = false;
+ cullingParams.useShadowCasterCulling = false;
+ CullSceneWithoutUmbra(cullingParams, output);
+ return;
+ }
+ output.visible[kStaticRenderers].size = staticObjs.getSize();
+
+ // camera can move outside view volume Assert(e == Umbra::Query::ERROR_OK);
+ // Extract visible indices
+ if (!query->supportVFCulling())
+ CullDynamicObjectsWithoutUmbraInOut (output.visible[kStaticRenderers], cullingParams, cullingParams.renderers[kStaticRenderers].bounds);
+
+ //output.umbraVisibility->getOutputObjects()->setSize(output.visible[kStaticRenderers].size);
+
+ ProcessIndexListIsNodeVisible(output.visible[kStaticRenderers], cullingParams.renderers[kStaticRenderers].nodes, cullingParams.renderers[kStaticRenderers].bounds, cullingParams);
+
+ for (int i=kDynamicRenderer;i<kVisibleListCount;i++)
+ {
+ CullDynamicObjectsUmbraDeprecated (output.visible[i], cullingParams.renderers[i].bounds, cullingParams.renderers[i].rendererCount, region);
+ ProcessIndexListIsNodeVisible(output.visible[i], cullingParams.renderers[i].nodes, cullingParams.renderers[i].bounds, cullingParams);
+ }
+}
+#endif // SUPPORT_BACKWARDS_COMPATIBLE_UMBRA \ No newline at end of file
diff --git a/Runtime/Camera/SceneCulling.h b/Runtime/Camera/SceneCulling.h
new file mode 100644
index 0000000..baae99e
--- /dev/null
+++ b/Runtime/Camera/SceneCulling.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "Runtime/Utilities/dynamic_array.h"
+
+struct CullingOutput;
+struct SceneCullingParameters;
+class IntermediateRenderer;
+struct SceneNode;
+class AABB;
+class Sphere;
+class IntermediateRenderers;
+
+void CullSceneWithUmbra (SceneCullingParameters& cullingParams, CullingOutput& output);
+void CullSceneWithoutUmbra (const SceneCullingParameters& cullingParams, CullingOutput& output);
+
+bool IsNodeVisible (const SceneNode& node, const AABB& aabb, const SceneCullingParameters& params);
+
+void CullShadowCastersWithUmbra (const SceneCullingParameters& cullingParams, CullingOutput& output); \ No newline at end of file
diff --git a/Runtime/Camera/SceneNode.h b/Runtime/Camera/SceneNode.h
new file mode 100644
index 0000000..a7e9655
--- /dev/null
+++ b/Runtime/Camera/SceneNode.h
@@ -0,0 +1,23 @@
+#pragma once
+
+class BaseRenderer;
+
+struct SceneNode
+{
+ SceneNode() :
+ renderer(NULL), layer(0), pvsHandle(-1), lodGroup(0), lodIndexMask(0),
+ needsCullCallback(false), dirtyAABB(false), disable(false) {}
+
+ BaseRenderer* renderer;
+ UInt32 layer;
+ SInt32 pvsHandle;
+ UInt32 lodGroup;
+ UInt32 lodIndexMask;
+ bool needsCullCallback;
+ bool dirtyAABB;
+ ///@TODO: Maybe we can use Renderer* = NULL instead, we already set this to null for static objects...
+ bool disable;
+};
+
+typedef int SceneHandle;
+const int kInvalidSceneHandle = -1;
diff --git a/Runtime/Camera/SceneSettings.cpp b/Runtime/Camera/SceneSettings.cpp
new file mode 100644
index 0000000..9ee2d9d
--- /dev/null
+++ b/Runtime/Camera/SceneSettings.cpp
@@ -0,0 +1,140 @@
+#include "UnityPrefix.h"
+#include "SceneSettings.h"
+#include "UnityScene.h"
+#include "Runtime/Camera/OcclusionPortal.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "UmbraBackwardsCompatibility.h"
+
+SceneSettings::SceneSettings (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+}
+
+SceneSettings::~SceneSettings ()
+{
+ if (GetScene().GetUmbraTome () == m_UmbraTome)
+ InvalidatePVSOnScene();
+
+ Cleanup();
+}
+
+void SceneSettings::InitializeClass ()
+{
+ Scene::InitializeClass();
+}
+
+void SceneSettings::CleanupClass ()
+{
+ Scene::CleanupClass();
+}
+
+void SceneSettings::Cleanup ()
+{
+ CleanupUmbraTomeData(m_UmbraTome);
+}
+
+void SceneSettings::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad(awakeMode);
+
+ InvalidatePVSOnScene();
+}
+
+int SceneSettings::GetUmbraTotalDataSize() const
+{
+ return m_UmbraTome.tome ? m_UmbraTome.tome->getStatistic(Umbra::Tome::STAT_TOTAL_DATA_SIZE) : 0;
+}
+
+int SceneSettings::GetPortalDataSize() const
+{
+ return m_UmbraTome.tome ? m_UmbraTome.tome->getStatistic(Umbra::Tome::STAT_PORTAL_DATA_SIZE) : 0;
+}
+
+void SceneSettings::InvalidatePVSOnScene ()
+{
+ GetScene().CleanupPVSAndRequestRebuild();
+}
+
+template<class TransferFunction> inline
+void SceneSettings::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ dynamic_array<UInt8> tempPVSData;
+ if (transfer.IsWriting () && m_UmbraTome.tome != NULL)
+ {
+ UInt8* tomeData = (UInt8*)(m_UmbraTome.tome);
+ tempPVSData.assign(tomeData, tomeData + UMBRA_TOME_METHOD(m_UmbraTome, getSize()));
+
+ // Strip tomeData when building player data
+// if (transfer.IsWritingGameReleaseData())
+// {
+// Umbra::Tome* tempTomeData = (Umbra::Tome*)tempPVSData.begin();
+// Umbra::Tome::stripDebugInfo(tempTomeData);
+// tempPVSData.resize_uninitialized(tempTomeData->getSize());
+// }
+ }
+
+ ///@TODO: make a fast path for loading tome data when we know that the data version matches. (Just alllocate&read tome data directly...)
+ transfer.Transfer(tempPVSData, "m_PVSData");
+
+ if (transfer.DidReadLastProperty ())
+ {
+ Cleanup();
+
+ // In free editor don't load occlusion data since there is no way to clear or rebake it.
+ if (GetBuildSettings().hasPROVersion && !tempPVSData.empty())
+ m_UmbraTome = LoadUmbraTome(tempPVSData.begin(), tempPVSData.size());
+ }
+
+ TRANSFER(m_PVSObjectsArray);
+ TRANSFER(m_PVSPortalsArray);
+
+ TRANSFER_EDITOR_ONLY(m_OcclusionBakeSettings);
+}
+
+template<class TransferFunction> inline
+void OcclusionBakeSettings::Transfer (TransferFunction& transfer)
+{
+ TRANSFER(smallestOccluder);
+ TRANSFER(smallestHole);
+ TRANSFER(backfaceThreshold);
+}
+
+
+/*
+ CheckConsistency...
+
+void ComputationParameterGUIChange()
+{
+ m_SmallestOccluder = Mathf.Max(m_SmallestOccluder, 0.1F);
+ StaticOcclusionCullingVisualization.smallestOccluder = m_SmallestOccluder;
+
+ m_SmallestHole = Mathf.Max(m_SmallestHole, 0.001F);
+ m_SmallestHole = Mathf.Min(m_SmallestHole, m_SmallestOccluder);
+ StaticOcclusionCullingVisualization.smallestHole = m_SmallestHole;
+*/
+
+void SceneSettings::SetUmbraTome(const dynamic_array<PPtr<Renderer> >& pvsObjectsArray, const dynamic_array<PPtr<OcclusionPortal> >& portalArray, const UInt8* visibilitybuffer, int size)
+{
+ InvalidatePVSOnScene();
+ Cleanup();
+
+ m_PVSObjectsArray = pvsObjectsArray;
+ m_PVSPortalsArray = portalArray;
+ if (size != 0)
+ m_UmbraTome.tome = Umbra::TomeLoader::loadFromBuffer(visibilitybuffer, size);
+ else
+ m_UmbraTome = UmbraTomeData();
+
+ SetDirty();
+}
+
+
+GET_MANAGER(SceneSettings)
+
+IMPLEMENT_CLASS_HAS_INIT (SceneSettings)
+IMPLEMENT_OBJECT_SERIALIZE (SceneSettings)
diff --git a/Runtime/Camera/SceneSettings.h b/Runtime/Camera/SceneSettings.h
new file mode 100644
index 0000000..8193f76
--- /dev/null
+++ b/Runtime/Camera/SceneSettings.h
@@ -0,0 +1,94 @@
+#ifndef UNITYSCENE_SETTINGS_H
+#define UNITYSCENE_SETTINGS_H
+
+#include "Runtime/BaseClasses/GameManager.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "UmbraTomeData.h"
+
+class Renderer;
+class OcclusionPortal;
+
+const float defaultSmallestOccluderValue = 5.0F;;
+const float defaultSmallestHoleValue = 0.25F;
+const float defaultBackfaceThresholdValue = 100.0F;
+
+struct OcclusionBakeSettings
+{
+ float smallestOccluder;
+ float smallestHole;
+ float backfaceThreshold;
+
+ OcclusionBakeSettings()
+ {
+ smallestOccluder = defaultSmallestOccluderValue;
+ smallestHole = defaultSmallestHoleValue;
+ backfaceThreshold = defaultBackfaceThresholdValue;
+ }
+
+ void SetDefaultOcclusionBakeSettings()
+ {
+ smallestOccluder = defaultSmallestOccluderValue;
+ smallestHole = defaultSmallestHoleValue;
+ backfaceThreshold = defaultBackfaceThresholdValue;
+ }
+
+ friend bool operator == (const OcclusionBakeSettings& lhs, const OcclusionBakeSettings& rhs)
+ {
+ return memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
+ }
+
+ DECLARE_SERIALIZE(OcclusionBakeSettings);
+};
+
+class SceneSettings : public LevelGameManager
+{
+public:
+
+ REGISTER_DERIVED_CLASS (SceneSettings, LevelGameManager)
+ DECLARE_OBJECT_SERIALIZE (SceneSettings)
+
+ SceneSettings (MemLabelId label, ObjectCreationMode mode);
+ // ~SceneSettings (); declared-by-macro
+
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+
+ void SetUmbraTome (const dynamic_array<PPtr<Renderer> >& pvsObjectsArray, const dynamic_array<PPtr<OcclusionPortal> >& portalArray, const UInt8* visibilitybuffer, int size);
+
+#if UNITY_EDITOR
+ const OcclusionBakeSettings& GetOcclusionBakeSettings () const { return m_OcclusionBakeSettings; }
+ void SetDefaultOcclusionBakeSettings () { SetDirty(); m_OcclusionBakeSettings.SetDefaultOcclusionBakeSettings(); }
+ OcclusionBakeSettings& GetOcclusionBakeSettingsSetDirty () { SetDirty(); return m_OcclusionBakeSettings; }
+#endif
+
+
+ const dynamic_array<PPtr<Renderer> >& GetPVSObjectArray () { return m_PVSObjectsArray; }
+ const dynamic_array<PPtr<OcclusionPortal> >& GetPortalsArray () { return m_PVSPortalsArray; }
+
+ const UmbraTomeData& GetUmbraTome () { return m_UmbraTome; }
+
+ int GetUmbraTotalDataSize () const;
+ int GetPortalDataSize () const;
+
+ void InvalidatePVSOnScene ();
+
+private:
+
+ void Cleanup ();
+
+ UmbraTomeData m_UmbraTome;
+ dynamic_array<PPtr<Renderer> > m_PVSObjectsArray;
+ dynamic_array<PPtr<OcclusionPortal> > m_PVSPortalsArray;
+
+ #if UNITY_EDITOR
+ OcclusionBakeSettings m_OcclusionBakeSettings;
+ #endif
+};
+
+SceneSettings& GetSceneSettings ();
+
+
+#endif
diff --git a/Runtime/Camera/ShaderReplaceData.h b/Runtime/Camera/ShaderReplaceData.h
new file mode 100644
index 0000000..51863f0
--- /dev/null
+++ b/Runtime/Camera/ShaderReplaceData.h
@@ -0,0 +1,14 @@
+#pragma once
+
+class Shader;
+
+// Shader replacement
+struct ShaderReplaceData
+{
+ Shader* replacementShader;
+ int replacementTagID;
+ bool replacementTagSet;
+
+
+ ShaderReplaceData () { replacementShader = NULL; replacementTagID = 0; replacementTagSet = false; }
+}; \ No newline at end of file
diff --git a/Runtime/Camera/ShadowCulling.cpp b/Runtime/Camera/ShadowCulling.cpp
new file mode 100644
index 0000000..f245547
--- /dev/null
+++ b/Runtime/Camera/ShadowCulling.cpp
@@ -0,0 +1,885 @@
+#include "UnityPrefix.h"
+#include "ShadowCulling.h"
+#include "Runtime/Camera/Light.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Geometry/Sphere.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Camera/BaseRenderer.h"
+#include "Runtime/Camera/CullResults.h"
+#include "Runtime/Camera/SceneCulling.h"
+#include "Runtime/Camera/SceneNode.h"
+#include "Runtime/Camera/UnityScene.h"
+#include "Runtime/Geometry/BoundingUtils.h"
+
+#include "SceneCulling.h"
+
+PROFILER_INFORMATION(gShadowsCullCastersPoint, "Shadows.CullCastersPoint", kProfilerRender)
+PROFILER_INFORMATION(gShadowsCullCastersSpot, "Shadows.CullCastersSpot", kProfilerRender)
+PROFILER_INFORMATION(gShadowsCullCastersDir, "Shadows.CullCastersDir", kProfilerRender)
+
+static const float kShadowFadeRange = 0.2f;
+
+static bool AddShadowCasterCullPlane(const Plane frustumCullPlanes[6], const Matrix4x4f& clipToWorld, const Vector3f& centerWorld, float nearPlaneScale, float farPlaneScale, int sideA, int sideB, int sideNeighbor, LightType lightType, const Vector3f& lightVec, Plane& outPlane)
+{
+ static Vector3f sideOffsets[] = {
+ Vector3f(-1.0f, 0.0f, 0.0f), // kPlaneFrustumLeft
+ Vector3f( 1.0f, 0.0f, 0.0f), // kPlaneFrustumRight
+ Vector3f( 0.0f, -1.0f, 0.0f), // kPlaneFrustumBottom
+ Vector3f( 0.0f, 1.0f, 0.0f), // kPlaneFrustumTop
+ Vector3f( 0.0f, 0.0f, -1.0f), // kPlaneFrustumNear
+ Vector3f( 0.0f, 0.0f, 1.0f) // kPlaneFrustumFar
+ };
+
+ const Plane& planeA = frustumCullPlanes[sideA];
+ const Plane& planeB = frustumCullPlanes[sideB];
+ Vector3f edgeNormal = planeA.GetNormal() + planeB.GetNormal(); // No need to normalize
+ Vector3f edgeCenter = sideOffsets[sideA] + sideOffsets[sideB];
+ Vector3f edgeDir = sideOffsets[sideNeighbor];
+ Vector3f posWorld1, posWorld2;
+ Vector3f posLocal1 = edgeCenter - edgeDir;
+ Vector3f posLocal2 = edgeCenter + edgeDir;
+ clipToWorld.PerspectiveMultiplyPoint3( posLocal1, posWorld1 );
+ clipToWorld.PerspectiveMultiplyPoint3( posLocal2, posWorld2 );
+ float scale1 = (posLocal1.z < 0.0f) ? nearPlaneScale : farPlaneScale;
+ float scale2 = (posLocal2.z < 0.0f) ? nearPlaneScale : farPlaneScale;
+ posWorld1 = centerWorld + (posWorld1 - centerWorld) * scale1;
+ posWorld2 = centerWorld + (posWorld2 - centerWorld) * scale2;
+
+ if( lightType == kLightDirectional )
+ {
+ /////@TODO: RUnning around in debug mode this seems to hit quite often in the shantytown scene...
+ Vector3f edgeDirWorld = (posWorld2 != posWorld1) ? Normalize( posWorld2 - posWorld1 ) : Vector3f(0.0f, 0.0f, 0.0f);
+ Vector3f clipNormal = Cross( edgeDirWorld, lightVec );
+ float len = Magnitude( clipNormal );
+ // Discard plane if edge is almost parallel with light dir
+ if (len < 0.001f)
+ return false;
+ clipNormal /= len;
+
+ // Flip the normal if we got the wrong direction
+ if( Dot( clipNormal, edgeNormal ) < 0.0f )
+ {
+ clipNormal *= -1.0f;
+ }
+ outPlane.SetNormalAndPosition( clipNormal, posWorld1 );
+ }
+ else
+ {
+ // We know the light is not too close to either plane
+ outPlane.Set3Points( posWorld1, posWorld2, lightVec );
+
+ if( Dot( outPlane.GetNormal(), edgeNormal ) < 0.0f )
+ {
+ outPlane *= -1.0f;
+ }
+ }
+ return true;
+}
+
+void CalculateShadowCasterCull(const Plane frustumCullPlanes[6], const Matrix4x4f& clipToWorld,
+ const Vector3f& centerWorld, float nearPlaneScale, float farPlaneScale,
+ LightType lightType, const Vector3f& lightVec, ShadowCasterCull& result,
+ const bool alreadyTested[kPlaneFrustumNum])
+{
+ // Create clip volume from the distant planes and silhouette edges of camera's frustum (as seen by the light)
+ // Maximum planes generated should be 10, but we check at runtime to be sure
+ result.planeCount = 0;
+ bool facingLight[kPlaneFrustumNum];
+ bool enablePlane[kPlaneFrustumNum];
+ float epsilon = 0.01f;
+ for( int i = 0; i < kPlaneFrustumNum; ++i )
+ {
+ Plane plane = frustumCullPlanes[i];
+ if( lightType == kLightDirectional )
+ {
+ // Compare light direction and normal
+ float d = Dot( plane.GetNormal(), lightVec );
+ facingLight[i] = d < 0.0f;
+ enablePlane[i] = true;
+ }
+ else
+ {
+ float dist = plane.GetDistanceToPoint( lightVec );
+ facingLight[i] = false;
+ enablePlane[i] = true;
+ if( dist > -epsilon )
+ {
+ // Light is inside or just outside the frustum
+ facingLight[i] = true;
+ if ( dist < epsilon )
+ {
+ // Point is too close to the plane to care about edges
+ enablePlane[i] = false;
+ }
+ if ( dist < 0.0f )
+ {
+ // Push plane slightly so the frustum encloses the light
+ plane.distance -= dist;
+ }
+ }
+ }
+ if( facingLight[i] && !alreadyTested[i] )
+ {
+ result.planes[result.planeCount++] = plane;
+ if( result.planeCount == ShadowCasterCull::kMaxPlanes )
+ return;
+ }
+ }
+
+ // Sides in running order (each plane touches previous)
+ static int camera4Sides[] = {
+ kPlaneFrustumLeft,
+ kPlaneFrustumBottom,
+ kPlaneFrustumRight,
+ kPlaneFrustumTop
+ };
+ static int cameraNearFar[] = {
+ kPlaneFrustumNear,
+ kPlaneFrustumFar
+ };
+
+ // Iterate over diagonal edges
+ for( int edge = 0; edge < 4; ++edge )
+ {
+ int sideA = camera4Sides[edge];
+ // If we already tested side A, we also tested B
+ if( alreadyTested[sideA] )
+ continue;
+ int sideB = camera4Sides[(edge + 1) % 4];
+ int sideNeighbor = kPlaneFrustumFar;
+ if( facingLight[sideA] != facingLight[sideB] &&
+ enablePlane[sideA] && enablePlane[sideB] )
+ {
+ Plane& plane = result.planes[result.planeCount];
+ bool success = AddShadowCasterCullPlane(frustumCullPlanes, clipToWorld,
+ centerWorld, nearPlaneScale, farPlaneScale,
+ sideA, sideB, sideNeighbor, lightType, lightVec, plane);
+ result.planeCount += success ? 1 : 0;
+ if( result.planeCount == ShadowCasterCull::kMaxPlanes )
+ return;
+ }
+ }
+
+ // Iterate over near and far edges
+ for( int nearFar = 0; nearFar < 2; ++nearFar )
+ {
+ int sideA = cameraNearFar[nearFar];
+ // If we already tested side A, we also tested B
+ if( alreadyTested[sideA] )
+ continue;
+ for( int edge = 0; edge < 4; ++edge )
+ {
+ int sideB = camera4Sides[edge];
+ int sideNeighbor = camera4Sides[(edge + 1) % 4];
+ if( facingLight[sideA] != facingLight[sideB] &&
+ enablePlane[sideA] && enablePlane[sideB] )
+ {
+ Plane& plane = result.planes[result.planeCount];
+ bool success = AddShadowCasterCullPlane(frustumCullPlanes, clipToWorld,
+ centerWorld, nearPlaneScale, farPlaneScale,
+ sideA, sideB, sideNeighbor, lightType, lightVec, plane);
+ result.planeCount += success ? 1 : 0;
+ if( result.planeCount == ShadowCasterCull::kMaxPlanes )
+ return;
+ }
+ }
+ }
+}
+
+static const bool s_AlreadyTestedNone[kPlaneFrustumNum] = { false, false, false, false, false, false };
+
+void CalculateShadowCasterCull(const Plane frustumCullPlanes[6], const Matrix4x4f& clipToWorld, const Vector3f& centerWorld, float nearPlaneScale, float farPlaneScale, LightType lightType, const Transform& trans, ShadowCasterCull& result)
+{
+ // Get position for positional lights and direction for directional lights
+ Vector3f lightVec;
+ if( lightType == kLightDirectional )
+ {
+ lightVec = trans.TransformDirection( Vector3f::zAxis );
+ }
+ else
+ {
+ Assert( lightType == kLightPoint || lightType == kLightSpot );
+ lightVec = trans.GetPosition();
+ }
+ CalculateShadowCasterCull(frustumCullPlanes, clipToWorld, centerWorld, nearPlaneScale, farPlaneScale, lightType, lightVec, result, s_AlreadyTestedNone);
+}
+
+static bool CanUseProjectionForShadows (const Matrix4x4f& clipToWorld, float farNearRatio, const Camera& camera, const Vector3f& cameraPos)
+{
+ // Check if eye position is the focal point of the camera projection
+ // and we can scale near plane from the eye pos to get the far plane.
+ // This works for asymmetric projections, not for oblique etc.
+ Vector3f cameraFrustum[8];
+ GetFrustumPoints(clipToWorld, cameraFrustum);
+ for (int i = 0; i < 4; i++)
+ {
+ const Vector3f& nearPos = cameraFrustum[i];
+ const Vector3f& farPos = cameraFrustum[i + 4];
+ Vector3f derivedFar = cameraPos + (nearPos - cameraPos) * farNearRatio;
+ float diff = SqrMagnitude(derivedFar - farPos);
+ float length = SqrMagnitude(farPos - nearPos);
+ if (!(diff <= length * 0.01f))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+void SetupShadowCullData (Camera& camera, const Vector3f& cameraPos, const ShaderReplaceData& shaderReplaceData, const SceneCullingParameters* sceneCullParams, ShadowCullData& cullData)
+{
+ const Rectf screenRect = camera.GetScreenViewportRect();
+ float shadowDistance = CalculateShadowDistance (camera);
+
+ Vector3f viewDir = -NormalizeSafe(camera.GetCameraToWorldMatrix().GetAxisZ());
+
+ cullData.camera = &camera;
+ cullData.eyePos = cameraPos;
+ cullData.viewDir = viewDir;
+ cullData.shadowDistance = shadowDistance;
+ cullData.projectionNear = camera.GetProjectionNear();
+ cullData.projectionFar = camera.GetProjectionFar();
+ cullData.farPlaneScale = shadowDistance / cullData.projectionFar;
+ cullData.viewWidth = screenRect.width;
+ cullData.viewHeight = screenRect.height;
+ cullData.actualWorldToClip = camera.GetWorldToClipMatrix();
+ cullData.visbilityForShadowCulling = NULL;
+ Matrix4x4f::Invert_Full(cullData.actualWorldToClip, cullData.cameraClipToWorld);
+
+ float farNearRatio = cullData.projectionFar / cullData.projectionNear;
+ if (!CanUseProjectionForShadows(cullData.cameraClipToWorld, farNearRatio, camera, cameraPos))
+ {
+ // Use implicit projection instead (which we used always before 3.5)
+ Matrix4x4f proj;
+ camera.GetImplicitProjectionMatrix(camera.GetNear(), camera.GetFar(), proj);
+ MultiplyMatrices4x4 (&proj, &camera.GetWorldToCameraMatrix(), &cullData.cameraWorldToClip);
+ Matrix4x4f::Invert_Full(cullData.cameraWorldToClip, cullData.cameraClipToWorld);
+ }
+ else
+ cullData.cameraWorldToClip = cullData.actualWorldToClip;
+
+ camera.CalculateFrustumPlanes(cullData.shadowCullPlanes, cullData.cameraWorldToClip, shadowDistance, cullData.baseFarDistance, true);
+ for( int i = 0; i < kPlaneFrustumNum; i++ )
+ cullData.cameraCullPlanes[i] = cullData.shadowCullPlanes[i];
+ cullData.cameraCullPlanes[kPlaneFrustumFar].distance = cullData.baseFarDistance + camera.GetFar();
+ cullData.shadowCullCenter = Vector3f::zero;
+ float cullRadius = 1e15f;
+ cullData.useSphereCulling = CalculateSphericalShadowRange(camera, cullData.shadowCullCenter, cullRadius);
+ cullData.shadowCullRadius = cullRadius;
+ cullData.shadowCullSquareRadius = cullRadius * cullRadius;
+ const float* layerCullDistances = camera.GetLayerCullDistances();
+ std::copy(layerCullDistances, layerCullDistances + kNumLayers, cullData.layerCullDistances);
+ cullData.layerCullSpherical = camera.GetLayerCullSpherical();
+ cullData.shaderReplace = shaderReplaceData;
+ cullData.sceneCullParameters = sceneCullParams;
+}
+
+float CalculateShadowDistance (const Camera& camera)
+{
+ return std::min (QualitySettings::GetShadowDistanceForRendering(), camera.GetFar());
+}
+
+float CalculateShadowSphereOffset (const Camera& camera)
+{
+ float fov = camera.GetFov();
+ static float maxDegrees = 180.0f;
+ const float maxOffset = (1.0f - kShadowFadeRange) * 0.5f;
+ float weight = clamp(1.0f - fov / maxDegrees, 0.0f, 1.0f);
+ return maxOffset * weight;
+}
+
+bool CalculateSphericalShadowRange (const Camera& camera, Vector3f& outCenter, float& outRadius)
+{
+ const QualitySettings::QualitySetting& quality = GetQualitySettings().GetCurrent();
+ if (quality.shadowProjection == kShadowProjCloseFit)
+ return false;
+
+ outCenter = camera.GetPosition();
+ outRadius = CalculateShadowDistance(camera);
+
+ float sphereOffset = CalculateShadowSphereOffset(camera);
+ Vector3f vectorOffset(0, 0, -sphereOffset * outRadius);
+ outCenter += camera.GetCameraToWorldMatrix().MultiplyVector3(vectorOffset);
+ outRadius *= (1.0f - sphereOffset);
+ return true;
+
+ Assert(quality.shadowProjection == kShadowProjStableFit);
+ return true;
+}
+
+void CalculateLightShadowFade (const Camera& camera, float shadowStrength, Vector4f& outParams, Vector4f& outCenterAndType)
+{
+ float shadowDistance = CalculateShadowDistance(camera);
+ float shadowRange = shadowDistance;
+ Vector3f center = camera.GetPosition();
+ bool spherical = CalculateSphericalShadowRange(camera, center, shadowRange);
+
+ // R = 1-strength
+ // G = 1.0 / shadowDistance
+ // B = 1.0 / (shadowDistance - shadowStartFade)
+ // A = -shadowStartFade / (shadowDistance - shadowStartFade)
+ outParams.x = 1.0f - shadowStrength; // R = 1-strength
+
+ if (shadowRange > 0)
+ {
+ outParams.y = camera.GetFar() / shadowDistance;
+ // fade factor = (x-start)/len = x * invlen - start*invlen
+ const float shadowStartFade = shadowRange - shadowDistance * kShadowFadeRange;
+ const float shadowFadeInvLen = 1.0f / (shadowRange - shadowStartFade);
+ outParams.z = shadowFadeInvLen;
+ outParams.w = -shadowStartFade * shadowFadeInvLen;
+ }
+ else
+ {
+ outParams.y = std::numeric_limits<float>::infinity();
+ outParams.z = 0;
+ outParams.w = 1;
+ }
+
+ outCenterAndType = Vector4f(center.x, center.y, center.z, spherical);
+}
+
+struct ShadowCullContext
+{
+ const ShadowCullData* camera;
+ bool excludeLightmapped;
+ UInt32 cullLayers;
+};
+
+
+static void AddShadowCaster (const SceneNode& node, const AABB& aabb, const ShadowCullData& context, bool expandCasterBounds, MinMaxAABB& casterBounds, ShadowCasters& casters, ShadowCasterParts& casterParts)
+{
+ const BaseRenderer* renderer = node.renderer;
+
+ Assert( renderer && renderer->GetCastShadows() );
+
+ // Find out which sub-materials do cast shadows
+ // and prefetch shader pointers.
+ size_t partsStartIndex = casterParts.size();
+ int matCount = renderer->GetMaterialCount();
+
+ Shader* replaceShader = context.shaderReplace.replacementShader;
+ const bool replacementTagSet = context.shaderReplace.replacementTagSet;
+ const int replacementTagID = context.shaderReplace.replacementTagID;
+
+ for( int i = 0; i < matCount; ++i )
+ {
+ Material* mat = renderer->GetMaterial(i);
+ if( !mat )
+ continue;
+
+ Shader* originalShader = mat->GetShader();
+ Shader* actualShader = replaceShader ? replaceShader : originalShader;
+
+ // Find the subshader...
+ int usedSubshaderIndex = -1;
+ if (replaceShader)
+ {
+ if (replacementTagSet)
+ {
+ int subshaderTypeID = originalShader->GetShaderLabShader()->GetTag (replacementTagID, true);
+ if (subshaderTypeID < 0)
+ continue; // skip rendering
+ usedSubshaderIndex = replaceShader->GetSubShaderWithTagValue (replacementTagID, subshaderTypeID);
+ if (usedSubshaderIndex == -1)
+ continue; // skip rendering
+ }
+ else
+ usedSubshaderIndex = 0;
+ }
+ else
+ usedSubshaderIndex = originalShader->GetActiveSubShaderIndex();
+
+ Assert (usedSubshaderIndex != -1);
+
+ if (actualShader->GetShadowCasterPassToUse(usedSubshaderIndex))
+ {
+ ShadowCasterPartData part;
+ part.subMeshIndex = renderer->GetSubsetIndex(i);
+ part.shader = actualShader;
+ part.subShaderIndex = usedSubshaderIndex;
+ part.material = mat;
+ casterParts.push_back (part);
+ }
+ }
+
+ // This object does cast shadows, store info
+ size_t partsEndIndex = casterParts.size();
+ if( partsEndIndex != partsStartIndex )
+ {
+ ShadowCasterData data;
+ data.node = &node;
+ data.worldAABB = &aabb;
+ data.visibleCascades = 1;
+ data.partsStartIndex = partsStartIndex;
+ data.partsEndIndex = partsEndIndex;
+
+ casters.push_back( data );
+
+ if( expandCasterBounds )
+ casterBounds.Encapsulate( aabb );
+ }
+}
+
+static bool CullCastersCommon(const ShadowCullContext& context, const SceneNode& node, const AABB& aabb)
+{
+ const BaseRenderer* renderer = node.renderer;
+ if (!renderer->GetCastShadows())
+ return false;
+
+ if (context.excludeLightmapped && renderer->IsLightmappedForShadows())
+ return false;
+
+ const int nodeLayer = node.layer;
+ const int nodeLayerMask = 1 << nodeLayer;
+ if (!(nodeLayerMask & context.cullLayers))
+ return false;
+
+ // For casters that use per-layer culling distance: check if they are behind cull distance.
+ // If they are, don't cast shadows.
+ const ShadowCullData& cullData = *context.camera;
+ float layerCullDist = cullData.layerCullDistances[nodeLayer];
+ if (layerCullDist)
+ {
+ if (cullData.layerCullSpherical)
+ {
+
+ float sqDist = SqrMagnitude(aabb.GetCenter() - cullData.eyePos);
+ if (sqDist > Sqr(layerCullDist))
+ return false;
+ }
+ else
+ {
+ Plane farPlane = cullData.shadowCullPlanes[kPlaneFrustumFar];
+ farPlane.distance = layerCullDist + cullData.baseFarDistance;
+ if( !IntersectAABBPlaneBounds( aabb, &farPlane, 1 ) )
+ return false;
+ }
+ }
+
+ return true;
+}
+
+//
+// Point light shadow caster culling
+
+struct PointCullContext : public ShadowCullContext
+{
+ Sphere lightSphere;
+};
+
+static bool CullCastersPoint( PointCullContext* context, const SceneNode& node, const AABB& aabb )
+{
+ if (!CullCastersCommon(*context, node, aabb))
+ return false;
+
+ if (!IntersectAABBSphere( aabb, context->lightSphere ))
+ return false;
+
+ return true;
+}
+
+//
+// Spot light shadow caster culling
+
+struct SpotCullContext : public ShadowCullContext
+{
+ Matrix4x4f worldToLightMatrix, projectionMatrix;
+ Plane spotLightFrustum[6];
+};
+
+static bool CullCastersSpot( SpotCullContext* context, const SceneNode& node, const AABB& aabb )
+{
+ if (!CullCastersCommon(*context, node, aabb))
+ return false;
+
+ // World space light frustum vs. global AABB
+ if (!IntersectAABBFrustumFull (aabb, context->spotLightFrustum))
+ return false;
+
+ const TransformInfo& xformInfo = node.renderer->GetTransformInfo ();
+
+ Plane planes[6];
+ // NOTE: it's important to multiply world->light and node->world matrices
+ // before multiplying in the projection matrix. Otherwise when object/light
+ // coordinates will approach 10 thousands range, we'll get culling errors
+ // because of imprecision.
+ //Matrix4x4f proj = context->projectionMatrix * (context->worldToLightMatrix * node->worldMatrix);
+ Matrix4x4f temp, proj;
+ MultiplyMatrices4x4 (&context->worldToLightMatrix, &xformInfo.worldMatrix, &temp);
+ MultiplyMatrices4x4 (&context->projectionMatrix, &temp, &proj);
+ ExtractProjectionPlanes( proj, planes );
+
+ if (!IntersectAABBFrustumFull( xformInfo.localAABB, planes ) )
+ return false;
+
+ return true;
+}
+
+
+//
+// Directional light shadow caster culling
+
+struct DirectionalCullContext : public ShadowCullContext
+{
+};
+
+static bool CullCastersDirectional( DirectionalCullContext* context, const SceneNode& node, const AABB& aabb )
+{
+ return CullCastersCommon(*context, node, aabb);
+}
+
+
+static void CullDirectionalShadows (IndexList& visible, const SceneNode* nodes, const AABB* boundingBoxes, DirectionalCullContext &context )
+{
+ // Generate Visible nodes from all static & dynamic objects
+ int visibleCount = 0;
+ for (int i=0;i<visible.size;i++)
+ {
+ const SceneNode& node = nodes[visible.indices[i]];
+ const AABB& bounds = boundingBoxes[visible.indices[i]];
+
+ if (CullCastersDirectional( &context, node, bounds))
+ visible.indices[visibleCount++] = visible.indices[i];
+ }
+ visible.size = visibleCount;
+}
+
+static void CullSpotShadows (IndexList& visible, const SceneNode* nodes, const AABB* boundingBoxes, SpotCullContext &context )
+{
+ // Generate Visible nodes from all static & dynamic objects
+ int visibleCount = 0;
+ for (int i=0;i<visible.size;i++)
+ {
+ const SceneNode& node = nodes[visible.indices[i]];
+ const AABB& bounds = boundingBoxes[visible.indices[i]];
+
+ if (CullCastersSpot( &context, node, bounds))
+ visible.indices[visibleCount++] = visible.indices[i];
+ }
+ visible.size = visibleCount;
+}
+
+static void CullPointShadows (IndexList& visible, const SceneNode* nodes, const AABB* boundingBoxes, PointCullContext &context )
+{
+ // Generate Visible nodes from all static & dynamic objects
+ int visibleCount = 0;
+ for (int i=0;i<visible.size;i++)
+ {
+ const SceneNode& node = nodes[visible.indices[i]];
+ const AABB& bounds = boundingBoxes[visible.indices[i]];
+
+ if (CullCastersPoint( &context, node, bounds ))
+ visible.indices[visibleCount++] = visible.indices[i];
+ }
+ visible.size = visibleCount;
+}
+
+static void CullShadowCastersDetail( const Light& light, const ShadowCullData& cullData, bool excludeLightmapped, CullingOutput& visibleRenderers )
+{
+ const RendererCullData* rendererCullData = cullData.sceneCullParameters->renderers;
+
+ int cullLayers = light.GetCullingMask() & cullData.camera->GetCullingMask();
+ LightType lightType = light.GetType();
+ switch (lightType)
+ {
+ case kLightPoint:
+ {
+ PointCullContext context;
+ const Transform& lt = light.GetComponent (Transform);
+ Vector3f lightPos = lt.GetPosition();
+ context.lightSphere = Sphere( lightPos, light.GetRange() );
+ context.camera = &cullData;
+ context.cullLayers = cullLayers;
+ context.excludeLightmapped = excludeLightmapped;
+
+ for (int i=0;i<kVisibleListCount;i++)
+ CullPointShadows (visibleRenderers.visible[i], rendererCullData[i].nodes, rendererCullData[i].bounds, context);
+ }
+ break;
+
+ case kLightSpot:
+ {
+ SpotCullContext context;
+ Matrix4x4f zscale, proj;
+ zscale.SetScale (Vector3f (1.0F, 1.0F, -1.0F));
+ proj.SetPerspectiveCotan( light.GetCotanHalfSpotAngle(), 0.0001F, light.GetRange() );
+ MultiplyMatrices4x4 (&proj, &zscale, &context.projectionMatrix);
+ const Transform& lt = light.GetComponent (Transform);
+ context.worldToLightMatrix = lt.GetWorldToLocalMatrixNoScale();
+
+ Matrix4x4f temp;
+ MultiplyMatrices4x4 (&context.projectionMatrix, &context.worldToLightMatrix, &temp);
+ ExtractProjectionPlanes (temp, context.spotLightFrustum);
+
+ context.camera = &cullData;
+ context.cullLayers = cullLayers;
+ context.excludeLightmapped = excludeLightmapped;
+
+ for (int i=0;i<kVisibleListCount;i++)
+ CullSpotShadows (visibleRenderers.visible[i], rendererCullData[i].nodes, rendererCullData[i].bounds, context);
+ }
+ break;
+ case kLightDirectional:
+ {
+ DirectionalCullContext context;
+ context.camera = &cullData;
+ context.cullLayers = cullLayers;
+ context.excludeLightmapped = excludeLightmapped;
+
+ for (int i=0;i<kVisibleListCount;i++)
+ CullDirectionalShadows (visibleRenderers.visible[i], rendererCullData[i].nodes, rendererCullData[i].bounds, context);
+ }
+ break;
+ }
+}
+
+void CullShadowCasters (const Light& light, const ShadowCullData& cullData, bool excludeLightmapped, CullingOutput& cullingOutput )
+{
+ LightType lightType = light.GetType();
+
+ if (lightType == kLightPoint)
+ {
+ PROFILER_BEGIN(gShadowsCullCastersPoint,&light)
+ }
+ else if (lightType == kLightSpot)
+ {
+ PROFILER_BEGIN(gShadowsCullCastersSpot,&light)
+ }
+ else
+ {
+ PROFILER_BEGIN(gShadowsCullCastersDir,&light)
+ }
+
+ const Transform& lt = light.GetComponent (Transform);
+ ShadowCasterCull casterCull;
+
+ CalculateShadowCasterCull( cullData.shadowCullPlanes, cullData.cameraClipToWorld, cullData.eyePos,
+ 1.0, cullData.farPlaneScale, lightType, lt, casterCull );
+
+ SceneCullingParameters cullParams = *cullData.sceneCullParameters;
+ cullData.camera->CalculateCustomCullingParameters(cullParams, casterCull.planes, casterCull.planeCount);
+
+ if (lightType == kLightDirectional)
+ {
+ Vector3f lightDir = lt.TransformDirection (Vector3f(0,0,-1));
+ cullParams.lightDir = lightDir;
+ }
+
+ if (cullParams.useShadowCasterCulling && (lightType == kLightDirectional))
+ {
+ CullShadowCastersWithUmbra(cullParams, cullingOutput);
+ }
+ else
+ {
+ CullSceneWithoutUmbra (cullParams, cullingOutput);
+ }
+
+
+ CullShadowCastersDetail (light, cullData, excludeLightmapped, cullingOutput);
+
+ PROFILER_END
+}
+
+
+void GenerateShadowCasterParts (const Light& light, const ShadowCullData& cullData, const CullingOutput& visibleRenderers, MinMaxAABB& casterBounds, ShadowCasters& casters, ShadowCasterParts& casterParts)
+{
+ const bool isDirectionalLight = light.GetType() == kLightDirectional;
+
+ const RendererCullData* rendererCullData = cullData.sceneCullParameters->renderers;
+ for (int t=0;t<kVisibleListCount;t++)
+ {
+ const IndexList& visibleList = visibleRenderers.visible[t];
+ const RendererCullData& renderLists = rendererCullData[t];
+ for (int i=0;i<visibleList.size;i++)
+ {
+ int renderIndex = visibleList[i];
+ bool casterTouchesView = false;
+ if (isDirectionalLight)
+ {
+ // Only compute caster AABB for casters that touch the view frustum.
+ // Other casters might be valid casters, but behind near plane of resulting
+ // shadow camera. This is ok, we manually push those onto near plane in caster vertex shader.
+ //
+ // If we'd bound all casters we get the issue of insufficient depth precision when some caster
+ // has a valid caster, but is waaay far away (50000 units or so).
+ casterTouchesView = IntersectAABBFrustumFull( renderLists.bounds[renderIndex], cullData.cameraCullPlanes );
+ }
+
+ // Last argument expandCasterBounds should be false for point lights as they don't use casterBounds to adjust shadow map view
+ AddShadowCaster( renderLists.nodes[renderIndex], renderLists.bounds[renderIndex], cullData, casterTouchesView, casterBounds, casters, casterParts );
+ }
+ }
+}
+
+
+
+/*
+
+ static bool CullCastersDirectional( DirectionalCullContext* context, const SceneNode& node, const AABB& aabb, float lodFade )
+ {
+ if (!CullCastersCommon(*context, node, aabb))
+ return false;
+
+ // Only compute caster AABB for casters that touch the view frustum.
+ // Other casters might be valid casters, but behind near plane of resulting
+ // shadow camera. This is ok, we manually push those onto near plane in caster vertex shader.
+ //
+ // If we'd bound all casters we get the issue of insufficient depth precision when some caster
+ // has a valid caster, but is waaay far away (50000 units or so).
+ bool casterTouchesView = IntersectAABBFrustumFull( aabb, context->camera->cameraCullPlanes );
+ AddShadowCaster( node, aabb, *context, casterTouchesView );
+ return true;
+ }
+
+
+ */
+
+
+
+struct Circle2f
+{
+ Vector2f center;
+ float radius;
+};
+
+void CullDirectionalCascades(ShadowCasters& casters, const ShadowCascadeInfo cascades[kMaxShadowCascades], int cascadeCount,
+ const Quaternionf& lightRot, const Vector3f& lightDir, const ShadowCullData& cullData)
+{
+ Assert(cascadeCount <= kMaxShadowCascades);
+ const QualitySettings::QualitySetting& quality = GetQualitySettings().GetCurrent();
+ if (quality.shadowProjection == kShadowProjCloseFit && cascadeCount == 1)
+ {
+ // We already culled casters for one non-spherical cascade
+ return;
+ }
+ const Camera& camera = *cullData.camera;
+
+ // Split frustums extruded towards light
+ ShadowCasterCull cullPlanes[kMaxShadowCascades];
+
+ // Only objects inside a cylinder can cast shadows onto a sphere
+ // We cull in light space so the cylinders become circles
+ Circle2f cullCylinders[kMaxShadowCascades];
+
+ Matrix4x4f lightMat;
+ QuaternionToMatrix(lightRot, lightMat);
+
+ for (int casc = 0; casc < cascadeCount; casc++)
+ {
+ const ShadowCascadeInfo& cascade = cascades[casc];
+ if (!cascade.enabled)
+ continue;
+ if (quality.shadowProjection == kShadowProjStableFit)
+ {
+ // We use spherical splits so cull against a cylinder
+ const Vector3f& centerWorld = cascade.outerSphere.GetCenter();
+ cullCylinders[casc].center.x = Dot(centerWorld, lightMat.GetAxisX());
+ cullCylinders[casc].center.y = Dot(centerWorld, lightMat.GetAxisY());
+ cullCylinders[casc].radius = cascade.outerSphere.GetRadius();
+ }
+ if (cascadeCount == 1)
+ {
+ // We have already culled against non-cascaded frustum
+ cullPlanes[casc].planeCount = 0;
+ continue;
+ }
+
+ bool alreadyTested[kPlaneFrustumNum];
+ for (int p = 0; p < kPlaneFrustumNear; p++)
+ alreadyTested[p] = true;
+ alreadyTested[kPlaneFrustumNear] = (casc == 0);
+ alreadyTested[kPlaneFrustumFar] = ((casc + 1) == cascadeCount);
+
+ Plane splitPlanes[kPlaneFrustumNum];
+ memcpy(splitPlanes, cullData.shadowCullPlanes, sizeof(splitPlanes));
+ splitPlanes[kPlaneFrustumNear].distance += cascade.minViewDistance - camera.GetNear();
+ splitPlanes[kPlaneFrustumFar].distance += cascade.maxViewDistance - cullData.shadowDistance;
+ float nearPlaneScale = cascade.minViewDistance / cullData.projectionNear;
+ float farPlaneScale = cascade.maxViewDistance / cullData.projectionFar;
+ CalculateShadowCasterCull(splitPlanes, cullData.cameraClipToWorld, cullData.eyePos,
+ nearPlaneScale, farPlaneScale, kLightDirectional, lightDir, cullPlanes[casc], alreadyTested);
+ }
+
+ UInt32 allVisible = 0;
+ for (int casc = 0; casc < cascadeCount; casc++)
+ allVisible = (allVisible << 1) | 1;
+
+ // Go over casters and determine in which cascades they are visible
+ int casterCount = casters.size();
+ for (int i = 0; i < casterCount; i++)
+ {
+ ShadowCasterData& caster = casters[i];
+ const AABB& bounds = *caster.worldAABB;
+ caster.visibleCascades = allVisible;
+ UInt32 currentCascadeMask = 1;
+
+ if (quality.shadowProjection == kShadowProjStableFit)
+ {
+ float casterRadius = Magnitude(bounds.GetExtent());
+ Vector2f lightSpaceCenter;
+ lightSpaceCenter.x = Dot(bounds.GetCenter(), lightMat.GetAxisX());
+ lightSpaceCenter.y = Dot(bounds.GetCenter(), lightMat.GetAxisY());
+ currentCascadeMask = 1;
+ for (int casc = 0; casc < cascadeCount; casc++, currentCascadeMask <<= 1)
+ {
+ if (!cascades[casc].enabled)
+ continue;
+ // Do the caster bounds and cascade intersect in light space?
+ Vector2f vec = lightSpaceCenter - cullCylinders[casc].center;
+ float sqrDist = Sqr(vec.x) + Sqr(vec.y);
+ float cullRadius = cullCylinders[casc].radius;
+ if (sqrDist > Sqr(casterRadius + cullRadius))
+ {
+ // Circles do not intersect, mark as invisible
+ caster.visibleCascades &= ~currentCascadeMask;
+ }
+ }
+ }
+
+ if (cascadeCount > 1)
+ {
+ currentCascadeMask = 1;
+ for (int casc = 0; casc < cascadeCount; casc++, currentCascadeMask <<= 1)
+ {
+ if (!cascades[casc].enabled)
+ continue;
+ // Did we already cull this?
+ if (caster.visibleCascades & currentCascadeMask)
+ {
+ if (!IntersectAABBPlaneBounds(bounds, cullPlanes[casc].planes, cullPlanes[casc].planeCount))
+ {
+ // Outside cull planes, mark this as invisible
+ caster.visibleCascades &= ~currentCascadeMask;
+ }
+ }
+ }
+ }
+ }
+}
+
+bool IsObjectWithinShadowRange (const ShadowCullData& shadowCullData, const AABB& bounds)
+{
+ if (shadowCullData.useSphereCulling)
+ {
+ float sqrDist = SqrMagnitude(bounds.GetCenter() - shadowCullData.shadowCullCenter);
+ if (sqrDist < shadowCullData.shadowCullSquareRadius)
+ return true;
+ Sphere sphere(shadowCullData.shadowCullCenter, shadowCullData.shadowCullRadius);
+ return IntersectAABBSphere(bounds, sphere);
+ }
+ else
+ {
+ return IntersectAABBPlaneBounds(bounds, &shadowCullData.shadowCullPlanes[kPlaneFrustumFar], 1);
+ }
+}
diff --git a/Runtime/Camera/ShadowCulling.h b/Runtime/Camera/ShadowCulling.h
new file mode 100644
index 0000000..d54ca3c
--- /dev/null
+++ b/Runtime/Camera/ShadowCulling.h
@@ -0,0 +1,121 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#include "Lighting.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Math/Vector4.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Geometry/Sphere.h"
+#include "Runtime/BaseClasses/Tags.h"
+#include "CullingParameters.h"
+#include "ShaderReplaceData.h"
+
+const int kMaxShadowCascades = 4;
+
+class Camera;
+class Light;
+class Transform;
+class AABB;
+class MinMaxAABB;
+struct SceneNode;
+struct CullingOutput;
+struct SceneCullState;
+struct SceneCullingParameters;
+class Shader;
+
+namespace Unity { class Material; }
+namespace Umbra { class Visibility; class Tome; }
+
+struct ShadowCullData
+{
+ Camera* camera;
+ Matrix4x4f cameraClipToWorld;
+ Matrix4x4f cameraWorldToClip;
+ Matrix4x4f actualWorldToClip;
+ Vector3f eyePos;
+ Vector3f viewDir;
+ float viewWidth, viewHeight; // in pixels
+ float layerCullDistances[kNumLayers];
+ bool layerCullSpherical;
+ float baseFarDistance;
+ Plane cameraCullPlanes[6];
+ Plane shadowCullPlanes[6];
+ Vector3f shadowCullCenter;
+ float shadowCullRadius;
+ float shadowCullSquareRadius;
+ bool useSphereCulling;
+ float shadowDistance;
+ float projectionNear;
+ float projectionFar;
+ float farPlaneScale;
+
+ const CullingOutput* visbilityForShadowCulling;
+ const SceneCullingParameters* sceneCullParameters;
+
+ ShaderReplaceData shaderReplace;
+};
+
+struct ShadowCasterCull
+{
+ enum { kMaxPlanes = 10 };
+ Plane planes[kMaxPlanes];
+ int planeCount;
+};
+
+struct ShadowCascadeInfo
+{
+ bool enabled;
+ Matrix4x4f lightMatrix;
+ Matrix4x4f viewMatrix;
+ Matrix4x4f projMatrix;
+ Matrix4x4f worldToClipMatrix;
+ Matrix4x4f shadowMatrix;
+ Sphere outerSphere;
+ float minViewDistance;
+ float maxViewDistance;
+ float nearPlane;
+ float farPlane;
+};
+
+struct ShadowCasterPartData
+{
+ int subMeshIndex; // 4
+ int subShaderIndex; // 4
+ Shader* shader; // 4
+ Unity::Material* material; // 4
+ // 16 bytes
+};
+
+struct ShadowCasterData
+{
+ const SceneNode* node; // 4
+ const AABB* worldAABB; // 4
+ size_t partsStartIndex; // 4
+ size_t partsEndIndex; // 4
+ UInt32 visibleCascades; // 4
+ // 20 bytes
+};
+
+typedef UNITY_TEMP_VECTOR(ShadowCasterData) ShadowCasters;
+typedef UNITY_TEMP_VECTOR(ShadowCasterPartData) ShadowCasterParts;
+
+void CalculateShadowCasterCull(const Plane frustumCullPlanes[6], const Matrix4x4f& clipToWorld, const Vector3f& centerWorld, float nearPlaneScale, float farPlaneScale, LightType lightType, const Vector3f& lightVec, ShadowCasterCull& result, const bool alreadyTested[kPlaneFrustumNum]);
+void CalculateShadowCasterCull (const Plane frustumClipPlanes[6], const Matrix4x4f& clipToWorld, const Vector3f& centerWorld, float nearPlaneScale, float farPlaneScale, LightType lightType, const Transform& trans, ShadowCasterCull& result);
+void SetupShadowCullData (Camera& camera, const Vector3f& cameraPos, const ShaderReplaceData& shaderReplaceData, const SceneCullingParameters* sceneCullParams, ShadowCullData& cullData);
+float CalculateShadowDistance (const Camera& camera);
+float CalculateShadowSphereOffset (const Camera& camera);
+bool CalculateSphericalShadowRange (const Camera& camera, Vector3f& outCenter, float& outRadius);
+void CalculateLightShadowFade (const Camera& camera, float shadowStrength, Vector4f& outParams, Vector4f& outCenterAndType);
+
+void CullDirectionalCascades(ShadowCasters& casters, const ShadowCascadeInfo cascades[kMaxShadowCascades], int cascadeCount,
+ const Quaternionf& lightRot, const Vector3f& lightDir, const ShadowCullData& cullData);
+
+void CullShadowCasters (const Light& light, const ShadowCullData& cullData, bool excludeLightmapped, CullingOutput& cullingOutput );
+
+void GenerateShadowCasterParts (const Light& light, const ShadowCullData& cullData, const CullingOutput& visibleRenderers, MinMaxAABB& casterBounds, ShadowCasters& casters, ShadowCasterParts& casterParts);
+
+
+bool IsObjectWithinShadowRange (const ShadowCullData& shadowCullData, const AABB& bounds);
diff --git a/Runtime/Camera/ShadowSettings.cpp b/Runtime/Camera/ShadowSettings.cpp
new file mode 100644
index 0000000..af1d3a6
--- /dev/null
+++ b/Runtime/Camera/ShadowSettings.cpp
@@ -0,0 +1,14 @@
+#include "UnityPrefix.h"
+#include "Runtime/Serialize/SerializationMetaFlags.h"
+#include "ShadowSettings.h"
+#include "Lighting.h"
+
+void ShadowSettings::Reset()
+{
+ m_Type = kShadowNone;
+ m_Resolution = -1; // auto
+ m_Strength = 1.0f;
+ m_Bias = 0.05f; // 5 cm
+ m_Softness = 4.0f;
+ m_SoftnessFade = 1.0f;
+}
diff --git a/Runtime/Camera/ShadowSettings.h b/Runtime/Camera/ShadowSettings.h
new file mode 100644
index 0000000..256ba83
--- /dev/null
+++ b/Runtime/Camera/ShadowSettings.h
@@ -0,0 +1,33 @@
+#ifndef SHADOW_SETTINGS_H
+#define SHADOW_SETTINGS_H
+
+#include "Runtime/Serialize/SerializeUtility.h"
+
+struct ShadowSettings
+{
+ DECLARE_SERIALIZE_NO_PPTR (ShadowSettings)
+
+ int m_Type; ///< enum { No Shadows, Hard Shadows, Soft Shadows } Shadow cast options
+ // -1 is auto; the rest must match the values in Quality Settings!
+ int m_Resolution; ///< enum { Use Quality Settings = -1, Low Resolution = 0, Medium Resolution = 1, High Resolution = 2, Very High Resolution = 3 } Shadow resolution
+ float m_Strength; ///< Shadow intensity range {0.0, 1.0}
+ float m_Bias; ///< Bias for shadows range {0.0, 10.0}
+ float m_Softness; ///< Shadow softness range {1.0, 8.0}
+ float m_SoftnessFade; ///< Shadow softness fadeout range {0.1, 5.0}
+
+ ShadowSettings () { Reset (); }
+ void Reset();
+};
+
+template<class TransferFunc>
+void ShadowSettings::Transfer (TransferFunc& transfer)
+{
+ TRANSFER_SIMPLE(m_Type);
+ TRANSFER (m_Resolution);
+ TRANSFER (m_Strength);
+ TRANSFER (m_Bias);
+ TRANSFER (m_Softness);
+ TRANSFER (m_SoftnessFade);
+}
+
+#endif
diff --git a/Runtime/Camera/Shadows.cpp b/Runtime/Camera/Shadows.cpp
new file mode 100644
index 0000000..8a7de2f
--- /dev/null
+++ b/Runtime/Camera/Shadows.cpp
@@ -0,0 +1,1227 @@
+#include "UnityPrefix.h"
+#include "Shadows.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Shaders/ShaderKeywords.h"
+
+#if ENABLE_SHADOWS
+
+#include "Runtime/Geometry/AABB.h"
+#include "Light.h"
+#include "RenderManager.h"
+#include "Runtime/Graphics/RenderBufferManager.h"
+#include "BaseRenderer.h"
+#include "External/shaderlab/Library/properties.h"
+#include "Runtime/Graphics/GraphicsHelper.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Renderqueue.h"
+#include "Runtime/Geometry/BoundingUtils.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Camera/Culler.h"
+#include "IntermediateRenderer.h"
+#include "Runtime/GfxDevice/VramLimits.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Misc/GraphicsDevicesDB.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "Runtime/Interfaces/ITerrainManager.h"
+#include "Runtime/GfxDevice/BatchRendering.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "CullResults.h"
+#include "Runtime/Shaders/ShaderNameRegistry.h"
+#include "UnityScene.h"
+#include "Runtime/Profiler/Profiler.h"
+
+#include "Configuration/UnityConfigure.h"
+#if ENABLE_MONO && ENABLE_TERRAIN
+#include "Runtime/Scripting/Backend/ScriptingInvocation.h"
+#endif
+#include "Runtime/Scripting/ScriptingUtility.h"
+
+#if UNITY_PS3 || UNITY_XENON
+# define kShadowmapPointSizeMin 512
+# define kShadowmapPointSizeMax 1024
+# define kShadowmapSpotSizeMin 1024
+# define kShadowmapSpotSizeMax 2048
+# define kShadowmapDirSizeMin 1024
+# define kShadowmapDirSizeMax 4096
+#elif UNITY_WINRT
+# define kShadowmapPointSizeMin 512
+# define kShadowmapPointSizeMax 1024
+# define kShadowmapSpotSizeMin 1024
+# define kShadowmapSpotSizeMax 2048
+# define kShadowmapDirSizeMin 1024
+# define kShadowmapDirSizeMax 2048
+#else
+# define kShadowmapPointSizeMin 512
+# define kShadowmapPointSizeMax 1024
+# define kShadowmapSpotSizeMin 1024
+# define kShadowmapSpotSizeMax 2048
+# define kShadowmapDirSizeMin 2048
+# define kShadowmapDirSizeMax 4096
+#endif
+
+// 4000 is PS3 / Xbox360 and it should be fine on those.
+#define kShadowRotatedBlurFillrateThreshold (4000)
+
+
+#endif // ENABLE_SHADOWS
+
+// --------------------------------------------------------------------------
+
+static ShaderKeyword kShadowsOffKeyword = keywords::Create("SHADOWS_OFF");
+static ShaderKeyword kShadowsDepthKeyword = keywords::Create("SHADOWS_DEPTH");
+static ShaderKeyword kShadowsScreenKeyword = keywords::Create("SHADOWS_SCREEN");
+static ShaderKeyword kShadowsCubeKeyword = keywords::Create("SHADOWS_CUBE");
+static ShaderKeyword kShadowsSoftKeyword = keywords::Create("SHADOWS_SOFT");
+static ShaderKeyword kShadowsSplitSpheresKeyword = keywords::Create("SHADOWS_SPLIT_SPHERES");
+static ShaderKeyword kShadowsNativeKeyword = keywords::Create("SHADOWS_NATIVE");
+
+void SetNoShadowsKeywords()
+{
+ g_ShaderKeywords.Enable( kShadowsOffKeyword );
+ g_ShaderKeywords.Disable( kShadowsDepthKeyword );
+ g_ShaderKeywords.Disable( kShadowsScreenKeyword );
+ g_ShaderKeywords.Disable( kShadowsCubeKeyword );
+ g_ShaderKeywords.Disable( kShadowsSoftKeyword );
+ g_ShaderKeywords.Disable( kShadowsSplitSpheresKeyword );
+ g_ShaderKeywords.Disable( kShadowsNativeKeyword );
+}
+
+
+bool CheckPlatformSupportsShadows ()
+{
+ return
+ gGraphicsCaps.hasRenderToTexture &&
+ (gGraphicsCaps.shaderCaps >= kShaderLevel2) &&
+ gGraphicsCaps.supportsRenderTextureFormat[kRTFormatDepth] &&
+ #if !UNITY_FLASH && !UNITY_WINRT //@TODO: remove me
+ gGraphicsCaps.hasRenderToCubemap &&
+ #endif
+ (gGraphicsCaps.npotRT != kNPOTNone);
+}
+
+// --------------------------------------------------------------------------
+
+#if ENABLE_SHADOWS
+
+bool GetSoftShadowsEnabled ()
+{
+ // Check build settings
+ const BuildSettings& buildSettings = GetBuildSettings();
+ if( !buildSettings.hasShadows || !buildSettings.hasSoftShadows )
+ return false;
+
+ // Disabled by graphics caps?
+ if( gGraphicsCaps.disableSoftShadows )
+ return false;
+
+ // Check quality settings
+ const QualitySettings::QualitySetting& quality = GetQualitySettings().GetCurrent();
+ if( quality.shadows < QualitySettings::kShadowsAll )
+ return false;
+
+ const float shadowDistance = QualitySettings::GetShadowDistanceForRendering();
+ return shadowDistance > 0.0f;
+}
+
+void SetShadowsKeywords( LightType lightType, ShadowType shadowType, bool screen, bool enableSoftShadows )
+{
+ ShadowProjection proj = (ShadowProjection)GetQualitySettings().GetCurrent().shadowProjection;
+ g_ShaderKeywords.Disable( kShadowsOffKeyword );
+ g_ShaderKeywords.Disable (kShadowsNativeKeyword);
+
+ if( IsSoftShadow(shadowType) && enableSoftShadows )
+ g_ShaderKeywords.Enable( kShadowsSoftKeyword );
+ else
+ g_ShaderKeywords.Disable( kShadowsSoftKeyword );
+
+ if( lightType == kLightDirectional && shadowType != kShadowNone &&
+ proj == kShadowProjStableFit )
+ g_ShaderKeywords.Enable( kShadowsSplitSpheresKeyword );
+ else
+ g_ShaderKeywords.Disable( kShadowsSplitSpheresKeyword );
+
+ if( screen )
+ {
+ g_ShaderKeywords.Enable( kShadowsScreenKeyword );
+ g_ShaderKeywords.Disable( kShadowsDepthKeyword );
+ g_ShaderKeywords.Disable( kShadowsCubeKeyword );
+ if (gGraphicsCaps.hasNativeShadowMap && !gGraphicsCaps.hasShadowCollectorPass)
+ g_ShaderKeywords.Enable (kShadowsNativeKeyword);
+ }
+ else if( lightType == kLightPoint )
+ {
+ g_ShaderKeywords.Enable( kShadowsCubeKeyword );
+ g_ShaderKeywords.Disable( kShadowsDepthKeyword );
+ g_ShaderKeywords.Disable( kShadowsScreenKeyword );
+ }
+ else
+ {
+ g_ShaderKeywords.Enable( kShadowsDepthKeyword );
+ g_ShaderKeywords.Disable( kShadowsCubeKeyword );
+ g_ShaderKeywords.Disable( kShadowsScreenKeyword );
+ if (gGraphicsCaps.hasNativeShadowMap &&
+ !(lightType == kLightSpot && gGraphicsCaps.buggySpotNativeShadowMap))
+ {
+ g_ShaderKeywords.Enable (kShadowsNativeKeyword);
+ }
+ }
+}
+
+
+PROFILER_INFORMATION(gShadowsRender, "Shadows.RenderShadowmap", kProfilerRender)
+PROFILER_INFORMATION(gShadowsRenderPoint, "Shadows.RenderShadowmapPoint", kProfilerRender)
+PROFILER_INFORMATION(gShadowsRenderSpot, "Shadows.RenderShadowmapSpot", kProfilerRender)
+PROFILER_INFORMATION(gShadowsRenderDir, "Shadows.RenderShadowmapDir", kProfilerRender)
+PROFILER_INFORMATION(gCullShadowCasters, "CullShadowCasters", kProfilerRender);
+
+// --------------------------------------------------------------------------
+
+
+// from Parallel Split Shadow Maps paper: practical splitting scheme
+void CalculatePSSMDistances (float nearPlane, float shadowFarPlane, int splitCount, float* outDistances, float* outPercentages)
+{
+ AssertIf( !outDistances || !outPercentages || splitCount < 1 );
+
+ // very first & last ones are always these (deals with rounding issues as well)
+ outDistances[0] = nearPlane;
+ outDistances[splitCount] = shadowFarPlane;
+ outPercentages[0] = 0.0f;
+ outPercentages[splitCount] = 1.0f;
+
+ // Each next split is 2x larger than the previous one.
+ // Different from classic PSSM paper; split ratios don't depent on near plane at all.
+ // Dependance on near plane is not very intuitive anyway!
+ if( splitCount == 2 )
+ {
+ // 2 splits: 0, 1/3, 1
+ outPercentages[1] = 1.0f / 3.0f;
+ }
+ else if( splitCount == 4 )
+ {
+ // 4 splits: 0, 1/15, 3/15, 7/15, 1
+ outPercentages[1] = 1.0f / 15.0f;
+ outPercentages[2] = 3.0f / 15.0f;
+ outPercentages[3] = 7.0f / 15.0f;
+ }
+ for( int i = 1; i < splitCount; ++i )
+ outDistances[i] = nearPlane + (shadowFarPlane - nearPlane) * outPercentages[i];
+
+ //if( splitCount == 4 )
+ // printf_console("PSSM: splits=%i near=%g far=%g p=%g %g %g %g\n", splitCount, nearPlane, shadowFarPlane, outDistances[0], outDistances[1], outDistances[2], outDistances[3] );
+ //if( splitCount == 2 )
+ // printf_console("PSSM: splits=%i near=%g far=%g p=%g %g\n", splitCount, nearPlane, shadowFarPlane, outDistances[0], outDistances[1] );
+}
+
+
+// --------------------------------------------------------------------------
+
+static SHADERPROP(ShadowProjectionParams); // x = unused, y = near plane, z = far plane, w = unused
+
+
+// Shadow caster sort data structure
+struct CompactCasterSortData
+{
+ UInt64 key;
+ int casterIndex;
+ size_t partsIndex;
+
+ CompactCasterSortData(UInt32 _smallMeshIndex, UInt32 _hashOfShadowCasterPass, TransformType _transformType, float _depth, int _casterIndex, size_t _partsIndex )
+ {
+ // 64b key: 32 bit shadow caster pass hash, 16b mesh ID, 2b transform type, and 14b depth
+ key=0;
+ UInt32 transformType = static_cast<UInt32>(_transformType);
+ UInt32 z = (UInt32)(16383.0f*_depth);
+
+ key |= (_hashOfShadowCasterPass);
+ key = key << 32;
+ key |= ((_smallMeshIndex&0x0000ffff)<<16)|((transformType&0x00000003)<<14)|(z&0x00003fff);
+
+ casterIndex = _casterIndex;
+ partsIndex = _partsIndex;
+ }
+};
+
+
+struct CompactShadowCasterKeySorter
+{
+ inline bool operator()(const CompactCasterSortData& a, const CompactCasterSortData& b)
+{
+ return a.key < b.key;
+ }
+};
+
+// Shadow caster sorting
+// Input: _splitIndex - cascade index
+// _data - casters object related data
+// _dataParts - casters materials related data
+// Output: _resultOrder - sorted shadow caster draws
+// Returns: Number of active casters
+static int SortCastersCompact( int _splitIndex, ShadowCasters& _data, ShadowCasterParts& _dataParts, const ShadowCameraData& _cameraData, CompactCasterSortData* _resultOrder)
+{
+ int activeCasters = 0;
+
+ // Generate key array for sorting
+ int cascadeMask = 1 << _splitIndex;
+ for( int i = 0; i < _data.size(); ++i )
+ {
+ ShadowCasterData& caster = _data[i];
+
+ // This caster can be skipped for this shadow render pass (e.g. this face of cubemap or this split of directional shadow).
+ if( caster.visibleCascades & cascadeMask )
+ {
+ for( size_t m = caster.partsStartIndex; m < caster.partsEndIndex; ++m )
+ {
+ const TransformInfo& xformInfo = caster.node->renderer->GetTransformInfo ();
+ Matrix4x4f worldToClipMatrix = _cameraData.cameraWorldToClip;
+
+ const Vector3f& worldPos = caster.worldAABB->GetCenter();
+ float z = worldToClipMatrix.Get (2, 0) * worldPos.x + worldToClipMatrix.Get (2, 1) * worldPos.y + worldToClipMatrix.Get (2, 2) * worldPos.z + worldToClipMatrix.Get (2, 3);
+ float w = worldToClipMatrix.Get (3, 0) * worldPos.x + worldToClipMatrix.Get (3, 1) * worldPos.y + worldToClipMatrix.Get (3, 2) * worldPos.z + worldToClipMatrix.Get (3, 3);
+ float z_proj = z/w;
+ z_proj = max(z_proj,0.0f);
+ z_proj = min(z_proj,1.0f);
+ _resultOrder[activeCasters++] = CompactCasterSortData( caster.node->renderer->GetMeshIDSmall(), _dataParts[m].material->GetShadowCasterHash(),
+ xformInfo.transformType, z_proj, i, m );
+ }
+ }
+ }
+
+ std::sort( _resultOrder, _resultOrder + activeCasters, CompactShadowCasterKeySorter() );
+
+ return activeCasters;
+ }
+
+static void RenderCasters( int splitIndex, const Light& light, const Vector3f& lightPos, const Vector3f& lightDir, ShadowCasters& data, ShadowCasterParts& dataParts, const ShadowCameraData& cameraData )
+{
+ GfxDevice& device = GetGfxDevice();
+
+ float matWorld[16], matView[16];
+ CopyMatrix(device.GetViewMatrix(), matView);
+ CopyMatrix(device.GetWorldMatrix(), matWorld);
+
+ device.SetInverseScale(1.0f);
+
+#if GFX_ENABLE_SHADOW_BATCHING
+
+ CompactCasterSortData* sortOrder;
+ ALLOC_TEMP(sortOrder, CompactCasterSortData, dataParts.size());
+ int activeCasters = SortCastersCompact( splitIndex, data, dataParts, cameraData, sortOrder);
+ device.GetFrameStats().AddShadowCasters(activeCasters);
+
+ if (activeCasters == 0)
+ return;
+
+ BatchRenderer casterBatchRenderer;
+ UInt64 previousKey = ((sortOrder[0].key)&0xFFFFFFFFFFFFC000ULL); // depth component does not affect state change boundaries
+ UInt32 prevCustomPropsHash = 0;
+ const ShadowCasterPartData* part = &dataParts[sortOrder[0].partsIndex];
+ const ChannelAssigns* channels = part->material->SetShadowCasterPassWithShader(part->shader, part->subShaderIndex);
+
+ for(int i=0; i<activeCasters;i++)
+ {
+ UInt64 currentKey = ((sortOrder[i].key)&0xFFFFFFFFFFFFC000ULL);
+
+ BaseRenderer* renderer = data[sortOrder[i].casterIndex].node->renderer;
+ const TransformInfo& xformInfo = renderer->GetTransformInfo ();
+ part = &dataParts[sortOrder[i].partsIndex];
+
+ const UInt32 customPropsHash = renderer->GetCustomPropertiesHash();
+ renderer->ApplyCustomProperties(*part->material, part->shader, part->subShaderIndex);
+
+ if (previousKey != currentKey || prevCustomPropsHash != customPropsHash) // Flush() and update state when key changes
+ {
+ casterBatchRenderer.Flush();
+ channels = part->material->SetShadowCasterPassWithShader(part->shader, part->subShaderIndex);
+ }
+
+ // if this pass needs to be rendered
+ if (channels)
+ casterBatchRenderer.Add(renderer, part->subMeshIndex, channels, xformInfo.worldMatrix, xformInfo.transformType);
+
+ previousKey = currentKey;
+ prevCustomPropsHash = customPropsHash;
+ }
+ casterBatchRenderer.Flush();
+
+#else
+
+ int castersSize = data.size();
+ device.GetFrameStats().AddShadowCasters(castersSize);
+ int cascadeMask = 1 << splitIndex;
+ for( int i = 0; i < castersSize; ++i )
+ {
+ ShadowCasterData& caster = data[i];
+
+ // This caster can be skipped for this shadow render pass (e.g. this face of cubemap
+ // or this split of directional shadow).
+ if( caster.visibleCascades & cascadeMask )
+ {
+ BaseRenderer* renderer = caster.node->renderer;
+ const TransformInfo& xformInfo = renderer->GetTransformInfo ();
+
+ SetupObjectMatrix(xformInfo.worldMatrix, xformInfo.transformType);
+ size_t partsStartIndex = caster.partsStartIndex;
+ size_t partsEndIndex = caster.partsEndIndex;
+ for( size_t m = partsStartIndex; m < partsEndIndex; ++m )
+ {
+ ShadowCasterPartData& part = dataParts[m];
+
+ //@TODO: if this returns true and we have any sort of batching, we'd have to break batches here
+ renderer->ApplyCustomProperties(*(part.material), part.shader, part.subShaderIndex);
+
+ const ChannelAssigns* channels = part.material->SetShadowCasterPassWithShader(part.shader, part.subShaderIndex);
+ renderer->Render( part.subMeshIndex, *channels );
+ }
+ }
+ }
+
+#endif // GFX_ENABLE_SHADOW_BATCHING
+
+ device.SetViewMatrix(matView);
+ device.SetWorldMatrix(matWorld);
+}
+
+// return false if focus region is empty: no need to render anything in that case
+static bool SetupDirectionalLightShadowCamera(
+ const ShadowCameraData& cameraData,
+ const Light& light,
+ int splitIndex, int shadowSizeX, int shadowSizeY,
+ const MinMaxAABB& casterBounds, const MinMaxAABB& receiverBounds, const Transform& lt,
+ ShadowCascadeInfo& outCascade )
+{
+ DebugAssertIf( light.GetType() != kLightDirectional );
+ DebugAssertIf( splitIndex < 0 || splitIndex >= cameraData.splitCount );
+
+ const Camera& camera = *cameraData.camera;
+ const Matrix4x4f* frustumTransform = &cameraData.cameraClipToWorld;
+ Matrix4x4f localFrustumTransform;
+ float cameraFarZ = cameraData.projectionFar;
+ float shadowFarZ = cameraData.shadowDistance;
+ float farPlaneScale = 1.0f;
+
+ ShadowProjection projectionType = (ShadowProjection)GetQualitySettings().GetCurrent().shadowProjection;
+ if( projectionType == kShadowProjStableFit )
+ {
+ /////////////////@TODO: WTF? Static abuse??? static Matrix4x4f cameraProjection;
+
+ // Choose the camera-local frustum matrix to keep us numerically stable!
+ static Matrix4x4f cameraProjection;
+ camera.GetImplicitProjectionMatrix( cameraData.projectionNear, cameraData.projectionFar, cameraProjection );
+ Matrix4x4f::Invert_Full( cameraProjection, localFrustumTransform );
+ frustumTransform = &localFrustumTransform;
+
+ Vector3f cornerPos;
+ localFrustumTransform.PerspectiveMultiplyPoint3( Vector3f(1, 1, 1), cornerPos );
+ float cornerDist = Magnitude( cornerPos );
+
+ // We scale our frustum to unit size by dividing lengths by shadowDistance
+ // and intersect the sphere with center (0,0,ShadowSphereOffset) going through (0,0,1)
+ // We need to get the Z distance where the sphere intersects the frustum edge
+ // This is really a 2D problem in the plane between two opposite edges of the frustum
+ // Let's look at the right-angled triangle with sides b = 1, c = cornerDist / farPlaneZ
+ // Pythagoras gives us the length of a: a^2 + b^2 = c^2, b=1 -> a = sqrt(c^2 - 1)
+ // We want to intersect the line y = a*x -> y = sqrt(c^2 - 1) * x
+ // and the circle (x-p)^2 + y^2 = r^2 with radius r and center (p,0)
+ float c = cornerDist / cameraFarZ;
+ float p = CalculateShadowSphereOffset(camera);
+ float r = 1.0f - p;
+ // Wolfram Alpha solution for (x-p)^2 + (sqrt(c^2 - 1)*x)^2 = r^2
+ farPlaneScale = (sqrt(-c*c*p*p+c*c*r*r+p*p)+p)/(c*c);
+
+ #if !UNITY_RELEASE
+ // Check that the distance we calculate the frustum from is correct
+ Vector3f edgeVector = cornerPos / cameraFarZ;
+ Vector3f frustumIntersection = edgeVector * shadowFarZ * farPlaneScale;
+ float shadowRadius = r * shadowFarZ;
+ float centerDist = p * shadowFarZ;
+ Vector3f shadowCenter(0, 0, -centerDist);
+ float dist = Magnitude( frustumIntersection - shadowCenter );
+ DebugAssert( Abs(dist - shadowRadius) < 0.001f * shadowRadius );
+ #endif
+ }
+ float nearZ = cameraData.projectionNear;
+ float scaledShadowRange = shadowFarZ * farPlaneScale - nearZ;
+ float frustumScale = scaledShadowRange / (cameraFarZ - nearZ);
+ if( frustumScale <= Vector3f::epsilon )
+ {
+ return false;
+ }
+
+ // calculate frustum split corners
+ Vector3f cameraFrustum[8];
+ GetFrustumPoints( *frustumTransform, cameraFrustum );
+ Vector3f frustumSplit[8];
+ // split factors are relative to camera frustum (not shadow frustum)
+ float nearSplit = cameraData.splitPercentages[splitIndex] * frustumScale;
+ float farSplit = cameraData.splitPercentages[splitIndex+1] * frustumScale;
+ outCascade.minViewDistance = nearZ + nearSplit * (cameraFarZ - nearZ);
+ outCascade.maxViewDistance = nearZ + farSplit * (cameraFarZ - nearZ);
+ GetFrustumPortion( cameraFrustum, nearSplit, farSplit, frustumSplit );
+
+ std::vector<Vector3f> focusPoints;
+ if( projectionType == kShadowProjCloseFit )
+ {
+ // find the focused body: intersection of frustum & receiver bounds, extruded along
+ // light to include all casters
+ Vector3f lightDir = lt.TransformDirection(Vector3f(0,0,1));
+ CalculateFocusedLightHull( frustumSplit, lightDir, receiverBounds, focusPoints);
+ if( focusPoints.empty() )
+ {
+ outCascade.lightMatrix.SetIdentity();
+ outCascade.projMatrix.SetOrtho( -1.0f, 1.0f, -1.0f, 1.0f, 0.1f, 10.0f );
+ return false;
+ }
+ }
+ else
+ {
+ // TODO: if frustum does not intersect scene caster&receiver bounds, return false
+ }
+
+ // do initial light placement
+ Vector3f center = casterBounds.GetCenter();
+ float castersRadius = Magnitude(casterBounds.GetMax() - casterBounds.GetMin()) * 0.5f;
+ Vector3f axisX = lt.TransformDirection(Vector3f(1,0,0));
+ Vector3f axisY = lt.TransformDirection(Vector3f(0,1,0));
+ Vector3f axisZ = lt.TransformDirection(Vector3f(0,0,1));
+ Vector3f pos = center - axisZ * castersRadius * 1.2f;
+
+ outCascade.lightMatrix.SetPositionAndOrthoNormalBasis( pos, axisX, axisY, axisZ );
+
+ // In Z direction, the final light frustum must encapsulate both caster and receiver bounds.
+ // So take union of those, transform into light space and figure out min/max Z.
+ MinMaxAABB unionBounds = AddAABB( casterBounds, receiverBounds );
+ float minLightZ = std::numeric_limits<float>::infinity();
+ float maxLightZ = -std::numeric_limits<float>::infinity();
+ Vector3f unionPoints[8];
+ unionBounds.GetVertices( unionPoints );
+ for( int i = 0; i < 8; ++i )
+ {
+ Vector3f p = outCascade.lightMatrix.InverseMultiplyPoint3Affine( unionPoints[i] );
+ minLightZ = std::min( p.z, minLightZ );
+ maxLightZ = std::max( p.z, maxLightZ );
+ }
+ float centerLightSpaceZ = (minLightZ + maxLightZ) * 0.5f;
+ float lightDistanceZ = (maxLightZ - minLightZ) * 0.5f;
+
+ Vector3f boundsSize;
+
+ // calculate frustum bounds in light space
+ MinMaxAABB frustumBounds;
+ if( projectionType == kShadowProjCloseFit )
+ {
+ for( int i = 0; i < focusPoints.size(); ++i )
+ {
+ Vector3f p = outCascade.lightMatrix.InverseMultiplyPoint3Affine( focusPoints[i] );
+ p.z = centerLightSpaceZ;
+ frustumBounds.Encapsulate( p );
+ }
+ boundsSize = frustumBounds.GetMax() - frustumBounds.GetMin();
+ }
+ else if( projectionType == kShadowProjStableFit )
+ {
+ Vector3f sphereCenter;
+ float radius;
+ // Sphere is in camera space, so view vector is along negative Z
+ CalculateBoundingSphereFromFrustumPoints( frustumSplit, sphereCenter, radius );
+ float maxViewDist = Abs( sphereCenter.z ) + radius;
+ outCascade.maxViewDistance = std::min( maxViewDist, shadowFarZ );
+
+ // Now we transform our sphere center into world coordinates
+ sphereCenter = camera.GetCameraToWorldMatrix().MultiplyPoint3( sphereCenter );
+ outCascade.outerSphere.Set( sphereCenter, radius );
+
+ Vector3f p = outCascade.lightMatrix.InverseMultiplyPoint3Affine( sphereCenter );
+ p.z = centerLightSpaceZ;
+ frustumBounds.Encapsulate( p );
+ frustumBounds.Expand( radius );
+ boundsSize = Vector3f(radius, radius, radius) * 2.0f;
+ }
+ else
+ {
+ for( int i = 0; i < 8; ++i )
+ {
+ Vector3f p = outCascade.lightMatrix.InverseMultiplyPoint3Affine( frustumSplit[i] );
+ p.z = centerLightSpaceZ;
+ frustumBounds.Encapsulate( p );
+ }
+ boundsSize = frustumBounds.GetMax() - frustumBounds.GetMin();
+ }
+ Vector3f boundsCenter = frustumBounds.GetCenter();
+ Vector3f halfSize = boundsSize * 0.5f;
+
+ // add a small guard band to prevent sampling outside map
+ //static const float kGuardPixels = 1.0f;
+ //frustumBounds.Expand( Vector3f(kGuardPixels / shadowSizeX, kGuardPixels / shadowSizeY, 0) );
+
+ // quantize the position to shadow map texel size; gets rid of some "shadow swimming"
+ double texelSizeX = boundsSize.x / shadowSizeX;
+ double texelSizeY = boundsSize.y / shadowSizeY;
+ pos = outCascade.lightMatrix.MultiplyPoint3(boundsCenter);
+ double projX = axisX.x * (double)pos.x + axisX.y * (double)pos.y + axisX.z * (double)pos.z;
+ double projY = axisY.x * (double)pos.x + axisY.y * (double)pos.y + axisY.z * (double)pos.z;
+ float modX = float( fmod( projX, texelSizeX ) );
+ float modY = float( fmod( projY, texelSizeY ) );
+ pos -= axisX * modX;
+ pos -= axisY * modY;
+
+ // move position back so it encloses everything we need
+ pos -= axisZ * lightDistanceZ * 1.1f;
+ outCascade.lightMatrix.SetPosition( pos );
+
+ outCascade.nearPlane = lightDistanceZ*0.1f;
+ outCascade.farPlane = lightDistanceZ*2.2f;
+ outCascade.projMatrix.SetOrtho( -halfSize.x, halfSize.x, -halfSize.y, halfSize.y, outCascade.nearPlane, outCascade.farPlane );
+
+ outCascade.viewMatrix = outCascade.lightMatrix;
+ outCascade.viewMatrix.SetAxisZ( -outCascade.viewMatrix.GetAxisZ() );
+ outCascade.viewMatrix.Invert_Full();
+
+ Matrix4x4f texMatrix = Matrix4x4f::identity;
+ texMatrix.Get(0,0) = 0.5f;
+ texMatrix.Get(1,1) = 0.5f;
+ texMatrix.Get(2,2) = 0.5f;
+ texMatrix.Get(0,3) = 0.5f;
+ texMatrix.Get(1,3) = 0.5f;
+ texMatrix.Get(2,3) = 0.5f;
+
+ MultiplyMatrices4x4 (&outCascade.projMatrix, &outCascade.viewMatrix, &outCascade.worldToClipMatrix);
+ MultiplyMatrices4x4 (&texMatrix, &outCascade.worldToClipMatrix, &outCascade.shadowMatrix);
+ return true;
+}
+
+
+static bool PositionShadowSpotCamera( const ShadowCameraData& cameraData, const Light* light, Matrix4x4f& outShadowMatrix )
+{
+ DebugAssertIf( light->GetType() != kLightSpot );
+ GfxDevice& device = GetGfxDevice();
+
+ Matrix4x4f viewMatrix, projMatrix;
+ const Transform& lt = light->GetComponent(Transform);
+ // just use spotlight
+ Matrix4x4f s;
+ s.SetScale( Vector3f(1,1,-1) );
+
+ Matrix4x4f worldToLocalMatrixNoScale = lt.GetWorldToLocalMatrixNoScale();
+ MultiplyMatrices4x4 (&s, &worldToLocalMatrixNoScale, &viewMatrix);
+ // On NVIDIA cards in OpenGL using too low near plane results in shadow artifacts. Something like 0.02
+ // is when artifacts start to appear. So I set near plane to be 4% of the range, that seems to work ok.
+ float nearPlane = light->GetRange() * 0.04f;
+ float farPlane = light->GetRange();
+ projMatrix.SetPerspectiveCotan( light->GetCotanHalfSpotAngle(), nearPlane, farPlane );
+
+ device.SetProjectionMatrix (projMatrix);
+ device.SetViewMatrix( viewMatrix.GetPtr() );
+ SetClippingPlaneShaderProps();
+
+ // shadow bias
+ float bias = light->GetShadowBias ();
+ float clampVerts = 0.0f; // disable vertex clamping for spot lights
+ device.GetBuiltinParamValues().SetVectorParam(kShaderVecLightShadowBias, Vector4f(bias, clampVerts, 0, 0));
+
+ Matrix4x4f texMatrix = Matrix4x4f::identity;
+ texMatrix.Get(0,0) = 0.5f;
+ texMatrix.Get(1,1) = 0.5f;
+ texMatrix.Get(2,2) = 0.5f;
+ texMatrix.Get(0,3) = 0.5f;
+ texMatrix.Get(1,3) = 0.5f;
+ texMatrix.Get(2,3) = 0.5f;
+
+ Matrix4x4f temp;
+ // outShadowMatrix = texMatrix * projMatrix * viewMatrix
+ MultiplyMatrices4x4 (&texMatrix, &projMatrix, &temp);
+ MultiplyMatrices4x4 (&temp, &viewMatrix, &outShadowMatrix);
+
+ ShaderLab::g_GlobalProperties->SetVector( kSLPropShadowProjectionParams, 0.0f, nearPlane, farPlane, 0.0f );
+ return true;
+}
+
+
+static void PositionShadowPointCamera( const Vector3f& lightPos, float lightRange, CubemapFace face, Matrix4x4f& outWorldToClipMatrix, Vector3f& outViewDir )
+{
+ GfxDevice& device = GetGfxDevice();
+
+ Matrix4x4f viewMatrix, projMatrix;
+
+ switch( face ) {
+ case kCubeFacePX:
+ outViewDir = Vector3f( 1, 0, 0);
+ viewMatrix.SetOrthoNormalBasisInverse( Vector3f( 0, 0,-1), Vector3f( 0,-1, 0), Vector3f(-1, 0, 0) );
+ break;
+ case kCubeFaceNX:
+ outViewDir = Vector3f(-1, 0, 0);
+ viewMatrix.SetOrthoNormalBasisInverse( Vector3f( 0, 0, 1), Vector3f( 0,-1, 0), Vector3f( 1, 0, 0) );
+ break;
+ case kCubeFacePY:
+ outViewDir = Vector3f( 0, 1, 0);
+ viewMatrix.SetOrthoNormalBasisInverse( Vector3f( 1, 0, 0), Vector3f( 0, 0, 1), Vector3f( 0,-1, 0) );
+ break;
+ case kCubeFaceNY:
+ outViewDir = Vector3f( 0,-1, 0);
+ viewMatrix.SetOrthoNormalBasisInverse( Vector3f( 1, 0, 0), Vector3f( 0, 0,-1), Vector3f( 0, 1, 0) );
+ break;
+ case kCubeFacePZ:
+ outViewDir = Vector3f( 0, 0, 1);
+ viewMatrix.SetOrthoNormalBasisInverse( Vector3f( 1, 0, 0), Vector3f( 0,-1, 0), Vector3f( 0, 0,-1) );
+ break;
+ case kCubeFaceNZ:
+ outViewDir = Vector3f( 0, 0,-1);
+ viewMatrix.SetOrthoNormalBasisInverse( Vector3f(-1, 0, 0), Vector3f( 0,-1, 0), Vector3f( 0, 0, 1) );
+ break;
+ default:
+ AssertString("Invalid cube face!");
+ outViewDir = Vector3f( 0, 0, 0);
+ viewMatrix.SetIdentity();
+ break;
+ }
+
+ Matrix4x4f tr;
+ tr.SetTranslate( -lightPos );
+ viewMatrix *= tr;
+
+ float nearPlane = std::min(lightRange * 0.01f,0.01f);
+ float farPlane = lightRange * 1.01f;
+ projMatrix.SetPerspective( 90.0f, 1.0f, nearPlane, farPlane );
+ device.SetProjectionMatrix (projMatrix);
+ device.SetViewMatrix( viewMatrix.GetPtr() );
+ SetClippingPlaneShaderProps();
+ MultiplyMatrices4x4 (&projMatrix, &viewMatrix, &outWorldToClipMatrix);
+
+ ShaderLab::g_GlobalProperties->SetVector( kSLPropShadowProjectionParams, 0.0f, nearPlane, farPlane, 0.0f );
+}
+
+
+static int CalculateShadowMapSize( const ShadowCameraData& cameraData, const ActiveLight& activeLight )
+{
+ const Light* light = activeLight.light;
+ const Rectf& bounds = activeLight.screenRect;
+ int mapSize = 128;
+
+#if UNITY_PS3
+ // Always allow high quality shadows.
+ bool allowHighQualityShadows = true;
+#elif UNITY_XENON
+ // Only allow high quality shadows if shadow resolution is Very High or higher. This enables predicated tiling.
+ bool allowHighQualityShadows = (light->GetFinalShadowResolution() >= 3);
+#else
+ bool allowHighQualityShadows = (gGraphicsCaps.videoMemoryMB >= kVRAMEnoughForLargeShadowmaps);
+#endif
+
+ const float kMultPoint = 1.0f; // Assume "Very High" shadow map resolution is 1x screen size for point lights.
+ const float kMultSpot = 2.0f; // Assume "Very High" shadow map resolution is 2x screen size for spot lights.
+ const float kMultDir = 3.8f; // Assume "Very High" shadow map resolution is almost 4x of screen size for directional lights.
+
+ switch( light->GetType() )
+ {
+ case kLightPoint:
+ {
+ const int kMaxShadowSize = std::min( gGraphicsCaps.maxCubeMapSize, allowHighQualityShadows ? kShadowmapPointSizeMax : kShadowmapPointSizeMin );
+ // Based on light size on screen
+ float pixelSize = std::max( bounds.width * cameraData.viewWidth, bounds.height * cameraData.viewHeight );
+ mapSize = NextPowerOfTwo( int(pixelSize * kMultPoint) );
+ mapSize >>= cameraData.qualityShift;
+ mapSize = clamp<int>( mapSize, 16, kMaxShadowSize );
+ }
+ break;
+ case kLightSpot:
+ {
+ const int kMaxShadowSize = std::min( gGraphicsCaps.maxRenderTextureSize, allowHighQualityShadows ? kShadowmapSpotSizeMax : kShadowmapSpotSizeMin );
+ // Based on light size on screen
+ float pixelSize = std::max( bounds.width * cameraData.viewWidth, bounds.height * cameraData.viewHeight );
+ mapSize = NextPowerOfTwo( int( pixelSize * kMultSpot ) );
+ mapSize >>= cameraData.qualityShift;
+ mapSize = clamp<int>( mapSize, 16, kMaxShadowSize );
+ }
+ break;
+ case kLightDirectional:
+ {
+ const int kMaxShadowSize = std::min( gGraphicsCaps.maxRenderTextureSize, allowHighQualityShadows ? kShadowmapDirSizeMax : kShadowmapDirSizeMin );
+ int viewSize = int( std::max( cameraData.viewWidth, cameraData.viewHeight ) );
+ mapSize = NextPowerOfTwo( int( viewSize * kMultDir ) );
+ mapSize >>= cameraData.qualityShift;
+ mapSize = clamp<int>( mapSize, 32, kMaxShadowSize );
+ }
+ break;
+ default:
+ AssertString( "Unknown light type!" );
+ }
+
+ return mapSize;
+}
+
+static void PrepareShadowMapParams (ShadowCameraData& camData, const Light* light, Matrix4x4f outShadowMatrices[kMaxShadowCascades])
+{
+ // Use cascaded shadow maps for directional lights and perspective cameras only.
+ // (cascaded shadow maps lose their point for ortho cameras).
+ bool usePSSM = (light->GetType() == kLightDirectional) && (!camData.camera->GetOrthographic());
+
+ // Quality setting for shadow maps
+ const int lightShadowResolution = light->GetFinalShadowResolution();
+ camData.qualityShift = QualitySettings::kShadowResolutionCount - 1 - lightShadowResolution;
+
+
+ if( usePSSM )
+ {
+ if ( GetGfxDevice().GetRenderer() == kGfxRendererOpenGLES20Mobile || GetGfxDevice().GetRenderer() == kGfxRendererOpenGLES30 )
+ camData.splitCount = 1;
+ else
+ camData.splitCount = GetQualitySettings().GetCurrent().shadowCascades;
+
+ CalculatePSSMDistances( camData.camera->GetNear(), camData.shadowDistance, camData.splitCount, camData.splitDistances, camData.splitPercentages );
+ for( int i = camData.splitCount+1; i < kMaxShadowCascades+1; ++i )
+ {
+ camData.splitDistances[i] = camData.splitDistances[i-1] * 1.1f;
+ camData.splitPercentages[i] = camData.splitPercentages[i-1] * 1.1f;
+ }
+ }
+ else
+ {
+ camData.splitDistances[0] = camData.camera->GetNear();
+ camData.splitDistances[1] = camData.shadowDistance;
+ camData.splitPercentages[0] = 0.0f;
+ camData.splitPercentages[1] = 1.0f;
+ camData.splitCount = 1;
+ }
+ // Clear shadow split spheres
+ Vector4f unusedSphere(0, 0, 0, -std::numeric_limits<float>::infinity());
+ for( int i = 0; i < kMaxShadowCascades; ++i )
+ {
+ camData.splitSphereCentersAndSquaredRadii[i] = unusedSphere;
+ }
+ // Zero out unused shadow map matrices. Otherwise for some reason occasionally causes wrong rendering
+ // on D3D REF.
+ for (int i = camData.splitCount; i < kMaxShadowCascades; ++i)
+ {
+ memset (&outShadowMatrices[i].m_Data[0], 0, sizeof(outShadowMatrices[i]));
+ }
+}
+
+
+RenderTexture* RenderShadowMaps( ShadowCameraData& cameraData, const ActiveLight& activeLight, const MinMaxAABB& receiverBounds, bool excludeLightmapped, Matrix4x4f outShadowMatrices[kMaxShadowCascades] )
+{
+ const Light* light = activeLight.light;
+ PROFILER_AUTO_GFX(gShadowsRender, light);
+ GPU_AUTO_SECTION(kGPUSectionShadowPass);
+
+ DebugAssertIf( outShadowMatrices == NULL );
+
+ if (!receiverBounds.IsValid())
+ return NULL;
+
+ PrepareShadowMapParams (cameraData, light, outShadowMatrices);
+
+ RenderTexture* shadowmap = NULL;
+ GfxDevice& device = GetGfxDevice();
+
+ int shadowSize = CalculateShadowMapSize( cameraData, activeLight );
+ int shadowWidth = shadowSize;
+ int shadowHeight = shadowSize;
+ DepthBufferFormat depthFormat = kDepthFormat16;
+ RenderTextureFormat shadowFormat;
+ bool shadowCubeMap;
+ if( light->GetType() == kLightPoint )
+ {
+ shadowFormat = kRTFormatARGB32;
+ shadowCubeMap = true;
+ if (!gGraphicsCaps.hasRenderToCubemap)
+ return NULL;
+ }
+ else
+ {
+ // two splits cascaded shadow map should use 2:1 aspect texture
+ if( cameraData.splitCount == 2 )
+ shadowHeight /= 2;
+ shadowFormat = gGraphicsCaps.hasNativeShadowMap ? kRTFormatShadowMap : kRTFormatDepth;
+ if (gGraphicsCaps.buggySpotNativeShadowMap && light->GetType() == kLightSpot)
+ shadowFormat = kRTFormatDepth;
+
+ shadowCubeMap = false;
+ }
+
+ // Try to somewhat intelligently reduce shadow map resolution if we're getting close to VRAM limits.
+ // Only take into account things that can't be easily moved off-VRAM (screen + render textures).
+ // Allow shadowmap to take 1/3 of the available VRAM at max.
+ const int vramSizeKB = int(gGraphicsCaps.videoMemoryMB * 1024);
+ const GfxDeviceStats::MemoryStats& memoryStats = device.GetFrameStats().GetMemoryStats();
+ const int currentVramUsageKB = (memoryStats.screenBytes + memoryStats.renderTextureBytes) / 1024;
+ const int allowedVramUsageKB = int((vramSizeKB - currentVramUsageKB) * kVRAMMaxFreePortionForShadowMap);
+ int neededVramForShadowmapKB;
+ do {
+ neededVramForShadowmapKB = EstimateRenderTextureSize (shadowWidth, shadowHeight, 1, shadowFormat, depthFormat, shadowCubeMap?kTexDimCUBE:kTexDim2D, false) / 1024;
+ if( neededVramForShadowmapKB < allowedVramUsageKB )
+ break;
+ #if !UNITY_RELEASE
+ printf_console("Shadowmap %ix%i won't fit, reducing size (needed mem=%i used mem=%i allowedmem=%i)\n", shadowWidth, shadowHeight, neededVramForShadowmapKB, currentVramUsageKB, allowedVramUsageKB );
+ #endif
+ shadowWidth /= 2;
+ shadowHeight /= 2;
+ } while( shadowWidth > 4 && shadowHeight > 4 );
+ // We totally don't have VRAM for shadows left! Continue without shadows...
+ if( shadowWidth <= 4 || shadowHeight <= 4 )
+ return NULL;
+
+
+ ///////////////@TODO: Move creation of the shadow buffer until after we have determined if there is anything to be culled...
+
+
+ // Create the shadowmap
+ UInt32 flags = 0;
+ if (shadowCubeMap)
+ flags |= RenderBufferManager::kRBCubemap;
+ shadowmap = GetRenderBufferManager().GetTempBuffer (shadowWidth, shadowHeight, depthFormat, shadowFormat, flags, kRTReadWriteLinear);
+
+ // By default, enable PCF filtering for native shadow maps.
+ // However on mobile that's quite expensive, so only enable it if light has Soft
+ // shadows set.
+ bool enablePCFFilter = (shadowFormat==kRTFormatShadowMap);
+ if (!gGraphicsCaps.hasShadowCollectorPass && (light->GetShadows() < kShadowSoft))
+ enablePCFFilter = false;
+ // Disable PCF filtering if we need to due to driver issues
+ if (gGraphicsCaps.buggyShadowMapBilinearSampling)
+ enablePCFFilter = false;
+
+ shadowmap->GetSettings().m_FilterMode = enablePCFFilter ? kTexFilterBilinear : kTexFilterNearest;
+ shadowmap->ApplySettings();
+
+ // Check if shadow map can be actually created. If for some reason it can't, return NULL.
+ if( !shadowmap->IsCreated() )
+ {
+ if( !shadowmap->Create() )
+ {
+ GetRenderBufferManager().ReleaseTempBuffer( shadowmap );
+ return NULL;
+ }
+ }
+
+ MinMaxAABB casterBounds;
+ ShadowCasters casters;
+ ShadowCasterParts casterParts;
+
+ // Cull shadow casters
+ CullingOutput visibleShadowCasters;
+ {
+ PROFILER_AUTO(gCullShadowCasters, NULL);
+ CreateCullingOutput(cameraData.sceneCullParameters->renderers, visibleShadowCasters);
+ CullShadowCasters (*activeLight.light, cameraData, cameraData.sceneCullParameters->excludeLightmappedShadowCasters, visibleShadowCasters);
+
+ }
+ // Send OnBecameVisible / OnBecameInvisible callback for culled shadow caster renderers
+ GetScene().NotifyVisible (visibleShadowCasters);
+
+ casters.reserve(64);
+ casterParts.reserve(64);
+
+ GenerateShadowCasterParts (*light, cameraData, visibleShadowCasters, casterBounds, casters, casterParts);
+
+ DestroyCullingOutput(visibleShadowCasters);
+
+ int castersSize = casters.size();
+ if( castersSize == 0 )
+ {
+ // If there are no shadow casters, there will be no shadows. Return NULL shadowmap
+ // in this case, the render queue code will use non-shadowed path in then.
+ GetRenderBufferManager().ReleaseTempBuffer( shadowmap );
+ return NULL;
+ }
+
+
+ SetAndRestoreWireframeMode setWireframeOff(false); // turn off wireframe; will restore old value in destructor
+
+ // If all casters for directional light are outside the view frustum, then caster bounds
+ // will be invalid at this point. In that case, make them equal to receiver bounds (case 17871).
+ if( !casterBounds.IsValid() )
+ casterBounds = receiverBounds;
+
+ const Transform& lt = light->GetComponent(Transform);
+ Vector3f lightPos = lt.GetPosition();
+ Quaternionf lightRot = lt.GetRotation();
+ Vector3f lightDir = RotateVectorByQuat(lightRot, Vector3f(0,0,1));
+
+ if( light->GetType() == kLightPoint )
+ {
+ PROFILER_AUTO_GFX(gShadowsRenderPoint,light);
+ // point light: render into cube map
+ device.GetBuiltinParamValues().SetVectorParam(kShaderVecLightPositionRange, Vector4f(lightPos.x, lightPos.y, lightPos.z, 1.0f/light->GetRange()));
+ for( int f = 0; f < 6; ++f )
+ {
+ CubemapFace face = (CubemapFace)f;
+ // activate shadow render target
+ RenderTexture::SetActive (shadowmap, 0, face, RenderTexture::kFlagDontRestore);
+
+ GraphicsHelper::Clear (kGfxClearAll, ColorRGBAf(1,1,1,1).GetPtr(), 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ // position the shadow camera
+ Matrix4x4f shadowWorldToClip;
+ Vector3f viewDir;
+ PositionShadowPointCamera( lightPos, light->GetRange(), face, shadowWorldToClip, viewDir );
+ Plane planes[6];
+ ExtractProjectionPlanes( shadowWorldToClip, planes );
+
+ // Go over casters and mark the ones that are not in our face pyramid as skipped.
+ // First four planes are the ones to check against (left, right, bottom, top).
+ int casterCount = casters.size();
+ for( int c = 0; c < casterCount; ++c )
+ {
+ ShadowCasterData& caster = casters[c];
+ if( IntersectAABBFrustum( *caster.worldAABB, planes, 15 ) )
+ caster.visibleCascades = 1;
+ else
+ caster.visibleCascades = 0;
+ }
+
+ RenderCasters( 0, *light, lightPos, viewDir, casters, casterParts, cameraData );
+ }
+ }
+ else if( light->GetType() == kLightDirectional )
+ {
+ PROFILER_AUTO_GFX(gShadowsRenderDir,light);
+ // directional light: render splits
+ RenderTexture::SetActive (shadowmap, 0, kCubeFaceUnknown, RenderTexture::kFlagDontRestore);
+ GraphicsHelper::Clear (kGfxClearAll, ColorRGBAf(1,1,1,1).GetPtr(), 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ int tilesX, tilesY;
+ switch( cameraData.splitCount )
+ {
+ case 1: tilesX = 1; tilesY = 1; break;
+ case 2: tilesX = 2; tilesY = 1; break;
+ case 4: tilesX = 2; tilesY = 2; break;
+ default: tilesX = 1; tilesY = 1; AssertString( "Unknown split count!" );
+ }
+
+ int splitIndex = 0;
+ bool validMatrices[kMaxShadowCascades];
+ memset(validMatrices, 0, sizeof(validMatrices));
+ int lastValidMatrix = 0;
+ int tileSizeX = shadowWidth / tilesX;
+ int tileSizeY = shadowHeight / tilesY;
+ ShadowCascadeInfo cascades[4];
+ for( int ty = 0; ty < tilesY; ++ty )
+ {
+ for( int tx = 0; tx < tilesX; ++tx )
+ {
+ // position the shadow camera for this split
+ ShadowCascadeInfo& cascade = cascades[splitIndex];
+ cascade.shadowMatrix.SetIdentity();
+ cascade.outerSphere.Set( Vector3f::zero, -1e9f );
+ cascade.enabled = SetupDirectionalLightShadowCamera( cameraData, *light, splitIndex,
+ tileSizeX, tileSizeY, casterBounds, receiverBounds, lt, cascade );
+ const Sphere& sphere = cascade.outerSphere;
+ outShadowMatrices[splitIndex] = cascade.shadowMatrix;
+ cameraData.splitSphereCentersAndSquaredRadii[splitIndex] = Vector4f(sphere.GetCenter(), Sqr(sphere.GetRadius()));
+ ++splitIndex;
+ }
+ }
+ CullDirectionalCascades( casters, cascades, splitIndex, lightRot, lightDir, cameraData);
+ splitIndex = 0;
+ for( int ty = 0; ty < tilesY; ++ty )
+ {
+ for( int tx = 0; tx < tilesX; ++tx )
+ {
+ const ShadowCascadeInfo& cascade = cascades[splitIndex];
+ if( cascade.enabled )
+ {
+ device.SetProjectionMatrix(cascade.projMatrix);
+ device.SetViewMatrix( cascade.viewMatrix.GetPtr() );
+ SetClippingPlaneShaderProps();
+
+ // shadow bias
+ float bias = light->GetShadowBias ();
+ bias *= device.GetDeviceProjectionMatrix()[2*4+2] * -1.0f; // make bias constant in world space
+ float clampVerts = 1.0f; // enable vertex clamping for directional lights
+ device.GetBuiltinParamValues().SetVectorParam(kShaderVecLightShadowBias, Vector4f(bias, clampVerts, 0, 0));
+
+ ShaderLab::g_GlobalProperties->SetVector( kSLPropShadowProjectionParams, 0.0f, cascade.nearPlane, cascade.farPlane, 0.0f );
+
+ Matrix4x4f texMatrix = Matrix4x4f::identity;
+ texMatrix.Get(0,0) = 1.0f / tilesX;
+ texMatrix.Get(1,1) = 1.0f / tilesY;
+ texMatrix.Get(2,2) = 1.0f;
+ texMatrix.Get(0,3) = (float)tx / (float)tilesX;
+ texMatrix.Get(1,3) = (float)ty / (float)tilesY;
+
+ MultiplyMatrices4x4 (&texMatrix, &cascade.shadowMatrix, &outShadowMatrices[splitIndex]);
+ lastValidMatrix = splitIndex;
+ validMatrices[splitIndex] = true;
+
+ if( cameraData.splitCount == 1 )
+ device.SetViewport( tx * tileSizeX+1, ty * tileSizeY+1, tileSizeX-2, tileSizeY-2 );
+ else
+ device.SetViewport( tx * tileSizeX, ty * tileSizeY, tileSizeX, tileSizeY );
+
+ RenderCasters( splitIndex, *light, lightPos, lightDir, casters, casterParts, cameraData );
+ }
+ else
+ {
+ outShadowMatrices[splitIndex].SetIdentity();
+ }
+ ++splitIndex;
+ }
+ }
+ // make sure all matrices are valid, since depth comparisons are not exact
+ //for( int i = 0; i < kMaxPSSMSplits; i++ )
+ // if( !validMatrices[i] )
+ // outShadowMatrices[i] = outShadowMatrices[lastValidMatrix];
+ }
+ else
+ {
+ PROFILER_AUTO_GFX(gShadowsRenderSpot,light);
+ // spot light: render into single shadow map
+ RenderTexture::SetActive (shadowmap, 0, kCubeFaceUnknown, RenderTexture::kFlagDontRestore);
+ GraphicsHelper::Clear (kGfxClearAll, ColorRGBAf(1,1,1,1).GetPtr(), 1.0f, 0);
+ GPU_TIMESTAMP();
+
+ // position the shadow camera
+ if( PositionShadowSpotCamera( cameraData, light, outShadowMatrices[0] ) )
+ {
+ RenderCasters( 0, *light, lightPos, lightDir, casters, casterParts, cameraData );
+ }
+ }
+
+ return shadowmap;
+}
+
+RenderTexture* BlurScreenShadowMap (RenderTexture* screenShadowMap, ShadowType shadowType, float farPlane, float blurWidth, float blurFade)
+{
+ DebugAssert (shadowType != kShadowHard); // paranoia
+ DebugAssert (shadowType != kShadowNone); // paranoia
+
+ const float kBlurThreshold = 0.2f; // 20 cm. If needed, this could be exposed per-light and passed down here.
+
+ float shaderBlurThreshold = kBlurThreshold / farPlane;
+ float invFarMul4 = 4.0f / farPlane * blurFade;
+ ShaderLab::g_GlobalProperties->SetVector (ShaderLab::Property("unity_ShadowBlurParams"), shaderBlurThreshold, invFarMul4, 0, 0);
+
+ screenShadowMap->GetSettings().m_FilterMode = kTexFilterNearest;
+
+ RenderTexture* blurredShadowMap = GetRenderBufferManager().GetTempBuffer (RenderBufferManager::kFullSize, RenderBufferManager::kFullSize, kDepthFormatNone, kRTFormatARGB32, 0, kRTReadWriteLinear);
+ RenderTexture::SetActive (blurredShadowMap, 0, kCubeFaceUnknown, RenderTexture::kFlagDontRestore);
+ // no need to clear
+
+ SetAndRestoreWireframeMode setWireframeOff(false); // turn off wireframe; will restore old value in destructor
+
+ const int* viewport = GetRenderManager().GetCurrentViewPort();
+ const float fWidth = viewport[2];
+ const float fHeight = viewport[3];
+
+ static Material* shadowBlurDiscMaterial = NULL;
+ static Material* shadowBlurDiscRotatedMaterial = NULL;
+ if(shadowBlurDiscMaterial == NULL)
+ {
+ Shader* shader = GetScriptMapper().FindShader ("Hidden/Shadow-ScreenBlur");
+ if (shader)
+ shadowBlurDiscMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ }
+ if(shadowBlurDiscRotatedMaterial == NULL)
+ {
+ Shader* shader = GetScriptMapper().FindShader ("Hidden/Shadow-ScreenBlurRotated");
+ if (shader)
+ shadowBlurDiscRotatedMaterial = Material::CreateMaterial (*shader, Object::kHideAndDontSave);
+ }
+
+ Material* material = NULL;
+ int fillrate = GetGraphicsPixelFillrate(gGraphicsCaps.vendorID, gGraphicsCaps.rendererID);
+ const bool shadowRotatedBlurFastEnough = (fillrate >= kShadowRotatedBlurFillrateThreshold) || (fillrate == -1); // if unknown, assume it's fast enough
+ if(shadowBlurDiscRotatedMaterial && shadowBlurDiscRotatedMaterial->GetShader()->IsSupported() && shadowRotatedBlurFastEnough)
+ {
+ // This value is for compensating that rotated shadows generally look softer than non rotated and makes them look more like the same.
+ const float kShadowBlurRotatedMultiplier = 0.8f;
+ blurWidth *= kShadowBlurRotatedMultiplier;
+ material = shadowBlurDiscRotatedMaterial;
+ }
+ else
+ {
+ material = shadowBlurDiscMaterial;
+ }
+
+ SHADERPROP (MainTex);
+ SHADERPROP (BlurOffsets0);
+ SHADERPROP (BlurOffsets1);
+ SHADERPROP (BlurOffsets2);
+ SHADERPROP (BlurOffsets3);
+ SHADERPROP (BlurOffsets4);
+ SHADERPROP (BlurOffsets5);
+ SHADERPROP (BlurOffsets6);
+ SHADERPROP (BlurOffsets7);
+
+ static ColorRGBAf kBlurTable[8] = {
+ // 9 tap Poisson disc
+ //ColorRGBAf( 0.098484f, 0.0951260f, 0.0f, 0.0f), // center tap, we always sample it on (0,0)
+ ColorRGBAf(-0.957152f, -0.3877980f, 0.0f, 0.0f),
+ ColorRGBAf(-0.799006f, 0.9533680f, 0.0f, 0.0f),
+ ColorRGBAf( 0.940856f, 0.7262480f, 0.0f, 0.0f),
+ ColorRGBAf( 0.599230f, -0.8810998f, 0.0f, 0.0f),
+ ColorRGBAf(-0.288248f, -0.8555254f, 0.0f, 0.0f),
+ ColorRGBAf( 0.038728f, 0.8900720f, 0.0f, 0.0f),
+ ColorRGBAf( 0.954100f, -0.1302840f, 0.0f, 0.0f),
+ ColorRGBAf(-0.455428f, 0.3171780f, 0.0f, 0.0f),
+ };
+
+ float kBlurRadius = fWidth * (1.f / 640.0f) * fHeight * (1.0f / 480.0f);
+ kBlurRadius = blurWidth * clamp(kBlurRadius, 1.0f, 2.0f);
+
+ ColorRGBAf multiplier(
+ kBlurRadius * screenShadowMap->GetTexelSizeX(),
+ kBlurRadius * screenShadowMap->GetTexelSizeY(),
+ 0.0f,
+ 0.0f );
+
+ DeviceMVPMatricesState preserveMVP;
+
+ GfxDevice& device = GetGfxDevice();
+ // Clear so that tiled and multi-GPU systems don't do a RT unresolve
+ float clearColor[4] = {1,0,1,0};
+ device.Clear(kGfxClearColor, clearColor, 1.0f, 0);
+
+ LoadFullScreenOrthoMatrix();
+ material->SetColor( kSLPropBlurOffsets0, kBlurTable[0] * multiplier );
+ material->SetColor( kSLPropBlurOffsets1, kBlurTable[1] * multiplier );
+ material->SetColor( kSLPropBlurOffsets2, kBlurTable[2] * multiplier );
+ material->SetColor( kSLPropBlurOffsets3, kBlurTable[3] * multiplier );
+ material->SetColor( kSLPropBlurOffsets4, kBlurTable[4] * multiplier );
+ material->SetColor( kSLPropBlurOffsets5, kBlurTable[5] * multiplier );
+ material->SetColor( kSLPropBlurOffsets6, kBlurTable[6] * multiplier );
+ material->SetColor( kSLPropBlurOffsets7, kBlurTable[7] * multiplier );
+ material->SetTexture( kSLPropMainTex, screenShadowMap );
+ material->SetPass( 0 );
+
+ device.ImmediateBegin( kPrimitiveQuads );
+ device.ImmediateTexCoord( 0, 0,0,0 ); device.ImmediateVertex( 0, 0, 0 );
+ device.ImmediateTexCoord( 0, 1,0,0 ); device.ImmediateVertex( 1, 0, 0 );
+ device.ImmediateTexCoord( 0, 1,1,0 ); device.ImmediateVertex( 1, 1, 0 );
+ device.ImmediateTexCoord( 0, 0,1,0 ); device.ImmediateVertex( 0, 1, 0 );
+ device.ImmediateEnd();
+ GPU_TIMESTAMP();
+
+ GetRenderBufferManager().ReleaseTempBuffer( screenShadowMap );
+ return blurredShadowMap;
+}
+
+void SetCascadedShadowShaderParams (const Matrix4x4f* shadowMatrices, const float* splitDistances, const Vector4f* splitSphereCentersAndSquaredRadii)
+{
+ BuiltinShaderParamValues& params = GetGfxDevice().GetBuiltinParamValues();
+
+ // Does not set first shadow matrix!
+ DebugAssert (shadowMatrices);
+ params.SetMatrixParam(kShaderMatWorldToShadow1, shadowMatrices[1]);
+ params.SetMatrixParam(kShaderMatWorldToShadow2, shadowMatrices[2]);
+ params.SetMatrixParam(kShaderMatWorldToShadow3, shadowMatrices[3]);
+
+ DebugAssert (splitDistances);
+ params.SetVectorParam(kShaderVecLightSplitsNear, Vector4f(splitDistances));
+ params.SetVectorParam(kShaderVecLightSplitsFar, Vector4f(splitDistances+1));
+ DebugAssert (splitSphereCentersAndSquaredRadii);
+ params.SetVectorParam(kShaderVecShadowSplitSpheres0, splitSphereCentersAndSquaredRadii[0]);
+ params.SetVectorParam(kShaderVecShadowSplitSpheres1, splitSphereCentersAndSquaredRadii[1]);
+ params.SetVectorParam(kShaderVecShadowSplitSpheres2, splitSphereCentersAndSquaredRadii[2]);
+ params.SetVectorParam(kShaderVecShadowSplitSpheres3, splitSphereCentersAndSquaredRadii[3]);
+ params.SetVectorParam(kShaderVecShadowSplitSqRadii, Vector4f(splitSphereCentersAndSquaredRadii[0].w, splitSphereCentersAndSquaredRadii[1].w, splitSphereCentersAndSquaredRadii[2].w, splitSphereCentersAndSquaredRadii[3].w));
+}
+
+#endif // ENABLE_SHADOWS
diff --git a/Runtime/Camera/Shadows.h b/Runtime/Camera/Shadows.h
new file mode 100644
index 0000000..19eed71
--- /dev/null
+++ b/Runtime/Camera/Shadows.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_SHADOWS
+
+#include "ShadowSettings.h"
+#include "ShadowCulling.h"
+
+class MinMaxAABB;
+class Transform;
+class Light;
+class RenderTexture;
+class Matrix4x4f;
+struct ShadowedLight;
+struct ActiveLight;
+struct CullResults;
+
+struct ShadowCameraData : public ShadowCullData
+{
+ ShadowCameraData(const ShadowCullData& cullData) : ShadowCullData(cullData) {}
+
+ int splitCount;
+ float splitDistances[kMaxShadowCascades+1];
+ float splitPercentages[kMaxShadowCascades+1];
+ Vector4f splitSphereCentersAndSquaredRadii[kMaxShadowCascades];
+ int qualityShift; // shadow quality (0 = best, larger numbers worse)
+};
+
+bool GetSoftShadowsEnabled ();
+
+void SetShadowsKeywords (LightType lightType, ShadowType shadowType, bool screen, bool enableSoftShadows);
+
+// outDistances is array of size [splitCount+1] with near&far distances for each split
+// outPercentages is the same, only percentages of shadowFarPlane distance (0.5 = 50% of distance)
+void CalculatePSSMDistances (float nearPlane, float shadowFarPlane, int splitCount, float* outDistances, float* outPercentages);
+
+RenderTexture* RenderShadowMaps (ShadowCameraData& cameraData, const ActiveLight& activeLight, const MinMaxAABB& receiverBounds, bool excludeLightmapped, Matrix4x4f outShadowMatrices[kMaxShadowCascades]);
+RenderTexture* BlurScreenShadowMap (RenderTexture* screenShadowMap, ShadowType shadowType, float farPlane, float blurWidth, float blurFade);
+
+// Does not set first shadow matrix!
+void SetCascadedShadowShaderParams (const Matrix4x4f* shadowMatrices, const float* splitDistances, const Vector4f* splitSphereCentersAndSquaredRadii);
+
+#endif // ENABLE_SHADOWS
+
+
+bool CheckPlatformSupportsShadows ();
+void SetNoShadowsKeywords ();
diff --git a/Runtime/Camera/Skybox.cpp b/Runtime/Camera/Skybox.cpp
new file mode 100644
index 0000000..528859e
--- /dev/null
+++ b/Runtime/Camera/Skybox.cpp
@@ -0,0 +1,199 @@
+#include "UnityPrefix.h"
+#include "Skybox.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Camera.h"
+#include "CameraUtil.h"
+#include "Runtime/Shaders/Material.h"
+#include "External/shaderlab/Library/intshader.h"
+#include "Runtime/Shaders/Shader.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+
+PROFILER_INFORMATION(gRenderSkyboxProfile, "Camera.RenderSkybox", kProfilerRender);
+
+
+using namespace ShaderLab;
+
+enum
+{
+ kSkyboxVertexChannels = (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0)
+};
+
+struct SkyboxVertex {
+ float x, y, z;
+ float tu, tv;
+};
+
+const int kSkyboxVertexCount = 6*4;
+static const SkyboxVertex kSkyboxVB[kSkyboxVertexCount] = {
+ { -1, 1, 1, 0,1 }, { 1, 1, 1, 1,1 }, { 1, -1, 1, 1,0 }, { -1, -1, 1, 0,0 },
+ { 1, 1, -1, 0,1 }, { -1, 1, -1, 1,1 }, { -1, -1, -1, 1,0 }, { 1, -1, -1, 0,0 },
+ { 1, 1, 1, 0,1 }, { 1, 1, -1, 1,1 }, { 1, -1, -1, 1,0 }, { 1, -1, 1, 0,0 },
+ { -1, 1, -1, 0,1 }, { -1, 1, 1, 1,1 }, { -1, -1, 1, 1,0 }, { -1, -1, -1, 0,0 },
+ { -1, 1, -1, 0,1 }, { 1, 1, -1, 1,1 }, { 1, 1, 1, 1,0 }, { -1, 1, 1, 0,0 },
+ { -1, -1, 1, 0,1 }, { 1, -1, 1, 1,1 }, { 1, -1, -1, 1,0 }, { -1, -1, -1, 0,0 },
+};
+
+const int kSkyplaneVertexCount = 4;
+static const SkyboxVertex kSkyplaneVB[kSkyplaneVertexCount] = {
+ { 1, 1, -1, 0,1 }, { -1, 1, -1, 1,1 }, { -1, -1, -1, 1,0 }, { 1, -1, -1, 0,0 },
+};
+
+Skybox::Skybox (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+{
+}
+
+Skybox::~Skybox ()
+{
+}
+
+void Skybox::AddToManager ()
+{
+
+}
+
+void Skybox::RemoveFromManager ()
+{
+
+}
+
+void Skybox::RenderSkybox (Material* mat, const Camera& camera)
+{
+ if( !mat )
+ return;
+
+ PROFILER_AUTO_GFX(gRenderSkyboxProfile, &camera)
+
+ Shader* shader = mat->GetShader();
+ Assert (shader);
+
+ GfxDevice& device = GetGfxDevice();
+
+ DeviceMVPMatricesState preserveMVP;
+
+ #if UNITY_WP8
+ void RotateScreenIfNeeded(Matrix4x4f& mat);
+ #endif
+
+ if (camera.GetOrthographic())
+ {
+ const float epsilon = 1e-6f;
+ const float nearPlane = camera.GetNear () * 0.01f;
+ const float dist = camera.GetFar() * 10.0f;
+
+ // Make Ortho matrix which passes W to Z
+ Matrix4x4f projection = Matrix4x4f::identity;
+ //camera.GetImplicitProjectionMatrix( nearPlane, projection );
+ projection.Get(2, 2) = -1.0f + epsilon;
+ projection.Get(2, 3) = (-2.0f + epsilon) * nearPlane;
+ projection.Get(3, 2) = -1.0f;
+
+ #if UNITY_WP8
+ if (!RenderTexture::GetActive()) {
+ RotateScreenIfNeeded(projection);
+ }
+ #endif
+
+ Matrix4x4f matrix = Matrix4x4f::identity;
+ //device.SetViewMatrix(matrix.GetPtr());
+ matrix.SetScale(Vector3f(dist,dist,dist));
+ matrix.SetPosition( camera.GetPosition() );
+ device.SetWorldMatrix(matrix.GetPtr());
+ device.SetProjectionMatrix(projection);
+ }
+ else
+ {
+ // Modify Projection matrix to make Infinite Projection Matrix (which passes W into Z)
+ // perspective divide will lend ZBuffer value to always be 1.0 (all points on far plane)
+ // NOTE: However we need to compensate on floating point precision errors
+ // by bringing Z slightly closer to viewer by adding Epsilon
+ // See "Projection Matrix Tricks" by Eric Lengyel GDC2007
+ // http://www.terathon.com/gdc07_lengyel.ppt
+
+ // In order to avoid clipping of skybox polys we increase skybox size and pull NearPlane as close as possible
+ // Higher epsilon values that Z/W < 1.0, but drastically reduces zBuffer precision close to FarPlane
+ // Epsilon 1e-6 gives good results as long as NearPlane >= 0.05
+ const float epsilon = 1e-6f;
+ const float nearPlane = camera.GetNear () * 0.01f;
+ const float dist = camera.GetFar() * 10.0f;
+
+ Matrix4x4f projection;
+ camera.GetImplicitProjectionMatrix( nearPlane, projection );
+ projection.Get(2, 2) = -1.0f + epsilon;
+ projection.Get(2, 3) = (-2.0f + epsilon) * nearPlane;
+ projection.Get(3, 2) = -1.0f;
+
+ #if UNITY_WP8
+ if (!RenderTexture::GetActive()) {
+ RotateScreenIfNeeded(projection);
+ }
+ #endif
+
+ Matrix4x4f matrix = Matrix4x4f::identity;
+ matrix.SetScale( Vector3f(dist,dist,dist) );
+ matrix.SetPosition( camera.GetPosition() );
+ device.SetWorldMatrix( matrix.GetPtr() );
+ device.SetProjectionMatrix(projection);
+ }
+
+ ShaderLab::IntShader* slshader = shader->GetShaderLabShader();
+ const int passCount = slshader->GetActiveSubShader().GetValidPassCount();
+ if( passCount == 6 )
+ {
+ // regular skybox with 6 separate textures per face
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ for( int j = 0; j < 6; j++ )
+ {
+ SkyboxVertex* vbPtr = 0;
+ if(vbo.GetChunk(kSkyboxVertexChannels, 4, 0, DynamicVBO::kDrawQuads, (void**)&vbPtr, NULL))
+ {
+ vbPtr[0] = kSkyboxVB[4*j+0];
+ vbPtr[1] = kSkyboxVB[4*j+1];
+ vbPtr[2] = kSkyboxVB[4*j+2];
+ vbPtr[3] = kSkyboxVB[4*j+3];
+
+ vbo.ReleaseChunk(4, 0);
+
+ const ChannelAssigns* channels = mat->SetPassWithShader( j, shader, 0 );
+ vbo.DrawChunk (*channels);
+ GPU_TIMESTAMP();
+ }
+ }
+ }
+ else
+ {
+ // cube mapped skybox
+ for (int pass = 0; pass < passCount; pass++)
+ {
+ mat->SetPassWithShader( pass, shader, 0 );
+ device.ImmediateBegin( kPrimitiveQuads );
+ const SkyboxVertex* verts = kSkyboxVB;
+ for ( int i = 0; i < kSkyboxVertexCount; i++ ) {
+ device.ImmediateTexCoordAll( verts->x, verts->y, verts->z );
+ device.ImmediateVertex( verts->x, verts->y, verts->z );
+ ++verts;
+ }
+ device.ImmediateEnd();
+ GPU_TIMESTAMP();
+ }
+ }
+}
+
+void Skybox::SetMaterial (Material* material) {
+ m_CustomSkybox = material;
+}
+
+Material* Skybox::GetMaterial ()const {
+ return m_CustomSkybox;
+}
+
+template<class TransferFunction>
+void Skybox::Transfer (TransferFunction& transfer) {
+ Super::Transfer (transfer);
+ TRANSFER_SIMPLE (m_CustomSkybox);
+}
+
+IMPLEMENT_CLASS (Skybox)
+IMPLEMENT_OBJECT_SERIALIZE (Skybox)
diff --git a/Runtime/Camera/Skybox.h b/Runtime/Camera/Skybox.h
new file mode 100644
index 0000000..3d59a27
--- /dev/null
+++ b/Runtime/Camera/Skybox.h
@@ -0,0 +1,27 @@
+#ifndef SKYBOX_H
+#define SKYBOX_H
+
+#include "Runtime/GameCode/Behaviour.h"
+
+namespace Unity { class Material; }
+class Camera;
+
+class Skybox : public Behaviour {
+public:
+ REGISTER_DERIVED_CLASS (Skybox, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (Skybox)
+
+ Skybox (MemLabelId label, ObjectCreationMode mode);
+ static void RenderSkybox (Material* material, const Camera& camera);
+
+ void SetMaterial (Material* material);
+ Material* GetMaterial ()const;
+
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+
+private:
+ PPtr<Material> m_CustomSkybox;
+};
+
+#endif
diff --git a/Runtime/Camera/UmbraBackwardsCompatibility.cpp b/Runtime/Camera/UmbraBackwardsCompatibility.cpp
new file mode 100644
index 0000000..f792ffb
--- /dev/null
+++ b/Runtime/Camera/UmbraBackwardsCompatibility.cpp
@@ -0,0 +1,37 @@
+#include "UnityPrefix.h"
+#include "UmbraBackwardsCompatibilityDefine.h"
+
+#if SUPPORT_BACKWARDS_COMPATIBLE_UMBRA
+
+#define UMBRA_DISABLE_SPU_QUERIES 1
+#define UMBRA_COMP_NO_EXCEPTIONS 1
+#define UMBRA_SUPPORT_LEGACY_DATA 1
+#define NO_SSE2_NAMESPACE
+
+#include "External/Umbra_3_0/source/source/runtime/umbraBSPTree_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraBitOps_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraConnectivity_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraInstrumentation_GPA_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraIntersect_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraLegacyTome_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraPVSCull_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraPortalCull_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraPortalCull2_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraPortalRaster_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraPortalRayTracer_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraQueryApi_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraQueryArgs_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraQueryContext_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraSIMD_SSE_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraNoSSE2_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraTome_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraTomeApi_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/umbraTransformer_3_0.cpp"
+#include "External/Umbra_3_0/source/source/shared/umbraAABB_3_0.cpp"
+#include "External/Umbra_3_0/source/source/shared/umbraMatrix_3_0.cpp"
+#include "External/Umbra_3_0/source/source/shared/umbraPrivateDefs_3_0.cpp"
+#include "External/Umbra_3_0/source/source/shared/umbraPrivateVersion_3_0.cpp"
+#include "External/Umbra_3_0/source/source/shared/umbraRandom_3_0.cpp"
+#include "External/Umbra_3_0/source/source/runtime/xbox360/umbraSIMD_XBOX360_3_0.cpp"
+
+#endif \ No newline at end of file
diff --git a/Runtime/Camera/UmbraBackwardsCompatibility.h b/Runtime/Camera/UmbraBackwardsCompatibility.h
new file mode 100644
index 0000000..d3eb40b
--- /dev/null
+++ b/Runtime/Camera/UmbraBackwardsCompatibility.h
@@ -0,0 +1,85 @@
+#ifndef UMBRA_BACKWARDS_COMPATIBILITY_H
+#define UMBRA_BACKWARDS_COMPATIBILITY_H
+
+#include "UmbraBackwardsCompatibilityDefine.h"
+#include "UmbraTomeData.h"
+
+#include "External/Umbra/builds/interface/runtime/umbraTome.hpp"
+#include "External/Umbra/builds/interface/runtime/umbraQuery.hpp"
+
+#if SUPPORT_BACKWARDS_COMPATIBLE_UMBRA
+# include "External/Umbra_3_0/source/interface/runtime/umbraTome_3_0.hpp"
+# include "External/Umbra_3_0/source/interface/runtime/umbraQuery_3_0.hpp"
+#endif
+
+inline void CleanupUmbraTomeData (UmbraTomeData& tomeData)
+{
+ if (tomeData.tome)
+ {
+ Umbra::TomeLoader::freeTome(tomeData.tome);
+ tomeData.tome = NULL;
+ }
+
+#if SUPPORT_BACKWARDS_COMPATIBLE_UMBRA
+ if (tomeData.legacyTome)
+ {
+ Umbra_3_0::TomeLoader::freeTome(tomeData.legacyTome);
+ tomeData.legacyTome = NULL;
+ }
+#endif
+}
+inline const UmbraTomeData LoadUmbraTome (const UInt8* buf, size_t bytes)
+{
+ if (buf == NULL || bytes == 0)
+ return UmbraTomeData ();
+
+ const Umbra::Tome* tome = Umbra::TomeLoader::loadFromBuffer(buf, bytes);
+ if (tome->getStatus() == Umbra::Tome::STATUS_OK)
+ {
+ UmbraTomeData tomeData;
+ tomeData.tome = tome;
+ return tomeData;
+ }
+
+
+#if SUPPORT_BACKWARDS_COMPATIBLE_UMBRA
+ if (tome && tome->getStatus() == Umbra::Tome::STATUS_OLDER_VERSION)
+ {
+ const Umbra_3_0::Tome* legacyTome = Umbra_3_0::TomeLoader::loadFromBuffer(buf, bytes);
+ if (legacyTome && legacyTome->getStatus() == Umbra_3_0::Tome::STATUS_OK)
+ {
+ UmbraTomeData tomeData;
+ tomeData.legacyTome = legacyTome;
+ return tomeData;
+ }
+
+ ErrorString("Failed to load legacy tome data");
+ return UmbraTomeData();
+ }
+#endif
+
+ WarningString ("Loading deprecated Occlusion Culling is not supported. Please rebake the occlusion culling data.");
+ return UmbraTomeData ();
+}
+
+inline void SetGateStates( Umbra::Query* query, const UmbraTomeData& tomeData, Umbra::GateStateVector& gateVector)
+{
+#if SUPPORT_BACKWARDS_COMPATIBLE_UMBRA
+ if (tomeData.IsLegacyTome ())
+ {
+ ((Umbra_3_0::QueryExt*)query)->setGateStates((const Umbra_3_0::GateStateVector*)&gateVector);
+ return;
+ }
+#endif
+
+ query->setGateStates(&gateVector);
+}
+
+#if SUPPORT_BACKWARDS_COMPATIBLE_UMBRA
+ #define UMBRA_TOME_METHOD(TOME,method) (TOME.tome != NULL ? TOME.tome->method : TOME.legacyTome->method)
+#else
+ #define UMBRA_TOME_METHOD(TOME,method) (TOME.tome->method)
+#endif
+
+
+#endif \ No newline at end of file
diff --git a/Runtime/Camera/UmbraBackwardsCompatibilityDefine.h b/Runtime/Camera/UmbraBackwardsCompatibilityDefine.h
new file mode 100644
index 0000000..756f05c
--- /dev/null
+++ b/Runtime/Camera/UmbraBackwardsCompatibilityDefine.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#define SUPPORT_BACKWARDS_COMPATIBLE_UMBRA WEBPLUG
+//#define SUPPORT_BACKWARDS_COMPATIBLE_UMBRA 1 \ No newline at end of file
diff --git a/Runtime/Camera/UmbraTomeData.h b/Runtime/Camera/UmbraTomeData.h
new file mode 100644
index 0000000..4e36363
--- /dev/null
+++ b/Runtime/Camera/UmbraTomeData.h
@@ -0,0 +1,22 @@
+#pragma once
+
+namespace Umbra { class Tome; };
+namespace Umbra_3_0 { class Tome; };
+
+struct UmbraTomeData
+{
+ const Umbra::Tome* tome;
+ const Umbra_3_0::Tome* legacyTome;
+
+ UmbraTomeData () { tome = NULL; legacyTome = NULL; }
+
+
+ bool HasTome () const { return tome != NULL || legacyTome != NULL; }
+ bool IsLegacyTome () const { return legacyTome != NULL; }
+
+
+ friend inline bool operator == (const UmbraTomeData& lhs, const UmbraTomeData& rhs)
+ {
+ return lhs.tome == rhs.tome && lhs.legacyTome == rhs.legacyTome;
+ }
+}; \ No newline at end of file
diff --git a/Runtime/Camera/UnityScene.cpp b/Runtime/Camera/UnityScene.cpp
new file mode 100644
index 0000000..84832d0
--- /dev/null
+++ b/Runtime/Camera/UnityScene.cpp
@@ -0,0 +1,505 @@
+#include "UnityPrefix.h"
+#include "UnityScene.h"
+#include "IntermediateRenderer.h"
+#include "SceneSettings.h"
+#include "CullingParameters.h"
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Camera/OcclusionPortal.h"
+#include "UmbraBackwardsCompatibility.h"
+
+///@TODO: Test for removing static renderers in pvs.
+// @TODO: Test to check that enable / disable renderer keeps layer & bounding volume correctly.
+//@TODO: Test for destroying all renderers during OnBecameInvisible callback
+
+//* Should we use setDynamicObjects?
+//@TODO: When a gate is baked. Make sure that the majority of the area is not tagged as occluded. (Eg. someone tagged static data around it)
+//* Culling test when static objects have been deleted after baking...
+
+namespace Unity
+{
+
+static Scene* gScene = NULL;
+
+Scene::Scene ()
+{
+ m_UmbraQuery = NULL;
+ m_PreventAddRemoveRenderer = 0;
+ m_RequestStaticPVSRebuild = false;
+ m_GateState = NULL;
+}
+
+Scene::~Scene ()
+{
+ Assert(m_RendererNodes.empty());
+
+ ClearIntermediateRenderers();
+ CleanupUmbra();
+}
+
+void Scene::ClearIntermediateRenderers()
+{
+ m_IntermediateNodes.Clear();
+}
+
+void Scene::CleanupUmbra ()
+{
+ // The scene class does not own the tome (SceneSettings does)
+ m_UmbraTome = UmbraTomeData();
+
+ if (m_UmbraQuery)
+ delete m_UmbraQuery;
+ m_UmbraQuery = NULL;
+
+ delete[] m_GateState;
+ m_GateState = NULL;
+
+ // Cleanup references to PVS handle
+ dynamic_array<SceneNode>::iterator it, itEnd = m_RendererNodes.end();
+ for (it = m_RendererNodes.begin(); it != itEnd; ++it)
+ {
+ SceneNode& node = *it;
+ node.pvsHandle = -1;
+ }
+
+ ///////@TODO: Cleanup all OcclusionPortals gateIndices
+
+ // Cleanup any renderer nodes which have already been deleted, but were kept around to keep the indices of pvs renderers
+ // in sync with umbra
+ for (int i=0;i<m_RendererNodes.size();i++)
+ {
+ if (m_RendererNodes[i].renderer == NULL)
+ {
+ RemoveRenderer(i);
+ i--;
+ }
+ }
+}
+
+void Scene::CleanupPVSAndRequestRebuild ()
+{
+ CleanupUmbra();
+ m_RequestStaticPVSRebuild = true;
+}
+
+void Scene::InitializeUmbra()
+{
+ RecalculateDirtyBounds();
+ CleanupUmbra();
+
+ m_UmbraTome = GetSceneSettings().GetUmbraTome();
+
+ if (!m_UmbraTome.HasTome())
+ return;
+
+ Assert(m_PendingRemoval.empty());
+
+ const dynamic_array<PPtr<Renderer> >& pvsObjectArray = GetSceneSettings().GetPVSObjectArray();
+ const dynamic_array<PPtr<OcclusionPortal> >& portalsArray = GetSceneSettings().GetPortalsArray();
+
+ int objectCount = UMBRA_TOME_METHOD(m_UmbraTome, getObjectCount());
+ // Setup pvs handles in renderer nodes
+ for (int i = 0; i < objectCount; i++)
+ {
+ Umbra::UINT32 userId = UMBRA_TOME_METHOD(m_UmbraTome, getObjectUserID(i));
+ Assert(userId < pvsObjectArray.size());
+ if (userId >= pvsObjectArray.size())
+ continue;
+
+ // Don't do any loading from disk to avoid weird corner cases and recursion - You never know
+ Object* obj = Object::IDToPointer(pvsObjectArray[userId].GetInstanceID());
+ Renderer* renderer = dynamic_pptr_cast<Renderer*> (obj);
+ SceneHandle handle = -1;
+ if (renderer)
+ handle = renderer->GetSceneHandle();
+
+ // Objects might have been deleted after baking. In this case we need to create holes in the PVS object array
+ // For each pvs object that can't be found we add a NULL renderer
+ if (handle < 0 || handle >= m_RendererNodes.size())
+ handle = AddRendererInternal (NULL, 0, AABB());
+
+ m_RendererNodes[handle].pvsHandle = i;
+ }
+
+ Assert(objectCount <= m_RendererNodes.size());
+
+ // Make sure that the pvs handles match the index in the renderer nodes array
+ // This way we can do very fast lookups.
+ for (int i=0;i<m_RendererNodes.size();i++)
+ {
+ while (m_RendererNodes[i].pvsHandle != -1 && m_RendererNodes[i].pvsHandle != i)
+ {
+ SInt32 pvsIndex = m_RendererNodes[i].pvsHandle;
+
+ std::swap(m_RendererNodes[i], m_RendererNodes[pvsIndex]);
+ std::swap(m_BoundingBoxes[i], m_BoundingBoxes[pvsIndex]);
+ std::swap(m_VisibilityBits[i], m_VisibilityBits[pvsIndex]);
+
+ Renderer* rendererI = static_cast<Renderer*> (m_RendererNodes[i].renderer);
+ if (rendererI)
+ rendererI->NotifySceneHandleChange(i);
+
+ Renderer* rendererPVS = static_cast<Renderer*> (m_RendererNodes[pvsIndex].renderer);
+ if (rendererPVS)
+ rendererPVS->NotifySceneHandleChange(pvsIndex);
+ }
+ }
+
+ for (int i=0;i<m_RendererNodes.size();i++)
+ {
+ Assert(m_RendererNodes[i].pvsHandle == -1 || m_RendererNodes[i].pvsHandle == i);
+ Assert(m_RendererNodes[i].renderer == NULL || static_cast<Renderer*> (m_RendererNodes[i].renderer)->GetSceneHandle() == i);
+ }
+ // Create query
+ m_UmbraQuery = new Umbra::QueryExt(m_UmbraTome.tome);
+
+ // Setup portals by querying the renderer and grabbing the OcclusionPortal on the same game object
+ int gateCount = UMBRA_TOME_METHOD(m_UmbraTome, getGateCount());
+ if (gateCount != 0 && !portalsArray.empty())
+ {
+ m_GateState = new UInt8[UMBRA_TOME_METHOD(m_UmbraTome, getGateStateSize())];
+ memset(m_GateState, 0, UMBRA_TOME_METHOD(m_UmbraTome, getGateStateSize()));
+
+ Umbra::GateStateVector gateVector (m_GateState, 0, false);
+
+ SetGateStates (m_UmbraQuery, m_UmbraTome, gateVector);
+
+ for (int i = 0; i < gateCount; i++)
+ {
+ // Umbra userID's need to be unique. They are allocated to be after the static renderers/
+ Umbra::UINT32 userId = UMBRA_TOME_METHOD(m_UmbraTome, getGateUserID(i)) - pvsObjectArray.size();
+ Assert(userId < portalsArray.size());
+ if (userId >= portalsArray.size())
+ continue;
+
+ // Don't do any loading from disk to avoid weird corner cases and recursion - You never know
+ Object* obj = Object::IDToPointer(portalsArray[i].GetInstanceID());
+ OcclusionPortal* portal = dynamic_pptr_cast<OcclusionPortal*> (obj);
+ if (portal)
+ {
+ portal->SetPortalIndex(i);
+ gateVector.setState(i, portal->CalculatePortalEnabled());
+ }
+ }
+ }
+}
+
+
+size_t Scene::GetStaticObjectCount () const
+{
+ if (!m_UmbraTome.HasTome())
+ return 0;
+
+ return UMBRA_TOME_METHOD(m_UmbraTome, getObjectCount());
+}
+
+size_t Scene::GetDynamicObjectCount () const
+{
+ return GetRendererNodeCount() - GetStaticObjectCount();
+}
+
+size_t Scene::GetIntermediateObjectCount () const
+{
+ return m_IntermediateNodes.GetRendererCount();
+}
+
+const SceneNode* Scene::GetStaticSceneNodes () const
+{
+ return m_RendererNodes.begin();
+}
+
+const SceneNode* Scene::GetDynamicSceneNodes () const
+{
+ return m_RendererNodes.begin() + GetStaticObjectCount();
+}
+
+const AABB* Scene::GetStaticBoundingBoxes () const
+{
+ return m_BoundingBoxes.begin();
+}
+
+const AABB* Scene::GetDynamicBoundingBoxes () const
+{
+ return m_BoundingBoxes.begin() + GetStaticObjectCount();
+}
+
+
+#if DEBUGMODE
+bool Scene::HasNodeForRenderer( const BaseRenderer* r )
+{
+ for (dynamic_array<SceneNode>::iterator j = m_RendererNodes.begin(); j != m_RendererNodes.end(); ++j)
+ {
+ if (j->renderer == r)
+ return true;
+ }
+
+ return false;
+}
+
+#endif
+
+SceneHandle Scene::AddRendererInternal (Renderer *renderer, int layer, const AABB& aabb)
+{
+ SceneHandle handle = m_RendererNodes.size();
+ Assert(m_BoundingBoxes.size() == handle);
+ Assert(m_VisibilityBits.size() == handle);
+
+ SceneNode node;
+ node.renderer = renderer;
+ node.layer = layer;
+ m_RendererNodes.push_back(node);
+ m_BoundingBoxes.push_back(aabb);
+ m_VisibilityBits.push_back(0);
+ return handle;
+}
+
+
+SceneHandle Scene::AddRenderer (Renderer *renderer)
+{
+ Assert (renderer);
+#if DEBUGMODE
+ DebugAssertIf (HasNodeForRenderer(renderer));
+#endif
+ if (m_PreventAddRemoveRenderer != 0)
+ {
+ AssertString("Adding renderer during rendering is not allowed.");
+ return kInvalidSceneHandle;
+ }
+
+ AABB aabb;
+ renderer->GetWorldAABB(aabb);
+ Assert(aabb.IsValid());
+
+ return AddRendererInternal(renderer, renderer->GetLayer(), aabb);
+}
+
+BaseRenderer* Scene::RemoveRenderer (SceneHandle handle)
+{
+ if (handle < 0 || handle >= m_RendererNodes.size())
+ {
+ ErrorString("Invalid SceneHandle");
+ return NULL;
+ }
+ SceneNode& node = m_RendererNodes[handle];
+ BaseRenderer* renderer = node.renderer;
+
+ if (m_PreventAddRemoveRenderer != 0)
+ {
+ // The current node can be removed during NotifyVisible()
+ // This is due to the fact that Animations can be updated during culling and
+ // our animation system allows users to set m_Enabled = false which then
+ // results in our node being removed (see fogbugz case 378739).
+
+ // We can't actually remove this or reorder nodes until after the render.
+ // There are pointers to nodes and AABBs being used during rendering!
+ m_PendingRemoval.push_back(handle);
+ node.disable = true;
+ return renderer;
+ }
+
+ // Static objects can not be removed from the array
+ if (handle < GetStaticObjectCount())
+ {
+ m_VisibilityBits[handle] = 0;
+ node.renderer = NULL;
+ node.dirtyAABB = false;
+ return renderer;
+ }
+
+ // Swap with last element (if we are not the last element)
+ int lastIndex = m_RendererNodes.size() - 1;
+ const SceneNode& lastNode = m_RendererNodes[lastIndex];
+ if (handle != lastIndex && lastNode.renderer != NULL)
+ {
+ const AABB& lastAABB = m_BoundingBoxes[lastIndex];
+ bool lastVisibilityBits = m_VisibilityBits[lastIndex];
+ m_RendererNodes[handle] = lastNode;
+ m_BoundingBoxes[handle] = lastAABB;
+ m_VisibilityBits[handle] = lastVisibilityBits;
+ // We don't remove old handle from dirty list, just check for invalid ones
+ if (lastNode.dirtyAABB)
+ m_DirtyAABBList.push_back(handle);
+
+ Renderer* swapRenderer = static_cast<Renderer*>(lastNode.renderer);
+ swapRenderer->NotifySceneHandleChange(handle);
+ }
+ m_RendererNodes.pop_back();
+ m_BoundingBoxes.pop_back();
+ m_VisibilityBits.pop_back();
+ return renderer;
+}
+
+#if UNITY_EDITOR
+
+unsigned Scene::GetUmbraDataSize ()
+{
+ if (!m_UmbraTome.HasTome())
+ return 0;
+
+ return UMBRA_TOME_METHOD(m_UmbraTome, getSize());
+}
+
+bool Scene::IsPositionInPVSVolume (const Vector3f& position)
+{
+ if (m_UmbraQuery == NULL)
+ return false;
+
+ ////@TODO: This is no longer working!
+
+ return true;
+
+// Umbra::Query::ErrorCode e = m_UmbraQuery->queryPointVisibility(m_QueryMode, NULL, NULL, (Umbra::Vector3&)position);
+// return e == Umbra::Query::ERROR_OK;
+}
+
+#endif
+
+void Scene::SetOcclusionPortalEnabled (unsigned int portalIndex, bool enabled)
+{
+ if (m_UmbraQuery == NULL)
+ return;
+
+ if (portalIndex >= UMBRA_TOME_METHOD(m_UmbraTome, getGateStateSize()))
+ {
+ ErrorString("Invalid portal index");
+ return;
+ }
+
+ Umbra::GateStateVector gateVector (m_GateState, 0, false);
+ gateVector.setState(portalIndex, enabled);
+}
+
+
+void Scene::RecalculateDirtyBounds()
+{
+ int dirtyCount = m_DirtyAABBList.size();
+ for (int i = 0; i < dirtyCount; ++i)
+ {
+ SceneHandle handle = m_DirtyAABBList[i];
+ // List may have invalid entries so check range/dirty flag
+ if (handle < m_RendererNodes.size())
+ {
+ SceneNode& node = m_RendererNodes[handle];
+ if (node.dirtyAABB)
+ {
+ node.renderer->GetWorldAABB(m_BoundingBoxes[handle]);
+ node.dirtyAABB = false;
+ }
+ }
+ }
+ m_DirtyAABBList.resize_uninitialized(0);
+}
+
+void Scene::SetPreventAddRemoveRenderer(bool enable)
+{
+ // Culling can be nested, so we need a count to disable changes to scene
+ m_PreventAddRemoveRenderer += enable ? 1 : -1;
+}
+
+void Scene::NotifyVisible (const CullingOutput& visibleObjects)
+{
+ // Update visibility bits for static objects
+ for (int i = 0; i < visibleObjects.visible[kStaticRenderers].size; ++i)
+ {
+ int index = visibleObjects.visible[kStaticRenderers].indices[i];
+ m_VisibilityBits[index] |= kVisibleCurrentFrame;
+ }
+
+ // Update visibility bits for dynamic objects
+ size_t offset = GetStaticObjectCount();
+ for (int i = 0; i < visibleObjects.visible[kDynamicRenderer].size; ++i)
+ {
+ int index = visibleObjects.visible[kDynamicRenderer].indices[i] + offset;
+ m_VisibilityBits[index] |= kVisibleCurrentFrame;
+ }
+
+ // We prevent changes to scene here and in OnWillRenderObject(), case 445226.
+ // Since array indices and pointers must stay valid, adding nodes is not allowed.
+ // We disable nodes instead of removing them, then remove them later.
+ SetPreventAddRemoveRenderer(true);
+ int nodeCount = m_RendererNodes.size();
+ for (int i = 0; i < nodeCount; ++i)
+ {
+ SceneNode& node = m_RendererNodes[i];
+ UInt8& vbits = m_VisibilityBits[i];
+ if (vbits == kVisibleCurrentFrame)
+ {
+ node.renderer->RendererBecameVisible();
+ vbits |= kBecameVisibleCalled;
+ }
+ }
+ SetPreventAddRemoveRenderer(false);
+}
+
+void Scene::NotifyInvisible ()
+{
+ // Happens after rendering, so modifying scene is not a problem.
+ int nodeCount = m_RendererNodes.size();
+ for (int i = 0; i < nodeCount; ++i)
+ {
+ SceneNode& node = m_RendererNodes[i];
+ UInt8& vbits = m_VisibilityBits[i];
+ if (vbits == kVisiblePreviousFrame)
+ {
+ node.renderer->RendererBecameInvisible();
+ }
+ // Roll visibility over to next frame
+ vbits = (vbits & kVisibleCurrentFrame) ? kVisiblePreviousFrame : 0;
+ }
+}
+
+void Scene::BeginCameraRender ()
+{
+ // Prepare Static SceneNode array
+ if (m_RequestStaticPVSRebuild)
+ {
+ m_RequestStaticPVSRebuild = false;
+ InitializeUmbra();
+ }
+}
+
+void Scene::EndCameraRender ()
+{
+ // Removal is done after rendering since we keep pointers around to nodes and AABBs.
+ // We do it in reverse order to avoid moving the last entries before deleting them.
+ // Do not change this without careful thinking as it is easy to break...
+ if (!m_PendingRemoval.empty())
+ {
+ std::sort(m_PendingRemoval.begin(), m_PendingRemoval.end());
+#if DEBUGMODE
+ int validateUnique = -1;
+#endif
+ for (int i=m_PendingRemoval.size()-1; i >= 0;i--)
+ {
+#if DEBUGMODE
+ Assert(validateUnique != m_PendingRemoval[i]);
+ validateUnique = m_PendingRemoval[i];
+#endif
+
+ RemoveRenderer(m_PendingRemoval[i]);
+ }
+ m_PendingRemoval.clear();
+ }
+}
+
+
+
+Scene& GetScene ()
+{
+ return *gScene;
+}
+
+void Scene::InitializeClass ()
+{
+ Assert(gScene == NULL);
+ gScene = new Scene ();
+}
+
+void Scene::CleanupClass ()
+{
+ Assert(gScene != NULL);
+ delete gScene;
+ gScene = NULL;
+}
+
+
+} // namespace Unity
diff --git a/Runtime/Camera/UnityScene.h b/Runtime/Camera/UnityScene.h
new file mode 100644
index 0000000..51bb4ba
--- /dev/null
+++ b/Runtime/Camera/UnityScene.h
@@ -0,0 +1,154 @@
+#ifndef UNITYSCENE_H
+#define UNITYSCENE_H
+
+#include "Runtime/Geometry/AABB.h"
+#include "IntermediateRenderer.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "SceneNode.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "UmbraTomeData.h"
+
+struct SceneCullingParameters;
+struct CullingParameters;
+struct CullingOutput;
+class BaseRenderer;
+class OcclusionPortal;
+class Renderer;
+class IntermediateRenderer;
+class LODGroupManager;
+class MinMaxAABB;
+class Texture2D;
+
+namespace Unity { class Component; class Culler; }
+namespace Umbra { class QueryExt; class Tome; class CameraTransform; class Visibility; }
+
+typedef int UmbraInt32;
+
+// Function called when a node is determined to be visible.
+//typedef bool CullFunction( void* user, const SceneNode& node, const AABB& aabb, float lodFade );
+
+namespace Unity
+{
+
+// Simple dummy for the scene class, used by the cullers.
+class Scene
+{
+public:
+
+ Scene ();
+ ~Scene ();
+
+ void NotifyVisible(const CullingOutput& visibleObjects);
+ void NotifyInvisible();
+
+ void BeginCameraRender();
+ void EndCameraRender();
+
+ // Adds/removes from the scene
+ SceneHandle AddRenderer (Renderer *renderer);
+ BaseRenderer* RemoveRenderer (SceneHandle handle);
+
+ // Functions to set information about renderers
+ void SetDirtyAABB (SceneHandle handle) { SceneNode& node = m_RendererNodes[handle]; if (!node.dirtyAABB) { m_DirtyAABBList.push_back(handle); node.dirtyAABB = true; } }
+ void SetRendererAABB (SceneHandle handle, const AABB& aabb) { m_BoundingBoxes[handle] = aabb; m_RendererNodes[handle].dirtyAABB = false; }
+ void SetRendererLayer (SceneHandle handle, UInt32 layer) { m_RendererNodes[handle].layer = layer; }
+ void SetRendererLODGroup (SceneHandle handle, int group) { m_RendererNodes[handle].lodGroup = group; }
+ void SetRendererLODIndexMask (SceneHandle handle, UInt32 mask) { m_RendererNodes[handle].lodIndexMask = mask; }
+ void SetRendererNeedsCullCallback (SceneHandle handle, bool flag) { m_RendererNodes[handle].needsCullCallback = flag; }
+
+ // Const access only to SceneNode and AABB!
+ // Scene needs to be notified about changes and some data is private
+ const SceneNode& GetRendererNode (SceneHandle handle) { return m_RendererNodes[handle]; }
+ const AABB& GetRendererAABB (SceneHandle handle) { return m_BoundingBoxes[handle]; }
+
+ const SceneNode* GetStaticSceneNodes () const;
+ const SceneNode* GetDynamicSceneNodes () const;
+
+ const AABB* GetStaticBoundingBoxes () const;
+ const AABB* GetDynamicBoundingBoxes () const;
+
+ size_t GetRendererNodeCount () const { return m_RendererNodes.size(); }
+ size_t GetStaticObjectCount () const;
+ size_t GetDynamicObjectCount () const;
+ size_t GetIntermediateObjectCount () const;
+
+ void RecalculateDirtyBounds();
+
+ // Intermediate nodes
+ void ClearIntermediateRenderers();
+ IntermediateRenderers& GetIntermediateRenderers () { return m_IntermediateNodes; }
+
+ void SetOcclusionPortalEnabled (unsigned int portalIndex, bool enabled);
+
+
+ //@TODO: REview this function
+ void SetPreventAddRemoveRenderer(bool enable);
+
+
+ #if DEBUGMODE
+ bool HasNodeForRenderer( const BaseRenderer* r );
+ #endif
+
+ #if UNITY_EDITOR
+ void GetUmbraDebugLines (const CullingParameters& cullingParameters, dynamic_array<Vector3f>& lines, bool targetCells);
+
+ unsigned GetUmbraDataSize ();
+
+ bool IsPositionInPVSVolume (const Vector3f& pos);
+
+ #endif
+
+ Umbra::QueryExt* GetUmbraQuery () { return m_UmbraQuery; }
+ int GetNumRenderers() { return m_RendererNodes.size(); }
+
+ const UmbraTomeData& GetUmbraTome () { return m_UmbraTome; }
+
+ void CleanupPVSAndRequestRebuild ();
+
+ static void InitializeClass ();
+ static void CleanupClass ();
+
+private:
+ void InitializeUmbra();
+ void CleanupUmbra ();
+ void CleanupUmbraNodesAndQuery ();
+
+ SceneHandle AddRendererInternal (Renderer *renderer, int layer, const AABB& aabb);
+
+ bool IsDirtyAABB (SceneHandle handle) const { return m_RendererNodes[handle].dirtyAABB; }
+
+ IntermediateRenderers m_IntermediateNodes;
+ dynamic_array<SceneHandle> m_PendingRemoval;
+
+ enum
+ {
+ kVisibleCurrentFrame = 1 << 0,
+ kVisiblePreviousFrame = 1 << 1,
+ kBecameVisibleCalled = 1 << 2
+ };
+
+ // These arrays are always kept in sync
+ dynamic_array<SceneNode> m_RendererNodes;
+ dynamic_array<AABB> m_BoundingBoxes;
+ dynamic_array<UInt8> m_VisibilityBits;
+
+ // Other data
+ dynamic_array<SceneHandle> m_DirtyAABBList;
+
+ Umbra::QueryExt* m_UmbraQuery;
+ UInt8* m_GateState;
+
+
+ UmbraTomeData m_UmbraTome;
+
+ int m_PreventAddRemoveRenderer;
+ bool m_RequestStaticPVSRebuild;
+};
+
+Scene& GetScene ();
+
+
+} // namespace Unity
+
+
+#endif