unityで自作fogをやりたい

はじめに

調べてもunityが用意してくれてる奴を使おうねーってのばっかり
俺は自作したいの!!!!!!!!

環境

unity 2021.2.12 URP

やりたいこと

カメラから見たときに,奥行に応じて画面に青みをつけたい
奥に行くほど青く,手前ほどそのままの色
オブジェクト毎じゃなくて,画面全体に適用したい

結果

  1. URPのプロジェクトを作った時についてくるForwardRenderer設定にあるRenderingのDepthPrimitingModeをForcedに変える,ついてこなかったらProjectフォルダの中で自分でURPAssetを作って設定する
  2. ProjectSettingsのGraphicsのSRPSettingsにさっき作ったのを入れる
  3. 使うカメラのPostProcessingをONにする
  4. 望んだグラフィック表現に辿り着くために 拡張性の高い「URP」で作るハイクオリティな独自表現 - ログミーTech の通りにクラスを作る
  5. 以下のようにshaderを書いて,カメラの色と深度をとって処理する
sampler2D _CameraDepthTexture;
sampler2D _CameraColorTexture;
v2f vert(appdata_base v){
    v2f o;
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    return o;
}
fixed4 frag(v2f i): SV_TARGET{
    fixed4 depthCol = tex2D(_CameraDepthTexture, i.uv);
    fixed4 col = tex2D(_CameraColorTexture, i.uv);
    float coef = clamp(saturate(10 * depthCol.r), 0.3, 1);
    col.rgb *= coef;
    return col;
}

※DepthPrimitingModeをAutoにする場合は,URPAssetのDepthTextureをONにしないと取れない

するとこうなる

青みがかってないけど,奥行きに応じた色の変化ができたので終了

懸念点

このままだと常時適用されるけど,
レンダーパイプラインの切り替え - Unity マニュアル
試してないけど,これでやれそう


終わり

                                          • -

以下は試行錯誤推移

経過観察

最初は霧(bloom)かなって思ったけど,どうやらfogの方がそれっぽい
霧はmist・fogなのに一体何を言っているんだ

描画する直前の色に深度値に応じた値を入れられればいいなぁって思った
その辺調べるとどうもPostEffectとかなんかその辺らしいよ?

とりあえず,URPだと自分でレンダリングパイプラインいじらないとだめらしい

いじっていれました

参考:
【Unity】Universal Render Pipelineで独自のレンダリングパスを追加する - LIGHT11
(他にも色々見た気がするけど覚えてない)

                                          • -
  1. カメラのPostProcessingをON
  2. URPを立ち上げたときに入ってるデフォルトのURPAssetの設定のDepthTextureとOpaqueTextureをON

なんかよくわからんけど,Blitで書き込みができるらしいので
こんなスクリプトを追加した

~
Material mat;
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) {
    ~
    var camera = renderingData.cameraData.camera; 
    var cmd = CommandBufferPool.Get(cRenderTag);
    cmd.Blit(renderingData.cameraData.targetTexture, camera.activeTexture, mat); // カメラにマテリアルを適用
    ~
}

マテリアルのシェーダーはこんな感じ

struct v2f{
    float4 pos : SV_POSITION;
    float2 uv : TEXCOORD0;
    half depth : TEXCOORD1;
};

v2f vert(appdata_img v){
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord.xy);
    COMPUTE_EYEDEPTH(o.depth.x);
    o.depth *= _ProjectionParams.w;
    return o;
}
sampler2D _CameraDepthTexture;
sampler2D _CameraOpaqueTexture;
            
fixed4 frag(v2f i): SV_TARGET{
    fixed4 col = tex2D(_CameraOpaqueTexture, i.uv);
    col.r = 1;
    col.g = 0;
    col.b = 0;
    return col;
}

確認のためにfragの方は適当に出力してる
これでカメラのテクスチャにmatを適用したのが描画されて画面が真っ赤になる・・・ならないね?なんで?
FrameDebugだと途中で真っ赤になってるのはわかるけど,最終的にはもみ消されてるなんで?

                                          • -

望んだグラフィック表現に辿り着くために 拡張性の高い「URP」で作るハイクオリティな独自表現 - ログミーTech
中身を全部ここの通りにしたら,とりあえず画面変わった
renderPassEventの位置を後ろの方にしないといけないらしい
一方でまだ深度値取れないね・・・あとこれ常時適用されてしまうから
特定の場面のみで適用したい場合の方法を考えないといけない


Unity - Manual: Cameras and depth textures
ここを見る限り,_CameraDepthTextureが有効で
Unity - Manual: Particle System vertex streams and Standard Shader support
ここのサンプルでz値を取ってるのでその通りのやったけど変化なし
Unity のソフトパーティクルのシェーダについて調べてみた - 凹みTips
後はこの辺とか参考になりそう

あー待った,
カメラのテクスチャを取得してる → 全体に描画されている → 画面全体の一枚絵として表示
→ 頂点は一枚絵を構成する画面端の4点のみ → 深度値どこも同じ
なのでは?
FrameDebugの自作レンダーのVerticesが4なので可能性高い
一方でDrawOpaqueObjectsに結構頂点があるので,その辺なら深度値とれるかも

→renderPassEventをAfterRenderingOpaquesにしたけどだめでした

_CameraDepthTextureが画面サイズと全く違う点が気になる・・・
うまくとれてないのか・・・?

見逃していたのかいつの間にか追加されたのかわからんけど,
URPのプロジェクト作った時に付いてくるForwardRenderer設定の
Rendering の Depth Primiting Mode を Forced にすると,
Frame Debug に CopyDepth というPipelineが追加されて
_CameraDepthTextureに画面サイズと同じサイズのTextureが生成されて,
深度値が取れるようになった
前見たときこんな設定なかったような・・・・・・・?
シェーダーに
sampler2D _CameraDepthAttachment;
っていうのを追加したあたりで出たのかな・・・・・・全く関係ないかもしれないけど
コメントアウトしても設定は出たままなので関係ないかも

どちらにしろできたので,ここの記事の最初の方に手順記して終わり