├── requirements.txt ├── img ├── lava.jpg ├── rust.jpg └── beast_pic.jpg ├── sreenshots └── 0.png ├── README.md ├── LICENSE ├── .gitignore └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | pygame 2 | numpy 3 | taichi 4 | taichi_glsl 5 | -------------------------------------------------------------------------------- /img/lava.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StanislavPetrovV/Tunnel-Shader-Imitation/HEAD/img/lava.jpg -------------------------------------------------------------------------------- /img/rust.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StanislavPetrovV/Tunnel-Shader-Imitation/HEAD/img/rust.jpg -------------------------------------------------------------------------------- /sreenshots/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StanislavPetrovV/Tunnel-Shader-Imitation/HEAD/sreenshots/0.png -------------------------------------------------------------------------------- /img/beast_pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StanislavPetrovV/Tunnel-Shader-Imitation/HEAD/img/beast_pic.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tunnel-Shader-Imitation 2 | OpenGL(GLSL) fragment shader Tunnel imitation in Python using Pygame, Numpy, Taichi 3 | 4 | ![tunnel_shader](/sreenshots/0.png) 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 StanislavPetrovV 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import numpy as np 3 | import taichi as ti 4 | import taichi_glsl as ts 5 | from taichi_glsl import vec2, vec3 6 | 7 | ti.init(arch=ti.cuda) # ti.cpu ti.gpu ti.vulkan ti.opengl ti.metal(macOS) 8 | resolution = width, height = vec2(1600, 900) 9 | 10 | # load texture 11 | texture = pg.image.load('img/rust.jpg') # texture res - 2^n x 2^n (512 x 512, 1024 x 1024, ...) 12 | texture_size = texture.get_size()[0] 13 | # texture color normalization 0 - 255 --> 0.0 - 1.0 14 | texture_array = pg.surfarray.array3d(texture).astype(np.float32) / 255 15 | 16 | 17 | @ti.data_oriented 18 | class PyShader: 19 | def __init__(self, app): 20 | self.app = app 21 | self.screen_array = np.full((width, height, 3), [0, 0, 0], np.uint8) 22 | # taichi fields 23 | self.screen_field = ti.Vector.field(3, ti.uint8, (width, height)) 24 | self.texture_field = ti.Vector.field(3, ti.float32, texture.get_size()) 25 | self.texture_field.from_numpy(texture_array) 26 | 27 | @ti.kernel 28 | def render(self, time: ti.float32): 29 | """fragment shader imitation""" 30 | for frag_coord in ti.grouped(self.screen_field): 31 | # normalized pixel coords 32 | uv = (frag_coord - 0.5 * resolution) / resolution.y 33 | col = vec3(0.0) 34 | 35 | # polar coords 36 | phi = ts.atan(uv.y, uv.x) 37 | rho = ts.length(uv) 38 | # rho = pow(pow(uv.x ** 2, 4) + pow(uv.y ** 2, 4), 0.125) 39 | 40 | st = vec2(phi / ts.pi * 2, 0.25 / rho) 41 | # st.x += time / 12 42 | st.y += time / 2 43 | col += self.texture_field[int(st * texture_size)] 44 | 45 | col *= rho + 0.1 46 | # col += 0.1 / rho * vec3(0.1, 0.1, 0.4) 47 | col = ts.clamp(col, 0.0, 1.0) 48 | self.screen_field[frag_coord.x, resolution.y - frag_coord.y] = col * 255 49 | 50 | def update(self): 51 | time = pg.time.get_ticks() * 1e-03 # time in sec 52 | self.render(time) 53 | self.screen_array = self.screen_field.to_numpy() 54 | 55 | def draw(self): 56 | pg.surfarray.blit_array(self.app.screen, self.screen_array) 57 | 58 | def run(self): 59 | self.update() 60 | self.draw() 61 | 62 | 63 | class App: 64 | def __init__(self): 65 | self.screen = pg.display.set_mode(resolution, pg.SCALED) 66 | self.clock = pg.time.Clock() 67 | self.shader = PyShader(self) 68 | 69 | def run(self): 70 | while True: 71 | self.shader.run() 72 | pg.display.flip() 73 | 74 | [exit() for i in pg.event.get() if i.type == pg.QUIT] 75 | self.clock.tick(60) 76 | pg.display.set_caption(f'FPS: {self.clock.get_fps() :.2f}') 77 | 78 | 79 | if __name__ == '__main__': 80 | app = App() 81 | app.run() 82 | --------------------------------------------------------------------------------