Shader上で1つ前のフレームの計算結果を使う
概要
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する
}
Author And Source
この問題について(Shader上で1つ前のフレームの計算結果を使う), 我々は、より多くの情報をここで見つけました https://qiita.com/okyk/items/e17ba7e2ce302832dd42著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .