using System.Reflection; using AcDream.Plugin.Abstractions.Rendering; namespace AcDream.RenderPacks.ShadowsOnlyTier2; /// /// Minimal Tier-2 moving celestial-shadow pack. The custom tone-map pass is only the /// technical HDR output copy; no Tier-2+ atmosphere post stack is declared. /// public sealed class ShadowsOnlyTier2RenderPack : IRenderPackPlugin, IRenderPackAssets { private const string ShaderPrefix = "shaders/"; private const string EmbeddedPrefix = "AcDream.RenderPacks.ShadowsOnlyTier2.Shaders."; private static RenderPackDescriptor Descriptor { get; } = new( "sample.shadows-only-tier2", "Shadows-Only Tier 2 SDK Sample", new Version(1, 0, 0), RenderPackApi.Current, RenderPackTier.Tier2, [ RenderCapability.MainWorldColorIntermediate, RenderCapability.FullscreenPasses, RenderCapability.AuthoredWeather, RenderCapability.DirectionalShadowMaps, RenderCapability.OutdoorDirectionalShadowCasterReplay, RenderCapability.AnimatedCasterTransforms, RenderCapability.AlphaCutoutShadowCasters, RenderCapability.AuthoredCelestialDirectionalLight, ], [RenderCapability.GpuTimestampQueries], [ShadowResource()], Passes(), [CasterReplay()], Variants(), [Preset()], Settings(), Policy()) { FeatureSummary = "Moving sun-and-moon shadows from terrain, trees, buildings, players, " + "and monsters without bloom, rays, grading, vignette, or volumetric shafts.", }; public void Register(IRenderPackRegistry registry) { ArgumentNullException.ThrowIfNull(registry); registry.Register(Descriptor, this); } public Stream OpenRead(string assetKey) { ArgumentException.ThrowIfNullOrWhiteSpace(assetKey); if (!assetKey.StartsWith(ShaderPrefix, StringComparison.Ordinal) || assetKey.Length == ShaderPrefix.Length || assetKey.Contains('\\') || assetKey.Contains("..", StringComparison.Ordinal)) { throw new FileNotFoundException("Only declared embedded shaders are available.", assetKey); } string fileName = assetKey[ShaderPrefix.Length..]; if (fileName.Contains('/')) throw new FileNotFoundException("Nested shader paths are not declared.", assetKey); return Assembly.GetExecutingAssembly().GetManifestResourceStream(EmbeddedPrefix + fileName) ?? throw new FileNotFoundException("The declared shader asset is missing.", assetKey); } private static RenderResourceDeclaration ShadowResource() => new( "directional-shadow-depth", RenderResourceKind.Image2DArray, RenderFormatClass.DirectionalDepth, new RenderExtentDeclaration(RenderExtentMode.AbsolutePixels, 1024, 1024, Layers: 2), SizeBytes: 0, RenderResourceUsage.Sampled | RenderResourceUsage.DepthAttachment, RenderResourceLifetime.ActivePack, EstimatedResidentBytes: 8L * 1024 * 1024) { Semantic = RenderResourceSemantic.DirectionalShadowDepth, }; private static IReadOnlyList Passes() => [ new RenderPassDeclaration( "record-directional-shadow-casters", RenderPassHook.ShadowDepthBeforeWorld, Shader("shadow-pass.vert.spv"), Shader("shadow-pass.frag.spv"), [ RenderSemanticInput.CameraMatrices, RenderSemanticInput.SelectedCelestialDirectionalLight, RenderSemanticInput.ShadowCasterTransforms, RenderSemanticInput.ActiveDayGroup, RenderSemanticInput.Weather, ], [], ["directional-shadow-depth"]) { Semantic = RenderPassSemantic.DirectionalShadowDepth, }, new RenderPassDeclaration( "copy-hdr-world-to-output", RenderPassHook.ToneMap, Shader("glow-filter.vert.spv"), Shader("glow-filter.frag.spv"), [RenderSemanticInput.WorldColor], [], []), ]; private static SceneReplayDeclaration CasterReplay() => new( "replay-all-headline-casters", RenderSceneReplaySemantic.OutdoorDirectionalShadowCasters, RenderCasterClass.Terrain | RenderCasterClass.OpaqueWorld | RenderCasterClass.AlphaCutoutWorld | RenderCasterClass.AnimatedOpaque | RenderCasterClass.AnimatedAlphaCutout, ViewCount: 4); private static IReadOnlyList Variants() => [ Variant("terrain-caster", RenderPipelineVariantSemantic.TerrainDirectionalShadowCaster, RenderPipelineBaseSemantic.Terrain, "landscape-shadow", RenderMaterialClass.Opaque, [RenderSemanticInput.CameraMatrices]), Variant("opaque-caster", RenderPipelineVariantSemantic.WorldOpaqueDirectionalShadowCaster, RenderPipelineBaseSemantic.WorldMesh, "solid-shadow", RenderMaterialClass.Opaque | RenderMaterialClass.AnimatedOpaque, [RenderSemanticInput.CameraMatrices, RenderSemanticInput.ShadowCasterTransforms]), Variant("cutout-caster", RenderPipelineVariantSemantic.WorldAlphaCutoutDirectionalShadowCaster, RenderPipelineBaseSemantic.WorldMesh, "cutout-shadow", RenderMaterialClass.AlphaCutout | RenderMaterialClass.AnimatedAlphaCutout, [RenderSemanticInput.CameraMatrices, RenderSemanticInput.ShadowCasterTransforms]), Variant("terrain-receiver", RenderPipelineVariantSemantic.TerrainDirectionalShadowReceiver, RenderPipelineBaseSemantic.Terrain, "landscape-lit", RenderMaterialClass.Opaque, [RenderSemanticInput.DirectionalShadowMaps, RenderSemanticInput.SelectedCelestialDirectionalLight]), Variant("world-receiver", RenderPipelineVariantSemantic.WorldDirectionalShadowReceiver, RenderPipelineBaseSemantic.WorldMesh, "object-lit", RenderMaterialClass.Opaque | RenderMaterialClass.AlphaCutout | RenderMaterialClass.AnimatedOpaque | RenderMaterialClass.AnimatedAlphaCutout, [RenderSemanticInput.DirectionalShadowMaps, RenderSemanticInput.SelectedCelestialDirectionalLight]), ]; private static PipelineVariantDeclaration Variant( string id, RenderPipelineVariantSemantic semantic, RenderPipelineBaseSemantic baseSemantic, string shaderStem, RenderMaterialClass materials, IReadOnlyList inputs) => new( id, baseSemantic, Shader(shaderStem + ".vert.spv"), Shader(shaderStem + ".frag.spv"), materials, inputs) { Semantic = semantic, }; private static RenderQualityPreset Preset() => new( "balanced", "Balanced", [], [], [], MaxResidentGpuBytes: 64L * 1024 * 1024, MaxIncrementalGpuMillisecondsP50: 2.0, MaxIncrementalGpuMillisecondsP99: 3.0, MaxIncrementalCpuMillisecondsP50: 0.2, MaxIncrementalCpuMillisecondsP99: 0.5) { Semantic = RenderQualitySemantic.Medium, }; private static IReadOnlyList Settings() => [ Float("shadow-opacity", "Shadow opacity", RenderSettingSemantic.DirectionalShadowStrength, "0.72", 0, 1, 0.02), Integer("shadow-reach", "Shadow reach (metres)", RenderSettingSemantic.DirectionalShadowReachMetres, "144", 16, 240, 1), new RenderSettingDeclaration( "shadow-filter", "Shadow filter samples", RenderSettingKind.Choice, "9", null, null, null, ["1", "9", "25"]) { Semantic = RenderSettingSemantic.DirectionalShadowPcfTaps, }, ]; private static RenderSettingDeclaration Float( string id, string displayName, RenderSettingSemantic semantic, string value, double minimum, double maximum, double step) => new( id, displayName, RenderSettingKind.Float, value, minimum, maximum, step, []) { Semantic = semantic, }; private static RenderSettingDeclaration Integer( string id, string displayName, RenderSettingSemantic semantic, string value, double minimum, double maximum, double step) => new( id, displayName, RenderSettingKind.Integer, value, minimum, maximum, step, []) { Semantic = semantic, }; private static AtmospherePolicyDeclaration Policy() => new( [ new SunElevationResponsePoint(-90, 1), new SunElevationResponsePoint(90, 1), ], [ new ActiveDayGroupMultiplier(0, 1), new ActiveDayGroupMultiplier(1, 0.35), new ActiveDayGroupMultiplier(2, 0.20), ]) { DirectionalShadowLightElevationResponse = [ new SunElevationResponsePoint(-90, 0), new SunElevationResponsePoint(1, 0), new SunElevationResponsePoint(12, 1), new SunElevationResponsePoint(90, 1), ], }; private static string Shader(string fileName) => ShaderPrefix + fileName; }