遮蔽・可視テスト
環境
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 } } }