using System.Collections.Generic; using System.Linq; using UnityEditor.ShaderGraph.Internal; using UnityEngine; namespace UnityEditor.VFX { class VFXShaderGraphParticleOutput : VFXAbstractParticleOutput, IVFXShaderGraphOutput { //"protected" is only to be listed by VFXModel.GetSettings, we should always use GetOrRefreshShaderGraphObject [SerializeField, VFXSetting] protected ShaderGraphVfxAsset shaderGraph; [SerializeField, VFXSetting] internal VFXMaterialSerializedSettings materialSettings = new VFXMaterialSerializedSettings(); private bool m_IsShaderGraphMissing; protected override IEnumerable untransferableSettings { get { //In case of convert from VFXComposedParticleOutput, we won't retrieve shaderGraph anymore, this settings is now hidden yield return nameof(shaderGraph); } } public ShaderGraphVfxAsset GetOrRefreshShaderGraphObject(bool refreshErrors = true) { var wasShaderGraphMissing = m_IsShaderGraphMissing; //This is the only place where shaderGraph property is updated or read if (shaderGraph == null && !object.ReferenceEquals(shaderGraph, null)) { var assetPath = AssetDatabase.GetAssetPath(shaderGraph.GetEntityId()); var newShaderGraph = AssetDatabase.LoadAssetAtPath(assetPath); m_IsShaderGraphMissing = newShaderGraph == null; if (!m_IsShaderGraphMissing) { shaderGraph = newShaderGraph; } } else { m_IsShaderGraphMissing = false; } if (refreshErrors && wasShaderGraphMissing != m_IsShaderGraphMissing) { RefreshErrors(); } return shaderGraph; } public override void Sanitize(int version) { base.Sanitize(version); if (version < 14) { var shaderGraph = GetOrRefreshShaderGraphObject(); if (shaderGraph && shaderGraph.generatesWithShaderGraph) { var path = AssetDatabase.GetAssetPath(shaderGraph); var referenceMaterial = AssetDatabase.LoadAssetAtPath(path); materialSettings.UpgradeToMaterialWorkflowVersion(referenceMaterial); } } if (version < 15) { SanitizeHelper.MigrateSGOutputToComposed(this); } } public override bool CanBeCompiled() { if (m_IsShaderGraphMissing) return false; var sg = GetOrRefreshShaderGraphObject(); if (sg != null && sg.generatesWithShaderGraph) return false; return base.CanBeCompiled(); } public override void OnSettingModified(VFXSetting setting) { if (setting.name == nameof(shaderGraph)) { VFXAnalytics.GetInstance().OnSpecificSettingChanged($"{GetType().Name}.{setting.name}"); } } public override void GetImportDependentAssets(HashSet dependencies) { base.GetImportDependentAssets(dependencies); if (!object.ReferenceEquals(shaderGraph, null)) { dependencies.Add(shaderGraph.GetInstanceID()); } } protected VFXShaderGraphParticleOutput(bool strip = false) : base(strip) { } // Here we maintain a list of settings that we do not need if we are using the ShaderGraph generation path (it will be in the material inspector). static IEnumerable FilterOutBuiltinSettings() { yield return "blendMode"; yield return "cullMode"; yield return "zWriteMode"; yield return "zTestMode"; yield return "excludeFromTUAndAA"; yield return "preserveSpecularLighting"; yield return "doubleSided"; yield return "onlyAmbientLighting"; yield return "useExposureWeight"; yield return "alphaThreshold"; yield return "normalBending"; } protected override IEnumerable filteredOutSettings { get { foreach (var setting in base.filteredOutSettings) yield return setting; var sg = GetOrRefreshShaderGraphObject(); if (sg != null || m_IsShaderGraphMissing) { yield return "colorMapping"; yield return "useBaseColorMap"; yield return "useAlphaClipping"; if (m_IsShaderGraphMissing) { yield return "useSoftParticle"; yield return "uvMode"; foreach (var builtinSetting in FilterOutBuiltinSettings()) yield return builtinSetting; } } else { yield return nameof(shaderGraph); } yield return nameof(materialSettings); } } public override bool supportsUV => base.supportsUV && GetOrRefreshShaderGraphObject() == null; public override bool exposeAlphaThreshold { get { var shaderGraph = GetOrRefreshShaderGraphObject(); if (shaderGraph == null) { if (base.exposeAlphaThreshold) return true; } else { if (!shaderGraph.alphaClipping) { //alpha clipping isn't enabled in shaderGraph, we implicitly still allows clipping for shadow & motion vector passes. if (!isBlendModeOpaque && (hasMotionVector || hasShadowCasting)) return true; } } return false; } } public override bool supportSoftParticles => base.supportSoftParticles && GetOrRefreshShaderGraphObject() == null; public override bool hasAlphaClipping { get { var shaderGraph = GetOrRefreshShaderGraphObject(); bool noShaderGraphAlphaThreshold = shaderGraph == null && useAlphaClipping; bool ShaderGraphAlphaThreshold = shaderGraph != null && shaderGraph.alphaClipping; return noShaderGraphAlphaThreshold || ShaderGraphAlphaThreshold; } } // Do not resync slots when shader graph is missing to keep potential links to the shader properties public override bool ResyncSlots(bool notify) => !m_IsShaderGraphMissing && base.ResyncSlots(notify); public override void CheckGraphBeforeImport() { base.CheckGraphBeforeImport(); var currentShaderGraph = GetOrRefreshShaderGraphObject(); // If the graph is reimported it can be because one of its dependency such as the shadergraphs, has been changed. if (!VFXGraph.explicitCompile) { ResyncSlots(true); // Ensure that the output context name is in sync with the shader graph shader enum name. if (currentShaderGraph != null && currentShaderGraph.generatesWithShaderGraph) Invalidate(InvalidationCause.kUIChangedTransient); else if (m_IsShaderGraphMissing) { var vfxName = GetGraph().visualEffectResource.name; Debug.LogError($"The VFX Graph '{vfxName}'" + VFXShaderGraphHelpers.GetMissingShaderGraphErrorMessage(currentShaderGraph)); } } } internal override void GenerateErrors(VFXErrorReporter report) { base.GenerateErrors(report); var currentShaderGraph = GetOrRefreshShaderGraphObject(false); if (m_IsShaderGraphMissing) { var message = VFXShaderGraphHelpers.GetMissingShaderGraphErrorMessage(currentShaderGraph); report.RegisterError("ErrorMissingShaderGraph", VFXErrorType.Error, "The VFX Graph" + message, this); } if (currentShaderGraph != null) { if (!currentShaderGraph.generatesWithShaderGraph) { report.RegisterError("DeprecatedOldShaderGraph", VFXErrorType.Error, ParticleShadingShaderGraph.kErrorOldSG, this); } else { //There isn't automatic sanitize if the SG change its status from old to new SG integration report.RegisterError("WrongOutputShaderGraph", VFXErrorType.Error, "Please convert this context to dedicated ShaderGraph Output.", this); } } } protected override IEnumerable inputProperties { get { IEnumerable properties = base.inputProperties; var sg = GetOrRefreshShaderGraphObject(); if (sg != null) { //This path is only used with old shader graph integration. It doesn't support keyword. var shaderGraphProperties = VFXShaderGraphHelpers.GetProperties(sg).Where(o => o.keywordsMapping == null).Select(o => o.property); properties = properties.Concat(shaderGraphProperties); } return properties; } } protected override IEnumerable CollectGPUExpressions(IEnumerable slotExpressions) { foreach (var exp in base.CollectGPUExpressions(slotExpressions)) yield return exp; var shaderGraph = GetOrRefreshShaderGraphObject(); if (shaderGraph != null) { foreach (var sgProperty in shaderGraph.properties) { if (inputSlots.Any(t => t.property.name == sgProperty.referenceName)) yield return slotExpressions.First(o => o.name == sgProperty.referenceName); } } } protected virtual VFXOldShaderGraphHelpers.RPInfo currentRP { get { return VFXOldShaderGraphHelpers.hdrpInfo; } } public override IEnumerable additionalDefines { get { foreach (var def in base.additionalDefines) yield return def; var shaderGraph = GetOrRefreshShaderGraphObject(); if (shaderGraph != null && !shaderGraph.generatesWithShaderGraph) { foreach (var def in VFXOldShaderGraphHelpers.GetAdditionalDefinesGetAdditionalReplacement(shaderGraph, currentRP, graphCodes, taskType == VFXTaskType.ParticleMeshOutput)) yield return def; } } } public override VFXExpressionMapper GetExpressionMapper(VFXDeviceTarget target) { var mapper = base.GetExpressionMapper(target); var shaderGraph = GetOrRefreshShaderGraphObject(); switch (target) { case VFXDeviceTarget.CPU: { } break; case VFXDeviceTarget.GPU: if (shaderGraph != null) { foreach (var texture in VFXShaderGraphHelpers.GetTextureConstant(shaderGraph)) mapper.AddExpression(texture, -1); } break; } return mapper; } public virtual bool isLitShader { get => false; } Dictionary graphCodes; public override bool SetupCompilation() { if (!base.SetupCompilation()) return false; var shaderGraph = GetOrRefreshShaderGraphObject(); if (shaderGraph != null && !shaderGraph.generatesWithShaderGraph) { graphCodes = VFXOldShaderGraphHelpers.BuildGraphCode(shaderGraph, currentRP, isLitShader); return graphCodes != null; } return true; } public override void EndCompilation() { graphCodes = null; } public override IEnumerable> additionalReplacements { get { foreach (var rep in base.additionalReplacements) yield return rep; var shaderGraph = GetOrRefreshShaderGraphObject(); if (shaderGraph != null && !shaderGraph.generatesWithShaderGraph) { foreach (var def in VFXOldShaderGraphHelpers.GetAdditionalReplacement(shaderGraph, currentRP, graphCodes, taskType == VFXTaskType.ParticleMeshOutput)) yield return def; } } } public ShaderGraphVfxAsset GetShaderGraph() { var shaderGraph = GetOrRefreshShaderGraphObject(); return shaderGraph; } } }