遮蔽・可視テスト

環境

Unity2021.2.18f1

概要

遮蔽(もしくは逆に可視)テストによるグレアです。

OcclusionQueryのようなことを_CameraDepthTextureとComputeShaderを用いて行ってみました。

古典的な手法ですが、ブルームはガウスで行っているけど、光芒的なものも欲しい時に使えると思います。

using System.Collections;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;

public unsafe class OcclusionTestGlare : MonoBehaviour
{
    [SerializeField] private GameObject[] _pointObjs = null;
    [SerializeField] private ComputeShader _computeShader = null;
    [SerializeField] private Material _matGlare = null;
    private const float _radius = 0.25f;
    private CommandBuffer _cbOcclusionTest = null;
    private CommandBuffer _cbGlare = null;
    private struct Occlusion
    {
        public Vector3 pos;
        public Vector3 screenPos;
        public float alpha;
    }
    private NativeArray<Occlusion> _occlusions;
    private GraphicsBuffer _gbOcclusions = null;
    private Camera _camera = null;
    private int _kernelIndex = -1;

    private static int _spRadius = Shader.PropertyToID("_Radius");
    private static int _spOcclusions = Shader.PropertyToID("_Occlusions");
    private static int _spViewProjMatrix = Shader.PropertyToID("_ViewProjMatrix");
    private static int _spCameraDepthTexture = Shader.PropertyToID("_CameraDepthTexture");

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.yellow;
        for(int i = 0; i < _pointObjs.Length; i++)
        {
            Gizmos.DrawSphere(_pointObjs[i].transform.position, _radius);
        }
    }

    private void Setup()
    {
        if(_camera != null)
            return;

        _camera = Camera.main;
        _camera.depthTextureMode |= DepthTextureMode.Depth;
        _occlusions = new NativeArray<Occlusion>(_pointObjs.Length, Allocator.Persistent);
        for(int i = 0; i < _occlusions.Length; i++)
            _occlusions[i] = new Occlusion(){pos = _pointObjs[i].transform.position, alpha = 0.0f};
        _gbOcclusions = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _pointObjs.Length, sizeof(Occlusion));
        _gbOcclusions.SetData(_occlusions);

        _kernelIndex = _computeShader.FindKernel("OcclusionTest");
        _computeShader.SetFloat(_spRadius, _radius);
        _computeShader.SetBuffer(_kernelIndex, _spOcclusions, _gbOcclusions);

        _cbOcclusionTest = new CommandBuffer();
        _cbOcclusionTest.name = "OcclusionTest";
        _cbOcclusionTest.DispatchCompute(_computeShader, _kernelIndex, _occlusions.Length, 1, 1);
        _camera.AddCommandBuffer(CameraEvent.AfterForwardOpaque, _cbOcclusionTest);


        _matGlare.SetBuffer(_spOcclusions, _gbOcclusions);

        _cbGlare = new CommandBuffer();
        _cbGlare.DrawProcedural(Matrix4x4.identity, _matGlare, 0, MeshTopology.Quads, _gbOcclusions.count * 4);
        _camera.AddCommandBuffer(CameraEvent.AfterImageEffects, _cbGlare);
    }

    private void OnDestroy()
    {
        if(_cbOcclusionTest != null)
            _cbOcclusionTest.Dispose();
        if(_occlusions.IsCreated == true)
            _occlusions.Dispose();
        if(_gbOcclusions != null)
            _gbOcclusions.Dispose();
    }

    private void LateUpdate()
    {
        var texture = Shader.GetGlobalTexture(_spCameraDepthTexture) as RenderTexture;
        if(texture == null)
            return;

        Setup();
        var projMatrix = _camera.projectionMatrix;
        var viewMatrix = _camera.worldToCameraMatrix;
        var viewProjMatrix = projMatrix * viewMatrix;

        //var zb = Shader.GetGlobalVector("_ZBufferParams");

        _computeShader.SetMatrix(_spViewProjMatrix, viewProjMatrix);
    }
}
#pragma kernel OcclusionTest

float _Radius;
struct Occlusion
{
    float3 pos;
    float3 screenPos;
    float alpha;
};

Texture2D<half> _CameraDepthTexture;
float4 _ZBufferParams;
float4 _ProjectionParams;
float4 _ScreenParams;
RWStructuredBuffer<Occlusion> _Occlusions;
float4x4 _ViewProjMatrix;

float3 GetScreenPos(float3 pos)
{
    float4 screenPos = mul(_ViewProjMatrix, float4(pos.x, pos.y, pos.z, 1.0f));
    screenPos.xy = screenPos.xy / screenPos.w;
    screenPos.xy = (screenPos.xy + 1.0) * 0.5 * _ScreenParams.xy;
    screenPos.z = screenPos.w * _ProjectionParams.w;
    return screenPos.xyz;
}

inline float Linear01Depth(float z)
{
    return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}

[numthreads(1, 1, 1)]
void OcclusionTest(uint3 id : SV_DispatchThreadID)
{
    Occlusion occ = _Occlusions[id.x];
#if 0
    float3 offset = float3(_Radius, _Radius, 0);
    float3 sp0 = GetScreenPos(occ.pos - offset);
    float3 sp1 = GetScreenPos(occ.pos + offset);
    uint test = 0;

    uint height = sp1.y - sp0.y;
    uint width = sp1.x - sp0.x;
    for (uint y = (uint)sp0.y; y <= (uint)sp1.y; y++)
    {
        if(y < 0 || y > _ScreenParams.y - 1)
            continue;
        for(uint x = (uint)sp0.x; x <= (uint)sp1.x; x++)
        {
            if (x < 0 || x > _ScreenParams.x - 1)
                continue;
            float depth = Linear01Depth(_CameraDepthTexture[float2(x,y)].r);
            test += (sp0.z < depth) ? 1 : 0;
        }
    }
    occ.alpha = (float)test / (height * width);
    occ.screenPos = GetScreenPos(occ.pos);
#else
#if 1
#define PI 3.14159265359
    float size = _Radius * 2.0;
    float div = 16;
    float step = size / div;
    float stepRad = PI / (div + 2);
    uint test = 0;
    uint count = 0;
    for (float j = 0; j <= div; j++)
    {
        float h = j * step;
        float width = size * sin(stepRad * (j + 1));
        float ow = (size - width) * 0.5;
        for (float w = ow; w <= width; w += step, count++)
        {
            float3 offset = float3(-_Radius + w, -_Radius + h, 0);
            float3 sp = GetScreenPos(occ.pos + offset);
            if (sp.x < 0 || sp.x > _ScreenParams.x - 1)
                continue;
            if (sp.y < 0 || sp.y > _ScreenParams.y - 1)
                continue;
            float depth = Linear01Depth(_CameraDepthTexture[sp.xy].r);
            test += (sp.z < depth) ? 1 : 0;
        }
    }
    occ.alpha = (float)test / count;
    occ.screenPos = GetScreenPos(occ.pos);
#else
    float size = _Radius * 2.0;
    float div = 16;
    float step = size / div;
    uint test = 0;
    uint count = 0;
    for (float h = 0; h <= size; h += step)
    {
        for (float w = 0; w <= size; w += step)
        {
            float3 offset = float3(-_Radius + w, -_Radius + h, 0);
            if(_Radius < length(offset))
                continue;
            count++;
            float3 sp = GetScreenPos(occ.pos + offset);
            if (sp.x < 0 || sp.x > _ScreenParams.x - 1)
                continue;
            if (sp.y < 0 || sp.y > _ScreenParams.y - 1)
                continue;
            float depth = Linear01Depth(_CameraDepthTexture[sp.xy].r);
            test += (sp.z < depth) ? 1 : 0;
        }
    }
    occ.alpha = (float)test / count;
    occ.screenPos = GetScreenPos(occ.pos);
#endif
#endif
    _Occlusions[id.x] = occ;
}
Shader "Unlit/OcclusionTestGlare"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Size("Glare Size", float) = 0.2
    }

    CGINCLUDE
    #include "UnityCG.cginc"

    struct Occlusion
    {
        float3 pos;
        float3 screenPos;
        float alpha;
    };
    StructuredBuffer<Occlusion> _Occlusions;

    struct appdata
    {
        uint vertexId : SV_VertexID;
    };

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

    float _Size;

    v2f vert (appdata v)
    {
        v2f o;

        uint vertexIndex = v.vertexId % 4;
        float aspect = (float)_ScreenParams.y / (float)_ScreenParams.x;
        float3 quad[4] = 
        {
            float3(-1, -1, 0),
            float3( 1, -1, 0),
            float3( 1,  1, 0),
            float3(-1,  1, 0),
        };
        float3 pos = quad[vertexIndex] * float3(aspect, 1.0, 1.0) * _Size;

        uint occlusionIndex = v.vertexId / 4;
        Occlusion occ = _Occlusions[occlusionIndex];
        float2 screenPos = occ.screenPos;
        pos.xy += (screenPos / _ScreenParams.xy) * 2.0 - 1.0;
        o.vertex = float4(pos, 1.0);
        o.uv = quad[vertexIndex].xy * 0.5 + 0.5;
        o.alpha = occ.alpha;
        return o;
    }

    sampler2D _MainTex;

    fixed4 frag (v2f i) : SV_Target
    {
        fixed4 col = tex2D(_MainTex, i.uv) * i.alpha;
        return col;
    }

    ENDCG

    SubShader
    {
        Blend SrcAlpha One
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
    }
}