331 lines
18 KiB
C#
Raw Normal View History

2025-07-20 17:26:12 +08:00
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
{
_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;
/// <inheritdoc/>
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 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 = softQuality == SoftQuality.None ? FilterMode.Bilinear : FilterMode.Point;
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);
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.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);
base.OnFinishCameraStackRendering(cmd);
}
}
}
}