summaryrefslogtreecommitdiff
path: root/Runtime/Camera/Shadows.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Camera/Shadows.cpp')
-rw-r--r--Runtime/Camera/Shadows.cpp1227
1 files changed, 1227 insertions, 0 deletions
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