描画順に依存しない半透明処理(OIT)

環境

Unity2022.2.2f1

概要

画像は複数のオブジェクトがあるように見えますが1つです。

Unityで生成した複数のオブジェクトのMeshをCombineMeshして1つのMeshにしています。

Unityだと通常、半透明はオブジェクト単位でソートされ、画面奥からZ書き込みなしで描画します。

半透明のMesh自体が複雑だと、オブジェクト単位のソートでは見え方が破綻してしまいます。

このOITの手法はピクセル単位でわりと正しく見えるように近似するものです。

詳しくは参考のページに丁寧に書いてあります。

コード

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;

public class TestOit : MonoBehaviour
{
    [SerializeField] Shader _shClear = null;
    [SerializeField] Shader _shCombine = null;
    private RenderTexture[] _renderTexs = new RenderTexture[2];
    private Material _matClear = null;
    private Material _matCombine = null;
    private static int _spTargetTex0 = Shader.PropertyToID("_TargetTex0");
    private static int _spTargetTex1 = Shader.PropertyToID("_TargetTex1");

    private void OnEnable()
    {
        _matClear = new Material(_shClear);
        _matCombine = new Material(_shCombine);

        var camera = Camera.main;
        _renderTexs[0] = new RenderTexture(camera.pixelWidth, camera.pixelHeight, 24, UnityEngine.Experimental.Rendering.DefaultFormat.HDR);
        _renderTexs[1] = new RenderTexture(camera.pixelWidth, camera.pixelHeight, 0, UnityEngine.Experimental.Rendering.DefaultFormat.HDR);

        var rts = new RenderBuffer[]
        {
            _renderTexs[0].colorBuffer,
            _renderTexs[1].colorBuffer,
        };
        camera.SetTargetBuffers(rts, _renderTexs[0].depthBuffer);
        Shader.SetGlobalTexture(_spTargetTex0, _renderTexs[0]);
        Shader.SetGlobalTexture(_spTargetTex1, _renderTexs[1]);

        var cbClear = new CommandBuffer();
        cbClear.name = "OitClear";
        cbClear.DrawProcedural(Matrix4x4.identity, _matClear, 0, MeshTopology.Quads, 4, 1);
        camera.AddCommandBuffer(CameraEvent.AfterSkybox, cbClear);

        var cbCombine = new CommandBuffer();
        cbCombine.name = "OitCombine";
        cbCombine.SetRenderTarget(-1);
        cbCombine.Blit(null, BuiltinRenderTextureType.CurrentActive, _matCombine);
        camera.AddCommandBuffer(CameraEvent.AfterEverything, cbCombine);
    }

    private void OnDisable()
    {
        Camera.main.targetTexture = null;
        for(int i = 0; i < _renderTexs.Length; i++)
            _renderTexs[i].Release();

        if(_matClear != null)
            GameObject.Destroy(_matClear);
        if(_matCombine != null)
            GameObject.Destroy(_matCombine);
    }
}
Shader "Hidden/OIT/Clear"
{
    Properties
    {
    }
    SubShader
    {
        ZTest Always
        Cull Back
        ZWrite Off
        Fog { Mode Off }
        // Color0
        ColorMask A 0
        // Color1

        Pass
        {
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            struct a2v
            {
                uint vertexId : SV_VertexID;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            struct outf
            {
                fixed4 color0 : SV_Target0;
                fixed4 color1 : SV_Target1;
            };
            
            v2f vert(a2v v)
            {
                float3 posCSs[4] = 
                {
                    float3(-1,  1, 0),
                    float3(-1, -1, 0),
                    float3( 1, -1, 0),
                    float3( 1,  1, 0),
                };
                v2f o;
                o.pos = float4(posCSs[v.vertexId], 1);
                return o;
            }
            
            outf frag(v2f i)
            {
                outf o;
                o.color0 = fixed4(0,0,0,1);
                o.color1 = fixed4(0,0,0,0);
                return o;
            }
            ENDCG
        }
    }
}
Shader "Hidden/OIT/Combine"
{
    Properties
    {
    }
    SubShader
    {
        ZTest Always
        Cull Off
        ZWrite Off
        Fog { Mode Off }

        Pass
        {
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            
            struct a2v
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 texcoord : TEXCOORD0;
            };
            
            sampler2D _TargetTex0;
            sampler2D _TargetTex1;
            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.texcoord = v.texcoord;
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 background = tex2D(_TargetTex0, i.texcoord);
                float revealage = background.a;
                float4 accum = tex2D(_TargetTex1, i.texcoord);
                fixed3 rgb = fixed3(accum.rgb / clamp(accum.a, 1e-4, 5e4));
                rgb = rgb * (1.0 - revealage) + background.rgb * revealage;
                return fixed4(rgb, 1.0);

            }
            ENDCG
        }
    }
}

「Unlit/OIT/Transparent」このシェーダを半透明オブジェクトに適用する

Shader "Unlit/OIT/Transparent"
{
    Properties
    {
        _Color("Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
        LOD 100
        ZWrite Off
        // Color0
        ColorMask A 0
        Blend 0 Zero OneMinusSrcAlpha
        // Color1
        Blend 1 One One

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };
            float4 _Color;

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float posVS_z : TEXCOORD0;
            };

            struct outf
            {
                fixed4 color0 : SV_Target0;
                fixed4 color1 : SV_Target1;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.posVS_z = abs(mul(UNITY_MATRIX_MV, v.vertex).z);
                return o;
            }

            outf frag (v2f i)
            {
                outf o;
                float4 col = _Color;
                //return col;

                o.color0 = fixed4(0,0,0,col.a);
                float z = i.posVS_z;
                float w = col.a * max(1e-2, min(3 * 1e3, 10.0/(1e-5 + pow(z/5, 2) + pow(z/200, 6))));
                o.color1 = fixed4(col.rgb * col.a, col.a) * w;
                return o;
            }
            ENDCG
        }
    }
}

参考

UnityでOIT(Order Independent Tranceparency) - UnityShader 入門

GitHub - happy-turtle/oit-unity: Order-independent Transparency Implementation in Unity with Per-Pixel Linked Lists

GitHub - candycat1992/OIT_Lab: :pencil2: Order-independent Transparent in Unity

もんしょの巣穴 - DirectXの話 第110回