using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.Graphing; using UnityEditor.ShaderGraph; namespace UnityEditor.Rendering.Universal { delegate void OnGeneratePassDelegate(IMasterNode masterNode, ref Pass pass, ref ShaderGraphRequirements requirements); struct Pass { public string Name; public string TemplatePath; public List VertexShaderSlots; public List PixelShaderSlots; public ShaderGraphRequirements Requirements; public List ExtraDefines; public void OnGeneratePass(IMasterNode masterNode, ShaderGraphRequirements requirements) { if (OnGeneratePassImpl != null) { OnGeneratePassImpl(masterNode, ref this, ref requirements); } } public OnGeneratePassDelegate OnGeneratePassImpl; } static class UniversalSubShaderUtilities { public static readonly NeededCoordinateSpace k_PixelCoordinateSpace = NeededCoordinateSpace.World; static string GetTemplatePath(string templateName) { var basePath = "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/"; string templatePath = Path.Combine(basePath, templateName); if (File.Exists(templatePath)) return templatePath; throw new FileNotFoundException(string.Format(@"Cannot find a template with name ""{0}"".", templateName)); } public static string GetSubShader(T masterNode, SurfaceMaterialTags tags, SurfaceMaterialOptions options, Pass[] passes, GenerationMode mode, string customEditorPath = null, List sourceAssetDependencyPaths = null) where T : IMasterNode { if (sourceAssetDependencyPaths != null) { var relativePath = "Packages/com.unity.render-pipelines.universal/"; var fullPath = Path.GetFullPath(relativePath); var shaderFiles = Directory.GetFiles(Path.Combine(fullPath, "ShaderLibrary")).Select(x => Path.Combine(relativePath, x.Substring(fullPath.Length))); sourceAssetDependencyPaths.AddRange(shaderFiles); } var subShader = new ShaderStringBuilder(); subShader.AppendLine("SubShader"); using (subShader.BlockScope()) { var tagsBuilder = new ShaderStringBuilder(0); tags.GetTags(tagsBuilder, UnityEngine.Rendering.Universal.UniversalRenderPipeline.k_ShaderTagName); subShader.AppendLines(tagsBuilder.ToString()); foreach(Pass pass in passes) { var templatePath = GetTemplatePath(pass.TemplatePath); if (!File.Exists(templatePath)) continue; if (sourceAssetDependencyPaths != null) sourceAssetDependencyPaths.Add(templatePath); string template = File.ReadAllText(templatePath); subShader.AppendLines(GetShaderPassFromTemplate( template, masterNode, pass, mode, options)); } } if(!string.IsNullOrEmpty(customEditorPath)) subShader.Append($"CustomEditor \"{customEditorPath}\""); return subShader.ToString(); } static string GetShaderPassFromTemplate(string template, IMasterNode iMasterNode, Pass pass, GenerationMode mode, SurfaceMaterialOptions materialOptions) { // ----------------------------------------------------- // // SETUP // // ----------------------------------------------------- // AbstractMaterialNode masterNode = iMasterNode as AbstractMaterialNode; // ------------------------------------- // String builders var shaderProperties = new PropertyCollector(); var shaderKeywords = new KeywordCollector(); var shaderPropertyUniforms = new ShaderStringBuilder(1); var shaderKeywordDeclarations = new ShaderStringBuilder(1); var functionBuilder = new ShaderStringBuilder(1); var functionRegistry = new FunctionRegistry(functionBuilder); var defines = new ShaderStringBuilder(1); var graph = new ShaderStringBuilder(0); var vertexDescriptionInputStruct = new ShaderStringBuilder(1); var vertexDescriptionStruct = new ShaderStringBuilder(1); var vertexDescriptionFunction = new ShaderStringBuilder(1); var surfaceDescriptionInputStruct = new ShaderStringBuilder(1); var surfaceDescriptionStruct = new ShaderStringBuilder(1); var surfaceDescriptionFunction = new ShaderStringBuilder(1); var vertexInputStruct = new ShaderStringBuilder(1); var vertexOutputStruct = new ShaderStringBuilder(2); var vertexShader = new ShaderStringBuilder(2); var vertexShaderDescriptionInputs = new ShaderStringBuilder(2); var vertexShaderOutputs = new ShaderStringBuilder(2); var pixelShader = new ShaderStringBuilder(2); var pixelShaderSurfaceInputs = new ShaderStringBuilder(2); var pixelShaderSurfaceRemap = new ShaderStringBuilder(2); // ------------------------------------- // Get Slot and Node lists per stage var vertexSlots = pass.VertexShaderSlots.Select(masterNode.FindSlot).ToList(); var vertexNodes = ListPool.Get(); NodeUtils.DepthFirstCollectNodesFromNode(vertexNodes, masterNode, NodeUtils.IncludeSelf.Include, pass.VertexShaderSlots); var pixelSlots = pass.PixelShaderSlots.Select(masterNode.FindSlot).ToList(); var pixelNodes = ListPool.Get(); NodeUtils.DepthFirstCollectNodesFromNode(pixelNodes, masterNode, NodeUtils.IncludeSelf.Include, pass.PixelShaderSlots); // ------------------------------------- // Get Requirements var vertexRequirements = ShaderGraphRequirements.FromNodes(vertexNodes, ShaderStageCapability.Vertex, false); var pixelRequirements = ShaderGraphRequirements.FromNodes(pixelNodes, ShaderStageCapability.Fragment); var graphRequirements = pixelRequirements.Union(vertexRequirements); var surfaceRequirements = ShaderGraphRequirements.FromNodes(pixelNodes, ShaderStageCapability.Fragment, false); var modelRequirements = pass.Requirements; // ----------------------------------------------------- // // START SHADER GENERATION // // ----------------------------------------------------- // // ------------------------------------- // Calculate material options var blendingBuilder = new ShaderStringBuilder(1); var cullingBuilder = new ShaderStringBuilder(1); var zTestBuilder = new ShaderStringBuilder(1); var zWriteBuilder = new ShaderStringBuilder(1); materialOptions.GetBlend(blendingBuilder); materialOptions.GetCull(cullingBuilder); materialOptions.GetDepthTest(zTestBuilder); materialOptions.GetDepthWrite(zWriteBuilder); // ------------------------------------- // Generate defines pass.OnGeneratePass(iMasterNode, graphRequirements); foreach(string define in pass.ExtraDefines) defines.AppendLine(define); // ----------------------------------------------------- // // KEYWORDS // // ----------------------------------------------------- // // ------------------------------------- // Get keyword permutations masterNode.owner.CollectShaderKeywords(shaderKeywords, mode); // Track permutation indices for all nodes List[] keywordPermutationsPerVertexNode = new List[vertexNodes.Count]; List[] keywordPermutationsPerPixelNode = new List[pixelNodes.Count]; // ------------------------------------- // Evaluate all permutations for(int i = 0; i < shaderKeywords.permutations.Count; i++) { // Get active nodes for this permutation var localVertexNodes = ListPool.Get(); var localPixelNodes = ListPool.Get(); NodeUtils.DepthFirstCollectNodesFromNode(localVertexNodes, masterNode, NodeUtils.IncludeSelf.Include, pass.VertexShaderSlots, shaderKeywords.permutations[i]); NodeUtils.DepthFirstCollectNodesFromNode(localPixelNodes, masterNode, NodeUtils.IncludeSelf.Include, pass.PixelShaderSlots, shaderKeywords.permutations[i]); // Track each vertex node in this permutation foreach(AbstractMaterialNode vertexNode in localVertexNodes) { int nodeIndex = vertexNodes.IndexOf(vertexNode); if(keywordPermutationsPerVertexNode[nodeIndex] == null) keywordPermutationsPerVertexNode[nodeIndex] = new List(); keywordPermutationsPerVertexNode[nodeIndex].Add(i); } // Track each pixel node in this permutation foreach(AbstractMaterialNode pixelNode in localPixelNodes) { int nodeIndex = pixelNodes.IndexOf(pixelNode); if(keywordPermutationsPerPixelNode[nodeIndex] == null) keywordPermutationsPerPixelNode[nodeIndex] = new List(); keywordPermutationsPerPixelNode[nodeIndex].Add(i); } } // ----------------------------------------------------- // // START VERTEX DESCRIPTION // // ----------------------------------------------------- // // ------------------------------------- // Generate Input structure for Vertex Description function // TODO - Vertex Description Input requirements are needed to exclude intermediate translation spaces vertexDescriptionInputStruct.AppendLine("struct VertexDescriptionInputs"); using (vertexDescriptionInputStruct.BlockSemicolonScope()) { ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(vertexRequirements.requiresNormal, InterpolatorType.Normal, vertexDescriptionInputStruct); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(vertexRequirements.requiresTangent, InterpolatorType.Tangent, vertexDescriptionInputStruct); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(vertexRequirements.requiresBitangent, InterpolatorType.BiTangent, vertexDescriptionInputStruct); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(vertexRequirements.requiresViewDir, InterpolatorType.ViewDirection, vertexDescriptionInputStruct); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(vertexRequirements.requiresPosition, InterpolatorType.Position, vertexDescriptionInputStruct); if (vertexRequirements.requiresVertexColor) vertexDescriptionInputStruct.AppendLine("float4 {0};", ShaderGeneratorNames.VertexColor); if (vertexRequirements.requiresScreenPosition) vertexDescriptionInputStruct.AppendLine("float4 {0};", ShaderGeneratorNames.ScreenPosition); foreach (var channel in vertexRequirements.requiresMeshUVs.Distinct()) vertexDescriptionInputStruct.AppendLine("half4 {0};", channel.GetUVName()); if (vertexRequirements.requiresTime) { vertexDescriptionInputStruct.AppendLine("float3 {0};", ShaderGeneratorNames.TimeParameters); } } // ------------------------------------- // Generate Output structure for Vertex Description function GraphUtil.GenerateVertexDescriptionStruct(vertexDescriptionStruct, vertexSlots); // ------------------------------------- // Generate Vertex Description function GraphUtil.GenerateVertexDescriptionFunction( masterNode.owner as GraphData, vertexDescriptionFunction, functionRegistry, shaderProperties, shaderKeywords, mode, masterNode, vertexNodes, keywordPermutationsPerVertexNode, vertexSlots); // ----------------------------------------------------- // // START SURFACE DESCRIPTION // // ----------------------------------------------------- // // ------------------------------------- // Generate Input structure for Surface Description function // Surface Description Input requirements are needed to exclude intermediate translation spaces surfaceDescriptionInputStruct.AppendLine("struct SurfaceDescriptionInputs"); using (surfaceDescriptionInputStruct.BlockSemicolonScope()) { ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(surfaceRequirements.requiresNormal, InterpolatorType.Normal, surfaceDescriptionInputStruct); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(surfaceRequirements.requiresTangent, InterpolatorType.Tangent, surfaceDescriptionInputStruct); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(surfaceRequirements.requiresBitangent, InterpolatorType.BiTangent, surfaceDescriptionInputStruct); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(surfaceRequirements.requiresViewDir, InterpolatorType.ViewDirection, surfaceDescriptionInputStruct); ShaderGenerator.GenerateSpaceTranslationSurfaceInputs(surfaceRequirements.requiresPosition, InterpolatorType.Position, surfaceDescriptionInputStruct); if (surfaceRequirements.requiresVertexColor) surfaceDescriptionInputStruct.AppendLine("float4 {0};", ShaderGeneratorNames.VertexColor); if (surfaceRequirements.requiresScreenPosition) surfaceDescriptionInputStruct.AppendLine("float4 {0};", ShaderGeneratorNames.ScreenPosition); if (surfaceRequirements.requiresFaceSign) surfaceDescriptionInputStruct.AppendLine("float {0};", ShaderGeneratorNames.FaceSign); foreach (var channel in surfaceRequirements.requiresMeshUVs.Distinct()) surfaceDescriptionInputStruct.AppendLine("half4 {0};", channel.GetUVName()); if (surfaceRequirements.requiresTime) { surfaceDescriptionInputStruct.AppendLine("float3 {0};", ShaderGeneratorNames.TimeParameters); } } // ------------------------------------- // Generate Output structure for Surface Description function GraphUtil.GenerateSurfaceDescriptionStruct(surfaceDescriptionStruct, pixelSlots); // ------------------------------------- // Generate Surface Description function GraphUtil.GenerateSurfaceDescriptionFunction( pixelNodes, keywordPermutationsPerPixelNode, masterNode, masterNode.owner as GraphData, surfaceDescriptionFunction, functionRegistry, shaderProperties, shaderKeywords, mode, "PopulateSurfaceData", "SurfaceDescription", null, pixelSlots); // ----------------------------------------------------- // // GENERATE VERTEX > PIXEL PIPELINE // // ----------------------------------------------------- // // ------------------------------------- // Keyword declarations shaderKeywords.GetKeywordsDeclaration(shaderKeywordDeclarations, mode); // ------------------------------------- // Property uniforms shaderProperties.GetPropertiesDeclaration(shaderPropertyUniforms, mode, masterNode.owner.concretePrecision); // ------------------------------------- // Generate Input structure for Vertex shader GraphUtil.GenerateApplicationVertexInputs(vertexRequirements.Union(pixelRequirements.Union(modelRequirements)), vertexInputStruct); // ------------------------------------- // Generate standard transformations // This method ensures all required transform data is available in vertex and pixel stages ShaderGenerator.GenerateStandardTransforms( 3, 10, vertexOutputStruct, vertexShader, vertexShaderDescriptionInputs, vertexShaderOutputs, pixelShader, pixelShaderSurfaceInputs, pixelRequirements, surfaceRequirements, modelRequirements, vertexRequirements, CoordinateSpace.World); // ------------------------------------- // Generate pixel shader surface remap foreach (var slot in pixelSlots) { pixelShaderSurfaceRemap.AppendLine("{0} = surf.{0};", slot.shaderOutputName); } // ------------------------------------- // Extra pixel shader work var faceSign = new ShaderStringBuilder(); if (pixelRequirements.requiresFaceSign) faceSign.AppendLine(", half FaceSign : VFACE"); // ----------------------------------------------------- // // FINALIZE // // ----------------------------------------------------- // // ------------------------------------- // Combine Graph sections graph.AppendLines(shaderKeywordDeclarations.ToString()); graph.AppendLines(shaderPropertyUniforms.ToString()); graph.AppendLine(vertexDescriptionInputStruct.ToString()); graph.AppendLine(surfaceDescriptionInputStruct.ToString()); graph.AppendLine(functionBuilder.ToString()); graph.AppendLine(vertexDescriptionStruct.ToString()); graph.AppendLine(vertexDescriptionFunction.ToString()); graph.AppendLine(surfaceDescriptionStruct.ToString()); graph.AppendLine(surfaceDescriptionFunction.ToString()); graph.AppendLine(vertexInputStruct.ToString()); // ------------------------------------- // Generate final subshader var resultPass = template.Replace("${Tags}", string.Empty); resultPass = resultPass.Replace("${Blending}", blendingBuilder.ToString()); resultPass = resultPass.Replace("${Culling}", cullingBuilder.ToString()); resultPass = resultPass.Replace("${ZTest}", zTestBuilder.ToString()); resultPass = resultPass.Replace("${ZWrite}", zWriteBuilder.ToString()); resultPass = resultPass.Replace("${Defines}", defines.ToString()); resultPass = resultPass.Replace("${Graph}", graph.ToString()); resultPass = resultPass.Replace("${VertexOutputStruct}", vertexOutputStruct.ToString()); resultPass = resultPass.Replace("${VertexShader}", vertexShader.ToString()); resultPass = resultPass.Replace("${VertexShaderDescriptionInputs}", vertexShaderDescriptionInputs.ToString()); resultPass = resultPass.Replace("${VertexShaderOutputs}", vertexShaderOutputs.ToString()); resultPass = resultPass.Replace("${FaceSign}", faceSign.ToString()); resultPass = resultPass.Replace("${PixelShader}", pixelShader.ToString()); resultPass = resultPass.Replace("${PixelShaderSurfaceInputs}", pixelShaderSurfaceInputs.ToString()); resultPass = resultPass.Replace("${PixelShaderSurfaceRemap}", pixelShaderSurfaceRemap.ToString()); return resultPass; } } }