カメラの開口形状からグレア形状を得る
環境
Unity2021.2.18f1
概要
FFTを用いたグレア形状(StarBurst)の生成を行ってみました。
参考リンクのレンズフレアプロジェクトのコードから該当部分のみUnityで動作するようにしています。
GenerateStarBurstWindow.cs
using System.IO; using UnityEditor; using UnityEngine; public class GenerateStarBurstWindow : EditorWindow { [MenuItem("Tools/Generate StarBurst")] private static void Create() { var window = GetWindow<GenerateStarBurstWindow>( "GenerateStarBurst" ); window.Show(); } private GenerateStarBurst _obj = null; private void OnEnable() { var ms = MonoScript.FromScriptableObject( this ); var path = AssetDatabase.GetAssetPath( ms ); path = path.Replace(Path.GetFileName( path ), "" ); path = path + "GenerateStarBurst.asset"; _obj = AssetDatabase.LoadAssetAtPath<GenerateStarBurst>( path ); if(_obj == null) { var assets = AssetDatabase.LoadAllAssetsAtPath(path); if(assets.Length > 0) _obj = assets[0] as GenerateStarBurst; } if( _obj == null ) { _obj = ScriptableObject.CreateInstance<GenerateStarBurst>(); AssetDatabase.CreateAsset( _obj, path ); AssetDatabase.ImportAsset( path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive ); } } private void OnDisable() { if( _obj == null ) return; EditorUtility.SetDirty( _obj ); AssetDatabase.ImportAsset( AssetDatabase.GetAssetPath( _obj ), ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive ); } private void OnGUI() { _obj.OnGUI(); Repaint(); } }
GenerateStarBurst.cs
using System; using System.IO; using UnityEditor; using UnityEngine; using UnityEngine.Experimental.Rendering; public class GenerateStarBurst : ScriptableObject { [SerializeField] private Texture2D _apertureTexture = null; [SerializeField] private Texture2D _dustTexture = null; [SerializeField] private ComputeShader _csFft = null; [SerializeField] private Material _matCombineApurture = null; [SerializeField] private Material _matStarBurst = null; [SerializeField] private Material _matGenAperture = null; private RenderTexture[] _fftTextures = null; private RenderTexture _starBurstTexture = null; private RenderTexture _starBurstFilterTexture = null; private const int _fftTextureSize = 512; //computeShaderも変更する private const int _starBurstTextureSize = 2048; private RenderTexture _genApertureTexture = null; private int _genApertureAngle = 0; private int _genApertureNumOfBlade = 5; private void OnDestroy() { if(_fftTextures != null) { for(int i = 0; i < _fftTextures.Length; i++) _fftTextures[i]?.Release(); _fftTextures = null; } _starBurstTexture?.Release(); _starBurstTexture = null; _starBurstFilterTexture?.Release(); _starBurstFilterTexture = null; _genApertureTexture?.Release(); _genApertureTexture = null; } private static Texture TextureField(string name, Texture texture, float size = 70) { using (var verticalScope = new GUILayout.VerticalScope()) { var style = new GUIStyle(GUI.skin.label); style.alignment = TextAnchor.UpperCenter; style.fixedWidth = size; GUILayout.Label(name, style); var result = (Texture)EditorGUILayout.ObjectField(texture, typeof(Texture), false, GUILayout.Width(size), GUILayout.Height(size)); return result; } } private static string LabeledTextField(string label, string text, float labelFieldWidth = 100, float fieldWidth = 30) { using (var horizontalScope = new GUILayout.HorizontalScope()) { EditorGUILayout.LabelField(label, GUILayout.Width(labelFieldWidth)); var str = EditorGUILayout.TextField(text, GUILayout.Width(fieldWidth)); return str; } } public void OnGUI() { var str = LabeledTextField("Angle", _genApertureAngle.ToString(), 100, 30); _genApertureAngle = Convert.ToInt32(str); str = LabeledTextField("Number of Blades", _genApertureNumOfBlade.ToString(), 100, 30); _genApertureNumOfBlade = Convert.ToInt32(str); if(GUILayout.Button( "Generate Apurture" ) == true) { GenerateAperture(); } //using (var horizontalScope = new GUILayout.HorizontalScope()) using (var verticalScope = new GUILayout.VerticalScope()) { _apertureTexture = TextureField("Apurture", _apertureTexture) as Texture2D; _dustTexture = TextureField("Dust", _dustTexture) as Texture2D; } if(GUILayout.Button( "Generate" ) == true) { Generate(); } var rect = GUILayoutUtility.GetLastRect(); rect.position = new Vector2(rect.position.x, rect.position.y + rect.size.y); rect.size = new Vector2(128, 128); var srcApertureTexture = (_apertureTexture != null) ? _apertureTexture as Texture : _genApertureTexture as Texture; if(srcApertureTexture != null) EditorGUI.DrawPreviewTexture(rect, srcApertureTexture); rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y); if(_fftTextures != null) { for(int i = 0; i < _fftTextures.Length; i++) { if(_fftTextures[i] != null) EditorGUI.DrawPreviewTexture(rect, _fftTextures[i]); rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y); } } if(_starBurstTexture != null) EditorGUI.DrawPreviewTexture(rect, _starBurstTexture); rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y); if(_starBurstFilterTexture != null) EditorGUI.DrawPreviewTexture(rect, _starBurstFilterTexture); rect.position = new Vector2(rect.position.x + rect.size.x, rect.position.y); } private void GenerateAperture() { if(_genApertureTexture == null) { var format = RenderTextureFormat.ARGB32;//.ARGBFloat; _genApertureTexture = new RenderTexture(_fftTextureSize, _fftTextureSize, 0, format, RenderTextureReadWrite.Default); _genApertureTexture.useMipMap = false; _genApertureTexture.enableRandomWrite = true; } var prevRt = RenderTexture.active; _matGenAperture.SetFloat("aperture_opening", _genApertureAngle * Mathf.Deg2Rad); _matGenAperture.SetFloat("number_of_blades", _genApertureNumOfBlade); RenderTexture.active = _genApertureTexture; Graphics.Blit(_dustTexture, _matGenAperture); RenderTexture.active = prevRt; SaveRenderTexture(_genApertureTexture, "GenAperture.png"); } private void SaveRenderTexture(RenderTexture renderTex, string path) { var prevRt = RenderTexture.active; Texture2D tex = new Texture2D(renderTex.width, renderTex.height, renderTex.graphicsFormat, TextureCreationFlags.None); RenderTexture.active = renderTex; tex.ReadPixels(new Rect(0, 0, renderTex.width, renderTex.height), 0, 0); tex.Apply(); RenderTexture.active = prevRt; var bytes = tex.EncodeToPNG(); File.WriteAllBytes(Path.Combine($"{Application.dataPath}", path), bytes); AssetDatabase.Refresh(); //AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate | ImportAssetOptions.ImportRecursive); } private void Generate() { if(_fftTextures == null) { _fftTextures = new RenderTexture[4]; for (int i = 0; i < _fftTextures.Length; i++) { _fftTextures[i] = new RenderTexture(_fftTextureSize, _fftTextureSize, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Default); _fftTextures[i].useMipMap = false; _fftTextures[i].enableRandomWrite = true; } _starBurstTexture = new RenderTexture(_starBurstTextureSize, _starBurstTextureSize, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Default); _starBurstTexture.useMipMap = false; _starBurstFilterTexture = new RenderTexture(_starBurstTextureSize, _starBurstTextureSize, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Default); _starBurstFilterTexture.useMipMap = false; } if(_matCombineApurture == null) return; var prevRt = RenderTexture.active; if(_apertureTexture != null) { RenderTexture.active = _fftTextures[0]; _matCombineApurture.SetTexture("_ApurtureTex", _apertureTexture); _matCombineApurture.SetTexture("_DustTex", _dustTexture); Graphics.Blit(null, _matCombineApurture); } else { Graphics.Blit(_genApertureTexture, _fftTextures[0]); } var kernelFFTRow = _csFft.FindKernel("ButterflySLM_ROW"); _csFft.SetTexture(kernelFFTRow, "TextureSourceR", _fftTextures[0]); _csFft.SetTexture(kernelFFTRow, "TextureSourceI", _fftTextures[1]); _csFft.SetTexture(kernelFFTRow, "TextureTargetR", _fftTextures[2]); _csFft.SetTexture(kernelFFTRow, "TextureTargetI", _fftTextures[3]); _csFft.Dispatch(kernelFFTRow, 1, _fftTextureSize, 1); var kernelFFTCol = _csFft.FindKernel("ButterflySLM_COL"); _csFft.SetTexture(kernelFFTCol, "TextureSourceR", _fftTextures[2]); _csFft.SetTexture(kernelFFTCol, "TextureSourceI", _fftTextures[3]); _csFft.SetTexture(kernelFFTCol, "TextureTargetR", _fftTextures[0]); _csFft.SetTexture(kernelFFTCol, "TextureTargetI", _fftTextures[1]); _csFft.Dispatch(kernelFFTCol, 1, _fftTextureSize, 1); RenderTexture.active = _starBurstTexture; _matStarBurst.SetTexture("_FFTTex0", _fftTextures[0]); _matStarBurst.SetTexture("_FFTTex1", _fftTextures[1]); Graphics.Blit(null, _matStarBurst, 0); RenderTexture.active = _starBurstFilterTexture; _matStarBurst.SetTexture("_MainTex", _starBurstTexture); Graphics.Blit(null, _matStarBurst, 1); RenderTexture.active = prevRt; SaveRenderTexture(_starBurstTexture, "StarBurst.png"); SaveRenderTexture(_starBurstFilterTexture, "StarBurstFilter.png"); } }
FFT.compute
//-------------------------------------------------------------------------------------- // Copyright 2014 Intel Corporation // All Rights Reserved // // Permission is granted to use, copy, distribute and prepare derivative works of this // software for any purpose and without fee, provided, that the above copyright notice // and this statement appear in all copies. Intel makes no representations about the // suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED "AS IS." // INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY, // INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE, // INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Intel does not // assume any responsibility for any errors which may appear in this software nor any // responsibility to update it. //-------------------------------------------------------------------------------------- #pragma kernel ButterflySLM_ROW ROWPASS #pragma kernel ButterflySLM_COL Texture2D<float3> TextureSourceR; Texture2D<float3> TextureSourceI; RWTexture2D<float3> TextureTargetR; RWTexture2D<float3> TextureTargetI; static const int Length = 512; static const int ButterflyCount = (int)(log(Length) / log(2.0)); static const float PI = 3.14159265f; void GetButterflyValues(uint passIndex, uint x, out uint2 indices, out float2 weights) { int sectionWidth = 2 << passIndex; int halfSectionWidth = sectionWidth >> 1; int sectionStartOffset = x & ~(sectionWidth - 1); int halfSectionOffset = x & (halfSectionWidth - 1); int sectionOffset = x & (sectionWidth - 1); sincos(2.0 * PI * sectionOffset / (float)sectionWidth, weights.y, weights.x); weights.y = -weights.y; indices.x = sectionStartOffset + halfSectionOffset; indices.y = sectionStartOffset + halfSectionOffset + halfSectionWidth; if (passIndex == 0) { indices = reversebits(indices) >> (32 - ButterflyCount) & (Length - 1); } } groupshared float3 pingPongArray[4][Length]; void ButterflyPass(int passIndex, uint x, uint t0, uint t1, out float3 resultR, out float3 resultI) { uint2 Indices; float2 Weights; GetButterflyValues(passIndex, x, Indices, Weights); float3 inputR1 = pingPongArray[t0][Indices.x]; float3 inputI1 = pingPongArray[t1][Indices.x]; float3 inputR2 = pingPongArray[t0][Indices.y]; float3 inputI2 = pingPongArray[t1][Indices.y]; resultR = inputR1 + Weights.x * inputR2 - Weights.y * inputI2; resultI = inputI1 + Weights.y * inputR2 + Weights.x * inputI2; } void ButterflySLM(uint3 position) { #ifdef ROWPASS uint2 texturePos = uint2(position.xy); #else uint2 texturePos = uint2(position.yx); #endif pingPongArray[0][position.x].xyz = TextureSourceR[texturePos]; #if defined(ROWPASS) pingPongArray[1][position.x].xyz = 0; #else pingPongArray[1][position.x].xyz = TextureSourceI[texturePos]; #endif uint4 textureIndices = uint4(0, 1, 2, 3); for (int i = 0; i < ButterflyCount - 1; i++) { GroupMemoryBarrierWithGroupSync(); ButterflyPass(i, position.x, textureIndices.x, textureIndices.y, pingPongArray[textureIndices.z][position.x].xyz, pingPongArray[textureIndices.w][position.x].xyz); textureIndices.xyzw = textureIndices.zwxy; } GroupMemoryBarrierWithGroupSync(); ButterflyPass(ButterflyCount - 1, position.x, textureIndices.x, textureIndices.y, TextureTargetR[texturePos], TextureTargetI[texturePos]); } [numthreads(Length, 1, 1)] void ButterflySLM_ROW(uint3 position : SV_DispatchThreadID) { ButterflySLM(position); } [numthreads(Length, 1, 1)] void ButterflySLM_COL(uint3 position : SV_DispatchThreadID) { ButterflySLM(position); }
Aperture.shader
Shader "Custom/Aperture" { Properties { _MainTex ("Dust Texture", 2D) = "white" {} aperture_resolution ("RenderTarget Resolution", Vector) = (512, 512, 0, 0) aperture_opening ("Opening", float) = 7.0 number_of_blades ("Number Of Blades", float) = 5.0 } 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; } Texture2D _MainTex; SamplerState sampler_linear_clamp; float2 aperture_resolution; float aperture_opening; float number_of_blades; float fade_aperture_edge(float radius, float fade, float signed_distance) { float l = radius; float u = radius + fade; float s = u - l; float c = 1.f - saturate(saturate(signed_distance - l)/s); return smoothstep(0, 1, c); } float smax(float a, float b, float k) { float diff = a - b; float h = saturate(0.5 + 0.5 * diff / k); return b + h * (diff + k * (1.0f - h)); } float4 frag(v2f In) : SV_Target { float2 uv = In.vertex.xy / aperture_resolution; float2 ndc = ((uv - 0.5f) * 2.f); int num_of_blades = int(number_of_blades); float a = (atan2(ndc.x, ndc.y) + aperture_opening)/UNITY_TWO_PI + 3.f/4.f; float o = frac(a * num_of_blades + 0.5); float w1 = lerp(0.010, 0.001f, saturate((num_of_blades - 4)/10.f)); float w2 = lerp(0.025, 0.001f, saturate((num_of_blades - 4)/10.f)); float s0 = sin(o * 2 * UNITY_PI); float s1 = s0 * w1; float s2 = s0 * w2; // fft aperture shape float signed_distance = 0.f; int i; for(i = 0; i < num_of_blades; ++i) { float angle = aperture_opening + (i/float(num_of_blades)) * UNITY_TWO_PI; float2 axis = float2(cos(angle), sin(angle)); signed_distance = max(signed_distance, dot(axis, ndc)); } //signed_distance += s1; float aperture_fft = fade_aperture_edge(0.7, 0.00001, signed_distance); // camera aperture shape signed_distance = 0.f; for(i = 0; i < num_of_blades; i++) { float angle = aperture_opening + (i/float(num_of_blades)) * UNITY_TWO_PI; float2 axis = float2(cos(angle), sin(angle)); signed_distance = smax(signed_distance, dot(axis, ndc), 0.1); } signed_distance += s2; float aperture_mask = fade_aperture_edge(0.7, 0.1, signed_distance); { // Diffraction rings float w = 0.2; float s = signed_distance + 0.05; float n = saturate(saturate(s + w) - (1.f - w)); float x = n/w; float a = x; float b = -x + 1.f; float c = min(a,b) * 2.f; float t = (sin(x * 6.f * UNITY_PI - 1.5f) + 1.f) * 0.5f; float rings = pow(t*c, 1.f); aperture_mask = aperture_mask + rings * 0.125; } float dust_fft = 0.f; { // Dust dust_fft = _MainTex.Sample(sampler_linear_clamp, uv).r; aperture_mask *= saturate(dust_fft + 0.9); } float3 rgb = float3(aperture_fft, dust_fft, aperture_mask); #if 1 rgb.b = 0.0; #endif return float4(rgb, 1); } ENDCG } } }
StarBurst.shader
Shader "Custom/StartBurst" { Properties { _MainTex ("Texture", 2D) = "white" {} starburst_resolution ("RenderTarget Resolution", Vector) = (2048, 2048, 0, 0) } CGINCLUDE #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; } bool Clamped(float2 ndc) { return ndc.x < -0.5 || ndc.x > 0.5 || ndc.y < -0.5 || ndc.y > 0.5; } float3 wl2rgbTannenbaum(float w) { float3 r; if(w < 350.0) r = float3(0.5, 0.0, 1.0); else if((w >= 350.0) && (w < 440.0)) r = float3((440.0 - w) / 90.0, 0.0, 1.0); else if((w >= 440.0) && (w <= 490.0)) r = float3(0.0, (w - 440.0) / 50.0, 1.0); else if((w >= 490.0) && (w < 510.0)) r = float3(0.0, 1.0, (-(w - 510.0)) / 20.0); else if ((w >= 510.0) && (w < 580.0)) r = float3((w - 510.0) / 70.0, 1.0, 0.0); else if((w >= 580.0) && (w < 645.0)) r = float3(1.0, (-(w - 645.0)) / 65.0, 0.0); else r = float3(1.0, 0.0, 0.0); if(w < 350.0) r *= 0.3; else if((w >= 350.0) && (w < 420.0)) r *= 0.3 + (0.7 * ((w - 350.0) / 70.0)); else if((w >= 420.0) && (w <= 700.0)) r *= 1.0; else if((w > 700.0) && (w <= 780.0)) r *= 0.3 + (0.7 * ((780.0 - w) / 80.0)); else r *= 0.3; return r; } float2 Rotate(float2 p, float a) { float x = p.x; float y = p.y; float cosa = cos(a); float sina = sin(a); float x1 = x * cosa - y * sina; float y1 = y * cosa + x * sina; return float2(x1, y1); } SamplerState sampler_linear_repeat; float2 starburst_resolution; ENDCG SubShader { // No culling or depth Cull Off ZWrite Off ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag Texture2D _FFTTex0; Texture2D _FFTTex1; float4 frag(v2f In) : SV_Target { float2 uv = In.vertex.xy / starburst_resolution - 0.5; float d = length(uv) * 2; // -ve violet, +v reds float scale1 = 0.50f; float scale2 = -0.75f; float fft_scale = 0.00001f; float3 result = 0.f; int num_steps = 256; for(int i = 0; i <= num_steps; ++i) { float n = (float)i/(float)num_steps; float2 scaled_uv1 = uv * lerp(1.f + scale1, 1.f, n); float2 scaled_uv2 = uv * lerp(1.f + scale2, 1.f, n); bool clamped1 = Clamped(scaled_uv1); bool clamped2 = Clamped(scaled_uv2); float r1 = _FFTTex0.Sample(sampler_linear_repeat, scaled_uv1).r * !clamped1; float i1 = _FFTTex1.Sample(sampler_linear_repeat, scaled_uv1).r * !clamped1; float r2 = _FFTTex0.Sample(sampler_linear_repeat, scaled_uv2).g * !clamped2; float i2 = _FFTTex1.Sample(sampler_linear_repeat, scaled_uv2).g * !clamped2; float2 p1 = float2(r1, i1); float2 p2 = float2(r2, i2); float starburst = pow(length(p1), 2.f) * fft_scale * lerp(0.0f, 25.f, d); float dust = pow(length(p2), 2.f) * fft_scale * lerp(0.5f, 0.f, d); float lambda = lerp(380.f, 700.f, n); float3 rgb = wl2rgbTannenbaum(lambda); rgb = lerp(1.f, rgb, 0.75f); result += (starburst + dust * rgb * 0.25f); } result /= (float)num_steps; return float4(result, 1); } ENDCG } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag Texture2D _MainTex; float4 frag(v2f In) : SV_Target { float2 uv = In.vertex.xy / starburst_resolution - 0.5f; float3 result = 0.f; uint num_steps = 256; for(uint i = 0; i <= num_steps; ++i) { float n = (float)i/(float)num_steps; float a = n * UNITY_TWO_PI * 2.f; float2 spiral = float2(cos(a), sin(a)) * n * 0.002f; float2 rotated_uv = Rotate(uv + spiral, n * 0.05f); float3 starburst = _MainTex.Sample(sampler_linear_repeat, rotated_uv + 0.5f).rgb * !Clamped(rotated_uv); float lambda = lerp(380.f, 700.f, (i % 80)/80.f); float3 rgb = wl2rgbTannenbaum(lambda); rgb = lerp(rgb, 1.f, 0.5f); result += starburst * rgb; } result /= (float)num_steps; return float4(result, 1); } ENDCG } } }
CombineAperture.shader
Shader "Custom/CombineAperture" { Properties { } SubShader { 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; } Texture2D _ApurtureTex; Texture2D _DustTex; SamplerState sampler_linear_clamp; float4 frag(v2f In) : SV_Target { float aperture = _ApurtureTex.Sample(sampler_linear_clamp, In.uv).r; float dust = _DustTex.Sample(sampler_linear_clamp, In.uv).g; return float4(aperture, dust, 0, 1); } ENDCG } } }
感想
カメラの開口形状の代わりに目の形等の形状を使っても生成されます。