مقدمة
مرحبا يا حبر اليوم أريد أن أتحدث قليلاً عن كيف يمكنك بسرعة وبدون ألم (تقريبًا) البدء في كتابة
تظليلات النص الكلاسيكي في الوحدة باستخدام
خط أنابيب التقديم الخفيف (LWRP) - أحد الأمثلة على
خط أنابيب التقديم النصي (SRP) .
ولكن ماذا عن شادر الرسم البياني؟
Shader Graph هو أداة مريحة وسريعة لنماذج أولية أو كتابة تأثيرات بسيطة. ومع ذلك ، في بعض الأحيان ، من الضروري أن تكتب شيئًا معقدًا ومعقدًا ، وبعد ذلك - يتزايد عدد العقد والوظائف المخصصة والرسوم البيانية الفرعية بشكل لا يصدق ، وهذا هو السبب في أن مبرمج الرسومات الأكثر خبرة يبدأ في الخلط في هذه الفوضى بأكملها. نحن جميعًا ندرك أن الشفرة التي تم إنشاؤها تلقائيًا لا يمكن أن تكون أفضل من الشفرة المكتوبة يدويًا - لا تحتاج إلى الذهاب بعيدًا للحصول على أمثلة ، لأن أي خطأ في تخطيط العقد يمكن أن يؤدي إلى حقيقة أن الحساب المعروف بالفعل يؤدي إلى تظليل قمة الرأس سيتم حسابه بشكل متكرر في الجزء. هناك أشخاص أكثر راحة في التعامل مع
الكود ، وليس مع العقد. قد تكون الأسباب مختلفة ، ولكن الجوهر هو نفسه - مع العقد ، يعيش الكود!

مشاكل
لذا ، ما هي مشكلة الجلوس وكتابة تظليل النص العادي تحت LWRP؟ والمشكلة هي أن أجهزة التظليل
السطحي القياسية المفضلة للجميع غير مدعومة في LWRP.
عند محاولة استخدامه ، نحصل على ما يلي:

كود شادرShader "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
ثم يتبادر إلى الذهن لمحاولة كتابة تظليل anlite منتظم مع جزء قمة وشظية. ولحسن الحظ ، كل شيء يعمل:

كود شادر Shader "Unlit/NewUnlitShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
ومع ذلك ، كيف لا يمكنك أن تكون حزينًا - يبدو أننا تركنا عراة على الهامش بدون ضوء وظلال وخرائط ضوئية و
PBR الحبيب ، والتي بدونها لم تكن الحياة حلوة.
بالطبع ، يمكنك كتابة كل شيء باليد:
فليكن نور!
كود شادر Shader "TheProxor/Simple Lit" { Properties { _MainTex("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
يبدو أن كل شيء يعمل ، لكن هذا مجرد إضاءة منتشرة. ماذا تفعل بعد ذلك؟ يمكنك الاستمرار في إرجاع كل شيء يدويًا ، ولكنه طويل وكئيب ، ولا يمكن إرجاع PBR بأي طريقة ، ونفقد جميع شرائح LWRP. لذلك ، ليس لدينا خيار سوى اختيار LWRP لإرجاع كل شيء بضربة سحرية واحدة.
قرار
هكذا ، كما تعلمون ، تم تصميم LWRP على أساس
العارض الأمامي ، مما يعني أن لديه LitForwardPass الخاص به ، والذي يجب وصفه في مكان ما. من الواضح ، في مكان ما يوجد CGInclude فيه كل هذا موصوف. هذا هو المكان الذي انطلقنا فيه في مغامرة ممتعة في:
٪ localappdata٪ \ الوحدة \ ذاكرة التخزين المؤقت \ الحزم \ الحزم. com.com \ com.unity.render-pipelines.lightweight @ (إصدار LWRP) \
في الواقع ، بعد الوصول إلى العنوان المحدد والانتقال إلى مجلد
Shaders ، يمكننا العثور على تظليل واحد مثير للاهتمام يسمى
Lit.shader . في الواقع ، يمكننا أن نقول أن بحثنا قد انتهى ، ها هو - التظليل المرغوب. الذهاب إلى الداخل - نجد المحتويات التالية:
Lit.shader Shader "Lightweight Render Pipeline/Lit" { Properties {
يبقى فقط لتوسيعه لسهولة التحرير ، والتخلص من التضمين. حسنا ، قليلا تعديل في طريقتك الخاصة.
نحصل على شيء مثل هذا:

تظليل اوبر النهائي Shader "TheProxor/LWRP/Dissolve + Vertex Offset" { Properties {
بادئ ذي بدء ، تحتاج إلى معرفة ما في التظليل الناتج. أولاً ، نحن مهتمون بالخط:
CustomEditor "UnityEditor.Rendering.LWRP.ShaderGUI.LitShaderEditor"
والتي ، كما نتذكر ، في الأصل كانت كما يلي:
CustomEditor "UnityEditor.Rendering.LWRP.ShaderGUI.LitShader"
إذن ما هذا ولماذا؟ إجابة هذا السؤال بسيطة للغاية - إذا نظرت عن كثب ، فإن جهاز التظليل يحتوي على عدد كبير من الأنواع المختلفة من التعريفات ، والتي من الغريب أنها تحتاج إلى التنشيط وإلغاء تنشيطها ، وهذا ، للحظة ، يجب القيام به من التعليمات البرمجية ، وهذا هو سبب حاجتنا إلى مفتش مخصص. علاوة على ذلك ، ينبغي أن يتيح لنا المفتش المخصص فرصة تحرير ليس فقط الخصائص المضمنة ، ولكن أيضًا تلك التي قد نحتاجها في تظليلنا.
يحتوي التظليل الأصلي بالفعل على مفتش مخصص ، لذلك نحن بالتأكيد ندوسه من خلال البحث عنه على طول المسار التالي:٪ localappdata٪ \ الوحدة \ ذاكرة التخزين المؤقت \ الحزم \ package.unity.com \ com.unity.render-pipelines.lightweight@6.9.2 \ Editor \ ShaderGUI \ Shaders \
في الواقع ، نحن مهتمون بملف LitShader.cs ، الموروث من BaseShaderGUI: internal class LitShader : BaseShaderGUI { ... }
يتم إجراء الدُفعة الرئيسية تمامًا في BaseShaderGUI.cs ، والتي يمكن العثور عليها في المجلد بمستوى أعلى: public abstract class BaseShaderGUI : ShaderGUI { ... }
Editor ( — , , UnityEditor ). , , -
SavedBool , bool. . , .
:
SavedBool m_SurfaceOptionsFoldout; SavedBool m_SurfaceInputsFoldout; SavedBool m_AdvancedFoldout;
:
AnimatedValues.AnimBool m_SurfaceOptionsFoldout; AnimatedValues.AnimBool m_SurfaceInputsFoldout; AnimatedValues.AnimBool m_AdvancedFoldout;
:
AnimatedValues.AnimBool m_OtherFoldout;
, :
protected class Styles {
حسنًا ، دعنا الآن نفعل خدعة صغيرة. كما ترون ، بالنسبة لجميع الخصائص القياسية ، أقوم بتعيين سمة HideInInspector ، التي تشير مباشرةً إلى أن هذه الخاصية ستكون مخفية في المفتش. ومع ذلك ، هذا هو ذات الصلة فقط للمفتش القياسية للمواد ، ولكن أي واحد؟ هذا صحيح ، مخصص! هذا يعني أن جميع الخصائص المدمجة لدينا يتم تقديمها بأي شكل من الأشكال. لذلك دعونا نخفيهم: ... [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 ...
وداخل رمز المحرر المخصص ، ندعو ببساطة إلى تقديم المفتش القياسي: 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(); ... }
والنتيجة واضحة: وهنا رمز لكلا النصين:
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 {
الآن دعونا ننظر إلى الدواخل من تظليل.أول شيء يجب الانتباه إليه هو أنه لا يوجد سوى خمس تمريرات في التظليل. دعنا نتناولها قليلاً:- ForwardLit: تمرير إلى الأمام ، وهنا نعتبر الضوء ، PBR والضباب.
- ShadowCaster: هنا هو حساب الظلال.
- DepthOnly: العمق (Z-Buffer).
- الفوقية: ضوء خبز (lightmaps).
- Lightweight2D: العفاريت واجهة المستخدم.
علاوة على ذلك ، لدى Unity عدة برامج تجميع تظليل مختلفة ومقاطع متقاطعة. يتطلب SRP HLSLcc. في الأنظمة الأساسية التي تستخدم API OpenGL ES للرسومات (مثل Android) ، لا يتم استخدام HLSLcc افتراضيًا.لذلك ، نجبرها على الاتصال: #pragma prefer_hlslcc gles
لكن DirectX 9 غير مدعوم ، لذا قم بتعطيله بالقوة: #pragma exclude_renderers d3d11_9x
من حيث كتابة الكود نفسه ، لم يتغير أي شيء باستثناء أننا نكتب الآن ليس في CG ، ولكن في HLSL الخالص ، وبالتالي فإن نص برنامج التظليل سيبدو كما يلي: HLSLPROGRAM ... ENDHLSL
-
_Time ,
_ScreenParams _WorldSpaceCameraPos . ,
UnityCG.cginc . , ,
UnityObjectToClipPos(POSITION) TransformWorldToHClip(POSITION) , —
UNITY_APPLY_FOG(fogCoord, color) MixFog(color, fogCoord) .
, Core.hlsl:
٪ localappdata٪ \ الوحدة \ ذاكرة التخزين المؤقت \ الحزم \ package.unity.com \ com.unity.render-pipelines.lightweight @ (إصدار LWRP) \ ShaderLibrary \ Core.hlsl
يمكننا العثور على قائمة كاملة من الميزات المتاحة.تجدر الإشارة أيضًا إلى وجود مخازن مؤقتة (CBUFFER) و UnityPerMaterial. تستخدم المخازن المؤقتة الثابتة لتخزين البيانات التي نادراً ما تتغير على وحدة معالجة الرسومات ، على التوالي ، يمكن استخدامها لتخزين متغيرات التظليل. للقيام بذلك ، ما عليك سوى الاتصال بوحدات الماكروCBUFFER_START و 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
يتم الإعلان عن المتغيرات العامة أو المتغيرات المحددة بشكل بارز (من الكود أو الرسوم المتحركة ، على سبيل المثال) بالطريقة القديمة داخل جسم برنامج تظليل.يستخدم LWRP نوعين من المخازن المؤقتة الثابتة - UnityPerObject و UnityPerMaterial . يتم ربط هذه المخازن المؤقتة مرة واحدة بحيث يمكن استخدامها أثناء التقديم. بمعنى تقريبي ، هذا يعني أنه أثناء العرض ، لن يتم إعادة تعزيز المخازن المؤقتة الثابتة أو لن يتم استدعاء setpass للمواد. يكون ذلك مفيدًا عندما تشترك تظليلات متعددة في نفس المخزن المؤقت الثابت ، حيث يمكن لـ LWRP تعبئة مواد مختلفة لهذا الغرض.في الواقع ، إذا قمت بدراسة بنية التظليل بعناية ، يمكنك أن تجد أن معظم البيانات القياسية تستخدم فقط مخازن مؤقتة ثابتة في كل مكان.بمزيد من التفصيل ، حول جميع الاختلافات ، ولكن باللغة الإنجليزية ، يمكنك قراءة هنا .بالمناسبة ، إذا نظرت عن قرب إلى SurfaceData: SurfaceData surfaceData;
قد تجد أن هذا هو PBR Master العزيزة من ShaderGraph .مثال
لذا ، أصبحت أيدينا الآن غير مقيدة تمامًا ، مما يعني أن الوقت قد حانلترتيب ملفوف! دعنا نضيف Vertex Displacement و Dissolve Effect كمثال ، والسماح للباقي بالرقص. من المريح جدًا أن تكون جميع التمريرات أمام أعيننا ويمكننا تعديل كل شيء بشكل شامل. دعونا وصف الخصائص:
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) }
والتي ستظهر بلا شك في علامة التبويب الخاصة بنا والحبيبة في المفتش: إضافة المتغيرات:
CBUFFER_START(UnityPerMaterial) ... float4 _DissolveMap_ST;
أولاً ، أرسل الهندسة إلى ذهول مخمور: Varyings LitPassVertex(Attributes input) { ... input.positionOS.xyz += normalize(input.positionOS.xyz) * sin(input.positionOS.x) * sin(_Time.x * 100); ... }
ثم الظل: Varyings ShadowPassVertex(Attributes input) { ... input.positionOS.xyz += normalize(input.positionOS.xyz) * sin(input.positionOS.x) * sin(_Time.x * 100); ... }
عمق: Varyings DepthOnlyVertex(Attributes input) { ... input.position.xyz += normalize(input.position.xyz) * sin(input.position.x) * sin(_Time.x * 100); ... }
حسنًا ، قم الآن بحل الوقت والحركة: half4 LitPassFragment(Varyings input) : SV_Target { ...
يمكنك أيضًا إدخال Dissolve في الظل ، ثم بنقرة من الرسغ ، سيكون لدينا الظل الصحيح ، وهو أمر صعب للغاية تحقيقه في Shader Graph ، وهنا فقط بضعة أسطر من التعليمات البرمجية. half4 ShadowPassFragment(Varyings input) : SV_TARGET { ... float4 mask = SAMPLE_TEXTURE2D(_DissolveMap, sampler_DissolveMap, input.uv); if (mask.r > _DissolveFactor) discard; ... }
حسنا ، رمز التظليل النهائي:SimpleDissolve Shader "TheProxor/LWRP/Dissolve + Vertex Offset" { Properties {
استنتاج
حسنًا ، حان الوقت للتقييم. كما أصبح واضحًا ، في LWRP ، من الممكن وحتى الضروري كتابة تظليل مع الكود ، لأنه يحرر يديك بشكل كبير ، مما يساعد على كتابة أشياء رائعة دون عكازات ، على سبيل المثال ، نظام الإضاءة لديك. بالطبع ، لا يمكن مقارنتها مع Standard Surface Shader المألوف والمريح ، ولكن ربما سأحصل في يوم من الأيام على الكتابة التماثلية المريحة لـ LWRP و HDRP ، ولكن المزيد حول ذلك في وقت آخر.روابط مفيدة:
مستودع مع جميع المواد من المادةوجدت هذا أثناء إعداد المواد ، وثائق غير رسمية وثائقLWRP