Skip to main content

Modular Shader System

A guide to creating shader modules for the Poiyomi shader framework.

Overview

The Poiyomi shader system uses a modular architecture where individual features are defined as separate modules. Each module consists of two main files:

  1. Module Definition (.asset) - YAML file defining module metadata and template references
  2. Template Collection (.poiTemplateCollection) - Contains the actual shader code templates

File Structure

Poi_FeatureModules/
├── Poi_YourModule/
│ ├── VRLM_PoiYourModule.asset # Module definition
│ ├── VRLM_PoiYourModule.asset.meta
│ ├── VRLTC_PoiYourModule.poiTemplateCollection # Shader code
│ └── VRLTC_PoiYourModule.poiTemplateCollection.meta

Naming Convention

PrefixType
VRLM_Module Definition (asset)
VRLT_Single Template (poiTemplate)
VRLTC_Template Collection (poiTemplateCollection)

Key Fields

FieldDescription
IdUnique identifier for the module
ModuleDependenciesList of required modules
IncompatibleWithModules that conflict with this one
TemplatesReferences to template sections with keyword mappings
QueuePriority order (lower = earlier in shader)

Template Collection (.poiTemplateCollection)

Templates contain shader code organized into named sections. Each section is marked with #T#SectionName.

Template Structure

#T#YourModuleProperties
//ifex _EnableYourModule==0
[HideInInspector] m_start_yourModule (" Your Module--{reference_property:_EnableYourModule}", Float) = 0
[HideInInspector][ThryToggle(POI_YOUR_MODULE)]_EnableYourModule ("Enable", Float) = 0
_YourProperty ("Property Name", Range(0, 1)) = 0.5
[HideInInspector] m_end_yourModule ("", Float) = 0
//endex

#T#YourModuleKeywords
//ifex _EnableYourModule==0
#pragma shader_feature_local POI_YOUR_MODULE
//endex

#T#YourModuleVariables
//ifex _EnableYourModule==0
#ifdef POI_YOUR_MODULE
float _YourProperty;
#endif
//endex

#T#YourModuleFunction
//ifex _EnableYourModule==0
#ifdef POI_YOUR_MODULE
void ApplyYourModule(inout PoiFragData poiFragData, in PoiMesh poiMesh)
{
// Your shader logic here
}
#endif
//endex

#T#YourModuleFunctionCall
//ifex _EnableYourModule==0
#ifdef POI_YOUR_MODULE
ApplyYourModule(poiFragData, poiMesh);
#endif
//endex

Common Keywords (Injection Points)

Keywords define where code is injected into the shader pipeline. Keywords are marked with #K# in templates.

Top-Level Shader Structure

These keywords define the overall shader structure and pass ordering:

KeywordDescription
SHADER_TAGSShader-level tags and properties
DEPTH_NORMAL_PASSDepth normals pass injection
GRAB_PASSGrab pass for screen effects
EARLY_Z_PASSEarly Z depth pass
EARLY_OUTLINE_PASSEarly outline pass (before base)
BASE_PASSMain forward base pass
FUR_BASE_PASSFur geometry base pass
ADD_PASSAdditive lighting pass
BASE_PASS_TWOSecond base pass (two-pass shaders)
ADD_PASS_TWOSecond additive pass
OUTLINE_PASSOutline rendering pass
SHADOW_PASSShadow casting pass
META_PASSLightmapping/GI meta pass

Property Categories

Properties are organized into UI categories:

KeywordCategory
TOP_PROPERTIESTop-level shader properties
MAIN_PROPERTIESColor & Normals section
2NDPASS_PROPERTIESSecond pass properties
OUTLINE_PROPERTIESOutline section
LIGHTING_PROPERTIESShading section
SPECIALFX_PROPERTIESSpecial FX section
VERTEX_PROPERTIESVertex Options section
GRABPASS_PROPERTIESGrab Pass section
GLOBAL_PROPERTIESGlobal Data and Masks
UV_PROPERTIESUV modification properties
POSTPROCESSING_PROPERTIESPost Processing section
MODIFIER_PROPERTIESGlobal Modifiers section
EXTRAS_PROPERTIESExtra/miscellaneous properties
THIRDPARTY_PROPERTIESThird Party integrations
RENDERING_PROPERTIESRendering options section

Generic Pass Keywords

These keywords are available in VRLT_PoiGenericPass.poiTemplate for all passes:

KeywordDescription
PRAGMA_DIRECTIVESShader pragma statements
REQUIREMENTSShader requirements/features
SHADER_KEYWORDSShader feature keywords
MACROS_AND_DEFINESPreprocessor macros and defines
INCLUDESInclude file directives

Pass-Specific Pragma & Keywords

Each pass type has its own keyword and pragma injection points:

PassKeywordsPragma Programs
BaseBASE_PASS_KEYWORDSBASE_PASS_PRAGMA_PROGRAMS
AddADD_PASS_KEYWORDSADD_PASS_PRAGMA_PROGRAMS
ShadowSHADOW_PASS_KEYWORDSSHADOW_PASS_PRAGMA_PROGRAMS
MetaMETA_PASS_KEYWORDSMETA_PASS_PRAGMA_PROGRAMS
OutlineOUTLINE_PASS_KEYWORDSOUTLINE_PASS_PRAGMA_PROGRAMS
EarlyZEARLYZ_PASS_KEYWORDSEARLYZ_PASS_PRAGMA_PROGRAMS
DepthNormalDEPTHNORMAL_PASS_KEYWORDS-
LilFurLILFUR_PASS_KEYWORDSLILFUR_PASS_PRAGMA_PROGRAMS
Base TwoBASE_PASS_TWO_KEYWORDSBASE_PASS_TWO_PRAGMA_PROGRAMS
Add TwoADD_PASS_TWO_KEYWORDSADD_PASS_TWO_PRAGMA_PROGRAMS

Stencil Injection Points

KeywordPass
BASE_PASS_STENCILBase pass stencil settings
ADD_PASS_STENCILAdd pass stencil settings
SHADOW_PASS_STENCILShadow pass stencil settings
META_PASS_STENCILMeta pass stencil settings
EARLYZ_PASS_STENCILEarlyZ pass stencil settings
DEPTHNORMAL_PASS_STENCILDepth normal stencil settings
BASETWO_PASS_STENCILBase two pass stencil
ADDTWO_PASS_STENCILAdd two pass stencil

Variable Declarations

Variables must be declared in the correct pass sections:

KeywordUsage
BASE_PROPERTY_VARIABLESVariables for base pass
ADD_PROPERTY_VARIABLESVariables for add pass
SHADOW_PROPERTY_VARIABLESVariables for shadow pass
META_PROPERTY_VARIABLESVariables for meta pass
OUTLINE_PROPERTY_VARIABLESVariables for outline pass
EARLYZ_PROPERTY_VARIABLESVariables for early Z pass
DEPTHNORMAL_PROPERTY_VARIABLESVariables for depth normal pass
LILFUR_PROPERTY_VARIABLESVariables for LilFur pass
BASETWO_PROPERTY_VARIABLESVariables for base two pass
ADDTWO_PROPERTY_VARIABLESVariables for add two pass

Vertex Shader Keywords

Vertex program injection points (per pass - replace BASE with pass name):

KeywordStage
VERTEX_BASE_DATA_STRUCTURESVertex data structure definitions
VERTEX_BASE_FUNCTIONS_EARLYEarly vertex function definitions
VERTEX_BASE_FUNCTIONSMain vertex function definitions
VERTEX_BASE_FUNCTIONS_LATELate vertex function definitions
VERTEX_BASE_PROGRAMMain vertex program code

Vertex Modification Keywords

These keywords are used within the vertex program for modifying vertex data:

KeywordTimingDescription
VERTEX_INPUT_MODSVery earlyModify input vertex data
VERTEX_MODS_EARLYEarlyEarly vertex modifications
VERTEX_MODSMainPrimary vertex modifications
VERTEX_MODS_LATELateLate vertex modifications
VERTEX_MODS_PENETRATIONPost-offsetTPS/DPS penetration systems
VERTEX_MODS_PRE_CLIPPOSBefore clipBefore clip position calculation
VERTEX_MODS_POST_CLIPPOSAfter clipAfter clip position calculation
VERTEX_MODS_LILFURFur-specificLilFur vertex modifications
VERTEX_MODS_FINALFinalFinal vertex modifications
vertexOutDataData structAdditional vertex output data

Geometry & Tessellation Keywords

KeywordDescription
HULLDOMAIN_BASE_PROGRAMHull/Domain shader for base pass
HULLDOMAIN_ADD_PROGRAMHull/Domain shader for add pass
HULLDOMAIN_SHADOW_PROGRAMHull/Domain shader for shadow pass
HULLDOMAIN_META_PROGRAMHull/Domain shader for meta pass
HULLDOMAIN_DEPTHNORMAL_PROGRAMHull/Domain shader for depth normal
HULLDOMAIN_LILFUR_PROGRAMHull/Domain shader for LilFur
GEOMETRY_BASE_PROGRAMGeometry shader for base pass
GEOMETRY_ADD_PROGRAMGeometry shader for add pass
GEOMETRY_SHADOW_PROGRAMGeometry shader for shadow pass
GEOMETRY_META_PROGRAMGeometry shader for meta pass
GEOMETRY_DEPTHNORMAL_PROGRAMGeometry shader for depth normal
GEOMETRY_EARLYZ_PROGRAMGeometry shader for early Z
GEOMETRY_LILFUR_PROGRAMGeometry shader for LilFur

Fragment Shader Keywords

Fragment program injection points (per pass - replace BASE with pass name):

KeywordStage
FRAGMENT_BASE_DATA_STRUCTURESFragment data structure definitions
FRAGMENT_BASE_FUNCTIONS_EARLYEarly fragment function definitions
FRAGMENT_BASE_FUNCTIONSMain fragment function definitions
FRAGMENT_BASE_FUNCTIONS_LATELate fragment function definitions
FRAGMENT_BASE_PROGRAMMain fragment program

Fragment Execution Pipeline

These keywords control the fragment shader execution order:

KeywordTimingDescription
FRAGMENT_BASE_DECLARATIONSStartVariable declarations
FRAGMENT_BASE_INIT_EARLYInitEarly initialization
FRAGMENT_BASE_INITInitMain initialization
FRAGMENT_BASE_INIT_LATEInitLate initialization
FRAGMENT_BASE_COLOR_EARLYColorEarly color modifications
FRAGMENT_BASE_COLORColorMain color calculations
FRAGMENT_BASE_COLOR_LATEColorLate color modifications
FRAGMENT_BASE_LIGHTING_EARLYLightingEarly lighting setup
FRAGMENT_BASE_LIGHTINGLightingMain lighting calculations
FRAGMENT_BASE_LIGHTING_LATELightingPost-lighting effects
FRAGMENT_BASE_EMISSION_EARLYEmissionEarly emission
FRAGMENT_BASE_EMISSIONEmissionMain emission calculations
FRAGMENT_BASE_EMISSION_LATEEmissionLate emission effects
FRAGMENT_BASE_RETURNReturnBefore final return
FRAGMENT_BASE_ALPHA_LATEAlphaFinal alpha modifications

The same pattern applies to other passes: FRAGMENT_ADD_*, FRAGMENT_OUTLINE_*, FRAGMENT_LILFUR_*, etc.

Data Initialization Keywords

KeywordDescription
POI_MESH_PROPERTIESPoiMesh struct properties
POI_CAM_PROPERTIESPoiCam struct properties
POI_LIGHT_PROPERTIESPoiLight struct properties
POI_VERTEX_LIGHTS_PROPERTIESVertex light properties
MESH_DATA_INITMesh data initialization
MESH_DATA_POST_INITPost mesh initialization
MAIN_TEXTURE_SAMPLEMain texture sampling
FRAGMENT_NORMAL_CALCULATIIONNormal calculation
CAMERA_DATA_INITCamera data initialization

Feature-Specific Keywords

Some modules expose their own injection points:

KeywordModuleDescription
BASE_COLOR_MODMainBase color modification
TANGENT_NORMAL_INJECTIONMainNormal map injection
BASE_LIGHTDATA_MODLight DataLight data modification
BASE_AFTER_ATTENUATIONLight DataAfter attenuation calculation
BASE_REALISTIC_MODShadingRealistic shading modifications
MOCHIE_METALLIC_SMOOTHNESS_MODMetallicsMetallic/smoothness mods
POI_EMISSION0_MODSEmissionEmission 0 modifications
LILFUR_TWO_PASS_PROPSLilFurFur two-pass properties

Core Data Structures

PoiFragData

Fragment output data for color, lighting, and material properties.

struct PoiFragData {
float3 baseColor; // Albedo color before lighting
float3 finalColor; // Final color after all effects
float alpha; // Transparency
float3 emission; // Emissive color

// PBR Properties
float smoothness; // Primary smoothness (0-1)
float smoothness2; // Secondary smoothness for layered materials
float metallic; // Metallic value (0-1)
float specularMask; // Mask for specular highlights
float reflectionMask; // Mask for reflections

float toggleVertexLights; // Enable/disable vertex lights contribution
};

PoiMesh

Mesh geometry, UVs, and vertex data.

struct PoiMesh {
// Normals
float3 normals[2]; // [0] = vertex normal, [1] = fragment/normal-mapped
float3 objNormal; // Object-space normal
float3 tangentSpaceNormal; // Normal in tangent space
float3 binormal[2]; // Binormal vectors
float3 tangent[2]; // Tangent vectors

// Position
float3 worldPos; // World-space position
float3 localPos; // Object-space position
float3 objectPosition; // Object pivot position in world space

// Face
float isFrontFace; // 1.0 = front face, 0.0 = back face

// Vertex Data
float4 vertexColor; // Vertex color RGBA
float4 lightmapUV; // Lightmap UVs

// UV Channels (10 total)
// [0-3] = UV0-UV3
// [4] = Panosphere UV
// [5] = World position XZ
// [6] = Polar UV
// [7] = Distorted UV
// [8] = Local position
// [9] = Matcap UV
float2 uv[10];

// Parallax
float2 parallaxUV; // UV offset from parallax
float2 dx; // Screen-space derivatives X
float2 dy; // Screen-space derivatives Y

uint isRightHand; // Handedness for normal mapping
};

PoiCam

Camera and view information.

struct PoiCam {
float3 viewDir; // View direction (pixel to camera)
float3 forwardDir; // Camera forward direction
float3 worldPos; // Camera world position
float distanceToVert; // Distance from camera to vertex

// Screen Space
float4 clipPos; // Clip-space position
float4 screenSpacePosition; // Screen-space position
float4 posScreenSpace; // Normalized screen position
float2 posScreenPixels; // Screen position in pixels
float2 screenUV; // Screen UVs (0-1)

// Reflections
float3 reflectionDir; // Reflection direction
float3 vertexReflectionDir; // Vertex-based reflection direction
float3 tangentViewDir; // View direction in tangent space

float vDotN; // View dot normal
float4 worldDirection; // World-space direction
};

PoiLight

Lighting calculations and light source data.

struct PoiLight {
// Light Direction & Color
float3 direction; // Light direction
float3 directColor; // Direct light color
float3 indirectColor; // Indirect/ambient light color
float3 halfDir; // Half vector (view + light)

// Attenuation
float attenuation; // Light attenuation
float attenuationStrength; // Attenuation intensity
float occlusion; // Ambient occlusion
float shadowMask; // Shadow mask value
float detailShadow; // Detail shadow value

// Dot Products
float nDotL; // Normal dot light direction
float nDotV; // Normal dot view direction
float nDotH; // Normal dot half vector
float nDotVCentered; // Centered nDotV
float vertexNDotL; // Vertex-based nDotL
float vertexNDotV; // Vertex-based nDotV
float vertexNDotH; // Vertex-based nDotH
float lDotv; // Light dot view
float lDotH; // Light dot half
float nDotLSaturated; // Clamped nDotL (0-1)
float nDotLNormalized; // Normalized nDotL (0-1 range)

// Light Maps
float lightMap; // Light map value
float lightMapNoAttenuation;// Light map without attenuation
float3 rampedLightMap; // Toon ramp applied light map

// Final Lighting
float3 finalLighting; // Combined final lighting
float3 finalLightAdd; // Additive light contribution

// LTCGI
float3 LTCGISpecular; // LTCGI specular contribution
float3 LTCGIDiffuse; // LTCGI diffuse contribution

// Luminance
float directLuminance; // Direct light luminance
float indirectLuminance; // Indirect light luminance
float finalLuminance; // Final combined luminance

// Add Pass Only
#ifdef POI_PASS_ADD
float additiveShadow; // Shadow for additive pass
#endif

// Vertex Lights (4 non-important lights)
#if defined(VERTEXLIGHT_ON)
float4 vDotNL; // Per-light nDotL
float4 vertexVDotNL; // Vertex-based per-light nDotL
float3 vColor[4]; // Light colors
float4 vCorrectedDotNL; // Corrected per-light nDotL
float4 vAttenuation; // Per-light attenuation
float4 vSaturatedDotNL; // Clamped per-light nDotL
float3 vPosition[4]; // Light positions
float3 vDirection[4]; // Light directions
float3 vFinalLighting; // Combined vertex lighting
float3 vHalfDir[4]; // Per-light half vectors
half4 vDotNH; // Per-light nDotH
half4 vertexVDotNH; // Vertex-based per-light nDotH
half4 vDotLH; // Per-light lDotH
#endif
};

PoiMods

Global modifiers, masks, and AudioLink data.

struct PoiMods {
float4 Mask; // Legacy mask (deprecated, use globalMask)

// AudioLink
float audioLink[5]; // [0]=Bass, [1]=LowMid, [2]=HighMid, [3]=Treble, [4]=Volume
float audioLinkAvailable; // 1.0 if AudioLink is present
float audioLinkVersion; // AudioLink version number
float4 audioLinkTexture; // Raw AudioLink texture sample
float ALTime[8]; // AudioLink chronotensity time values

// Detail Masks
float2 detailMask; // Detail texture masks
float2 backFaceDetailIntensity; // Detail intensity for back faces

// Global Systems
float globalEmission; // Global emission multiplier
float4 globalColorTheme[12];// Theme colors (4 user + 4 ColorChord + 4 AL Theme)
float globalMask[16]; // 16 global mask channels (4 textures × 4 RGBA)
};

PoiVertexLights

Individual vertex light data (used in vertex lighting calculations).

struct PoiVertexLights {
float3 direction; // Light direction
float3 color; // Light color
float attenuation; // Light attenuation
};

Preprocessor Directives

//ifex Blocks

Conditionally exclude code based on material properties:

//ifex _EnableFeature==0
// Code only included if _EnableFeature != 0
//endex

Texture Property Guards

Optimize by only including texture sampling when texture is assigned:

#if defined(PROP_YOURTEXTURE) || !defined(OPTIMIZER_ENABLED)
Texture2D _YourTexture;
#endif

Keyword Guards

Wrap code in shader feature checks:

#ifdef POI_YOUR_MODULE
// Module code
#endif

Duplicate Module Support

For modules that can have multiple instances (like Emission 0, 1, 2...):

AllowDuplicates: 1
ForceDuplicateLogic: 1

Use placeholders in templates:

  • __mIDx__ - Replaced with instance index (0, 1, 2...)
  • __mID__ - Replaced with module ID string
  • <ms_include_in_first>{ } - Only in first instance
  • <ms_include_in_not_first>{ } - Only in subsequent instances
  • <ms_include_in_last>{ } - Only in last instance

ThryEditor Property Attributes

Common UI attributes for material properties:

AttributePurpose
[HideInInspector]Hide from default inspector
[ThryToggle(KEYWORD)]Toggle that enables shader keyword
[ThryToggleUI(true)]Toggle without keyword
[ThryWideEnum(...)]Wide dropdown with named options
[sRGBWarning]Warn if texture is sRGB when it shouldn't be
[Gradient]Gradient texture picker
[Vector2]2D vector input
[VectorLabel(X, Y)]Labeled vector components
[Curve]Animation curve texture

UI Organization

// Foldout sections
[HideInInspector] m_start_sectionName (" Section Title--{reference_property:_EnableProperty}", Float) = 0
// ... properties ...
[HideInInspector] m_end_sectionName ("", Float) = 0

// Subsections
[HideInInspector] s_start_subsection ("Subsection--{reference_property:_EnableSub}", Float) = 0
// ... properties ...
[HideInInspector] s_end_subsection ("", Float) = 0

Conditional Visibility

_Property ("Name--{condition_showS:_OtherProperty==1}", Float) = 0

Helper Functions

Common utility functions available in modules:

// UV helpers
float2 poiUV(float2 uv, float4 st); // Apply scale/offset
float2 POI2D_SAMPLER_PAN(tex, sampler, uv, pan); // Sample with panning

// Color operations
float3 poiThemeColor(PoiMods mods, float3 color, float themeIndex);
float3 hueShift(float3 color, float shift, int colorSpace, int mode);
float calculateluminance(float3 color);

// Masking
float maskBlend(float base, float mask, int blendType);
void applyToGlobalMask(inout PoiMods mods, int index, int blendType, float value);

// Math helpers
float inverseLerp(float a, float b, float value);
float remap(float value, float inMin, float inMax, float outMin, float outMax);
float poiMax(float3 color);
float poiEdgeLinear(float value, float border, float blur);
#ifdef POI_AUDIOLINK
if (poiMods.audioLinkAvailable)
{
float bassValue = poiMods.audioLink[0]; // Bass
float lowMidValue = poiMods.audioLink[1]; // Low Mid
float highMidValue = poiMods.audioLink[2]; // High Mid
float trebleValue = poiMods.audioLink[3]; // Treble
float volumeValue = poiMods.audioLink[4]; // Volume
}
#endif

Best Practices

  1. Always wrap code in feature guards - Use #ifdef and //ifex to exclude unused code
  2. Use texture property guards - Only declare textures when they might be used
  3. Follow naming conventions - Consistent prefixes help organization
  4. Declare variables in correct sections - Match passes where they're used
  5. Use appropriate queue values - Lower values execute first
  6. Test with optimizer enabled - Ensure guards work correctly
  7. Provide documentation links - Use button_help for tutorials

Example: Simple Color Tint Module

#T#ColorTintProperties
//ifex _EnableColorTint==0
[HideInInspector] m_start_colorTint (" Color Tint--{reference_property:_EnableColorTint}", Float) = 0
[HideInInspector][ThryToggle(POI_COLOR_TINT)]_EnableColorTint ("Enable", Float) = 0
_TintColor ("Tint Color--{reference_property:_TintColorThemeIndex}", Color) = (1, 1, 1, 1)
[HideInInspector][ThryWideEnum(Off, 0, Theme Color 0, 1, Theme Color 1, 2, Theme Color 2, 3, Theme Color 3, 4)] _TintColorThemeIndex ("", Int) = 0
_TintStrength ("Strength", Range(0, 1)) = 1
[HideInInspector] m_end_colorTint ("", Float) = 0
//endex

#T#ColorTintKeywords
//ifex _EnableColorTint==0
#pragma shader_feature_local POI_COLOR_TINT
//endex

#T#ColorTintVariables
//ifex _EnableColorTint==0
#ifdef POI_COLOR_TINT
float4 _TintColor;
float _TintColorThemeIndex;
float _TintStrength;
#endif
//endex

#T#ColorTintFunction
//ifex _EnableColorTint==0
#ifdef POI_COLOR_TINT
void ApplyColorTint(inout PoiFragData poiFragData, in PoiMods poiMods)
{
float3 tint = poiThemeColor(poiMods, _TintColor.rgb, _TintColorThemeIndex);
poiFragData.baseColor.rgb = lerp(poiFragData.baseColor.rgb, poiFragData.baseColor.rgb * tint, _TintStrength);
}
#endif
//endex

#T#ColorTintFunctionCall
//ifex _EnableColorTint==0
#ifdef POI_COLOR_TINT
ApplyColorTint(poiFragData, poiMods);
#endif
//endex

Global Masks System

The shader provides 16 global mask channels (4 textures × 4 RGBA channels) that can be shared between modules.

Mask Indices

IndexChannelIndexChannel
01R83R
11G93G
21B103B
31A113A
42R124R
52G134G
62B144B
72A154A

Using Global Masks

// Reading a global mask (0-indexed)
float mask = poiMods.globalMask[_YourGlobalMaskIndex - 1];

// Writing to a global mask with blending
if (_YourApplyGlobalMaskIndex > 0)
{
applyToGlobalMask(poiMods, _YourApplyGlobalMaskIndex - 1, _YourBlendType, value);
}

// Blend types: Add=7, Subtract=1, Multiply=2, Divide=3, Min=4, Max=5, Average=6, Replace=0

Global Mask Property Pattern

[ThryWideEnum(Off, 0, 1R, 1, 1G, 2, 1B, 3, 1A, 4, 2R, 5, 2G, 6, 2B, 7, 2A, 8, 3R, 9, 3G, 10, 3B, 11, 3A, 12, 4R, 13, 4G, 14, 4B, 15, 4A, 16)] _YourGlobalMask ("Global Mask--{reference_property:_YourGlobalMaskBlendType}", Int) = 0
[HideInInspector][ThryWideEnum(Add, 7, Subtract, 1, Multiply, 2, Divide, 3, Min, 4, Max, 5, Average, 6, Replace, 0)] _YourGlobalMaskBlendType ("Blending", Int) = 2

Vertex Shader Examples

For modules that modify vertices, use the vertex keywords documented in Vertex Modification Keywords above.

Vertex Modification Example

#T#YourVertexFunction
//ifex _YourVertexFeature==0
#ifdef YOUR_VERTEX_KEYWORD
// Access vertex data via 'v' (appdata) and output via 'o' (VertexOut)
v.vertex.xyz += v.normal * _YourOffset;
o.normal = UnityObjectToWorldNormal(v.normal);
#endif
//endex

UV Types

The shader supports multiple UV coordinate sources:

ValueTypeDescription
0UV0Primary UVs
1UV1Secondary UVs (lightmap)
2UV2Tertiary UVs
3UV3Quaternary UVs
4PanosphereSpherical projection
5World PosWorld position XZ
6Polar UVPolar coordinates
7Distorted UVPost-distortion UVs
8Local PosObject-space position
9MatcapView-space matcap UVs

String Builders (Duplicate Modules)

For accumulating values across duplicate module instances:

// Define a string builder (first instance creates, others append)
<ms_stringbuilder::MY_ACCUMULATED_VALUE>initial_value

// In subsequent instances, append to the builder
<ms_stringbuilder::MY_ACCUMULATED_VALUE>|| additional_value

// Use the accumulated value
#if <ms_stringwriter::MY_ACCUMULATED_VALUE>
// Code using the combined condition
#endif

Example from Emission module:

<ms_include_in_first>
{
<ms_stringbuilder::_ANY_EMISSION_KEYWORD>defined(_EMISSION)
}
<ms_include_in_not_first>
{
<ms_stringbuilder::_ANY_EMISSION_KEYWORD>|| defined(POI_EMISSION___mID__)
}

Animation Optimization

Use isNotAnimated() to skip code when properties aren't animated:

//ifex isNotAnimated(_YourProperty) && _YourProperty==0
// This code is excluded if _YourProperty is 0 AND not animated
//endex

This allows the optimizer to remove code paths that will never be used at runtime.

VRChat-Specific Functions

// Mirror detection
float VRCMirrorMode(); // Returns >0 if in VRC mirror
bool IsInMirror(); // Generic mirror detection

// Camera detection (VRC only)
float VRCCameraMode(); // Returns >0 if in VRC camera

// Usage
if (VRCMirrorMode() > 0)
{
// In mirror rendering
}

Troubleshooting

Common Issues

  1. Module not appearing in shader

    • Verify the module is added to the appropriate ModuleCollection
    • Check that the GUID references in the .asset file are correct
    • Ensure Unity has reimported the meta files
  2. Properties not showing in material

    • Check //ifex conditions match property values
    • Verify m_start_ and m_end_ markers are properly paired
    • Ensure reference_property points to a valid toggle
  3. Shader compilation errors

    • Check for missing #endif or //endex closures
    • Verify variable declarations are in the correct pass sections
    • Ensure texture samplers are properly guarded with PROP_ checks
  4. Optimizer stripping needed code

    • Use isNotAnimated() checks for animated properties
    • Verify //ifex conditions correctly reflect property states
    • Check that shader features use shader_feature_local for optimization
  5. Code running in wrong passes

    • Verify keywords match the intended passes (BASE, ADD, SHADOW, etc.)
    • Check queue values don't conflict with other modules
    • Use pass-specific defines like POI_PASS_BASE for conditional code

Debugging Tips

  • Use the Debug module to visualize values
  • Enable POI_DEBUG to access debug output
  • Check the generated shader code in the Shaders folder after generation
  • Test with optimizer both enabled and disabled

Development Workflow

Auto-Rebuild Shaders

When developing modules, enable automatic shader rebuilding to see changes immediately:

  1. Go to Poi → Modular Shaders Generator
  2. Check the checkbox next to the shader(s) you want to auto-rebuild
  3. The shader will now regenerate whenever you modify module files

The generator window shows all available shader variants with their source module definitions. You can also manually trigger rebuilds by clicking Generate Shaders.

Shader Output Destinations

Each shader has configurable output settings:

  • Destination - Output folder for generated shader files
  • Shader Name Match - Filter which shaders get generated (StartsWith, Always, etc.)
  • Version Override - Custom version number for the generated shader

Adding Modules to Shaders

Modules are included via Module Collections. Add your module reference to the appropriate collection asset (e.g., ModuleCollectionPro.asset) or create a custom shader definition that includes your module.

Additional Resources