// ShaderLab doesn't understand virtual texture inputs, they need to be processed to replace the virtual texture input with the layers that compose it instead public static GraphInputData ProcessVirtualTextureProperty(VirtualTextureShaderProperty virtualTextureShaderProperty) { var layerReferenceNames = new List <string>(); virtualTextureShaderProperty.GetPropertyReferenceNames(layerReferenceNames); var virtualTextureLayerDataList = new List <SubPropertyData>(); // Skip the first entry in this list as that's the Virtual Texture reference name itself, which won't exist in ShaderLab foreach (var referenceName in layerReferenceNames.Skip(1)) { var layerPropertyData = new SubPropertyData() { referenceName = referenceName, propertyType = PropertyType.Texture2D }; virtualTextureLayerDataList.Add(layerPropertyData); } var virtualTexturePropertyData = new GraphInputData() { referenceName = virtualTextureShaderProperty.displayName, propertyType = PropertyType.VirtualTexture, isKeyword = false }; virtualTexturePropertyData.isCompoundProperty = true; virtualTexturePropertyData.subProperties = virtualTextureLayerDataList; return(virtualTexturePropertyData); }
public override void OnImportAsset(AssetImportContext ctx) { var importLog = new AssetImportErrorLog(ctx); string path = ctx.assetPath; AssetCollection assetCollection = new AssetCollection(); MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection); var textGraph = File.ReadAllText(path, Encoding.UTF8); var graph = new GraphData { messageManager = new MessageManager(), assetGuid = AssetDatabase.AssetPathToGUID(path) }; MultiJson.Deserialize(graph, textGraph); graph.OnEnable(); graph.ValidateGraph(); UnityEngine.Object mainObject = null; #if VFX_GRAPH_10_0_0_OR_NEWER if (!graph.isOnlyVFXTarget) #endif { // build shaders mainObject = BuildAllShaders(ctx, importLog, assetCollection, graph); } #if VFX_GRAPH_10_0_0_OR_NEWER ShaderGraphVfxAsset vfxAsset = null; if (graph.hasVFXTarget) { vfxAsset = GenerateVfxShaderGraphAsset(graph); if (mainObject == null) { mainObject = vfxAsset; } else { //Correct main object if we have a shader and ShaderGraphVfxAsset : save as sub asset vfxAsset.name = Path.GetFileNameWithoutExtension(path); ctx.AddObjectToAsset("VFXShaderGraph", vfxAsset); } } #endif Texture2D texture = Resources.Load <Texture2D>("Icons/sg_graph_icon"); ctx.AddObjectToAsset("MainAsset", mainObject, texture); ctx.SetMainObject(mainObject); foreach (var target in graph.activeTargets) { if (target is IHasMetadata iHasMetadata) { var metadata = iHasMetadata.GetMetadataObject(); if (metadata == null) { continue; } metadata.hideFlags = HideFlags.HideInHierarchy; ctx.AddObjectToAsset($"{iHasMetadata.identifier}:Metadata", metadata); } } var sgMetadata = ScriptableObject.CreateInstance <ShaderGraphMetadata>(); sgMetadata.hideFlags = HideFlags.HideInHierarchy; sgMetadata.assetDependencies = new List <UnityEngine.Object>(); foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage)) { // this sucks that we have to fully load these assets just to set the reference, // which then gets serialized as the GUID that we already have here. :P var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key); if (!string.IsNullOrEmpty(dependencyPath)) { sgMetadata.assetDependencies.Add( AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object))); } } } List <GraphInputData> inputInspectorDataList = new List <GraphInputData>(); foreach (AbstractShaderProperty property in graph.properties) { // Don't write out data for non-exposed blackboard items if (!property.isExposed) { continue; } // VTs are treated differently if (property is VirtualTextureShaderProperty virtualTextureShaderProperty) { inputInspectorDataList.Add(MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty)); } else { inputInspectorDataList.Add(new GraphInputData() { referenceName = property.referenceName, propertyType = property.propertyType, isKeyword = false }); } } foreach (ShaderKeyword keyword in graph.keywords) { // Don't write out data for non-exposed blackboard items if (!keyword.isExposed) { continue; } var sanitizedReferenceName = keyword.referenceName; if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON")) { sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty); } inputInspectorDataList.Add(new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true }); } sgMetadata.categoryDatas = new List <MinimalCategoryData>(); foreach (CategoryData categoryData in graph.categories) { // Don't write out empty categories if (categoryData.childCount == 0) { continue; } MinimalCategoryData mcd = new MinimalCategoryData() { categoryName = categoryData.name, propertyDatas = new List <GraphInputData>() }; foreach (var input in categoryData.Children) { GraphInputData propData; // Only write out data for exposed blackboard items if (input.isExposed == false) { continue; } // VTs are treated differently if (input is VirtualTextureShaderProperty virtualTextureShaderProperty) { propData = MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty); inputInspectorDataList.RemoveAll(inputData => inputData.referenceName == propData.referenceName); mcd.propertyDatas.Add(propData); continue; } else if (input is ShaderKeyword keyword) { var sanitizedReferenceName = keyword.referenceName; if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON")) { sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty); } propData = new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true }; } else { var prop = input as AbstractShaderProperty; propData = new GraphInputData() { referenceName = input.referenceName, propertyType = prop.propertyType, isKeyword = false }; } mcd.propertyDatas.Add(propData); inputInspectorDataList.Remove(propData); } sgMetadata.categoryDatas.Add(mcd); } // Any uncategorized elements get tossed into an un-named category at the top as a fallback if (inputInspectorDataList.Count > 0) { sgMetadata.categoryDatas.Insert(0, new MinimalCategoryData() { categoryName = "", propertyDatas = inputInspectorDataList }); } ctx.AddObjectToAsset("SGInternal:Metadata", sgMetadata); // declare dependencies foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency)) { ctx.DependsOnSourceAsset(asset.Key); // I'm not sure if this warning below is actually used or not, keeping it to be safe var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key); // Ensure that dependency path is relative to project if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/")) { importLog.LogWarning($"Invalid dependency path: {assetPath}", mainObject); } } // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency)) { ctx.DependsOnArtifact(asset.Key); } } }
public override void OnImportAsset(AssetImportContext ctx) { var oldShader = AssetDatabase.LoadAssetAtPath <Shader>(ctx.assetPath); if (oldShader != null) { ShaderUtil.ClearShaderMessages(oldShader); } List <PropertyCollector.TextureInfo> configuredTextures; string path = ctx.assetPath; AssetCollection assetCollection = new AssetCollection(); MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection); var textGraph = File.ReadAllText(path, Encoding.UTF8); var graph = new GraphData { messageManager = new MessageManager(), assetGuid = AssetDatabase.AssetPathToGUID(path) }; MultiJson.Deserialize(graph, textGraph); graph.OnEnable(); graph.ValidateGraph(); Shader shader = null; #if VFX_GRAPH_10_0_0_OR_NEWER if (!graph.isOnlyVFXTarget) #endif { // build the shader text // this will also add Target dependencies into the asset collection var text = GetShaderText(path, out configuredTextures, assetCollection, graph); #if UNITY_2021_1_OR_NEWER // 2021.1 or later is guaranteed to have the new version of this function shader = ShaderUtil.CreateShaderAsset(ctx, text, false); #else // earlier builds of Unity may or may not have it // here we try to invoke the new version via reflection var createShaderAssetMethod = typeof(ShaderUtil).GetMethod( "CreateShaderAsset", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.ExactBinding, null, new Type[] { typeof(AssetImportContext), typeof(string), typeof(bool) }, null); if (createShaderAssetMethod != null) { shader = createShaderAssetMethod.Invoke(null, new Object[] { ctx, text, false }) as Shader; } else { // method doesn't exist in this version of Unity, call old version // this doesn't create dependencies properly, but is the best that we can do shader = ShaderUtil.CreateShaderAsset(text, false); } #endif ReportErrors(graph, shader, path); EditorMaterialUtility.SetShaderDefaults( shader, configuredTextures.Where(x => x.modifiable).Select(x => x.name).ToArray(), configuredTextures.Where(x => x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); EditorMaterialUtility.SetShaderNonModifiableDefaults( shader, configuredTextures.Where(x => !x.modifiable).Select(x => x.name).ToArray(), configuredTextures.Where(x => !x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); } UnityEngine.Object mainObject = shader; #if VFX_GRAPH_10_0_0_OR_NEWER ShaderGraphVfxAsset vfxAsset = null; if (graph.hasVFXTarget) { vfxAsset = GenerateVfxShaderGraphAsset(graph); if (mainObject == null) { mainObject = vfxAsset; } else { //Correct main object if we have a shader and ShaderGraphVfxAsset : save as sub asset vfxAsset.name = Path.GetFileNameWithoutExtension(path); ctx.AddObjectToAsset("VFXShaderGraph", vfxAsset); } } #endif Texture2D texture = Resources.Load <Texture2D>("Icons/sg_graph_icon"); ctx.AddObjectToAsset("MainAsset", mainObject, texture); ctx.SetMainObject(mainObject); foreach (var target in graph.activeTargets) { if (target is IHasMetadata iHasMetadata) { var metadata = iHasMetadata.GetMetadataObject(); if (metadata == null) { continue; } metadata.hideFlags = HideFlags.HideInHierarchy; ctx.AddObjectToAsset($"{iHasMetadata.identifier}:Metadata", metadata); } } var sgMetadata = ScriptableObject.CreateInstance <ShaderGraphMetadata>(); sgMetadata.hideFlags = HideFlags.HideInHierarchy; sgMetadata.assetDependencies = new List <UnityEngine.Object>(); foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage)) { // this sucks that we have to fully load these assets just to set the reference, // which then gets serialized as the GUID that we already have here. :P var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key); if (!string.IsNullOrEmpty(dependencyPath)) { sgMetadata.assetDependencies.Add( AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object))); } } } List <GraphInputData> inputInspectorDataList = new List <GraphInputData>(); foreach (AbstractShaderProperty property in graph.properties) { // Don't write out data for non-exposed blackboard items if (!property.isExposed) { continue; } // VTs are treated differently if (property is VirtualTextureShaderProperty virtualTextureShaderProperty) { inputInspectorDataList.Add(MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty)); } else { inputInspectorDataList.Add(new GraphInputData() { referenceName = property.referenceName, propertyType = property.propertyType, isKeyword = false }); } } foreach (ShaderKeyword keyword in graph.keywords) { // Don't write out data for non-exposed blackboard items if (!keyword.isExposed) { continue; } var sanitizedReferenceName = keyword.referenceName; if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON")) { sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty); } inputInspectorDataList.Add(new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true }); } sgMetadata.categoryDatas = new List <MinimalCategoryData>(); foreach (CategoryData categoryData in graph.categories) { // Don't write out empty categories if (categoryData.childCount == 0) { continue; } MinimalCategoryData mcd = new MinimalCategoryData() { categoryName = categoryData.name, propertyDatas = new List <GraphInputData>() }; foreach (var input in categoryData.Children) { GraphInputData propData; // Only write out data for exposed blackboard items if (input.isExposed == false) { continue; } // VTs are treated differently if (input is VirtualTextureShaderProperty virtualTextureShaderProperty) { propData = MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty); inputInspectorDataList.RemoveAll(inputData => inputData.referenceName == propData.referenceName); mcd.propertyDatas.Add(propData); continue; } else if (input is ShaderKeyword keyword) { var sanitizedReferenceName = keyword.referenceName; if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON")) { sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty); } propData = new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true }; } else { var prop = input as AbstractShaderProperty; propData = new GraphInputData() { referenceName = input.referenceName, propertyType = prop.propertyType, isKeyword = false }; } mcd.propertyDatas.Add(propData); inputInspectorDataList.Remove(propData); } sgMetadata.categoryDatas.Add(mcd); } // Any uncategorized elements get tossed into an un-named category at the top as a fallback if (inputInspectorDataList.Count > 0) { sgMetadata.categoryDatas.Insert(0, new MinimalCategoryData() { categoryName = "", propertyDatas = inputInspectorDataList }); } ctx.AddObjectToAsset("SGInternal:Metadata", sgMetadata); // declare dependencies foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency)) { ctx.DependsOnSourceAsset(asset.Key); // I'm not sure if this warning below is actually used or not, keeping it to be safe var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key); // Ensure that dependency path is relative to project if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/")) { Debug.LogWarning($"Invalid dependency path: {assetPath}", mainObject); } } // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency)) { ctx.DependsOnArtifact(asset.Key); } } }