Rendering overview¶
Tick UI ships three render-pipeline integration paths:
Universal Render Pipeline (URP), Built-in (BIRP), and a
pipeline-agnostic RenderTexture path for in-world UI. All three
forward draw recording through an TickRecorder (typically
BaseGameUI) into the same IDrawer, so most code stays
pipeline-agnostic. This page describes the recorder pattern that
unifies them and the points where the paths differ.
The three paths¶
URP lives in Source/Integration/URP/. TickURPInjector
hooks RenderPipelineManager.beginCameraRendering, and an
TickRenderPass enqueued onto the camera's renderer writes into
resourceData.activeColorTexture. Compiled under
TICK_URP.
BIRP lives in Source/Integration/BIRP/.
TickBIRPInjector adds a CommandBuffer to the camera at a
configurable CameraEvent. The buffer writes into the camera's
currently bound target.
RenderTexture lives in Source/Integration/RenderTexture/.
TickRenderTextureInjector is a pipeline-agnostic MonoBehaviour
that records the recorder's commands directly into a
RenderTexture each LateUpdate via Graphics.ExecuteCommandBuffer.
Use it to display Tick UI on an in-world surface. Assign
the texture to a material on a quad or screen mesh, and the UI
appears on it.
The URP and BIRP injectors find the recorder on the same
GameObject via GetComponent<TickRecorder>(). The RenderTexture
injector takes the recorder as an explicit Inspector field, so it
can drive a recorder that lives elsewhere in the scene.
For per-pipeline configuration, refer to Configure URP, Configure BIRP, and Configure RenderTexture.
The recorder pattern¶
TickRecorder is the abstract base that all three injector paths
talk to. It exposes two virtual methods:
public abstract void RecordInto(CommandBuffer target);
public virtual void SetRenderTarget(RenderTargetIdentifier target, Vector2Int size, bool isOffscreen);
BaseGameUI is the standard subclass. It owns the UI instance
and forwards both calls to the underlying Drawer. Custom
integrations subclass TickRecorder directly when BaseGameUI's
MonoBehaviour-driven setup does not fit; refer to
Build a custom integration.
Each injector calls SetRenderTarget(target, size, isOffscreen)
every frame before RecordInto. The drawer needs all three: the
target identifier so it can re-bind it after the frosted-glass
pipeline's grab-blit, the pixel size so its shape shaders'
pixel-to-NDC conversion matches the actual target, and the
off-screen flag so it can cancel the shader Y-flip on D3D-like
APIs when drawing into a non-backbuffer target. The URP path
forwards resourceData.activeColorTexture and the camera's pixel
dimensions; the BIRP path forwards BuiltinRenderTextureType.CameraTarget
and the camera's pixel dimensions; the RenderTexture path forwards
the destination texture and its pixel dimensions.
Pixel coordinates and the target size¶
Tick UI's shape shaders convert pixel positions to NDC
against a _ScreenSize uniform. The drawer sets that uniform from
the size forwarded through SetRenderTarget, so render scale ≠ 1,
viewport-rect cameras, off-screen RenderTexture targets, and
dynamic resolution all position the UI correctly as long as the
injector forwards the active target's pixel dimensions each frame.
The shipped injectors do.
If no size has been forwarded, the drawer falls back to
Screen.width × Screen.height. This happens when a custom
integration skips the call. Custom integrations need to pass the
actual target size to avoid that fallback. Refer
to Build a custom integration.
For the math behind the conversion, refer to Coordinate spaces. For the drawer's command-list model that drives the conversion, refer to Drawer and batching.
Frosted glass and the temp RT¶
HandleDrawFrosted allocates a temporary RenderTexture sized to
the forwarded target size so the shader can grab the current
target, blur it, and composite it back. The grab-and-blit changes
the render target mid-frame. Drawer keeps the
RenderTargetIdentifier forwarded through SetRenderTarget and
uses it to restore the bound target after the blit.
A custom integration must call recorder.SetRenderTarget(target, size, isOffscreen)
each frame so the drawer knows both which target to restore to
and which pixel dimensions to size its conversion against.
UI Toolkit¶
Tick UI ships no UI Toolkit integration path. UI Toolkit
hosting requires a custom integration through TickRecorder.
Composite with uGUI¶
A scene that uses both Tick UI and Unity's uGUI (a Canvas
in Screen Space - Overlay mode) composites them in a fixed
order: uGUI draws on top of Tick UI, regardless of which
RenderPassEvent (URP) or CameraEvent (BIRP) Tick UI is
configured to use. Screen Space - Overlay canvases run after every
camera has finished rendering, which is later than any pipeline
event Tick UI can hook.
Practical consequences:
- A uGUI
Canvascovers Tick UI panels even when Interlude UI is configured forAfterRendering(URP) orAfterEverything(BIRP). - Switching the canvas to Screen Space - Camera or World Space brings it into the same render-pass timeline as Tick UI, so the configured pipeline event controls draw order.
- Tick UI supports running both UI stacks together. Its API covers HUDs, menus, and debug overlays. Use uGUI when a project already depends on it.
Pitfalls¶
UI scales wrong under render scale ≠ 1.0 or on a non-display
target.
: Almost always a custom integration that forgot to call
recorder.SetRenderTarget(target, size, isOffscreen) each frame, so the
drawer fell back to Screen.*. The shipped URP, BIRP, and
RenderTexture injectors handle this for you; check there
only if you wrote your own integration.
The frosted blit corrupts the render target.
: The custom integration's bind lifecycle does not match the
drawer's expectations. Confirm SetRenderTarget(target, size, isOffscreen)
runs each frame so the drawer knows which target to restore
to after the blit.
Multiple cameras render the UI more than once.
: TickURPInjector hooks every Game-type camera. Multiple
cameras with the injector enqueued enqueue the pass on each.
Restrict the injector to a single camera, or filter inside
OnBeginCameraRendering.
The integration runs before camera rendering completes.
: The chosen RenderPassEvent (URP) or CameraEvent (BIRP)
schedules Tick UI too early. Default to the
after-everything events for HUD usage. Refer to
Configure URP and
Configure BIRP.
A uGUI Canvas covers Tick UI panels.
: Screen Space - Overlay canvases composite after every camera
has finished, so they land on top of Tick UI regardless
of the chosen pipeline event. Switch the canvas to Screen
Space - Camera or World Space to share Tick UI's
pipeline timeline. Refer to the Composite with uGUI
section above.
Additional resources¶
- Architecture: broader source-tree map.
- Drawer and batching: the command-list model the recorder consumes.
- Render pipeline support: the matrix of supported features.
- Build a custom integration: for HDRP and other non-URP/BIRP pipelines.
- Configure URP and Configure BIRP: per-pipeline setup.