using System; using System.Collections.Generic; using System.Linq; using UnityEditor.ShaderGraph.Drawing.Controls; using UnityEngine; using UnityEditor.Graphing; namespace UnityEditor.ShaderGraph { [Title("Utility", "Sub-graph")] public class SubGraphNode : AbstractMaterialNode , IGeneratesBodyCode , IOnAssetEnabled , IGeneratesFunction , IMayRequireNormal , IMayRequireTangent , IMayRequireBitangent , IMayRequireMeshUV , IMayRequireScreenPosition , IMayRequireViewDirection , IMayRequirePosition , IMayRequireVertexColor , IMayRequireTime { [SerializeField] private string m_SerializedSubGraph = string.Empty; [Serializable] private class SubGraphHelper { public MaterialSubGraphAsset subGraph; } protected SubGraph referencedGraph { get { if (subGraphAsset == null) return null; return subGraphAsset.subGraph; } } #if UNITY_EDITOR [ObjectControl("")] public MaterialSubGraphAsset subGraphAsset { get { if (string.IsNullOrEmpty(m_SerializedSubGraph)) return null; var helper = new SubGraphHelper(); EditorJsonUtility.FromJsonOverwrite(m_SerializedSubGraph, helper); return helper.subGraph; } set { if (subGraphAsset == value) return; var helper = new SubGraphHelper(); helper.subGraph = value; m_SerializedSubGraph = EditorJsonUtility.ToJson(helper, true); UpdateSlots(); Dirty(ModificationScope.Topological); } } #else public MaterialSubGraphAsset subGraphAsset {get; set; } #endif public INode outputNode { get { if (subGraphAsset != null && subGraphAsset.subGraph != null) return subGraphAsset.subGraph.outputNode; return null; } } public override bool hasPreview { get { return referencedGraph != null; } } public override PreviewMode previewMode { get { if (referencedGraph == null) return PreviewMode.Preview2D; return PreviewMode.Preview3D; } } public SubGraphNode() { name = "Sub-graph"; } public override bool allowedInSubGraph { get { return false; } } public override string documentationURL { get { return "https://github.com/Unity-Technologies/ShaderGraph/wiki/Sub-graph-Node"; } } public void GenerateNodeCode(ShaderGenerator visitor, GenerationMode generationMode) { if (referencedGraph == null) return; foreach (var outSlot in referencedGraph.graphOutputs) visitor.AddShaderChunk(string.Format("{0} {1};", NodeUtils.ConvertConcreteSlotValueTypeToString(precision, outSlot.concreteValueType), GetVariableNameForSlot(outSlot.id)), true); var arguments = new List(); foreach (var prop in referencedGraph.graphInputs) { var inSlotId = prop.guid.GetHashCode(); if (prop is TextureShaderProperty) arguments.Add(string.Format("TEXTURE2D_PARAM({0}, sampler{0})", GetSlotValue(inSlotId, generationMode))); if (prop is Texture2DArrayShaderProperty) arguments.Add(string.Format("TEXTURE2D_PARAM({0}, sampler{0})", GetSlotValue(inSlotId, generationMode))); if (prop is Texture3DShaderProperty) arguments.Add(string.Format("TEXTURE3D_PARAM({0}, sampler{0})", GetSlotValue(inSlotId, generationMode))); else if (prop is CubemapShaderProperty) arguments.Add(string.Format("TEXTURECUBE_PARAM({0}, sampler{0})", GetSlotValue(inSlotId, generationMode))); else arguments.Add(GetSlotValue(inSlotId, generationMode)); } // pass surface inputs through arguments.Add("IN"); foreach (var outSlot in referencedGraph.graphOutputs) arguments.Add(GetVariableNameForSlot(outSlot.id)); visitor.AddShaderChunk( string.Format("{0}({1});" , SubGraphFunctionName() , arguments.Aggregate((current, next) => string.Format("{0}, {1}", current, next))) , false); } public void OnEnable() { UpdateSlots(); } public virtual void UpdateSlots() { var validNames = new List(); if (referencedGraph == null) { RemoveSlotsNameNotMatching(validNames); return; } var props = referencedGraph.properties; foreach (var prop in props) { var propType = prop.propertyType; SlotValueType slotType; switch (propType) { case PropertyType.Color: slotType = SlotValueType.Vector4; break; case PropertyType.Texture: slotType = SlotValueType.Texture2D; break; case PropertyType.Texture2DArray: slotType = SlotValueType.Texture2DArray; break; case PropertyType.Texture3D: slotType = SlotValueType.Texture3D; break; case PropertyType.Cubemap: slotType = SlotValueType.Cubemap; break; case PropertyType.Gradient: slotType = SlotValueType.Gradient; break; case PropertyType.Vector1: slotType = SlotValueType.Vector1; break; case PropertyType.Vector2: slotType = SlotValueType.Vector2; break; case PropertyType.Vector3: slotType = SlotValueType.Vector3; break; case PropertyType.Vector4: slotType = SlotValueType.Vector4; break; case PropertyType.Matrix2: slotType = SlotValueType.Matrix2; break; case PropertyType.Matrix3: slotType = SlotValueType.Matrix3; break; case PropertyType.Matrix4: slotType = SlotValueType.Matrix4; break; default: throw new ArgumentOutOfRangeException(); } var id = prop.guid.GetHashCode(); MaterialSlot slot = MaterialSlot.CreateMaterialSlot(slotType, id, prop.displayName, prop.referenceName, SlotType.Input, prop.defaultValue, ShaderStageCapability.Fragment); // copy default for texture for niceness if (slotType == SlotValueType.Texture2D && propType == PropertyType.Texture) { var tSlot = slot as Texture2DInputMaterialSlot; var tProp = prop as TextureShaderProperty; if (tSlot != null && tProp != null) tSlot.texture = tProp.value.texture; } // copy default for texture array for niceness else if (slotType == SlotValueType.Texture2DArray && propType == PropertyType.Texture2DArray) { var tSlot = slot as Texture2DArrayInputMaterialSlot; var tProp = prop as Texture2DArrayShaderProperty; if (tSlot != null && tProp != null) tSlot.textureArray = tProp.value.textureArray; } // copy default for texture 3d for niceness else if (slotType == SlotValueType.Texture3D && propType == PropertyType.Texture3D) { var tSlot = slot as Texture3DInputMaterialSlot; var tProp = prop as Texture3DShaderProperty; if (tSlot != null && tProp != null) tSlot.texture = tProp.value.texture; } // copy default for cubemap for niceness else if (slotType == SlotValueType.Cubemap && propType == PropertyType.Cubemap) { var tSlot = slot as CubemapInputMaterialSlot; var tProp = prop as CubemapShaderProperty; if (tSlot != null && tProp != null) tSlot.cubemap = tProp.value.cubemap; } AddSlot(slot); validNames.Add(id); } var subGraphOutputNode = outputNode; if (outputNode != null) { foreach (var slot in NodeExtensions.GetInputSlots(subGraphOutputNode)) { AddSlot(MaterialSlot.CreateMaterialSlot(slot.valueType, slot.id, slot.RawDisplayName(), slot.shaderOutputName, SlotType.Output, Vector4.zero, ShaderStageCapability.Fragment)); validNames.Add(slot.id); } } RemoveSlotsNameNotMatching(validNames); } public override void ValidateNode() { if (referencedGraph != null) { referencedGraph.OnEnable(); referencedGraph.ValidateGraph(); if (referencedGraph.GetNodes().Any(x => x.hasError)) hasError = true; } base.ValidateNode(); } public override void CollectShaderProperties(PropertyCollector visitor, GenerationMode generationMode) { base.CollectShaderProperties(visitor, generationMode); if (referencedGraph == null) return; referencedGraph.CollectShaderProperties(visitor, GenerationMode.ForReals); } public override void CollectPreviewMaterialProperties(List properties) { base.CollectPreviewMaterialProperties(properties); if (referencedGraph == null) return; properties.AddRange(referencedGraph.GetPreviewProperties()); } private string SubGraphFunctionName() { var functionName = subGraphAsset != null ? NodeUtils.GetHLSLSafeName(subGraphAsset.name) : "ERROR"; return string.Format("sg_{0}_{1}", functionName, GuidEncoder.Encode(referencedGraph.guid)); } public virtual void GenerateNodeFunction(FunctionRegistry registry, GraphContext graphContext, GenerationMode generationMode) { if (subGraphAsset == null || referencedGraph == null) return; referencedGraph.GenerateNodeFunction(registry, graphContext, GenerationMode.ForReals); referencedGraph.GenerateSubGraphFunction(SubGraphFunctionName(), registry, graphContext, ShaderGraphRequirements.FromNodes(new List {this}), GenerationMode.ForReals); } public NeededCoordinateSpace RequiresNormal(ShaderStageCapability stageCapability) { if (referencedGraph == null) return NeededCoordinateSpace.None; return referencedGraph.activeNodes.OfType().Aggregate(NeededCoordinateSpace.None, (mask, node) => { mask |= node.RequiresNormal(stageCapability); return mask; }); } public bool RequiresMeshUV(UVChannel channel, ShaderStageCapability stageCapability) { if (referencedGraph == null) return false; return referencedGraph.activeNodes.OfType().Any(x => x.RequiresMeshUV(channel, stageCapability)); } public bool RequiresScreenPosition(ShaderStageCapability stageCapability) { if (referencedGraph == null) return false; return referencedGraph.activeNodes.OfType().Any(x => x.RequiresScreenPosition(stageCapability)); } public NeededCoordinateSpace RequiresViewDirection(ShaderStageCapability stageCapability) { if (referencedGraph == null) return NeededCoordinateSpace.None; return referencedGraph.activeNodes.OfType().Aggregate(NeededCoordinateSpace.None, (mask, node) => { mask |= node.RequiresViewDirection(stageCapability); return mask; }); } public NeededCoordinateSpace RequiresPosition(ShaderStageCapability stageCapability) { if (referencedGraph == null) return NeededCoordinateSpace.None; return referencedGraph.activeNodes.OfType().Aggregate(NeededCoordinateSpace.None, (mask, node) => { mask |= node.RequiresPosition(stageCapability); return mask; }); } public NeededCoordinateSpace RequiresTangent(ShaderStageCapability stageCapability) { if (referencedGraph == null) return NeededCoordinateSpace.None; return referencedGraph.activeNodes.OfType().Aggregate(NeededCoordinateSpace.None, (mask, node) => { mask |= node.RequiresTangent(stageCapability); return mask; }); } public bool RequiresTime() { if (referencedGraph == null) return false; return referencedGraph.activeNodes.OfType().Any(x => x.RequiresTime()); } public NeededCoordinateSpace RequiresBitangent(ShaderStageCapability stageCapability) { if (referencedGraph == null) return NeededCoordinateSpace.None; return referencedGraph.activeNodes.OfType().Aggregate(NeededCoordinateSpace.None, (mask, node) => { mask |= node.RequiresBitangent(stageCapability); return mask; }); } public bool RequiresVertexColor(ShaderStageCapability stageCapability) { if (referencedGraph == null) return false; return referencedGraph.activeNodes.OfType().Any(x => x.RequiresVertexColor(stageCapability)); } } }