yum/3ner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum/3ner
df08b1f
master
1using System . IO ; 2using System . Collections . Generic ; 3using System . Reflection ; 4using UnityEditor ; 5using UnityEngine ; 6 7 8namespace FourierWater . Editor 9{ 10public class FourierWater : EditorWindow 11{ 12[ Tooltip ( "Size in meters to simulate" )] 13public float sizeM = 256f ; 14 15[ SerializeField ] 16private int bigN = 1024 ; 17 18[ Tooltip ( "Gravitational acceleration (m/s^2)" )] 19private float g = 9.81f ; 20[ Tooltip ( "Surface tension (N/m)" )] 21private float surfaceTension = 0.076f ; 22[ Tooltip ( "Density (kg / m^3)" )] 23private float density = 1000f ; 24[ Tooltip ( "Wind speed (m/s)" )] 25private float U = 30f ; 26[ Tooltip ( "Fetch (m)" )] 27private float F = 100f * 1000f ; 28[ Tooltip ( "Depth (m)" )] 29private float h = 60f ; 30[ Tooltip ( "Peak enhancement (unitless)" )] 31private float gamma = 4f ; 32 33[ SerializeField ] 34private MeshFilter waterMesh ; 35 36// Viscous deep water wave relation. See: 37// https://yummers.dev/wave-dispersion-relations-with-derivatives.html 38private float Omega ( float k ) 39{ 40float k3 = k * k * k ; 41return Mathf . Sqrt ( g * k + surfaceTension * k3 / density ); 42} 43 44private float DOmegaDK ( float k ) 45{ 46float k2 = k * k ; 47float k3 = k2 * k ; 48return ( g + 3 * surfaceTension * k2 / density ) / ( 2 * Mathf . Sqrt ( g * k + surfaceTension * k3 / density )); 49} 50 51// JONSWAP spectrum 52private float SJonswap ( float omega ) { 53// Correction from equation 8 of Barfuss et. al. "The Impact of 54// Offshore Wind Farms on Sea State Demonstrated by Airborne LiDAR 55// Measurements." 56float omega_p = 22.0f * Mathf . Pow (( g * g / ( U * F )), 0.33333f ); 57float sigma = ( omega <= omega_p ) ? 0.07f : 0.09f ; 58float alpha = 0.076f * Mathf . Pow ( U * U / ( F * g ), 0.22f ); 59float r = Mathf . Exp ( - ( omega - omega_p ) * ( omega - omega_p ) / ( 2 * sigma * sigma * omega_p * omega_p )); 60float omega2 = omega * omega ; 61float omega4 = omega2 * omega2 ; 62float omega5 = omega4 * omega ; 63float meow4 = ( omega_p / omega ); 64meow4 = meow4 * meow4 ; 65meow4 = meow4 * meow4 ; 66float peak_enhancement = Mathf . Pow ( gamma , r ); 67return ( alpha * g * g / omega5 ) * Mathf . Exp ( - 1.25f * meow4 ) * peak_enhancement ; 68} 69 70// TMA spectrum 71private float STma ( float omega ) { 72float omega_h = omega * Mathf . Sqrt ( h / g ); 73if ( omega_h <= 1.0f ) { 74return SJonswap ( omega ) * 0.5f * omega_h * omega_h ; 75} else if ( omega_h < 2.0f ) { 76return SJonswap ( omega ) * ( 1.0f - 0.5f * ( 2.0f - omega_h ) * ( 2.0f - omega_h )); 77} else { 78return SJonswap ( omega ); 79} 80} 81 82// Flat directional spreading function 83private float DFlat () 84{ 85return 1.0f / ( 3.14159265f * 2.0f ); 86} 87 88// https://stackoverflow.com/a/218600 89private float RandomGaussian ( System . Random r ) 90{ 91float u1 = ( float ) ( 1.0f - r . NextDouble ()); 92float u2 = ( float ) ( 1.0f - r . NextDouble ()); 93float randStdNormal = Mathf . Sqrt ( - 2.0f * Mathf . Log ( u1 )) * Mathf . Sin ( 2.0f * Mathf . PI * u2 ); 94// Want mean=0 stdev=1 so just return randStdNormal 95return randStdNormal ; 96} 97 98private Texture2D MakePowerSpectrum ( string name , int sizePx , float sizeM ) 99{ 100Texture2D tex = new Texture2D ( sizePx , sizePx , TextureFormat . RGBAHalf , 101mipChain : false , linear : true ) 102{ 103name = name , 104filterMode = FilterMode . Point , 105wrapMode = TextureWrapMode . Clamp , 106}; 107 108System . Random rand = new System . Random (); 109float tau = Mathf . PI * 2.0f ; 110for ( int y = 0 ; y < sizePx ; y ++ ) 111{ 112// y-axis wave number 113float kY = ( tau / sizeM ) * ( y < sizePx / 2 ? y : y - sizePx ); 114for ( int x = 0 ; x < sizePx ; x ++ ) 115{ 116// x-axis wave number 117float kX = ( tau / sizeM ) * ( x < sizePx / 2 ? x : x - sizePx ); 118float k = Mathf . Sqrt ( kX * kX + kY * kY ); 119 120// Per Horvath 2015 eq. 61: 121// S(k_x, k_y) = S(omega, theta) * DOmegaDK(k) / k. 122float omega = Omega ( k ); 123float s = DFlat () * STma ( omega ) * DOmegaDK ( k ) / k ; 124 125float dk = tau / sizeM ; 126float nu = RandomGaussian ( rand ); 127// Horvath eq. 47 128float amp = nu * Mathf . Sqrt ( 2.0f * s * dk * dk ); 129float phase = ( float ) rand . NextDouble () * tau ; 130 131Color res = new Color ( amp * Mathf . Cos ( phase ), amp * Mathf . Sin ( phase ), 0 , 1 ); 132if ( k == 0 ) { 133res = new Color ( 0 , 0 , 0 , 1 ); 134} 135tex . SetPixel ( x , y , res ); 136} 137} 138 139tex . Apply ( updateMipmaps : false ); 140return tex ; 141} 142 143readonly string pipelineName = "Fourier Water" ; 144 145[ MenuItem ( "Tools/yum_food/Fourier Water" )] 146public static void ShowWindow () 147{ 148GetWindow < FourierWater > ( "Fourier Water" ); 149} 150 151private string GetGeneratedAssetsDir () 152{ 153MonoScript script = MonoScript . FromScriptableObject ( this ); 154string top = Path . GetDirectoryName ( Path . GetDirectoryName ( 155AssetDatabase . GetAssetPath ( script ))); 156string genDir = Path . Combine ( top , "Generated" ). Replace ( '\\' , '/' ); 157if ( AssetDatabase . IsValidFolder ( genDir )) 158{ 159AssetDatabase . DeleteAsset ( genDir ); 160} 161Directory . CreateDirectory ( genDir ); 162AssetDatabase . Refresh (); 163return genDir ; 164} 165 166private CustomRenderTexture MakeCRT ( string name , int size , Material mat ) 167{ 168CustomRenderTexture tex = new CustomRenderTexture ( 169size , size , 170RenderTextureFormat . ARGBHalf , 171RenderTextureReadWrite . Linear ) 172{ 173name = name , 174material = mat , 175shaderPass = 0 , 176filterMode = FilterMode . Bilinear , 177wrapMode = TextureWrapMode . Repeat , 178useMipMap = false , 179autoGenerateMips = false , 180doubleBuffered = false , 181initializationMode = CustomRenderTextureUpdateMode . OnDemand , 182initializationSource = CustomRenderTextureInitializationSource . TextureAndColor , 183initializationColor = Color . black , 184updateMode = CustomRenderTextureUpdateMode . OnDemand , 185updatePeriod = 0f 186}; 187 188tex . Create (); 189return tex ; 190} 191 192private static int [] PlanRadices ( int size ) 193{ 194if ( size < 1 || ( size & ( size - 1 )) != 0 ) 195{ 196throw new System . ArgumentException ( "FFT size must be a power of two." ); 197} 198var stages = new List < int > (); 199while ( size > 1 ) { 200int radix = size % 8 == 0 ? 8 : size % 4 == 0 ? 4 : 2 ; 201stages . Add ( radix ); 202size /= radix ; 203} 204return stages . ToArray (); 205} 206 207private void OnGUI () 208{ 209bigN = EditorGUILayout . IntField ( "FFT resolution" , bigN ); 210sizeM = EditorGUILayout . FloatField ( "Patch size (meters)" , sizeM ); 211U = EditorGUILayout . FloatField ( "Wind speed (m/s)" , U ); 212F = EditorGUILayout . FloatField ( "Fetch (meters)" , F ); 213h = EditorGUILayout . FloatField ( "Depth (meters)" , h ); 214gamma = EditorGUILayout . FloatField ( "Peak enhancement" , gamma ); 215 216waterMesh = ( MeshFilter ) EditorGUILayout . ObjectField ( 217"Water mesh" , waterMesh , typeof ( MeshFilter ), true ); 218 219if ( ! GUILayout . Button ( "Generate" )) 220{ 221return ; 222} 223 224if ( bigN < 1 || bigN > 1024 || ( bigN & ( bigN - 1 )) != 0 ) 225{ 226Debug . LogError ( "FFT resolution must be a power of two from 1 to 1024." ); 227return ; 228} 229 230if ( waterMesh == null || waterMesh . sharedMesh == null ) 231{ 232Debug . LogError ( "Assign a water mesh before generating." ); 233return ; 234} 235Renderer waterRenderer = waterMesh . GetComponent < Renderer > (); 236if ( waterRenderer == null || waterRenderer . sharedMaterial == null ) 237{ 238Debug . LogError ( "The water mesh needs a renderer and material." ); 239return ; 240} 241Material waterMaterial = waterRenderer . sharedMaterial ; 242float meshSize = waterMesh . sharedMesh . bounds . size . x ; 243if ( sizeM <= 0 || meshSize <= 0 ) 244{ 245Debug . LogError ( "Patch size and mesh size must be positive." ); 246return ; 247} 248 249// Delete old shit 250GameObject oldPipeline = GameObject . Find ( pipelineName ); 251if ( oldPipeline != null ) 252{ 253Undo . DestroyObjectImmediate ( oldPipeline ); 254} 255string genDir = GetGeneratedAssetsDir (); 256 257GameObject parent = new GameObject ( pipelineName ); 258 259Shader shader = Shader . Find ( "yum_food/fourier_water_compute" ); 260Shader displayShader = Shader . Find ( "yum_food/fourier_water_display" ); 261 262Texture spectrum = MakePowerSpectrum ( "spectrum" , bigN , sizeM ); 263string spectrumPath = Path . Combine ( genDir , "spectrum.asset" ); 264AssetDatabase . CreateAsset ( spectrum , spectrumPath ); 265 266string [] outputProperties = { 267"_Fourier_Water_Height" , 268"_Fourier_Water_Normals" , 269"_Fourier_Water_Displacement" , 270"_Fourier_Water_Displacement_Derivatives" 271}; 272string [] inputKeywords = { 273"_TIME_EVOLVE" , "_DERIVATIVE" , "_DISPLACEMENT" , "_DISPLACEMENT_DERIVATIVE" 274}; 275int n_pipelines = outputProperties . Length ; 276var outputs = new CustomRenderTexture [ n_pipelines ]; 277int [] radices = PlanRadices ( bigN ); 278for ( int which_pipeline = 0 ; which_pipeline < n_pipelines ; which_pipeline ++ ) { 279Texture input = spectrum ; 280int n_stages = radices . Length ; 281for ( int axis = 0 ; axis < 2 ; axis ++ ) 282{ 283string axisStr = axis == 0 ? "x" : "y" ; 284int stageN = 1 ; 285int stride = bigN ; 286for ( int i = ( axis == 0 ? - 1 : 0 ); i < n_stages ; i ++ ) 287{ 288if ( i > - 1 ) { 289stride /= radices [ i ]; 290stageN *= radices [ i ]; 291} 292 293string stageName = $"pipeline_ { which_pipeline } _axis_ { axisStr } _stage_ { i } " ; 294Material mat = new Material ( shader ); 295mat . EnableKeyword ( $"_FFT_SIZE_ { bigN } " ); 296 297GameObject quad = GameObject . CreatePrimitive ( PrimitiveType . Quad ); 298quad . transform . SetParent ( parent . transform ); 299quad . transform . localPosition = new Vector3 ( i , 2 * ( n_pipelines - which_pipeline ) - axis , 0 ); 300 301if ( i == - 1 ) { 302mat . EnableKeyword ( inputKeywords [ which_pipeline ]); 303} else if ( radices [ i ] == 8 ) { 304mat . EnableKeyword ( "_RADIX_8" ); 305} else if ( radices [ i ] == 4 ) { 306mat . EnableKeyword ( "_RADIX_4" ); 307} 308mat . SetTexture ( "_Signal" , input ); 309mat . SetInt ( "_Stage" , i ); 310mat . SetInt ( "_Big_N" , stageN ); 311mat . SetFloat ( "_Size_M" , sizeM ); 312mat . SetInt ( "_Stride" , stride ); 313mat . SetInt ( "_Direction" , axis ); 314 315string matPath = Path . Combine ( genDir , 316$" { stageName } .mat" ). Replace ( '\\' , '/' ); 317AssetDatabase . CreateAsset ( mat , matPath ); 318 319CustomRenderTexture crt = MakeCRT ( stageName , bigN , mat ); 320string crtPath = Path . Combine ( genDir , 321$" { stageName } .asset" ). Replace ( '\\' , '/' ); 322AssetDatabase . CreateAsset ( crt , crtPath ); 323 324Material displayMat = new Material ( displayShader ); 325displayMat . SetTexture ( "_MainTex" , crt ); 326displayMat . SetInt ( "_Stage" , i ); 327displayMat . SetInt ( "_Num_Stages" , Mathf . Max ( 1 , n_stages )); 328displayMat . SetInt ( "_Direction" , axis ); 329string displayMatPath = Path . Combine ( genDir , 330$" { stageName } _display.mat" ). Replace ( '\\' , '/' ); 331AssetDatabase . CreateAsset ( displayMat , displayMatPath ); 332quad . GetComponent < Renderer > (). sharedMaterial = displayMat ; 333 334var updater = quad . AddComponent < ShaderFes . CustomRenderTextureUpdater > (); 335var serializedUpdater = new SerializedObject ( updater ); 336serializedUpdater . FindProperty ( "_targetTexture" ). objectReferenceValue = crt ; 337serializedUpdater . FindProperty ( "_updateCount" ). intValue = 1 ; 338serializedUpdater . FindProperty ( "_updatePeriod" ). floatValue = 0 ; 339serializedUpdater . ApplyModifiedPropertiesWithoutUndo (); 340 341input = crt ; 342} 343} 344outputs [ which_pipeline ] = ( CustomRenderTexture ) input ; 345} 346 347for ( int pipeline = 0 ; pipeline < n_pipelines ; pipeline ++ ) { 348waterMaterial . SetTexture ( outputProperties [ pipeline ], outputs [ pipeline ]); 349} 350waterMaterial . SetFloat ( "_Fourier_Water_Patch_Size" , sizeM ); 351waterMaterial . SetFloat ( "_Fourier_Water_Mesh_Size" , meshSize ); 352EditorUtility . SetDirty ( waterMaterial ); 353AssetDatabase . SaveAssets (); 354} 355} 356}