💨

【Unity】RenderTextureにParticleがうまく描画されなかった話

2024/05/16に公開

概要

RenderTextureにParticleを書き込んで使おうとしたところ、思ったように描画されなかったので、描画されるようになるまでの備忘録です

今回使ったプロジェクトは下記のGithubにあります。手短に結果が見たい人は参照してみてください

https://github.com/tkada/ParticleWithRenderTexture

目指す最終状態

RenderTextureに描画したParticleが正しく描画され、後ろのオブジェクトが透けて見えている状態を目指します。

初期状態

Hierarchyは下記のようになっています

また、シーン上ではこのような配置になっています。CanvasScreen Space - Overlayを使用しています。

大まかな処理の流れは
1.ParticleCameraParticleSystemを撮影
2.RenderTextureに書き込む
3.CanvasRawImageRenderTextureを表示
という流れになっています

ParticleCameraClear FlagSkyBoxのときは正しくParticleが表示されていました。
ただし、SkyBoxが邪魔をしてその後ろの球体を見ることはできません。

このままではSkyBoxが映り込んでしまうので、Clear FlagSolid Colorに設定し、BackGroundを(0,0,0,0)にしたところ、Particleが表示されなくなってしまいました

何が起きているのかRendeTextureを確認してみると、RGBに色は書き込まれているもののAlphaチャンネルに色が書き込まれていないことがわかりました

対策1 CameraのBackgroundを調整する

Alphaチャンネルに情報が書き込まれていないことが原因なので、カメラ側で塗りつぶすようにすればとりあえずはParticleが表示されます。
ただし、この方法だとRenderTextureは透過しないので、後ろのオブジェクトは見えないままです。
CameraのBackgroundを(0,0,0,1)にする

一応Particleは表示される

対策2 ParticleのShaderを変更する

Particleに使われているShaderがAlphaチャンネルに情報を書き込まないのが原因なので、これを修正します。標準で使われているシェーダー(組み込みシェーダー)は
https://unity.com/releases/editor/archive
からダウンロードすることができます。お使いのバージョンに合わせたシェーダーをダウンロードします。

この中からParticleに使われているシェーダーを探します。DefaultResourcesExtraのフォルダの中におそらく入っていると思われます。
私の場合はParticle Premultiply Blend.shaderでした。
このファイルをUnityプロジェクトの中にコピーします。
そして、ColorMask RGBとなっている部分をColorMask RGBAに書き換え、シェーダーの名前も変えておきます

-  Shader "Legacy Shaders/Particles/Alpha Blended Premultiply" {
+  Shader "Legacy Shaders/Particles/Alpha Blended Premultiply RGBA" {
Properties {
    _MainTex ("Particle Texture", 2D) = "white" {}
    _InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
}

Category {
    Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
    Blend One OneMinusSrcAlpha
-    ColorMask RGB
+    ColorMask RGBA
    Cull Off Lighting Off ZWrite Off

新たにMaterialを作成し、上記のShaderをアサイン、TextureにはDefault-Particleを設定しました。

この状態だと後ろのオブジェクトが見えた状態でParticleも描画されるようになりました。

ただ、よく見てみるとParticleの縁が黒くなっています。

この現象は下記のサイトなどで詳しく説明されています。端的に説明すると、RenderTextureを描画する際にBackgroundColorとアルファブレンドした状態で描画しているのにもかかわらず、UIとして描画する際に再度アルファブレンドしてしまっているのでおかしな色になっています。
http://compojigoku.blog.fc2.com/blog-entry-5.html
https://logmi.jp/tech/articles/296077

対策3 RenderTextureに書き込む際に対策をいれる

アルファブレンドを2回してしまうのが原因ということがわかりました。そこでRenderTextureに書き込む際に、色情報の方にはアルファ適用前の色を書き込むようにします。
具体的にはImageEffectShaderを利用します。
(URPの場合はImageEffectが使えないので、Renderer Featureを使えば同じことができると思います。)

まず、下記のC#スクリプトを作成します

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Camera))]
public class ImageEffect : MonoBehaviour
{
    [SerializeField] Material _material;

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit(source, destination, _material);
    }
}

次にImageEffect用のShaderを作成します

Shader "ImageEffect/WriteRT"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                col.rgb /= col.a;   //col.rgb * col.aがアルファを適用した色なので、割って元に戻してる
                return col;
            }
            ENDCG
        }
    }
}

このShaderを適用したMaterialを作成します。(ToRTという名前)

そしてParticleCameraImageEffectのスクリプトをアタッチし、Materialには今回作成したMaterial(ToRT)をアサインします。

この状態で実行すると、Particleが正しく描画され、後ろのオブジェクトも見えている状態になりました

Discussion