├── .gitignore ├── Captures └── suibokuga.gif ├── LICENSE ├── README.md └── Suibokuga └── Assets ├── Scenes.meta ├── Scenes ├── Test.unity └── Test.unity.meta ├── Scripts.meta ├── Scripts ├── FboPingPong.cs ├── FboPingPong.cs.meta ├── Suibokuga.cs └── Suibokuga.cs.meta ├── Shaders.meta ├── Shaders ├── Suibokuga.shader └── Suibokuga.shader.meta ├── Textures.meta └── Textures ├── paper.png └── paper.png.meta /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Pp]rojectSettings/ 6 | 7 | # Autogenerated VS/MD solution and project files 8 | *.csproj 9 | *.unityproj 10 | *.sln 11 | *.suo 12 | *.user 13 | *.userprefs 14 | *.pidb 15 | *.booproj 16 | 17 | # Unity3D Generated File On Crash Reports 18 | sysinfo.txt 19 | -------------------------------------------------------------------------------- /Captures/suibokuga.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattatz/unity-cellular-automaton-based-ink-simulation/0976b8240d18f7fd3f1d79cf32dfdcd35bbea90b/Captures/suibokuga.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 mattatz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | unity-cellular-automaton-based-ink-simulation 2 | ===================== 3 | 4 | Cellular automaton based ink simulation for Unity. 5 | 6 | ![suibokuga](https://raw.githubusercontent.com/mattatz/unity-cellular-automaton-based-ink-simulation/master/Captures/suibokuga.gif) 7 | 8 | ## Sources 9 | 10 | - Simple cellular automaton-based simulation of ink behaviour and its application to Suibokuga-like 3D rendering of trees - http://kucg.korea.ac.kr/seminar/2004/src/PA-04-09.pdf 11 | 12 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a01f174b4d4e34ec38a41ae0f9c762c6 3 | folderAsset: yes 4 | timeCreated: 1451703718 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Scenes/Test.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattatz/unity-cellular-automaton-based-ink-simulation/0976b8240d18f7fd3f1d79cf32dfdcd35bbea90b/Suibokuga/Assets/Scenes/Test.unity -------------------------------------------------------------------------------- /Suibokuga/Assets/Scenes/Test.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9d1f3213229c445a99cfb07afc6e47e6 3 | timeCreated: 1451703757 4 | licenseType: Pro 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5720d464261b240f79c3be1f14191bf4 3 | folderAsset: yes 4 | timeCreated: 1451703722 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Scripts/FboPingPong.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Utils { 6 | public class FboPingPong { 7 | 8 | private int _readTex = 0; 9 | private int _writeTex = 1; 10 | private RenderTexture[] _buffer; 11 | 12 | /// 13 | /// Init the specified width_ and height_. 14 | /// 15 | /// Width_. 16 | /// Height_. 17 | public FboPingPong (int width_, int height_, FilterMode filterMode = FilterMode.Point, TextureWrapMode wrapMode = TextureWrapMode.Repeat){ 18 | 19 | _readTex = 0; 20 | _writeTex = 1; 21 | 22 | _buffer = new RenderTexture [2]; 23 | 24 | for (int i = 0; i < 2; i++){ 25 | _buffer [i] = new RenderTexture (width_, height_, 0, RenderTextureFormat.ARGBFloat); 26 | _buffer [i].hideFlags = HideFlags.DontSave; 27 | _buffer [i].filterMode = filterMode; 28 | _buffer [i].wrapMode = wrapMode; 29 | _buffer [i].Create (); 30 | } 31 | 32 | Clear (); 33 | } 34 | 35 | /// 36 | /// Swap buffers. 37 | /// 38 | public void Swap (){ 39 | //RenderTexture temp = _buffer[0]; 40 | //_buffer [0] = _buffer [1]; 41 | //_buffer [1] = temp; 42 | int t = _readTex; 43 | _readTex = _writeTex; 44 | _writeTex = t; 45 | } 46 | 47 | /// 48 | /// Clear buffers. 49 | /// 50 | public void Clear (){ 51 | for (int i = 0; i < _buffer.Length; i++){ 52 | RenderTexture.active = _buffer [i]; 53 | GL.Clear (false, true, Color.black); 54 | RenderTexture.active = null; 55 | } 56 | } 57 | 58 | /// 59 | /// Delete buffers. 60 | /// 61 | public void Delete (){ 62 | if (_buffer != null) { 63 | for (int i = 0; i < _buffer.Length; i++){ 64 | _buffer[i].Release (); 65 | _buffer[i].DiscardContents (); 66 | _buffer[i] = null; 67 | } 68 | } 69 | } 70 | 71 | /// 72 | /// Gets the read tex. 73 | /// 74 | /// The read tex. 75 | public RenderTexture GetReadTex (){ 76 | return _buffer [_readTex]; 77 | } 78 | 79 | /// 80 | /// Gets the write tex. 81 | /// 82 | /// The write tex. 83 | public RenderTexture GetWriteTex (){ 84 | return _buffer [_writeTex]; 85 | } 86 | 87 | } 88 | 89 | } 90 | 91 | 92 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Scripts/FboPingPong.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c502e519798dc4e69be083cfd4a32ad8 3 | timeCreated: 1451705085 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Scripts/Suibokuga.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | using Utils; 5 | 6 | [RequireComponent (typeof (Camera) ) ] 7 | public class Suibokuga : MonoBehaviour { 8 | 9 | [SerializeField] Shader shader; 10 | Material mat; 11 | 12 | [SerializeField] Texture2D paper; 13 | [SerializeField, Range(0f, 1f)] float alpha = 1.0f; 14 | [SerializeField, Range(0f, 0.005f)] float evaporation = 0.0001f; 15 | 16 | [SerializeField, Range(0, 10)] int iterations = 4; 17 | [SerializeField] int size = 1024; 18 | 19 | FboPingPong fpp; 20 | 21 | bool dragging = false; 22 | Vector2 previous; 23 | 24 | void Start () { 25 | fpp = new FboPingPong(size, size); 26 | mat = new Material(shader); 27 | 28 | mat.SetTexture("_PaperTex", paper); 29 | 30 | Init(); 31 | } 32 | 33 | void Update () { 34 | 35 | Vector3 mousePos = Input.mousePosition; 36 | Vector2 current = new Vector2(mousePos.x / Screen.width, mousePos.y / Screen.height); 37 | mat.SetVector("_Prev", previous); 38 | 39 | if(dragging) { 40 | mat.SetVector("_Brush", new Vector3(current.x, current.y, 0.015f)); 41 | } else { 42 | mat.SetVector("_Brush", new Vector3(0, 0, 0)); 43 | } 44 | 45 | if(Input.GetMouseButtonDown(0)) { 46 | dragging = true; 47 | } else if(Input.GetMouseButtonUp(0)) { 48 | dragging = false; 49 | } 50 | 51 | previous = current; 52 | 53 | mat.SetFloat("_Alpha", alpha); 54 | mat.SetFloat("_Evaporation", evaporation); 55 | 56 | for(int i = 0; i < iterations; i++) { 57 | Graphics.Blit(fpp.GetReadTex(), fpp.GetWriteTex(), mat, 1); // update 58 | fpp.Swap(); 59 | } 60 | } 61 | 62 | void Init () { 63 | Graphics.Blit(fpp.GetReadTex(), fpp.GetWriteTex(), mat, 0); // init 64 | fpp.Swap(); 65 | } 66 | 67 | void OnRenderImage (RenderTexture src, RenderTexture dst) { 68 | Graphics.Blit(fpp.GetReadTex(), dst, mat, 2); 69 | } 70 | 71 | void OnGUI () { 72 | const float offset = 10; 73 | if(GUI.Button(new Rect(offset, offset, 100, 30), "Reset")) { 74 | Init(); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Scripts/Suibokuga.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9d12623b70d41461c8815fc882ae6772 3 | timeCreated: 1451703738 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Shaders.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b02113b53a97f43e188a712a6cd6270f 3 | folderAsset: yes 4 | timeCreated: 1451703725 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Shaders/Suibokuga.shader: -------------------------------------------------------------------------------- 1 | Shader "Suibokuga/Suibokuga" { 2 | 3 | Properties { 4 | _MainTex ("Water Texture", 2D) = "white" {} 5 | _Alpha ("Transfer/Diffusion coefficient for water particles", Range(0.01, 1.5)) = 1.0 6 | _Evaporation ("a unit quantity of water for evaporation", Range(0.0001, 0.005)) = 0.00015 7 | 8 | _PaperTex ("Paper Texture", 2D) = "white" {} 9 | 10 | _Brush ("brush", Vector) = (-1, -1, -1, -1) 11 | _Prev ("previous brush position", Vector) = (-1, -1, -1, -1) 12 | } 13 | 14 | CGINCLUDE 15 | 16 | #include "UnityCG.cginc" 17 | #pragma target 3.0 18 | 19 | /* 20 | * r : water particles 21 | * b : capacities of water 22 | * a : the heights of the bottoms 23 | */ 24 | sampler2D _MainTex; 25 | float4 _MainTex_TexelSize; 26 | 27 | float _Alpha; 28 | float _Evaporation; 29 | 30 | sampler2D _PaperTex; 31 | 32 | float2 _Prev; 33 | float3 _Brush; // x,y : position, z : size 34 | 35 | struct appdata { 36 | float4 vertex : POSITION; 37 | float2 uv : TEXCOORD0; 38 | }; 39 | 40 | struct v2f { 41 | float4 vertex : POSITION; 42 | float2 uv : TEXCOORD0; 43 | }; 44 | 45 | v2f vert (appdata IN) { 46 | v2f OUT; 47 | OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex); 48 | OUT.uv = IN.uv; 49 | return OUT; 50 | } 51 | 52 | void sample (float2 uv, out float4 o, out float4 l, out float4 t, out float4 r, out float4 b) { 53 | float2 texel = _MainTex_TexelSize.xy; 54 | o = tex2D(_MainTex, uv); 55 | l = tex2D(_MainTex, uv + float2(-texel.x, 0)); 56 | t = tex2D(_MainTex, uv + float2( 0, -texel.y)); 57 | r = tex2D(_MainTex, uv + float2( texel.x, 0)); 58 | b = tex2D(_MainTex, uv + float2( 0, texel.y)); 59 | } 60 | 61 | float waterDelta (float4 k, float4 o) { 62 | float ld = (k.w + k.x) - (o.w + o.x); // level difference 63 | float transfer = (k.w + k.x) - max(o.w, k.w + k.z); // transferable water particles 64 | return max( 65 | 0.0, 66 | 0.25 * _Alpha * min(ld, transfer) 67 | ); 68 | } 69 | 70 | float waterFlow (float2 uv) { 71 | float4 o, l, t, r, b; 72 | sample(uv, o, l, t, r, b); 73 | 74 | float nw = o.r; 75 | nw += (waterDelta(l, o) - waterDelta(o, l)); 76 | nw += (waterDelta(t, o) - waterDelta(o, t)); 77 | nw += (waterDelta(r, o) - waterDelta(o, r)); 78 | nw += (waterDelta(b, o) - waterDelta(o, b)); 79 | return max(nw, 0); 80 | } 81 | 82 | float evaporation (float wo) { 83 | return max(wo - _Evaporation, 0.0); 84 | } 85 | 86 | float brush (float2 uv) { 87 | const int count = 10; 88 | 89 | float2 dir = _Brush.xy - _Prev.xy; 90 | float l = length(dir); 91 | if(l <= 0) { 92 | float d = length(uv - _Brush.xy); 93 | return smoothstep(0.0, _Brush.z, _Brush.z - d); 94 | } 95 | 96 | float ld = l / count; 97 | float2 norm = normalize(dir); 98 | float md = 100; 99 | for(int i = 0; i < count; i++) { 100 | float2 p = _Prev.xy + norm * ld * i; 101 | float d = length(uv - p); 102 | if(d < md) { 103 | md = d; 104 | } 105 | } 106 | return smoothstep(0.0, _Brush.z, _Brush.z - md); 107 | 108 | // float d = length(uv - _Brush.xy); 109 | // return smoothstep(0.0, _Brush.z, _Brush.z - d); 110 | } 111 | 112 | ENDCG 113 | 114 | SubShader { 115 | Cull Off ZWrite Off ZTest Always 116 | 117 | Pass { 118 | CGPROGRAM 119 | #pragma vertex vert 120 | #pragma fragment init 121 | 122 | float4 init (v2f IN) : SV_Target { 123 | float4 paper = tex2D(_PaperTex, IN.uv); 124 | return float4( 125 | 0, 126 | 0, 127 | paper.r, 128 | paper.r 129 | ); 130 | } 131 | 132 | ENDCG 133 | } 134 | 135 | Pass { 136 | CGPROGRAM 137 | #pragma vertex vert 138 | #pragma fragment waterUpdate 139 | 140 | float4 waterUpdate (v2f IN) : SV_Target { 141 | float4 col = tex2D(_MainTex, IN.uv); 142 | col.x = evaporation(waterFlow(IN.uv)); 143 | 144 | float dw = brush(IN.uv); 145 | // if(dw > 0) { 146 | col.x = min(col.x + brush(IN.uv), 1.0); 147 | // } 148 | 149 | return col; 150 | } 151 | 152 | ENDCG 153 | } 154 | 155 | Pass { 156 | CGPROGRAM 157 | #pragma vertex vert 158 | #pragma fragment visualize 159 | 160 | float4 visualize (v2f IN) : SV_Target { 161 | float4 col = tex2D(_MainTex, IN.uv); 162 | return float4(1.0 - col.xxx, 1.0); 163 | } 164 | 165 | ENDCG 166 | } 167 | 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Shaders/Suibokuga.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eacee52b0310a4d1b91369a2c0fa96c6 3 | timeCreated: 1451704102 4 | licenseType: Pro 5 | ShaderImporter: 6 | defaultTextures: [] 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Textures.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9fd001677c1984764971860ea5467f27 3 | folderAsset: yes 4 | timeCreated: 1451710557 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Suibokuga/Assets/Textures/paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattatz/unity-cellular-automaton-based-ink-simulation/0976b8240d18f7fd3f1d79cf32dfdcd35bbea90b/Suibokuga/Assets/Textures/paper.png -------------------------------------------------------------------------------- /Suibokuga/Assets/Textures/paper.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b8483e7951ca44d5b9af187b239b1b1f 3 | timeCreated: 1451710625 4 | licenseType: Pro 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 2 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 1 11 | linearTexture: 0 12 | correctGamma: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: .25 21 | normalMapFilter: 0 22 | isReadable: 0 23 | grayScaleToAlpha: 0 24 | generateCubemap: 0 25 | cubemapConvolution: 0 26 | cubemapConvolutionSteps: 8 27 | cubemapConvolutionExponent: 1.5 28 | seamlessCubemap: 0 29 | textureFormat: -1 30 | maxTextureSize: 2048 31 | textureSettings: 32 | filterMode: -1 33 | aniso: -1 34 | mipBias: -1 35 | wrapMode: -1 36 | nPOTScale: 1 37 | lightmap: 0 38 | rGBM: 0 39 | compressionQuality: 50 40 | spriteMode: 0 41 | spriteExtrude: 1 42 | spriteMeshType: 1 43 | alignment: 0 44 | spritePivot: {x: .5, y: .5} 45 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 46 | spritePixelsToUnits: 100 47 | alphaIsTransparency: 0 48 | textureType: -1 49 | buildTargetSettings: [] 50 | spriteSheet: 51 | sprites: [] 52 | spritePackingTag: 53 | userData: 54 | assetBundleName: 55 | assetBundleVariant: 56 | --------------------------------------------------------------------------------