こんにちわ、Unityエンジニアのオオバです。

Universal Render Pipeline(以下:URP)
描画処理(パス)をカンタンに追加することができます。

やってみたいけど、、、
始め方が分からない。

そういう人結構いるんじゃないかなって思います。

今回オオバ的に最小コードで
URPの描画拡張を作ってみました。

URPの拡張をこれからやっていきたい人に向けて
わかりやすく紹介していきたいと思います。

※シェーダーは登場しません
今回作る表現

【超基本編】URPに独自のパスを追加する方法_0

解像度を徐々に下げていく、
モザイクのような表現を作っていこうと思います。

この記事の内容

1.PassとFeatureを作成

まずURPを拡張する上で2つのクラスを紹介します。
ScriptableRenderPassScriptableRendererFeature

クラス名説明
ScriptableRenderPass実際の描画処理
ScriptableRendererFeatureURPへの追加機能単位

これらのクラスを継承したクラスを作ります。

Passは描画処理

💻ソースコード : DownSamplingRenderPass.cs抜粋
public class DownSamplingRenderPass : ScriptableRenderPass  
{
     public override void Execute(ScriptableRenderContext context,  
         ref RenderingData renderingData)  
     {
         // 描画処理を記述  
     }

    /// <summary>Constructor</summary>  
    public DownSamplingRenderPass() =>  
        renderPassEvent = RenderPassEvent.AfterRenderingTransparents;  
}

Executeメソッド内に描画の処理を記述します。

Passの処理タイミングは決まっている

先のソースコードでrenderPassEventが登場しました。

renderPassEvent = RenderPassEvent.AfterRenderingTransparents;  

Enum RenderPassEventにはさまざまな
描画タイミングが定義されています。

RenderPassEventをPassにセットすることで、
指定のタイミングで描画処理を走らせることができるのです。

今回はAfterRenderingTransparentsなので、
「透明オブジェクトを描画した後に実行」という意味になります。

FeatureはURP拡張の最小単位

💻ソースコード : DownSamplingRenderFeature.cs抜粋
public class DownSamplingRenderFeature : ScriptableRendererFeature  
{
    DownSamplingRenderPass _renderPass;  

    public override void Create() =>  
        // Passの作成  
        _renderPass = new DownSamplingRenderPass();  

    public override void AddRenderPasses(  
        ScriptableRenderer renderer, ref RenderingData renderingData)  
    {
        // ScriptableRendererにPassを渡す  
        renderer.EnqueuePass(_renderPass);  
    }
}

Featureクラスの仕事は2つあります。

つまりFeatureとPassの関係は以下です。

【超基本編】URPに独自のパスを追加する方法_1

Featureは複数のパスを管理することができます。

※今回はパスは1つしか扱いません

2.Featureの適用

作成したFeatureを適用するため、
Rendererに登録していきます。

【超基本編】URPに独自のパスを追加する方法_2

URPのForwardRendererに
Featureを追加していきます。

ForwardRendererのインスペクタ

【超基本編】URPに独自のパスを追加する方法_3

Add Renderer Feature をクリックして、
作成したDown Sampling Render Featureを追加します。

ForwardRendererのインスペクタ

【超基本編】URPに独自のパスを追加する方法_4

するとFeatureはRendererに適用されます。

モザイク処理の解説

実際の描画処理はPassです。
前述のとおり、描画処理はPassのExecuteに記述します。

DownSamplingRenderPass Executeメソッド抜粋
// CommandBuffer作成(描画コマンドです)  
var commandBuffer = CommandBufferPool.Get(CommandBufferName);  

var cameraData = renderingData.cameraData;  
// 現在描画しているカメラの解像度を`_downSample`で除算  
var w = cameraData.camera.scaledPixelWidth / _downSample;  
var h = cameraData.camera.scaledPixelHeight / _downSample;  

// RenderTextureを生成  
commandBuffer.GetTemporaryRT(RenderTextureId,  
    w, h, 0, FilterMode.Point, RenderTextureFormat.Default);  

// 現在のカメラ描画画像をRenderTextureにコピー  
commandBuffer.Blit(_currentTarget, RenderTextureId);  

// RenderTextureを現在のRenderTarget(カメラ)にコピーしてモニタに写す  
commandBuffer.Blit(RenderTextureId, _currentTarget);  
処理のイメージ図

【超基本編】URPに独自のパスを追加する方法_5

  1. 小さいサイズのRenderTextureを生成
  2. カメラが写している画像をRenderTextureにコピー
  3. 小さいサイズのRenderTextureを元のカメラのサイズにコピー

一度解像度を小さくしているため、
元のサイズに戻すとモザイク状態になるという仕組みです。

CommandBufferでさまざまな処理をさせる

CommandBufferは描画コマンドの単位で、
描画処理を作っていく上で必要な存在です。

CommandBufferに処理を詰め込み、
GPUにわたすことで描画処理が実行されます。

他にもたくさんの処理が定義されています。
↑今回はこの2つを使っています。

PassのExecute前にパラメータを渡す

PassにSetParamメソッドを定義して
描画前にFeatureからパラメータを渡しています。

💻ソースコード : DownSamplingRenderPass.csの抜粋
public override void AddRenderPasses(  
    ScriptableRenderer renderer, ref RenderingData renderingData)  
{
    // 描画処理前にパラメータをPassに渡す  
    _renderPass.SetParam(renderer.cameraColorTarget, downSample);  
    renderer.EnqueuePass(_renderPass);  
}

この2つをPassの描画処理前に渡します。

カメラ情報とはカメラが写している画像情報 です。

※カメラの解像度はExecuteメソッドの引数RenderingDataから取得可能

ダウンサンプル値は
モザイク表現するための値です。

この値を毎フレーム変化させることで
モザイクアニメーションを表現しています。

描画前のPassに対してFeatureからパラメータを渡す。

この手順は今後も出てくると思うので、
覚えておきましょう。

3.モザイクの大きさをアニメーションさせる

FeatureはScriptableObjectです。

URP ForwardRendererの中に作られる

【超基本編】URPに独自のパスを追加する方法_6

再生中に調整したいパラメータを定義しておき、
スクリプトから変更することが可能です

スクリプトからFeatureのパラメータを更新しているイメージ図

スクリプトからFeatureのパラメータを更新しているイメージ図

パラメータを更新することで
アニメーション表現を作れます。

サイン波を使ったアニメーション処理のサンプル
public class DownSampleAnimation : MonoBehaviour  
{
    // Featureの参照を保持  
    [SerializeField] private DownSamplingRenderFeature _feature;  

    void Update()  
    {
        var tmp = (Mathf.Sin(Time.frameCount * Mathf.PI / 180f) + 1f) / 2f;  
        // ダウンサンプリング最小1/120のサイズまで適用  
        var value = tmp * 120f;  
        // Featureのパラメータを更新  
        _feature.downSample = (int)value;  
    }
}

するとこのようなこのようなアニメーションが作成できます。

【超基本編】URPに独自のパスを追加する方法_7

ゲームの状態によるパラメータ調整ってよくありますよね。
ダメージを受けたら画面を赤くするとか。

そういう処理もURPの拡張を使えば
わかりやすく実装できそうです。

FrameDebuggerで描画タイミングを確認

最後に描画処理のデバッグ、確認方法の紹介です。

Window > Analysis > Frame Debugger
Frame Debuggerを使うと描画タイミングを確認できます。

【超基本編】URPに独自のパスを追加する方法_8

FrameDebuggerの Enableボタン をクリック。

Frame Debugウィンドウ

【超基本編】URPに独自のパスを追加する方法_9

するとDownSamplingRenderPass
Frame Debuggerに表示されます。

DrawTransparentObjects
(透明オブジェクトの描画)の後に
実行されていることが分かります。

意図通りですね。

まとめ

URPの拡張を最小コードで紹介してきました。

  1. Feature、Passの作成
  2. Featureの中でPassを生成
  3. 描画前にFeatureからPassにパラメータを渡す
  4. PassのExecuteに描画処理を書く
  5. FeatureをRendererに登録

5ステップで大まかな描画の流れは実装できます。

Frame Debuggerを使うことで
意図したタイミングで処理が走っているか
確認可能です。

次回はシェーダーを使ってURPを
拡張していこうと思います。

全ソースコードはこちら

最後にサンプル全ソースを公開しておきます。
サンプルも内包されて分かりづらいですが、
Assets/DownSamplingフォルダ
今回のサンプルは入っています。

何か参考になれば幸いです。
GitHub - baobao/URP-DownSamplingSample


フォローすると UIデザイナー力の上がるTwitter やってます!
今日から使えるテクニックを発信中。
ぜひフォローしてみてください!
👉フォローはこちら!

最後まで読んでいただきありがとうございました!
すばらしいURP拡張ライフをお過ごしください。

オススメ記事
検証環境