LoginSignup
13
19

More than 5 years have passed since last update.

Shader上で1つ前のフレームの計算結果を使う

Posted at

概要

Unityでshaderを使って実装しているときに、1つ前のフレームで計算した値を使って今のフレームのピクセルの色を計算する必要がありました
が、shaderの知識がない自分はかなり手こずったので、解決した方法を共有します

実現したかったこと

「《パラメータX》と 《前フレームの計算結果R'》 をごにょごにょして《現フレームのピクセル色》を計算」すること

1つのshaderでは実現できませんでした

shader上の変数に値を入れておけば次のフレームでもその値が使えるというものではなく、shaderはフレーム間で状態を保持しません
となると、値をshaderの外に出しておく必要があるのですが、1つのshaderの出力(frag関数の返り値)は1つだけです
よって、1つのshaderでは計算結果を保存する(出力1)とピクセルの色を指定する(出力2)の両方を実現できません

shaderの分割&データ保持用textureの追加で解決しました

  • shaderを2つに分けました。shader1の結果はRGBのR値(GでもBでも良い)として小さなtextureに出力します
    • shader1: 《パラメータX》 と 《前フレームの計算結果R'》 から 《現フレームの計算結果R》 を計算 → (小さなtextureに)出力
    • shader2: 《現フレームの計算結果R》 から 《現フレームのピクセル色》 を計算 → (色を描画したいtextureに)出力
  • 小さなtextureは2つ用意して、《前フレームの計算結果R'》を持つtextureと《現フレームの計算結果R》を持つtextureが毎フレーム交互に変わるようにスクリプトで操作します

具体的に

必要なもの

  • 値保存用material (_dataMaterial)
    • 値の計算を行って結果を出力するshader (DataCalcShader)を設定する
  • 描画用material (_paintMaterial)
    • 保存された値を取得してピクセル色を出力するshader (PaintShader)を設定する
  • 各materialにパラメータを設定するスクリプト

コード

各materialにパラメータを設定するスクリプト

以下のクラスはStart()内で初期化し、Update()内でCalc()を呼び出して使うことを想定しています
Graphics.Blitでmaterialを指定すると、textureAを_MainTexとしてmaterialに紐づくshaderを実行し結果をtextureBに描画することが可能です

using System;
using UnityEngine;

public class Sample
{
    private Material _dataMaterial;
    private Material _paintMaterial;
    private RenderTexture _dataTex1;
    private RenderTexture _dataTex2;

    public Sample(Material dataMaterial, Material paintMaterial)
    {
        _dataMaterial = dataMaterial;
        _paintMaterial = paintMaterial;

        // 1x1のRenderTextureを2つ作成しておく
        _dataTex1 = new RenderTexture(1, 1, 0, RenderTextureFormat.ARGB32);
        _dataTex1.Create();
        _dataTex2 = new RenderTexture(1, 1, 0, RenderTextureFormat.ARGB32);
        _dataTex2.Create();
    }

    public void Calc(float paramX)
    {
        // 計算のためのパラメータを設定
        _dataMaterial.SetFloat("_ParamX", paramX);

        // フレーム毎に交互に以下のif-elseブロックを呼び出す
        if (Time.frameCount % 2 == 0)
        {
            // _dataTex1を "_MainTex" として_dataMaterialに紐づくshaderの計算を実行し、
            // 結果を_dataTex2に出力(保存)
            Graphics.Blit(_dataTex1, _dataTex2, _dataMaterial, -1);

            // _paintMaterialのshaderの計算では_dataTex2を使う
            _paintMaterial.SetTexture("_DataTex", _dataTex2);
        }
        else
        {
            // _dataTex2を "_MainTex" として_dataMaterialに紐づくshaderの計算を実行し、
            // 結果を_dataTex1に出力(保存)
            Graphics.Blit(_dataTex2, _dataTex1, _dataMaterial, -1);

            // _paintMaterialのshaderの計算では_dataTex1を使う
            _paintMaterial.SetTexture("_DataTex", _dataTex1);
        }
    }

}

DataCalcShaderのfrag関数

float4 frag (v2f i) : SV_Target
{
     // _MainTexのR値 = 前フレームの計算結果
     float previousResult = tex2D(_MainTex, i.uv).r;

     // 計算(ここではlerp)を行う
     float result = lerp(previousResult, _ParamX, 0.5);

     // 結果をR値として保存する
   return float4(result, 0, 0, 0);
}

PaintShaderのfrag関数

float4 frag (v2f i) : SV_Target
{
     // R値として保存されていたデータを取り出す
     float result = tex2D(_DataTex, float2(0, 0)).r;

     // resultからピクセルの色を計算してreturnする
}
13
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
19