Présentation
Bonjour, Habr. Aujourd'hui, je veux parler un peu de la façon dont vous pouvez commencer rapidement et sans douleur (presque) à écrire des ombrages de
texte classiques dans Unity en utilisant le
pipeline de rendu léger (LWRP) - l'un des exemples du
pipeline Scriptable Rendering Pipeline (SRP) .
Mais qu'en est-il du graphique Shader?
Shader Graph est un outil pratique et rapide pour le prototypage ou l'écriture d'effets simples. Cependant, parfois, il est nécessaire d'écrire quelque chose de compliqué et complexe, puis - le nombre de nœuds, de fonctions personnalisées, de sous-graphiques augmente incroyablement, c'est pourquoi même le programmeur graphique le plus expérimenté commence à se perdre dans tout ce bordel. Nous comprenons tous que le code généré automatiquement a priori ne peut pas être meilleur qu'écrit manuellement - vous n'avez pas besoin d'aller loin pour des exemples, car toute erreur dans la disposition des nœuds peut conduire au fait que le résultat de calcul déjà connu dans le vertex shader sera compté à plusieurs reprises dans le fragment. Il y a des gens qui sont tout simplement plus à l'aise avec le
code qu'avec les nœuds. Les raisons peuvent être différentes, mais l'essence est la même - avec les nœuds, vive le code!

Problème
Alors, quel est le problème de s'asseoir et d'écrire un shader de texte normal sous LWRP? Et le problème est que les Shaders de
surface standard préférés de tout le monde ne sont pas pris en charge dans LWRP.
Lorsque vous essayez de l'utiliser, nous obtenons ce qui suit:

Code de shaderShader "Custom/NewSurfaceShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM
Ensuite, il vient à l'esprit pour essayer d'écrire un shader anlite régulier avec une partie vertex et fragment. Et heureusement, tout fonctionne:

Code de shader Shader "Unlit/NewUnlitShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
Cependant, comment pouvez-vous ne pas être triste - nous semblions être laissés nus sur la touche sans lumière, ombres, cartes lumineuses et
PBR bien-aimé, sans lesquels la vie n'était pas douce.
Bien sûr, vous pouvez tout écrire à la main:
Que la lumière soit!
Code de shader Shader "TheProxor/Simple Lit" { Properties { _MainTex("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
Tout semble fonctionner, mais ce n'est qu'un éclairage diffus. Que faire ensuite? Vous pouvez continuer à tout renvoyer à la main, mais c'est long et morne, et PBR ne peut en aucun cas être retourné, et nous perdons toutes les puces LWRP. Par conséquent, nous n'avons pas d'autre choix que de choisir LWRP afin de tout retourner d'un seul coup magique.
Solution
Donc, comme vous le savez, LWRP est construit sur la base du
moteur de rendu direct , ce qui signifie qu'il a son propre LitForwardPass, qui devrait être décrit quelque part. De toute évidence, quelque part il y a un CGInclude dans lequel tout cela est décrit. C'est là que nous avons commencé une aventure amusante à:
% localappdata% \ Unity \ cache \ packages \ packages.unity.com \ com.unity.render-pipelines.lightweight @ (version LWRP) \
En fait, étant arrivé à l'adresse spécifiée et entré dans le dossier
Shaders , nous pouvons trouver un shader intéressant appelé
Lit.shader . En fait, nous pouvons dire que notre recherche est terminée, le voici - le shader convoité. Entrer à l'intérieur - nous trouvons le contenu suivant:
Lit.shader Shader "Lightweight Render Pipeline/Lit" { Properties {
Il ne reste plus qu'à l'étendre pour faciliter l'édition, en supprimant les inclusions. Eh bien, modifiez un peu à votre façon.
Nous obtenons quelque chose comme ça:

Ombrage final Shader "TheProxor/LWRP/Dissolve + Vertex Offset" { Properties {
Tout d'abord, vous devez savoir ce que contient le shader résultant. Tout d'abord, nous nous intéressons à la ligne:
CustomEditor "UnityEditor.Rendering.LWRP.ShaderGUI.LitShaderEditor"
Ce qui, comme nous nous en souvenons, dans l'original était le suivant:
CustomEditor "UnityEditor.Rendering.LWRP.ShaderGUI.LitShader"
Alors qu'est-ce que c'est et pourquoi est-ce? La réponse à cette question est assez simple - si vous regardez de près, le shader a un grand nombre de types différents de définitions, qui, curieusement, doivent être activées et désactivées, et cela, pour un moment, doit être fait à partir du code, c'est pourquoi nous avons besoin d'un inspecteur personnalisé. De plus, notre inspecteur personnalisé devrait nous donner la possibilité de modifier non seulement les propriétés intégrées, mais aussi celles dont nous pourrions avoir besoin dans nos shaders.
Le shader d'origine a déjà un inspecteur personnalisé, donc nous le piétinons sûrement en le recherchant le long du chemin suivant:% localappdata% \ Unity \ cache \ packages \ packages.unity.com \ com.unity.render-pipelines.lightweight@6.9.2 \ Editor \ ShaderGUI \ Shaders \
En fait, nous sommes intéressés par le fichier LitShader.cs, qui est hérité de BaseShaderGUI: internal class LitShader : BaseShaderGUI { ... }
Le lot principal se déroule exactement dans BaseShaderGUI.cs, qui se trouve dans le dossier un niveau plus haut: public abstract class BaseShaderGUI : ShaderGUI { ... }
Nous prenons ces scripts et les jetons dans le dossier Editor (s'il n'y en a pas, créez-les, sinon des erreurs apparaîtront naturellement lors de la construction du projet, car l'espace de noms UnityEditor n'est pas inclus dans la construction). Bien sûr, sur notre tête tombe mille et une erreur, qui sont associés à type internel SavedBool , qui est un éditeur de variable bool type de fenêtre sérialisé. Ceci est fait pour maintenir l'état des sections de pliage du matériau. En fait, pour la correction, nous effectuons une manipulation simple.Changement: SavedBool m_SurfaceOptionsFoldout; SavedBool m_SurfaceInputsFoldout; SavedBool m_AdvancedFoldout;
Le: AnimatedValues.AnimBool m_SurfaceOptionsFoldout; AnimatedValues.AnimBool m_SurfaceInputsFoldout; AnimatedValues.AnimBool m_AdvancedFoldout;
Et ajoutez une autre variable pour des propriétés personnalisées supplémentaires: AnimatedValues.AnimBool m_OtherFoldout;
Vous devez également ajouter le nom et la description de la section avec nos paramètres personnalisés, en respectant les traditions établies à l'intérieur du script: protected class Styles {
Eh bien, maintenant, faisons un petit tour. Comme vous pouvez le voir, pour toutes les propriétés standard, j'ai défini l'attribut HideInInspector , qui indique directement que cette propriété sera masquée dans l'inspecteur. Cependant, cela ne concerne que l'inspecteur standard des matériaux, mais lequel? C'est vrai, personnalisé! Cela signifie que toutes nos propriétés intégrées sont rendues de quelque manière que ce soit. Alors cachons-les: ... [MainColor][HideInInspector] _BaseColor("Color", Color) = (0.5,0.5,0.5,1) [MainTexture][HideInInspector] _BaseMap("Albedo", 2D) = "white" {} [HideInInspector]_Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5 [HideInInspector]_Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5 [HideInInspector]_GlossMapScale("Smoothness Scale", Range(0.0, 1.0)) = 1.0 [HideInInspector]_SmoothnessTextureChannel("Smoothness texture channel", Float) = 0 ...
Et à l'intérieur du code de l'éditeur personnalisé, nous appelons simplement le rendu de l'inspecteur standard: public override void OnGUI(...) { ... m_OtherFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(m_OtherFoldout.value, Styles.OtherOptions); if (m_OtherFoldout.value) { base.OnGUI(materialEditorIn, properties); EditorGUILayout.Space(); } EditorGUILayout.EndFoldoutHeaderGroup(); ... }
Le résultat est évident: et voici le code des deux scripts:
BaseShaderEditor.cs using System; using UnityEngine; using UnityEngine.Rendering; using UnityEditor.Rendering; namespace UnityEditor { public abstract class BaseShaderEditor : ShaderGUI { #region EnumsAndClasses public enum SurfaceType { Opaque, Transparent } public enum BlendMode { Alpha, // Old school alpha-blending mode, fresnel does not affect amount of transparency Premultiply, // Physically plausible transparency mode, implemented as alpha pre-multiply Additive, Multiply } public enum SmoothnessSource { BaseAlpha, SpecularAlpha } public enum RenderFace { Front = 2, Back = 1, Both = 0 } protected class Styles { // Catergories public static readonly GUIContent SurfaceOptions = new GUIContent("Surface Options", "Controls how LWRP renders the Material on a screen."); public static readonly GUIContent SurfaceInputs = new GUIContent("Surface Inputs", "These settings describe the look and feel of the surface itself."); public static readonly GUIContent AdvancedLabel = new GUIContent("Advanced", "These settings affect behind-the-scenes rendering and underlying calculations."); public static readonly GUIContent surfaceType = new GUIContent("Surface Type", "Select a surface type for your texture. Choose between Opaque or Transparent."); public static readonly GUIContent blendingMode = new GUIContent("Blending Mode", "Controls how the color of the Transparent surface blends with the Material color in the background."); public static readonly GUIContent cullingText = new GUIContent("Render Face", "Specifies which faces to cull from your geometry. Front culls front faces. Back culls backfaces. None means that both sides are rendered."); public static readonly GUIContent alphaClipText = new GUIContent("Alpha Clipping", "Makes your Material act like a Cutout shader. Use this to create a transparent effect with hard edges between opaque and transparent areas."); public static readonly GUIContent alphaClipThresholdText = new GUIContent("Threshold", "Sets where the Alpha Clipping starts. The higher the value is, the brighter the effect is when clipping starts."); public static readonly GUIContent receiveShadowText = new GUIContent("Receive Shadows", "When enabled, other GameObjects can cast shadows onto this GameObject."); public static readonly GUIContent baseMap = new GUIContent("Base Map", "Specifies the base Material and/or Color of the surface. If you've selected Transparent or Alpha Clipping under Surface Options, your Material uses the Texture's alpha channel or color."); public static readonly GUIContent emissionMap = new GUIContent("Emission Map", "Sets a Texture map to use for emission. You can also select a color with the color picker. Colors are multiplied over the Texture."); public static readonly GUIContent normalMapText = new GUIContent("Normal Map", "Assigns a tangent-space normal map."); public static readonly GUIContent bumpScaleNotSupported = new GUIContent("Bump scale is not supported on mobile platforms"); public static readonly GUIContent fixNormalNow = new GUIContent("Fix now", "Converts the assigned texture to be a normal map format."); public static readonly GUIContent queueSlider = new GUIContent("Priority", "Determines the chronological rendering order for a Material. High values are rendered first."); public static readonly GUIContent OtherOptions = new GUIContent("Your own options", "You own custom options"); } #endregion #region Variables protected MaterialEditor materialEditor { get; set; } protected MaterialProperty surfaceTypeProp { get; set; } protected MaterialProperty blendModeProp { get; set; } protected MaterialProperty cullingProp { get; set; } protected MaterialProperty alphaClipProp { get; set; } protected MaterialProperty alphaCutoffProp { get; set; } protected MaterialProperty receiveShadowsProp { get; set; } // Common Surface Input properties protected MaterialProperty baseMapProp { get; set; } protected MaterialProperty baseColorProp { get; set; } protected MaterialProperty emissionMapProp { get; set; } protected MaterialProperty emissionColorProp { get; set; } protected MaterialProperty queueOffsetProp { get; set; } public bool m_FirstTimeApply = true; private const string k_KeyPrefix = "LightweightRP:Material:UI_State:"; private string m_HeaderStateKey = null; // Header foldout states AnimatedValues.AnimBool m_SurfaceOptionsFoldout; AnimatedValues.AnimBool m_SurfaceInputsFoldout; AnimatedValues.AnimBool m_AdvancedFoldout; AnimatedValues.AnimBool m_OtherFoldout; #endregion private const int queueOffsetRange = 50; //////////////////////////////////// // General Functions // //////////////////////////////////// #region GeneralFunctions public abstract void MaterialChanged(Material material); public virtual void FindProperties(MaterialProperty[] properties) { surfaceTypeProp = FindProperty("_Surface", properties); blendModeProp = FindProperty("_Blend", properties); cullingProp = FindProperty("_Cull", properties); alphaClipProp = FindProperty("_AlphaClip", properties); alphaCutoffProp = FindProperty("_Cutoff", properties); receiveShadowsProp = FindProperty("_ReceiveShadows", properties, false); baseMapProp = FindProperty("_BaseMap", properties, false); baseColorProp = FindProperty("_BaseColor", properties, false); emissionMapProp = FindProperty("_EmissionMap", properties, false); emissionColorProp = FindProperty("_EmissionColor", properties, false); queueOffsetProp = FindProperty("_QueueOffset", properties, false); } public override void OnGUI(MaterialEditor materialEditorIn, MaterialProperty[] properties) { if (materialEditorIn == null) throw new ArgumentNullException("materialEditorIn"); FindProperties(properties); // MaterialProperties can be animated so we do not cache them but fetch them every event to ensure animated values are updated correctly materialEditor = materialEditorIn; Material material = materialEditor.target as Material; // Make sure that needed setup (ie keywords/renderqueue) are set up if we're switching some existing // material to a lightweight shader. if (m_FirstTimeApply) { OnOpenGUI(material, materialEditorIn); m_FirstTimeApply = false; } ShaderPropertiesGUI(material); m_OtherFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(m_OtherFoldout.value, Styles.OtherOptions); if (m_OtherFoldout.value) { base.OnGUI(materialEditorIn, properties); EditorGUILayout.Space(); } EditorGUILayout.EndFoldoutHeaderGroup(); foreach (var obj in materialEditor.targets) MaterialChanged((Material)obj); } public virtual void OnOpenGUI(Material material, MaterialEditor materialEditor) { // Foldout states m_HeaderStateKey = k_KeyPrefix + material.shader.name; // Create key string for editor prefs m_SurfaceOptionsFoldout = new AnimatedValues.AnimBool(true); m_SurfaceInputsFoldout = new AnimatedValues.AnimBool(true); m_AdvancedFoldout = new AnimatedValues.AnimBool(true); m_OtherFoldout = new AnimatedValues.AnimBool(true); foreach (var obj in materialEditor.targets) MaterialChanged((Material)obj); } public void ShaderPropertiesGUI(Material material) { if (material == null) throw new ArgumentNullException("material"); EditorGUI.BeginChangeCheck(); m_SurfaceOptionsFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(m_SurfaceOptionsFoldout.value, Styles.SurfaceOptions); if (m_SurfaceOptionsFoldout.value) { DrawSurfaceOptions(material); EditorGUILayout.Space(); } EditorGUILayout.EndFoldoutHeaderGroup(); m_SurfaceInputsFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(m_SurfaceInputsFoldout.value, Styles.SurfaceInputs); if (m_SurfaceInputsFoldout.value) { DrawSurfaceInputs(material); EditorGUILayout.Space(); } EditorGUILayout.EndFoldoutHeaderGroup(); m_AdvancedFoldout.value = EditorGUILayout.BeginFoldoutHeaderGroup(m_AdvancedFoldout.value, Styles.AdvancedLabel); if (m_AdvancedFoldout.value) { DrawAdvancedOptions(material); EditorGUILayout.Space(); } EditorGUILayout.EndFoldoutHeaderGroup(); DrawAdditionalFoldouts(material); if (EditorGUI.EndChangeCheck()) { foreach (var obj in materialEditor.targets) MaterialChanged((Material)obj); } } #endregion //////////////////////////////////// // Drawing Functions // //////////////////////////////////// #region DrawingFunctions public virtual void DrawSurfaceOptions(Material material) { DoPopup(Styles.surfaceType, surfaceTypeProp, Enum.GetNames(typeof(SurfaceType))); if ((SurfaceType)material.GetFloat("_Surface") == SurfaceType.Transparent) DoPopup(Styles.blendingMode, blendModeProp, Enum.GetNames(typeof(BlendMode))); EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = cullingProp.hasMixedValue; var culling = (RenderFace)cullingProp.floatValue; culling = (RenderFace)EditorGUILayout.EnumPopup(Styles.cullingText, culling); if (EditorGUI.EndChangeCheck()) { materialEditor.RegisterPropertyChangeUndo(Styles.cullingText.text); cullingProp.floatValue = (float)culling; material.doubleSidedGI = (RenderFace)cullingProp.floatValue != RenderFace.Front; } EditorGUI.showMixedValue = false; EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = alphaClipProp.hasMixedValue; var alphaClipEnabled = EditorGUILayout.Toggle(Styles.alphaClipText, alphaClipProp.floatValue == 1); if (EditorGUI.EndChangeCheck()) alphaClipProp.floatValue = alphaClipEnabled ? 1 : 0; EditorGUI.showMixedValue = false; if (alphaClipProp.floatValue == 1) materialEditor.ShaderProperty(alphaCutoffProp, Styles.alphaClipThresholdText, 1); if (receiveShadowsProp != null) { EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = receiveShadowsProp.hasMixedValue; var receiveShadows = EditorGUILayout.Toggle(Styles.receiveShadowText, receiveShadowsProp.floatValue == 1.0f); if (EditorGUI.EndChangeCheck()) receiveShadowsProp.floatValue = receiveShadows ? 1.0f : 0.0f; EditorGUI.showMixedValue = false; } } public virtual void DrawSurfaceInputs(Material material) { DrawBaseProperties(material); } public virtual void DrawAdvancedOptions(Material material) { materialEditor.EnableInstancingField(); if (queueOffsetProp != null) { EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = queueOffsetProp.hasMixedValue; var queue = EditorGUILayout.IntSlider(Styles.queueSlider, (int)queueOffsetProp.floatValue, -queueOffsetRange, queueOffsetRange); if (EditorGUI.EndChangeCheck()) queueOffsetProp.floatValue = queue; EditorGUI.showMixedValue = false; } } public virtual void DrawAdditionalFoldouts(Material material) { } public virtual void DrawBaseProperties(Material material) { if (baseMapProp != null && baseColorProp != null) // Draw the baseMap, most shader will have at least a baseMap { materialEditor.TexturePropertySingleLine(Styles.baseMap, baseMapProp, baseColorProp); // TODO Temporary fix for lightmapping, to be replaced with attribute tag. if (material.HasProperty("_MainTex")) { material.SetTexture("_MainTex", baseMapProp.textureValue); var baseMapTiling = baseMapProp.textureScaleAndOffset; material.SetTextureScale("_MainTex", new Vector2(baseMapTiling.x, baseMapTiling.y)); material.SetTextureOffset("_MainTex", new Vector2(baseMapTiling.z, baseMapTiling.w)); } } } protected virtual void DrawEmissionProperties(Material material, bool keyword) { var emissive = true; var hadEmissionTexture = emissionMapProp.textureValue != null; if (!keyword) { materialEditor.TexturePropertyWithHDRColor(Styles.emissionMap, emissionMapProp, emissionColorProp, false); } else { // Emission for GI? emissive = materialEditor.EmissionEnabledProperty(); EditorGUI.BeginDisabledGroup(!emissive); { // Texture and HDR color controls materialEditor.TexturePropertyWithHDRColor(Styles.emissionMap, emissionMapProp, emissionColorProp, false); } EditorGUI.EndDisabledGroup(); } // If texture was assigned and color was black set color to white var brightness = emissionColorProp.colorValue.maxColorComponent; if (emissionMapProp.textureValue != null && !hadEmissionTexture && brightness <= 0f) emissionColorProp.colorValue = Color.white; // LW does not support RealtimeEmissive. We set it to bake emissive and handle the emissive is black right. if (emissive) { material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive; if (brightness <= 0f) material.globalIlluminationFlags |= MaterialGlobalIlluminationFlags.EmissiveIsBlack; } } public static void DrawNormalArea(MaterialEditor materialEditor, MaterialProperty bumpMap, MaterialProperty bumpMapScale = null) { if (bumpMapScale != null) { materialEditor.TexturePropertySingleLine(Styles.normalMapText, bumpMap, bumpMap.textureValue != null ? bumpMapScale : null); if (bumpMapScale.floatValue != 1 && UnityEditorInternal.InternalEditorUtility.IsMobilePlatform( EditorUserBuildSettings.activeBuildTarget)) if (materialEditor.HelpBoxWithButton(Styles.bumpScaleNotSupported, Styles.fixNormalNow)) bumpMapScale.floatValue = 1; } else { materialEditor.TexturePropertySingleLine(Styles.normalMapText, bumpMap); } } protected static void DrawTileOffset(MaterialEditor materialEditor, MaterialProperty textureProp) { materialEditor.TextureScaleOffsetProperty(textureProp); } #endregion //////////////////////////////////// // Material Data Functions // //////////////////////////////////// #region MaterialDataFunctions public static void SetMaterialKeywords(Material material, Action<Material> shadingModelFunc = null, Action<Material> shaderFunc = null) { // Clear all keywords for fresh start material.shaderKeywords = null; // Setup blending - consistent across all LWRP shaders SetupMaterialBlendMode(material); // Receive Shadows if (material.HasProperty("_ReceiveShadows")) CoreUtils.SetKeyword(material, "_RECEIVE_SHADOWS_OFF", material.GetFloat("_ReceiveShadows") == 0.0f); // Emission if (material.HasProperty("_EmissionColor")) MaterialEditor.FixupEmissiveFlag(material); bool shouldEmissionBeEnabled = (material.globalIlluminationFlags & MaterialGlobalIlluminationFlags.EmissiveIsBlack) == 0; if (material.HasProperty("_EmissionEnabled") && !shouldEmissionBeEnabled) shouldEmissionBeEnabled = material.GetFloat("_EmissionEnabled") >= 0.5f; CoreUtils.SetKeyword(material, "_EMISSION", shouldEmissionBeEnabled); // Normal Map if (material.HasProperty("_BumpMap")) CoreUtils.SetKeyword(material, "_NORMALMAP", material.GetTexture("_BumpMap")); // Shader specific keyword functions shadingModelFunc?.Invoke(material); shaderFunc?.Invoke(material); } public static void SetupMaterialBlendMode(Material material) { if (material == null) throw new ArgumentNullException("material"); bool alphaClip = material.GetFloat("_AlphaClip") == 1; if (alphaClip) { material.EnableKeyword("_ALPHATEST_ON"); } else { material.DisableKeyword("_ALPHATEST_ON"); } var queueOffset = 0; // queueOffsetRange; if (material.HasProperty("_QueueOffset")) queueOffset = queueOffsetRange - (int)material.GetFloat("_QueueOffset"); SurfaceType surfaceType = (SurfaceType)material.GetFloat("_Surface"); if (surfaceType == SurfaceType.Opaque) { if (alphaClip) { material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.AlphaTest; material.SetOverrideTag("RenderType", "TransparentCutout"); } else { material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Geometry; material.SetOverrideTag("RenderType", "Opaque"); } material.renderQueue += queueOffset; material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); material.SetInt("_ZWrite", 1); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); material.SetShaderPassEnabled("ShadowCaster", true); } else { BlendMode blendMode = (BlendMode)material.GetFloat("_Blend"); var queue = (int)UnityEngine.Rendering.RenderQueue.Transparent; // Specific Transparent Mode Settings switch (blendMode) { case BlendMode.Alpha: material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); break; case BlendMode.Premultiply: material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); material.EnableKeyword("_ALPHAPREMULTIPLY_ON"); break; case BlendMode.Additive: material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); break; case BlendMode.Multiply: material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.DstColor); material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); material.EnableKeyword("_ALPHAMODULATE_ON"); break; } // General Transparent Material Settings material.SetOverrideTag("RenderType", "Transparent"); material.SetInt("_ZWrite", 0); material.renderQueue = queue + queueOffset; material.SetShaderPassEnabled("ShadowCaster", false); } } #endregion //////////////////////////////////// // Helper Functions // //////////////////////////////////// #region HelperFunctions public static void TwoFloatSingleLine(GUIContent title, MaterialProperty prop1, GUIContent prop1Label, MaterialProperty prop2, GUIContent prop2Label, MaterialEditor materialEditor, float labelWidth = 30f) { EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = prop1.hasMixedValue || prop2.hasMixedValue; Rect rect = EditorGUILayout.GetControlRect(); EditorGUI.PrefixLabel(rect, title); var indent = EditorGUI.indentLevel; var preLabelWidth = EditorGUIUtility.labelWidth; EditorGUI.indentLevel = 0; EditorGUIUtility.labelWidth = labelWidth; Rect propRect1 = new Rect(rect.x + preLabelWidth, rect.y, (rect.width - preLabelWidth) * 0.5f, EditorGUIUtility.singleLineHeight); var prop1val = EditorGUI.FloatField(propRect1, prop1Label, prop1.floatValue); Rect propRect2 = new Rect(propRect1.x + propRect1.width, rect.y, propRect1.width, EditorGUIUtility.singleLineHeight); var prop2val = EditorGUI.FloatField(propRect2, prop2Label, prop2.floatValue); EditorGUI.indentLevel = indent; EditorGUIUtility.labelWidth = preLabelWidth; if (EditorGUI.EndChangeCheck()) { materialEditor.RegisterPropertyChangeUndo(title.text); prop1.floatValue = prop1val; prop2.floatValue = prop2val; } EditorGUI.showMixedValue = false; } public void DoPopup(GUIContent label, MaterialProperty property, string[] options) { DoPopup(label, property, options, materialEditor); } public static void DoPopup(GUIContent label, MaterialProperty property, string[] options, MaterialEditor materialEditor) { if (property == null) throw new ArgumentNullException("property"); EditorGUI.showMixedValue = property.hasMixedValue; var mode = property.floatValue; EditorGUI.BeginChangeCheck(); mode = EditorGUILayout.Popup(label, (int)mode, options); if (EditorGUI.EndChangeCheck()) { materialEditor.RegisterPropertyChangeUndo(label.text); property.floatValue = mode; } EditorGUI.showMixedValue = false; } // Helper to show texture and color properties public static Rect TextureColorProps(MaterialEditor materialEditor, GUIContent label, MaterialProperty textureProp, MaterialProperty colorProp, bool hdr = false) { Rect rect = EditorGUILayout.GetControlRect(); EditorGUI.showMixedValue = textureProp.hasMixedValue; materialEditor.TexturePropertyMiniThumbnail(rect, textureProp, label.text, label.tooltip); EditorGUI.showMixedValue = false; if (colorProp != null) { EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = colorProp.hasMixedValue; int indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; Rect rectAfterLabel = new Rect(rect.x + EditorGUIUtility.labelWidth, rect.y, EditorGUIUtility.fieldWidth, EditorGUIUtility.singleLineHeight); var col = EditorGUI.ColorField(rectAfterLabel, GUIContent.none, colorProp.colorValue, true, false, hdr); EditorGUI.indentLevel = indentLevel; if (EditorGUI.EndChangeCheck()) { materialEditor.RegisterPropertyChangeUndo(colorProp.displayName); colorProp.colorValue = col; } EditorGUI.showMixedValue = false; } return rect; } // Copied from shaderGUI as it is a protected function in an abstract class, unavailable to others public new static MaterialProperty FindProperty(string propertyName, MaterialProperty[] properties) { return FindProperty(propertyName, properties, true); } // Copied from shaderGUI as it is a protected function in an abstract class, unavailable to others public new static MaterialProperty FindProperty(string propertyName, MaterialProperty[] properties, bool propertyIsMandatory) { for (int index = 0; index < properties.Length; ++index) { if (properties[index] != null && properties[index].name == propertyName) return properties[index]; } if (propertyIsMandatory) throw new ArgumentException("Could not find MaterialProperty: '" + propertyName + "', Num properties: " + (object)properties.Length); return null; } #endregion } }
LitShaderEditor.cs using System; using UnityEngine; using UnityEngine.Rendering; using UnityEditor.Rendering.LWRP; namespace UnityEditor.Rendering.LWRP.ShaderGUI { internal class LitShaderEditor : BaseShaderEditor {
Voyons maintenant l'intérieur du shader.La première chose à laquelle vous faites attention est qu'il n'y a que cinq passes dans le shader. Arrêtons-nous un peu sur eux:- ForwardLit: Pass avant, nous considérons ici la lumière, le PBR et le brouillard.
- ShadowCaster: voici le calcul des ombres.
- DepthOnly: Depth (Z-Buffer).
- Méta: lumière cuite (lightmaps).
- Lightweight2D: sprites et UI.
De plus, Unity a plusieurs compilateurs de shader et compilateurs croisés différents. SRP nécessite HLSLcc. Sur les plates-formes qui utilisent l'API graphique OpenGL ES (comme Android), HLSLcc n'est pas utilisé par défaut.Par conséquent, nous le forçons à se connecter: #pragma prefer_hlslcc gles
Mais DirectX 9 n'est pas pris en charge, désactivez-le donc de force: #pragma exclude_renderers d3d11_9x
En termes d'écriture du code lui-même, rien n'a changé sauf que maintenant nous n'écrivons pas en CG, mais en HLSL pur, et donc maintenant le corps du programme shader ressemblera à ceci: HLSLPROGRAM ... ENDHLSL
-
_Time ,
_ScreenParams _WorldSpaceCameraPos . ,
UnityCG.cginc . , ,
UnityObjectToClipPos(POSITION) TransformWorldToHClip(POSITION) , —
UNITY_APPLY_FOG(fogCoord, color) MixFog(color, fogCoord) .
, Core.hlsl:
% localappdata% \ Unity \ cache \ packages \ packages.unity.com \ com.unity.render-pipelines.lightweight @ (version LWRP) \ ShaderLibrary \ Core.hlsl
Nous pouvons trouver une liste complète des fonctionnalités disponibles.Il convient également de mentionner les tampons constants (CBUFFER) et UnityPerMaterial. Des tampons constants sont utilisés pour stocker des données qui sont rarement modifiées sur le GPU, respectivement, ils peuvent être utilisés pour stocker des variables de shader. Pour ce faire, il suffit d'appeler les macrosCBUFFER_START et CBUFFER_END : CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; half4 _BaseColor; half4 _SpecColor; half4 _EmissionColor; half _Cutoff; half _Smoothness; half _Metallic; half _BumpScale; half _OcclusionStrength; CBUFFER_END
La déclaration des variables globales ou de diverses variables paramétrées (à partir de code ou d'animation, par exemple) a lieu à l'ancienne dans le corps d'un programme de shader.LWRP utilise deux types de tampons constants - UnityPerObject et UnityPerMaterial . Ces tampons sont liés une fois afin de pouvoir être utilisés pendant le rendu. En gros, cela signifie que pendant le dessin, les tampons constants ne seront pas renforcés ou le setpass ne sera pas appelé pour les matériaux. Cela est avantageux lorsque plusieurs shaders partagent le même tampon constant, car LWRP peut emballer différents matériaux pour cela.En fait, si vous étudiez attentivement la structure du shader, vous pouvez constater que la plupart des données standard utilisent simplement des tampons constants partout.Plus en détail, sur toutes les différences, mais en anglais, vous pouvez lire ici .Soit dit en passant, si vous regardez attentivement SurfaceData: SurfaceData surfaceData;
Vous trouverez peut-être que c'est le maître PBR chéri de ShaderGraph .Exemple
Donc, maintenant nos mains sont complètement déliées, ce qui signifie que le moment est venud'organiser une bacchanale! Ajoutons le déplacement de sommet et l' effet de dissolution à titre d' exemple , et laissons le reste danser. Il est très pratique que toutes les passes soient devant nos yeux et que nous puissions tout éditer de manière complète. Décrivons les propriétés:
Properties { ... _DissolveMap("Dissolve Map", 2D) = "white" {} _DissolveFactor("Dissolve Factor", Range(0, 1)) = 0.0 _DissolveWidth("Dissolve Width", Range(0, 1)) = 0.0 [HDR]_DissolveColor("Color", Color) = (1,1,0) }
Qui apparaîtra sans aucun doute dans notre propre onglet bien-aimé dans l'inspecteur: Ajouter des variables:
CBUFFER_START(UnityPerMaterial) ... float4 _DissolveMap_ST;
Tout d'abord, envoyez la géométrie à une stupeur ivre: Varyings LitPassVertex(Attributes input) { ... input.positionOS.xyz += normalize(input.positionOS.xyz) * sin(input.positionOS.x) * sin(_Time.x * 100); ... }
Puis l'ombre: Varyings ShadowPassVertex(Attributes input) { ... input.positionOS.xyz += normalize(input.positionOS.xyz) * sin(input.positionOS.x) * sin(_Time.x * 100); ... }
Profondeur: Varyings DepthOnlyVertex(Attributes input) { ... input.position.xyz += normalize(input.position.xyz) * sin(input.position.x) * sin(_Time.x * 100); ... }
Eh bien, dissolvez maintenant le temps et le mouvement: half4 LitPassFragment(Varyings input) : SV_Target { ...
Vous pouvez également amener Dissolve dans l'ombre, puis avec un coup de poignet, nous aurons l'ombre correcte, ce qui est assez difficile à réaliser dans le Shader Graph , et voici quelques lignes de code. half4 ShadowPassFragment(Varyings input) : SV_TARGET { ... float4 mask = SAMPLE_TEXTURE2D(_DissolveMap, sampler_DissolveMap, input.uv); if (mask.r > _DissolveFactor) discard; ... }
Eh bien, le code du shader final:SimpleDissolve Shader "TheProxor/LWRP/Dissolve + Vertex Offset" { Properties {
Conclusion
Eh bien, il est temps de faire le point. Comme il est devenu clair, dans LWRP, il est possible et même nécessaire d'écrire des shaders avec du code, car cela libère considérablement vos mains, aidant à écrire des choses cool sans béquilles, par exemple, votre système d'éclairage. Bien sûr, cela ne peut pas être comparé au Shader de surface standard pratique et familier , mais peut-être qu'un jour j'aurai les mains pour écrire le même analogique pratique pour LWRP et HDRP, mais plus à ce sujet une autre fois.Liens utiles:Référentiel avec tous les matériaux de l'articleTrouvé lors de la préparation du matériel, documentation non officielle DocumentationLWRP