using System; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.Profiling; using UnityEngine.Rendering; using UnityEngine.Rendering.RendererUtils; using UnityEngine.Rendering.Universal; namespace X.Rendering.Feature { [DisallowMultipleRendererFeature("PlanarScreenShadow")] public class PlanarScreenShadow : ScriptableRendererFeature { public enum SoftQuality { None = 0, Low = 10, Medium = 16, High = 32, } public enum ShadowResolution { _128 = 128, _256 = 256, _512 = 512, _768 = 768, _1024 = 1024, } [Serializable] private class Settings { public Material PlanarShadowMat; public Color planarShadowColor = Color.grey; public float castPlaneOffset = 0; public Vector3 castPlaneNormal = Vector3.up; public ShadowResolution shadowResolution = ShadowResolution._512; public SoftQuality softQuality = SoftQuality.Medium; public float softScaleByDistance = 1.0f; public float softGradientDistance = 15f; [Range(0.0f, 1.0f)] public float minSoft = 0.1f; } [SerializeField] private Settings settings = new(); PlanarShadowRenderPass pnanarShaowPass; /// public override void Create() { pnanarShaowPass = new PlanarShadowRenderPass(settings); pnanarShaowPass.renderPassEvent = RenderPassEvent.BeforeRenderingOpaques; } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { renderer.EnqueuePass(pnanarShaowPass); } class PlanarShadowRenderPass : ScriptableRenderPass { private RTHandle planarShadowRT; private RTHandle shadowDistanceTempTarget; private RTHandle shadowDistanceTarget; private RTHandle softShadowRenderTarget; private Settings settings; private ShaderTagId[] planarShadowShaderIds = new ShaderTagId[] { new ShaderTagId("PlanarShadow"), new ShaderTagId("UniversalForward") }; private ProfilingSampler profiler; static GlobalKeyword planarShadowKeyword; static GlobalKeyword lowSoftQuality; static GlobalKeyword mediumSoftQuality; static GlobalKeyword highSoftQuality; static Vector4[] lowSoftOperator; static Vector4[] mediumSoftOperator; static Vector4[] highSoftOperator; static readonly string disableSoftShadow = "SOFT_SHADOW_OFF"; static readonly int playerLayer = LayerMask.NameToLayer("Player"); public PlanarShadowRenderPass(Settings settings) { this.settings = settings; InitOperators(); planarShadowKeyword = GlobalKeyword.Create("PLANAR_SHADOW"); lowSoftQuality = GlobalKeyword.Create("LOW_SOFT_QUALITY"); mediumSoftQuality = GlobalKeyword.Create("MEDIUM_SOFT_QUALITY"); highSoftQuality = GlobalKeyword.Create("HIGH_SOFT_QUALITY"); profiler = new(nameof(PlanarShadowRenderPass)); } private static void InitOperators() { lowSoftOperator = new Vector4[32] { new Vector4(-0.7449182f, -0.3948351f, 0.05994137f, 0f), new Vector4(0.04522014f, -0.7452399f, 0.08145966f, 0f), new Vector4(0.5859449f, -0.3042229f, 0.1038759f, 0f), new Vector4(0.2801918f, 0.1988664f, 0.1961446f, 0f), new Vector4(0.8921295f, 0.231646f, 0.04541548f, 0f), new Vector4(-0.2148036f, -0.2805325f, 0.1935014f, 0f), new Vector4(-0.3599106f, 0.2223929f, 0.1736383f, 0f), new Vector4(-0.8255251f, 0.4714117f, 0.04075267f, 0f), new Vector4(0.1672432f, 0.8953505f, 0.0472625f, 0f), new Vector4(-0.3843737f, 0.7612128f, 0.05800831f, 0f), Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, }; mediumSoftOperator = new Vector4[32] { new Vector4(-0.573014f, -0.3037193f, 0.07195906f, 0f), new Vector4(0.03478467f, -0.5732615f, 0.08628026f, 0f), new Vector4(-0.4175899f, -0.7260013f, 0.04103231f, 0f), new Vector4(-0.2290866f, -0.06732111f, 0.1488967f, 0f), new Vector4(0.2787023f, -0.9224322f, 0.02605386f, 0f), new Vector4(0.4750609f, 0.0006364584f, 0.1062624f, 0f), new Vector4(-0.8097122f, 0.1471269f, 0.04306531f, 0f), new Vector4(-0.6714117f, 0.6465726f, 0.0293584f, 0f), new Vector4(-0.0459074f, 0.6448682f, 0.07233827f, 0f), new Vector4(-0.413441f, 0.2941564f, 0.0997189f, 0f), new Vector4(0.596337f, -0.5908241f, 0.04076795f, 0f), new Vector4(0.8953905f, 0.02900922f, 0.03352015f, 0f), new Vector4(0.6646501f, 0.5270658f, 0.03957359f, 0f), new Vector4(0.2487512f, 0.3517451f, 0.1151315f, 0f), new Vector4(-0.9710876f, -0.2296215f, 0.0227782f, 0f), new Vector4(0.1961836f, 0.9729925f, 0.02326321f, 0f), Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero, Vector4.zero }; highSoftOperator = new Vector4[32] { new Vector4(0.4153066f, -0.0570948f, 0.06091154f, 0f), new Vector4(0.1744031f, 0.1689546f, 0.07693625f, 0f), new Vector4(0.1248782f, -0.3793401f, 0.06292317f, 0f), new Vector4(0.377597f, -0.5691307f, 0.03405317f, 0f), new Vector4(-0.2083057f, -0.1873559f, 0.07398886f, 0f), new Vector4(0.5849795f, -0.7913114f, 0.0124803f, 0f), new Vector4(-0.5503789f, -0.6051685f, 0.02270556f, 0f), new Vector4(0.5012408f, 0.3336388f, 0.04192101f, 0f), new Vector4(0.02808845f, -0.6995828f, 0.03247567f, 0f), new Vector4(-0.263142f, -0.4866776f, 0.04693219f, 0f), new Vector4(0.2714973f, 0.5782288f, 0.03827445f, 0f), new Vector4(0.6123171f, 0.6595281f, 0.0171343f, 0f), new Vector4(-0.6475288f, -0.1612791f, 0.03552699f, 0f), new Vector4(0.4024244f, 0.8707167f, 0.01374556f, 0f), new Vector4(-0.1119028f, 0.6489825f, 0.03636114f, 0f), new Vector4(-0.02142411f, 0.9497761f, 0.01423686f, 0f), new Vector4(-0.2120009f, 0.3269744f, 0.06389162f, 0f), new Vector4(-0.5039293f, 0.1003278f, 0.05105383f, 0f), new Vector4(0.8276886f, 0.4618528f, 0.01435572f, 0f), new Vector4(0.8411248f, -0.5285234f, 0.0120281f, 0f), new Vector4(-0.3644551f, 0.790384f, 0.0190265f, 0f), new Vector4(0.306049f, -0.8608109f, 0.01630674f, 0f), new Vector4(-0.7063691f, 0.6909254f, 0.01228318f, 0f), new Vector4(0.9388323f, -0.01849926f, 0.01484117f, 0f), new Vector4(0.6864765f, 0.1109735f, 0.03290983f, 0f), new Vector4(0.638592f, -0.3184139f, 0.03126587f, 0f), new Vector4(-0.07949254f, -0.9821419f, 0.01241689f, 0f), new Vector4(-0.4160894f, -0.9045444f, 0.01192026f, 0f), new Vector4(-0.7837164f, 0.1824391f, 0.02371033f, 0f), new Vector4(-0.8274034f, -0.4524156f, 0.01461939f, 0f), new Vector4(-0.9582478f, -0.1421478f, 0.01325019f, 0f), new Vector4(-0.4291434f, 0.5112085f, 0.03551322f, 0f), }; } public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) { Profiler.BeginSample("Planar Screen Shadow Setup"); RecreateRenderTexture(renderingData); Profiler.EndSample(); } private void RecreateRenderTexture(RenderingData renderingData) { Vector2Int sourceRTSize = new Vector2Int(renderingData.cameraData.scaledWidth, renderingData.cameraData.scaledHeight); int shadowResolution = (int)settings.shadowResolution; int resolutionWidth = shadowResolution; int resolutionHeight = shadowResolution; if (sourceRTSize.x > sourceRTSize.y) resolutionWidth = Mathf.RoundToInt(shadowResolution * ((float)sourceRTSize.x / (float)sourceRTSize.y)); else resolutionHeight = Mathf.RoundToInt(shadowResolution * ((float)sourceRTSize.y / (float)sourceRTSize.x)); if (resolutionWidth == 0 || resolutionHeight == 0) return; var shadowMapDesc = new RenderTextureDescriptor(); shadowMapDesc.width = resolutionWidth; shadowMapDesc.height = resolutionHeight; shadowMapDesc.depthBufferBits = 0; shadowMapDesc.msaaSamples = 1; shadowMapDesc.useMipMap = false; shadowMapDesc.autoGenerateMips = false; shadowMapDesc.dimension = TextureDimension.Tex2D; shadowMapDesc.graphicsFormat = RenderingUtils.SupportsGraphicsFormat(GraphicsFormat.R8G8_UNorm, FormatUsage.Linear | FormatUsage.Render) ? GraphicsFormat.R8G8_UNorm : GraphicsFormat.B8G8R8A8_UNorm; SoftQuality softQuality = settings.softQuality; FilterMode filterMode = FilterMode.Point; if (softQuality == SoftQuality.None) { filterMode = FilterMode.Bilinear; shadowMapDesc.graphicsFormat = RenderingUtils.SupportsGraphicsFormat(GraphicsFormat.R8_UNorm, FormatUsage.Linear | FormatUsage.Render) ? GraphicsFormat.R8_UNorm : GraphicsFormat.B8G8R8A8_UNorm; } RenderingUtils.ReAllocateIfNeeded(ref planarShadowRT, shadowMapDesc, filterMode, TextureWrapMode.Clamp, name: "_PlanarShadowMap"); int disMapSize = 64; int disMapHeight = disMapSize; int disMapWidth = disMapSize; if (sourceRTSize.x > sourceRTSize.y) disMapWidth = Mathf.RoundToInt(disMapSize * ((float)sourceRTSize.x / (float)sourceRTSize.y)); else disMapHeight = Mathf.RoundToInt(disMapSize * ((float)sourceRTSize.y / (float)sourceRTSize.x)); var distanceMapDesc = shadowMapDesc; distanceMapDesc.graphicsFormat = RenderingUtils.SupportsGraphicsFormat(GraphicsFormat.R8_UNorm, FormatUsage.Linear | FormatUsage.Render) ? GraphicsFormat.R8_UNorm : GraphicsFormat.B8G8R8A8_UNorm; distanceMapDesc.width = disMapWidth; distanceMapDesc.height = disMapHeight; RenderingUtils.ReAllocateIfNeeded(ref shadowDistanceTempTarget, distanceMapDesc, FilterMode.Point, TextureWrapMode.Clamp); RenderingUtils.ReAllocateIfNeeded(ref shadowDistanceTarget, distanceMapDesc, FilterMode.Bilinear, TextureWrapMode.Clamp); var softMapDesc = shadowMapDesc; softMapDesc.graphicsFormat = RenderingUtils.SupportsGraphicsFormat(GraphicsFormat.R8_UNorm, FormatUsage.Linear | FormatUsage.Render) ? GraphicsFormat.R8_UNorm : GraphicsFormat.B8G8R8A8_UNorm; RenderingUtils.ReAllocateIfNeeded(ref softShadowRenderTarget, softMapDesc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_SoftPlanarShadowMap"); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { var cameraData = renderingData.cameraData; var cmd = renderingData.commandBuffer; using var scp = new ProfilingScope(cmd, profiler); float castPlaneOffset = settings.castPlaneOffset; Vector3 castPlaneNormal = settings.castPlaneNormal; ShadowResolution shadowResolution = settings.shadowResolution; SoftQuality softQuality = settings.softQuality; float softScaleByDistance = settings.softScaleByDistance; float softGradientDistance = settings.softGradientDistance; float minSoft = settings.minSoft; var rendererListDesc = new RendererListDesc(planarShadowShaderIds, renderingData.cullResults, cameraData.camera) { renderQueueRange = RenderQueueRange.all, sortingCriteria = SortingCriteria.OptimizeStateChanges, rendererConfiguration = renderingData.perObjectData, overrideMaterial = settings.PlanarShadowMat, overrideMaterialPassIndex = 3, layerMask = 1 << playerLayer, }; RendererList rendererList = context.CreateRendererList(rendererListDesc); Vector3 planeNormal = castPlaneNormal.normalized; Vector4 shadowCasterPlane = new Vector4(planeNormal.x, planeNormal.y, planeNormal.z, castPlaneOffset); cmd.BeginSample("PlanarShadow"); cmd.SetGlobalVector("_PlanarShadowCastPlane", shadowCasterPlane); cmd.SetGlobalFloat("_MinSoft", Mathf.Clamp01(minSoft)); cmd.SetGlobalFloat("_PlanarShadowMaxSoftDistance", softGradientDistance); cmd.SetViewProjectionMatrices(cameraData.camera.worldToCameraMatrix, cameraData.camera.projectionMatrix); cmd.SetRenderTarget(planarShadowRT, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store); cmd.ClearRenderTarget(false, true, Color.clear); if (softQuality == SoftQuality.None) { cmd.EnableShaderKeyword(disableSoftShadow); } cmd.DrawRendererList(rendererList); cmd.EndSample("PlanarShadow"); if (softQuality != SoftQuality.None) { cmd.BeginSample("BlurShadow"); VisibleLight mainLight = renderingData.lightData.visibleLights[renderingData.lightData.mainLightIndex]; Vector3 lightDirection = -mainLight.localToWorldMatrix.GetColumn(2); lightDirection.y = 0; Vector3 pixelOffset = cameraData.camera.WorldToScreenPoint(Vector3.zero) - cameraData.camera.WorldToScreenPoint(lightDirection); pixelOffset.z = 0; pixelOffset = pixelOffset.normalized; Vector4 shadowScreenVDir = new Vector4(pixelOffset.x, pixelOffset.y, 0, 0); Vector4 shadowScreenHDir = new Vector4(pixelOffset.x, -pixelOffset.y, 0, 0); int disMapWidth = shadowDistanceTempTarget.rt.width; int disMapHeight = shadowDistanceTempTarget.rt.height; cmd.SetGlobalVector("_ShadowScreenHDir", shadowScreenHDir); cmd.SetGlobalVector("_ShadowScreenVDir", shadowScreenVDir); cmd.SetGlobalVector("_EdgeMapTexelSize", new Vector4(1.0f / disMapWidth, 1.0f / disMapHeight, disMapWidth, disMapHeight)); cmd.SetGlobalTexture("_SourceTex", planarShadowRT); cmd.SetRenderTarget(shadowDistanceTempTarget, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store); cmd.DrawProcedural(Matrix4x4.identity, settings.PlanarShadowMat, 1, MeshTopology.Triangles, 3); cmd.SetGlobalTexture("_SourceTex", shadowDistanceTempTarget); cmd.SetRenderTarget(shadowDistanceTarget, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store); cmd.DrawProcedural(Matrix4x4.identity, settings.PlanarShadowMat, 0, MeshTopology.Triangles, 3); Vector4[] possionOperator = lowSoftOperator; if (softQuality == SoftQuality.Medium) possionOperator = mediumSoftOperator; if (softQuality == SoftQuality.High) possionOperator = highSoftOperator; cmd.DisableKeyword(lowSoftQuality); cmd.DisableKeyword(mediumSoftQuality); cmd.DisableKeyword(highSoftQuality); if (softQuality == SoftQuality.Low) cmd.EnableKeyword(lowSoftQuality); if (softQuality == SoftQuality.Medium) cmd.EnableKeyword(mediumSoftQuality); if (softQuality == SoftQuality.High) cmd.EnableKeyword(highSoftQuality); cmd.SetGlobalFloat("_SoftScaleByDistance", softScaleByDistance * (float)shadowResolution / 256f); cmd.SetGlobalTexture("_DistanceTex", shadowDistanceTarget); cmd.SetGlobalVectorArray(Shader.PropertyToID("_PossionOperator"), possionOperator); cmd.SetGlobalTexture("_SourceTex", planarShadowRT); cmd.SetRenderTarget(softShadowRenderTarget, loadAction: RenderBufferLoadAction.DontCare, storeAction: RenderBufferStoreAction.Store); cmd.DisableShaderKeyword(disableSoftShadow); cmd.DrawProcedural(Matrix4x4.identity, settings.PlanarShadowMat, 2, MeshTopology.Triangles, 3); cmd.EndSample("BlurShadow"); } cmd.EnableKeyword(planarShadowKeyword); cmd.SetGlobalColor("_PlanarShadowColor", settings.planarShadowColor); cmd.SetGlobalTexture("_PlanarShadowMap", softQuality == SoftQuality.None ? planarShadowRT : softShadowRenderTarget); } public override void OnFinishCameraStackRendering(CommandBuffer cmd) { cmd.DisableKeyword(planarShadowKeyword); cmd.DisableShaderKeyword(disableSoftShadow); base.OnFinishCameraStackRendering(cmd); } } } }