using System; using UnityEngine.Rendering; using UnityEngine.Rendering.PostProcessing; using UnityEngine.XR; namespace UnityEngine.Experimental.Rendering.LightweightPipeline { [Serializable] public class ShadowSettings { public bool enabled; public int shadowAtlasWidth; public int shadowAtlasHeight; public float maxShadowDistance; public int directionalLightCascadeCount; public Vector3 directionalLightCascades; public float directionalLightNearPlaneOffset; static ShadowSettings defaultShadowSettings = null; public static ShadowSettings Default { get { if (defaultShadowSettings == null) { defaultShadowSettings = new ShadowSettings(); defaultShadowSettings.enabled = true; defaultShadowSettings.shadowAtlasHeight = defaultShadowSettings.shadowAtlasWidth = 4096; defaultShadowSettings.directionalLightCascadeCount = 1; defaultShadowSettings.directionalLightCascades = new Vector3(0.05F, 0.2F, 0.3F); defaultShadowSettings.directionalLightCascadeCount = 4; defaultShadowSettings.directionalLightNearPlaneOffset = 5; defaultShadowSettings.maxShadowDistance = 1000.0F; } return defaultShadowSettings; } } } public struct ShadowSliceData { public Matrix4x4 shadowTransform; public int atlasX; public int atlasY; public int shadowResolution; } public struct LightData { public int additionalPixelLightsCount; public int vertexLightsCount; public int mainLightIndex; public bool hasAdditionalLights; public bool shadowsRendered; } public class LightweightPipeline : RenderPipeline { private readonly LightweightPipelineAsset m_Asset; // Max amount of visible lights. This controls the lights constant buffers in shader but not the max shaded lights. // Lights are set per-object and the max shaded lights for each object are controlled by the max pixel lights in pipeline asset and kMaxVertexLights. private static readonly int kMaxVisibleLights = 16; private static readonly int kMaxPerObjectLights = 4; private Vector4[] m_LightPositions = new Vector4[kMaxVisibleLights]; private Vector4[] m_LightColors = new Vector4[kMaxVisibleLights]; private Vector4[] m_LightAttenuations = new Vector4[kMaxVisibleLights]; private Vector4[] m_LightSpotDirections = new Vector4[kMaxVisibleLights]; private Camera m_CurrCamera = null; private int m_LightIndicesCount = 0; private ComputeBuffer m_LightIndexListBuffer; private static readonly int kMaxCascades = 4; private int m_ShadowCasterCascadesCount = kMaxCascades; private int m_ShadowMapTexture; private int m_CameraColorTexture; private int m_CameraDepthTexture; private RenderTargetIdentifier m_ShadowMapRT; private RenderTargetIdentifier m_CameraColorRT; private RenderTargetIdentifier m_CameraDepthRT; private bool m_RenderToIntermediateTarget = false; private bool m_IntermediateTextureArray = false; private const int kShadowDepthBufferBits = 16; private const int kCameraDepthBufferBits = 32; private Vector4[] m_DirectionalShadowSplitDistances = new Vector4[kMaxCascades]; private ShadowSettings m_ShadowSettings = ShadowSettings.Default; private ShadowSliceData[] m_ShadowSlices = new ShadowSliceData[kMaxCascades]; private static readonly ShaderPassName m_LitPassName = new ShaderPassName("LightweightForward"); private static readonly ShaderPassName m_UnlitPassName = new ShaderPassName("SRPDefaultUnlit"); private RenderTextureFormat m_ColorFormat; private PostProcessRenderContext m_PostProcessRenderContext; private CameraComparer m_CameraComparer = new CameraComparer(); private Mesh m_BlitQuad = null; private Material m_BlitMaterial = null; private int m_BlitTexID = Shader.PropertyToID("_BlitTex"); public LightweightPipeline(LightweightPipelineAsset asset) { m_Asset = asset; BuildShadowSettings(); PerFrameBuffer._GlossyEnvironmentColor = Shader.PropertyToID("_GlossyEnvironmentColor"); PerFrameBuffer._AttenuationTexture = Shader.PropertyToID("_AttenuationTexture"); PerCameraBuffer._MainLightPosition = Shader.PropertyToID("_MainLightPosition"); PerCameraBuffer._MainLightColor = Shader.PropertyToID("_MainLightColor"); PerCameraBuffer._MainLightAttenuationParams = Shader.PropertyToID("_MainLightAttenuationParams"); PerCameraBuffer._MainLightSpotDir = Shader.PropertyToID("_MainLightSpotDir"); PerCameraBuffer._AdditionalLightCount = Shader.PropertyToID("_AdditionalLightCount"); PerCameraBuffer._AdditionalLightPosition = Shader.PropertyToID("_AdditionalLightPosition"); PerCameraBuffer._AdditionalLightColor = Shader.PropertyToID("_AdditionalLightColor"); PerCameraBuffer._AdditionalLightAttenuationParams = Shader.PropertyToID("_AdditionalLightAttenuationParams"); PerCameraBuffer._AdditionalLightSpotDir = Shader.PropertyToID("_AdditionalLightSpotDir"); m_ShadowMapTexture = Shader.PropertyToID("_ShadowMap"); m_CameraColorTexture = Shader.PropertyToID("_CameraRT"); m_CameraDepthTexture = Shader.PropertyToID("_CameraDepthTexture"); m_ShadowMapRT = new RenderTargetIdentifier(m_ShadowMapTexture); m_CameraColorRT = new RenderTargetIdentifier(m_CameraColorTexture); m_CameraDepthRT = new RenderTargetIdentifier(m_CameraDepthTexture); m_PostProcessRenderContext = new PostProcessRenderContext(); // Let engine know we have MSAA on for cases where we support MSAA backbuffer if (QualitySettings.antiAliasing != m_Asset.MSAASampleCount) QualitySettings.antiAliasing = m_Asset.MSAASampleCount; Shader.globalRenderPipeline = "LightweightPipeline"; m_BlitQuad = LightweightUtils.CreateQuadMesh(false); m_BlitMaterial = new Material(m_Asset.BlitShader) { hideFlags = HideFlags.HideAndDontSave }; } public override void Dispose() { base.Dispose(); Shader.globalRenderPipeline = ""; if (m_LightIndexListBuffer != null) { m_LightIndexListBuffer.Dispose(); m_LightIndexListBuffer = null; m_LightIndicesCount = 0; } } CullResults m_CullResults; public override void Render(ScriptableRenderContext context, Camera[] cameras) { base.Render(context, cameras); bool stereoEnabled = XRSettings.isDeviceActive; // TODO: This is at the moment required for all pipes. We should not implicitly change user project settings // instead this should be forced when using SRP, since all SRP use linear lighting. GraphicsSettings.lightsUseLinearIntensity = true; SetupPerFrameShaderConstants(ref context); // Sort cameras array by camera depth Array.Sort(cameras, m_CameraComparer); foreach (Camera camera in cameras) { m_CurrCamera = camera; ScriptableCullingParameters cullingParameters; if (!CullResults.GetCullingParameters(m_CurrCamera, stereoEnabled, out cullingParameters)) continue; cullingParameters.shadowDistance = Mathf.Min(m_ShadowSettings.maxShadowDistance, m_CurrCamera.farClipPlane); #if UNITY_EDITOR // Emit scene view UI if (camera.cameraType == CameraType.SceneView) ScriptableRenderContext.EmitWorldGeometryForSceneView(camera); #endif CullResults.Cull(ref cullingParameters, context, ref m_CullResults); VisibleLight[] visibleLights = m_CullResults.visibleLights.ToArray(); LightData lightData; InitializeLightData(visibleLights, out lightData); ShadowPass(visibleLights, ref context, ref lightData); ForwardPass(visibleLights, ref context, ref lightData, stereoEnabled); context.Submit(); } } private void ShadowPass(VisibleLight[] visibleLights, ref ScriptableRenderContext context, ref LightData lightData) { if (m_Asset.AreShadowsEnabled() && lightData.mainLightIndex != -1) { VisibleLight mainLight = visibleLights[lightData.mainLightIndex]; if (mainLight.light.shadows != LightShadows.None) lightData.shadowsRendered = RenderShadows (ref m_CullResults, ref mainLight, lightData.mainLightIndex, ref context); } } private void ForwardPass(VisibleLight[] visibleLights, ref ScriptableRenderContext context, ref LightData lightData, bool stereoEnabled) { PostProcessLayer postProcessLayer; RenderingConfiguration renderingConfig = SetupRendering(out postProcessLayer); bool postProcessEnabled = LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.PostProcess); CommandBuffer cmd = CommandBufferPool.Get("SetupShaderConstants"); SetupShaderLightConstants(cmd, visibleLights, ref lightData, ref m_CullResults, ref context); SetShaderKeywords(cmd, ref lightData, visibleLights); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); // Setup camera matrices context.SetupCameraProperties(m_CurrCamera, stereoEnabled); RendererConfiguration rendererSettings = GetRendererSettings(ref lightData); BeginForwardRendering(ref context, renderingConfig); RenderOpaques(ref context, rendererSettings); context.DrawSkybox(m_CurrCamera); if (postProcessEnabled) RenderPostProcess(ref context, postProcessLayer, true); RenderTransparents(ref context, rendererSettings); if (postProcessEnabled) RenderPostProcess(ref context, postProcessLayer, false); EndForwardRendering(ref context, renderingConfig); // Release temporary RT var discardRT = CommandBufferPool.Get(); discardRT.ReleaseTemporaryRT(m_ShadowMapTexture); discardRT.ReleaseTemporaryRT(m_CameraColorTexture); discardRT.ReleaseTemporaryRT(m_CameraDepthTexture); context.ExecuteCommandBuffer(discardRT); CommandBufferPool.Release(discardRT); } private void RenderOpaques(ref ScriptableRenderContext context, RendererConfiguration settings) { var opaqueDrawSettings = new DrawRendererSettings(m_CurrCamera, m_LitPassName); opaqueDrawSettings.sorting.flags = SortFlags.CommonOpaque; opaqueDrawSettings.rendererConfiguration = settings; var opaqueFilterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque }; context.DrawRenderers(m_CullResults.visibleRenderers, ref opaqueDrawSettings, opaqueFilterSettings); } private void RenderTransparents(ref ScriptableRenderContext context, RendererConfiguration settings) { var transparentSettings = new DrawRendererSettings(m_CurrCamera, m_LitPassName); transparentSettings.SetShaderPassName(1, m_UnlitPassName); transparentSettings.sorting.flags = SortFlags.CommonTransparent; transparentSettings.rendererConfiguration = settings; var transparentFilterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.transparent }; context.DrawRenderers(m_CullResults.visibleRenderers, ref transparentSettings, transparentFilterSettings); } private void BuildShadowSettings() { m_ShadowSettings = ShadowSettings.Default; m_ShadowSettings.directionalLightCascadeCount = m_Asset.CascadeCount; m_ShadowSettings.shadowAtlasWidth = m_Asset.ShadowAtlasResolution; m_ShadowSettings.shadowAtlasHeight = m_Asset.ShadowAtlasResolution; m_ShadowSettings.maxShadowDistance = m_Asset.ShadowDistance; switch (m_ShadowSettings.directionalLightCascadeCount) { case 1: m_ShadowSettings.directionalLightCascades = new Vector3(1.0f, 0.0f, 0.0f); break; case 2: m_ShadowSettings.directionalLightCascades = new Vector3(m_Asset.Cascade2Split, 1.0f, 0.0f); break; default: m_ShadowSettings.directionalLightCascades = m_Asset.Cascade4Split; break; } } private RenderingConfiguration SetupRendering(out PostProcessLayer postProcessLayer) { RenderingConfiguration renderingConfig = (XRSettings.enabled) ? RenderingConfiguration.Stereo : RenderingConfiguration.None; if (XRSettings.eyeTextureDesc.dimension == TextureDimension.Tex2DArray) renderingConfig |= RenderingConfiguration.IntermediateTextureArray; m_ColorFormat = m_CurrCamera.allowHDR ? RenderTextureFormat.ARGBHalf : RenderTextureFormat.ARGB32; postProcessLayer = m_CurrCamera.GetComponent(); bool intermediateTexture = false; if (postProcessLayer != null && postProcessLayer.enabled) { renderingConfig |= RenderingConfiguration.PostProcess; intermediateTexture = true; } // When post process is enabled we disable msaa due to lack of depth resolve // One can still use PostFX AA else if (m_CurrCamera.allowMSAA && m_Asset.MSAASampleCount > 1) { renderingConfig |= RenderingConfiguration.Msaa; intermediateTexture = !LightweightUtils.PlatformSupportsMSAABackBuffer(); } Rect cameraRect = m_CurrCamera.rect; if (cameraRect.x > 0.0f || cameraRect.y > 0.0f || cameraRect.width < 1.0f || cameraRect.height < 1.0f) intermediateTexture = true; else renderingConfig |= RenderingConfiguration.DefaultViewport; intermediateTexture |= (m_CurrCamera.targetTexture != null || m_Asset.RenderScale < 1.0f || m_CurrCamera.allowHDR); if (intermediateTexture && !LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.IntermediateTextureArray)) renderingConfig |= RenderingConfiguration.IntermediateTexture; return renderingConfig; } private void InitializeLightData(VisibleLight[] visibleLights, out LightData lightData) { int visibleLightsCount = visibleLights.Length; // kMaxPerObjectLights + 1 main light int maxSupportedPixelLights = Math.Min(m_Asset.MaxSupportedPixelLights, kMaxPerObjectLights + 1); int maxPixelLights = Math.Min(maxSupportedPixelLights, visibleLightsCount); if (maxPixelLights <= 1) { lightData.mainLightIndex = maxPixelLights - 1; lightData.additionalPixelLightsCount = 0; } else { lightData.mainLightIndex = GetMainLightIndex(visibleLights); lightData.additionalPixelLightsCount = maxPixelLights - 1; } lightData.vertexLightsCount = (m_Asset.SupportsVertexLight) ? Math.Min(visibleLightsCount - maxPixelLights, kMaxPerObjectLights) : 0; lightData.hasAdditionalLights = (lightData.additionalPixelLightsCount + lightData.vertexLightsCount) > 0; lightData.shadowsRendered = false; } // How main light is decided: // 1) If shadows are enabled and at least a shadow light is present: // Main light is a shadow light. Directional shadow light has priority over the other supported // shadow lights types. // Lightweight pipeline only supports 1 single directional shadow light. // Any additional pixel lights don't cast shadows. // // 2) If shadows are disabled or no shadow light is present then main light is the main brighest directional // 3) If neither a shadow light or main light is first visible light. private int GetMainLightIndex(VisibleLight[] lights) { bool shadowsEnabled = m_Asset.AreShadowsEnabled(); int mainDirectional = -1; int mainShadowLight = -1; for (int i = 0; i < lights.Length; ++i) { if (shadowsEnabled && LightweightUtils.IsSupportedShadowType (lights[i].lightType) && lights[i].light.shadows != LightShadows.None) { // Shadow Type priority: Soft > Hard > None if (mainShadowLight < 0 || lights[i].light.shadows > lights[mainShadowLight].light.shadows) mainShadowLight = i; } if (lights[i].lightType == LightType.Directional) { if (mainDirectional < 0 || lights[i].light.intensity > lights[mainDirectional].light.intensity) mainDirectional = i; } } if (mainShadowLight >= 0) { if (mainDirectional > 0 && lights[mainDirectional].light.shadows != LightShadows.None) return mainDirectional; else return mainShadowLight; } if (mainDirectional > 0) return mainDirectional; return 0; } private void InitializeLightConstants(VisibleLight[] lights, int lightIndex, out Vector4 lightPos, out Vector4 lightColor, out Vector4 lightSpotDir, out Vector4 lightAttenuationParams) { lightPos = Vector4.zero; lightColor = Color.black; lightAttenuationParams = new Vector4(0.0f, 1.0f, 0.0f, 0.0f); lightSpotDir = new Vector4(0.0f, 0.0f, 1.0f, 0.0f); // When no lights are available in the pipeline or maxPixelLights is set to 0 // In this case we want to initialize the lightData to default values and return if (lightIndex < 0) return; VisibleLight light = lights[lightIndex]; if (light.lightType == LightType.Directional) { Vector4 dir = -light.localToWorld.GetColumn(2); lightPos = new Vector4(dir.x, dir.y, dir.z, 0.0f); } else { Vector4 pos = light.localToWorld.GetColumn(3); lightPos = new Vector4(pos.x, pos.y, pos.z, 1.0f); } lightColor = light.finalColor; float rangeSq = light.range * light.range; float quadAtten = 0.0f; if (light.lightType != LightType.Directional) quadAtten = (m_Asset.AttenuationTexture != null) ? 1.0f : 25.0f / rangeSq; if (light.lightType == LightType.Spot) { Vector4 dir = light.localToWorld.GetColumn(2); lightSpotDir = new Vector4(-dir.x, -dir.y, -dir.z, 0.0f); float spotAngle = Mathf.Deg2Rad * light.spotAngle; float cosOuterAngle = Mathf.Cos(spotAngle * 0.5f); float cosInneAngle = Mathf.Cos(spotAngle * 0.25f); float angleRange = cosInneAngle - cosOuterAngle; lightAttenuationParams = new Vector4(cosOuterAngle, Mathf.Approximately(angleRange, 0.0f) ? 1.0f : angleRange, quadAtten, rangeSq); } else { lightSpotDir = new Vector4(0.0f, 0.0f, 1.0f, 0.0f); lightAttenuationParams = new Vector4(-1.0f, 1.0f, quadAtten, rangeSq); } } private void SetupPerFrameShaderConstants(ref ScriptableRenderContext context) { // When glossy reflections are OFF in the shader we set a constant color to use as indirect specular SphericalHarmonicsL2 ambientSH = RenderSettings.ambientProbe; Vector4 glossyEnvColor = new Vector4(ambientSH[0, 0], ambientSH[1, 0], ambientSH[2, 0]) * RenderSettings.reflectionIntensity; CommandBuffer cmd = CommandBufferPool.Get("SetupPerFrameConstants"); cmd.SetGlobalVector(PerFrameBuffer._GlossyEnvironmentColor, glossyEnvColor); if (m_Asset.AttenuationTexture != null) cmd.SetGlobalTexture(PerFrameBuffer._AttenuationTexture, m_Asset.AttenuationTexture); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release (cmd); } private void SetupShaderLightConstants(CommandBuffer cmd, VisibleLight[] lights, ref LightData lightData, ref CullResults cullResults, ref ScriptableRenderContext context) { // Main light has an optimized shader path for main light. This will benefit games that only care about a single light. // Lightweight pipeline also supports only a single shadow light, if available it will be the main light. if (lightData.mainLightIndex != -1) { SetupMainLightConstants (cmd, lights, lightData.mainLightIndex, ref context); if (lightData.shadowsRendered) SetupShadowShaderConstants (cmd, ref context, ref lights[lightData.mainLightIndex], m_ShadowCasterCascadesCount); } if (lightData.hasAdditionalLights) SetupAdditionalListConstants(cmd, lights, ref lightData, ref context); } private void SetupMainLightConstants(CommandBuffer cmd, VisibleLight[] lights, int lightIndex, ref ScriptableRenderContext context) { Vector4 lightPos, lightColor, lightSpotDir, lightAttenuationParams; InitializeLightConstants(lights, lightIndex, out lightPos, out lightColor, out lightSpotDir, out lightAttenuationParams); cmd.SetGlobalVector(PerCameraBuffer._MainLightPosition, lightPos); cmd.SetGlobalColor(PerCameraBuffer._MainLightColor, lightColor); cmd.SetGlobalVector(PerCameraBuffer._MainLightSpotDir, lightSpotDir); cmd.SetGlobalVector(PerCameraBuffer._MainLightAttenuationParams, lightAttenuationParams); } private void SetupAdditionalListConstants(CommandBuffer cmd, VisibleLight[] lights, ref LightData lightData, ref ScriptableRenderContext context) { int totalLightCount = lights.Length; int maxLights = Math.Min(kMaxVisibleLights, totalLightCount); int[] lightIndexMap = m_CullResults.GetLightIndexMap(); int lightIter = 0; for (int i = 0; i < totalLightCount; ++i) { if (i == lightData.mainLightIndex || lightIter >= maxLights) { lightIndexMap[i] = -1; continue; } InitializeLightConstants(lights, i, out m_LightPositions[lightIter], out m_LightColors[lightIter], out m_LightSpotDirections[lightIter], out m_LightAttenuations[lightIter]); lightIndexMap[i] = lightIter; lightIter++; } m_CullResults.SetLightIndexMap(lightIndexMap); cmd.SetGlobalVector(PerCameraBuffer._AdditionalLightCount, new Vector4 (lightData.additionalPixelLightsCount, lightData.vertexLightsCount, 0.0f, 0.0f)); cmd.SetGlobalVectorArray (PerCameraBuffer._AdditionalLightPosition, m_LightPositions); cmd.SetGlobalVectorArray (PerCameraBuffer._AdditionalLightColor, m_LightColors); cmd.SetGlobalVectorArray (PerCameraBuffer._AdditionalLightAttenuationParams, m_LightAttenuations); cmd.SetGlobalVectorArray (PerCameraBuffer._AdditionalLightSpotDir, m_LightSpotDirections); } private void SetupShadowShaderConstants(CommandBuffer cmd, ref ScriptableRenderContext context, ref VisibleLight shadowLight, int cascadeCount) { Vector3 shadowLightDir = Vector3.Normalize(shadowLight.localToWorld.GetColumn(2)); float bias = shadowLight.light.shadowBias * 0.1f; float normalBias = shadowLight.light.shadowNormalBias; float shadowResolution = m_ShadowSlices[0].shadowResolution; const int maxShadowCascades = 4; Matrix4x4[] shadowMatrices = new Matrix4x4[maxShadowCascades]; for (int i = 0; i < cascadeCount; ++i) shadowMatrices[i] = (cascadeCount >= i) ? m_ShadowSlices[i].shadowTransform : Matrix4x4.identity; // TODO: shadow resolution per cascade in case cascades endup being supported. float invShadowResolution = 1.0f / shadowResolution; float[] pcfKernel = { -0.5f * invShadowResolution, 0.5f * invShadowResolution, 0.5f * invShadowResolution, 0.5f * invShadowResolution, -0.5f * invShadowResolution, -0.5f * invShadowResolution, 0.5f * invShadowResolution, -0.5f * invShadowResolution }; cmd.SetGlobalMatrixArray("_WorldToShadow", shadowMatrices); cmd.SetGlobalVectorArray("_DirShadowSplitSpheres", m_DirectionalShadowSplitDistances); cmd.SetGlobalVector("_ShadowLightDirection", new Vector4(-shadowLightDir.x, -shadowLightDir.y, -shadowLightDir.z, 0.0f)); cmd.SetGlobalVector("_ShadowData", new Vector4(0.0f, bias, normalBias, 0.0f)); cmd.SetGlobalFloatArray("_PCFKernel", pcfKernel); } private void SetShaderKeywords(CommandBuffer cmd, ref LightData lightData, VisibleLight[] visibleLights) { LightweightUtils.SetKeyword(cmd, "_LIGHTWEIGHT_FORCE_LINEAR", m_Asset.ForceLinearRendering); LightweightUtils.SetKeyword(cmd, "_VERTEX_LIGHTS", lightData.vertexLightsCount > 0); LightweightUtils.SetKeyword(cmd, "_ATTENUATION_TEXTURE", m_Asset.AttenuationTexture != null); int mainLightIndex = lightData.mainLightIndex; LightweightUtils.SetKeyword (cmd, "_MAIN_DIRECTIONAL_LIGHT", mainLightIndex != -1 && visibleLights[mainLightIndex].lightType == LightType.Directional); LightweightUtils.SetKeyword (cmd, "_MAIN_SPOT_LIGHT", mainLightIndex != -1 && visibleLights[mainLightIndex].lightType == LightType.Spot); LightweightUtils.SetKeyword (cmd, "_MAIN_POINT_LIGHT", mainLightIndex != -1 && visibleLights[mainLightIndex].lightType == LightType.Point); LightweightUtils.SetKeyword(cmd, "_ADDITIONAL_PIXEL_LIGHTS", lightData.additionalPixelLightsCount > 0); string[] shadowKeywords = new string[] { "_HARD_SHADOWS", "_SOFT_SHADOWS", "_HARD_SHADOWS_CASCADES", "_SOFT_SHADOWS_CASCADES" }; for (int i = 0; i < shadowKeywords.Length; ++i) cmd.DisableShaderKeyword(shadowKeywords[i]); if (m_Asset.AreShadowsEnabled() && lightData.shadowsRendered) { int keywordIndex = (int)m_Asset.ShadowSetting - 1; if (m_Asset.CascadeCount > 1) keywordIndex += 2; cmd.EnableShaderKeyword(shadowKeywords[keywordIndex]); } } private bool RenderShadows(ref CullResults cullResults, ref VisibleLight shadowLight, int shadowLightIndex, ref ScriptableRenderContext context) { m_ShadowCasterCascadesCount = m_ShadowSettings.directionalLightCascadeCount; if (shadowLight.lightType == LightType.Spot) m_ShadowCasterCascadesCount = 1; int shadowResolution = GetMaxTileResolutionInAtlas(m_ShadowSettings.shadowAtlasWidth, m_ShadowSettings.shadowAtlasHeight, m_ShadowCasterCascadesCount); Bounds bounds; if (!cullResults.GetShadowCasterBounds(shadowLightIndex, out bounds)) return false; var setRenderTargetCommandBuffer = CommandBufferPool.Get(); setRenderTargetCommandBuffer.name = "Render packed shadows"; setRenderTargetCommandBuffer.GetTemporaryRT(m_ShadowMapTexture, m_ShadowSettings.shadowAtlasWidth, m_ShadowSettings.shadowAtlasHeight, kShadowDepthBufferBits, FilterMode.Bilinear, RenderTextureFormat.Depth); setRenderTargetCommandBuffer.SetRenderTarget(m_ShadowMapRT); setRenderTargetCommandBuffer.ClearRenderTarget(true, true, Color.black); context.ExecuteCommandBuffer(setRenderTargetCommandBuffer); CommandBufferPool.Release(setRenderTargetCommandBuffer); float shadowNearPlane = m_Asset.ShadowNearOffset; Vector3 splitRatio = m_ShadowSettings.directionalLightCascades; Matrix4x4 view, proj; var settings = new DrawShadowsSettings(cullResults, shadowLightIndex); bool needRendering = false; if (shadowLight.lightType == LightType.Spot) { needRendering = cullResults.ComputeSpotShadowMatricesAndCullingPrimitives(shadowLightIndex, out view, out proj, out settings.splitData); if (!needRendering) return false; SetupShadowSliceTransform(0, shadowResolution, proj, view); RenderShadowSlice(ref context, 0, proj, view, settings); } else if (shadowLight.lightType == LightType.Directional) { for (int cascadeIdx = 0; cascadeIdx < m_ShadowCasterCascadesCount; ++cascadeIdx) { needRendering = cullResults.ComputeDirectionalShadowMatricesAndCullingPrimitives(shadowLightIndex, cascadeIdx, m_ShadowCasterCascadesCount, splitRatio, shadowResolution, shadowNearPlane, out view, out proj, out settings.splitData); m_DirectionalShadowSplitDistances[cascadeIdx] = settings.splitData.cullingSphere; m_DirectionalShadowSplitDistances[cascadeIdx].w *= settings.splitData.cullingSphere.w; if (!needRendering) return false; SetupShadowSliceTransform(cascadeIdx, shadowResolution, proj, view); RenderShadowSlice(ref context, cascadeIdx, proj, view, settings); } } else { Debug.LogWarning("Only spot and directional shadow casters are supported in lightweight pipeline"); return false; } return true; } private void SetupShadowSliceTransform(int cascadeIndex, int shadowResolution, Matrix4x4 proj, Matrix4x4 view) { // Assumes MAX_CASCADES = 4 m_ShadowSlices[cascadeIndex].atlasX = (cascadeIndex % 2) * shadowResolution; m_ShadowSlices[cascadeIndex].atlasY = (cascadeIndex / 2) * shadowResolution; m_ShadowSlices[cascadeIndex].shadowResolution = shadowResolution; m_ShadowSlices[cascadeIndex].shadowTransform = Matrix4x4.identity; var matScaleBias = Matrix4x4.identity; matScaleBias.m00 = 0.5f; matScaleBias.m11 = 0.5f; matScaleBias.m22 = 0.5f; matScaleBias.m03 = 0.5f; matScaleBias.m23 = 0.5f; matScaleBias.m13 = 0.5f; // Later down the pipeline the proj matrix will be scaled to reverse-z in case of DX. // We need account for that scale in the shadowTransform. if (SystemInfo.usesReversedZBuffer) matScaleBias.m22 = -0.5f; var matTile = Matrix4x4.identity; matTile.m00 = (float)m_ShadowSlices[cascadeIndex].shadowResolution / (float)m_ShadowSettings.shadowAtlasWidth; matTile.m11 = (float)m_ShadowSlices[cascadeIndex].shadowResolution / (float)m_ShadowSettings.shadowAtlasHeight; matTile.m03 = (float)m_ShadowSlices[cascadeIndex].atlasX / (float)m_ShadowSettings.shadowAtlasWidth; matTile.m13 = (float)m_ShadowSlices[cascadeIndex].atlasY / (float)m_ShadowSettings.shadowAtlasHeight; m_ShadowSlices[cascadeIndex].shadowTransform = matTile * matScaleBias * proj * view; } private void RenderShadowSlice(ref ScriptableRenderContext context, int cascadeIndex, Matrix4x4 proj, Matrix4x4 view, DrawShadowsSettings settings) { var buffer = CommandBufferPool.Get("Prepare Shadowmap Slice"); buffer.SetViewport(new Rect(m_ShadowSlices[cascadeIndex].atlasX, m_ShadowSlices[cascadeIndex].atlasY, m_ShadowSlices[cascadeIndex].shadowResolution, m_ShadowSlices[cascadeIndex].shadowResolution)); buffer.SetViewProjectionMatrices(view, proj); context.ExecuteCommandBuffer(buffer); context.DrawShadows(ref settings); CommandBufferPool.Release(buffer); } private int GetMaxTileResolutionInAtlas(int atlasWidth, int atlasHeight, int tileCount) { int resolution = Mathf.Min(atlasWidth, atlasHeight); if (tileCount > Mathf.Log(resolution)) { Debug.LogError( String.Format( "Cannot fit {0} tiles into current shadowmap atlas of size ({1}, {2}). ShadowMap Resolution set to zero.", tileCount, atlasWidth, atlasHeight)); return 0; } int currentTileCount = atlasWidth / resolution * atlasHeight / resolution; while (currentTileCount < tileCount) { resolution = resolution >> 1; currentTileCount = atlasWidth / resolution * atlasHeight / resolution; } return resolution; } private void BeginForwardRendering(ref ScriptableRenderContext context, RenderingConfiguration renderingConfig) { RenderTargetIdentifier colorRT = BuiltinRenderTextureType.CameraTarget; RenderTargetIdentifier depthRT = BuiltinRenderTextureType.None; // When postprocess is enabled, msaa is forced to be disabled due to lack of depth resolve. int msaaSamples = (LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.Msaa)) ? m_Asset.MSAASampleCount : 1; var cmd = CommandBufferPool.Get("SetCameraRenderTarget"); if (LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.IntermediateTexture)) { int rtWidth = (int)((float)m_CurrCamera.pixelWidth * m_Asset.RenderScale); int rtHeight = (int)((float)m_CurrCamera.pixelHeight * m_Asset.RenderScale); if (m_CurrCamera.targetTexture == null) { RenderTextureDescriptor rtDesc = new RenderTextureDescriptor(); if (LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.Stereo)) { context.StartMultiEye(m_CurrCamera); rtDesc = XRSettings.eyeTextureDesc; rtDesc.colorFormat = m_ColorFormat; rtDesc.msaaSamples = msaaSamples; cmd.GetTemporaryRT(m_CameraColorTexture, rtDesc, FilterMode.Bilinear); } else { cmd.GetTemporaryRT(m_CameraColorTexture, rtWidth, rtHeight, kCameraDepthBufferBits, FilterMode.Bilinear, m_ColorFormat, RenderTextureReadWrite.Default, msaaSamples); } colorRT = m_CameraColorRT; } if (LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.PostProcess)) { cmd.GetTemporaryRT(m_CameraDepthTexture, rtWidth, rtHeight, kCameraDepthBufferBits, FilterMode.Bilinear, RenderTextureFormat.Depth); depthRT = m_CameraDepthRT; } } SetupRenderTargets(cmd, colorRT, depthRT); // Clear RenderTarget to avoid tile initialization on mobile GPUs // https://community.arm.com/graphics/b/blog/posts/mali-performance-2-how-to-correctly-handle-framebuffers if (m_CurrCamera.clearFlags != CameraClearFlags.Nothing) { bool clearDepth = (m_CurrCamera.clearFlags != CameraClearFlags.Nothing); bool clearColor = (m_CurrCamera.clearFlags == CameraClearFlags.Color || m_CurrCamera.clearFlags == CameraClearFlags.Skybox); cmd.ClearRenderTarget(clearDepth, clearColor, m_CurrCamera.backgroundColor.linear); } context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } private void EndForwardRendering(ref ScriptableRenderContext context, RenderingConfiguration renderingConfig) { // No additional rendering needs to be done if this is an offscren rendering camera if (m_CurrCamera.targetTexture != null) return; var cmd = CommandBufferPool.Get("Blit"); if (LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.IntermediateTextureArray)) { cmd.SetRenderTarget(BuiltinRenderTextureType.CameraTarget, 0, CubemapFace.Unknown, -1); cmd.Blit(m_CameraColorRT, BuiltinRenderTextureType.CurrentActive); } else if (LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.IntermediateTexture)) { // If PostProcessing is enabled, it is already blitted to CameraTarget. if (!LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.PostProcess)) Blit(cmd, renderingConfig, m_CameraColorRT, BuiltinRenderTextureType.CameraTarget); } cmd.SetRenderTarget(BuiltinRenderTextureType.CameraTarget); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); if (LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.Stereo)) { context.StopMultiEye(m_CurrCamera); context.StereoEndRender(m_CurrCamera); } } RendererConfiguration GetRendererSettings(ref LightData lightData) { RendererConfiguration settings = RendererConfiguration.PerObjectReflectionProbes | RendererConfiguration.PerObjectLightmaps | RendererConfiguration.PerObjectLightProbe; if (lightData.hasAdditionalLights) settings |= RendererConfiguration.PerObjectLightIndices8; return settings; } private void SetupRenderTargets(CommandBuffer cmd, RenderTargetIdentifier colorRT, RenderTargetIdentifier depthRT) { if (m_IntermediateTextureArray) { if (depthRT != BuiltinRenderTextureType.None) cmd.SetRenderTarget(colorRT, 0, CubemapFace.Unknown, -1); else cmd.SetRenderTarget(colorRT, depthRT, 0, CubemapFace.Unknown, -1); } else { if (depthRT != BuiltinRenderTextureType.None) cmd.SetRenderTarget(colorRT, depthRT); else cmd.SetRenderTarget(colorRT); } } private void RenderPostProcess(ref ScriptableRenderContext renderContext, PostProcessLayer postProcessLayer, bool opaqueOnly) { var postProcessCommand = CommandBufferPool.Get("Post Processing"); postProcessCommand.SetGlobalTexture("m_CameraDepthTexture", m_CameraDepthRT); m_PostProcessRenderContext.Reset(); m_PostProcessRenderContext.camera = m_CurrCamera; m_PostProcessRenderContext.source = BuiltinRenderTextureType.CurrentActive; m_PostProcessRenderContext.sourceFormat = m_ColorFormat; m_PostProcessRenderContext.destination = BuiltinRenderTextureType.CameraTarget; m_PostProcessRenderContext.command = postProcessCommand; m_PostProcessRenderContext.flip = true; if (opaqueOnly) postProcessLayer.RenderOpaqueOnly(m_PostProcessRenderContext); else postProcessLayer.Render(m_PostProcessRenderContext); renderContext.ExecuteCommandBuffer(postProcessCommand); CommandBufferPool.Release(postProcessCommand); } private void Blit(CommandBuffer cmd, RenderingConfiguration renderingConfig, RenderTargetIdentifier sourceRT, RenderTargetIdentifier destRT, Material material = null) { if (LightweightUtils.HasFlag(renderingConfig, RenderingConfiguration.DefaultViewport)) { cmd.Blit(sourceRT, destRT, material); } else { if (m_BlitQuad == null) m_BlitQuad = LightweightUtils.CreateQuadMesh(false); cmd.SetGlobalTexture(m_BlitTexID, sourceRT); cmd.SetRenderTarget(destRT); cmd.SetViewport(m_CurrCamera.pixelRect); cmd.DrawMesh(m_BlitQuad, Matrix4x4.identity, m_BlitMaterial); } } } }