Shader "CrispyPin/Lenia" { Properties { _LastFrame ("Texture", 2D) = "white" {} _Radius ("Radius", Range(1,100)) = 10 _GrowtCenter ("Growth fn center (mu)", Range(0, 1)) = 0.2 _GrowthWidth ("Growth fn width (sigma / std deviation)", Range(0, 1)) = 0.07 _KSharpness ("Kernel sharpness", Range(1, 100)) = 2 _KOffset ("Kernel offset", Range(0, 1)) = 0.2 _Speed ("Speed factor", Range(0.001, 1)) = 0.1 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 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; }; sampler2D _LastFrame; int _Radius; float _GrowtCenter; float _GrowthWidth; float _KSharpness; float _KOffset; float _Speed; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } float kernel(float p) { float r = p / _Radius; // -- example from the lenia paper // if (r >= 1) return 0; // const float alpha = 4; // return exp(alpha - alpha/(4.0 * r * (1.0 - r))); // -- forgor // return exp(-((r-0.5)*(r-0.5))*25); // -- circle :) // return r < 1 && r > 0.2; // -- adjustable hard circle // float a = _KOffset+_KSharpness; // float b =_KOffset -_KSharpness; // return r < a && r > b; // -- normal return exp(-((r - _KOffset)*(r - _KOffset)) * _KSharpness); } float activation(float neighbors) { // return old_state * (neighbors > 2 && neighbors < 5) + // ((1 - old_state) * neighbors == 3); // const float sharpness = 1000.0; // float x = neighbors - _GrowtCenter; // return exp(-(x*x) * _GrowthWidth) * 2.0 - 1.0; float mu = _GrowtCenter; float sigma = _GrowthWidth; float u = neighbors; // float u = neighbors - _GrowtCenter; return exp(-((u-mu) * (u-mu)) / (2 * sigma * sigma)) * 2.0 - 1.0; } half value(float2 center, float x, float y) { return tex2D(_LastFrame, center + float2(x, y)).r; } fixed4 frag (v2f i) : SV_Target { const float resolution = 256.0; const float d = 1.0 / resolution; float total_max = 0.0; // TODO replace with const? float total = 0.0; for (int x = -_Radius; x <= _Radius; x++) { for (int y = -_Radius; y <= _Radius; y++) { float dist = sqrt(x*x+y*y); float kval = kernel(dist); total_max += kval; total += value(i.uv, x*d, y*d) * kval; } } float old_state = value(i.uv, 0.0, 0.0) ; float count = total / total_max; float state = activation(count) * _Speed + old_state; state = clamp(state, 0, 1); // float2 p = (i.uv - 0.5) * resolution; // float k = kernel(length(p)) * (max(abs(p.x), abs(p.y)) <= _Radius); // kernel visualisation: real size // // float k = kernel(length(i.uv - 0.5) * _Radius * 2); // kernel visualisation: fill square // float a = activation(i.uv.x); // float4 col = float4(state, k, a, 1); // float4 col = float4(state, state * 0.5, 0, 1); float4 col = float4(state, i.uv.x * state, i.uv.y * state, 1); // float4 col = state; // col.a = 1; return col; } ENDCG } } }