Skip to content

A Lightweight, Flexible, Powerful Shader GUI System for Unity.

License

MIT, Unknown licenses found

Licenses found

MIT
LICENSE
Unknown
LICENSE.meta
Notifications You must be signed in to change notification settings

JasonMa0012/LWGUI

Repository files navigation

LWGUI (Light Weight Shader GUI)

中文 | English

A lightweight, flexible, and powerful Unity Shader GUI system built to maximize material inspector productivity.

LWGUI has been proven in many large-scale commercial projects:
with concise Material Property Drawer syntax, you can build complex inspectors quickly while benefiting from a modular Drawer/Decorator extension architecture, robust MetaData lifecycle caching, a complete Ramp/Preset/Toolbar toolchain, and Timeline integration.

It significantly shortens iteration cycles while improving collaboration between artists and technical artists.

809c4a1c-ce80-48b1-b415-7e8d4bea716e

LWGUI

image-20240716183800118
A more powerful Gradient editor than UE, with support for both Shader and C# NEW: Use Ramp Atlas to include multiple Ramps in one Texture
image-20250314160119094 image-20220926025611208
NEW: When recording material parameter animations in Timeline, automatically capture changes to Toggle's Keywords to enable switching material Keywords at runtime. Feature-rich toolbar
With your sponsorship, I will update more actively. 有你的赞助我会更加积极地更新
paypal.me/JasonMa0012 723ddce6-fb86-48ff-9683-a12cf6cff7a0

Installation

  1. Make sure your environment is compatible with LWGUI

    • LWGUI <1.17: Unity 2017.4+

    • LWGUI >=1.17: Unity 2021.3+

      • Recommended minimum version: Unity 2022.2+, lower versions can be used but may have bugs
  2. Open your project

  3. Window > Package Manager > Add > Add package from git URL , enter: https://github.com/JasonMa0012/LWGUI.git

    • You can also choose to manually download the Zip from Github,then: Package Manager > Add package from disk
    • For Unity 2017, please extract the Zip directly to the Assets directory

Getting Started

  1. Create a newer or use the existing Shader

  2. Open the Shader in the code editor

  3. At the bottom of the Shader, before the last large bracket, add line:CustomEditor "LWGUI.LWGUI"

  4. Completed! Start using the following powerful Drawer to easily draw your Shader GUI

    • MaterialPropertyDrawer is C#-like attribute syntax, it can be used in front of shader properties to change the drawing method, more information can be found in the official documentation
    • You can refer to the example Shaders in the Test directory
    • Please note: Each Property can only have one Drawer, but can have multiple Decorators

Basic Drawers

Main & Sub

/// Create a Folding Group
/// 
/// group: group name (Default: Property Name)
/// keyword: keyword used for toggle, "_" = ignore, none or "__" = Property Name +  "_ON", always Upper (Default: none)
/// default Folding State: "on" or "off" (Default: off)
/// default Toggle Displayed: "on" or "off" (Default: on)
/// preset File Name: "Shader Property Preset" asset name, see Preset() for detail (Default: none)
/// Target Property Type: Float, express Toggle value
public MainDrawer() : this(String.Empty) { }
public MainDrawer(string group) : this(group, String.Empty) { }
public MainDrawer(string group, string keyword) : this(group, keyword, "off") { }
public MainDrawer(string group, string keyword, string defaultFoldingState) : this(group, keyword, defaultFoldingState, "on") { }
public MainDrawer(string group, string keyword, string defaultFoldingState, string defaultToggleDisplayed) : this(group, keyword, defaultFoldingState, defaultToggleDisplayed, String.Empty) { }
public MainDrawer(string group, string keyword, string defaultFoldingState, string defaultToggleDisplayed, string presetFileName)
/// Draw a property with default style in the folding group
/// 
/// group: parent group name (Default: none)
/// Target Property Type: Any
public SubDrawer() { }
public SubDrawer(string group)

Example:

[Title(Main Samples)]
[Main(GroupName)]
_group ("Group", float) = 0
[Sub(GroupName)] _float ("Float", float) = 0


[Main(Group1, _KEYWORD, on)] _group1 ("Group - Default Open", float) = 1
[Sub(Group1)] _float1 ("Sub Float", float) = 0
[Sub(Group1)] _vector1 ("Sub Vector", vector) = (1, 1, 1, 1)
[Sub(Group1)] [HDR] _color1 ("Sub HDR Color", color) = (0.7, 0.7, 1, 1)

[Title(Group1, Conditional Display Samples       Enum)]
[KWEnum(Group1, Name 1, _KEY1, Name 2, _KEY2, Name 3, _KEY3)]
_enum ("KWEnum", float) = 0

// Display when the keyword ("group name + keyword") is activated
[Sub(Group1_KEY1)] _key1_Float1 ("Key1 Float", float) = 0
[Sub(Group1_KEY2)] _key2_Float2 ("Key2 Float", float) = 0
[Sub(Group1_KEY3)] _key3_Float3_Range ("Key3 Float Range", Range(0, 1)) = 0
[SubPowerSlider(Group1_KEY3, 10)] _key3_Float4_PowerSlider ("Key3 Power Slider", Range(0, 1)) = 0

[Title(Group1, Conditional Display Samples       Toggle)]
[SubToggle(Group1, _TOGGLE_KEYWORD)] _toggle ("SubToggle", float) = 0
[Tex(Group1_TOGGLE_KEYWORD)][Normal] _normal ("Normal Keyword", 2D) = "bump" { }
[Sub(Group1_TOGGLE_KEYWORD)] _float2 ("Float Keyword", float) = 0


[Main(Group2, _, off, off)] _group2 ("Group - Without Toggle", float) = 0
[Sub(Group2)] _float3 ("Float 2", float) = 0

Default result:

image-20220828003026556

Then change values:

image-20220828003129588

Extra Drawers

Numeric

SubToggle

/// Similar to builtin Toggle()
/// 
/// group: parent group name (Default: none)
/// keyword: keyword used for toggle, "_" = ignore, none or "__" = Property Name +  "_ON", always Upper (Default: none)
/// preset File Name: "Shader Property Preset" asset name, see Preset() for detail (Default: none)
/// Target Property Type: Float
public SubToggleDrawer() { }
public SubToggleDrawer(string group) : this(group, String.Empty, String.Empty) { }
public SubToggleDrawer(string group, string keyWord) : this(group, keyWord, String.Empty) { }
public SubToggleDrawer(string group, string keyWord, string presetFileName)

SubPowerSlider

/// Similar to builtin PowerSlider()
/// 
/// group: parent group name (Default: none)
/// power: power of slider (Default: 1)
/// presetFileName: "Shader Property Preset" asset name, it rounds up the float to choose which Preset to use.  
///    You can create new Preset by  
///    "Right Click > Create > LWGUI > Shader Property Preset" in Project window,  
///    *any Preset in the entire project cannot have the same name*
/// Target Property Type: Range
public SubPowerSliderDrawer(float power) : this("_", power) { }  
public SubPowerSliderDrawer(string group, float power) : this(group, power, string.Empty) { }  
public SubPowerSliderDrawer(string group, float power, string presetFileName)

SubIntRange

/// Similar to builtin IntRange()
/// 
/// group: parent group name (Default: none)
/// Target Property Type: Range
public SubIntRangeDrawer(string group)

MinMaxSlider

/// Draw a min max slider
/// 
/// group: parent group name (Default: none)
/// minPropName: Output Min Property Name
/// maxPropName: Output Max Property Name
/// Target Property Type: Range, range limits express the MinMaxSlider value range
/// Output Min/Max Property Type: Range, it's value is limited by it's range
public MinMaxSliderDrawer(string minPropName, string maxPropName) : this("_", minPropName, maxPropName) { }
public MinMaxSliderDrawer(string group, string minPropName, string maxPropName)

Example:

[Title(MinMaxSlider Samples)]
[MinMaxSlider(_rangeStart, _rangeEnd)] _minMaxSlider("Min Max Slider (0 - 1)", Range(0.0, 1.0)) = 1.0
/*[HideInInspector]*/_rangeStart("Range Start", Range(0.0, 0.5)) = 0.0
/*[HideInInspector]*/[PowerSlider(10)] _rangeEnd("Range End PowerSlider", Range(0.5, 1.0)) = 1.0

Result:

image-20220828003810353

KWEnum

/// Similar to builtin Enum() / KeywordEnum()
/// 
/// group: parent group name (Default: none)
/// n(s): display name
/// k(s): keyword
/// v(s): value
/// Target Property Type: Float, express current keyword index
public KWEnumDrawer(string n1, string k1)
public KWEnumDrawer(string n1, string k1, string n2, string k2)
public KWEnumDrawer(string n1, string k1, string n2, string k2, string n3, string k3)
public KWEnumDrawer(string n1, string k1, string n2, string k2, string n3, string k3, string n4, string k4)
public KWEnumDrawer(string n1, string k1, string n2, string k2, string n3, string k3, string n4, string k4, string n5, string k5)
  
public KWEnumDrawer(string group, string n1, string k1)
public KWEnumDrawer(string group, string n1, string k1, string n2, string k2)
public KWEnumDrawer(string group, string n1, string k1, string n2, string k2, string n3, string k3)
public KWEnumDrawer(string group, string n1, string k1, string n2, string k2, string n3, string k3, string n4, string k4)
public KWEnumDrawer(string group, string n1, string k1, string n2, string k2, string n3, string k3, string n4, string k4, string n5, string k5)

SubEnum & SubKeywordEnum

// enumName: like "UnityEngine.Rendering.BlendMode"
public SubEnumDrawer(string group, string enumName) : base(group, enumName)

public SubEnumDrawer(string group, string n1, float v1, string n2, float v2)
public SubEnumDrawer(string group, string n1, float v1, string n2, float v2, string n3, float v3)
public SubEnumDrawer(string group, string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4)
public SubEnumDrawer(string group, string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5)
public SubEnumDrawer(string group, string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6)
public SubEnumDrawer(string group, string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7)


public SubKeywordEnumDrawer(string group, string kw1, string kw2)
public SubKeywordEnumDrawer(string group, string kw1, string kw2, string kw3)
public SubKeywordEnumDrawer(string group, string kw1, string kw2, string kw3, string kw4)
public SubKeywordEnumDrawer(string group, string kw1, string kw2, string kw3, string kw4, string kw5)
public SubKeywordEnumDrawer(string group, string kw1, string kw2, string kw3, string kw4, string kw5, string kw6)
public SubKeywordEnumDrawer(string group, string kw1, string kw2, string kw3, string kw4, string kw5, string kw6, string kw7)
public SubKeywordEnumDrawer(string group, string kw1, string kw2, string kw3, string kw4, string kw5, string kw6, string kw7, string kw8)
public SubKeywordEnumDrawer(string group, string kw1, string kw2, string kw3, string kw4, string kw5, string kw6, string kw7, string kw8, string kw9)

Preset

/// Popping a menu, you can select the Shader Property Preset, the Preset values will replaces the default values
/// 
/// group: parent group name (Default: none)
///	presetFileName: "Shader Property Preset" asset name, you can create new Preset by
///		"Right Click > Create > LWGUI > Shader Property Preset" in Project window,
///		*any Preset in the entire project cannot have the same name*
/// Target Property Type: Float, express current keyword index
public PresetDrawer(string presetFileName) : this("_", presetFileName) {}
public PresetDrawer(string group, string presetFileName)

Example:

[Title(Preset Samples)]
[Preset(LWGUI_BlendModePreset)] _BlendMode ("Blend Mode Preset", float) = 0 
[Enum(UnityEngine.Rendering.CullMode)]_Cull("Cull", Float) = 2
[Enum(UnityEngine.Rendering.BlendMode)]_SrcBlend("SrcBlend", Float) = 1
[Enum(UnityEngine.Rendering.BlendMode)]_DstBlend("DstBlend", Float) = 0
[Toggle(_)]_ZWrite("ZWrite ", Float) = 1
[Enum(UnityEngine.Rendering.CompareFunction)]_ZTest("ZTest", Float) = 4 // 4 is LEqual
[Enum(RGBA,15,RGB,14)]_ColorMask("ColorMask", Float) = 15 // 15 is RGBA (binary 1111)
  

Cull [_Cull] ZWrite [_ZWrite] Blend [_SrcBlend] [_DstBlend] ColorMask [_ColorMask]


Result:

The Property Value in the selected Preset will be the default value:

![image-20221122231655378](assets~/image-20221122231655378.png)![image-20221122231816714](assets~/image-20221122231816714.png)

##### Create Preset File

![image-20221122232307362](assets~/image-20221122232307362.png)

##### Edit Preset

![image-20221122232354623](assets~/image-20221122232354623.png)![image-20221122232415972](assets~/image-20221122232415972.png)![image-20221122232425194](assets~/image-20221122232425194.png)

#### BitMask

```C#
/// Draw the Int value as a Bit Mask.
/// Note:
///    - Currently only 8 bits are supported.
///
/// Warning 1: If used to set Stencil, it will conflict with SRP Batcher!  
///		(Reproduced in Unity 2022)  
///		SRP Batcher does not correctly handle multiple materials with different Stencil Ref values,  
///		mistakenly merging them into a single Batch and randomly selecting one material's Stencil Ref value for the entire Batch.  
///		In theory, if different materials have different Stencil Ref values, they should not be merged into a single Batch due to differing Render States.  
/// Solution:  
///		- Force disable SRP Batcher by setting the Material Property Block  
///		- Place materials with the same Stencil Ref value in a separate Render Queue to ensure the Batch's Render State is correct
///
/// Warning 2: Once in use, do not change the Target Property Type!
///		The underlying type of Int Property is Float Property, and in Materials, Int and Integer are stored separately.  
///		Once a Material is saved, the Property Type is determined.  
///		If you change the Property Type at this point (such as switching between Int/Integer), some strange bugs may occur.  
///		If you must change the Property Type, it is recommended to modify the Property Name as well or delete the saved Property in the material.
/// group: parent group name (Default: none)
/// bitDescription 7-0: Description of each Bit. (Default: none)
/// Target Property Type: Int
public BitMaskDrawer() : this(string.Empty, null) { }
public BitMaskDrawer(string group) : this(group, null) { }
public BitMaskDrawer(string group, string bitDescription7, string bitDescription6, string bitDescription5, string bitDescription4, string bitDescription3, string bitDescription2, string bitDescription1, string bitDescription0) 
    : this(group, new List<string>() { bitDescription0, bitDescription1, bitDescription2, bitDescription3, bitDescription4, bitDescription5, bitDescription6, bitDescription7 }) { }

Example:

[BitMask(Preset)] _Stencil ("Stencil", Integer) = 0  
[BitMask(Preset, Left, Bit6, Bit5, Bit4, Description, Bit2, Bit1, Right)] _StencilWithDescription ("Stencil With Description", Integer) = 0

Result:

Caution

If used to set Stencil, it will conflict with SRP Batcher! (Reproduced in Unity 2022)

SRP Batcher does not correctly handle multiple materials with different Stencil Ref values, mistakenly merging them into a single Batch and randomly selecting one material's Stencil Ref value for the entire Batch. In theory, if different materials have different Stencil Ref values, they should not be merged into a single Batch due to differing Render States.

Solution:

  • Force disable SRP Batcher by setting the Material Property Block
  • Place materials with the same Stencil Ref value in a separate Render Queue to ensure the Batch's Render State is correct

RampAtlasIndexer

/// Visually similar to Ramp(), but RampAtlasIndexer() must be used together with RampAtlas().  
/// The actual stored value is the index of the current Ramp in the Ramp Atlas SO, used for sampling the Ramp Atlas Texture in the Shader.
///  
/// group: parent group name.  
/// rampAtlasPropName: RampAtlas() property name.  
/// defaultRampName: default ramp name. (Default: Ramp)  
/// colorSpace: default ramp color space. (sRGB/Linear) (Default: sRGB)  
/// viewChannelMask: editable channels. (Default: RGBA)  
/// timeRange: the abscissa display range (1/24/2400), is used to optimize the editing experience when the abscissa is time of day. (Default: 1)  
/// Target Property Type: Float
public RampAtlasIndexerDrawer(string group, string rampAtlasPropName) : this(group, rampAtlasPropName, "Ramp") {}  
public RampAtlasIndexerDrawer(string group, string rampAtlasPropName, string defaultRampName) : this(group, rampAtlasPropName, defaultRampName, "sRGB") {}  
public RampAtlasIndexerDrawer(string group, string rampAtlasPropName, string defaultRampName, string colorSpace) : this(group, rampAtlasPropName, defaultRampName, colorSpace, "RGBA") {}  
public RampAtlasIndexerDrawer(string group, string rampAtlasPropName, string defaultRampName, string colorSpace, string viewChannelMask) : this(group, rampAtlasPropName, defaultRampName, colorSpace, viewChannelMask, 1) {}  
public RampAtlasIndexerDrawer(string group, string rampAtlasPropName, string defaultRampName, string colorSpace, string viewChannelMask, float timeRange)  

See details for usage: RampAtlas()

Texture

Tex

/// Draw a Texture property in single line with a extra property
/// 
/// group: parent group name (Default: none)
/// extraPropName: extra property name (Default: none)
/// Target Property Type: Texture
/// Extra Property Type: Color, Vector
/// Target Property Type: Texture2D
public TexDrawer() { }
public TexDrawer(string group) : this(group, String.Empty) { }
public TexDrawer(string group, string extraPropName)

Example:

[Main(Group3, _, on)] _group3 ("Group - Tex and Color Samples", float) = 0
[Tex(Group3, _color)] _tex_color ("Tex with Color", 2D) = "white" { }
[HideInInspector] _color (" ", Color) = (1, 0, 0, 1)
[Tex(Group3, _textureChannelMask1)] _tex_channel ("Tex with Channel", 2D) = "white" { }
[HideInInspector] _textureChannelMask1(" ", Vector) = (0,0,0,1)

// Display up to 4 colors in a single line
[Color(Group3, _mColor1, _mColor2, _mColor3)]
_mColor ("Multi Color", Color) = (1, 1, 1, 1)
[HideInInspector] _mColor1 (" ", Color) = (1, 0, 0, 1)
[HideInInspector] _mColor2 (" ", Color) = (0, 1, 0, 1)
[HideInInspector] [HDR] _mColor3 (" ", Color) = (0, 0, 1, 1)

Result:

image-20220828003507825

Ramp

ShaderLab
/// Draw an unreal style Ramp Map Editor (Default Ramp Map Resolution: 512 * 2)
/// NEW: The new LwguiGradient type has both the Gradient and Curve editors, and can be used in C# scripts and runtime, and is intended to replace UnityEngine.Gradient
///
/// group: parent group name (Default: none)
/// defaultFileName: default Ramp Map file name when create a new one (Default: RampMap)
/// rootPath: the path where ramp is stored, replace '/' with '.' (for example: Assets.Art.Ramps). when selecting ramp, it will also be filtered according to the path (Default: Assets)
/// colorSpace: switch sRGB / Linear in ramp texture import setting (Default: sRGB)
/// defaultWidth: default Ramp Width (Default: 256)
/// viewChannelMask: editable channels. (Default: RGBA)
/// timeRange: the abscissa display range (1/24/2400), is used to optimize the editing experience when the abscissa is time of day. (Default: 1)
/// Target Property Type: Texture2D
public RampDrawer() : this(String.Empty) { }
public RampDrawer(string group) : this(group, "RampMap") { }
public RampDrawer(string group, string defaultFileName) : this(group, defaultFileName, DefaultRootPath, 256) { }
public RampDrawer(string group, string defaultFileName, float defaultWidth) : this(group, defaultFileName, DefaultRootPath, defaultWidth) { }
public RampDrawer(string group, string defaultFileName, string rootPath, float defaultWidth) : this(group, defaultFileName, rootPath, "sRGB", defaultWidth) { }
public RampDrawer(string group, string defaultFileName, string rootPath, string colorSpace, float defaultWidth) : this(group, defaultFileName, rootPath, colorSpace, defaultWidth, "RGBA") { }
public RampDrawer(string group, string defaultFileName, string rootPath, string colorSpace, float defaultWidth, string viewChannelMask) : this(group, defaultFileName, rootPath, colorSpace, defaultWidth, viewChannelMask, 1) { }
public RampDrawer(string group, string defaultFileName, string rootPath, string colorSpace, float defaultWidth, string viewChannelMask, float timeRange)

Example:

[Ramp(_, RampMap, Assets.Art, 512)] _Ramp ("Ramp Map", 2D) = "white" { }

Result:

image-20230625185730363

You must manually Save the edit results, if there are unsaved changes, the Save button will display yellow.

When you move or copy the Ramp Map, remember to move together with the .meta file, otherwise you will not be able to edit it again!

C#

Example:

public class Test : MonoBehaviour
{
    public LwguiGradient lwguiGradientSrgb = new LwguiGradient();

    [LwguiGradientUsage(ColorSpace.Linear, LwguiGradient.ChannelMask.RGB, LwguiGradient.GradientTimeRange.TwentyFourHundred)]
    public LwguiGradient lwguiGradientLinear = new LwguiGradient();
}

Result:

image-20240717104144821image-20240717104206365

Default display settings can be set using the LwguiGradientUsage() Attribute.

Gradient Editor

The new LWGUI Gradient Editor integrates with Unity's built-in Gradient Editor and Curve Editor, enabling more powerful features than UE's Gradient Editor.

image-20241126110012922

Editor Description
Time Range The display range of the horizontal axis, selectable from 0-1 / 0-24 / 0-2400, is very useful when the horizontal axis is time. Note that only the display is affected, the actual value stored in the horizontal axis is always 0-1.
Channels The channels displayed. Can be displayed individually.
sRGB Preview It should be checked when the value of Gradient is a color to preview the correct color, otherwise it doesn't need to be checked. Only affects the display, Gradient and Ramp Map are always stored as Linear.
Value / R / G / B / A Used to edit the Value of the selected Key, you can edit the Value of multiple Keys at the same time.
Time Used to edit the Time of the selected Key, you can edit the Time of multiple Keys at the same time. If you enter a number manually, you mustpress enter to end the editing.
Gradient Editor This is similar to Unity's built-inGradient Editor, but the Alpha channels are separated into black and white. ``Note that adding keys from the Gradient Editor is limited to a maximum of 8 keys, adding keys from the Curve Editor is unlimited. Exceeding the key limit will not affect preview or usage.
Curve Editor Similar to Unity's built-in Curve Editor, it displays the XY 0-1 range by default, and you can use the scroll wheel to zoom or move the display range.``As you can see in the image below, the context menu has a number of functions for controlling the shape of the curve, and you can consult the Unity documentation to get the most out of these functions.
Presets You can save the current LWGUI Gradient as a preset and apply it anytime. These presets are common between different engine versions on the local computer, but are not saved to the project.

image-20241126105823397image-20241126112320151

Note

Known issues:

  • Preview images below Unity 2022 have no difference between sRGB/Linear color spaces
  • Ctrl + Z results may be slightly different from expected when the editor frame rate is too low

RampAtlas

/// Draw a "Ramp Atlas Scriptable Object" selector and texture preview.  
/// The Ramp Atlas SO is responsible for storing multiple ramps and generating the corresponding Ramp Atlas Texture.  
/// Use it together with RampAtlasIndexer() to sample specific ramps in Shader using Index, similar to UE's Curve Atlas.  
/// Note: Currently, the material only saves Texture reference and Int value,  
///      if you manually modify the Ramp Atlas, the references will not update automatically!
///  
/// group: parent group name (Default: none)
/// defaultFileName: the default file name when creating a Ramp Atlas SO (Default: RampAtlas)
/// rootPath: the default directory when creating a Ramp Atlas SO, replace '/' with '.' (for example: Assets.Art.RampAtlas). (Default: Assets)
/// colorSpace: the Color Space of Ramp Atlas Texture. (sRGB/Linear) (Default: sRGB)
/// defaultWidth: default Ramp Atlas Texture width (Default: 256)
/// defaultHeight: default Ramp Atlas Texture height (Default: 4)
/// showAtlasPreview: Draw the preview of Ramp Atlas below (True/False) (Default: True)
/// rampAtlasTypeName: custom RampAtlas type name for user-defined RampAtlas classes (Default: LwguiRampAtlas)
/// Target Property Type: Texture2D
public RampAtlasDrawer() : this(string.Empty) { }  
public RampAtlasDrawer(string group) : this(group, "RampAtlas") { }  
public RampAtlasDrawer(string group, string defaultFileName) : this(group, defaultFileName, "Assets") { }  
public RampAtlasDrawer(string group, string defaultFileName, string rootPath) : this(group, defaultFileName, rootPath, "sRGB") { }  
public RampAtlasDrawer(string group, string defaultFileName, string rootPath, string colorSpace) : this(group, defaultFileName, rootPath, colorSpace, 256) { } 
public RampAtlasDrawer(string group, string defaultFileName, string rootPath, string colorSpace, float defaultWidth) : this(group, defaultFileName, rootPath, colorSpace, defaultWidth, 4) { }  
public RampAtlasDrawer(string group, string defaultFileName, string rootPath, string colorSpace, float defaultWidth, float defaultHeight) : this(group, defaultFileName, rootPath, colorSpace, defaultWidth, defaultHeight, "true") { }  
public RampAtlasDrawer(string group, string defaultFileName, string rootPath, string colorSpace, float defaultWidth, float defaultHeight, string showAtlasPreview) : this(group, defaultFileName, rootPath, colorSpace, defaultWidth, defaultHeight, showAtlasPreview, "") { }  
public RampAtlasDrawer(string group, string defaultFileName, string rootPath, string colorSpace, float defaultWidth, float defaultHeight, string showAtlasPreview, string rampAtlasTypeName)

Example:

[RampAtlas(g2)] _RampAtlas ("Ramp Atlas", 2D) = "white" { }  
[Space]  
[RampAtlasIndexer(g2, _RampAtlas, Default Ramp)] _RampAtlasIndex0 ("Indexer", float) = 0  
[RampAtlasIndexer(g2, _RampAtlas, Default Ramp)] _RampAtlasIndex1 ("Indexer", float) = 1  
[RampAtlasIndexer(g2, _RampAtlas, Green, Linear, GA, 24)] _RampAtlasIndex2 ("Indexer Linear/Green/24", float) = 3

Result:

Shaderlab:

sampler2D _RampAtlas;  
float4 _RampAtlas_TexelSize;  
int _RampAtlasIndex0;

......

float2 rampUV = float2(i.uv.x, _RampAtlas_TexelSize.y * (_RampAtlasIndex0 + 0.5f));  
fixed4 color = tex2D(_RampAtlas, saturate(rampUV));
Ramp Atlas Scriptable Object

Ramp Atlas SO is responsible for storing and generating Ramp Atlas Texture: When loading an SO or modifying a Ramp on a material, a Ramp Atlas Texture will be automatically created at the same path as the SO, with the file extension .tga. After manually modifying the SO, you need to click Save Texture Toggle to generate the Texture.

You can create an SO in the following ways:

  • Right-click in the Project panel: Create > LWGUI > Ramp Atlas
  • Right-click on a material property using RampAtlas(): Create Ramp Atlas or Clone Ramp Atlas
    • An SO created this way will include default values for all Ramps in the current material.

You can click the add button of RampAtlasIndexer() to add a new Ramp to the SO.

The context menu in the upper right corner has a one-click color space conversion feature.

Caution

Currently, the material only saves Texture references and Int values. If you manually modify the number and order of Ramps in the Ramp Atlas SO, the selected Ramps in the material may be disrupted!

Suggestions:

  • Limit the usage scope of a single Ramp Atlas
  • Only add Ramps
  • Do not modify the Ramp order

Image

/// Draw an image preview.
/// display name: The path of the image file relative to the Unity project, such as: "assets~/test.png", "Doc/test.png", "../test.png"
/// 
/// group: parent group name (Default: none)
/// Target Property Type: Any
public ImageDrawer() { }
public ImageDrawer(string group)

Result:

image-20240416142736663

Vector

Color

/// Display up to 4 colors in a single line
/// 
/// group: parent group name (Default: none)
/// color2-4: extra color property name
/// Target Property Type: Color
public ColorDrawer(string group, string color2) : this(group, color2, String.Empty, String.Empty) { }
public ColorDrawer(string group, string color2, string color3) : this(group, color2, color3, String.Empty) { }
public ColorDrawer(string group, string color2, string color3, string color4)

Example:

[Main(Group3, _, on)] _group3 ("Group - Tex and Color Samples", float) = 0
[Tex(Group3, _color)] _tex_color ("Tex with Color", 2D) = "white" { }
[HideInInspector] _color (" ", Color) = (1, 0, 0, 1)
[Tex(Group3, _textureChannelMask1)] _tex_channel ("Tex with Channel", 2D) = "white" { }
[HideInInspector] _textureChannelMask1(" ", Vector) = (0,0,0,1)

// Display up to 4 colors in a single line
[Color(Group3, _mColor1, _mColor2, _mColor3)]
_mColor ("Multi Color", Color) = (1, 1, 1, 1)
[HideInInspector] _mColor1 (" ", Color) = (1, 0, 0, 1)
[HideInInspector] _mColor2 (" ", Color) = (0, 1, 0, 1)
[HideInInspector] [HDR] _mColor3 (" ", Color) = (0, 0, 1, 1)

Result:

image-20220828003507825

Channel

/// Draw a R/G/B/A drop menu:
/// 	R = (1, 0, 0, 0)
/// 	G = (0, 1, 0, 0)
/// 	B = (0, 0, 1, 0)
/// 	A = (0, 0, 0, 1)
/// 	RGB Average = (1f / 3f, 1f / 3f, 1f / 3f, 0)
/// 	RGB Luminance = (0.2126f, 0.7152f, 0.0722f, 0)
///		None = (0, 0, 0, 0)
/// 
/// group: parent group name (Default: none)
/// Target Property Type: Vector, used to dot() with Texture Sample Value
public ChannelDrawer() { }
public ChannelDrawer(string group)

Example:

[Title(_, Channel Samples)]
[Channel(_)]_textureChannelMask("Texture Channel Mask (Default G)", Vector) = (0,1,0,0)

......

float selectedChannelValue = dot(tex2D(_Tex, uv), _textureChannelMask);

image-20220822010511978

Other

Button

/// Draw one or more Buttons within the same row, using the Display Name to control the appearance and behavior of the buttons
/// 
/// Declaring a set of Button Name and Button Command in Display Name generates a Button, separated by '@':
/// ButtonName0@ButtonCommand0@ButtonName1@ButtonCommand1
/// 
/// Button Name can be any other string, the format of Button Command is:
/// TYPE:Argument
/// 
/// The following TYPEs are currently supported:
/// - URL: Open the URL, Argument is the URL
/// - C#: Call the public static C# function, Argument is NameSpace.Class.Method(arg0, arg1, ...),
///		for target function signatures, see: LWGUI.ButtonDrawer.TestMethod().
///
/// The full example:
/// [Button(_)] _button0 ("URL Button@URL:https://github.com/JasonMa0012/LWGUI@C#:LWGUI.ButtonDrawer.TestMethod(1234, abcd)", Float) = 0
/// 
/// group: parent group name (Default: none)
/// Target Property Type: Any
public ButtonDrawer() { }
public ButtonDrawer(string group)

Example:

[Title(Button Samples)]
[Button(_)] _button0 ("URL Button@URL:https://github.com/JasonMa0012/LWGUI@C# Button@C#:LWGUI.ButtonDrawer.TestMethod(1234, abcd)", Float) = 0

image-20241127180711449

Extra Decorators

Appearance

Title & SubTitle

/// <summary>
/// Similar to Header()
/// 
/// group: parent group name (Default: none)
/// header: string to display, "SpaceLine" or "_" = none (Default: none)
/// height: line height (Default: 22)
public TitleDecorator(string header) : this("_", header, DefaultHeight) {}
public TitleDecorator(string header, float  height) : this("_", header, height) {}
public TitleDecorator(string group,  string header) : this(group, header, DefaultHeight) {}
public TitleDecorator(string group, string header, float height)


/// Similar to Title()
/// 
/// group: parent group name (Default: none)
/// header: string to display, "SpaceLine" or "_" = none (Default: none)
/// height: line height (Default: 22)
public SubTitleDecorator(string group,  string header) : base(group, header, DefaultHeight) {}
public SubTitleDecorator(string group, string header, float height) : base(group, header, height) {}

Tooltip & Helpbox

/// Tooltip, describes the details of the property. (Default: property.name and property default value)
/// You can also use "#Text" in DisplayName to add Tooltip that supports Multi-Language.
/// 
/// tooltip: a single-line string to display, support up to 4 ','. (Default: Newline)
public TooltipDecorator() : this(string.Empty) {}
public TooltipDecorator(string tooltip) { this._tooltip = tooltip; }
public TooltipDecorator(string s1, string s2) : this(s1 + ", " + s2) { }
public TooltipDecorator(string s1, string s2, string s3) : this(s1 + ", " + s2 + ", " + s3) { }
public TooltipDecorator(string s1, string s2, string s3, string s4) : this(s1 + ", " + s2 + ", " + s3 + ", " + s4) { }
public TooltipDecorator(string s1, string s2, string s3, string s4, string s5) : this(s1 + ", " + s2 + ", " + s3 + ", " + s4 + ", " + s5) { }
/// Display a Helpbox on the property
/// You can also use "%Text" in DisplayName to add Helpbox that supports Multi-Language.
/// 
/// message: a single-line string to display, support up to 4 ','. (Default: Newline)
public HelpboxDecorator() : this(string.Empty) {}
public HelpboxDecorator(string message) { this._message = message; }
public HelpboxDecorator(string s1, string s2) : this(s1 + ", " + s2) { }
public HelpboxDecorator(string s1, string s2, string s3) : this(s1 + ", " + s2 + ", " + s3) { }
public HelpboxDecorator(string s1, string s2, string s3, string s4) : this(s1 + ", " + s2 + ", " + s3 + ", " + s4) { }
public HelpboxDecorator(string s1, string s2, string s3, string s4, string s5) : this(s1 + ", " + s2 + ", " + s3 + ", " + s4 + ", " + s5) { }

Example:

[Title(Metadata Samples)]
[Tooltip(Test multiline Tooltip, a single line supports up to 4 commas)]
[Tooltip()]
[Tooltip(Line 3)]
[Tooltip(Line 4)]
_float_tooltip ("Float with Tooltips#这是中文Tooltip#これは日本語Tooltipです", float) = 1
[Helpbox(Test multiline Helpbox)]
[Helpbox(Line2)]
[Helpbox(Line3)]
_float_helpbox ("Float with Helpbox%这是中文Helpbox%これは日本語Helpboxです", float) = 1

image-20221231221240686

image-20221231221254101

Tips:

  • Tooltip may disappear when the Editor is running. This is a feature of Unity itself (or a bug)

ReadOnly

/// Set the property to read-only.
public ReadOnlyDecorator()

Logic

PassSwitch

/// Cooperate with Toggle to switch certain Passes.
/// 
/// lightModeName(s): Light Mode in Shader Pass (https://docs.unity3d.com/2017.4/Documentation/Manual/SL-PassTags.html)
public PassSwitchDecorator(string   lightModeName1) 
public PassSwitchDecorator(string   lightModeName1, string lightModeName2) 
public PassSwitchDecorator(string   lightModeName1, string lightModeName2, string lightModeName3) 
public PassSwitchDecorator(string   lightModeName1, string lightModeName2, string lightModeName3, string lightModeName4) 
public PassSwitchDecorator(string   lightModeName1, string lightModeName2, string lightModeName3, string lightModeName4, string lightModeName5) 
public PassSwitchDecorator(string   lightModeName1, string lightModeName2, string lightModeName3, string lightModeName4, string lightModeName5, string lightModeName6) 

Structure

Advanced & AdvancedHeaderProperty

/// Collapse the current Property into an Advanced Block.
/// Specify the Header String to create a new Advanced Block.
/// All Properties using Advanced() will be collapsed into the nearest Advanced Block.
/// 
/// headerString: The title of the Advanced Block. Default: "Advanced"
public AdvancedDecorator() : this(string.Empty) { }
public AdvancedDecorator(string headerString)
/// Create an Advanced Block using the current Property as the Header.
public AdvancedHeaderPropertyDecorator()

Example:

[Main(Group2, _, off, off)] _group2 ("Group - Without Toggle", float) = 0
[Sub(Group2)] _float3 ("Float 2", float) = 0
[Advanced][Sub(Group2)] _Advancedfloat0 ("Advanced Float 0", float) = 0
[Advanced][Sub(Group2)] _Advancedfloat1 ("Advanced Float 1", float) = 0
[Advanced(Advanced Header Test)][Sub(Group2)] _Advancedfloat3 ("Advanced Float 3", float) = 0
[Advanced][Sub(Group2)] _Advancedfloat4 ("Advanced Float 4", float) = 0
[AdvancedHeaderProperty][Tex(Group2, _Advancedfloat7)] _AdvancedTex0 ("Advanced Header Property Test", 2D) = "white" { }
[Advanced][HideInInspector] _Advancedfloat7 ("Advanced Float 7", float) = 0
[Advanced][Tex(Group2, _AdvancedRange0)] _AdvancedTex1 ("Advanced Tex 1", 2D) = "white" { }
[Advanced][HideInInspector] _AdvancedRange0 ("Advanced Range 0", Range(0, 1)) = 0

image-20231007163044176

Tips:

  • LWGUI uses a tree data structure to store the relationship between Group, Advanced Block and their children. In theory, it can store unlimited multi-level parent-child relationships, but currently LWGUI only manually handles 3-level parent-child relationships, which means you can put an Advanced Block in a Group, but a Group cannot be placed in an Advanced Block.

Condition Display

Hidden

/// Similar to HideInInspector(), the difference is that Hidden() can be unhidden through the Display Mode button.
public HiddenDecorator()

ShowIf

/// Control the show or hide of a single or a group of properties based on multiple conditions.
///
/// logicalOperator: And | Or (Default: And).
/// propName: Target Property Name used for comparison.
/// compareFunction: Less (L) | Equal (E) | LessEqual (LEqual / LE) | Greater (G) | NotEqual (NEqual / NE) | GreaterEqual (GEqual / GE).
/// value: Target Property Value used for comparison.
public ShowIfDecorator(string propName, string comparisonMethod, float value) : this("And", propName, comparisonMethod, value) { }
public ShowIfDecorator(string logicalOperator, string propName, string compareFunction, float value)

Example:

[ShowIf(_enum, Equal, 1)]
[Title(ShowIf Main Samples)]
[Main(GroupName)] _group ("Group", float) = 0
[Sub(GroupName)] _float ("Float", float) = 0
[Sub(GroupName)] _Tex ("Tex", 2D) = "white" { }

...

[SubTitle(Group1, Conditional Display Samples       Enum)]
[KWEnum(Group1, Name 1, _KEY1, Name 2, _KEY2, Name 3, _KEY3)] _enum ("KWEnum", float) = 0
[Sub(Group1)][ShowIf(_enum, Equal, 0)] _key1_Float1 ("Key1 Float", float) = 0
[Sub(Group1)][ShowIf(_enum, Equal, 1)] _key2_Float2 ("Key2 Float", float) = 0
[SubIntRange(Group1)][ShowIf(_enum, Equal, 2)] _key3_Int_Range ("Key3 Int Range", Range(0, 10)) = 0
[ShowIf(_enum, Equal, 0)][ShowIf(Or, _enum, Equal, 2)]
[SubPowerSlider(Group1, 3)] _key13_PowerSlider ("Key1 or Key3 Power Slider", Range(0, 1)) = 0

image-20231023010137495

image-20231023010153213

image-20231023010204399

ActiveIf

/// Control whether a single property or a group can be edited based on multiple conditions.
/// 
/// logicalOperator: And | Or (Default: And).
/// propName: Target Property Name used for comparison.
/// compareFunction: Less (L) | Equal (E) | LessEqual (LEqual / LE) | Greater (G) | NotEqual (NEqual / NE) | GreaterEqual (GEqual / GE).
/// value: Target Property Value used for comparison.
/// 
/// When the condition is false, the property is read-only.
public ActiveIfDecorator(string propName, string comparisonMethod, float value) : this("And", propName, comparisonMethod, value) { }
public ActiveIfDecorator(string logicalOperator, string propName, string compareFunction, float value)

Example:

[Main(GroupName)] _group ("Group", float) = 0
[Sub(GroupName)][KWEnum(Key 1, _KEY1, key 2, _KEY2)] _enum ("KWEnum", float) = 0
[Sub(GroupName)][ActiveIf(_enum, Equal, 0)] _float0 ("Editable only when key 1", float) = 0
[Sub(GroupName)][ActiveIf(_enum, E, 1)] _float1 ("Editable only when key 2", float) = 0
[Sub(GroupName)][ActiveIf(Or, _enum, E, 0)][ActiveIf(Or, _enum, G, 0)] _float2 ("Editable when key >= 0", float) = 0

LWGUI Timeline Tracks

MaterialKeywordToggleTrack

When recording material parameter animation, Keyword changes are automatically captured and the track is added to the Timeline Asset. The Keyword state is set according to the float value during runtime.

Supports Toggle-type Drawer with Keyword.

Unity Builtin Drawers

Space

MaterialSpaceDecorator(float height)

Header

MaterialHeaderDecorator(string header)

Enum

MaterialEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7)

IntRange

MaterialIntRangeDrawer()

KeywordEnum

MaterialKeywordEnumDrawer(string kw1, string kw2, string kw3, string kw4, string kw5, string kw6, string kw7, string kw8, string kw9)

PowerSlider

MaterialPowerSliderDrawer(float power)

Toggle

MaterialToggleUIDrawer(string keyword)

FAQs

Problems Occurred After Modifying the Material in the Code

After modifying material properties in the code, the Drawer logic does not run, potentially losing some data (e.g., Keywords). You need to manually call LWGUI.UnityEditorExtension.ApplyMaterialPropertyAndDecoratorDrawers() to set up this part of the data (it will actually call MaterialPropertyDrawer.Apply()).

Problems Occurred After Creating the Material in the Code

When creating materials in code, some Drawer logic may not run, and default values might not meet expectations. You need to manually call LWGUI.PresetHelper.ApplyPresetsInMaterial() to ensure default values are correct.

Custom Shader GUI

Custom Header and Footer

image-20230821211652918

  1. Custom Headers and Footers enable you to integrate bespoke modules at the top or bottom of the ShaderGUI without altering LWGUI plugin code.

  2. Depending on the desired location for the custom GUI, duplicate the following script into an Editor folder within your project:

    • Top: Packages/com.jasonma.lwgui/Editor/CustomGUISample/CustomHeader.cs
    • Bottom: Packages/com.jasonma.lwgui/Editor/CustomGUISample/CustomFooter.cs
  3. Modify the file name and class name accordingly.

  4. Implement your custom GUI code within the DoCustomHeader() / DoCustomFooter() functions.

  5. It is advisable to examine the lwgui object definition to obtain any requisite data.

Custom Drawer

You can build new Drawer or Decorator behaviors by inheriting SubDrawer (Decorators in this project also follow the SubDrawer pipeline). The following best practices make customization easier to maintain:

  1. Define clear responsibilities first
    • Drawers should focus on value input and visual interaction.
    • Decorators should focus on structure, display control, or visual enhancement.
    • Avoid mixing too many unrelated responsibilities into one drawer.
  2. Start from a minimal working loop
    • Implement a minimal useful version first, then iterate.
    • Prefer referencing similar implementations in Editor/ShaderDrawers/ExtraDrawers/ and Editor/ShaderDrawers/ExtraDecorators/.
  3. Keep logic idempotent and side effects low
    • OnGUI can be called frequently, so avoid repeated allocations and unnecessary writes.
    • Only write back to materials or trigger linkage when values truly change.
  4. Use the correct cache scope
    • Put inspector-temporary state in PerInspectorData.
    • Put material-related state in PerMaterialData.
    • Put cross-material shader parse results in PerShaderData.
  5. Reuse existing Helpers whenever possible
    • For context menu, Ramp, Preset, or Toolbar requirements, integrate with existing utilities in Editor/Helper/ instead of reimplementing.
  6. Validate real production scenarios
    • At minimum, verify behavior under multi-material editing, Undo/Redo, asset reimport, and script recompilation.
    • Before submission, run regression checks with assets in Test/.

Contribution

  1. Create multiple empty projects using different versions of Unity
  2. Pull this repo
  3. Use symbolic links to place this repo in the Assets or Packages directory of all projects
  4. Inherit the Subdrawer in shadeerdrawer.cs to start developing your custom Drawer
  5. Check whether the functionality works in different Unity versions
  6. Pull requests

Development Guide

Project Positioning and Core Goal

LWGUI aims to upgrade Shader properties in Unity Inspector from a "linear parameter list" to an editing experience that is "grouped, conditionally displayed, and highly extensible."

It uses ShaderGUI as the entry point, dispatches properties to corresponding Drawers/Decorators through property tags and custom rules, and relies on Helpers plus MetaData to manage state, caching, and resource linkage.

Architecture and Layering Principles

The system can be understood in three layers:

  1. Editor layer (core capabilities)
    • Handles inspector rendering, property parsing, UI interaction, menu behavior, state caching, and asset synchronization.
    • Main directory: Editor/
  2. Runtime layer (runtime supplement)
    • Provides a small set of runtime-reusable data structures and Timeline-related capabilities.
    • Main directory: Runtime/
  3. UnityEditorExtension layer (editor enhancements)
    • Hosts additional editor windows and extension tools, such as LwguiGradientEditor.
    • Main directory: UnityEditorExtension/

The core call chain can be summarized as:

  1. Editor/LWGUI.cs takes over the material inspector entry.
  2. Parse shader properties, MaterialProperty, and tag metadata.
  3. Dispatch properties to ShaderDrawers / BasicDrawers / ExtraDrawers / ExtraDecorators.
  4. During rendering, use Helper modules for cross-cutting behaviors such as context menu, Ramp/Preset/Toolbar.
  5. Use MetaData to maintain state scopes across frames, materials, and shaders.
  6. Use AssetProcessor and ScriptableObject for lifecycle tasks such as import handling, reference sync, and atlas maintenance.

Code Structure and Responsibilities

  • Editor/LWGUI.cs
    • Main ShaderGUI entry that orchestrates one inspector draw flow and stage events.
  • Editor/ShaderDrawerBase.cs
    • Base drawer and common capabilities; defines extension points and core contracts.
  • Editor/BasicDrawers/
    • Basic structural drawers such as foldout groups and sub-item containers.
  • Editor/ShaderDrawers/
    • Shader-property-level drawers; core mapping from properties to UI.
  • Editor/ShaderDrawers/ExtraDrawers/
    • Extra type drawers such as Numeric/Texture/Vector/Other.
  • Editor/ExtraDecorators/
    • Decorator capabilities including appearance, conditional display, logic, and structure.
  • Editor/Helper/
    • Cross-module utilities such as ContextMenuHelper, RampHelper, PresetHelper, and ToolbarHelper.
  • Editor/MetaData/
    • Cache and state-scope management across inspector/material/shader dimensions.
  • Editor/ScriptableObject/
    • Resource data definitions such as LwguiRampAtlas, LwguiShaderPropertyPreset, and GradientObject.
  • Editor/AssetProcessor/
    • Handles import, rename, and asset-change listening to keep editor logic and resource states consistent.
  • Editor/Timeline/ and Runtime/Timeline/
    • Timeline-related editor and runtime features.
  • Runtime/LwguiGradient/
    • Runtime-usable gradient data structures and logic.
  • Test/
    • Regression and sample assets (Shader/Material/Preset) for key-path verification.

MetaData Deep Dive: Data Structures and Lifecycle

The core purpose of Editor/MetaData/ is to avoid state contamination and reduce repeated computation by isolating caches across scopes.

1) PerInspectorData (inspector scope)

  • Scope: within a single inspector window/session.
  • Typical usage:
    • Foldout/expand UI states.
    • Temporary interaction states (current editing target, current menu context, etc.).
    • Short-lived caches reusable within a draw cycle.
  • Lifecycle:
    • Initialized when the inspector is first drawn.
    • Read and updated during each OnGUI pass.
    • Released/rebuilt when the inspector is destroyed, reloaded, or context changes.
  • Why it matters:
    • Prevents state contamination between inspector instances.

2) PerMaterialData (material scope)

  • Scope: per material asset (or instance).
  • Typical usage:
    • Cache results tightly coupled with a specific material.
    • Derived data computed from material properties.
    • Material-level UI auxiliary states.
  • Lifecycle:
    • Created when the material is first accessed by inspector/tools.
    • Key fields refreshed when material properties change, reimport happens, or shader is replaced.
    • Recycled when material becomes invalid, references are removed, or cleanup policies are triggered.
  • Why it matters:
    • Ensures different materials using the same shader do not share incorrect state.

3) PerShaderData (shader scope)

  • Scope: per shader, shared by multiple materials.
  • Typical usage:
    • Parsed shader-property metadata cache (property list, tag parse results, grouping structure, etc.).
    • Static or semi-static data tied to shader text/structure and reusable across materials.
  • Lifecycle:
    • Built when a shader is used for the first time.
    • Invalidated/rebuilt when shader source changes, reimport happens, or related dependencies update.
    • Lazily rebuilt after editor domain reload.
  • Why it matters:
    • Reduces repeated parsing cost and improves inspector performance for large material sets.

Overall MetaData Lifecycle Flow (recommended mental model)

  1. Enter Inspector
    • Locate current material and shader, then fetch/create PerInspectorData, PerMaterialData, and PerShaderData.
  2. Render stage
    • Drawers/Decorators read scoped data to execute conditional display, structural layout, and interaction logic.
  3. Interaction and mutation stage
    • After user edits, write values back to material and update required caches; trigger helper/resource logic when needed.
  4. Asset change stage
    • If shader/material/related resources are imported or structurally changed, listeners invalidate and rebuild related caches.
  5. Exit or reload stage
    • Inspector-level temporary state is released; material/shader caches are retained or cleaned according to policy.

Key benefits of this layered design:

  • Clear state isolation, reducing risks of cross-material contamination and cross-inspector state leaks.
  • Reasonable cache granularity that balances correctness and performance.
  • Easier troubleshooting by following Inspector -> Material -> Shader.

ShaderGUI Key Events and Invocation Timing

The following timing model aligns with common Unity Inspector lifecycle behavior:

  1. Entry stage (LWGUI invoked as ShaderGUI)

    • Triggered when a material is selected in Inspector and needs redraw.
    • Typical actions: establish context, prepare property list, fetch MetaData.
  2. Main OnGUI rendering stage

    • Entered on each Inspector Repaint / Layout / interaction event.
    • Typical actions:
      • Parse and iterate MaterialProperty.
      • Invoke Drawers/Decorators for structure and control rendering.
      • Apply conditional decorators for show/hide and disabled states.
  3. Property change detection and write-back stage

    • Triggered after GUI change checks pass.
    • Typical actions:
      • Write new values back to material properties.
      • Refresh keywords, dependent properties, and linkage logic.
      • Update related PerMaterialData cache.
  4. Context behavior stage (menu/toolbar/quick actions)

    • Triggered when users open context menus or invoke toolbar features.
    • Typical actions:
      • Route through ContextMenuHelper, ToolbarHelper, RampHelper, and PresetHelper.
      • May cause resource-reference updates, preset application, or atlas operations.
  5. Resource lifecycle linkage stage (import/rename/rebuild)

    • Triggered when related assets change (shader, texture, preset, ScriptableObject, etc.).
    • Typical actions:
      • AssetProcessor responds to changes and synchronizes references.
      • Mark and rebuild affected PerShaderData / PerMaterialData.
  6. Domain reload and reinitialization stage

    • Occurs after script recompilation or enter/exit PlayMode (depending on settings).
    • Typical actions:
      • Static caches are invalidated or reset.
      • Next inspector render performs lazy initialization on demand.

Troubleshooting Tips (by event order)

  • Incorrect display/grouping: first inspect dispatch flow in LWGUI.cs and corresponding Drawer/Decorator paths.
  • Menu/tool behavior issues: focus on trigger conditions and side effects in Editor/Helper/.
  • State contamination or stale cache: verify MetaData scope selection first, then check cache invalidation timing.
  • Failure after asset changes: inspect synchronization flow between AssetProcessor and ScriptableObject.

About

A Lightweight, Flexible, Powerful Shader GUI System for Unity.

Topics

Resources

License

MIT, Unknown licenses found

Licenses found

MIT
LICENSE
Unknown
LICENSE.meta

Stars

Watchers

Forks

Packages

No packages published