Pendahuluan
Halo Habr. Hari ini saya ingin berbicara sedikit tentang bagaimana Anda dapat dengan cepat dan tanpa rasa sakit (hampir) mulai menulis
text shaders klasik di Unity menggunakan
Lightweight Rendering Pipeline (LWRP) - salah satu contoh dari
pipeline Scriptable Rendering Pipeline (SRP) .
Tapi bagaimana dengan Grafik Shader?
Grafik Shader adalah alat yang mudah dan cepat untuk membuat prototipe atau menulis efek sederhana. Namun, kadang-kadang, perlu untuk menulis sesuatu yang rumit dan kompleks, dan kemudian - jumlah node, fungsi kustom, sub-grafik sangat meningkat, itulah sebabnya bahkan programmer grafis yang paling berpengalaman pun mulai menjadi bingung dalam seluruh kekacauan ini. Kita semua memahami bahwa kode yang dibuat secara otomatis a priori tidak bisa lebih baik daripada yang ditulis secara manual - Anda tidak perlu melangkah jauh untuk contoh, karena kesalahan dalam tata letak node dapat mengarah pada fakta bahwa hasil perhitungan yang sudah diketahui dalam vertex shader akan dihitung berulang kali dalam fragmen. Ada orang yang lebih nyaman bekerja dengan
kode , daripada dengan node. Alasannya mungkin berbeda, tetapi esensinya sama - turun dengan node, tinggal kode!

Masalah
Jadi, apa masalah duduk dan menulis shader teks biasa di bawah LWRP? Dan masalahnya adalah
Standard Surface Shaders semua orang tidak didukung di LWRP.
Saat mencoba menggunakannya, kami mendapatkan yang berikut:

Kode 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
Kemudian terlintas dalam pikiran untuk mencoba menulis shader anlite biasa dengan bagian vertex dan fragmen. Dan untungnya, semuanya bekerja:

Kode shader Shader "Unlit/NewUnlitShader" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
Namun, bagaimana Anda bisa tidak sedih - kita seolah dibiarkan telanjang di sela-sela tanpa cahaya, bayangan, lightmaps dan
PBR tercinta, tanpanya hidup tidak manis.
Tentu saja, Anda dapat menulis semuanya dengan tangan:
Jadilah terang!
Kode shader Shader "TheProxor/Simple Lit" { Properties { _MainTex("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
Segalanya tampak bekerja, tetapi ini hanya pencahayaan difus. Apa yang harus dilakukan selanjutnya? Anda dapat terus mengembalikan semuanya dengan tangan, tetapi itu panjang dan suram, dan PBR tidak dapat dikembalikan dengan cara apa pun, dan kami kehilangan semua chip LWRP. Karena itu, kami tidak punya pilihan selain memilih LWRP untuk mengembalikan semuanya dengan satu pukulan ajaib.
Solusi
Jadi, seperti yang Anda ketahui, LWRP dibangun atas dasar
Forward-renderer , yang berarti ia memiliki LitForwardPass sendiri, yang harus dijelaskan di suatu tempat. Jelas, di suatu tempat ada CGInclude di mana semua ini dijelaskan. Di sinilah kami memulai petualangan yang menyenangkan di:
% localappdata% \ Unity \ cache \ Packages \ Packages.unity.com \ com.unity.render-pipelines.lightweight @ (versi LWRP) \
Sebenarnya, setelah datang ke alamat yang ditentukan dan masuk ke folder
Shaders , kita dapat menemukan satu shader yang menarik bernama
Lit.shader . Sebenarnya, kita dapat mengatakan pencarian kita sudah selesai, ini dia - shader yang diidamkan. Masuk ke dalam - kami menemukan konten berikut:
Lit.pemimpin Shader "Lightweight Render Pipeline/Lit" { Properties {
Tetap hanya untuk memperluasnya agar mudah diedit, menyingkirkan sertakan. Nah, sedikit modifikasi dengan cara Anda sendiri.
Kami mendapatkan sesuatu seperti ini:

Final uber shader Shader "TheProxor/LWRP/Dissolve + Vertex Offset" { Properties {
Pertama-tama, Anda perlu mencari tahu apa yang ada di shader yang dihasilkan. Pertama, kami tertarik pada baris:
CustomEditor "UnityEditor.Rendering.LWRP.ShaderGUI.LitShaderEditor"
Yang, seperti yang kita ingat, dalam aslinya adalah sebagai berikut:
CustomEditor "UnityEditor.Rendering.LWRP.ShaderGUI.LitShader"
Jadi apa itu dan mengapa? Jawaban untuk pertanyaan ini cukup sederhana - jika Anda perhatikan lebih dekat, shader memiliki sejumlah besar jenis definisi yang berbeda, yang, anehnya, perlu diaktifkan dan dinonaktifkan, dan ini, untuk sesaat, perlu dilakukan dari kode, itulah sebabnya kami memerlukan inspektur khusus. Selain itu, inspektur kebiasaan kami harus memberi kami kesempatan untuk mengedit tidak hanya properti bawaan, tetapi juga properti yang mungkin kami butuhkan di shader kami.
Shader asli sudah memiliki inspektur khusus, jadi kami pasti menginjaknya dengan mencarinya di sepanjang jalur berikut:% localappdata% \ Unity \ cache \ Packages \ paket.unity.com \ com.unity.render-pipelines.lightweight@6.9.2 \ Editor \ ShaderGUI \ Shaders \
Sebenarnya, kami tertarik pada file LitShader.cs, yang diwarisi dari BaseShaderGUI: internal class LitShader : BaseShaderGUI { ... }
Batch utama berlangsung tepat di BaseShaderGUI.cs, yang dapat ditemukan di folder satu tingkat lebih tinggi: public abstract class BaseShaderGUI : ShaderGUI { ... }
Kami mengambil skrip ini dan melemparkannya ke folder Editor (jika tidak ada, buatlah, jika tidak, kesalahan akan muncul secara alami selama pembuatan proyek, karena namespace UnityEditor tidak termasuk dalam build). Tentu saja, di kepala kita jatuh seribu satu kesalahan, yang terkait dengan jenis-internel SavedBool , yang merupakan jenis jendela variabel bool Editor serial. Hal ini dilakukan untuk menjaga keadaan melipat bagian material. Sebenarnya, untuk koreksi kami melakukan manipulasi sederhana.Ubah: SavedBool m_SurfaceOptionsFoldout; SavedBool m_SurfaceInputsFoldout; SavedBool m_AdvancedFoldout;
Pada: AnimatedValues.AnimBool m_SurfaceOptionsFoldout; AnimatedValues.AnimBool m_SurfaceInputsFoldout; AnimatedValues.AnimBool m_AdvancedFoldout;
Dan tambahkan variabel lain untuk properti khusus tambahan: AnimatedValues.AnimBool m_OtherFoldout;
Anda juga perlu menambahkan nama dan deskripsi bagian dengan parameter khusus kami, mengamati tradisi yang ada di dalam skrip: protected class Styles {
Nah, sekarang mari kita lakukan sedikit trik. Seperti yang Anda lihat, untuk semua properti standar saya mengatur atribut HideInInspector , yang secara langsung mengisyaratkan bahwa properti ini akan disembunyikan di inspektur. Namun, ini hanya relevan untuk inspektur bahan standar, tetapi yang mana? Itu benar, kebiasaan! Ini berarti bahwa semua properti bawaan kami dirender dengan cara apa pun. Jadi mari sembunyikan mereka: ... [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 ...
Dan di dalam kode editor khusus, kami cukup memanggil rendering inspektur standar: 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(); ... }
Hasilnya jelas: Dan di sini adalah kode untuk kedua skrip:
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 {
Sekarang mari kita lihat bagian dalam shader.Hal pertama yang Anda perhatikan adalah bahwa hanya ada lima lintasan dalam shader. Mari kita membahasnya sedikit:- ForwardLit: Forward pass, kami anggap ringan, PBR, dan kabut.
- ShadowCaster: di sini adalah perhitungan bayangan.
- DepthOnly: Depth (Z-Buffer).
- Meta: cahaya panggang (lightmaps).
- Lightweight2D: sprite dan UI.
Lebih lanjut, Unity memiliki beberapa kompiler shader dan cross compiler yang berbeda. SRP membutuhkan HLSLcc. Pada platform yang menggunakan API grafis OpenGL ES (seperti Android), HLSLcc tidak digunakan secara default.Karena itu, kami memaksanya untuk terhubung: #pragma prefer_hlslcc gles
Tetapi DirectX 9 tidak didukung, jadi nonaktifkan secara paksa: #pragma exclude_renderers d3d11_9x
Dalam hal menulis kode itu sendiri, tidak ada yang berubah kecuali bahwa sekarang kami menulis tidak dalam CG, tetapi dalam HLSL murni, dan karena itu sekarang tubuh program shader akan terlihat seperti ini: HLSLPROGRAM ... ENDHLSL
Selain itu, kami masih memiliki akses ke variabel global yang sudah dikenal seperti _Time , _ScreenParams atau _WorldSpaceCameraPos . Namun, banyak fungsi standar yang dijelaskan dalam UnityCG.cginc tidak lagi tersedia bagi kami. Dalam kebanyakan kasus, ini berlaku untuk berbagai matriks transorfmatsy, misalnya, analog UnityObjectToClipPos (POSISI) adalah TransformWorldToHClip (POSISI tersebut) , baik atau kabut - bukan UNITY_APPLY_FOG (fogCoord, warna) sekarang kita ispozuem MixFog (warna, fogCoord) .Sebenarnya, mengikuti jalur ajaib yang sama dan menemukan file Core.hlsl:% localappdata% \ Unity \ cache \ Packages \ Packages.unity.com \ com.unity.render-pipelines.lightweight @ (versi LWRP) \ ShaderLibrary \ Core.hlsl
Kami dapat menemukan daftar lengkap fitur yang tersedia.Yang juga layak disebutkan adalah buffer konstan (CBUFFER) dan UnityPerMaterial. Buffer konstan digunakan untuk menyimpan data yang jarang berubah pada GPU, masing-masing, mereka dapat digunakan untuk menyimpan variabel shader. Untuk melakukan ini, cukup hubungi macroCBUFFER_START dan 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
Deklarasi variabel global atau berbagai parameter yang ditetapkan secara parametrik (dari kode atau animasi, misalnya) terjadi dengan cara lama di dalam tubuh program shader.LWRP menggunakan dua jenis buffer konstan - UnityPerObject dan UnityPerMaterial . Buffer ini diikat sekali sehingga dapat digunakan selama rendering. Secara kasar, ini berarti bahwa selama menggambar, buffer konstan tidak akan diperkuat kembali atau setpass tidak akan dipanggil untuk material. Ini bermanfaat ketika beberapa shader berbagi buffer konstan yang sama, karena LWRP dapat mengemas material yang berbeda untuk ini.Sebenarnya, jika Anda hati-hati mempelajari struktur shader, Anda dapat menemukan bahwa sebagian besar data standar hanya menggunakan buffer konstan di mana-mana.Secara lebih rinci, tentang semua perbedaan, tetapi dalam bahasa Inggris, Anda dapat membaca di sini .Omong-omong, jika Anda melihat dari dekat ke SurfaceData: SurfaceData surfaceData;
Anda mungkin menemukan bahwa ini adalah Master PBR yang sangat dihargai dari ShaderGraph .Contoh
Jadi, sekarang tangan kita sama sekali tidak terikat, yang berarti bahwa waktunya telah tibauntuk mengatur bacchanal! Mari kita tambahkan Vertex Displacement dan Dissolve Effect sebagai contoh , dan biarkan sisanya menari. Sangat nyaman bahwa semua lintasan ada di depan mata kita dan kita dapat mengedit semuanya secara komprehensif. Mari kita jelaskan propertinya:
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) }
Yang pasti akan muncul di tab kita sendiri dan yang dicintai di inspektur: Tambahkan variabel:
CBUFFER_START(UnityPerMaterial) ... float4 _DissolveMap_ST;
Pertama, kirim geometri ke pemabuk yang mabuk: Varyings LitPassVertex(Attributes input) { ... input.positionOS.xyz += normalize(input.positionOS.xyz) * sin(input.positionOS.x) * sin(_Time.x * 100); ... }
Lalu bayangannya: Varyings ShadowPassVertex(Attributes input) { ... input.positionOS.xyz += normalize(input.positionOS.xyz) * sin(input.positionOS.x) * sin(_Time.x * 100); ... }
Kedalaman: Varyings DepthOnlyVertex(Attributes input) { ... input.position.xyz += normalize(input.position.xyz) * sin(input.position.x) * sin(_Time.x * 100); ... }
Nah, sekarang Larutkan waktu dan gerakan: half4 LitPassFragment(Varyings input) : SV_Target { ...
Anda juga dapat membawa Larutkan ke dalam bayangan, kemudian dengan gerakan pergelangan tangan kita akan memiliki bayangan yang benar, yang cukup sulit untuk dicapai dalam Grafik Shader , dan di sini hanya ada beberapa baris kode. half4 ShadowPassFragment(Varyings input) : SV_TARGET { ... float4 mask = SAMPLE_TEXTURE2D(_DissolveMap, sampler_DissolveMap, input.uv); if (mask.r > _DissolveFactor) discard; ... }
Nah, kode shader terakhir:SimpleDissolve Shader "TheProxor/LWRP/Dissolve + Vertex Offset" { Properties {
Kesimpulan
Nah, ini saatnya mengambil persediaan. Ketika menjadi jelas, di LWRP adalah mungkin dan bahkan perlu untuk menulis shader dengan kode, karena sangat melepaskan tangan Anda, membantu menulis hal-hal keren tanpa kruk, misalnya, sistem pencahayaan Anda. Tentu saja, ini tidak dapat dibandingkan dengan Standard Surface Shader yang nyaman dan akrab , tetapi mungkin suatu hari nanti saya akan mendapatkan tangan untuk menulis analog nyaman yang sama untuk LWRP dan HDRP, tetapi lebih banyak tentang itu di waktu lain.Tautan yang bermanfaat:Repositori dengan semua bahan artikelDitemukan ini selama persiapan materi, dokumentasi tidak resmi DokumentasiLWRP