using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Rendering.HighDefinition; using UnityEngine.VFX; namespace UnityEditor.VFX.HDRP { [VFXInfo(name = "Output Particle HDRP Volumetric Fog", category = "Output", experimental = true)] class VFXVolumetricFogOutput : VFXAbstractParticleOutput { public override string name => "Output Particle HDRP Volumetric Fog"; public override string codeGeneratorTemplate => RenderPipeTemplate("VFXVolumetricFogOutput"); public override VFXTaskType taskType => VFXTaskType.ParticleQuadOutput; [VFXSetting, SerializeField, Tooltip("Specifies how the fog of the output is blended with the fog in the scene.")] protected VFXLocalVolumetricFogBlendingMode fogBlendMode = VFXLocalVolumetricFogBlendingMode.Additive; [VFXSetting, SerializeField, Tooltip("When Blend Distance is above 0, controls which kind of falloff is applied to the transition area.")] public LocalVolumetricFogFalloffMode falloffMode = LocalVolumetricFogFalloffMode.Exponential; [VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), SerializeField, Tooltip("When enabled, the output will accept a 3D Texture modifying both the color and density of the particle.")] protected bool useMaskMap = false; [VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), SerializeField, Tooltip("When enabled, the particles will start fading depending on their distance to the camera.")] protected bool useDistanceFading = false; /// /// We only implement non commutative blend modes because it's impossible to sort VFX and Fog volumes together. /// public enum VFXLocalVolumetricFogBlendingMode { Additive = LocalVolumetricFogBlendingMode.Additive, Multiply = LocalVolumetricFogBlendingMode.Multiply, Min = LocalVolumetricFogBlendingMode.Min, Max = LocalVolumetricFogBlendingMode.Max, } public class VolumetricFogInputProperties { [Range(0, 1), Tooltip("Distance in meter where density will fade out towards the outside of the sphere.")] public float fadeRadius = 0.1f; [Min(0), Tooltip("How dense the fog will be. A denser fog will absorb more light, making it look darker.")] public float density = 1f; } public class VolumetricFogMaskProperties { [Tooltip("The 3D texture used to modify the color and density of the fog.")] public Texture3D mask = VFXResources.tileableGradientNoise; [Tooltip("Modifies the tiling of the mask texture on each axis individually.")] public Vector3 uvScale = Vector3.one; [Tooltip("Offsets the texture UVs on each axis individually..")] public Vector3 uvBias = Vector3.zero; } public class DistanceFadeProperties { [Min(0), Tooltip("Distance at which density fading starts."), Delayed] public float distanceFadeStart = 10000; [Min(0), Tooltip("Distance at which density fading ends."), Delayed] public float distanceFadeEnd = 10000; } public override IEnumerable attributes { get { yield return new VFXAttributeInfo(VFXAttribute.Position, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.Alive, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.AxisX, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.AxisY, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.AxisZ, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.AngleX, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.AngleY, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.AngleZ, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.PivotX, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.PivotY, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.PivotZ, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.Size, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.ScaleX, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.Color, VFXAttributeMode.Read); yield return new VFXAttributeInfo(VFXAttribute.Alpha, VFXAttributeMode.Read); } } public override VFXOutputUpdate.Features outputUpdateFeatures => base.outputUpdateFeatures | VFXOutputUpdate.Features.VolumetricFog; public override bool NeedsOutputUpdate() => false; public override sealed bool CanBeCompiled() { return (VFXLibrary.currentSRPBinder is VFXHDRPBinder) && base.CanBeCompiled(); } protected override IEnumerable inputProperties { get { foreach (var prop in base.inputProperties) yield return prop; foreach (var prop in PropertiesFromType(nameof(VolumetricFogInputProperties))) yield return prop; if (useMaskMap) foreach (var prop in PropertiesFromType(nameof(VolumetricFogMaskProperties))) yield return prop; if (useDistanceFading) foreach (var prop in PropertiesFromType(nameof(DistanceFadeProperties))) yield return prop; } } protected override IEnumerable CollectGPUExpressions(IEnumerable slotExpressions) { var vfxNamedExpressions = slotExpressions as VFXNamedExpression[] ?? slotExpressions.ToArray(); foreach (var exp in base.CollectGPUExpressions(vfxNamedExpressions)) yield return exp; yield return vfxNamedExpressions.First(o => o.name == nameof(VolumetricFogInputProperties.fadeRadius)); yield return new VFXNamedExpression(VFXValue.Constant((int)falloffMode), nameof(falloffMode)); yield return new VFXNamedExpression(VFXValue.Constant((int)fogBlendMode), nameof(fogBlendMode)); if (useMaskMap) { yield return vfxNamedExpressions.First(o => o.name == nameof(VolumetricFogMaskProperties.mask)); yield return vfxNamedExpressions.First(o => o.name == nameof(VolumetricFogMaskProperties.uvScale)); yield return vfxNamedExpressions.First(o => o.name == nameof(VolumetricFogMaskProperties.uvBias)); } var densityExpr = vfxNamedExpressions.First(o => o.name == nameof(VolumetricFogInputProperties.density)); yield return new VFXNamedExpression { name = densityExpr.name, exp = new VFXExpressionMax(densityExpr.exp, VFXValue.Constant(0.0f)), }; if (useMaskMap) { var maskTextureExpression = vfxNamedExpressions.First(o => o.name == nameof(VolumetricFogMaskProperties.mask)); var condition = VFXOperatorUtility.TextureFormatEquals(maskTextureExpression.exp, TextureFormat.Alpha8); yield return new VFXNamedExpression { name = "isTextureAlpha8", exp = condition }; } if (useDistanceFading) { var distanceFadeEndExpr = vfxNamedExpressions.First(o => o.name == nameof(DistanceFadeProperties.distanceFadeStart)); var distanceFadeStartExpr = vfxNamedExpressions.First(o => o.name == nameof(DistanceFadeProperties.distanceFadeEnd)); var diff = new VFXExpressionSubtract(distanceFadeEndExpr.exp, distanceFadeStartExpr.exp); var fadeDistance = new VFXExpressionMax(diff, VFXValue.Constant(0.0001f)); var rcpFadeDistance = new VFXExpressionDivide(VFXValue.Constant(1.0f), fadeDistance); yield return new VFXNamedExpression { name = "rcpDistanceFadeLength", exp = rcpFadeDistance, }; var endTimesRcpDistanceFade = new VFXExpressionMul(rcpFadeDistance, distanceFadeEndExpr.exp); yield return new VFXNamedExpression { name = "endTimesRcpDistanceFadeLength", exp = endTimesRcpDistanceFade, }; } } public override VFXExpressionMapper GetExpressionMapper(VFXDeviceTarget target) { var localSpace = ((VFXDataParticle)GetData()).space == VFXSpace.Local; var mapper = base.GetExpressionMapper(target); if (target == VFXDeviceTarget.GPU && localSpace) { mapper ??= new VFXExpressionMapper(); mapper.AddExpression(VFXBuiltInExpression.LocalToWorld, "localToWorld", -1); } return mapper; } protected override IEnumerable filteredOutSettings { get { foreach (var setting in base.filteredOutSettings) yield return setting; yield return nameof(blendMode); yield return nameof(useAlphaClipping); yield return nameof(colorMapping); yield return nameof(cullMode); yield return nameof(zWriteMode); yield return nameof(zTestMode); yield return nameof(uvMode); yield return nameof(useSoftParticle); yield return nameof(indirectDraw); yield return nameof(castShadows); yield return nameof(useExposureWeight); yield return nameof(sort); yield return nameof(sortMode); yield return nameof(enableRayTracing); yield return nameof(revertSorting); yield return nameof(excludeFromTUAndAA); yield return nameof(computeCulling); yield return nameof(vfxSystemSortPriority); yield return nameof(sortingPriority); } } protected override IEnumerable untransferableSettings { get { foreach (var setting in base.untransferableSettings) yield return setting; foreach (var setting in filteredOutSettings) yield return setting; } } public override IEnumerable additionalDefines { get { foreach (var define in base.additionalDefines) yield return define; if (useMaskMap) { yield return "HDRP_VOLUMETRIC_MASK"; if (useDistanceFading) yield return "HDRP_VOLUMETRIC_DISTANCE_FADING"; } if (frustumCulling) yield return "VFX_FEATURE_FRUSTUM_CULL"; yield return "VFX_PRIMITIVE_QUAD"; var data = GetData(); if (data.IsCurrentAttributeWritten(VFXAttribute.AngleX) || data.IsCurrentAttributeWritten(VFXAttribute.AngleY) || data.IsCurrentAttributeWritten(VFXAttribute.AngleZ) || data.IsCurrentAttributeWritten(VFXAttribute.AxisX) || data.IsCurrentAttributeWritten(VFXAttribute.AxisY) || data.IsCurrentAttributeWritten(VFXAttribute.AxisZ)) { yield return "VFX_APPLY_ANGULAR_ROTATION"; } } } public override bool HasSorting() => false; public override bool isRayTraced => false; protected override VFXShaderWriter renderState { get { var writer = new VFXShaderWriter(); writer.WriteLine("ZWrite Off"); writer.WriteLine("ZTest Always"); writer.WriteLine("Cull Off"); FogVolumeAPI.ComputeBlendParameters((LocalVolumetricFogBlendingMode)fogBlendMode, out var srcColorBlend, out var srcAlphaBlend, out var dstColorBlend, out var dstAlphaBlend, out var colorBlendOp, out var alphaBlendOp); writer.WriteLine($"Blend {srcColorBlend} {dstColorBlend}, {srcAlphaBlend} {dstAlphaBlend}"); writer.WriteLine($"BlendOp {colorBlendOp}, {alphaBlendOp}"); return writer; } } internal override void GenerateErrors(VFXErrorReporter report) { if (!HDRenderPipeline.currentAsset?.currentPlatformRenderPipelineSettings.supportVolumetrics ?? false) { report.RegisterError("VolumetricFogDisabled", VFXErrorType.Warning, $"The current HDRP Asset does not support volumetric fog. To fix this error, go to the Lighting section of your HDRP asset and enable 'Volumetric Fog'.", this); } var data = GetData(); if (data != null) { if (!data.IsCurrentAttributeWritten(VFXAttribute.Size) && !data.IsCurrentAttributeWritten(VFXAttribute.ScaleX)) { report.RegisterError("SizeTooSmall", VFXErrorType.Warning, $"The size of the fog particle is not modified. This can make the volumetric fog effect invisible because the default size is too small. To fix this, add a size block in your system and increase it's value.", this); } if (data.IsCurrentAttributeWritten(VFXAttribute.ScaleY) || data.IsCurrentAttributeWritten(VFXAttribute.ScaleZ)) { report.RegisterError("ScaleYZIgnored", VFXErrorType.Warning, $"The scale on Y and Z axis are ignored by the volumetric fog. Configure your scale component to X only to remove this message.", this); } } } public override VFXContextCompiledData PrepareCompiledData() { var compiledData = base.PrepareCompiledData(); var outputTask = compiledData.tasks.Last(); outputTask.bufferMappings.Add(VFXDataParticle.k_IndirectBufferName); outputTask.bufferMappings.Add("maxSliceCount"); compiledData.AllocateIndirectBuffer(); compiledData.buffers.Add(new VFXContextBufferDescriptor { baseName = "maxSliceCount", size = 1, bufferSizeMode = VFXContextBufferSizeMode.FixedSize, bufferTarget = GraphicsBuffer.Target.Structured, stride = sizeof(uint), bufferCount = 1, }); // Volumetric output task need to be inserted before the output work compiledData.tasks.Insert(0, new VFXTask { doesGenerateShader = true, templatePath = VisualEffectGraphPackageInfo.assetPackagePath + "/Shaders/VFXVolumetricFogUpdate", additionalDefines = new string[] { "VFX_VOLUMETRIC_FOG_PASS_CLEAR", "HAVE_VFX_MODIFICATION" }, type = VFXTaskType.PerCameraUpdate, shaderType = VFXTaskShaderType.ComputeShader, bufferMappings = new() { "maxSliceCount" }, name = "Clear", }); compiledData.tasks.Insert(1, new VFXTask { doesGenerateShader = true, templatePath = VisualEffectGraphPackageInfo.assetPackagePath + "/Shaders/VFXVolumetricFogUpdate", additionalDefines = new string[] { "VFX_VOLUMETRIC_FOG_PASS_0", "HAVE_VFX_MODIFICATION" }, type = VFXTaskType.PerCameraUpdate, shaderType = VFXTaskShaderType.ComputeShader, bufferMappings = new() { "maxSliceCount"}, name = "Count", }); compiledData.tasks.Insert(2, new VFXTask { doesGenerateShader = true, templatePath = VisualEffectGraphPackageInfo.assetPackagePath + "/Shaders/VFXVolumetricFogUpdate", additionalDefines = new string[] { "VFX_VOLUMETRIC_FOG_PASS_1", "HAVE_VFX_MODIFICATION" }, type = VFXTaskType.PerCameraUpdate, shaderType = VFXTaskShaderType.ComputeShader, bufferMappings = new() { "maxSliceCount", new VFXTask.BufferMapping(VFXDataParticle.k_IndirectBufferName, "outputBuffer")}, name = "Fill", }); return compiledData; } } }