diff options
Diffstat (limited to 'Runtime/Camera')
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, ¶ms.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, ¶ms.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, ¶ms.GetWritableMatrixParam(kShaderMatProjector)); + MultiplyMatrices4x4 (&settings.distance, &transform, ¶ms.GetWritableMatrixParam(kShaderMatProjectorDistance)); + MultiplyMatrices4x4 (&settings.clipping, &transform, ¶ms.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, ¶ms.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(), ¶ms.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*)¶ms.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 |