You are viewing development documentation

Open Live Docs

Version 6.4.0 is out!Hotspotting, in-shader Tonemapping, vertex lights, and bug fixes!

Get it now See What's New
Logo

Shader Generator

ORL Shader Definition

As mentioned in the credits section of the main page - the ORL Shader package is built with the ORL Shader Generator which works by assembling a bunch of separated Modules into a single shadder based on a Template and a core Lighting Model. At the root of it all is an ORL Shader Definition, and its corresponding format - .orlshader.

Shader Anatomy

The .orlshader file supports multiple blocks all of which are optional, except for the %ShaderName().

You can see all of the blocks available to you out of the box in the built-in templates in the shader example at the bottom of this page.

Provided blocks

Beyond the configuration blocks like %ShaderName() and %LightingModel() - everything else is template-dependent. So a %ShaderDefines block will only be embedded in the shader if the base template of the lighting model has a %ShaderDefines hook inside of it.

This means, if you're using a custom template, you can add any new directives you want, and they will be inserted into the template as-is. You can read more about custom templates over here

%ShaderName(string name)

Required. Defines the name on which the shader will be registered in unity.

%ShaderName("orels1/Standard")

%LightingModel(string path)

Defines the lighting model to use, defaults to PBR if not specified.

%LightingModel("@/LightingModels/Toon") // will pick the Toon lighting model from the shader generator package

Lighting models are special kinds of .orlsource modules,that specify where the rest of the code gets included in the shader using the target keyword in their %Includes() block. Check out the built in LightingModels to see how they work.

You can also create your own lighting models, and use them in your shaders. Check out the Creating Lighting Models guide for more info.

Path Resolution

ORL Shader definitions automatically resolve paths to built-in assets if they start with @/.
For your own dependencies you can either provide relative paths, or absolute paths that start with /Assets or /Packages.

%Template(string path)

Defines the template to use. Is meant to be defined inside of the LightingModel, but can be overriden here if needed.

%Template("@/Templates/Toon")

%CustomEditor(string className)

Defines the custom editor to use, no default is provided, but you can use the ORL Shader Inspector by specifying the following

%CustomEditor("ORL.ShaderInspector.InspectorGUI")

%TemplateFeatures(string[] features)

Defines the template features to use. Template features a special blocks of the source Template that are only included when the specified feature is listed in this block

%TemplateFeatures("MyFeature", "PrePass")

%Includes()

Contains the list of other shader modules to include, can be .orlshader shader files or .orlsource modules. The order of the includes is important, as the modules will be included in the order they are specified. The resolver will recursively follow the trail of includes and pull in any submodules if they are needed.

Self Include

You should use self to include current shader's module in a specific spot

%Includes()
{
    "@/Modules/AudioLink", // include a module from the generator package
    "@/Shaders/ORL Standard", // include a base shader form the shaders package
    "../MyModule", // include a module from the parent folder,
    "Assets/OtherModule", // include a module from the project folder,
    "self" // mount point for the current shader
}

Batteries Included

To make the development process nice and easy, a lot of things come pre-included, so unless defined otherwise - the following defaults will be provided:

  • A PBR LightingModel
  • A set of FragmentData, VertexData, MeshData and SurfaceData structs
  • A base VertexFunction
  • A pack of Utility functions like remap, invLerp, HSV2RGB, etc (check out Utilities.orlsource in the generator package)
  • A CoreRP sampling library for unified cross-platform cross-pipleine sampling macros

%CheckedInclude(string path)

Includes a shader include file (e.g. a .cginc or .hlsl) if that file actually exists on disk. This is useful when working around conitional includes via keywords that sometimes produce errors in console.

%CheckedInclude("Assets/MyCustomStuff/MyInclude.cginc")

%Properties()

Contains your shader properties, uses regular ShaderLab properties syntax

%Properties()
{
    UI_FaderHeader ("# Block Fader", Int) = 0
    _Progress ("Progress", Range(0, 1.1)) = 0
    _FaderColumns ("Fader Columns", Int) = 10
    [ToggleUI]_FaderRemap ("Enable Fader Remapping", Int) = 0
    _FaderRemapMin ("Min", Float) = 0.15
    _FaderRemapMax ("Max", Float) = 0.95
}

%ShaderFeatures()

Contains a list of shader feature / multi compile pragmas for the shader

%ShaderFeatures()
{
    #pragma shader_feature_local DETAILS_OVERLAY
}

%ShaderDefines()

Contains a list of defines for the shader

Built in LightingModels provide some addition features you can request by specifying special defines, see below

%ShaderDefines()
{
    #if !defined(PLAT_QUEST)
        #define _INTEGRATE_CUSTOMGI
    #endif
}

%ShaderTags()

Contains a list of top-level tags that will be appended to the Tags { } list

%ShaderTags()
{
    "Queue" = "AlphaTest" "RenderType" = "TransparentCutout"
}

%PassTags()

Same as ShaderTags but for individual passes

%PassTags()
{
    "LTCGI"="Always"
}

%PassModifiers()

Contains a list of extra modifiers that will be appended right below tags in the ForwardBase pass

Usually used for things like Blend keywords or Cull Off, etc.

%PassModifiers()
{
    Blend SrcAlpha OneMinusSrcAlpha
}

Some built-in LightingModels (like Toon and PBR) also support modifying other passes, like:

%AddPassModifiers()
{
  // Modifiers for ForwardAdd
}

%MetaPassModifiers()
{
  // Modifiers for Meta
}

%ShadowPassModifiers()
{
  // Modifiers for ShadowCaster
}

%PrePassModifiers()
{
  // Modifiers for the PrePass
  // Only available in the Toon template
}

%PassModifiers are resolved in the order of inclusion. For example, if you have a %PassModifiers() block in your .orlshader file directly - its values will override the values in the FragmentBase files of your lighting model.

%ShaderModifiers()

Same as %PassModifiers() but for the entire shader. You can override the Shader Modifiers by specifying individual Pass Modifiers

%ShaderModifiers()
{
    ZWrite Off
}

%ShaderModifiers are resolved in the order of inclusion. For example, if you have a %ShaderModifiers() block in your .orlshader file directly - its values will override the values in the FragmentBase files of your lighting model.

%Variables

Contains a list of variables used in the pass. You must declare all the shared variables (like the ones bound to the properties) here so they can be de-duplicated across all included modules

E.g., half _Cutoff or half4 _MainTex_ST etc

%Variables()
{
    half _Progress;
    int _FaderColumns;
    int _FaderRemap;
    half _FaderRemapMin;
    half _FaderRemapMax;
}

%Fragment(string functionName)

Contains your fragment code that will be injected into the Fragment stage of the shader

You should define it as a void function that takes in w/e shader data you might need to be included, you can see the list of built-in Data structs here

%Fragment("CutoutFragment")
{
    void CutoutFragment(SurfaceData o)
    {
        #if !defined(_NATIVE_A2C)
        if (o.Alpha < _Cutoff)
        {
           clip(-1);
        }
        #endif
    }
}

Function Names

You must provide a function name, and it must be unique across all included modules, otherwise it will get deduplicated. Providing a function name allows you to include other functions in the same code block, which can be useful for refactoring.

%Vertex(string functionName)

Same as Fragment but now its injected into the vertex stage

%PostVertex(string functionName)

Same as Vertex but this one is injected after the main VertexBase function of the Lighting Model. This is useful if you want to modify the vertex data after the main vertex function has already run.

%Color(string functionName)

Contains your FinalColor modifier code. It is appended after the lighting calculations have already been done, and allows to affect the very final output of the shader

Modifying anything but the FinalColor variable here will not have any effect

%Color("MyColor")
{
    void MyColor(inout half4 FinalColor)
    {
        FinalColor.rgb *= (sin(_Time.y) + 1) / 2.0;
    }
}

%PrePassColor(string functioName)

Same as Fragment but this one is injected into the PrePass stage of the Toon template.

PrePass stage is only available when the PrePass template feature is enabled.

Read more about template features here.

%Shadow(string functionName)

Contains your custom shadowcaster function. By Default it does not have access to any mesh data or fragment shader evaluation results for performance reasons.

You can change that behavior and run the full set of fragment functions by using a special define see below

Order Overload

All of the function directives can optionally take an order int parameter, like this

%Framgent("MyFragment", -100)
{
    void MyFragment(SurfaceData o)
    {
        // some code
    }
}

This will make this fragment function be called before functions with no order specified, and after functions with a higher order specified.

So if your function gets included like this

%Includes()
{
    "self",
    "MyAwesomeModuleWithFunction"
}

Then despite the module being included below the self keyword, its Fragment function will still be called before the self fragment function.

This is primarily useful for maintaining a particular order of properties, while adjusting how the actual code gets executed. For example, the ORL Standard shader uses this to call Parallax UV adjustments before any other function while injecting its properties below the MainSettings of the base shader it is included from.

%PrePasses()

Contains a list of extra passes that will be appended at the beginning of the shader before the main passes.

Note that these do not use the Variables and Textures in Built-In render pipeline, so you'll have manually define the full contents of the pass.

All of the LibraryFunctions, however, including the sampling library and utilities, will be available to you.

%PrePasses()
{
    GrabPass { _GrabTexture }

    Pass
    {
        Cull Off

        CGPROGRAM

        // some shader code

        ENDCG
    }
}

%PostPasses()

Contains a list of extra passes that will be appended at the end of the shader after the main passes.

Note that these do not use the Variables and Textures in Built-In render pipeline, so you'll have manually define the full contents of the pass.

All of the LibraryFunctions, however, including the sampling library and utilities, will be available to you.

%PostPasses()
{
    Pass
    {
        Cull Off

        CGPROGRAM

        // some shader code

        ENDCG
    }
}

%ExtraPass(string passName)

Contains blocks for an extra pass. This allows you to add extra generated passes to the shader, which will leverage all of the features of existing passes.

Template Requiremenets

This requires an ExtraPass template to be available for the current template. E.g. if the current template is Toon, then the ToonExtraPass.orltemplate file must be present somewhere Shader Generator can find it. Which, at the moment, means it must be in the same Templates folder.

You can nest most of the block types inside the %ExtraPass. It will also inherit all the blocks of the current shader apart from any function blocks, e.g. %Vertex, %Fragment, etc. Those are expected to be defined in the %ExtraPass block to implement your desired effect.

Current ExtraPass templates are using ForwardBase light mode.

%ExtraPass("Wave")
{
    %PassModifiers()
    {
        Blend One One
    }

    %Vertex("WaveVertex")
    {
        void WaveVertex(inout VertexData v)
        {
            float mask = sin(_Time.y * .8 - v.vertex.x * 0.1) * 0.5 + 0.5;
            v.vertex.y += (sin(_Time.y * 1.2 + length(v.uv0.xy) * 10)) * 0.5 * mask + 0.8 * mask;
        }
    }

    %Fragment("WaveFragment")
    {
        void WaveFragment(MeshData d, inout SurfaceData o)
        {
            float mask = sin(_Time.y * 0.8 - d.localSpacePosition.x * 0.1) * 0.5 + 0.5;
            o.Albedo = _Color;
            o.Albedo *= mask;
        }
    }
}

Optional Features

The built-in templates allow you to enable optional features by specifying some special defines in your %ShaderDefines section

  • NEED_FRAGMENT_IN_SHADOW: Forces the shadowcaster pass to execute all of the included fragment functions (except the lighting calculation), useful if you want to utilize the final calculated alpha to augment the shadow silhouette.
  • NEED_FRAGMENT_IN_PREPASS: When using Toon template with PrePass TemplateFeature enabled - forces the prepass to execute all of the included fragment functions. Majority of the time, to save performance, you probably want to reimplement the bare minimum of the calculations inside a custom PrePassColor function instead of using this define.
  • EXTRA_V2F_0, EXTRA_V2F_1, EXTRA_V2F_2, EXTRA_V2F_3: Tells the templates to compile in extra float4s in the Vertex stage so you can pass some custom data to your Fragment stage, see the struct definition below
  • NEED_UV4, NEED_UV5, NEED_UV6, NEED_UV7: Tells the templates to include UV channels 4-7 in the Vertex stage and pass them to the Fragment stage.
  • _INTEGRATE_CUSTOMGI: LEGACY Enables support for custom GI injection on top of built-in GI. Only avaible in the PBR Lighting Model.
    • You must define a function of the following signature inside of the %Fragment() block IntegrateCustomGI(MeshData d, SurfaceData o, inout half3 indirectSpecular, inout half3 indirectDiffuse). This function will be called if the _INTEGRATE_CUSTOMGI is defined.
    • Check out Packages/sh.orels.shaders.generator/Runtime/Sources/Modules/LTCGI.orlsource for reference.
  • _INTEGRATE_CUSTOMGI_FLEX Enables support for custom GI injection on top of the built-in GI in the PBR Lighting Model. Compared to _INTEGRATE_CUSTOMGI, this version allows you to define a function with an arbitrary call signature, which is useful for more complex GI implementations.
%CustomGI("MyGIFunction")
{
    void MyGIFunction(MeshData d, inout half3 indirectSpecular)
    {
        indirectSpecular += pow(saturate(dot(d.worldNormal, d.worldSpaceViewDirection)), 10);
    }
}

Since the call sign is arbitrary - you can access any variable available in the PBR FragmentBase function's scope at the %CustomGIFunctions hook point.

%CustomGI("MyGIFunction")
{
    void MyGIFunction(MeshData d, half3 envBRDF, inout half3 indirectSpecular, inout half3 indirectDiffuse)
    {
        indirectDiffuse += envBRDF;
    }
}

Mesh and Surface Data

ORL Shader Generator's built-in templates provide all the relevant data via a couple of structs, which are described below

All of the structs are located in /Packages/sh.orels.shaders.generator/Runtime/Sources/Structs. With root-level folder containing structs shared between the templates, and individual folders containing LightingModel specific structs

VertexData

You will be manipulating and accessing this in the vertex functions

The vertex data should be accessed via the v variable, for example

void MyVertex(VertexData v)
{
    v.vertex.xyz += v.normal.xyz * 0.1;
}
struct VertexData
{
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 tangent : TANGENT;
    float4 color : COLOR;
    float4 uv0 : TEXCOORD0;
    float4 uv1 : TEXCOORD1;
    float4 uv2 : TEXCOORD2;
    float4 uv3 : TEXCOORD3;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

FragmentData

Unless you're planning to roll your own lighting models, the most you should do is use the extraV2F slots via the optional feature defines.

// first - declare that you need an extra v2f in your defines
%ShaderDefines()
{
    #define EXTRA_V2F_0
}

// then pass data in your vertex function
%Vertex("MyVertex")
{
    void MyVertex(inout FragmentData o)
    {
        o.extraV2F0.r = mySuperRandomFunction();
    }
}

// then consume data in your fragment function
%Fragment("MyFragment")
{
    void MyFragment(FragmentData i)
    {
        o.Emission.rgb = i.extraV2F0.r;
    }
}
  • First, you define that you need an extra v2f variable, you can request up to 3, see Optional Features
  • Then you pass it via the FragData variable
  • Then you consume it via the MeshData variable - d, e.g., d.extraV2F0.xyz

MeshData

Contains all of the relevant vectors and parameters you might want to use in your Fragment/FinalColor code. Passed as a variable called d. Can vary per lighting model, but here is the one from the PBR/VFX one at the time of writing. Check out /Packages/sh.orels.shaders.generator/Runtime/Sources/Structs/PBR for the latest version.

struct MeshData
{
    half2 uv0;
    half2 uv1;
    half2 uv2;
    half2 uv3;
    #if !defined(UNITY_PASS_SHADOWCASTER)
    half4 lightmapUv;
    #endif
    half4 vertexColor;
    half3 normal;
    half3 worldNormal;
    half3 localSpacePosition;
    half3 worldSpacePosition;
    half3 worldSpaceViewDir;
    half3 tangentSpaceViewDir;
    float3x3 TBNMatrix;
    float4 extraV2F0;
    float4 extraV2F1;
    float4 extraV2F2;
    float4 extraV2F3;
    float4 screenPos;
};

For example - you can access mesh UVs like this

%Fragment("MyFragment")
{
    void MyFragment(MeshData d)
    {
        half2 uv = d.uv0.xy;
    }
}

SurfaceData

This is the struct that will be used in the lighting function. The PBR template consumes all of the provided values, while the VFX template only uses Albedo, Emission, and Alpha to construct the FinalColor. And the Toon shader has its own set of values. Check out /Packages/sh.orels.shaders.generator/Runtime/Sources/Structs to see whats available.

Here's the PBR SurfaceData struct at the time of writing

struct SurfaceData
{
    half3 Albedo;
    half3 Emission;
    half Metallic;
    half Smoothness;
    half Occlusion;
    half3 Normal;
    half Alpha;
};

For example, this is how you would make your material look red and metallic. Because red goes fasta'

%Fragment("MyFragment")
{
    void MyFragment(inout SurfaceData o)
    {
        o.Albedo = half3(1, 0, 0);
        o.Metallic = 1;
    }
}

Don't forget to add inout to any struct which values you plan to modify! Otherwise they won't propagate to the next stage

FinalColor

FinalColor is a value that contains the result of the final lighting calculation. You can modify its value using the Color Function, see Color function above

For example, this is how you would lower the overall brightness of the material no matter what kind of calculations happened prior to that

%Color("MyColor")
{
    void MyColor(inout half4 FinalColor)
    {
      FinalColor.rgb *= 0.5; // halves the overall values
    }
}

VFX Template

In the VFX template FinalColor is constructed from o.Albedo and o.Alpha, so the Fragment function essentially operates on the FinalColor directly. As such, there is no need to use a ColorFunction in there.

Facing attribute

A float facing parameter is also passed into the function. This allows you to detect if the current fragment is rendering the front face or the backface of the triangle.

Shader Example

Below is an example of a shader with all the blocks supported by the built-in toon template. You can also find this example in the /Packages/sh.orels.shaders.generator/Runtime/Resources/ORL Shader Example.txt

%ShaderName("My Awesome Shader")
%LightingModel("@/LightingModels/Toon")
%CustomEditor("ORL.ShaderInspector.InspectorGUI")

%Properties()
{
    UI_MainHeader("# My Awesome Settings", Int) = 0
    _Level("Level", Range(0,1)) = 0
    _Mask("Mask", 2D) = "black" {}
    _Brightness("Brightness", Range(0,2)) = 1
    _Cutoff("Cutoff", Range(0, 1)) = 0.5
}

%Includes()
{
  "@/Shaders/ORL Standard",
  "self"
}


%ShaderFeatures()
{
    #pragma shader_feature_local FANCY_FEATURE
}

%ShaderDefines()
{
    #define MY_THING
}

%ShaderTags()
{
    "Queue" = "AlphaTest" "RenderType"="TransparentCutout"
}

%PassTags()
{
    "MyTag"="MyValue"
}

%PassModifiers()
{
    Cull Off
}

%AddPassModifiers()
{
    Blend One One
}

%MetaPassModifiers()
{
    Cull Off
}

%ShadowPassModifiers()
{
  Cull Off
}

%PrePasses()
{
    GrabPass { _GrabTexture }
}

%Variables()
{
    float _Level;
    float4 _Mask_ST;
    float _Brightness;
}

%Textures()
{
    TEXTURE2D(_Mask);
    SAMPLER(sampler_Mask);
}

%Fragment("MyFragment")
{
    void MyFragment(MeshData d, inout SurfaceData o)
    {
        half2 uv = d.uv0.xy * _Mask_ST.xy + _Mask_ST.zw;
        half mask = SAMPLE_TEXTURE2D(_Mask, sampler_Mask, uv).r;

        o.Albedo = mask * _Level;
    }
}

%Vertex("MyVertex")
{
    void MyVertex(inout VertexData v)
    {
        v.vertex.xyz += v.normal * _Level * 0.1;
    }
}

%Color("MyColor")
{
    void MyColor(inout half4 FInalColor)
    {
       FinalColor.rgb *= _Brightness;
    }
}

%Shadow("MyShadow")
{
    void MyShadow(MeshData d)
    {
        half2 uv = d.uv0.xy * _Mask_ST.xy + _Mask_ST.zw;
        half mainAlpha = SAMPLE_TEXTURE2D(_Mask, sampler_Mask, uv).a;
        if (mainAlpha < _Cutoff)
        {
            clip(-1);
        }
    }
}

%PostPasses()
{
    Pass
    {
        CGPROGRAM
        // shader code
        ENDCG
    }
}
Previous
Development Basics