Custom Shaders
The uber shader covers most looks. When you need something else, set a custom material in YauiElement.Material. It draws the element's own primitives (its box and content, not its children).
A change of material between elements splits the draw call. Elements with the same material are drawn together when they are next to each other in the draw order, or when the elements between them do not overlap them.
Writing a shader
A shader for YAUI:
- Includes
Packages/com.ruccho.yaui/Runtime/Shaders/Yaui.hlsl, the shared code of YAUI shaders. - Declares two passes:
Overlay, with the tag"LightMode" = "YauiOverlay"and the vertex functionVertOverlayWorld, with the vertex functionVertWorld
- Writes its own fragment function on the
Varyingsstruct. It can callFragImpl(i, world)to get the result of the uber shader and change it. - Uses premultiplied alpha blending (
Blend One OneMinusSrcAlpha).
This example from the Custom Shader sample tints the uber shader's result with a horizontal gradient:
Shader "Yaui/Samples/Gradient Tint"
{
Properties
{
_ColorA ("Left", Color) = (1, 0.3, 0.6, 1)
_ColorB ("Right", Color) = (0.3, 0.6, 1, 1)
// Set by the renderer inside masks (YauiMask).
[HideInInspector] _YauiStencilRef ("Stencil Ref", Float) = 0
[HideInInspector] _YauiStencilComp ("Stencil Comparison", Float) = 8
[HideInInspector] _YauiStencilPass ("Stencil Pass", Float) = 0
}
HLSLINCLUDE
#pragma target 4.5
// Rotated quads and rounded clips are clipped per pixel only with this (it costs GPU time).
#define YAUI_PIXEL_CLIP
#include "Packages/com.ruccho.yaui/Runtime/Shaders/Yaui.hlsl"
float4 _ColorA;
float4 _ColorB;
half4 Tint(Varyings i, half4 color)
{
// local: position relative to the shape center, and its half size.
float t = saturate(i.local.x / max(i.local.z, 1e-4) * 0.5 + 0.5);
return color * half4(lerp(_ColorA.rgb, _ColorB.rgb, t), 1.0);
}
half4 FragTintOverlay(Varyings i) : SV_Target { return Tint(i, FragImpl(i, false)); }
half4 FragTintWorld(Varyings i) : SV_Target { return Tint(i, FragImpl(i, true)); }
ENDHLSL
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "RenderPipeline" = "UniversalPipeline" }
Blend One OneMinusSrcAlpha
ZWrite Off
Cull Off
Stencil
{
Ref [_YauiStencilRef]
Comp [_YauiStencilComp]
Pass [_YauiStencilPass]
}
Pass
{
Name "Overlay"
Tags { "LightMode" = "YauiOverlay" }
ZTest Always
HLSLPROGRAM
#pragma vertex VertOverlay
#pragma fragment FragTintOverlay
ENDHLSL
}
Pass
{
Name "World"
ZTest LEqual
HLSLPROGRAM
#pragma vertex VertWorld
#pragma fragment FragTintWorld
ENDHLSL
}
}
}
Per-pixel clipping
Rotated quads and rounded clips need a test per pixel. Define YAUI_PIXEL_CLIP before including Yaui.hlsl to enable it. It adds varyings, which cost GPU time on mobile, so leave it out if the elements with your material are never rotated or inside a rounded clip. (The uber shader enables it only for the panels that need it.)
Masks
To be masked by a YauiMask, the shader declares the _YauiStencilRef, _YauiStencilComp and _YauiStencilPass properties and the Stencil block, as in the example above. The renderer sets them.
Mask shapes
By default, a YauiMask masks with the box of its element or the alpha of its image. When the element has a custom material with a pass named MaskOverlay, the mask draws its shape with that pass instead (in overlay and camera panels), so that a shape made by the material, such as a dissolve, also hides the children.
The pass writes no color, keeps the Stencil block of the shader, and discards the pixels outside the shape. Its vertex function is VertMaskOverlay, which also makes a fully transparent mask element mask nothing:
// In HLSLINCLUDE: Shape returns the alpha of the element's shape.
half4 FragShapeMask(Varyings i) : SV_Target
{
clip(Shape(i) - 0.5);
return 0;
}
// In the SubShader, next to the Overlay and World passes:
Pass
{
Name "MaskOverlay"
ZTest Always
ColorMask 0
HLSLPROGRAM
#pragma vertex VertMaskOverlay
#pragma fragment FragShapeMask
ENDHLSL
}
Backdrop blur
The Yaui/Backdrop Blur shader shows what is drawn behind the element, blurred, inside the shape of the element and multiplied by its color, like backdrop-filter: blur() in CSS. Create a material with it, set Blur (_YauiBlur, the standard deviation in pixels of the target), and use it as the element's material.
Before a draw whose shader has the tag "YauiBackdrop" = "True", the panel's pass ends, the target is copied and blurred, and the next pass binds the result as _YauiBackdropTexture. Elements next to each other with the same material share one copy. To write a shader of your own, add the tag and sample the texture at the pixel's position:
TEXTURE2D(_YauiBackdropTexture);
SAMPLER(sampler_YauiBackdropTexture);
float4 _YauiBackdropParams; // xy: one pixel of the target in UV
half4 FragFrostedOverlay(Varyings i) : SV_Target
{
half4 shape = FragImpl(i, false);
float2 uv = i.positionCS.xy * _YauiBackdropParams.xy;
half3 backdrop = SAMPLE_TEXTURE2D(_YauiBackdropTexture, sampler_YauiBackdropTexture, uv).rgb;
return half4(backdrop * shape.rgb, shape.a);
}
The target has to be readable: a camera panel drawn before the final blit, or a panel on a camera that renders to a texture. On the back buffer (for instance, overlay panels of a camera that renders to the screen) and in world space panels, backdrops show their color only.
Each backdrop costs a copy of the target, the blur, and a split of the draws. See Performance.
Shader modules
A custom material splits the draw calls of a panel. A package that shades some primitives in its own way, such as glyphs with effects, can instead provide a shader module: a fragment function that the uber shader calls for the primitives that select it. These primitives are then drawn together with the boxes, images and texts around them.
A package provides a module:
- An HLSL file that defines
half4 Name(Varyings i, bool world). It returns the premultiplied color of the pixel; clipping is applied after it. The file is included afterYaui.hlsl, so it can use its types and functions. It reads the varyings of the features its primitives use (the UV of images, the colors). - A
YauiShaderModuleasset (Create > YAUI > Shader Module) with aKey, the include path of the file and the name of the function. - Code that gets the id of the module with
YauiShaderModules.IdOf(key)and builds its primitives withWithModule(id)(see Custom Elements). The id is 0 when the project does not use the module; draw without it then, for example with a material of your own.
A project uses modules:
- Create a
YauiShaderSettingsasset (Create > YAUI > Shader Settings) namedYauiShaderSettingsin aResourcesfolder, and list the modules. - Press Generate Uber Shader in its Inspector. It writes the uber shader with the modules next to the asset, and YAUI uses it instead of its own.
A module's id is its position in the list, so generate the shader again after changing the list. A project can use up to 31 modules. The keyword YAUI_MODULE is enabled only for the draws that contain primitives with a module. A primitive with a module that is used as a mask shape is drawn as an image there.