using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using UnityEngine.Graphing; namespace UnityEngine.MaterialGraph { [Serializable] public abstract class AbstractSurfaceMasterNode : AbstractMasterNode { public const string AlbedoSlotName = "Albedo"; public const string NormalSlotName = "Normal"; public const string EmissionSlotName = "Emission"; public const string SmoothnessSlotName = "Smoothness"; public const string OcclusionSlotName = "Occlusion"; public const string AlphaSlotName = "Alpha"; public const string VertexOffsetName = "VertexPosition"; public const int AlbedoSlotId = 0; public const int NormalSlotId = 1; public const int EmissionSlotId = 3; public const int SmoothnessSlotId = 4; public const int OcclusionSlotId = 5; public const int AlphaSlotId = 6; public const int VertexOffsetId = 7; [SerializeField] private SurfaceMaterialOptions m_MaterialOptions = new SurfaceMaterialOptions(); public SurfaceMaterialOptions options { get { return m_MaterialOptions; } } public abstract string GetSurfaceOutputName(); public abstract string GetLightFunction(); void GenerateNodeFunctionsAndPropertyUsages( ShaderGenerator shaderBody, ShaderGenerator propertyUsages, ShaderGenerator nodeFunction, GenerationMode mode, int[] validNodeIds) { var activeNodeList = new List(); NodeUtils.DepthFirstCollectNodesFromNode(activeNodeList, this, NodeUtils.IncludeSelf.Include, new List(validNodeIds)); foreach (var node in activeNodeList.OfType()) { if (node is IGeneratesFunction) (node as IGeneratesFunction).GenerateNodeFunction(nodeFunction, mode); node.GeneratePropertyUsages(propertyUsages, mode); } var nodes = ListPool.Get(); //Get the rest of the nodes for all the other slots NodeUtils.DepthFirstCollectNodesFromNode(nodes, this, NodeUtils.IncludeSelf.Exclude, new List(vertexInputs)); for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; if (node is IGeneratesBodyCode) (node as IGeneratesBodyCode).GenerateNodeCode(shaderBody, mode); } ListPool.Release(nodes); } void GenerateVertexShaderInternal( ShaderGenerator propertyUsages, ShaderGenerator shaderBody, ShaderGenerator nodeFunction, ShaderGenerator vertexShaderBlock, GenerationMode mode) { GenerateNodeFunctionsAndPropertyUsages(vertexShaderBlock, propertyUsages, nodeFunction, mode, vertexInputs); var slot = FindInputSlot(VertexOffsetId); foreach (var edge in owner.GetEdges(slot.slotReference)) { var outputRef = edge.outputSlot; var fromNode = owner.GetNodeFromGuid(outputRef.nodeGuid); if (fromNode == null) continue; var remapper = fromNode as INodeGroupRemapper; if (remapper != null && !remapper.IsValidSlotConnection(outputRef.slotId)) continue; vertexShaderBlock.AddShaderChunk("v.vertex.xyz = " + fromNode.GetVariableNameForSlot(outputRef.slotId) + ";", true); } } public override string GetSubShader(GenerationMode mode, PropertyGenerator shaderPropertiesVisitor) { var templateLocation = ShaderGenerator.GetTemplatePath("subshader.template"); if (!File.Exists(templateLocation)) return string.Empty; var activeNodeList = new List(); NodeUtils.DepthFirstCollectNodesFromNode(activeNodeList, this); foreach (var node in activeNodeList.OfType()) node.GeneratePropertyBlock(shaderPropertiesVisitor, mode); var templateText = File.ReadAllText(templateLocation); var shaderBodyVisitor = new ShaderGenerator(); var shaderFunctionVisitor = new ShaderGenerator(); var shaderPropertyUsagesVisitor = new ShaderGenerator(); var shaderInputVisitor = new ShaderGenerator(); var vertexShaderBlock = new ShaderGenerator(); GenerateSurfaceShaderInternal( shaderPropertyUsagesVisitor, shaderBodyVisitor, shaderFunctionVisitor, shaderInputVisitor, vertexShaderBlock, mode); GenerateVertexShaderInternal( shaderPropertyUsagesVisitor, shaderBodyVisitor, shaderFunctionVisitor, vertexShaderBlock, mode); var tagsVisitor = new ShaderGenerator(); var blendingVisitor = new ShaderGenerator(); var cullingVisitor = new ShaderGenerator(); var zTestVisitor = new ShaderGenerator(); var zWriteVisitor = new ShaderGenerator(); m_MaterialOptions.GetTags(tagsVisitor); m_MaterialOptions.GetBlend(blendingVisitor); m_MaterialOptions.GetCull(cullingVisitor); m_MaterialOptions.GetDepthTest(zTestVisitor); m_MaterialOptions.GetDepthWrite(zWriteVisitor); var resultShader = templateText.Replace("${ShaderPropertyUsages}", shaderPropertyUsagesVisitor.GetShaderString(2)); resultShader = resultShader.Replace("${LightingFunctionName}", GetLightFunction()); resultShader = resultShader.Replace("${SurfaceOutputStructureName}", GetSurfaceOutputName()); resultShader = resultShader.Replace("${ShaderFunctions}", shaderFunctionVisitor.GetShaderString(2)); resultShader = resultShader.Replace("${ShaderInputs}", shaderInputVisitor.GetShaderString(3)); resultShader = resultShader.Replace("${PixelShaderBody}", shaderBodyVisitor.GetShaderString(3)); resultShader = resultShader.Replace("${Tags}", tagsVisitor.GetShaderString(2)); resultShader = resultShader.Replace("${Blending}", blendingVisitor.GetShaderString(2)); resultShader = resultShader.Replace("${Culling}", cullingVisitor.GetShaderString(2)); resultShader = resultShader.Replace("${ZTest}", zTestVisitor.GetShaderString(2)); resultShader = resultShader.Replace("${ZWrite}", zWriteVisitor.GetShaderString(2)); resultShader = resultShader.Replace("${LOD}", ""+m_MaterialOptions.lod); resultShader = resultShader.Replace("${VertexShaderDecl}", "vertex:vert"); resultShader = resultShader.Replace("${VertexShaderBody}", vertexShaderBlock.GetShaderString(3)); return resultShader; } public override string GetFullShader( GenerationMode mode, out List configuredTextures) { var templateLocation = ShaderGenerator.GetTemplatePath("shader.template"); if (!File.Exists(templateLocation)) { configuredTextures = new List(); return string.Empty; } var templateText = File.ReadAllText(templateLocation); var shaderPropertiesVisitor = new PropertyGenerator(); var resultShader = templateText.Replace("${ShaderName}", GetType() + guid.ToString()); resultShader = resultShader.Replace("${SubShader}", GetSubShader(mode, shaderPropertiesVisitor)); resultShader = resultShader.Replace("${ShaderPropertiesHeader}", shaderPropertiesVisitor.GetShaderString(2)); configuredTextures = shaderPropertiesVisitor.GetConfiguredTexutres(); return Regex.Replace(resultShader, @"\r\n|\n\r|\n|\r", Environment.NewLine); } protected abstract int[] surfaceInputs { get; } protected abstract int[] vertexInputs { get; } private void GenerateSurfaceShaderInternal( ShaderGenerator propertyUsages, ShaderGenerator shaderBody, ShaderGenerator nodeFunction, ShaderGenerator shaderInputVisitor, ShaderGenerator vertexShaderBlock, GenerationMode mode) { var activeNodeList = new List(); NodeUtils.DepthFirstCollectNodesFromNode(activeNodeList, this, NodeUtils.IncludeSelf.Include, new List(surfaceInputs)); foreach (var node in activeNodeList.OfType()) { if (node is IGeneratesFunction) (node as IGeneratesFunction).GenerateNodeFunction(nodeFunction, mode); node.GeneratePropertyUsages(propertyUsages, mode); } // always add color because why not. shaderInputVisitor.AddShaderChunk("float4 color : COLOR;", true); bool requiresBitangent = activeNodeList.OfType().Any(x => x.RequiresBitangent()); bool requiresTangent = activeNodeList.OfType().Any(x => x.RequiresTangent()); bool requiresViewDirTangentSpace = activeNodeList.OfType().Any(x => x.RequiresViewDirectionTangentSpace()); bool requiresViewDir = activeNodeList.OfType().Any(x => x.RequiresViewDirection()); bool requiresWorldPos = activeNodeList.OfType().Any(x => x.RequiresWorldPosition()); bool requiresNormal = activeNodeList.OfType().Any(x => x.RequiresNormal()); bool requiresScreenPosition = activeNodeList.OfType().Any(x => x.RequiresScreenPosition()); bool requiresVertexColor = activeNodeList.OfType().Any(x => x.RequiresVertexColor()); for (int uvIndex = 0; uvIndex < ShaderGeneratorNames.UVCount; ++uvIndex) { var channel = (UVChannel)uvIndex; if (activeNodeList.OfType().Any(x => x.RequiresMeshUV(channel))) { shaderInputVisitor.AddShaderChunk(string.Format("half4 meshUV{0};", uvIndex), true); vertexShaderBlock.AddShaderChunk(string.Format("o.meshUV{0} = v.texcoord{1};", uvIndex, uvIndex == 0 ? "" : uvIndex.ToString()), true); shaderBody.AddShaderChunk(string.Format("half4 {0} = IN.meshUV{1};", channel.GetUVName(), uvIndex), true); } } if (requiresViewDir || requiresViewDirTangentSpace) { shaderInputVisitor.AddShaderChunk("float3 worldViewDir;", true); shaderBody.AddShaderChunk("float3 " + ShaderGeneratorNames.WorldSpaceViewDirection + " = IN.worldViewDir;", true); } if (requiresWorldPos) { shaderInputVisitor.AddShaderChunk("float3 worldPos;", true); shaderBody.AddShaderChunk("float3 " + ShaderGeneratorNames.WorldSpacePosition + " = IN.worldPos;", true); } if (requiresScreenPosition) { shaderInputVisitor.AddShaderChunk("float4 screenPos;", true); shaderBody.AddShaderChunk("float4 " + ShaderGeneratorNames.ScreenPosition + " = IN.screenPos;", true); } if (requiresBitangent || requiresTangent || requiresViewDirTangentSpace) { shaderInputVisitor.AddShaderChunk("float4 worldTangent;", true); vertexShaderBlock.AddShaderChunk("o.worldTangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);", true); shaderBody.AddShaderChunk("float3 " + ShaderGeneratorNames.WorldSpaceTangent + " = normalize(IN.worldTangent.xyz);", true); } if (requiresBitangent || requiresNormal || requiresViewDirTangentSpace) { // is the normal connected? var normalSlot = FindInputSlot(NormalSlotId); var edges = owner.GetEdges(normalSlot.slotReference); shaderInputVisitor.AddShaderChunk("float3 worldNormal;", true); if (edges.Any()) shaderInputVisitor.AddShaderChunk("INTERNAL_DATA", true); shaderBody.AddShaderChunk("float3 " + ShaderGeneratorNames.WorldSpaceNormal + " = normalize(IN.worldNormal);", true); } if (requiresBitangent || requiresViewDirTangentSpace) { shaderBody.AddShaderChunk(string.Format("float3 {0} = cross({1}, {2}) * IN.worldTangent.w;", ShaderGeneratorNames.WorldSpaceBitangent, ShaderGeneratorNames.WorldSpaceNormal, ShaderGeneratorNames.WorldSpaceTangent), true); } if (requiresViewDirTangentSpace) { // shaderInputVisitor.AddShaderChunk("float3 tangentViewDir;", true); // shaderBody.AddShaderChunk("float3 " + ShaderGeneratorNames.TangentSpaceViewDirection + " = IN.tangentViewDir;", true); shaderBody.AddShaderChunk( "float3 " + ShaderGeneratorNames.TangentSpaceViewDirection + ";", true); shaderBody.AddShaderChunk( ShaderGeneratorNames.TangentSpaceViewDirection + ".x = dot(" + ShaderGeneratorNames.WorldSpaceViewDirection + "," + ShaderGeneratorNames.WorldSpaceTangent + ");", true); shaderBody.AddShaderChunk( ShaderGeneratorNames.TangentSpaceViewDirection + ".y = dot(" + ShaderGeneratorNames.WorldSpaceViewDirection + "," + ShaderGeneratorNames.WorldSpaceBitangent + ");", true); shaderBody.AddShaderChunk( ShaderGeneratorNames.TangentSpaceViewDirection + ".z = dot(" + ShaderGeneratorNames.WorldSpaceViewDirection + "," + ShaderGeneratorNames.WorldSpaceNormal + ");", true); } if (requiresVertexColor) { shaderBody.AddShaderChunk("float4 " + ShaderGeneratorNames.VertexColor + " = IN.color;", true); } GenerateNodeCode(shaderBody, mode); } public void GenerateNodeCode(ShaderGenerator shaderBody, GenerationMode generationMode) { var nodes = ListPool.Get(); //Get the rest of the nodes for all the other slots NodeUtils.DepthFirstCollectNodesFromNode(nodes, this, NodeUtils.IncludeSelf.Exclude, new List(surfaceInputs)); for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; if (node is IGeneratesBodyCode) (node as IGeneratesBodyCode).GenerateNodeCode(shaderBody, generationMode); } ListPool.Release(nodes); foreach (var slot in GetInputSlots()) { if (surfaceInputs.Contains(slot.id)) { foreach (var edge in owner.GetEdges(slot.slotReference)) { var outputRef = edge.outputSlot; var fromNode = owner.GetNodeFromGuid(outputRef.nodeGuid); if (fromNode == null) continue; var remapper = fromNode as INodeGroupRemapper; if (remapper != null && !remapper.IsValidSlotConnection(outputRef.slotId)) continue; shaderBody.AddShaderChunk("o." + slot.shaderOutputName + " = " + fromNode.GetVariableNameForSlot(outputRef.slotId) + ";", true); if (slot.id == NormalSlotId) shaderBody.AddShaderChunk("o." + slot.shaderOutputName + " += 1e-6;", true); } } } } } }