Skip to content

Creating Custom Effects

Jonathan Swardson edited this page Sep 25, 2026 · 1 revision

Custom Underwater Effects

Version 4 introduced a shared underwater rendering framework that allows custom underwater effects to be added without modifying the renderer feature.

Effects are automatically discovered, sorted, and executed through the framework.


Overview

A custom underwater effect consists of:

  1. A Volume Component
  2. An Underwater Effect
  3. (Optional) One or more shaders

The typical workflow looks like:

Volume
    ↓
UnderwaterEffect<TVolume>
    ↓
RenderGraph
    ↓
Shader

Creating A Volume

Create a volume component that stores all user-facing settings.

using System;
using UnityEngine.Rendering;

[Serializable]
[VolumeComponentMenu(
    "Underwater Effects/Example")]
public sealed class ExampleVolume
    : VolumeComponent
{
    public ClampedFloatParameter intensity =
        new ClampedFloatParameter(
            1f,
            0f,
            10f);

    public BoolParameter debug =
        new BoolParameter(
            false);
}

This makes the effect configurable through Unity's Volume system.


Creating An Effect

Create a class that inherits from:

UnderwaterEffect<TVolume>

Example:

using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;

[UnderwaterEffect(
    After =
        UnderwaterEffectOrder.Fog)]
public sealed class ExampleEffect
    : UnderwaterEffect<ExampleVolume>
{
    public override void RecordRenderGraph(
        RenderGraph renderGraph,
        ContextContainer frameData)
    {
        if (!IsActive())
            return;

        // RenderGraph work here.
    }
}

Accessing Volume Values

The base class automatically provides access to the active volume.

float intensity =
    Volume.intensity.value;

No manual volume lookups are required.


Effect Ordering

The framework automatically sorts effects by execution order.

Built-in stages are:

0     WaterLine
100   TransparentDepthTexture
200   Caustics
300   Fog
400   Refraction
500   SunShafts
600   BlurredWaterLine

Recommended

Insert effects relative to built-in stages.

Run before fog:

[UnderwaterEffect(
    Before =
        UnderwaterEffectOrder.Fog)]

Run after fog:

[UnderwaterEffect(
    After =
        UnderwaterEffectOrder.Fog)]

Also Supported

Run at a custom position:

[UnderwaterEffect(350)]

Example:

Caustics
    ↓
Fog
    ↓
MyEffect
    ↓
Refraction

Important

Avoid directly using built-in stage values unless you intentionally want to share the same execution order as a built-in effect.

Example:

[UnderwaterEffect(
    UnderwaterEffectOrder.Fog)]

This places the effect at the exact same execution stage as the built-in fog effect.

Execution order between effects sharing the same order value should not be relied upon.

For custom effects, prefer:

Before

or:

After

instead.


Example Full Effect

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.RenderGraphModule.Util;
using UnityEngine.Rendering.Universal;

[UnderwaterEffect(
    After =
        UnderwaterEffectOrder.Fog)]
public sealed class ExampleEffect
    : UnderwaterEffect<ExampleVolume>
{
    private readonly Material material;

    public ExampleEffect()
    {
        material =
            CoreUtils.CreateEngineMaterial(
                Shader.Find(
                    "Hidden/Example"));
    }

    public override void RecordRenderGraph(
        RenderGraph renderGraph,
        ContextContainer frameData)
    {
        if (!IsActive())
            return;

        UniversalResourceData resourceData =
            frameData.Get<UniversalResourceData>();

        if (resourceData.isActiveTargetBackBuffer)
            return;

        TextureHandle cameraColor =
            resourceData.activeColorTexture;

        TextureDesc descriptor =
            cameraColor.GetDescriptor(
                renderGraph);

        descriptor.name =
            "Example Effect";

        TextureHandle destination =
            renderGraph.CreateTexture(
                descriptor);

        material.SetFloat(
            "_Intensity",
            Volume.intensity.value);

        renderGraph.AddBlitPass(
            new RenderGraphUtils
                .BlitMaterialParameters(
                    cameraColor,
                    destination,
                    material,
                    0),
            "Example Effect");

        renderGraph.AddCopyPass(
            destination,
            cameraColor,
            "Example Effect Copy");
    }

    public override void Dispose()
    {
        CoreUtils.Destroy(
            material);
    }
}

Shared Resources

Several built-in effects generate reusable resources.

Custom effects can consume these resources the same way built-in effects do.

Currently available:

_WaterLineMask
_TransparentDepthTexture

Water Line Mask

Separates underwater pixels from pixels above the water surface.

float horizonMask =
    SAMPLE_TEXTURE2D(
        _WaterLineMask,
        sampler_WaterLineMask,
        input.uv).r;

float underwaterMask =
    saturate(
        1.0 -
        horizonMask);

Transparent Depth Texture

Provides transparent object participation data.

Stored channels:

R = Transparent Depth
G = Thickness
B = Opacity
A = Reserved
float4 transparentData =
    SAMPLE_TEXTURE2D(
        _TransparentDepthTexture,
        sampler_TransparentDepthTexture,
        input.uv);

float depth = transparentData.r;
float thickness = transparentData.g;
float opacity = transparentData.b;

Transparent Depth Participation

Objects may opt into the transparent depth system using:

TransparentDepthSettings

Available settings:

Thickness
Opacity

This allows effects such as:

  • Glass
  • Observation Domes
  • Force Fields
  • Shields
  • Portals
  • Energy Barriers

to participate in underwater fog and other custom underwater effects.


Summary

The framework automatically:

  • Discovers effects
  • Sorts effects
  • Executes effects
  • Provides Volume integration
  • Supports RenderGraph
  • Exposes shared underwater resources

Most custom effects only need:

  1. A Volume Component
  2. An Underwater Effect
  3. A Shader

Everything else is handled by the framework.