├── .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 | 
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 |
--------------------------------------------------------------------------------