using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEngine; using UnityEditor.Graphing; namespace UnityEditor.ShaderGraph { [Serializable] public class LayeredShaderGraph : AbstractMaterialGraph, IShaderGraph { [Serializable] public class Layer { [SerializeField] private SerializableGuid m_Guid = new SerializableGuid(); [SerializeField] private Shader m_Shader; public Layer() { } public Guid guid { get { return m_Guid.guid; } } public Shader shader { get { return m_Shader; } set { m_Shader = value; } } } [NonSerialized] private List m_Layers = new List(); [NonSerialized] List m_AddedLayers = new List(); [NonSerialized] List m_RemovedLayers = new List(); [SerializeField] List m_SerializedLayers = new List(); public IEnumerable layers { get { return m_Layers; } } public List addedLayers { get { return m_AddedLayers; } } public List removedLayers { get { return m_RemovedLayers; } } [NonSerialized] private LayerWeightsOutputNode m_OutputNode; public LayerWeightsOutputNode outputNode { get { // find existing node if (m_OutputNode == null) m_OutputNode = GetNodes().FirstOrDefault(); return m_OutputNode; } } public override void ClearChanges() { base.ClearChanges(); m_AddedLayers.Clear(); m_RemovedLayers.Clear(); } public override void AddNode(INode node) { if (outputNode != null && node is LayerWeightsOutputNode) { Debug.LogWarning("Attempting to add second LayerWeightsOutputNode to LayeredShaderGraph. This is not allowed."); return; } base.AddNode(node); } public void AddLayer() { var layer = new Layer(); m_Layers.Add(layer); m_AddedLayers.Add(layer); if (outputNode != null) outputNode.onModified(outputNode, ModificationScope.Graph); } public bool SetLayer(Guid layerId, Shader newShader) { try { var path = AssetDatabase.GetAssetPath(newShader); if (!path.EndsWith("shaderGraph", StringComparison.InvariantCultureIgnoreCase)) return false; var textGraph = File.ReadAllText(path, Encoding.UTF8); var graph = JsonUtility.FromJson(textGraph); if (graph == null) return false; var layer = layers.FirstOrDefault(x => x.guid == layerId); if (layer == null) return false; layer.shader = newShader; if (outputNode != null) { outputNode.OnEnable(); outputNode.onModified(outputNode, ModificationScope.Graph); } return true; } catch (Exception) { // ignored } return false; } public void RemoveLayer(Guid id) { var num = m_Layers.RemoveAll(x => x.guid == id); if (num > 0) { m_RemovedLayers.Add(id); if (outputNode != null) outputNode.onModified(outputNode, ModificationScope.Graph); } } public override void OnBeforeSerialize() { base.OnBeforeSerialize(); m_SerializedLayers = SerializationHelper.Serialize(m_Layers); } public override void OnAfterDeserialize() { m_OutputNode = null; m_Layers = SerializationHelper.Deserialize(m_SerializedLayers, null); m_SerializedLayers = null; base.OnAfterDeserialize(); } public static string LayerToFunctionName(Guid id) { return string.Format("Layer_{0}", GuidEncoder.Encode(id)); } public string GetShader(string name, GenerationMode mode, out List configuredTextures) { if (outputNode == null) throw new InvalidOperationException(); var layerMap = new Dictionary(); foreach (var layer in layers) { var path = AssetDatabase.GetAssetPath(layer.shader); if (!path.EndsWith("shaderGraph", StringComparison.InvariantCultureIgnoreCase)) continue; var textGraph = File.ReadAllText(path, Encoding.UTF8); var graph = JsonUtility.FromJson(textGraph); if (graph == null) continue; layerMap[layer.guid] = graph; } if (layerMap.Count == 0) { configuredTextures = new List(); return string.Empty; } var vertexShader = new ShaderGenerator(); var layerShaders = new ShaderGenerator(); var surfaceDescriptionFunction = new ShaderGenerator(); var surfaceDescriptionStruct = new ShaderGenerator(); var shaderFunctionVisitor = new ShaderGenerator(); var surfaceInputs = new ShaderGenerator(); var graphVertexInput = @" struct GraphVertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 texcoord0 : TEXCOORD0; float4 lightmapUV : TEXCOORD1; UNITY_VERTEX_INPUT_INSTANCE_ID };"; surfaceInputs.AddShaderChunk("struct SurfaceInputs{", false); surfaceInputs.Indent(); using (var activeNodesDisposable = ListPool.GetDisposable()) { var activeNodes = activeNodesDisposable.value; foreach (var layer in layerMap) { NodeUtils.DepthFirstCollectNodesFromNode(activeNodes, layer.Value.masterNode as AbstractMaterialNode); } NodeUtils.DepthFirstCollectNodesFromNode(activeNodes, outputNode); var requirements = GetRequirements(activeNodes); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(requirements.requiresNormal, InterpolatorType.Normal, surfaceInputs); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(requirements.requiresTangent, InterpolatorType.Tangent, surfaceInputs); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(requirements.requiresBitangent, InterpolatorType.BiTangent, surfaceInputs); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(requirements.requiresViewDir, InterpolatorType.ViewDirection, surfaceInputs); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(requirements.requiresPosition, InterpolatorType.Position, surfaceInputs); if (requirements.requiresVertexColor) surfaceInputs.AddShaderChunk(string.Format("float4 {0};", ShaderGeneratorNames.VertexColor), false); if (requirements.requiresScreenPosition) surfaceInputs.AddShaderChunk(string.Format("float4 {0};", ShaderGeneratorNames.ScreenPosition), false); foreach (var channel in requirements.requiresMeshUVs.Distinct()) surfaceInputs.AddShaderChunk(string.Format("half4 {0};", channel.GetUVName()), false); surfaceInputs.Deindent(); surfaceInputs.AddShaderChunk("};", false); vertexShader.AddShaderChunk("GraphVertexInput PopulateVertexData(GraphVertexInput v){", false); vertexShader.Indent(); vertexShader.AddShaderChunk("return v;", false); vertexShader.Deindent(); vertexShader.AddShaderChunk("}", false); var shaderProperties = new PropertyCollector(); var baseGraph = layerMap.Values.FirstOrDefault(); if (baseGraph == null) { configuredTextures = new List(); return string.Empty; } var masterNode = baseGraph.masterNode; GenerateSurfaceDescriptionStruct(surfaceDescriptionStruct, ((AbstractMaterialNode) masterNode).GetInputSlots().ToList(), true); foreach (var layer in layerMap) { activeNodes.Clear(); NodeUtils.DepthFirstCollectNodesFromNode(activeNodes, layer.Value.masterNode as AbstractMaterialNode); GenerateSurfaceDescription( activeNodes, layer.Value.masterNode as AbstractMaterialNode, this, surfaceDescriptionFunction, shaderFunctionVisitor, shaderProperties, requirements, mode, LayerToFunctionName(layer.Key)); } surfaceDescriptionStruct.AddShaderChunk("struct WeightsSurfaceDescription{", false); surfaceDescriptionStruct.Indent(); foreach (var slot in outputNode.GetInputSlots()) surfaceDescriptionStruct.AddShaderChunk(AbstractMaterialNode.ConvertConcreteSlotValueTypeToString(AbstractMaterialNode.OutputPrecision.@float, slot.concreteValueType) + " " + slot.shaderOutputName + ";", false); surfaceDescriptionStruct.Deindent(); surfaceDescriptionStruct.AddShaderChunk("};", false); activeNodes.Clear(); NodeUtils.DepthFirstCollectNodesFromNode(activeNodes, outputNode); GenerateSurfaceDescription( activeNodes, outputNode, this, surfaceDescriptionFunction, shaderFunctionVisitor, shaderProperties, requirements, mode, "PopulateWeightsGraph", "WeightsSurfaceDescription"); string functionName = "PopulateSurfaceData"; string surfaceDescriptionName = "SurfaceDescription"; layerShaders.AddShaderChunk(string.Format("{0} {1}(SurfaceInputs IN) {{", surfaceDescriptionName, functionName), false); layerShaders.Indent(); layerShaders.AddShaderChunk("WeightsSurfaceDescription weights = PopulateWeightsGraph(IN);", false); layerShaders.AddShaderChunk("SurfaceDescription result = (SurfaceDescription)0;", false); foreach (var layer in layerMap) { layerShaders.AddShaderChunk( string.Format( "{0} {1} = {2}({3});", surfaceDescriptionName, LayerToFunctionName(layer.Key) + "_surface", LayerToFunctionName(layer.Key), "IN"), false); layerShaders.AddShaderChunk( string.Format("ScaleSurfaceDescription({0}_surface, weights.{0});", LayerToFunctionName(layer.Key)), false); layerShaders.AddShaderChunk(string.Format("AddSurfaceDescription(result, {0}_surface);", LayerToFunctionName(layer.Key)), false); } layerShaders.AddShaderChunk("return result;", false); layerShaders.Deindent(); layerShaders.AddShaderChunk("}", false); var finalShader = new ShaderGenerator(); finalShader.AddShaderChunk(string.Format(@"Shader ""{0}""", name), false); finalShader.AddShaderChunk("{", false); finalShader.Indent(); finalShader.AddShaderChunk("Properties", false); finalShader.AddShaderChunk("{", false); finalShader.Indent(); finalShader.AddShaderChunk(shaderProperties.GetPropertiesBlock(2), false); finalShader.Deindent(); finalShader.AddShaderChunk("}", false); finalShader.AddShaderChunk("CGINCLUDE", false); finalShader.AddShaderChunk("#include \"UnityCG.cginc\"", false); finalShader.AddShaderChunk(shaderFunctionVisitor.GetShaderString(2), false); finalShader.AddShaderChunk(graphVertexInput, false); finalShader.AddShaderChunk(surfaceInputs.GetShaderString(2), false); finalShader.AddShaderChunk(surfaceDescriptionStruct.GetShaderString(2), false); finalShader.AddShaderChunk(shaderProperties.GetPropertiesDeclaration(2), false); finalShader.AddShaderChunk(vertexShader.GetShaderString(2), false); finalShader.AddShaderChunk(surfaceDescriptionFunction.GetShaderString(2), false); finalShader.AddShaderChunk(layerShaders.GetShaderString(2), false); finalShader.AddShaderChunk("ENDCG", false); if (masterNode != null) { var subShaders = masterNode.GetSubshader(requirements, null); foreach (var ss in subShaders) finalShader.AddShaderChunk(ss, false); } finalShader.Deindent(); finalShader.AddShaderChunk("}", false); configuredTextures = shaderProperties.GetConfiguredTexutres(); return finalShader.GetShaderString(0); } } public void LoadedFromDisk() { OnEnable(); ValidateGraph(); } } }