using UnityEngine; using System.Collections; using UnityEngine.Rendering; using UnityEngine.Profiling; using System.Collections.Generic; using System; namespace UnityEngine.ScriptableRenderLoop { [System.Serializable] public struct ShadowSettings { public bool enabled; public int shadowAtlasWidth; public int shadowAtlasHeight; public float maxShadowDistance; public int directionalLightCascadeCount; public Vector3 directionalLightCascades; public static ShadowSettings Default { get { ShadowSettings settings; settings.enabled = true; settings.shadowAtlasHeight = settings.shadowAtlasWidth = 4096; settings.directionalLightCascadeCount = 1; settings.directionalLightCascades = new Vector3 (0.05F, 0.2F, 0.3F); settings.directionalLightCascadeCount = 4; settings.maxShadowDistance = 1000.0F; return settings; } } } public struct InputShadowLightData { public int lightIndex; public int shadowResolution; } public struct ShadowLight { public int shadowSliceIndex; public int shadowSliceCount; } public struct ShadowSliceData { public Matrix4x4 shadowTransform; public int atlasX; public int atlasY; public int shadowResolution; } public struct ShadowOutput { public ShadowSliceData[] shadowSlices; public ShadowLight[] shadowLights; public Vector4[] directionalShadowSplitSphereSqr; public int GetShadowSliceCountLightIndex(int lightIndex) { return shadowLights[lightIndex].shadowSliceCount; } public int GetShadowSliceIndex(int lightIndex, int sliceIndex) { if (sliceIndex >= shadowLights[lightIndex].shadowSliceCount) throw new System.IndexOutOfRangeException (); return shadowLights[lightIndex].shadowSliceIndex + sliceIndex; } } public struct ShadowRenderPass : IDisposable { ShadowSettings m_Settings; [NonSerialized] bool m_bFailedToPackLastTime; int m_ShadowTexName; const int kDepthBuffer = 24; public ShadowRenderPass(ShadowSettings settings) { m_Settings = settings; m_bFailedToPackLastTime = false; m_ShadowTexName = Shader.PropertyToID("g_tShadowBuffer"); } public void Dispose() { } struct AtlasEntry { public AtlasEntry(int splitIndex, int lightIndex) { this.splitIndex = splitIndex; this.lightIndex = lightIndex; } public int splitIndex; public int lightIndex; } int CalculateNumShadowSplits(int index, ActiveLight[] lights) { LightType lightType = lights [index].lightType; if (lightType == LightType.Spot) return 1; if (lightType == LightType.Directional) return m_Settings.directionalLightCascadeCount; return 6; } static public void ClearPackedShadows(ActiveLight[] lights, out ShadowOutput packedShadows) { packedShadows.directionalShadowSplitSphereSqr = null; packedShadows.shadowSlices = null; packedShadows.shadowLights = new ShadowLight[lights.Length]; } //--------------------------------------------------------------------------------------------------------------------------------------------------- bool AutoPackLightsIntoShadowTexture(List shadowLights, ActiveLight[] lights, out ShadowOutput packedShadows) { Dictionary activeShadowLights = new Dictionary(); List shadowIndices = new List(); //@TODO: Disallow multiple directional lights for (int i = 0; i < shadowLights.Count; i++) { shadowIndices.Add (shadowLights[i].lightIndex); activeShadowLights[shadowLights[i].lightIndex] = shadowLights[i]; } // World's stupidest sheet packer: // 1. Sort all lights from largest to smallest // 2. In a left->right, top->bottom pattern, fill quads until you reach the edge of the texture // 3. Move position to x=0, y=bottomOfFirstTextureInThisRow // 4. Goto 2. // Yes, this will produce holes as the quads shrink, but it's good enough for now. I'll work on this more later to fill the gaps. // Sort all lights from largest to smallest shadowIndices.Sort( delegate( int l1, int l2 ) { int nCompare = 0; // Sort shadow-casting lights by shadow resolution nCompare = activeShadowLights[l1].shadowResolution.CompareTo(activeShadowLights[l2].shadowResolution); // Sort by shadow size if ( nCompare == 0 ) // Same, so sort by range to stabilize sort results nCompare = lights[l1].range.CompareTo( lights[l2].range ); // Sort by shadow size if ( nCompare == 0 ) // Still same, so sort by instance ID to stabilize sort results nCompare = lights[l1].light.GetInstanceID().CompareTo( lights[l2].light.GetInstanceID() ); return nCompare; } ); // Start filling lights into texture List requestedPages = new List(); packedShadows.shadowLights = new ShadowLight[lights.Length]; for (int i = 0; i != shadowIndices.Count;i++) { int numShadowSplits = CalculateNumShadowSplits(shadowIndices[i], lights); packedShadows.shadowLights[shadowIndices [i]].shadowSliceCount = numShadowSplits; packedShadows.shadowLights[shadowIndices [i]].shadowSliceIndex = requestedPages.Count; for (int s = 0; s < numShadowSplits; s++) requestedPages.Add (new AtlasEntry (requestedPages.Count, shadowIndices[i])); } int nCurrentX = 0; int nCurrentY = -1; int nNextY = 0; packedShadows.shadowSlices = new ShadowSliceData[requestedPages.Count]; packedShadows.directionalShadowSplitSphereSqr = new Vector4[4]; foreach (AtlasEntry entry in requestedPages) { int shadowResolution = activeShadowLights[entry.lightIndex].shadowResolution; // Check if first texture is too wide if ( nCurrentY == -1 ) { if ( ( shadowResolution > m_Settings.shadowAtlasWidth ) || ( shadowResolution > m_Settings.shadowAtlasHeight ) ) { Debug.LogError( "ERROR! Shadow packer ran out of space in the " + m_Settings.shadowAtlasWidth + "x" + m_Settings.shadowAtlasHeight + " texture!\n\n" ); m_bFailedToPackLastTime = true; ClearPackedShadows (lights, out packedShadows); return false; } } // Goto next scanline if ( ( nCurrentY == -1 ) || ( ( nCurrentX + shadowResolution ) > m_Settings.shadowAtlasWidth ) ) { nCurrentX = 0; nCurrentY = nNextY; nNextY += shadowResolution; } // Check if we've run out of space if ( ( nCurrentY + shadowResolution ) > m_Settings.shadowAtlasHeight ) { Debug.LogError( "ERROR! Shadow packer ran out of space in the " + m_Settings.shadowAtlasWidth + "x" + m_Settings.shadowAtlasHeight + " texture!\n\n" ); m_bFailedToPackLastTime = true; ClearPackedShadows (lights, out packedShadows); return false; } // Save location to light packedShadows.shadowSlices[entry.splitIndex].atlasX = nCurrentX; packedShadows.shadowSlices[entry.splitIndex].atlasY = nCurrentY; packedShadows.shadowSlices[entry.splitIndex].shadowResolution = shadowResolution; // Move ahead nCurrentX += shadowResolution; //Debug.Log( "Sheet packer: " + vl.m_cachedLight.name + " ( " + vl.m_shadowX + ", " + vl.m_shadowY + " ) " + vl.m_shadowResolution + "\n\n" ); } if ( m_bFailedToPackLastTime ) { m_bFailedToPackLastTime = false; Debug.Log( "SUCCESS! Shadow packer can now fit all lights into the " + m_Settings.shadowAtlasWidth + "x" + m_Settings.shadowAtlasHeight + " texture!\n\n" ); } return requestedPages.Count != 0; } static List GetInputShadowLightData(CullResults cullResults) { var shadowCasters = new List (); ActiveLight[] lights = cullResults.culledLights; int directionalLightCount = 0; for (int i = 0; i < lights.Length; i++) { //@TODO: ignore baked. move this logic to c++... if (lights[i].light.shadows != LightShadows.None) { // Only a single directional shadow casting light is supported if (lights [i].lightType == LightType.Directional) { directionalLightCount++; if (directionalLightCount != 1) continue; } AdditionalLightData additionalLight = lights [i].light.GetComponent (); InputShadowLightData light; light.lightIndex = i; light.shadowResolution = AdditionalLightData.GetShadowResolution(additionalLight); shadowCasters.Add (light); } } return shadowCasters; } public void UpdateCullingParameters(ref CullingParameters parameters) { parameters.shadowDistance = Mathf.Min (m_Settings.maxShadowDistance, parameters.shadowDistance); } public void Render(RenderLoop loop, CullResults cullResults, out ShadowOutput packedShadows) { if (!m_Settings.enabled) { ClearPackedShadows(cullResults.culledLights, out packedShadows); } // Pack all shadow quads into the texture if ( !AutoPackLightsIntoShadowTexture(GetInputShadowLightData(cullResults), cullResults.culledLights, out packedShadows) ) { // No shadowing lights found, so skip all rendering return; } RenderPackedShadows (loop, cullResults, ref packedShadows); } //--------------------------------------------------------------------------------------------------------------------------------------------------- // Render shadows //--------------------------------------------------------------------------------------------------------------------------------------------------- void RenderPackedShadows(RenderLoop loop, CullResults cullResults, ref ShadowOutput packedShadows) { var setRenderTargetCommandBuffer = new CommandBuffer (); setRenderTargetCommandBuffer.name = "Render packed shadows"; setRenderTargetCommandBuffer.GetTemporaryRT (m_ShadowTexName, m_Settings.shadowAtlasWidth, m_Settings.shadowAtlasHeight, kDepthBuffer, FilterMode.Bilinear, RenderTextureFormat.Shadowmap, RenderTextureReadWrite.Linear); setRenderTargetCommandBuffer.SetRenderTarget (new RenderTargetIdentifier (m_ShadowTexName)); setRenderTargetCommandBuffer.ClearRenderTarget (true, true, Color.green); loop.ExecuteCommandBuffer (setRenderTargetCommandBuffer); setRenderTargetCommandBuffer.Dispose (); ActiveLight[] activeLights = cullResults.culledLights; var shadowSlices = packedShadows.shadowSlices; // Render each light's shadow buffer into a subrect of the shared depth texture for ( int lightIndex = 0; lightIndex < packedShadows.shadowLights.Length; lightIndex++ ) { int shadowSliceCount = packedShadows.shadowLights[lightIndex].shadowSliceCount; if (shadowSliceCount == 0) continue; Profiler.BeginSample ("Shadows.GetShadowCasterBounds"); Bounds bounds; if (!cullResults.GetShadowCasterBounds (lightIndex, out bounds)) { Profiler.EndSample (); return; } Profiler.EndSample (); Profiler.BeginSample ("Shadows.DrawShadows"); Matrix4x4 proj; Matrix4x4 view; LightType lightType = activeLights[lightIndex].lightType; Vector3 lightDirection = activeLights[lightIndex].light.transform.forward; var shadowNearClip = activeLights[lightIndex].light.shadowNearPlane; int shadowSliceIndex = packedShadows.GetShadowSliceIndex (lightIndex, 0); if (lightType == LightType.Spot) { DrawShadowsSettings settings = new DrawShadowsSettings(cullResults, lightIndex); bool needRendering = cullResults.ComputeSpotShadowsMatricesAndCullingPrimitives(lightIndex, out view, out proj, out settings.splitData); SetupShadowSplitMatrices(ref packedShadows.shadowSlices[shadowSliceIndex], proj, view); if (needRendering) RenderShadowSplit(ref shadowSlices[shadowSliceIndex], lightDirection, proj, view, ref loop, settings); } else if (lightType == LightType.Directional) { Vector3 splitRatio = m_Settings.directionalLightCascades; for (int s = 0; s < 4; ++s) packedShadows.directionalShadowSplitSphereSqr[s] = new Vector4 (0, 0, 0, float.NegativeInfinity); for (int s = 0; s < shadowSliceCount; ++s, shadowSliceIndex++) { var settings = new DrawShadowsSettings(cullResults, lightIndex); var shadowResolution = shadowSlices [shadowSliceIndex].shadowResolution; bool needRendering = cullResults.ComputeDirectionalShadowMatricesAndCullingPrimitives(lightIndex, s, shadowSliceCount, splitRatio, shadowResolution, shadowNearClip, out view, out proj, out settings.splitData); packedShadows.directionalShadowSplitSphereSqr[s] = settings.splitData.cullingSphere; packedShadows.directionalShadowSplitSphereSqr[s].w *= packedShadows.directionalShadowSplitSphereSqr[s].w; SetupShadowSplitMatrices(ref shadowSlices[shadowSliceIndex], proj, view); if (needRendering) RenderShadowSplit(ref shadowSlices[shadowSliceIndex], lightDirection, proj, view, ref loop, settings); } } else if (lightType == LightType.Point) { for(int s = 0; s < shadowSliceCount; ++s, shadowSliceIndex++) { DrawShadowsSettings settings = new DrawShadowsSettings(cullResults, lightIndex); bool needRendering = cullResults.ComputePointShadowsMatricesAndCullingPrimitives(lightIndex, (CubemapFace)s, 2.0f, out view, out proj, out settings.splitData); SetupShadowSplitMatrices(ref shadowSlices[shadowSliceIndex], proj, view); if (needRendering) RenderShadowSplit(ref shadowSlices[shadowSliceIndex], lightDirection, proj, view, ref loop, settings); } } Profiler.EndSample (); } } private void SetupShadowSplitMatrices(ref ShadowSliceData lightData, Matrix4x4 proj, Matrix4x4 view) { Matrix4x4 matScaleBias = Matrix4x4.identity; matScaleBias.m00 = 0.5f; matScaleBias.m11 = 0.5f; matScaleBias.m22 = 0.5f; matScaleBias.m03 = 0.5f; matScaleBias.m13 = 0.5f; matScaleBias.m23 = 0.5f; Matrix4x4 matTile = Matrix4x4.identity; matTile.m00 = (float)lightData.shadowResolution / (float)m_Settings.shadowAtlasWidth; matTile.m11 = (float)lightData.shadowResolution / (float)m_Settings.shadowAtlasHeight; matTile.m03 = (float)lightData.atlasX / (float)m_Settings.shadowAtlasWidth; matTile.m13 = (float)lightData.atlasY / (float)m_Settings.shadowAtlasHeight; lightData.shadowTransform = matTile * matScaleBias * proj * view; } //--------------------------------------------------------------------------------------------------------------------------------------------------- private void RenderShadowSplit(ref ShadowSliceData slice, Vector3 lightDirection, Matrix4x4 proj, Matrix4x4 view, ref RenderLoop loop, DrawShadowsSettings settings) { var commandBuffer = new CommandBuffer(); commandBuffer.name = "ShadowSetup"; // Set viewport / matrices etc commandBuffer.SetViewport(new Rect(slice.atlasX, slice.atlasY, slice.shadowResolution, slice.shadowResolution)); //commandBuffer.ClearRenderTarget (true, true, Color.green); commandBuffer.SetGlobalVector("g_vLightDirWs", new Vector4(lightDirection.x, lightDirection.y, lightDirection.z)); commandBuffer.SetProjectionAndViewMatrices(proj, view); // commandBuffer.SetGlobalDepthBias (1.0F, 1.0F); loop.ExecuteCommandBuffer(commandBuffer); commandBuffer.Dispose(); // Render loop.DrawShadows(ref settings); } } }