├── .gitignore ├── README.md ├── README.zh-CN.md ├── images ├── gif_images │ ├── 1gear.gif │ ├── 2taichi_rot.gif │ ├── 3star.gif │ ├── 4star_multi.gif │ ├── 5star2tree.gif │ ├── 6tree2wave.gif │ ├── 7waterdrop2heart.gif │ ├── 8wave2flower.gif │ └── 9circle2mascot.gif ├── img00.png ├── img01.png ├── img02.png ├── img03.png ├── img04.png └── img05.png ├── main.py ├── music ├── Dynamic-good-electronic-music.mp3 ├── dubstep-drum-solo-140bpm-by-prettysleepy-art-15454.mp3 └── electronic-drum-loop-by-prettysleepy-art-12918.mp3 ├── requirements.txt └── ticore.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | **/*.pyc 3 | some.py 4 | 5 | **/.DS_Store 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # whycode 2 | `whycoding?` It is the work of the "Colorful Black" team in the TaiChi Hackathon 2022. It mainly uses the parallel programming language Taichi to achieve some dynamic transformation visual effects and assist in music accompaniment. The project mainly describes the scene with SDF (Signed Distance Field), which is implicitly expressed. It uses Taichi's efficient parallelism to perform multiple sampling calculations on the GPU. The return value of the SDF is used for transformation processing of various shapes. 3 | 4 | [中文](README.zh-CN.md), [English](README.md) 5 | 6 | 7 | ## Install and Use 8 | ```bash 9 | # install ffplay [ffmpeg] 10 | # ubuntu 11 | sudo apt-get install ffmpeg 12 | # mac 13 | brew insatll ffmpeg 14 | 15 | # clone code 16 | git clone https://github.com/ElonKou/whycode 17 | 18 | # install environment. 19 | cd whycode 20 | pip install -r requirements.txt 21 | 22 | # run 23 | python3 main.py 24 | ``` 25 | 26 | ## Results 27 | ![](images/img05.png) 28 | 29 | ![gear](images/gif_images/1gear.gif) 30 | ![taichi](images/gif_images/2taichi_rot.gif) 31 | ![star](images/gif_images/3star.gif) 32 | ![multi-stars](images/gif_images/4star_multi.gif) 33 | ![star2tree](images/gif_images/5star2tree.gif) 34 | ![tree2wave](images/gif_images/6tree2wave.gif) 35 | ![drop2geart](images/gif_images/7waterdrop2heart.gif) 36 | ![wave2folwer](images/gif_images/8wave2flower.gif) 37 | ![circle2mascot](images/gif_images/9circle2mascot.gif) 38 | 39 | ## Project introduction 🎎 40 | **Team name**: colorful black 41 | **project name**: why coding? 42 | **Entry direction**: application direction 43 | **Project category**: interactive art 44 | 45 | ## Project details 46 | **Project introduction**: Interactive and cool program animation clips can be used to render animation in a similar way using shader toy. 47 | **Expected effect**: to achieve the effect of continuous switching of multiple shader SDF scenes over time, with music as accompaniment. 48 | 49 | ## Technical solution: 50 | overall technology: module division is realized by fragment shader rendering: 51 | - **shader module ✔**: Use taichi to implement clip shaders 52 | - **Scene module ✔**: Maintain the information of various scenarios, including time, and call different shaders in different time periods 53 | - **Basic component module ✔**: The geometry of SDF is used to represent physics 54 | - **Special effect component module ✔**: Realize different transformation functions for the control of shader special effects 55 | - **Scene transition special effect components ✔**: Realizing scene switching special effects 56 | - **music control component ✔**: Realize special effect music 57 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # whycode 2 | `whycoding?`是2022太极黑客马拉松中`五彩斑斓的黑` 团队的作品,主要是使用并行编程语言太极来实现一些动态变换的视觉特效,并辅助音乐伴奏。项目主要是使用隐式表达的SDF(Signed Distance Field)描述场景,利用taichi高效的并行能力在GPU上执行多重采样计算,有符号的距离函数的返回值用于各种形状的变换处理。 3 | 4 | [中文](README.zh-CN.md), [English](README.md) 5 | 6 | ## 安装和使用 7 | ```bash 8 | # 安装 ffplay [安装ffmpeg即可] 9 | # ubuntu 10 | sudo apt-get install ffmpeg 11 | # mac 12 | brew insatll ffmpeg 13 | 14 | # 下载代码 15 | git clone https://github.com/ElonKou/whycode 16 | 17 | # 按住那个python库 18 | cd whycode 19 | pip install -r requirements.txt 20 | 21 | # 运行 22 | python3 main.py 23 | ``` 24 | 25 | ## 效果展示图 26 | ![赛博飞鼠](images/img05.png) 27 | 28 | 动态效果 29 | ![齿轮](images/gif_images/1gear.gif) 30 | ![太极图案](images/gif_images/2taichi_rot.gif) 31 | ![星星](images/gif_images/3star.gif) 32 | ![多层星星](images/gif_images/4star_multi.gif) 33 | ![星星变树](images/gif_images/5star2tree.gif) 34 | ![树变波浪](images/gif_images/6tree2wave.gif) 35 | ![水滴变心](images/gif_images/7waterdrop2heart.gif) 36 | ![波浪变花朵](images/gif_images/8wave2flower.gif) 37 | ![圆圈变笋笋](images/gif_images/9circle2mascot.gif) 38 | 39 | ## 项目介绍🎎 40 | **团队名**:五彩斑斓的黑 41 | **项目名**:why coding? 42 | **参赛方向**:应用方向 43 | **项目类别**:互动艺术 44 | 45 | ## 项目细节 46 | **项目简介**: 可以交互的、酷炫的程序动画短片,利用使用shader toy类似的方式渲染动画。 47 | **期望效果**: 实现多个shader SDF场景的随着时间不断的切换的效果,并且有音乐作为伴奏。 48 | 49 | ## 技术方案: 50 | **整体技术**:采用fragment shader渲染的方式实现 51 | **模块划分**: 52 | - **shader模块✔︎**:使用taichi实现片段着色器。 53 | - **场景模块✔︎**:维护各种场景的信息,包含时间等,在不同的时间段调用不同shader。 54 | - **基本组件模块✔︎**:使用SDF的几何形状表示物理。 55 | - **特效组件模块✔︎**:实现不同的变换函数用于shader特效的控制。 56 | - **场景转场特效组件✔︎**:实现场景的切换特效 57 | - **音乐控制组件✔︎**:实现特效音乐 58 | 59 | ## 技术风险: 60 | **风险一**:内容太多做不完(解决方案:使用参数化的设计场景)`(果然是风险,已经遇到这个风险😄)` 61 | **风险二**:难度太高(解决方案:使用简单几何形状去表达复杂的物体) -------------------------------------------------------------------------------- /images/gif_images/1gear.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/gif_images/1gear.gif -------------------------------------------------------------------------------- /images/gif_images/2taichi_rot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/gif_images/2taichi_rot.gif -------------------------------------------------------------------------------- /images/gif_images/3star.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/gif_images/3star.gif -------------------------------------------------------------------------------- /images/gif_images/4star_multi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/gif_images/4star_multi.gif -------------------------------------------------------------------------------- /images/gif_images/5star2tree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/gif_images/5star2tree.gif -------------------------------------------------------------------------------- /images/gif_images/6tree2wave.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/gif_images/6tree2wave.gif -------------------------------------------------------------------------------- /images/gif_images/7waterdrop2heart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/gif_images/7waterdrop2heart.gif -------------------------------------------------------------------------------- /images/gif_images/8wave2flower.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/gif_images/8wave2flower.gif -------------------------------------------------------------------------------- /images/gif_images/9circle2mascot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/gif_images/9circle2mascot.gif -------------------------------------------------------------------------------- /images/img00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/img00.png -------------------------------------------------------------------------------- /images/img01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/img01.png -------------------------------------------------------------------------------- /images/img02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/img02.png -------------------------------------------------------------------------------- /images/img03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/img03.png -------------------------------------------------------------------------------- /images/img04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/img04.png -------------------------------------------------------------------------------- /images/img05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/images/img05.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # !/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2022.12.04 4 | # @Author : elonkou 5 | # @File : main.py 6 | # Contains all Scenes / Movie for render. 7 | 8 | from ticore import * 9 | import _thread 10 | 11 | 12 | @ti.data_oriented 13 | class Scene(): 14 | def __init__(self) -> None: 15 | self.scene_name = "default" 16 | self.dura = 15.0 # [seconds] 17 | self.st = 0.0 # start time 18 | self.ed = 0.0 # end time 19 | self.multi_samples = True # if use multi-sample, render will sample [2x2] points per pixel. 20 | self.draw_l = 0 21 | self.draw_r = res[0] 22 | self.draw_b = 0 23 | self.draw_t = res[1] 24 | 25 | def LoadScene(self): 26 | print("Load Scene") 27 | 28 | def Render(self): 29 | self.RenderCore(t - self.st, self.draw_l, self.draw_r, self.draw_b, self.draw_t) 30 | 31 | @ti.kernel 32 | def RenderCore(self, t: float, left: int, right: int, bot: int, top: int): 33 | for I in ti.grouped(ti.ndrange((left, right), (bot, top))): 34 | width = right - left 35 | height = top - bot 36 | # uv = ti.Vector([(I[0]*2.0 - res[0]) / res[0], (I[1]*2.0 - res[1]) / res[1]]) # [-1.0, -1.0] -> [1.0, 1.0] 37 | # uv = ti.Vector([(I[0]*2.0 - res[0]) / res[1], (I[1]*2.0 - res[1]) / res[1]]) # [-some, -1.0] -> [some, 1.0] 38 | uv = ti.Vector([(I[0] - width/2.0) / height, (I[1] - height/2.0) / height]) # [-some/2, -0.5] -> [some/2, 0.5] 39 | 40 | samples = 2 41 | 42 | if self.multi_samples: 43 | total_col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 44 | offset = ti.Vector([(2.0 * width / height) / height, 2.0 / height]) / 2 45 | # offset = ti.Vector([(1.0 * res[0] / res[1]) / res[1], 1.0 / res[1]]) / 2 46 | for i in range(samples): # heigth 47 | for j in range(samples): # width 48 | off = ti.Vector([offset[0] * j, offset[1] * i]) 49 | dist = self.GetSDF(uv + off, I, t) 50 | col = self.GetColor(uv + off, I, dist, t) 51 | if col.norm() > 0.0: 52 | col = combine(pixels[I], col) 53 | total_col = total_col + col 54 | total_col = total_col / samples / samples 55 | if total_col.norm() > 0.0: 56 | pixels[I] = total_col 57 | else: 58 | dist = self.GetSDF(uv, I, t) 59 | col = self.GetColor(uv, I, dist, t) 60 | if col.norm() > 0.0: 61 | pixels[I] = combine(pixels[I], col) 62 | 63 | @ ti.func 64 | def GetSDF(self, uv, I, t): 65 | dist = sdf_sphere(uv, 0.5) 66 | return dist 67 | 68 | @ ti.func 69 | def GetColor(self, uv, I, dist, t): 70 | if dist < 0.0: 71 | col = ti.Vector([0.06, 0.23, 0.85, 1.0]) 72 | else: 73 | col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 74 | return col 75 | 76 | @ ti.func 77 | def SetColor(self, uv, I, dist, t, col): 78 | pass 79 | 80 | @ ti.kernel 81 | def Clear(self): 82 | for I in ti.grouped(ti.ndrange(res[0], res[1])): 83 | pixels[I] = clear_color 84 | 85 | 86 | @ti.data_oriented 87 | class CircleScene(Scene): 88 | def __init__(self) -> None: 89 | super(CircleScene, self).__init__() 90 | self.scene_name = "CircleScene" 91 | 92 | @ti.func 93 | def GetSDF(self, uv, I, t): 94 | if t > 5.0 and t < 15.0: 95 | cnt = perid_time(t-5.0, 4, 2) 96 | if cnt > 1.0: 97 | uv = fract(uv * cnt) - ti.Vector([0.5, 0.5]) 98 | dist = sdf_sphere(uv, 0.5) + ti.sin(t * 0.5) * 0.1 99 | return dist 100 | 101 | @ti.func 102 | def GetColor(self, uv, I, dist, t): 103 | col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 104 | if dist < 0.0: 105 | col = render_scale(dist, t * 3.0) 106 | return col 107 | 108 | 109 | @ ti.data_oriented 110 | class TaiChiScene(Scene): 111 | def __init__(self) -> None: 112 | super(TaiChiScene, self).__init__() 113 | self.scene_name = "TaiChiScene" 114 | self.multi_samples = False 115 | self.dura = 17.0 116 | 117 | @ ti.func 118 | def GetSDF(self, uv, I, t): 119 | rotate_speed = 12.2 120 | ang = t + ti.sin(t) * 3 + 1.5 121 | radius = 0.4 + ti.sin(t * rotate_speed) * 0.1 + 0.1 122 | padding = 0.02 + ti.sin(t * 2.3) * 0.02 + 0.02 123 | inner_radius = 0.16 + ti.sin(t * 2.32) * 0.08 + 0.08 124 | 125 | step = int(ti.floor(t / 3.0)) 126 | step = step % 6 127 | dist = 0.0 128 | dist_taichi = sdf_taichi(uv, radius, ang, padding, inner_radius, step) 129 | dist_sphere = sdf_sphere(uv, radius) 130 | if t < 12.0: 131 | dist = dist_taichi 132 | elif t >= 12.0 and t < 15.0: 133 | dist = mix(dist_taichi, dist_sphere, (t - 12.0) * (1.0 / 3.0)) 134 | else: 135 | dist = dist_sphere 136 | return dist 137 | 138 | @ ti.func 139 | def GetColor(self, uv, I, dist, t): 140 | col = render_scale(dist, t) 141 | return col 142 | 143 | @ ti.func 144 | def SetColor(self, uv, I, dist, t, col): 145 | pass 146 | 147 | @ti.kernel 148 | def Clear(self): 149 | for I in ti.grouped(ti.ndrange(res[0], res[1])): 150 | pixels[I] = clear_color 151 | 152 | 153 | @ ti.data_oriented 154 | class LineScene(Scene): 155 | def __init__(self) -> None: 156 | super(LineScene, self).__init__() 157 | self.scene_name = "LineScene" 158 | self.dura = 21.0 159 | 160 | @ti.func 161 | def GetSDF(self, uv, I, t): 162 | dist = 10.0 163 | dist_sphere = sdf_sphere(uv, 0.5) 164 | dist_star = sdf_star(uv, 0.6, 0.5) 165 | 166 | if t < 12.0: 167 | cnt = int(perid_time(t, 5, 2) + 1) 168 | for l in range(cnt): 169 | d = ti.abs(sdf_line(uv, t + math.pi * 1.0 / cnt * l)) - 0.05 170 | dist = ti.min(dist, d) 171 | if l >= 3: 172 | d = sdf_sphere(uv, 0.5 + ti.sin(t * 4.5) * 0.1 + 0.1) 173 | dist = ti.max(dist, d) 174 | if l >= 4: 175 | d = sdf_sphere(uv, 0.5 - ti.sin(t * 4.5) * 0.2 + 0.2) 176 | dist = ti.min(dist, d) 177 | if t < 2.0: 178 | dist = mix(dist, dist_sphere, convert_0to1_smooth(t - 2.0, 2.0)) 179 | elif t < 15.0: 180 | dist = mix(dist_sphere, dist_star, (t-12.0) * (1.0 / 3.0)) 181 | elif t < 18.0: 182 | dist = ti.max(dist_star, -(dist_sphere + 0.3 + ti. sin(t) * 0.1)) 183 | else: 184 | dist = dist_star 185 | 186 | return dist 187 | 188 | @ti.func 189 | def GetColor(self, uv, I, dist, t): 190 | col = render_scale(dist, t) 191 | return col 192 | 193 | 194 | @ ti.data_oriented 195 | class StarScene(Scene): 196 | def __init__(self) -> None: 197 | super(StarScene, self).__init__() 198 | self.rad = 0.5 199 | self.rf = 0.6 200 | self.multi_samples = False 201 | self.dura = 20 202 | self.star_init() 203 | 204 | def Render(self): 205 | self.RenderCore(t - self.st, self.draw_l, self.draw_r, self.draw_b, self.draw_t) 206 | vel[None].z = -1000 * (1.2 + math.cos(t * math.pi)) 207 | self.step() 208 | self.paint(t) 209 | 210 | @ti.func 211 | def RenderCoreStar(self, t, left, right, bot, top, colr, colg, colb, cola): 212 | for I in ti.grouped(ti.ndrange((left, right), (bot, top))): 213 | width = right - left 214 | height = top - bot 215 | pos = I - ti.Vector([left, bot]) - ti.Vector([width, height]) * 0.5 216 | uv = pos / height 217 | samples = 2 218 | if self.multi_samples: 219 | total_col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 220 | offset = ti.Vector([(2.0 * width / height) / height, 2.0 / height]) / 2 221 | for i in range(samples): # heigth 222 | for j in range(samples): # width 223 | off = ti.Vector([offset[0] * j, offset[1] * i]) 224 | dist = self.GetSDF(uv + off, I, t) 225 | col = ti.Vector([colr, colg, colb, cola]) 226 | if dist > 0.0: 227 | col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 228 | if col.norm() > 0.0: 229 | col = combine(pixels[I], col) 230 | total_col = total_col + col 231 | total_col = total_col / samples / samples 232 | if total_col.norm() > 0.0: 233 | pixels[I] = total_col 234 | else: 235 | dist = self.GetSDF(uv, I, t) 236 | col = ti.Vector([colr, colg, colb, cola]) 237 | if dist > 0.0: 238 | col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 239 | if col.norm() > 0.0: 240 | pixels[I] = combine(pixels[I], col) 241 | 242 | @ ti.func 243 | def GetSDF(self, uv, I, t): 244 | dist_star = sdf_star(uv, self.rf, self.rad) 245 | dist_sphere = sdf_sphere(uv, self.rad) 246 | dist = 0.0 247 | if t < 5.0: 248 | dist = dist_star 249 | elif t >= 5.0 and t < 13.0: 250 | dist = mix(dist_star, dist_sphere, ti.sin((t - 5.0) * math.pi * 0.5) * 0.5 + 0.5) 251 | else: 252 | dist = dist_star 253 | return dist 254 | 255 | @ ti.func 256 | def GetColor(self, uv, I, dist, t): 257 | col = render_scale(dist, t) 258 | return col 259 | 260 | @ ti.func 261 | def draw_star(self, c_pos, radius, t, col): 262 | left = ti.max(int(c_pos.x - radius - 1.0), 0) 263 | right = ti.min(int(c_pos.x + radius + 1.0), res[0]) 264 | bot = ti.max(int(c_pos.y - radius - 1.0), 0) 265 | top = ti.min(int(c_pos.y + radius + 1.0), res[1]) 266 | self.RenderCoreStar(t - self.st, left, right, bot, top, col[0], col[1], col[2], col[3]) 267 | 268 | @ ti.kernel 269 | def paint(self, t: float): 270 | for I in ti.grouped(pos): 271 | rad = 10.0 * (1.0 - pos[I].z / z_far)**2 272 | cur_p = pos[I] 273 | p = self.project(cur_p) 274 | 275 | col = color[I] * (1.0 - pos[I].z / z_far + 0.1) ** 0.5 276 | dist = ((pos[I].x / res[0] - 0.5)**2 + (pos[I].y / res[1] - 0.5)**2)**0.2 # distance from pos to center. 277 | col = col * (1.0 - dist) 278 | col_ret = ti.Vector([col[0], col[1], col[2], 1.0]) 279 | 280 | self.draw_star(p, rad, t, col_ret) 281 | 282 | @ ti.kernel 283 | def Clear(self): 284 | for I in ti.grouped(ti.ndrange(res[0], res[1])): 285 | pixels[I] = ti.Vector([0.83, 0.83, 0.83, 1.0]) * pixels[I] 286 | 287 | @ti.kernel 288 | def step(self): 289 | for I in ti.grouped(pos): 290 | pos[I] += vel[None] * dt 291 | if pos[I].z < z_near: 292 | pos[I].z += z_far - z_near 293 | pos[I].x = vel[None].z * ti.cos(pos[I].x) 294 | 295 | @ti.kernel 296 | def star_init(self): 297 | for I in ti.grouped(pos): 298 | pos[I] = (I + rand3()) * grid_size 299 | pos[I].z += z_near 300 | color[I] = rand3() + ti.Vector([1.0, 2.0, 3.0]) 301 | 302 | @ti.func 303 | def project(self, pos_3d): 304 | center = ti.Vector(res) / 2 305 | w = tan_half_fov * pos_3d.z 306 | res_pos = (ti.Vector([pos_3d.x, pos_3d.y]) - center) / w 307 | screen_pos = res_pos * res[1] + center 308 | return screen_pos 309 | 310 | 311 | @ ti.data_oriented 312 | class TreeScene(Scene): 313 | def __init__(self) -> None: 314 | super(TreeScene, self).__init__() 315 | self.scene_name = "TreeScene" 316 | 317 | @ ti.func 318 | def GetSDF(self, uv, I, t): 319 | dist_star = sdf_star(uv, 0.6, 0.5) 320 | rad = 0.5 321 | ang = -60 / 180.0 * math.pi 322 | p0 = ti.Vector([0.0, rad]) 323 | p1 = ti.Vector([rad * ti.cos(ang), rad * ti.sin(ang)]) 324 | p2 = ti.Vector([-rad * ti.cos(ang), rad * ti.sin(ang)]) 325 | dist_tri1 = sdf_tri(uv, p0, p1, p2) 326 | p0 = ti.Vector([0.0, 0.45]) 327 | p1 = ti.Vector([0.18, 0.18]) 328 | p2 = ti.Vector([-0.18, 0.18]) 329 | dist_tri2 = sdf_tri(uv, p0, p1, p2) 330 | dist_tree = sdf_tree(uv) 331 | 332 | dist = 0.0 333 | if t < 1.0: 334 | dist = dist_star 335 | elif t >= 1.0 and t < 5.0: 336 | dist = mix(dist_star, dist_tri1, convert_0to1_smooth(t - 1.0, 4.0)) 337 | elif t >= 5.0 and t < 7.0: 338 | dist = dist_tri1 339 | elif t >= 7.0 and t < 12.0: 340 | dist = mix(dist_tri1, dist_tree, convert_0to1_smooth(t - 7.0, 5.0)) 341 | else: 342 | dist = dist_tree 343 | return dist 344 | 345 | @ ti.func 346 | def GetColor(self, uv, I, dist, t): 347 | col = render_scale(dist, t) 348 | return col 349 | 350 | 351 | @ti.data_oriented 352 | class FulidScene(Scene): 353 | def __init__(self) -> None: 354 | super(FulidScene, self).__init__() 355 | self.dura = 30 356 | 357 | @ti.func 358 | def GetSDF(self, uv, I, t): 359 | dist_tree = sdf_tree(uv) 360 | dist_sphere1 = sdf_sphere(uv - ti.Vector([0.0, 0.1]), 0.4) 361 | dist_sphere2 = sdf_sphere(uv + ti.Vector([0.0, 0.1]), 0.2) 362 | dist_egg = sdf_egg(uv + ti.Vector([0.0, 0.25]), 0.20, 0.09) 363 | dist_heart = sdf_heart(uv * 2.0 + ti.Vector([0.0, 0.5])) 364 | 365 | dist = 0.0 366 | combine_time = 3.0 367 | 368 | # NOTE : I am not familiar with taichi variable, is list or vector like "x = [...]"? 369 | if t >= 0.0 and t < 2.0: 370 | dist = dist_tree 371 | elif t >= 2.0 and t < 5.0: 372 | dist = mix(dist_tree, dist_sphere1, convert_0to1_smooth(t - 2.0, combine_time)) 373 | elif t >= 5.0 and t < 7.0: 374 | dist = dist_sphere1 375 | elif t >= 7.0 and t < 10.0: 376 | dist = mix(dist_sphere1, dist_egg, convert_0to1_smooth(t - 7.0, combine_time)) 377 | elif t >= 10.0 and t < 12.0: 378 | dist = dist_egg 379 | elif t >= 12.0 and t < 15.0: 380 | dist = mix(dist_egg, dist_heart, convert_0to1_smooth(t - 12.0, combine_time)) 381 | elif t >= 15.0 and t < 17.0: 382 | dist = dist_heart 383 | elif t >= 17.0 and t < 20.0: 384 | dist = mix(dist_heart, dist_sphere2, convert_0to1_smooth(t - 17.0, combine_time)) 385 | elif t >= 20.0 and t < 22.0: 386 | dist = dist_sphere2 387 | elif t >= 22.0 and t < 25.0: 388 | dist = mix(dist_sphere2, dist_tree, convert_0to1_smooth(t - 22.0, combine_time)) 389 | else: 390 | # cnt = tooth((t - 25.0) * 0.2) * 4.0 + 1.0 391 | # uv = fract(uv * cnt) - ti.Vector([0.5, 0.5]) 392 | # dist = sdf_tree(uv) 393 | dist = dist_tree 394 | 395 | return dist 396 | 397 | @ti.func 398 | def GetColor(self, uv, I, dist, t): 399 | col = render_scale(dist, t) 400 | return col 401 | 402 | 403 | @ti.data_oriented 404 | class MultiCircleScene(Scene): 405 | def __init__(self) -> None: 406 | super(MultiCircleScene, self).__init__() 407 | self.dura = 25.0 408 | 409 | @ti.func 410 | def GetSDF(self, uv, I, t): 411 | dist = 100.0 412 | dis_circle = 100.0 413 | 414 | dis_tree = sdf_tree(uv) 415 | for i in range(10): 416 | rad = fract(i * 0.1 + t * 0.12) 417 | d = sdf_multi_circle(uv, rad) 418 | dis_circle = ti.min(dis_circle, d) 419 | dist_flower = sdf_flower(uv, t) 420 | dis_sph = sdf_sphere(uv, 0.5) 421 | 422 | if t < 2.0: 423 | dist = dis_tree 424 | elif t >= 2.0 and t < 5.0: 425 | dist = mix(dis_tree, dis_circle, convert_0to1_smooth(t - 2.0, 3.0)) 426 | elif t >= 5.0 and t < 10.0: 427 | dist = dis_circle 428 | elif t >= 10.0 and t < 15.0: 429 | dist = mix(dis_circle, dist_flower, convert_0to1_smooth(t - 10.0, 5.0)) 430 | elif t >= 15.0 and t < 20.0: 431 | dist = dist_flower 432 | elif t >= 20.0 and t < 25.0: 433 | dist = mix(dist_flower, dis_sph, convert_0to1_smooth(t - 20.0, 5.0)) 434 | else: 435 | dist = dis_sph 436 | return dist 437 | 438 | @ti.func 439 | def GetColor(self, uv, I, dist, t): 440 | col2 = render_scale(dist, t) 441 | col3 = render_black(dist, uv) 442 | col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 443 | if t < 2.0: 444 | col = col2 445 | elif t >= 2.0 and t < 5.0: 446 | col = mix(col2, col3, convert_0to1_smooth(t - 2.0, 3.0)) 447 | elif t >= 5.0 and t < 10.0: 448 | col = col3 449 | elif t >= 10.0 and t < 15.0: 450 | col = mix(col3, col2, convert_0to1_smooth(t - 10.0, 5.0)) 451 | elif t >= 15.0: 452 | col = col2 453 | return col 454 | 455 | 456 | @ti.data_oriented 457 | class AnimalScene(Scene): 458 | def __init__(self) -> None: 459 | super(AnimalScene, self).__init__() 460 | 461 | @ti.func 462 | def GetSDF(self, uv, I, t): 463 | dist_sph = sdf_sphere(uv, 0.5) 464 | dist_animal = sdf_animal(uv) 465 | dist = 0.0 466 | if t < 3.0: 467 | dist = dist_sph 468 | elif t >= 3.0 and t < 11.0: 469 | dist = mix(dist_sph, dist_animal, convert_whole_smooth(t - 3.0, 8.0 / 3 * 2)) 470 | else: 471 | dist = dist_animal 472 | return dist 473 | 474 | @ti.func 475 | def GetColor(self, uv, I, dist, t): 476 | col = render_scale(dist, t) 477 | return col 478 | 479 | 480 | @ ti.data_oriented 481 | class Movie(): 482 | def __init__(self) -> None: 483 | # add all frames 484 | self.frames = [] 485 | self.frames.append(CircleScene()) 486 | self.frames.append(TaiChiScene()) 487 | self.frames.append(LineScene()) 488 | self.frames.append(StarScene()) 489 | self.frames.append(TreeScene()) 490 | self.frames.append(FulidScene()) 491 | self.frames.append(MultiCircleScene()) 492 | self.frames.append(AnimalScene()) 493 | 494 | self.sum_t = 0.0 # modified frame dura. 495 | self.play_music = False 496 | self.play_music = True 497 | 498 | # load music 499 | self.music_files = [ 500 | "./music/" + "dubstep-drum-solo-140bpm-by-prettysleepy-art-15454.mp3", 501 | "./music/" + "Dynamic-good-electronic-music.mp3", 502 | "./music/" + "electronic-drum-loop-by-prettysleepy-art-12918.mp3" 503 | ] 504 | self.music = [] 505 | for mf in self.music_files: 506 | song = AudioSegment.from_mp3(mf) 507 | self.music.append(song) 508 | 509 | # modified start and end time 510 | for frame in self.frames: 511 | frame.st = frame.st + self.sum_t 512 | frame.ed = frame.st + frame.dura 513 | self.sum_t = self.sum_t + frame.dura 514 | self.gui = ti.GUI("why coding?", res, fast_gui=True) 515 | 516 | @ ti.kernel 517 | def Init(self): 518 | for I in ti.grouped(ti.ndrange(res[0], res[1])): 519 | pixels[I] = clear_color 520 | 521 | @ ti.kernel 522 | def Copy(self): 523 | for I in ti.grouped(ti.ndrange(res[0], res[1])): 524 | col = pixels[I] 525 | pixels_dis[I] = ti.Vector([col[0], col[1], col[2]]) * col[3] 526 | 527 | def Play(self): 528 | global t 529 | print("Start movie") 530 | cur_frame = 0 531 | frame = self.frames[cur_frame] 532 | print("Current frame : ", frame.scene_name, frame.st, frame.ed) 533 | 534 | if self.play_music: 535 | _thread.start_new_thread(PlayMusic, ("Music-1", self.music[1][:173000])) 536 | 537 | while self.gui.running: 538 | if self.gui.get_event(ti.GUI.ESCAPE): 539 | self.gui.running = False 540 | t = t + dt 541 | # print(t) 542 | if t >= frame.ed and cur_frame < (len(mov.frames)-1): 543 | cur_frame = cur_frame + 1 544 | frame = self.frames[cur_frame] 545 | print("Current frame : ", frame.scene_name, frame.st, frame.ed) 546 | frame.Render() 547 | mov.Copy() 548 | self.gui.set_image(pixels_dis) 549 | self.gui.show() 550 | frame.Clear() 551 | 552 | 553 | if __name__ == "__main__": 554 | mov = Movie() # Movie contains all scenes. 555 | mov.Init() 556 | mov.Play() 557 | -------------------------------------------------------------------------------- /music/Dynamic-good-electronic-music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/music/Dynamic-good-electronic-music.mp3 -------------------------------------------------------------------------------- /music/dubstep-drum-solo-140bpm-by-prettysleepy-art-15454.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/music/dubstep-drum-solo-140bpm-by-prettysleepy-art-15454.mp3 -------------------------------------------------------------------------------- /music/electronic-drum-loop-by-prettysleepy-art-12918.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElonKou/whycode/eb138f1c9ac4fc1350dbf974073f7a5cc3729a67/music/electronic-drum-loop-by-prettysleepy-art-12918.mp3 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pydub==0.25.1 2 | taichi==1.3.0 3 | -------------------------------------------------------------------------------- /ticore.py: -------------------------------------------------------------------------------- 1 | # !/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2022.12.04 4 | # @Author : elonkou 5 | # @File : ticore.py 6 | # Library for some shader functions. 7 | 8 | # Reference: 9 | # https://iquilezles.org/articles/distfunctions2d/ 10 | # https://zhuanlan.zhihu.com/p/581644801 11 | # https://zhuanlan.zhihu.com/p/574728205 12 | # https://www.shadertoy.com/user/iq 13 | 14 | import os 15 | import time 16 | import math 17 | import taichi as ti 18 | from pydub import AudioSegment 19 | from pydub.playback import play 20 | 21 | 22 | ti.init(arch=ti.gpu, default_ip=ti.i32, default_fp=ti.f32) 23 | 24 | # ================================================common================================================ 25 | # res = (960, 540) 26 | # res = (1920, 1080) 27 | # res = (2560, 1600) # If you are using high resolutiond computer/mac. 28 | res = (2560//2, 1600//2) 29 | t = 0.0 30 | fps = 60.0 31 | dt = 1.0 / fps 32 | pixels = ti.Vector.field(n=4, dtype=float, shape=res) # write color [rgba] 33 | pixels_dis = ti.Vector.field(n=3, dtype=float, shape=res) # display [rgb] 34 | clear_color = ti.Vector([0.12, 0.12, 0.12, 1.0]) 35 | 36 | # ================================================star================================================ 37 | fov = 120 38 | tan_half_fov = math.tan(fov / 360 * math.pi) 39 | z_near, z_far, grid_size = 200, 4200, 120 40 | N = (res[0]//grid_size, res[1]//grid_size, (z_far - z_near) // grid_size) 41 | pos = ti.Vector.field(n=3, dtype=float, shape=N) 42 | color = ti.Vector.field(n=3, dtype=float, shape=N) 43 | vel = ti.Vector.field(n=3, dtype=float, shape=()) 44 | 45 | 46 | # ================================================music================================================ 47 | def PlayMusic(threadName, music): 48 | print("Start play " + threadName) 49 | play(music) 50 | print("End play " + threadName) 51 | 52 | 53 | # ================================================tools================================================ 54 | @ti.func 55 | def rand3(): 56 | return ti.Vector([ti.random(), ti.random(), ti.random()]) 57 | 58 | 59 | @ti.func 60 | def rand4(): 61 | return ti.Vector([ti.random(), ti.random(), ti.random(), 1.0]) 62 | 63 | 64 | @ti.func 65 | def clamp(x, low=0.0, high=1.0): 66 | ret = x 67 | if x > high: 68 | ret = high 69 | elif x < low: 70 | ret = low 71 | return ret 72 | 73 | 74 | @ti.func 75 | def sign(x): 76 | if x >= 0: 77 | x = 1.0 78 | elif x < 0.0: 79 | x = -1.0 80 | return x 81 | 82 | 83 | @ti.func 84 | def fract(x): 85 | return x - ti.floor(x) 86 | 87 | 88 | @ti.func 89 | def smoothstep(edge0, edge1, x): 90 | t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0) 91 | return t * t * (3.0 - 2.0 * t) 92 | 93 | 94 | @ti.func 95 | def mix(x, y, a): 96 | # x * (1.0 - a) + y * a 97 | return x * (1.0 - a) + y * a 98 | 99 | 100 | @ti.func 101 | def tooth(x): 102 | # /\/\/\/\/\ 103 | return ti.min(fract(x * 2.0), 1.0 - fract(x * 2.0)) * 2.0 104 | 105 | 106 | @ti.func 107 | def dot2(x): 108 | return x.dot(x) 109 | 110 | 111 | @ti.func 112 | def Union(d1, d2): 113 | return ti.min(d1, d2) 114 | 115 | 116 | @ti.func 117 | def Subtraction(d1, d2): 118 | # return max(-d1, d2) # maybe has erro. 119 | return ti.max(d1, -d2) 120 | 121 | 122 | @ti.func 123 | def Intersection(d1, d2): 124 | return ti.max(d1, d2) 125 | 126 | 127 | @ti.func 128 | def SmoothUnion(d1, d2, k): 129 | h = clamp(0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0) 130 | return mix(d2, d1, h) - k * h * (1.0 - h) 131 | 132 | 133 | @ti.func 134 | def SmoothSubstraction(d1, d2, k): 135 | h = clamp(0.5 - 0.5 * (d2 + d1) / k, 0.0, 1.0) 136 | return mix(d2, -d1, h) - k * h * (1.0 - h) 137 | 138 | 139 | @ti.func 140 | def SmoothIntersection(d1, d2, k): 141 | h = clamp(0.5 - 0.5 * (d2 - d1) / k, 0.0, 1.0) 142 | return mix(d2, d1, h) - k * h * (1.0 - h) 143 | 144 | 145 | @ti.func 146 | def convert_0to1_smooth(x, maxv=1.0): 147 | return ti.sin(x / maxv * math.pi * 0.5) # return [0, 1] with sin smooth 148 | 149 | 150 | @ti.func 151 | def convert_whole_smooth(x, maxv=1.0): 152 | # return [0, 1, 0, -1, 0] with sin smooth 153 | # return [0, 1, 0, -1, 0] with sin smooth 154 | return ti.abs(ti.sin(x / maxv * math.pi)) 155 | 156 | 157 | @ti.func 158 | def combine(x, y): 159 | # combine RGBA color. y : high level, x : low level 160 | col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 161 | r = y[0] * y[3] + x[0] * x[3] * (1.0 - y[3]) 162 | g = y[1] * y[3] + x[1] * x[3] * (1.0 - y[3]) 163 | b = y[2] * y[3] + x[2] * x[3] * (1.0 - y[3]) 164 | a = 1.0 - (1.0 - y[3]) * (1.0 - x[3]) 165 | r = r / a 166 | g = g / a 167 | b = b / a 168 | r = clamp(r) 169 | g = clamp(g) 170 | b = clamp(b) 171 | a = clamp(a) 172 | col = ti.Vector([r, g, b, a]) 173 | return col 174 | 175 | # ================================================sdf================================================ 176 | 177 | 178 | @ti.func 179 | def sdf_star(uv, rf, rad): 180 | # generate distance field of 2D-stars 181 | k1 = ti.Vector([0.809016994375, -0.587785252292]) 182 | k2 = ti.Vector([-k1[0], k1[1]]) 183 | uv[0] = ti.abs(uv[0]) 184 | uv -= 2.0 * ti.max(k1.dot(uv), 0.0) * k1 185 | uv -= 2.0 * ti.max(k2.dot(uv), 0.0) * k2 186 | uv[0] = ti.abs(uv[0]) 187 | uv[1] -= rad 188 | 189 | ba = rf * ti.Vector([-k1[1], k1[0]]) - ti.Vector([0.0, 1.0]) 190 | h = uv.dot(ba) / ba.dot(ba) 191 | h = clamp(h, 0.0, rad) # use clmap for good render. 192 | 193 | v = uv[1] * ba[0] - uv[0] * ba[1] 194 | v = sign(v) 195 | 196 | dist = (uv - ba * h).norm() * v # diatance field of 2D-stars 197 | return dist 198 | 199 | 200 | @ti.func 201 | def sdf_sphere(uv, rad): 202 | return uv.norm() - rad 203 | 204 | 205 | @ti.func 206 | def sdf_verica(p, r, d): 207 | p = ti.abs(p) 208 | b = ti.sqrt(r * r - d * d) 209 | dist = 0.0 210 | if (p.y - b) * d > p.x * b: 211 | dist = (p - ti.Vector([0.0, b])).norm() 212 | else: 213 | dist = (p - ti.Vector([-d, 0.0])).norm() - r 214 | return dist 215 | 216 | 217 | @ti.func 218 | def sdf_box(pos, size): 219 | d = ti.abs(pos) - size 220 | dist = ti.max(d, 0.0).norm() 221 | dist = dist + ti.min(ti.max(d.x, d.y), 0.0) 222 | return dist 223 | 224 | 225 | @ti.func 226 | def sdf_arc(pos, sc, ra, rb): 227 | # pos.x = ti.abs(pos.x) 228 | dist = 0.0 229 | if sc.y * pos.x > sc.x * pos.y: 230 | dist = (pos - sc * ra).norm() 231 | else: 232 | dist = ti.abs(pos.norm() - ra) - rb 233 | return dist 234 | 235 | 236 | @ti.func 237 | def sdf_line(pos, ang): 238 | n = ti.floor(ang / (2.0 * math.pi)) 239 | ang = ang - 2.0 * math.pi * n 240 | k = ti.tan(ang) 241 | dist = (k * pos[0] - pos[1]) / ti.sqrt(k * k + 1) 242 | if ang > 0.5 * math.pi and ang < 1.5 * math.pi: 243 | dist = - dist 244 | return dist 245 | 246 | 247 | @ti.func 248 | def sdf_taichi(pos, radius, ang, padding=0.01, inner_radius=0.2, step=0): 249 | sub_offset = ti.Vector([ti.cos(ang), ti.sin(ang)]) * radius * 0.5 250 | d = sdf_sphere(pos, radius) 251 | d1 = sdf_sphere(pos - sub_offset, radius * 0.5) 252 | d0 = sdf_sphere(pos + sub_offset, radius * 0.5) 253 | dl = sdf_line(pos, ang) 254 | 255 | dist0 = ti.max(ti.min(ti.max(d, dl - padding), d0), -d1) + padding 256 | dist1 = ti.max(ti.min(ti.max(d, -dl - padding), d1), -d0) + padding 257 | dist = 0.0 258 | if step > -1: 259 | dist = dist0 260 | if step > 0: 261 | dist = dist1 262 | if step > 1: 263 | dist = ti.min(dist0, dist1) 264 | if step > 2: 265 | dist = ti.max(dist, -(d0 + inner_radius)) 266 | dist = ti.max(dist, -(d1 + inner_radius)) 267 | return dist 268 | 269 | 270 | @ti.func 271 | def sdf_tri(p, p0, p1, p2): 272 | dist = 0.0 273 | e0 = p1 - p0 274 | e1 = p2 - p1 275 | e2 = p0 - p2 276 | v0 = p - p0 277 | v1 = p - p1 278 | v2 = p - p2 279 | pq0 = v0 - e0 * clamp(v0.dot(e0) / e0.dot(e0), 0.0, 1.0) 280 | pq1 = v1 - e1 * clamp(v1.dot(e1) / e1.dot(e1), 0.0, 1.0) 281 | pq2 = v2 - e2 * clamp(v2.dot(e2) / e2.dot(e2), 0.0, 1.0) 282 | 283 | s = sign(e0.x * e2.y - e0.y * e2.x) 284 | d = ti.Vector([pq0.dot(pq0), s * (v0.x * e0.y - v0.y * e0.x)]) 285 | d = ti.min(d, ti.Vector([pq1.dot(pq1), s * (v1.x * e1.y - v1.y * e1.x)])) 286 | d = ti.min(d, ti.Vector([pq2.dot(pq2), s * (v2.x * e2.y - v2.y * e2.x)])) 287 | 288 | dist = -ti.sqrt(d.x) * sign(d.y) 289 | return dist 290 | 291 | 292 | @ti.func 293 | def sdf_tree(uv, height=2, width=0.05): 294 | p0 = ti.Vector([0.0, 0.45]) 295 | p1 = ti.Vector([0.18, 0.18]) 296 | p2 = ti.Vector([-0.18, 0.18]) 297 | dist = sdf_tri(uv, p0, p1, p2) 298 | for i in range(height): 299 | offset = 0.43 / height 300 | p0 = p0 - ti.Vector([0.0, offset * 0.5]) 301 | p1 = p1 - ti.Vector([-offset * 0.18, offset + i * 0.02]) 302 | p2 = p2 - ti.Vector([offset * 0.18, offset + i * 0.02]) 303 | trii = sdf_tri(uv, p0, p1, p2) 304 | dist = ti.min(dist, trii) 305 | c = ti.Vector([0.0, -0.25]) 306 | bb = ti.Vector([0.05, 0.3]) 307 | truck = sdf_box(uv - c, bb) 308 | dist = ti.min(dist, truck) 309 | return dist 310 | 311 | 312 | @ti.func 313 | def sdf_egg(p, ra, rb): 314 | dist = 0.0 315 | k = ti.sqrt(3.0) 316 | p.x = ti.abs(p.x) 317 | r = ra - rb 318 | 319 | f = 0.0 320 | f2 = 0.0 321 | d1 = ti.Vector([p.x, p.y - k * r]).norm() 322 | d2 = ti.Vector([p.x + r, p.y]).norm() - 2.0 * r 323 | if k * (p.x + r) < p.y: 324 | f2 = d1 325 | else: 326 | f2 = d2 327 | f1 = p.norm() - r 328 | 329 | if p.y < 0.0: 330 | f = f1 331 | else: 332 | f = f2 333 | 334 | dist = f - rb 335 | return dist 336 | 337 | 338 | @ti.func 339 | def sdf_trape(pos, r1, r2, he): 340 | k1 = ti.Vector([r2, he]) 341 | k2 = ti.Vector([r2 - r1, 2.0 * he]) 342 | pos.x = ti.abs(pos.x) 343 | 344 | r = 0.0 345 | if pos.y < 0.0: 346 | r = r1 347 | else: 348 | r = r2 349 | ca = ti.Vector([pos.x - ti.min(pos.x, r), ti.abs(pos.y) - he]) 350 | cb = pos - k1 + k2 * clamp((k1 - pos).dot(k2) / dot2(k2), 0.0, 1.0) 351 | s = 0.0 352 | if cb.x < 0.0 and ca.y < 0.0: 353 | s = -1.0 354 | else: 355 | s = 1.0 356 | dist = s * ti.sqrt(ti.min(dot2(ca), dot2(cb))) 357 | return dist 358 | 359 | 360 | @ti.func 361 | def sdf_animal(uv): 362 | head_pos = uv - ti.Vector([0.0, 0.23]) 363 | dist_face = sdf_egg(ti.Vector([uv[0], uv[1] * 1.4]) - ti.Vector([0.0, 0.23]), 0.21, 0.15) 364 | p0 = ti.Vector([-0.16, 0.22]) 365 | p4 = ti.Vector([-0.10, 0.22]) 366 | p1 = ti.Vector([-0.16, 0.01]) 367 | p2 = ti.Vector([-0.10, 0.01]) 368 | head_pos.x = ti.abs(head_pos.x) 369 | dist_tri1 = sdf_tri(head_pos, p0, p1, p2) 370 | dist_tri2 = sdf_tri(head_pos, p4, p1, p2) 371 | dist_tri3 = sdf_tri(head_pos, ti.Vector([-p0.x, p0.y]), ti.Vector([-p1.x, p1.y]), ti.Vector([-p2.x, p2.y])) 372 | dist_tri4 = sdf_tri(head_pos, ti.Vector([-p4.x, p4.y]), ti.Vector([-p1.x, p1.y]), ti.Vector([-p2.x, p2.y])) 373 | 374 | dist_ear = ti.min(ti.min(dist_tri1, dist_tri2), ti.min(dist_tri3, dist_tri4)) 375 | 376 | eye_pos = uv 377 | eye_pos.y = eye_pos.y * 0.5 378 | dist_sphere1 = sdf_sphere(eye_pos - ti.Vector([0.11, 0.09]), 0.02) 379 | dist_sphere2 = sdf_sphere(eye_pos - ti.Vector([-0.11, 0.09]), 0.02) 380 | dist_eye = ti.min(dist_sphere1, dist_sphere2) 381 | 382 | cloth_pos = uv 383 | x_scale = 1.0 - (cloth_pos.y - 0.11) 384 | x_scale = ti.abs(pow(x_scale, 2.3)) 385 | cloth_pos.x = cloth_pos.x * x_scale 386 | 387 | y_scale = cloth_pos.x 388 | y_scale = 1.0 - ti.abs(pow(y_scale, 2.0)) 389 | cloth_pos.y = cloth_pos.y * y_scale 390 | cloth_pos.y = cloth_pos.y * y_scale 391 | 392 | cloth_pos = cloth_pos + ti.Vector([0.0, 0.16]) 393 | dist_cloth = sdf_trape(cloth_pos, 0.24, 0.34, 0.18) - 0.03 394 | 395 | dist = ti.min(dist_face, dist_ear) 396 | dist = ti.max(dist, -dist_eye) 397 | dist = ti.min(dist, dist_cloth) 398 | return dist 399 | 400 | 401 | @ti.func 402 | def sdf_heart(p): 403 | dist = 0.0 404 | p.x = ti.abs(p.x) 405 | if ((p.x + p.y) > 1.0): 406 | dist = ti.sqrt(dot2(p - ti.Vector([0.25, 0.75]))) - ti.sqrt(2.0) / 4.0 407 | else: 408 | dist = ti.min(dot2(p - ti.Vector([0.0, 1.0])), dot2(p - 0.5 * ti.max(p.x + p.y, 0.0))) 409 | dist = ti.sqrt(dist) * sign(p.x - p.y) 410 | return dist 411 | 412 | 413 | @ti.func 414 | def sdf_multi_circle(uv, radius=0.5, thickness=0.006): 415 | dist = ti.abs(sdf_sphere(uv, radius)) - thickness 416 | return dist 417 | 418 | 419 | @ti.func 420 | def sdf_wave_sphere(uv, rad, cnt=20, wave=0.01): 421 | ang = ti.atan2(uv[1], uv[0]) 422 | r = uv.norm() + ti.sin(ang * 20) * 0.01 423 | uv.x = r * ti.cos(ang) 424 | uv.y = r * ti.sin(ang) 425 | dist = sdf_sphere(uv, rad) 426 | return dist 427 | 428 | 429 | @ti.func 430 | def sdf_fish(uv): 431 | dist_eye = sdf_sphere(uv- ti.Vector([-0.4, 0.02]), 0.03) 432 | dist_sph1 = sdf_sphere(uv - ti.Vector([-0.2, 0.3]), 0.5) 433 | dist_sph2 = sdf_sphere(uv - ti.Vector([-0.2, -0.3]), 0.5) 434 | p0 = ti.Vector([0.0, 0.0]) 435 | p1 = ti.Vector([0.3, 0.25]) 436 | p2 = ti.Vector([0.3, -0.25]) 437 | dist_tri = sdf_tri(uv, p0, p1, p2) 438 | dist = max(dist_sph1, dist_sph2) 439 | dist = max(dist, -dist_eye) 440 | dist = min(dist_tri, dist) 441 | return dist 442 | 443 | 444 | @ti.func 445 | def sdf_flower(uv, t): 446 | sph1 = sdf_sphere(uv, 0.5 + ti.sin(t) * 0.12) 447 | sph2 = sdf_sphere(uv, 0.8 + ti.cos(t * 1.4) * 0.12) 448 | sph3 = sdf_wave_sphere(uv, 0.2 + ti.cos(2.1) * 0.15) 449 | 450 | dist = Subtraction(sph2, sph1) 451 | dist = Union(dist, ti.abs(sph3) - 0.02) 452 | 453 | cnt = 10 454 | for i in range(cnt): 455 | ang = 2.0 * math.pi / cnt * i 456 | radius = 0.45 + ti.cos(t * 1.1) * 0.14 457 | sub_uv = ti.Vector([ti.cos(ang), ti.sin(ang)]) * radius 458 | dist_sph = sdf_sphere(uv - sub_uv, 0.16 + ti.sin(t * 3.1) * 0.12) 459 | dist = Subtraction(dist, dist_sph) 460 | sph4 = sdf_wave_sphere(uv, 0.6 + ti.cos(t) * 0.12) 461 | dist = Union(dist, ti.abs(sph4) - 0.02) 462 | return dist 463 | 464 | # ================================================render================================================ 465 | 466 | 467 | @ti.func 468 | def blink_col(): 469 | pass 470 | 471 | 472 | inner_col = ti.Vector([0.25, 0.85, 1.0]) 473 | outer_col = ti.Vector([0.65, 0.35, 0.76]) 474 | 475 | 476 | @ti.func 477 | def render_grad(dist, cnt=350): 478 | # ref: https://github.com/ElonKou/biulab/blob/master/shaders/shadertoy/sdf2d1.fs 479 | col = ti.Vector([0.0, 0.0, 0.0]) 480 | if (dist < 0.0): 481 | col = inner_col 482 | else: 483 | col = outer_col 484 | col = col * (1.0 - ti.exp(-6.0 * ti.abs(dist))) 485 | col = col * (0.8 + 0.2 * ti.cos(dist * cnt)) 486 | c = smoothstep(0.0, 0.01, ti.abs(dist)) 487 | col = mix(col, ti.Vector([1.0, 1.0, 1.0]), 1.0 - c) 488 | 489 | ret = ti.Vector([col[0], col[1], col[2], 1.0]) 490 | return ret 491 | 492 | 493 | @ti.func 494 | def render_general(dist): 495 | col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 496 | if dist < 0.0: 497 | col = ti.Vector([0.34, 0.45, 0.56, 1.0]) 498 | return col 499 | 500 | 501 | @ti.func 502 | def render_black(dist, uv): 503 | col = ti.Vector([0.4, 0.2, 0.12, 1.0]) 504 | col_purple = ti.Vector([0.99, 0.25, 0.94, 1.0]) 505 | col_blue = ti.Vector([0.24, 0.75, 0.89, 1.0]) 506 | d = uv.norm() 507 | if dist < 0.0: 508 | col = mix(col_purple, col_blue, 1.0 - d) 509 | else: 510 | col = mix(col_purple, col_blue, 1.0 - d) 511 | col[3] = col[3] * ti.max(0.03 - dist, 0.0) 512 | 513 | return col 514 | 515 | 516 | @ti.func 517 | def render_total_black(dist): 518 | black = ti.Vector([0.02, 0.02, 0.02, 0.5]) 519 | col = ti.Vector([0.9, 0.9, 0.9, 0.5]) 520 | if dist < 0.0: 521 | col = black 522 | return col 523 | 524 | 525 | @ti.func 526 | def render_blink(dist, t): 527 | dist = fract(dist * 5.0) * 0.2 - 0.4 528 | col = ti.Vector([0.0, 0.0, 0.0, 0.0]) 529 | if dist < 0.0: 530 | col = ti.Vector([0.7 + ti.sin(t * 8.03) * 0.3, 0.9 * ti.sin(dist * 2 + 1.0), 0.7, 1.0]) 531 | return col 532 | 533 | 534 | @ti.func 535 | def render_scale(dist, t): 536 | dist = dist * ti.sin(t) * 1.45 537 | col = render_grad(dist, 250) 538 | col.x = mix(col.x, 1.0, ti.sin(t * 2.3) * 0.9) 539 | col.z = mix(col.z, 1.0, ti.cos(t * 3.1) * 0.9) 540 | col.y = mix(col.y, 0.0, ti.sin(t * 0.67) * 0.3) 541 | return col 542 | 543 | 544 | @ti.func 545 | def fade_in(t, offset=1.0): 546 | return ti.min(ti.exp(t + offset), t) 547 | 548 | 549 | @ti.func 550 | def perid_time(x, up=1.0, dura=2.0): 551 | # | ____ 552 | # |/______\__ 553 | 554 | scale = dura + up * 2.0 555 | ret = fract(x / scale) * scale 556 | ret = ti.min(ret, scale - ret) 557 | ret = ti.min(ret, up) 558 | return ret 559 | 560 | 561 | @ti.func 562 | def render_in(dist, t): 563 | c = fade_in(t) 564 | dist = dist * ti.sin(c) * 1.45 565 | col = render_grad(dist, min(c * 13, 250)) 566 | col.x = mix(col.x, 1.0, ti.sin(c * 2.3) * 0.9) 567 | col.z = mix(col.z, 1.0, ti.cos(c * 3.1) * 0.9) 568 | col.y = mix(col.y, 0.0, ti.sin(c * 0.67) * 0.3) 569 | return col 570 | --------------------------------------------------------------------------------