├── .gitignore ├── LICENSE ├── README.md ├── Tina-Dev.py ├── assets ├── .gitignore ├── atms.png ├── caustics.gltf ├── cloth.jpg ├── cornell.gltf ├── cornell.mtl ├── cornell.obj ├── cube.obj ├── cylinder.obj ├── logo.obj ├── lut.jpg ├── monkey.obj ├── multimtl.mtl ├── multimtl.obj ├── normal.png ├── orient.obj ├── pattern.jpg ├── plane.obj ├── shadow.mtl ├── shadow.obj ├── skybox.jpg ├── smallptwall.png ├── sphere.gltf ├── sphere.obj ├── torus.obj └── uvsphere.obj ├── bench.py ├── conv.py ├── docs ├── connect.py ├── gltf.py ├── lighting.py ├── monkey.py ├── options.py ├── particles.py ├── pathtrace.py ├── primitives.py ├── smooth.py ├── transform.py ├── triangle.py ├── volume.py └── wireframe.py ├── examples ├── cornell_box.py ├── ibl_matball.py ├── matball.py ├── mciso_mpm3d.py ├── meshgrid_cloth.py ├── meshgrid_wave.py ├── pars_mpm3d.py └── rtx_matball.py ├── lbm.py ├── melt ├── dump.py ├── lbm.py ├── lbvh.py ├── mpm.py ├── mrt.py ├── mrtlbm.py ├── rbd.py ├── vbrbd.py └── voxelizer.py ├── setup.py ├── tests ├── bdpt.py ├── blooming.py ├── brdf.py ├── cookibl.py ├── emission.py ├── fpe.py ├── fxaa.py ├── glass.py ├── inter.py ├── knn.py ├── mis.py ├── mtlid.py ├── nlm.py ├── noise.py ├── path.py ├── pick.py ├── probe.py ├── ptlight.py ├── raytrace.py ├── rtao.py ├── rtx.py ├── skybox.py ├── ssao.py ├── ssr.py ├── volume.py ├── voxl.py ├── wave.py └── wire.py ├── tina ├── __init__.py ├── __main__.py ├── advans.py ├── assimp │ ├── __init__.py │ ├── gltf.py │ ├── obj.py │ ├── pfm.py │ └── tet.py ├── cli │ ├── __init__.py │ ├── mesh.py │ ├── particles.py │ └── volume.py ├── common.py ├── core │ ├── __init__.py │ ├── engine.py │ ├── lighting.py │ ├── material.py │ ├── particle.py │ ├── shader.py │ ├── triangle.py │ ├── volume.py │ └── wireframe.py ├── hacker.py ├── inject.py ├── lazimp.py ├── matr │ ├── __init__.py │ ├── material.py │ ├── nodes.py │ └── wavelen.py ├── memory.py ├── mesh │ ├── __init__.py │ ├── base.py │ ├── conn.py │ ├── cull.py │ ├── export.py │ ├── grid.py │ ├── model.py │ ├── norm.py │ ├── prim.py │ ├── simple.py │ ├── trans.py │ └── wire.py ├── pars │ ├── __init__.py │ ├── base.py │ ├── export.py │ ├── simple.py │ └── trans.py ├── path │ ├── __init__.py │ ├── bidir.py │ ├── engine.py │ ├── geometry.py │ ├── particle.py │ ├── tree.py │ ├── triangle.py │ └── volume.py ├── postp │ ├── __init__.py │ ├── blooming.py │ ├── denoise.py │ ├── fxaa.py │ ├── ssao.py │ ├── ssr.py │ └── tonemap.py ├── probe.py ├── random.py ├── scene │ ├── __init__.py │ ├── raster.py │ └── tracer.py ├── shield.py ├── skybox.py ├── util │ ├── __init__.py │ ├── _mciso_data.py │ ├── accumator.py │ ├── control.py │ ├── matrix.py │ ├── mciso.py │ └── stack.py └── voxl │ ├── __init__.py │ ├── base.py │ ├── scale.py │ ├── simple.py │ └── trans.py └── view.py /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /build/ 3 | /*.egg-info/ 4 | /rubbish.bin 5 | __pycache__/ 6 | /GNUmakefile 7 | /.*_localrc 8 | /.idea 9 | /*.ipynb 10 | /.vscode 11 | node_modules/ 12 | package-lock.json 13 | *.xcf 14 | *.png 15 | *.jpg 16 | *.blend 17 | /outputs 18 | tags 19 | /a.py 20 | /b.py 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 archibate 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 | -------------------------------------------------------------------------------- /Tina-Dev.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | 'name': 'Tina (dev mode)', 3 | 'description': 'A soft-renderer based on Taichi programming language', 4 | 'author': 'archibate <1931127624@qq.com>', 5 | 'version': (0, 0, 0), 6 | 'blender': (2, 81, 0), 7 | 'location': 'Render -> Tina', 8 | 'support': 'TESTING', 9 | 'wiki_url': 'https://github.com/archibate/tina/wiki', 10 | 'tracker_url': 'https://github.com/archibate/tina/issues', 11 | 'warning': 'Development mode', 12 | 'category': 'Render', 13 | } 14 | 15 | 16 | import sys 17 | sys.path.insert(0, '/home/bate/Develop/three_taichi') 18 | 19 | 20 | registered = False 21 | 22 | 23 | def register(): 24 | print('Tina-Dev register...') 25 | import tina_blend 26 | tina_blend.register() 27 | 28 | global registered 29 | registered = True 30 | print('...register done') 31 | 32 | 33 | def unregister(): 34 | print('Tina-Dev unregister...') 35 | import tina_blend 36 | tina_blend.unregister() 37 | 38 | global registered 39 | registered = False 40 | print('...unregister done') 41 | 42 | 43 | def reload_addon(): 44 | import tina 45 | import tina_blend 46 | if registered: 47 | tina_blend.unregister() 48 | tina.__lazyreload__() 49 | tina_blend.__lazyreload__() 50 | tina_blend.register() 51 | 52 | 53 | @eval('lambda x: x()') 54 | def _(): 55 | class Reload: 56 | def __repr__(self): 57 | import os 58 | import bpy 59 | os.system('clear') 60 | reload_addon() 61 | bpy.context.scene.frame_current = bpy.context.scene.frame_current 62 | return 'reloaded' 63 | 64 | __import__('bpy').a = Reload() 65 | -------------------------------------------------------------------------------- /assets/.gitignore: -------------------------------------------------------------------------------- 1 | bunny.* 2 | lens.* 3 | *.npy 4 | *.vdb 5 | *.blend* 6 | hp_* 7 | *.hdr 8 | *.npz 9 | semisphere.* 10 | monkey_cornell.* 11 | -------------------------------------------------------------------------------- /assets/atms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichi-dev/taichi_three/62596cf36fba1c5a528796c51942ce44ed76292a/assets/atms.png -------------------------------------------------------------------------------- /assets/cloth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichi-dev/taichi_three/62596cf36fba1c5a528796c51942ce44ed76292a/assets/cloth.jpg -------------------------------------------------------------------------------- /assets/cornell.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'cornell.blend' 2 | # Material Count: 2 3 | 4 | newmtl Material 5 | Ns 225.000000 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.800000 0.800000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.450000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl Material.001 15 | Ns 820.041320 16 | Ka 0.881818 0.881818 0.881818 17 | Kd 0.800000 0.800000 0.800000 18 | Ks 0.545455 0.545455 0.545455 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.450000 21 | d 1.000000 22 | illum 3 23 | -------------------------------------------------------------------------------- /assets/cornell.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.90.0 OBJ File: 'cornell.blend' 2 | # www.blender.org 3 | mtllib cornell.mtl 4 | o 立方体_立方体.003 5 | v -0.026283 0.000000 0.756144 6 | v -0.026283 1.382884 0.756144 7 | v 0.433536 0.000000 -0.470799 8 | v 0.433536 1.382884 -0.470799 9 | v 1.200660 0.000000 1.215963 10 | v 1.200660 1.382884 1.215963 11 | v 1.660479 0.000000 -0.010981 12 | v 1.660479 1.382884 -0.010981 13 | vt 0.375000 0.000000 14 | vt 0.625000 0.000000 15 | vt 0.625000 0.250000 16 | vt 0.375000 0.250000 17 | vt 0.625000 0.500000 18 | vt 0.375000 0.500000 19 | vt 0.625000 0.750000 20 | vt 0.375000 0.750000 21 | vt 0.625000 1.000000 22 | vt 0.375000 1.000000 23 | vt 0.125000 0.500000 24 | vt 0.125000 0.750000 25 | vt 0.875000 0.500000 26 | vt 0.875000 0.750000 27 | vn -0.9364 0.0000 -0.3509 28 | vn 0.3509 0.0000 -0.9364 29 | vn 0.9364 0.0000 0.3509 30 | vn -0.3509 0.0000 0.9364 31 | vn 0.0000 -1.0000 0.0000 32 | vn 0.0000 1.0000 -0.0000 33 | usemtl Material.001 34 | s off 35 | f 1/1/1 2/2/1 4/3/1 3/4/1 36 | f 3/4/2 4/3/2 8/5/2 7/6/2 37 | f 7/6/3 8/5/3 6/7/3 5/8/3 38 | f 5/8/4 6/7/4 2/9/4 1/10/4 39 | f 3/11/5 7/6/5 5/8/5 1/12/5 40 | f 8/5/6 4/13/6 2/14/6 6/7/6 41 | o 立方体.001_立方体.004 42 | v -1.247766 0.000000 -0.050946 43 | v -1.247766 1.846960 -0.050946 44 | v -1.650416 0.000000 -1.262043 45 | v -1.650416 1.846960 -1.262043 46 | v -0.036669 0.000000 -0.453596 47 | v -0.036669 1.846960 -0.453596 48 | v -0.439319 0.000000 -1.664693 49 | v -0.439319 1.846960 -1.664693 50 | vt 0.375000 0.000000 51 | vt 0.625000 0.000000 52 | vt 0.625000 0.250000 53 | vt 0.375000 0.250000 54 | vt 0.625000 0.500000 55 | vt 0.375000 0.500000 56 | vt 0.625000 0.750000 57 | vt 0.375000 0.750000 58 | vt 0.625000 1.000000 59 | vt 0.375000 1.000000 60 | vt 0.125000 0.500000 61 | vt 0.125000 0.750000 62 | vt 0.875000 0.500000 63 | vt 0.875000 0.750000 64 | vn -0.9489 0.0000 0.3155 65 | vn -0.3155 0.0000 -0.9489 66 | vn 0.9489 0.0000 -0.3155 67 | vn 0.3155 0.0000 0.9489 68 | vn 0.0000 -1.0000 0.0000 69 | vn 0.0000 1.0000 0.0000 70 | usemtl Material.001 71 | s off 72 | f 9/15/7 10/16/7 12/17/7 11/18/7 73 | f 11/18/8 12/17/8 16/19/8 15/20/8 74 | f 15/20/9 16/19/9 14/21/9 13/22/9 75 | f 13/22/10 14/21/10 10/23/10 9/24/10 76 | f 11/25/11 15/20/11 13/22/11 9/26/11 77 | f 16/19/12 12/27/12 10/28/12 14/21/12 78 | o 立方体.002_立方体.007 79 | v -2.000000 0.000000 2.000000 80 | v -2.000000 4.000000 2.000000 81 | v -2.000000 0.000000 -2.000000 82 | v -2.000000 4.000000 -2.000000 83 | v 2.000000 0.000000 2.000000 84 | v 2.000000 4.000000 2.000000 85 | v 2.000000 0.000000 -2.000000 86 | v 2.000000 4.000000 -2.000000 87 | vt 0.375000 0.000000 88 | vt 0.375000 0.250000 89 | vt 0.625000 0.250000 90 | vt 0.625000 0.000000 91 | vt 0.375000 0.500000 92 | vt 0.625000 0.500000 93 | vt 0.375000 0.750000 94 | vt 0.625000 0.750000 95 | vt 0.125000 0.500000 96 | vt 0.125000 0.750000 97 | vt 0.875000 0.750000 98 | vt 0.875000 0.500000 99 | vn 1.0000 0.0000 0.0000 100 | vn 0.0000 0.0000 1.0000 101 | vn -1.0000 0.0000 0.0000 102 | vn 0.0000 1.0000 0.0000 103 | vn 0.0000 -1.0000 0.0000 104 | usemtl Material 105 | s off 106 | f 17/29/13 19/30/13 20/31/13 18/32/13 107 | f 19/30/14 23/33/14 24/34/14 20/31/14 108 | f 23/33/15 21/35/15 22/36/15 24/34/15 109 | f 19/37/16 17/38/16 21/35/16 23/33/16 110 | f 24/34/17 22/36/17 18/39/17 20/40/17 -------------------------------------------------------------------------------- /assets/cube.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.90.0 OBJ File: '' 2 | # www.blender.org 3 | o Cube 4 | v 1.000000 1.000000 -1.000000 5 | v 1.000000 -1.000000 -1.000000 6 | v 1.000000 1.000000 1.000000 7 | v 1.000000 -1.000000 1.000000 8 | v -1.000000 1.000000 -1.000000 9 | v -1.000000 -1.000000 -1.000000 10 | v -1.000000 1.000000 1.000000 11 | v -1.000000 -1.000000 1.000000 12 | vt 0.625000 0.500000 13 | vt 0.875000 0.500000 14 | vt 0.875000 0.750000 15 | vt 0.625000 0.750000 16 | vt 0.375000 0.750000 17 | vt 0.625000 1.000000 18 | vt 0.375000 1.000000 19 | vt 0.375000 0.000000 20 | vt 0.625000 0.000000 21 | vt 0.625000 0.250000 22 | vt 0.375000 0.250000 23 | vt 0.125000 0.500000 24 | vt 0.375000 0.500000 25 | vt 0.125000 0.750000 26 | vn 0.0000 1.0000 0.0000 27 | vn 0.0000 0.0000 1.0000 28 | vn -1.0000 0.0000 0.0000 29 | vn 0.0000 -1.0000 0.0000 30 | vn 1.0000 0.0000 0.0000 31 | vn 0.0000 0.0000 -1.0000 32 | s off 33 | f 1/1/1 5/2/1 7/3/1 3/4/1 34 | f 4/5/2 3/4/2 7/6/2 8/7/2 35 | f 8/8/3 7/9/3 5/10/3 6/11/3 36 | f 6/12/4 2/13/4 4/5/4 8/14/4 37 | f 2/13/5 1/1/5 3/4/5 4/5/5 38 | f 6/11/6 5/10/6 1/1/6 2/13/6 39 | -------------------------------------------------------------------------------- /assets/lut.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichi-dev/taichi_three/62596cf36fba1c5a528796c51942ce44ed76292a/assets/lut.jpg -------------------------------------------------------------------------------- /assets/multimtl.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 2 3 | 4 | newmtl Material1 5 | Ns 225.000000 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.149896 0.800000 0.202048 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.450000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl Material2 15 | Ns 225.000000 16 | Ka 1.000000 1.000000 1.000000 17 | Kd 0.800000 0.093049 0.192718 18 | Ks 0.500000 0.500000 0.500000 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.450000 21 | d 1.000000 22 | illum 2 23 | -------------------------------------------------------------------------------- /assets/normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichi-dev/taichi_three/62596cf36fba1c5a528796c51942ce44ed76292a/assets/normal.png -------------------------------------------------------------------------------- /assets/orient.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.90.0 OBJ File: 'orient.blend' 2 | # www.blender.org 3 | mtllib orient.mtl 4 | o 立方体 5 | v -0.064687 -1.000000 0.085711 6 | v -0.064687 -1.000000 -0.085710 7 | v -0.064687 1.000000 0.085710 8 | v -0.064687 1.000000 -0.085711 9 | v 0.077301 -1.000000 0.085711 10 | v 0.077301 -1.000000 -0.085710 11 | v 0.077301 -0.006751 0.085710 12 | v 0.077301 -0.006751 -0.085711 13 | v 0.077301 0.119093 -0.085711 14 | v 0.077301 0.119093 0.085710 15 | v 0.077301 0.874156 0.085710 16 | v 0.077301 0.874156 -0.085711 17 | v 0.812300 0.119093 -0.085711 18 | v 0.812300 -0.006751 -0.085711 19 | v 0.812300 -0.006751 0.085710 20 | v 0.812300 0.119093 0.085710 21 | v 0.077301 0.874156 0.085710 22 | v 0.077301 0.874156 -0.085711 23 | v 1.001936 1.000000 -0.085711 24 | v 1.001936 1.000000 0.085710 25 | v 1.001936 0.874156 0.085710 26 | v 1.001936 0.874156 -0.085711 27 | vt 0.875000 0.500000 28 | vt 0.625000 0.610113 29 | vt 0.625000 0.625844 30 | vt 0.375000 0.625844 31 | vt 0.375000 0.750000 32 | vt 0.625000 0.750000 33 | vt 0.375000 1.000000 34 | vt 0.625000 0.500000 35 | vt 0.625000 0.250000 36 | vt 0.375000 0.250000 37 | vt 0.625000 0.610113 38 | vt 0.375000 0.610114 39 | vt 0.375000 0.625844 40 | vt 0.625000 0.625844 41 | vt 0.375000 0.610114 42 | vt 0.125000 0.500000 43 | vt 0.375000 0.515731 44 | vt 0.625000 0.515731 45 | vt 0.625000 0.515731 46 | vt 0.375000 0.500000 47 | vt 0.375000 0.515731 48 | vt 0.375000 0.515731 49 | vt 0.625000 0.515731 50 | vt 0.625000 0.000000 51 | vt 0.375000 0.000000 52 | vt 0.875000 0.750000 53 | vt 0.625000 1.000000 54 | vt 0.125000 0.750000 55 | vn -0.0000 -0.0000 -1.0000 56 | vn 1.0000 0.0000 -0.0000 57 | vn 0.0000 -1.0000 0.0000 58 | vn 0.0000 1.0000 -0.0000 59 | vn -0.0000 0.0000 1.0000 60 | vn -1.0000 0.0000 0.0000 61 | usemtl None 62 | s off 63 | f 4/1/1 9/2/1 8/3/1 64 | f 8/3/2 7/4/2 5/5/2 65 | f 6/6/3 5/5/3 1/7/3 66 | f 19/8/4 4/9/4 3/10/4 67 | f 13/11/2 16/12/2 15/13/2 68 | f 7/4/3 8/3/3 14/14/3 69 | f 9/2/4 10/15/4 16/12/4 70 | f 3/16/5 7/4/5 10/15/5 71 | f 11/17/5 12/18/5 18/19/5 72 | f 19/8/2 20/20/2 21/21/2 73 | f 17/22/3 18/19/3 22/23/3 74 | f 12/18/2 11/17/2 10/15/2 75 | f 4/9/6 2/24/6 1/25/6 76 | f 9/2/5 18/19/5 12/18/5 77 | f 18/19/1 19/8/1 22/23/1 78 | f 4/1/1 8/3/1 2/26/1 79 | f 2/26/1 8/3/1 6/6/1 80 | f 8/3/1 13/11/1 14/14/1 81 | f 18/19/1 4/1/1 19/8/1 82 | f 8/3/1 9/2/1 13/11/1 83 | f 9/2/1 4/1/1 18/19/1 84 | f 8/3/2 5/5/2 6/6/2 85 | f 6/6/3 1/7/3 2/27/3 86 | f 19/8/4 3/10/4 20/20/4 87 | f 13/11/2 15/13/2 14/14/2 88 | f 7/4/3 14/14/3 15/13/3 89 | f 9/2/4 16/12/4 13/11/4 90 | f 3/16/5 17/22/5 20/20/5 91 | f 20/20/5 17/22/5 21/21/5 92 | f 17/22/5 10/15/5 11/17/5 93 | f 5/5/5 7/4/5 1/28/5 94 | f 1/28/5 7/4/5 3/16/5 95 | f 16/12/5 7/4/5 15/13/5 96 | f 3/16/5 10/15/5 17/22/5 97 | f 10/15/5 7/4/5 16/12/5 98 | f 11/17/5 18/19/5 17/22/5 99 | f 19/8/2 21/21/2 22/23/2 100 | f 17/22/3 22/23/3 21/21/3 101 | f 12/18/2 10/15/2 9/2/2 102 | f 4/9/6 1/25/6 3/10/6 103 | o 平面_平面.001 104 | v -0.065454 0.123447 0.153313 105 | v 0.079101 0.123447 0.153313 106 | v -0.065454 -0.021108 0.153313 107 | v 0.079101 -0.021108 0.153313 108 | v -0.065454 0.123447 0.104426 109 | v 0.079101 0.123447 0.104426 110 | v -0.065454 -0.021108 0.104426 111 | v 0.079101 -0.021108 0.104426 112 | vt 0.000000 0.000000 113 | vt 0.000000 1.000000 114 | vt 1.000000 1.000000 115 | vt 1.000000 0.000000 116 | vt 0.000000 0.000000 117 | vt 1.000000 0.000000 118 | vt 1.000000 1.000000 119 | vt 0.000000 1.000000 120 | vn 0.0000 0.0000 1.0000 121 | vn 0.0000 0.0000 -1.0000 122 | vn 0.0000 -1.0000 0.0000 123 | vn 0.0000 1.0000 0.0000 124 | vn 1.0000 0.0000 0.0000 125 | vn -1.0000 0.0000 0.0000 126 | usemtl None 127 | s off 128 | f 23/29/7 25/30/7 26/31/7 24/32/7 129 | f 27/33/8 28/34/8 30/35/8 29/36/8 130 | f 26/31/9 25/30/9 29/36/9 30/35/9 131 | f 23/29/10 24/32/10 28/34/10 27/33/10 132 | f 24/32/11 26/31/11 30/35/11 28/34/11 133 | f 25/30/12 23/29/12 27/33/12 29/36/12 134 | -------------------------------------------------------------------------------- /assets/pattern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichi-dev/taichi_three/62596cf36fba1c5a528796c51942ce44ed76292a/assets/pattern.jpg -------------------------------------------------------------------------------- /assets/plane.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.90.0 OBJ File: '' 2 | # www.blender.org 3 | o Plane 4 | v -1.000000 0.000000 1.000000 5 | v 1.000000 0.000000 1.000000 6 | v -1.000000 0.000000 -1.000000 7 | v 1.000000 0.000000 -1.000000 8 | vt 0.000000 0.000000 9 | vt 1.000000 0.000000 10 | vt 1.000000 1.000000 11 | vt 0.000000 1.000000 12 | vn 0.0000 1.0000 0.0000 13 | s off 14 | f 1/1/1 2/2/1 4/3/1 3/4/1 15 | -------------------------------------------------------------------------------- /assets/shadow.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'shadow.blend' 2 | # Material Count: 2 3 | 4 | newmtl Material 5 | Ns 323.999994 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.800000 0.800000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.0 0.0 0.0 10 | Ni 1.450000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl None 15 | Ns 500 16 | Ka 0.8 0.8 0.8 17 | Kd 0.8 0.8 0.8 18 | Ks 0.8 0.8 0.8 19 | d 1 20 | illum 2 21 | -------------------------------------------------------------------------------- /assets/shadow.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.80 (sub 75) OBJ File: 'shadow.blend' 2 | # www.blender.org 3 | mtllib shadow.mtl 4 | o Cube 5 | v 0.303343 -0.008899 -0.428899 6 | v 0.303343 -0.428899 0.008899 7 | v 0.303343 0.428899 -0.008899 8 | v 0.303343 0.008899 0.428899 9 | v -0.303343 -0.008899 -0.428899 10 | v -0.303343 -0.428899 0.008899 11 | v -0.303343 0.428899 -0.008899 12 | v -0.303343 0.008899 0.428899 13 | vt 0.375000 0.000000 14 | vt 0.625000 0.000000 15 | vt 0.625000 0.250000 16 | vt 0.375000 0.250000 17 | vt 0.375000 0.250000 18 | vt 0.625000 0.250000 19 | vt 0.625000 0.500000 20 | vt 0.375000 0.500000 21 | vt 0.625000 0.750000 22 | vt 0.375000 0.750000 23 | vt 0.625000 0.750000 24 | vt 0.625000 1.000000 25 | vt 0.375000 1.000000 26 | vt 0.125000 0.500000 27 | vt 0.375000 0.500000 28 | vt 0.375000 0.750000 29 | vt 0.125000 0.750000 30 | vt 0.625000 0.500000 31 | vt 0.875000 0.500000 32 | vt 0.875000 0.750000 33 | vn 0.0000 0.6923 -0.7216 34 | vn 0.0000 0.7216 0.6923 35 | vn -1.0000 0.0000 0.0000 36 | vn 0.0000 -0.6923 0.7216 37 | vn 1.0000 -0.0000 0.0000 38 | vn 0.0000 -0.7216 -0.6923 39 | usemtl Material 40 | s off 41 | f 1/1/1 5/2/1 7/3/1 3/4/1 42 | f 4/5/2 3/6/2 7/7/2 8/8/2 43 | f 8/8/3 7/7/3 5/9/3 6/10/3 44 | f 6/10/4 2/11/4 4/12/4 8/13/4 45 | f 2/14/5 1/15/5 3/16/5 4/17/5 46 | f 6/18/6 5/19/6 1/20/6 2/11/6 47 | o Plane 48 | v -2.000000 0.750960 2.106195 49 | v 2.000000 0.750960 2.106195 50 | v -2.000000 -2.135532 -0.662949 51 | v 2.000000 -2.135532 -0.662949 52 | vt 0.000000 0.000000 53 | vt 1.000000 0.000000 54 | vt 1.000000 1.000000 55 | vt 0.000000 1.000000 56 | vn 0.0000 0.6923 -0.7216 57 | usemtl None 58 | s off 59 | f 9/21/7 10/22/7 12/23/7 11/24/7 60 | -------------------------------------------------------------------------------- /assets/skybox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichi-dev/taichi_three/62596cf36fba1c5a528796c51942ce44ed76292a/assets/skybox.jpg -------------------------------------------------------------------------------- /assets/smallptwall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichi-dev/taichi_three/62596cf36fba1c5a528796c51942ce44ed76292a/assets/smallptwall.png -------------------------------------------------------------------------------- /bench.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | 4 | ti.init(ti.gpu) 5 | 6 | 7 | nx, ny = 512, 512 8 | 9 | 10 | x = ti.field(float, (2, nx, ny // 2)) 11 | r = ti.field(float, (2, nx, ny // 2)) 12 | 13 | 14 | @ti.pyfunc 15 | def at(i, j): 16 | return (i + j) % 2, i, j // 2 17 | 18 | 19 | @ti.kernel 20 | def solve(): 21 | for t in ti.static(range(2)): 22 | for i, j in ti.ndrange(nx, ny): 23 | if (i + j) % 2 != t: 24 | continue 25 | 26 | xl = x[at(i - 1, j)] 27 | xr = x[at(i + 1, j)] 28 | xb = x[at(i, j - 1)] 29 | xt = x[at(i, j + 1)] 30 | div = r[at(i, j)] 31 | xc = (xl + xr + xb + xt - div) * 0.25 32 | x[at(i, j)] = xc 33 | 34 | 35 | @ti.kernel 36 | def dump(out: ti.ext_arr()): 37 | for i, j in ti.ndrange(nx, ny): 38 | out[i, j] = x[at(i, j)] 39 | 40 | 41 | r[at(256, 256)] = -2 42 | gui = ti.GUI('jacobi', (nx, ny)) 43 | while gui.running and not gui.get_event(gui.ESCAPE): 44 | for i in range(200): 45 | solve() 46 | out = np.empty((nx, ny), dtype=np.float32) 47 | dump(out) 48 | gui.set_image(out) 49 | gui.show() 50 | -------------------------------------------------------------------------------- /conv.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | 4 | 5 | A = np.array([ 6 | [0, 1, 0], 7 | [1, 0, 1], 8 | [0, 1, 0], 9 | ]) 10 | 11 | 12 | def conv(A, B): 13 | m, n = A.shape 14 | s, t = B.shape 15 | C = np.zeros((m + s - 1, n + t - 1), dtype=A.dtype) 16 | for i in range(m): 17 | for j in range(n): 18 | for k in range(s): 19 | for l in range(t): 20 | C[i + k, j + l] += A[i, j] * B[k, l] 21 | return C 22 | 23 | 24 | B = A 25 | print(B) 26 | B = conv(B, A) 27 | print(B) 28 | B = conv(B, A) 29 | print(B) 30 | B = conv(B, A) 31 | print(B) 32 | -------------------------------------------------------------------------------- /docs/connect.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.gpu) 5 | 6 | verts, faces = tina.readobj('assets/monkey.obj', simple=True) 7 | 8 | scene = tina.Scene() 9 | mesh = tina.ConnectiveMesh() 10 | scene.add_object(mesh, tina.Classic()) 11 | 12 | mesh.set_vertices(verts) 13 | mesh.set_faces(faces) 14 | 15 | 16 | gui = ti.GUI() 17 | while gui.running: 18 | scene.input(gui) 19 | verts[100, 1] = ti.sin(gui.frame * 0.02) 20 | mesh.set_vertices(verts) 21 | scene.render() 22 | gui.set_image(scene.img) 23 | gui.show() 24 | -------------------------------------------------------------------------------- /docs/gltf.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.cpu) 5 | 6 | scene = tina.Scene(smoothing=True, texturing=True) 7 | scene.load_gltf('assets/cornell.gltf') 8 | #scene.load_gltf('/home/bate/Downloads/军用飞机/DamagedHelmet.gltf') 9 | 10 | gui = ti.GUI('gltf') 11 | scene.init_control(gui, center=(0, 2, 0), radius=6) 12 | 13 | while gui.running: 14 | scene.input(gui) 15 | scene.render() 16 | gui.set_image(scene.img) 17 | gui.show() 18 | -------------------------------------------------------------------------------- /docs/lighting.py: -------------------------------------------------------------------------------- 1 | # In this episode, you'll learn how to use lights and materials in Tina 2 | # 3 | # This tutorial is based on docs/monkey.py, make sure you check that first 4 | 5 | import taichi as ti 6 | import tina 7 | 8 | ti.init(ti.gpu) 9 | 10 | scene = tina.Scene() 11 | 12 | # 5. Material - for describing the material of an object 13 | material = tina.PBR(metallic=0.6, roughness=0.2) 14 | # parameters may also be specified by textures (add texturing=True to Scene) 15 | #material = tina.PBR(basecolor=tina.Texture('assets/cloth.jpg')) 16 | 17 | model = tina.MeshModel('assets/monkey.obj') 18 | # load our model into the scene with material specified: 19 | scene.add_object(model, material) 20 | 21 | gui = ti.GUI('lighting') 22 | 23 | # now, let's add some custom light sources into the scene for test 24 | # 25 | # first of all, remove the 'default light' from scene: 26 | scene.lighting.clear_lights() 27 | # adds a directional light with direction (0, 0, 1), with white color 28 | # the direction will be automatically normalized to obtain desired result 29 | scene.lighting.add_light(dir=[0, 0, 1], color=[1, 1, 1]) 30 | # adds a point light at position (1, 1.5, 0.3), with red color 31 | scene.lighting.add_light(pos=[1, 1.5, 0.3], color=[1, 0, 0]) 32 | # specifies the ambient color to be dark green 33 | scene.lighting.set_ambient_light([0, 0.06, 0]) 34 | 35 | while gui.running: 36 | scene.input(gui) 37 | scene.render() 38 | gui.set_image(scene.img) 39 | gui.show() 40 | -------------------------------------------------------------------------------- /docs/monkey.py: -------------------------------------------------------------------------------- 1 | # Tina is a real-time soft renderer based on Taichi for visualizing 3D scenes. 2 | # 3 | # To get started, let's try to load and display a monkey model in the GUI. 4 | 5 | import taichi as ti 6 | import tina 7 | 8 | ti.init(ti.gpu) # use GPU backend for better speed 9 | 10 | # to make tina actually display things, we need at least three things: 11 | # 12 | # 1. Scene - the top structure that manages all resources in the scene 13 | scene = tina.Scene() 14 | 15 | # 2. Model - the model to be displayed 16 | # 17 | # here we use `tina.MeshModel` which can load models from OBJ format files 18 | model = tina.MeshModel('assets/monkey.obj') 19 | # and, don't forget to add the model into the scene so that it gets displayed 20 | scene.add_object(model) 21 | 22 | # 3. GUI - we also need to create an window for display 23 | gui = ti.GUI('monkey') 24 | 25 | while gui.running: 26 | # update the camera transform from mouse events (will invoke gui.get_events) 27 | scene.input(gui) 28 | 29 | # render scene to image 30 | scene.render() 31 | 32 | # show the image in GUI 33 | gui.set_image(scene.img) 34 | gui.show() 35 | -------------------------------------------------------------------------------- /docs/options.py: -------------------------------------------------------------------------------- 1 | # In this episode, you'll learn some basic options to specify for a Tina scene. 2 | # 3 | # This tutorial is based on docs/monkey.py, make sure you check that first 4 | 5 | import taichi as ti 6 | import tina 7 | 8 | ti.init(ti.gpu) 9 | 10 | # There are some options you may specify to tina.Scene, try turn off some 11 | # of them and see what's the difference 12 | # 13 | # culling: enable face culling for better performance (default: on) 14 | # clipping: enable view space clipping for rid objects out of depth (default: off) 15 | # smoothing: enable smooth shading by interpolating normals (default: off) 16 | # texturing: enable texture coordinates, see docs/texture.py (default: off) 17 | # taa: temporal anti-aliasing, won't work well for dynamic scenes (default: off) 18 | scene = tina.Scene(smoothing=True, taa=True) 19 | 20 | # (make sure your OBJ model have normals to make smooth shading work) 21 | model = tina.MeshModel('assets/torus.obj') 22 | scene.add_object(model) 23 | 24 | gui = ti.GUI('options') 25 | 26 | while gui.running: 27 | scene.input(gui) 28 | scene.render() 29 | gui.set_image(scene.img) 30 | gui.show() 31 | -------------------------------------------------------------------------------- /docs/particles.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.Scene() 8 | 9 | pars = tina.SimpleParticles() 10 | material = tina.Classic() 11 | scene.add_object(pars, material) 12 | 13 | gui = ti.GUI('particles') 14 | 15 | pos = np.random.rand(1024, 3).astype(np.float32) * 2 - 1 16 | pars.set_particles(pos) 17 | radius = np.random.rand(1024).astype(np.float32) * 0.1 + 0.1 18 | pars.set_particle_radii(radius) 19 | color = np.random.rand(1024, 3).astype(np.float32) * 0.8 + 0.2 20 | pars.set_particle_colors(color) 21 | 22 | while gui.running: 23 | scene.input(gui) 24 | scene.render() 25 | gui.set_image(scene.img) 26 | gui.show() 27 | -------------------------------------------------------------------------------- /docs/pathtrace.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene(smoothing=True, texturing=True) 8 | scene.lighting.skybox = tina.Atomsphere() 9 | material = tina.Phong() 10 | mesh = tina.MeshModel('assets/monkey.obj') 11 | scene.add_object(mesh, material) 12 | pars = tina.SimpleParticles() 13 | scene.add_object(pars, material) 14 | pars.set_particles(np.random.rand(2**10, 3) * 2 - 1) 15 | 16 | gui = ti.GUI('pathtrace', scene.res) 17 | 18 | scene.update() 19 | while gui.running: 20 | scene.input(gui) 21 | scene.render(nsteps=8) 22 | gui.set_image(scene.img) 23 | gui.show() 24 | -------------------------------------------------------------------------------- /docs/primitives.py: -------------------------------------------------------------------------------- 1 | import tina 2 | 3 | scene = tina.Scene(smoothing=True, texturing=True, taa=True) 4 | 5 | #mesh = tina.PrimitiveMesh.sphere() 6 | mesh = tina.PrimitiveMesh.cylinder() 7 | wire = tina.MeshToWire(mesh) 8 | scene.add_object(mesh) 9 | scene.add_object(wire) 10 | 11 | gui = tina.ti.GUI('primitives') 12 | 13 | while gui.running: 14 | scene.input(gui) 15 | scene.render() 16 | gui.set_image(scene.img) 17 | gui.show() 18 | -------------------------------------------------------------------------------- /docs/smooth.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.gpu) 5 | 6 | scene = tina.Scene(smoothing=True) 7 | 8 | model = tina.MeshSmoothNormal(tina.MeshModel('assets/monkey.obj')) 9 | scene.add_object(model) 10 | 11 | gui = ti.GUI('smooth') 12 | 13 | while gui.running: 14 | scene.input(gui) 15 | scene.render() 16 | gui.set_image(scene.img) 17 | gui.show() 18 | -------------------------------------------------------------------------------- /docs/transform.py: -------------------------------------------------------------------------------- 1 | # In this episode, you'll learn how to make use of nodes for transformation 2 | # 3 | # This tutorial is based on docs/monkey.py, make sure you check that first 4 | 5 | import taichi as ti 6 | import tina 7 | 8 | ti.init(ti.gpu) 9 | 10 | scene = tina.Scene() 11 | 12 | # load the monkey model with the `tina.MeshModel` node: 13 | model = tina.MeshModel('assets/monkey.obj') 14 | # transform the mesh using the `tina.Transform` node: 15 | tmodel = tina.MeshTransform(model) 16 | # load the desired node to be displayed: 17 | scene.add_object(tmodel) 18 | #scene.add_object(model) 19 | 20 | gui = ti.GUI('transform') 21 | 22 | while gui.running: 23 | scene.input(gui) 24 | 25 | # create a matrix representing translation along X-axis 26 | dx = ti.sin(gui.frame * 0.03) 27 | matrix = tina.translate([dx, 0, 0]) 28 | # set the model transformation matrix for `tina.Transform` node 29 | tmodel.set_transform(matrix) 30 | 31 | scene.render() 32 | gui.set_image(scene.img) 33 | gui.show() 34 | -------------------------------------------------------------------------------- /docs/triangle.py: -------------------------------------------------------------------------------- 1 | # Tina is a real-time soft renderer based on Taichi for visualizing 3D scenes. 2 | # 3 | # To get started, let's try to make a simple triangle and display it in the GUI. 4 | 5 | import taichi as ti 6 | import numpy as np 7 | import tina 8 | 9 | # Tina use a right-handed coordinate system in world space: 10 | # 11 | # +Z: back. 12 | # +X: right. 13 | # +Y: up. 14 | # 15 | # The camera looks from +Z to target by default. 16 | 17 | # make a simple triangle by specifying the vertices 18 | verts = np.array([[ 19 | [-1, -1, 0], # vertex 1 20 | [ 1, -1, 0], # vertex 2 21 | [ 0, 1, 0], # vertex 3 22 | ]]) 23 | # also note that face vertices needs to be **counter-clockwise** to be visible 24 | # you may disable such face culling policy by using tina.Scene(culling=False) 25 | 26 | # to make tina actually display things, we need at least three things: 27 | # 28 | # 1. Scene - the top structure that manages all resources in the scene 29 | scene = tina.Scene() 30 | 31 | # 2. Model - the model to be displayed 32 | # 33 | # here we use `tina.SimpleMesh` which allows use to specify the vertices manually 34 | mesh = tina.SimpleMesh() 35 | # and, don't forget to add the object into the scene so that it gets displayed 36 | scene.add_object(mesh) 37 | 38 | # 3. GUI - we also need to create an window for display 39 | gui = ti.GUI('triangle') 40 | 41 | while gui.running: 42 | # update the camera transform from mouse events (will invoke gui.get_events) 43 | scene.input(gui) 44 | 45 | # set face vertices by feeding a numpy array into it 46 | mesh.set_face_verts(verts) 47 | # render image with objects (a triangle in this case) in scene 48 | scene.render() 49 | 50 | # show the image in GUI 51 | gui.set_image(scene.img) 52 | gui.show() 53 | -------------------------------------------------------------------------------- /docs/volume.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | dens = np.load('assets/smoke.npy') 8 | scene = tina.Scene(N=dens.shape[0], taa=True, density=16) 9 | volume = tina.SimpleVolume(N=dens.shape[0]) 10 | #model = tina.MeshModel('assets/monkey.obj') 11 | #scene.add_object(model, tina.CookTorrance(metallic=0.8)) 12 | scene.add_object(volume) 13 | 14 | gui = ti.GUI('volume', scene.res) 15 | 16 | volume.set_volume_density(dens) 17 | while gui.running: 18 | scene.input(gui) 19 | scene.render() 20 | gui.set_image(scene.img) 21 | gui.show() 22 | -------------------------------------------------------------------------------- /docs/wireframe.py: -------------------------------------------------------------------------------- 1 | # In this episode, you'll learn how to render a wireframe model in Tina 2 | # 3 | # This tutorial is based on docs/monkey.py, make sure you check that first 4 | 5 | import taichi as ti 6 | import tina 7 | 8 | ti.init(ti.cpu) 9 | 10 | # you may specify the line width for rendering wireframes: 11 | # taa=True turns on Temporal Anti-Aliasing to make lines smoother 12 | scene = tina.Scene(linewidth=2, taa=True) 13 | 14 | # load the monkey using `tina.MeshModel` node (`tina.SimpleMesh` works too): 15 | model = tina.MeshModel('assets/monkey.obj') 16 | # convert the mesh to its wireframe using the `tina.MeshToWire` node: 17 | wiremodel = tina.MeshToWire(model) 18 | 19 | # add the wireframe model into scene: 20 | scene.add_object(wiremodel) 21 | 22 | # add the original model, with a tiny scale: 23 | model = tina.MeshTransform(model, tina.scale(0.9)) 24 | scene.add_object(model) 25 | 26 | gui = ti.GUI('wireframe') 27 | 28 | while gui.running: 29 | scene.input(gui) 30 | scene.render() 31 | gui.set_image(scene.img) 32 | gui.show() 33 | -------------------------------------------------------------------------------- /examples/cornell_box.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene(smoothing=True, texturing=True) 8 | scene.load_gltf('assets/cornell.gltf') 9 | scene.add_object(tina.MeshTransform(tina.MeshModel('assets/plane.obj'), 10 | tina.translate([0, 3.98, 0]) @ tina.scale(0.1)), tina.Lamp(color=64)) 11 | 12 | if isinstance(scene, tina.PTScene): 13 | scene.update() 14 | 15 | gui = ti.GUI('cornell_box', scene.res) 16 | scene.init_control(gui, center=(0, 2, 0), radius=5) 17 | 18 | while gui.running: 19 | scene.input(gui) 20 | if isinstance(scene, tina.PTScene): 21 | scene.render(nsteps=8) 22 | else: 23 | scene.render() 24 | gui.set_image(scene.img) 25 | gui.show() 26 | 27 | ti.imwrite(scene.img, 'cornell.png') 28 | -------------------------------------------------------------------------------- /examples/ibl_matball.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.gpu) 5 | 6 | scene = tina.Scene(smoothing=True, taa=True, ibl=True) 7 | 8 | roughness = tina.Param(float, initial=0)#.15) 9 | metallic = tina.Param(float, initial=0)#.25) 10 | material = tina.PBR(metallic=metallic, roughness=roughness) 11 | 12 | #model = tina.PrimitiveMesh.sphere() 13 | model = tina.MeshModel('assets/bunny.obj') 14 | scene.add_object(model, material) 15 | 16 | gui = ti.GUI('matball') 17 | roughness.make_slider(gui, 'roughness') 18 | metallic.make_slider(gui, 'metallic') 19 | 20 | while gui.running: 21 | scene.input(gui) 22 | scene.render() 23 | gui.set_image(scene.img) 24 | gui.show() 25 | -------------------------------------------------------------------------------- /examples/matball.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.cpu) 5 | 6 | scene = tina.Scene(smoothing=True) 7 | 8 | metallic = tina.Param(float, initial=0.0) 9 | specular = tina.Param(float, initial=0.5) 10 | roughness = tina.Param(float, initial=0.4) 11 | material = tina.PBR(metallic=metallic, roughness=roughness, specular=specular) 12 | 13 | #shineness = tina.Param(float, initial=32) 14 | #specular = tina.Param(float, initial=0.5) 15 | #material = tina.Classic(shineness=shineness, specular=specular) 16 | 17 | model = tina.PrimitiveMesh.sphere() 18 | scene.add_object(model, material) 19 | 20 | gui = ti.GUI('matball') 21 | if 'roughness' in globals(): 22 | roughness.make_slider(gui, 'roughness') 23 | if 'metallic' in globals(): 24 | metallic.make_slider(gui, 'metallic') 25 | if 'shineness' in globals(): 26 | shineness.make_slider(gui, 'shineness', 1, 500, 1) 27 | if 'specular' in globals(): 28 | specular.make_slider(gui, 'specular') 29 | 30 | while gui.running: 31 | scene.input(gui) 32 | scene.render() 33 | gui.set_image(scene.img) 34 | gui.show() 35 | -------------------------------------------------------------------------------- /examples/mciso_mpm3d.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(arch=ti.gpu) 6 | 7 | 8 | #dim, n_grid, steps, dt = 3, 32, 25, 4e-4 9 | dim, n_grid, steps, dt = 3, 64, 10, 2e-4 10 | #dim, n_grid, steps, dt = 3, 128, 30, 8e-5 11 | 12 | n_particles = n_grid**dim // 2**(dim - 1) 13 | dx = 1 / n_grid 14 | 15 | print(f'n_particles={n_particles}') 16 | 17 | p_rho = 1 18 | p_vol = (dx * 0.5)**2 19 | p_mass = p_vol * p_rho 20 | gravity = 9.8 21 | bound = 4 22 | E = 400 23 | 24 | x = ti.Vector.field(dim, float, n_particles) 25 | v = ti.Vector.field(dim, float, n_particles) 26 | C = ti.Matrix.field(dim, dim, float, n_particles) 27 | J = ti.field(float, n_particles) 28 | 29 | grid_v = ti.Vector.field(dim, float, (n_grid, ) * dim) 30 | grid_m = ti.field(float, (n_grid, ) * dim) 31 | 32 | neighbour = (3, ) * dim 33 | 34 | 35 | @ti.func 36 | def list_subscript(a, i): 37 | '''magic method to subscript a list with dynamic index''' 38 | ret = sum(a) * 0 39 | for j in ti.static(range(len(a))): 40 | if i == j: 41 | ret = a[j] 42 | return ret 43 | 44 | 45 | @ti.kernel 46 | def substep(): 47 | for I in ti.grouped(grid_m): 48 | grid_v[I] = ti.zero(grid_v[I]) 49 | grid_m[I] = 0 50 | ti.block_dim(n_grid) 51 | for p in x: 52 | Xp = x[p] / dx 53 | base = int(Xp - 0.5) 54 | fx = Xp - base 55 | w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] 56 | stress = -dt * 4 * E * p_vol * (J[p] - 1) / dx**2 57 | affine = ti.Matrix.identity(float, dim) * stress + p_mass * C[p] 58 | for offset in ti.grouped(ti.ndrange(*neighbour)): 59 | dpos = (offset - fx) * dx 60 | weight = 1.0 61 | for i in ti.static(range(dim)): 62 | weight *= list_subscript(w, offset[i])[i] 63 | grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) 64 | grid_m[base + offset] += weight * p_mass 65 | for I in ti.grouped(grid_m): 66 | if grid_m[I] > 0: 67 | grid_v[I] /= grid_m[I] 68 | grid_v[I][1] -= dt * gravity 69 | cond = I < bound and grid_v[I] < 0 or I > n_grid - bound and grid_v[ 70 | I] > 0 71 | grid_v[I] = 0 if cond else grid_v[I] 72 | ti.block_dim(n_grid) 73 | for p in x: 74 | Xp = x[p] / dx 75 | base = int(Xp - 0.5) 76 | fx = Xp - base 77 | w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] 78 | new_v = ti.zero(v[p]) 79 | new_C = ti.zero(C[p]) 80 | for offset in ti.grouped(ti.ndrange(*neighbour)): 81 | dpos = (offset - fx) * dx 82 | weight = 1.0 83 | for i in ti.static(range(dim)): 84 | weight *= list_subscript(w, offset[i])[i] 85 | g_v = grid_v[base + offset] 86 | new_v += weight * g_v 87 | new_C += 4 * weight * g_v.outer_product(dpos) / dx**2 88 | v[p] = new_v 89 | x[p] += dt * v[p] 90 | J[p] *= 1 + dt * new_C.trace() 91 | C[p] = new_C 92 | 93 | 94 | @ti.kernel 95 | def init(): 96 | for i in range(n_particles): 97 | x[i] = ti.Vector([ti.random() for i in range(dim)]) * 0.4 + 0.15 98 | v[i] *= 0 99 | J[i] = 1 100 | 101 | 102 | mciso = tina.MCISO((n_grid, n_grid, n_grid)) 103 | 104 | scene = tina.Scene(smoothing=True, maxfaces=2**18, ibl=True, ssao=True) 105 | material = tina.PBR(metallic=0.15, roughness=0.0) 106 | scene.add_object(mciso, material) 107 | 108 | #boundbox = tina.MeshToWire(tina.MeshTransform(tina.MeshModel('assets/cube.obj'), tina.scale(0.5) @ tina.translate(1))) 109 | #scene.add_object(boundbox) 110 | 111 | gui = ti.GUI('mciso_mpm3d', scene.res) 112 | scene.init_control(gui, center=[0.5, 0.5, 0.5], radius=1.5) 113 | 114 | if not scene.ibl: 115 | scene.lighting.clear_lights() 116 | scene.lighting.add_light([-0.4, 1.5, 1.8], color=[0.8, 0.8, 0.8]) 117 | scene.lighting.set_ambient_light([0.22, 0.22, 0.22]) 118 | 119 | init() 120 | while gui.running: 121 | scene.input(gui) 122 | 123 | if gui.is_pressed('r'): 124 | init() 125 | for s in range(steps): 126 | substep() 127 | 128 | mciso.march(x, w0=2, rad=0, sig=0) 129 | 130 | # tina.writeobj(f'/tmp/{gui.frame:04d}.obj', tina.export_connective_mesh(mciso)) 131 | # np.save(f'/tmp/{gui.frame:04d}.npy', x.to_numpy()) 132 | 133 | scene.render() 134 | gui.set_image(scene.img) 135 | gui.show() 136 | -------------------------------------------------------------------------------- /examples/meshgrid_cloth.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(arch=ti.gpu) 6 | 7 | ### Parameters 8 | 9 | N = 128 10 | NN = N, N 11 | W = 1 12 | L = W / N 13 | gravity = 0.5 14 | stiffness = 1600 15 | ball_radius = 0.4 16 | damping = 2 17 | steps = 30 18 | dt = 5e-4 19 | 20 | ### Physics 21 | 22 | x = ti.Vector.field(3, float, NN) 23 | v = ti.Vector.field(3, float, NN) 24 | ball_pos = ti.Vector.field(3, float, ()) 25 | ball_vel = ti.Vector.field(3, float, ()) 26 | 27 | 28 | @ti.kernel 29 | def init(): 30 | ball_pos[None] = ti.Vector([0.0, +0.0, 0.0]) 31 | for i in ti.grouped(x): 32 | m, n = (i + 0.5) * L - 0.5 33 | x[i] = ti.Vector([m, 0.6, n]) 34 | v[i] = ti.Vector([0.0, 0.0, 0.0]) 35 | 36 | 37 | links = [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (1, -1), (-1, 1), (1, 1)] 38 | links = [ti.Vector(_) for _ in links] 39 | 40 | @ti.kernel 41 | def substep(): 42 | ball_pos[None] += ball_vel[None] * dt 43 | 44 | for i in ti.grouped(x): 45 | acc = x[i] * 0 46 | for d in ti.static(links): 47 | disp = x[min(max(i + d, 0), ti.Vector(NN) - 1)] - x[i] 48 | length = L * float(d).norm() 49 | acc += disp * (disp.norm() - length) / length**2 50 | v[i] += stiffness * acc * dt 51 | 52 | for i in ti.grouped(x): 53 | v[i].y -= gravity * dt 54 | dp = x[i] - ball_pos[None] 55 | dp2 = dp.norm_sqr() 56 | if dp2 <= ball_radius**2: 57 | # a fun execise left for you: add angular velocity to the ball? 58 | # it should drag the cloth around with a specific friction rate. 59 | dv = v[i] - ball_vel[None] 60 | NoV = dv.dot(dp) 61 | if NoV < 0: 62 | v[i] -= NoV * dp / dp2 63 | 64 | for i in ti.grouped(x): 65 | v[i] *= ti.exp(-damping * dt) 66 | x[i] += dt * v[i] 67 | 68 | 69 | ### Rendering GUI 70 | 71 | # Hint: remove ibl=True if you find it compiles too slow... 72 | scene = tina.Scene((1024, 768), smoothing=True, texturing=True, ibl=True) 73 | 74 | mesh = tina.MeshNoCulling(tina.MeshGrid((N, N))) 75 | ball = tina.MeshTransform(tina.PrimitiveMesh.sphere()) 76 | 77 | cloth = tina.PBR(basecolor=tina.ChessboardTexture(size=0.2)) 78 | metal = tina.PBR(basecolor=[1.0, 0.9, 0.8], metallic=0.8, roughness=0.4) 79 | 80 | scene.add_object(mesh, cloth) 81 | scene.add_object(ball, metal) 82 | 83 | 84 | gui = ti.GUI('Mass Spring', scene.res, fast_gui=True) 85 | scene.init_control(gui, 86 | center=[0.0, 0.0, 0.0], 87 | theta=np.pi / 2 - np.radians(30), 88 | radius=1.5) 89 | 90 | init() 91 | 92 | print('[Hint] Press ASWD to move the ball, R to reset') 93 | while gui.running: 94 | scene.input(gui) 95 | 96 | if not gui.is_pressed(gui.SPACE): 97 | for i in range(steps): 98 | substep() 99 | 100 | if gui.is_pressed('r'): 101 | init() 102 | 103 | if gui.is_pressed('w'): 104 | ball_vel[None] = (0, +1, 0) 105 | elif gui.is_pressed('s'): 106 | ball_vel[None] = (0, -1, 0) 107 | elif gui.is_pressed('a'): 108 | ball_vel[None] = (-1, 0, 0) 109 | elif gui.is_pressed('d'): 110 | ball_vel[None] = (+1, 0, 0) 111 | else: 112 | ball_vel[None] = (0, 0, 0) 113 | 114 | mesh.pos.copy_from(x) 115 | ball.set_transform(tina.translate(ball_pos[None].value) @ tina.scale(ball_radius)) 116 | 117 | scene.render() 118 | gui.set_image(scene.img) 119 | gui.show() 120 | -------------------------------------------------------------------------------- /examples/meshgrid_wave.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import time 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | 8 | @ti.func 9 | def Z(xy, t): 10 | return 0.1 * ti.sin(10 * xy.norm() - ti.tau * t) 11 | 12 | 13 | @ti.kernel 14 | def deform_mesh(t: float): 15 | for i, j in mesh.pos: 16 | mesh.pos[i, j].z = Z(mesh.pos[i, j].xy, t) 17 | 18 | 19 | scene = tina.Scene(smoothing=True) 20 | 21 | mesh = tina.MeshNoCulling(tina.MeshGrid(64)) 22 | scene.add_object(mesh) 23 | 24 | gui = ti.GUI('meshgrid_wave', scene.res) 25 | 26 | while gui.running: 27 | scene.input(gui) 28 | 29 | deform_mesh(gui.frame * 0.01) 30 | 31 | scene.render() 32 | gui.set_image(scene.img) 33 | gui.show() 34 | -------------------------------------------------------------------------------- /examples/pars_mpm3d.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(arch=ti.gpu) 6 | 7 | 8 | dim, n_grid, steps, dt = 3, 32, 25, 4e-4 9 | #dim, n_grid, steps, dt = 3, 64, 10, 2e-4 10 | #dim, n_grid, steps, dt = 3, 128, 30, 8e-5 11 | 12 | n_particles = n_grid**dim // 2**(dim - 1) 13 | dx = 1 / n_grid 14 | 15 | print(f'n_particles={n_particles}') 16 | 17 | p_rho = 1 18 | p_vol = (dx * 0.5)**2 19 | p_mass = p_vol * p_rho 20 | gravity = 9.8 21 | bound = 4 22 | E = 400 23 | 24 | x = ti.Vector.field(dim, float, n_particles) 25 | v = ti.Vector.field(dim, float, n_particles) 26 | C = ti.Matrix.field(dim, dim, float, n_particles) 27 | J = ti.field(float, n_particles) 28 | 29 | grid_v = ti.Vector.field(dim, float, (n_grid, ) * dim) 30 | grid_m = ti.field(float, (n_grid, ) * dim) 31 | 32 | neighbour = (3, ) * dim 33 | 34 | 35 | @ti.func 36 | def list_subscript(a, i): 37 | '''magic method to subscript a list with dynamic index''' 38 | ret = sum(a) * 0 39 | for j in ti.static(range(len(a))): 40 | if i == j: 41 | ret = a[j] 42 | return ret 43 | 44 | 45 | @ti.kernel 46 | def substep(): 47 | for I in ti.grouped(grid_m): 48 | grid_v[I] = ti.zero(grid_v[I]) 49 | grid_m[I] = 0 50 | ti.block_dim(n_grid) 51 | for p in x: 52 | Xp = x[p] / dx 53 | base = int(Xp - 0.5) 54 | fx = Xp - base 55 | w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] 56 | stress = -dt * 4 * E * p_vol * (J[p] - 1) / dx**2 57 | affine = ti.Matrix.identity(float, dim) * stress + p_mass * C[p] 58 | for offset in ti.grouped(ti.ndrange(*neighbour)): 59 | dpos = (offset - fx) * dx 60 | weight = 1.0 61 | for i in ti.static(range(dim)): 62 | weight *= list_subscript(w, offset[i])[i] 63 | grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) 64 | grid_m[base + offset] += weight * p_mass 65 | for I in ti.grouped(grid_m): 66 | if grid_m[I] > 0: 67 | grid_v[I] /= grid_m[I] 68 | grid_v[I][1] -= dt * gravity 69 | cond = I < bound and grid_v[I] < 0 or I > n_grid - bound and grid_v[ 70 | I] > 0 71 | grid_v[I] = 0 if cond else grid_v[I] 72 | ti.block_dim(n_grid) 73 | for p in x: 74 | Xp = x[p] / dx 75 | base = int(Xp - 0.5) 76 | fx = Xp - base 77 | w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] 78 | new_v = ti.zero(v[p]) 79 | new_C = ti.zero(C[p]) 80 | for offset in ti.grouped(ti.ndrange(*neighbour)): 81 | dpos = (offset - fx) * dx 82 | weight = 1.0 83 | for i in ti.static(range(dim)): 84 | weight *= list_subscript(w, offset[i])[i] 85 | g_v = grid_v[base + offset] 86 | new_v += weight * g_v 87 | new_C += 4 * weight * g_v.outer_product(dpos) / dx**2 88 | v[p] = new_v 89 | x[p] += dt * v[p] 90 | J[p] *= 1 + dt * new_C.trace() 91 | C[p] = new_C 92 | 93 | 94 | @ti.kernel 95 | def init(): 96 | for i in range(n_particles): 97 | x[i] = ti.Vector([ti.random() for i in range(dim)]) * 0.4 + 0.15 98 | v[i] *= 0 99 | J[i] = 1 100 | 101 | 102 | scene = tina.Scene(maxpars=n_particles, bgcolor=ti.hex_to_rgb(0xaaaaff)) 103 | pars = tina.SimpleParticles(n_particles, radius=0.01) 104 | color = tina.Diffuse(color=ti.hex_to_rgb(0xffaaaa)) 105 | scene.add_object(pars, color) 106 | 107 | gui = ti.GUI('pars_mpm3d', scene.res) 108 | scene.init_control(gui, center=[0.5, 0.5, 0.5], radius=1.5) 109 | 110 | scene.lighting.clear_lights() 111 | scene.lighting.add_light([-0.4, 1.5, 1.8], color=[0.8, 0.8, 0.8]) 112 | scene.lighting.set_ambient_light([0.22, 0.22, 0.22]) 113 | 114 | init() 115 | while gui.running: 116 | scene.input(gui) 117 | if gui.is_pressed('r'): 118 | init() 119 | for s in range(steps): 120 | substep() 121 | 122 | pars.set_particles(x.to_numpy()) 123 | scene.render() 124 | 125 | gui.set_image(scene.img) 126 | gui.show() 127 | -------------------------------------------------------------------------------- /examples/rtx_matball.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.gpu) 5 | 6 | scene = tina.PTScene(smoothing=True) 7 | scene.engine.skybox = tina.Atomsphere() 8 | #scene.engine.skybox = tina.Skybox('assets/skybox.jpg') 9 | 10 | roughness = tina.Param(float, initial=0.15) 11 | metallic = tina.Param(float, initial=1.0) 12 | specular = tina.Param(float, initial=0.5) 13 | material = tina.PBR(metallic=metallic, roughness=roughness, specular=specular) 14 | 15 | model = tina.PrimitiveMesh.sphere() 16 | scene.add_object(model, material) 17 | 18 | gui = ti.GUI('matball') 19 | roughness.make_slider(gui, 'roughness') 20 | metallic.make_slider(gui, 'metallic') 21 | specular.make_slider(gui, 'specular') 22 | 23 | scene.update() 24 | while gui.running: 25 | scene.input(gui) 26 | scene.render() 27 | gui.set_image(scene.img) 28 | gui.show() 29 | 30 | #tina.pfmwrite('/tmp/color.pfm', scene.img) 31 | -------------------------------------------------------------------------------- /melt/dump.py: -------------------------------------------------------------------------------- 1 | from tina.advans import * 2 | 3 | pars = np.load('/tmp/mpm.npy') 4 | 5 | scene = tina.Scene() 6 | model = tina.SimpleParticles() 7 | scene.add_object(model) 8 | 9 | gui = ti.GUI() 10 | while gui.running: 11 | scene.input(gui) 12 | model.set_particles(pars) 13 | scene.render() 14 | gui.set_image(scene.img) 15 | gui.show() 16 | -------------------------------------------------------------------------------- /melt/lbvh.py: -------------------------------------------------------------------------------- 1 | #from tina.common import * 2 | 3 | 4 | #verts, faces = tina.readobj('assets/bunny.obj', simple=True) 5 | #verts = verts[faces] 6 | import numpy as np 7 | pos = np.load('assets/fluid.npy')[:3] 8 | 9 | 10 | def expandBits(v): 11 | v = (v * 0x00010001) & 0xFF0000FF; 12 | v = (v * 0x00000101) & 0x0F00F00F; 13 | v = (v * 0x00000011) & 0xC30C30C3; 14 | v = (v * 0x00000005) & 0x49249249; 15 | return v; 16 | 17 | def morton3D(x, y, z): 18 | x = min(max(x * 1024, 0), 1023); 19 | y = min(max(y * 1024, 0), 1023); 20 | z = min(max(z * 1024, 0), 1023); 21 | xx = expandBits(int(x)); 22 | yy = expandBits(int(y)); 23 | zz = expandBits(int(z)); 24 | return xx * 4 + yy * 2 + zz; 25 | 26 | 27 | def generateHierarchy(ids, codes, first, last): 28 | if first == last: 29 | return ids[first] 30 | 31 | split = findSplit(codes, first, last) 32 | 33 | childA = generateHierarchy(ids, codes, first, split) 34 | childB = generateHierarchy(ids, codes, split + 1, last) 35 | 36 | return (childA, childB) 37 | 38 | 39 | def countLeadingZeros(x): 40 | n = 0 41 | while x != 0: 42 | x >>= 1 43 | n += 1 44 | return 32 - n 45 | 46 | 47 | def findSplit(codes, first, last): 48 | code_first = codes[first] 49 | code_last = codes[last] 50 | 51 | if code_first == code_last: 52 | return (first + last) >> 1 53 | 54 | common_prefix = countLeadingZeros(code_first ^ code_last) 55 | 56 | split = first 57 | step = last - first 58 | 59 | while True: 60 | step = (step + 1) >> 1 61 | new_split = split + step 62 | if new_split < last: 63 | code_split = codes[new_split] 64 | split_prefix = countLeadingZeros(code_first ^ code_split) 65 | if split_prefix > common_prefix: 66 | split = new_split 67 | 68 | if step <= 1: 69 | break 70 | 71 | return split 72 | 73 | 74 | ids = range(len(pos)) 75 | codes = [morton3D(*pos[i]) for i in ids] 76 | _ = sorted(zip(ids, codes), key=lambda x: x[1]) 77 | ids = [_ for _, __ in _] 78 | codes = [_ for __, _ in _] 79 | tree = generateHierarchy(ids, codes, 0, len(ids) - 1) 80 | print(pos) 81 | print(tree) 82 | exit(1) 83 | -------------------------------------------------------------------------------- /melt/rbd.py: -------------------------------------------------------------------------------- 1 | from tina.advans import * 2 | 3 | _, __ = tina.readobj('assets/cube.obj', simple=True) 4 | verts_np = _[__] 5 | verts = texture_as_field(verts_np) 6 | 7 | _, __ = tina.readobj('assets/sphere.obj', simple=True) 8 | passes_np = _[__] 9 | passes_np[:, :, 1] -= 2.5 10 | passes = texture_as_field(passes_np) 11 | 12 | dt = 0.001 13 | invM = 1 / 4 14 | invI = 1 / 16 15 | Ks = 16000 16 | Kd = 256 17 | pos = ti.Vector.field(3, float, ()) 18 | vel = ti.Vector.field(3, float, ()) 19 | rot = ti.Vector.field(3, float, ()) 20 | anv = ti.Vector.field(3, float, ()) 21 | 22 | scene = tina.Scene() 23 | model = tina.MeshTransform(tina.SimpleMesh()) 24 | scene.add_object(model) 25 | passive = tina.MeshTransform(tina.SimpleMesh()) 26 | scene.add_object(passive) 27 | 28 | @ti.kernel 29 | def reset(): 30 | pos[None] = 0 31 | vel[None] = 0 32 | rot[None] = 0 33 | anv[None] = 0 34 | 35 | @ti.kernel 36 | def forward(): 37 | pos[None] += vel[None] * dt 38 | rot[None] += anv[None] * dt 39 | vel[None].y -= 0.98 40 | 41 | @ti.func 42 | def hit(curpos): 43 | ret_sdf = -inf 44 | ret_norm = V(0., 0., 0.) 45 | for p in range(passes.shape[0]): 46 | e1 = passes[p, 1] - passes[p, 0] 47 | e2 = passes[p, 2] - passes[p, 0] 48 | norm = e1.cross(e2).normalized() 49 | sdf = (curpos - passes[p, 0]).dot(norm) 50 | if sdf > ret_sdf: 51 | ret_sdf = sdf 52 | ret_norm = norm 53 | return ret_sdf, ret_norm 54 | 55 | @ti.func 56 | def hitedge(cp1, cp2): 57 | ret_sdf = -inf 58 | ret_norm = V(0., 0., 0.) 59 | ret_coor = 0. 60 | for p in range(passes.shape[0]): 61 | e1 = passes[p, 1] - passes[p, 0] 62 | e2 = passes[p, 2] - passes[p, 0] 63 | norm = e1.cross(e2).normalized() 64 | sdf1 = (cp1 - passes[p, 0]).dot(norm) 65 | sdf2 = (cp2 - passes[p, 0]).dot(norm) 66 | sdf = min(sdf1, sdf2) 67 | if sdf > ret_sdf: 68 | ret_coor = ti.sqrt(clamp( 69 | (sdf1**2 + eps) / ((sdf1 - sdf2)**2 + 2 * eps), 0., 1.)) 70 | ret_sdf = sdf 71 | ret_norm = norm 72 | return ret_sdf, ret_norm, ret_coor 73 | 74 | @ti.func 75 | def curposvel(relpos): 76 | curpos = mapply_pos(model.trans[None], relpos) 77 | curvel = vel[None] + anv[None].cross(relpos) 78 | return curpos, curvel 79 | 80 | @ti.kernel 81 | def collide(): 82 | force = V(0., 0., 0.) 83 | torque = V(0., 0., 0.) 84 | ''' 85 | for i, j in verts: 86 | curpos, curvel = curposvel(verts[i, j]) 87 | sdf, norm = hit(curpos) 88 | if sdf >= 0: 89 | continue 90 | F = -Ks * sdf * norm - Kd * curvel 91 | torque += verts[i, j].cross(F) 92 | force += F 93 | ''' 94 | for i in range(verts.shape[0]): 95 | for e in range(3): 96 | p1, p2 = verts[i, e], verts[i, (e + 1) % 3] 97 | cp1, cv1 = curposvel(p1) 98 | cp2, cv2 = curposvel(p2) 99 | sdf, norm, coor = hitedge(cp1, cp2) 100 | edgelen = (p1 - p2).norm() 101 | relpos = lerp(coor, p1, p2) 102 | curpos = lerp(coor, cp1, cp2) 103 | curvel = lerp(coor, cv1, cv2) 104 | if sdf >= 0: 105 | continue 106 | F = -Ks * sdf * norm - Kd * curvel 107 | F *= edgelen 108 | torque += relpos.cross(F) 109 | force += F 110 | vel[None] += force * dt * invM 111 | anv[None] += torque * dt * invI 112 | 113 | def step(): 114 | for i in range(3): 115 | forward() 116 | trans = tina.translate(pos[None].value) @ tina.eularXYZ(rot[None].value) 117 | model.set_transform(trans) 118 | collide() 119 | 120 | reset() 121 | model.set_face_verts(verts_np) 122 | passive.set_face_verts(passes_np) 123 | gui = ti.GUI('rbd') 124 | while gui.running: 125 | scene.input(gui) 126 | if not gui.is_pressed(gui.SPACE): 127 | step() 128 | if gui.is_pressed('r'): 129 | reset() 130 | scene.render() 131 | gui.set_image(scene.img) 132 | gui.show() 133 | -------------------------------------------------------------------------------- /melt/vbrbd.py: -------------------------------------------------------------------------------- 1 | # https://developer.nvidia.com/gpugems/gpugems3/part-v-physics-simulation/chapter-29-real-time-rigid-body-simulation-gpus 2 | 3 | from tina.advans import * 4 | #from voxelizer import MeshVoxelizer 5 | 6 | NE = 64 7 | N = 128 8 | R = 0.1 9 | Ks = 600 10 | Kd = 6 11 | Kt = 0 12 | Dt = 0.001 13 | Grav = 2.5 14 | pos = ti.Vector.field(3, float, N) 15 | vel = ti.Vector.field(3, float, N) 16 | epos = ti.Vector.field(3, float, NE) 17 | erot = ti.Vector.field(4, float, NE) 18 | 19 | @ti.kernel 20 | def reset(): 21 | for i in pos: 22 | pos[i] = V(ti.random(), ti.random(), ti.random()) * 2 - 1 23 | vel[i] = 0 24 | 25 | @ti.kernel 26 | def substep(): 27 | for i in pos: 28 | acc = V(0., 0., 0.) 29 | for j in range(N): 30 | if i == j: 31 | continue 32 | r = pos[j] - pos[i] 33 | v = vel[j] - vel[i] 34 | rn = r.norm() 35 | if rn > R * 2: 36 | continue 37 | rnn = r / rn 38 | fs = -Ks * (R * 2 - rn) * rnn 39 | vd = v.dot(rnn) * rnn 40 | ft = Kt * (v - vd) 41 | fd = Kd * vd 42 | acc += fs + fd + ft 43 | acc += V(0., -Grav, 0.) 44 | vel[i] += acc * Dt 45 | for i in pos: 46 | cond = (pos[i] < -1 and vel[i] < 0) or (pos[i] > 1 and vel[i] > 0) 47 | vel[i] = -0.1 * vel[i] if cond else vel[i] 48 | pos[i] += vel[i] * Dt 49 | 50 | def step(): 51 | for i in range(32): 52 | substep() 53 | 54 | scene = tina.Scene() 55 | model = tina.SimpleParticles(radius=R) 56 | scene.add_object(model) 57 | 58 | reset() 59 | gui = ti.GUI() 60 | while gui.running: 61 | scene.input(gui) 62 | if not gui.is_pressed(gui.SPACE): step() 63 | if gui.is_pressed('r'): reset() 64 | model.set_particles(pos.to_numpy()) 65 | scene.render() 66 | gui.set_image(scene.img) 67 | gui.show() 68 | -------------------------------------------------------------------------------- /melt/voxelizer.py: -------------------------------------------------------------------------------- 1 | from tina.advans import * 2 | 3 | 4 | @ti.func 5 | def inside(p, a, b, c): 6 | u = (a - p).cross(b - p) 7 | v = (b - p).cross(c - p) 8 | w = (c - p).cross(a - p) 9 | ccw = u >= 0 and v >= 0 and w >= 0 10 | cw = u <= 0 and v <= 0 and w <= 0 11 | return ccw or cw 12 | 13 | 14 | @ti.data_oriented 15 | class MeshVoxelizer: 16 | def __init__(self, res): 17 | self.res = tovector(res) 18 | self.dx = 1 / self.res.x 19 | self.padding = 3 20 | 21 | self.voxels = ti.field(int, self.res) 22 | self.temp = ti.field(int, self.res) 23 | #self.block = ti.root.pointer(ti.ijk, self.res // 8) 24 | #self.block.dense(ti.ijk, 8).place(self.voxels) 25 | 26 | def voxelize(self, verts, vmin=None, vmax=None): 27 | if vmin is None or vmax is None: 28 | vmin, vmax = np.min(verts) - 0.1, np.max(verts) + 0.1 29 | verts = (verts - vmin) / (vmax - vmin) 30 | 31 | self._voxelize(verts) 32 | self._update(None) 33 | 34 | tmp = np.array(verts) 35 | tmp[..., (0, 1, 2)] = verts[..., (2, 0, 1)] 36 | self._voxelize(tmp) 37 | self._update(lambda x, y, z: (z, x, y)) 38 | 39 | ''' 40 | tmp = np.array(verts) 41 | tmp[..., (0, 1, 2)] = verts[..., (1, 2, 0)] 42 | self._voxelize(tmp) 43 | self._update(lambda x, y, z: (y, z, x)) 44 | ''' 45 | 46 | return vmin, vmax, verts 47 | 48 | @ti.kernel 49 | def _update(self, f: ti.template()): 50 | for I in ti.grouped(self.temp): 51 | if ti.static(f is None): 52 | self.voxels[I] = self.temp[I] 53 | self.temp[I] = 0 54 | else: 55 | J = V(*f(*I)) 56 | self.voxels[I] = min(self.voxels[I], max(0, self.temp[J])) 57 | self.temp[J] = 0 58 | 59 | @ti.kernel 60 | def _voxelize(self, verts: ti.ext_arr()): 61 | for i in range(verts.shape[0]): 62 | jitter = V(-0.0576167239, -0.2560898629, 0.06716309129) * 1e-4 63 | a = V(verts[i, 0, 0], verts[i, 0, 1], verts[i, 0, 2]) + jitter 64 | b = V(verts[i, 1, 0], verts[i, 1, 1], verts[i, 1, 2]) + jitter 65 | c = V(verts[i, 2, 0], verts[i, 2, 1], verts[i, 2, 2]) + jitter 66 | 67 | bmin, bmax = min(a, b, c), max(a, b, c) 68 | 69 | pmin = max(self.padding, ifloor(bmin.xy / self.dx)) 70 | pmax = min(self.res.xy - self.padding, ifloor(bmax.xy / self.dx) + 1) 71 | 72 | normal = (b - a).cross(c - a).normalized() 73 | 74 | if abs(normal.z) < 1e-10: 75 | continue 76 | 77 | for p in range(pmin.x, pmax.x): 78 | for q in range(pmin.y, pmax.y): 79 | pos = (V(p, q) + 0.5) * self.dx 80 | if inside(pos, a.xy, b.xy, c.xy): 81 | base = V23(pos, 0.) 82 | hei = int(-normal.dot(base - a) / normal.z / self.dx) 83 | hei = min(hei, self.res.x - self.padding) 84 | inc = 1 if normal.z > 0 else -1 85 | for s in range(self.padding, hei): 86 | self.temp[p, q, s] += inc 87 | 88 | 89 | if __name__ == '__main__': 90 | ti.init(ti.cuda) 91 | 92 | vox = MeshVoxelizer([256] * 3) 93 | verts, faces = tina.readobj('assets/monkey.obj', simple=True) 94 | verts *= 0.5 95 | verts += 0.5 96 | 97 | scene = tina.Scene(taa=True) 98 | volume = tina.SimpleVolume(vox.res.x) 99 | scene.add_object(volume) 100 | #model = tina.MeshToWire(tina.MeshModel('assets/monkey.obj')) 101 | #scene.add_object(model) 102 | 103 | vox.voxelize(verts[faces]) 104 | volume.set_volume_density(np.abs(vox.voxels.to_numpy()) * 0.05) 105 | 106 | gui = ti.GUI() 107 | while gui.running: 108 | scene.input(gui) 109 | scene.render() 110 | gui.set_image(scene.img) 111 | gui.show() 112 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | import tina 3 | 4 | setuptools.setup( 5 | name='taichi-tina', 6 | version='.'.join(map(str, tina.__version__)), 7 | author=tina.__author__.split('<')[0][:-1], 8 | author_email=tina.__author__.split('<')[1][:-1], 9 | url='https://github.com/taichi-dev/taichi_three', 10 | description='A real-time soft renderer based on the Taichi programming language', 11 | long_description=tina.__doc__, 12 | license=tina.__license__, 13 | keywords=['graphics', 'renderer'], 14 | classifiers=[ 15 | 'Intended Audience :: Developers', 16 | 'Intended Audience :: Science/Research', 17 | 'Topic :: Multimedia :: Graphics', 18 | 'Topic :: Games/Entertainment :: Simulation', 19 | 'Operating System :: OS Independent', 20 | ], 21 | python_requires='>=3.6', 22 | install_requires=[ 23 | 'taichi', 24 | 'transformations' 25 | ], 26 | packages=setuptools.find_packages(), 27 | ) 28 | -------------------------------------------------------------------------------- /tests/bdpt.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene(smoothing=True, texturing=True) 8 | 9 | scene.add_object(tina.PrimitiveMesh.sphere(), tina.Glass()) 10 | scene.add_object(tina.MeshTransform(tina.MeshModel('assets/cube.obj'), 11 | tina.translate([0, -3, 0]) @ tina.scale(2)), tina.Lambert()) 12 | 13 | scene.add_object(tina.MeshTransform(tina.MeshModel('assets/plane.obj'), 14 | tina.translate([0, 4, 0]) @ tina.scale(0.2)), tina.Lamp(color=tina.Texture("assets/cloth.jpg")) * 64) 15 | 16 | gui = ti.GUI('bdpt', scene.res) 17 | 18 | scene.update() 19 | while gui.running: 20 | if scene.input(gui): 21 | scene.clear() 22 | scene.render(nsteps=6) 23 | scene.render_light(nsteps=6) 24 | gui.set_image(scene.img) 25 | gui.show() 26 | -------------------------------------------------------------------------------- /tests/blooming.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.gpu) 5 | 6 | scene = tina.Scene(smoothing=True, blooming=True) 7 | 8 | material = tina.Diffuse() 9 | model = tina.MeshModel('assets/monkey.obj') 10 | scene.add_object(model, material) 11 | 12 | gui = ti.GUI(res=scene.res) 13 | light = gui.slider('light', 0, 32, 0.1) 14 | light.value = 6 15 | 16 | scene.lighting.clear_lights() 17 | 18 | while gui.running: 19 | scene.lighting.set_ambient_light([light.value] * 3) 20 | scene.input(gui) 21 | scene.render() 22 | gui.set_image(scene.img) 23 | gui.show() 24 | -------------------------------------------------------------------------------- /tests/brdf.py: -------------------------------------------------------------------------------- 1 | from tina.advans import * 2 | 3 | ti.init(ti.cpu) 4 | 5 | PBR = 1 6 | 7 | if PBR: 8 | roughness = tina.Param(float, initial=0.4) 9 | metallic = tina.Param(float, initial=1.0) 10 | material = tina.PBR(basecolor=[1, 1, 1], roughness=roughness, metallic=metallic, specular=0.0) 11 | else: 12 | shineness = tina.Param(float, initial=10) 13 | material = tina.Phong(diffuse=[0, 0, 0], specular=[1, 1, 1], shineness=shineness) 14 | 15 | iu = ti.field(float, ()) 16 | 17 | k = 1 18 | n = 400 19 | f = ti.Vector.field(3, float, (n * k, n)) 20 | 21 | @ti.kernel 22 | def render(): 23 | idir = spherical(iu[None], 0.) 24 | for i, j in f: 25 | ou, ov = 1 - (i + 0.5) / n, (j + 0.5) / n 26 | odir = spherical(ou, ov) 27 | brdf = material.brdf(V(0., 0., 1.), idir, odir) 28 | f[i, j] = brdf * ou 29 | 30 | gui = ti.GUI('brdf', (n * k, n)) 31 | if PBR: 32 | roughness.make_slider(gui, 'roughness') 33 | metallic.make_slider(gui, 'metallic') 34 | else: 35 | shineness.make_slider(gui, 'shineness', 1, 128, 1) 36 | iu_slider = gui.slider('U', 0, 1, 0.01) 37 | avg_label = gui.label('average') 38 | 39 | while gui.running: 40 | for e in gui.get_events(): 41 | if e.key == gui.ESCAPE: 42 | gui.running = False 43 | render() 44 | im = f.to_numpy() 45 | avg_label.value = np.sum(im) / (3 * k * n**2) 46 | gui.set_image(1 - np.exp(-im)) 47 | gui.show() 48 | iu[None] = iu_slider.value 49 | -------------------------------------------------------------------------------- /tests/cookibl.py: -------------------------------------------------------------------------------- 1 | import tina 2 | 3 | tina.ti.init(tina.ti.gpu) 4 | 5 | li = tina.SkyboxLighting('assets/ballroom.npy', precision=2048) 6 | li.save('assets/ballroom.npz') 7 | -------------------------------------------------------------------------------- /tests/emission.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.gpu) 5 | 6 | scene = tina.PTScene() 7 | #scene.engine.skybox = tina.Atomsphere() 8 | 9 | model = tina.MeshTransform(tina.MeshModel('assets/plane.obj'), 10 | tina.translate([0, 0, 4]) @ tina.eularXYZ([ti.pi / 2, 0, 0])) 11 | material = tina.Emission() * 2 12 | scene.add_object(model, material) 13 | 14 | metallic = tina.Param(float, initial=1.0) 15 | roughness = tina.Param(float, initial=0.01) 16 | model = tina.MeshModel('assets/monkey.obj') 17 | material = tina.PBR(metallic=metallic, roughness=roughness) 18 | scene.add_object(model, material) 19 | 20 | gui = ti.GUI(res=scene.res) 21 | metallic.make_slider(gui, 'metallic') 22 | roughness.make_slider(gui, 'roughness') 23 | 24 | scene.update() 25 | while gui.running: 26 | scene.input(gui) 27 | scene.render() 28 | gui.set_image(scene.img) 29 | gui.show() 30 | -------------------------------------------------------------------------------- /tests/fpe.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.cpu) 6 | 7 | scene = tina.PTScene(smoothing=True) 8 | scene.engine.skybox = tina.PlainSkybox() 9 | 10 | scene.add_object(tina.MeshModel('assets/monkey.obj'), tina.Lambert()) 11 | 12 | gui = ti.GUI('fpe', scene.res) 13 | 14 | scene.update() 15 | while gui.running: 16 | if scene.input(gui): 17 | scene.clear() 18 | scene.render() 19 | gui.set_image(scene.img) 20 | gui.show() 21 | -------------------------------------------------------------------------------- /tests/fxaa.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.opengl) 5 | 6 | 7 | scene = tina.Scene(fxaa=True) 8 | model = tina.MeshModel('assets/monkey.obj') 9 | scene.add_object(model, tina.PBR(metallic=0.5, roughness=0.3)) 10 | 11 | 12 | gui = ti.GUI(res=scene.res) 13 | 14 | abs_thresh = gui.slider('abs_thresh', 0, 0.1, 0.002) 15 | rel_thresh = gui.slider('rel_thresh', 0, 0.5, 0.01) 16 | factor = gui.slider('factor', 0, 1, 0.01) 17 | abs_thresh.value = 0.0625 18 | rel_thresh.value = 0.063 19 | factor.value = 1 20 | 21 | while gui.running: 22 | scene.fxaa.rel_thresh[None] = rel_thresh.value 23 | scene.fxaa.abs_thresh[None] = abs_thresh.value 24 | scene.fxaa.factor[None] = factor.value 25 | scene.input(gui) 26 | scene.render() 27 | gui.set_image(scene.img) 28 | gui.show() 29 | -------------------------------------------------------------------------------- /tests/glass.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene(smoothing=True, texturing=True) 8 | scene.engine.skybox = tina.Skybox('assets/skybox.jpg', cubic=True) 9 | scene.add_object(tina.MeshModel('assets/cube.obj'), tina.Glass()) 10 | 11 | gui = ti.GUI('bdpt', scene.res) 12 | 13 | scene.update() 14 | while gui.running: 15 | if scene.input(gui): 16 | scene.clear() 17 | scene.render(nsteps=12) 18 | #scene.render_light(nsteps=6) 19 | gui.set_image(scene.img) 20 | gui.show() 21 | -------------------------------------------------------------------------------- /tests/inter.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.cpu) 5 | 6 | 7 | scene = tina.Scene() 8 | model = tina.MeshModel('assets/cube.obj') 9 | material = tina.Emission() * tina.Input('pos') 10 | scene.add_object(model, material) 11 | 12 | gui = ti.GUI(res=scene.res) 13 | 14 | while gui.running: 15 | scene.input(gui) 16 | scene.render() 17 | gui.set_image(scene.img) 18 | gui.show() 19 | -------------------------------------------------------------------------------- /tests/knn.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | 4 | img = ti.imread('noise.png') 5 | #img = ti.imread('/opt/cuda/samples/3_Imaging/imageDenoising/data/portrait_noise.bmp') 6 | img = np.float32(img / 255) 7 | w, h, chans = img.shape 8 | 9 | src = ti.Vector.field(chans, float, (w, h)) 10 | dst = ti.Vector.field(chans, float, (w, h)) 11 | 12 | src.from_numpy(img) 13 | 14 | noise = 1 / 0.32**2 15 | lerp_c = 0.2 16 | win_rad = 3 17 | blk_rad = 3 18 | win_area = (2 * win_rad + 1)**2 19 | wei_thres = 0.02 20 | lerp_thres = 0.79 21 | 22 | @ti.kernel 23 | def denoise(): 24 | for x, y in src: 25 | cnt = 0.0 26 | wei = 0.0 27 | clr = ti.Vector([0.0, 0.0, 0.0]) 28 | 29 | clr00 = src[x, y] 30 | for i, j in ti.ndrange((-win_rad, win_rad + 1), (-win_rad, win_rad + 1)): 31 | wij = 0.0 32 | 33 | clrij = src[x + i, y + j] 34 | disij = (clr00 - clrij).norm_sqr() 35 | 36 | wij = ti.exp(-(disij * noise + (i**2 + j**2) / win_area)) 37 | cnt += 1 / win_area if wij > wei_thres else 0 38 | clr += clrij * wij 39 | wei += wij 40 | 41 | clr /= wei 42 | lerp_q = lerp_c if cnt > lerp_thres else 1 - lerp_c 43 | dst[x, y] = clr * (1 - lerp_q) + src[x, y] * lerp_q 44 | 45 | 46 | denoise() 47 | gui1 = ti.GUI('before', (w, h)) 48 | gui2 = ti.GUI('after', (w, h)) 49 | gui1.fps_limit = 30 50 | gui2.fps_limit = 30 51 | while gui1.running and gui2.running: 52 | gui1.running = not gui1.get_event(ti.GUI.ESCAPE) 53 | gui2.running = not gui2.get_event(ti.GUI.ESCAPE) 54 | gui1.set_image(src) 55 | gui2.set_image(dst) 56 | gui1.show() 57 | gui2.show() 58 | -------------------------------------------------------------------------------- /tests/mis.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.opengl) 6 | 7 | scene = tina.PTScene(smoothing=True) 8 | 9 | roughness = tina.Param(float, initial=0.2) 10 | metallic = tina.Param(float, initial=1.0) 11 | scene.add_object(tina.MeshModel('assets/sphere.obj'), tina.PBR(metallic=metallic, roughness=roughness)) 12 | 13 | scene.add_object(tina.MeshTransform(tina.MeshModel('assets/plane.obj'), 14 | tina.translate([0, 0, 4]) @ tina.eularXYZ([ti.pi / 2, 0, 0]) 15 | #@ tina.scale(0.1)), tina.Lamp(color=32)) 16 | @ tina.scale(0.4)), tina.Lamp(color=1)) 17 | 18 | gui = ti.GUI('path', scene.res) 19 | roughness.make_slider(gui, 'roughness', 0, 1, 0.01) 20 | metallic.make_slider(gui, 'metallic', 0, 1, 0.01) 21 | 22 | scene.update() 23 | while gui.running: 24 | if scene.input(gui): 25 | scene.clear() 26 | scene.render(nsteps=6) 27 | gui.set_image(scene.img) 28 | gui.show() 29 | 30 | #ti.imwrite(scene.img, 'output.png') 31 | -------------------------------------------------------------------------------- /tests/mtlid.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene() 8 | verts, faces = tina.readobj('assets/sphere.obj', simple=True) 9 | verts = verts[faces] 10 | model = tina.SimpleMesh() 11 | scene.add_object(model) 12 | scene.engine.skybox = tina.Atomsphere() 13 | scene.materials = [tina.Lambert(), tina.Lambert() * [0, 1, 0]] 14 | 15 | mtlids = np.zeros(verts.shape[0]) 16 | mtlids[3] = 1 17 | mtlids[5] = 1 18 | model.set_face_verts(verts) 19 | model.set_face_mtlids(mtlids) 20 | 21 | scene.update() 22 | scene.visualize() 23 | -------------------------------------------------------------------------------- /tests/nlm.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | 4 | img = ti.imread('noise.png') 5 | #img = ti.imread('/opt/cuda/samples/3_Imaging/imageDenoising/data/portrait_noise.bmp') 6 | img = np.float32(img / 255) 7 | w, h, chans = img.shape 8 | 9 | src = ti.Vector.field(chans, float, (w, h)) 10 | dst = ti.Vector.field(chans, float, (w, h)) 11 | 12 | src.from_numpy(img) 13 | 14 | noise = 1 / 1.45**2 15 | lerp_c = 0.2 16 | win_rad = 3 17 | blk_rad = 3 18 | win_area = (2 * win_rad + 1)**2 19 | wei_thres = 0.1 20 | lerp_thres = 0.1 21 | 22 | @ti.kernel 23 | def denoise(): 24 | for x, y in src: 25 | cnt = 0.0 26 | wei = 0.0 27 | clr = ti.Vector([0.0, 0.0, 0.0]) 28 | 29 | for i, j in ti.ndrange((-win_rad, win_rad + 1), (-win_rad, win_rad + 1)): 30 | wij = 0.0 31 | 32 | for m, n in ti.ndrange((-blk_rad, blk_rad + 1), (-blk_rad, blk_rad + 1)): 33 | clr00 = src[x + m, y + n] 34 | clrij = src[x + i + m, y + j + n] 35 | wij += (clr00 - clrij).norm_sqr() 36 | 37 | wij = ti.exp(-(wij * noise + (i**2 + j**2) / win_area)) 38 | cnt += 1 / win_area if wij > wei_thres else 0 39 | clr += src[x + i, y + j] * wij 40 | wei += wij 41 | 42 | clr /= wei 43 | lerp_q = lerp_c if cnt > lerp_thres else 1 - lerp_c 44 | dst[x, y] = clr * (1 - lerp_q) + src[x, y] * lerp_q 45 | 46 | 47 | denoise() 48 | gui1 = ti.GUI('before', (w, h)) 49 | gui2 = ti.GUI('after', (w, h)) 50 | gui1.fps_limit = 30 51 | gui2.fps_limit = 30 52 | while gui1.running and gui2.running: 53 | gui1.running = not gui1.get_event(ti.GUI.ESCAPE) 54 | gui2.running = not gui2.get_event(ti.GUI.ESCAPE) 55 | gui1.set_image(src) 56 | gui2.set_image(dst) 57 | gui1.show() 58 | gui2.show() 59 | -------------------------------------------------------------------------------- /tests/noise.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene(smoothing=True, texturing=True) 8 | #scene.lighting.skybox = tina.Skybox('assets/skybox.jpg', cubic=True) 9 | model = tina.MeshModel('assets/bunny.obj') 10 | #material = tina.PBR(roughness=0.0, metallic=0.0) 11 | material = tina.PBR(roughness=0.2, metallic=0.8) 12 | scene.add_object(model, material) 13 | denoise = tina.Denoise(scene.res) 14 | 15 | if isinstance(scene, tina.PTScene): 16 | scene.update() 17 | 18 | gui = ti.GUI('noise', scene.res) 19 | 20 | while gui.running: 21 | scene.input(gui) 22 | if isinstance(scene, tina.PTScene): 23 | scene.render(nsteps=5) 24 | else: 25 | scene.render() 26 | #gui.set_image(scene.img) 27 | denoise.src.from_numpy(scene.img) 28 | denoise.nlm(radius=2, noiseness=0.9) 29 | gui.set_image(denoise.dst) 30 | gui.show() 31 | -------------------------------------------------------------------------------- /tests/path.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene(smoothing=True) 8 | #scene.lighting.skybox = tina.Atomsphere() 9 | 10 | #material = tina.Phong(color=[0.25, 0.5, 0.5]) 11 | #scene.add_object(tina.PrimitiveMesh.sphere(), tina.Glass()) 12 | roughness = tina.Param(float, initial=0.332) 13 | metallic = tina.Param(float, initial=0.0) 14 | scene.add_object(tina.MeshModel('assets/sphere.obj'), tina.PBR(metallic=metallic, roughness=roughness)) 15 | #scene.add_object(tina.PrimitiveMesh.sphere(), tina.Mirror()) 16 | #scene.add_object(tina.MeshModel('assets/bunny.obj'), tina.Glass()) 17 | #scene.add_object(tina.MeshModel('assets/monkey.obj'), tina.Mirror()) 18 | #scene.add_object(tina.MeshModel('assets/cube.obj'), tina.Classic()) 19 | #scene.add_object(tina.MeshTransform(tina.MeshModel('assets/cube.obj'), tina.translate([0, -1.2, 0]) @ tina.scale([2, 0.05, 2])), tina.Lambert()) 20 | 21 | scene.add_object(tina.MeshTransform(tina.MeshModel('assets/plane.obj'), 22 | tina.translate([0, 0, 4]) @ tina.eularXYZ([ti.pi / 2, 0, 0]) 23 | @ tina.scale(0.1)), tina.Lamp(color=128)) 24 | 25 | gui = ti.GUI('path', scene.res) 26 | roughness.make_slider(gui, 'roughness', 0, 1, 0.01) 27 | metallic.make_slider(gui, 'metallic', 0, 1, 0.01) 28 | 29 | scene.update() 30 | while gui.running: 31 | if scene.input(gui): 32 | scene.clear() 33 | scene.render(nsteps=6) 34 | gui.set_image(scene.img) 35 | gui.show() 36 | 37 | #ti.imwrite(scene.img, 'output.png') 38 | -------------------------------------------------------------------------------- /tests/pick.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import pickle 3 | import tina 4 | 5 | 6 | x = ti.Matrix.shield(3, 3, float, 4) 7 | x[3][2, 1] = 1 8 | print(x) 9 | x = pickle.dumps(x) 10 | print(x) 11 | ti.init() 12 | x = pickle.loads(x) 13 | print(x) 14 | exit(1) 15 | -------------------------------------------------------------------------------- /tests/probe.py: -------------------------------------------------------------------------------- 1 | import tina 2 | import taichi as ti 3 | 4 | 5 | scene = tina.Scene(texturing=True, prob=True) 6 | 7 | verts, faces = tina.readobj('assets/monkey.obj', simple=True) 8 | mesh = tina.SimpleMesh() 9 | texture = tina.LerpTexture(x0=[1.0, 1.0, 1.0], x1=[0.0, 0.0, 1.0]) 10 | material = tina.Diffuse(color=texture) 11 | scene.add_object(mesh, material) 12 | 13 | probe = tina.ProbeShader(scene.res) 14 | scene.post_shaders.append(probe) 15 | 16 | mesh.set_face_verts(verts[faces]) 17 | 18 | @ti.func 19 | def ontouch(probe, I, r): 20 | e = probe.elmid[I] 21 | for i in ti.static(range(3)): 22 | mesh.coors[e, i].x = 1.0 23 | 24 | gui = ti.GUI() 25 | while gui.running: 26 | scene.input(gui) 27 | if gui.is_pressed(gui.LMB): 28 | mx, my = gui.get_cursor_pos() 29 | probe.touch(ontouch, mx, my, 0) 30 | scene.render() 31 | gui.set_image(scene.img) 32 | gui.show() 33 | -------------------------------------------------------------------------------- /tests/ptlight.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene(smoothing=True) 8 | 9 | roughness = tina.Param(float, initial=0.2) 10 | metallic = tina.Param(float, initial=1.0) 11 | scene.add_object(tina.MeshModel('assets/sphere.obj'), tina.PBR(metallic=metallic, roughness=roughness)) 12 | 13 | pars = tina.SimpleParticles() 14 | scene.add_object(pars, tina.Lamp(color=32)) 15 | 16 | gui = ti.GUI('path', scene.res) 17 | roughness.make_slider(gui, 'roughness', 0, 1, 0.01) 18 | metallic.make_slider(gui, 'metallic', 0, 1, 0.01) 19 | 20 | pars.set_particles(np.array([ 21 | [0, 0, 5], 22 | ])) 23 | 24 | scene.update() 25 | while gui.running: 26 | if scene.input(gui): 27 | scene.clear() 28 | scene.render(nsteps=6) 29 | gui.set_image(scene.img) 30 | gui.show() 31 | 32 | #ti.imwrite(scene.img, 'output.png') 33 | -------------------------------------------------------------------------------- /tests/raytrace.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import taichi_inject 4 | import tina 5 | 6 | ti.init(ti.gpu) 7 | 8 | scene = tina.Scene(smoothing=True, texturing=True, rtx=True, taa=True) 9 | material = tina.Phong(shineness=64) 10 | mesh = tina.PrimitiveMesh.sphere() 11 | #mesh = tina.MeshModel('assets/monkey.obj') 12 | scene.add_object(mesh, material) 13 | 14 | gui = ti.GUI('raytrace', scene.res) 15 | 16 | scene.update() 17 | while gui.running: 18 | scene.input(gui) 19 | scene.render() 20 | gui.set_image(scene.img) 21 | gui.show() 22 | -------------------------------------------------------------------------------- /tests/rtao.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene(smoothing=True)#, texturing=True) 8 | scene.engine.skybox = tina.PlainSkybox() 9 | 10 | scene.add_object(tina.MeshModel('assets/monkey.obj'), tina.Lambert()) 11 | #scene.add_object(tina.MeshModel('assets/cube.obj'), tina.Emission() * [.9, .4, .9]) 12 | 13 | gui = ti.GUI('rtao', scene.res) 14 | 15 | scene.update() 16 | while gui.running: 17 | if scene.input(gui): 18 | scene.clear() 19 | scene.render() 20 | gui.set_image(scene.img) 21 | gui.show() 22 | -------------------------------------------------------------------------------- /tests/rtx.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.gpu) 5 | 6 | scene = tina.Scene(smoothing=True, rtx=True) 7 | 8 | material = tina.Phong(shineness=256) 9 | 10 | model = tina.PrimitiveMesh.sphere() 11 | scene.add_object(model, material) 12 | 13 | model2 = tina.MeshTransform(tina.PrimitiveMesh.sphere(), tina.translate([1.4, 0, 0]) @ tina.scale(0.5)) 14 | scene.add_object(model2, material) 15 | 16 | gui = ti.GUI('matball') 17 | 18 | scene.update() 19 | scene.init_control(gui) 20 | while gui.running: 21 | scene.input(gui) 22 | scene.render() 23 | gui.set_image(scene.img) 24 | gui.show() 25 | -------------------------------------------------------------------------------- /tests/skybox.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene() 8 | scene.add_object(tina.MeshModel('assets/shadow.obj')) 9 | scene.lighting.skybox = tina.Atomsphere() 10 | 11 | gui = ti.GUI('sky', scene.res) 12 | 13 | scene.update() 14 | while gui.running: 15 | scene.input(gui) 16 | scene.render() 17 | gui.set_image(scene.img) 18 | gui.show() 19 | -------------------------------------------------------------------------------- /tests/ssao.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.cuda) 5 | 6 | scene = tina.Scene(smoothing=True, ssao=True)#, ibl=True) 7 | #model = tina.MeshModel('assets/bunny.obj') 8 | model = tina.MeshModel('assets/monkey.obj') 9 | #model = tina.MeshModel('/home/bate/Documents/testssao.obj') 10 | scene.add_object(model) 11 | 12 | if not scene.ibl: 13 | scene.lighting.clear_lights() 14 | scene.lighting.set_ambient_light([1, 1, 1]) 15 | 16 | gui = ti.GUI(res=scene.res) 17 | 18 | radius = gui.slider('radius', 0, 2, 0.01) 19 | thresh = gui.slider('thresh', 0, 1, 0.01) 20 | factor = gui.slider('factor', 0, 2, 0.01) 21 | radius.value = 0.2 22 | thresh.value = 0.0 23 | factor.value = 1.0 24 | 25 | while gui.running: 26 | scene.ssao.radius[None] = radius.value 27 | scene.ssao.thresh[None] = thresh.value 28 | scene.ssao.factor[None] = factor.value 29 | 30 | scene.input(gui) 31 | scene.render() 32 | gui.set_image(scene.img) 33 | gui.show() 34 | -------------------------------------------------------------------------------- /tests/ssr.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | ti.init(ti.gpu) 5 | 6 | 7 | scene = tina.Scene((640, 480), smoothing=True, ssr=True, taa=True) 8 | monkey_material = tina.PBR(metallic=0.0, roughness=0.4) 9 | monkey = tina.MeshModel('assets/monkey.obj') 10 | scene.add_object(monkey, monkey_material) 11 | 12 | param_metallic = tina.Param() 13 | param_roughness = tina.Param() 14 | plane_material = tina.PBR(metallic=param_metallic, roughness=param_roughness) 15 | plane = tina.MeshTransform(tina.MeshGrid(32), 16 | tina.scale(2) @ tina.eularXYZ([-ti.pi / 2, 0, 0])) 17 | scene.add_object(plane, plane_material) 18 | 19 | gui = ti.GUI(res=scene.res) 20 | nsteps = gui.slider('nsteps', 1, 128, 1) 21 | nsamples = gui.slider('nsamples', 1, 128, 1) 22 | stepsize = gui.slider('stepsize', 0, 32, 0.1) 23 | tolerance = gui.slider('tolerance', 0, 64, 0.1) 24 | blurring = gui.slider('blurring', 1, 8, 1) 25 | metallic = gui.slider('metallic', 0, 1, 0.01) 26 | roughness = gui.slider('roughness', 0, 1, 0.01) 27 | nsteps.value = 64 28 | nsamples.value = 12 29 | blurring.value = 4 30 | stepsize.value = 2 31 | tolerance.value = 15 32 | metallic.value = 1.0 33 | roughness.value = 0.0 34 | 35 | while gui.running: 36 | scene.ssr.nsteps[None] = int(nsteps.value) 37 | scene.ssr.nsamples[None] = int(nsamples.value) 38 | scene.ssr.blurring[None] = int(blurring.value) 39 | scene.ssr.stepsize[None] = stepsize.value 40 | scene.ssr.tolerance[None] = tolerance.value 41 | param_metallic.value[None] = metallic.value 42 | param_roughness.value[None] = roughness.value 43 | scene.input(gui) 44 | scene.render() 45 | gui.set_image(scene.img) 46 | gui.show() 47 | -------------------------------------------------------------------------------- /tests/volume.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | dens = np.load('assets/smoke.npy') 8 | scene = tina.Scene(N=dens.shape[0], density=16)#, taa=True) 9 | volume = tina.SimpleVolume(N=dens.shape[0]) 10 | #model = tina.MeshModel('assets/monkey.obj') 11 | #scene.add_object(model, tina.CookTorrance(metallic=0.8)) 12 | scene.add_object(volume) 13 | 14 | gui = ti.GUI('volume', scene.res) 15 | 16 | volume.set_volume_density(dens) 17 | while gui.running: 18 | scene.input(gui) 19 | scene.render() 20 | gui.set_image(scene.img) 21 | gui.show() 22 | -------------------------------------------------------------------------------- /tests/voxl.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | dens = np.load('assets/smoke.npy')[::1, ::1, ::1] 8 | scene = tina.PTScene() 9 | scene.engine.skybox = tina.Atomsphere() 10 | volume = tina.VolumeScale(tina.SimpleVolume(N=dens.shape[0]), scale=5) 11 | scene.add_object(tina.MeshModel('assets/monkey.obj')) 12 | g = tina.Param(float, initial=0.76) 13 | scene.add_object(volume, tina.HenyeyGreenstein(g=g)) 14 | #scene.add_object(tina.MeshTransform(tina.MeshModel('assets/plane.obj'), 15 | # tina.translate([0, 0, 4]) @ tina.eularXYZ([ti.pi / 2, 0, 0])), 16 | # tina.Emission() * 4) 17 | 18 | gui = ti.GUI('volume', scene.res) 19 | g.make_slider(gui, 'g', -1, 1, 0.01) 20 | 21 | volume.set_volume_density(dens) 22 | scene.update() 23 | while gui.running: 24 | scene.input(gui) 25 | scene.render()#nsteps=32) 26 | gui.set_image(scene.img) 27 | gui.show() 28 | -------------------------------------------------------------------------------- /tests/wave.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | ti.init(ti.gpu) 6 | 7 | scene = tina.PTScene(smoothing=True) 8 | #scene.lighting.skybox = tina.Atomsphere() 9 | #scene.lighting.skybox = tina.Skybox('assets/grass.jpg') 10 | 11 | scene.add_object(tina.PrimitiveMesh.sphere(), tina.Glass()) 12 | scene.add_object(tina.MeshTransform(tina.MeshModel('assets/cube.obj'), 13 | tina.translate([0, -3, 0]) @ tina.scale(2)), tina.Lambert()) 14 | 15 | scene.lighting.set_lights(np.array([ 16 | [0, 5, 0], 17 | ])) 18 | scene.lighting.set_light_colors(np.array([ 19 | [16, 16, 16], 20 | ])) 21 | scene.lighting.set_light_radii(np.array([ 22 | 0.01, 23 | ])) 24 | 25 | gui = ti.GUI('wave', scene.res) 26 | 27 | scene.update() 28 | while gui.running: 29 | if scene.input(gui): 30 | scene.clear() 31 | scene.render_light(nsteps=6) 32 | scene.render(nsteps=6) 33 | gui.set_image(scene.img) 34 | gui.show() 35 | 36 | #tina.pfmwrite('/tmp/a.pfm', scene.img) 37 | -------------------------------------------------------------------------------- /tests/wire.py: -------------------------------------------------------------------------------- 1 | # In this episode, you'll learn how to render a wireframe model 2 | # 3 | # This tutorial is based on docs/monkey.py, make sure you check that first 4 | 5 | import taichi as ti 6 | import numpy as np 7 | import tina 8 | 9 | scene = tina.Scene() 10 | 11 | # load the monkey using `tina.MeshModel` node (`tina.SimpleMesh` works too): 12 | model = tina.SimpleMesh(npolygon=2) 13 | scene.add_object(model) 14 | 15 | 16 | gui = ti.GUI('wireframe') 17 | 18 | while gui.running: 19 | scene.input(gui) 20 | a = gui.frame * 0.03 21 | verts = np.array([ 22 | [[0, 0, 0], [np.cos(a), np.sin(a), 0]], 23 | ], dtype=np.float32) 24 | model.set_face_verts(verts) 25 | scene.render() 26 | gui.set_image(scene.img) 27 | gui.show() 28 | -------------------------------------------------------------------------------- /tina/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = (0, 1, 1) 2 | __author__ = 'archibate <1931127624@qq.com>' 3 | __license__ = 'MIT' 4 | 5 | print('[Tina] version', '.'.join(map(str, __version__))) 6 | 7 | 8 | def require_version(*ver): 9 | def tos(ver): 10 | return '.'.join(map(str, ver)) 11 | 12 | msg = f'This program requires Tina version {tos(ver)} to work.\n' 13 | msg += f'However your installed Tina version is {tos(__version__)}.\n' 14 | msg += f'Try run `pip install taichi-tina=={tos(ver)}` to upgrade/downgrade.' 15 | if __version__ > ver: 16 | print(msg) 17 | elif __version__ < ver: 18 | raise RuntimeError(msg) 19 | 20 | 21 | from .lazimp import * 22 | from .hacker import * 23 | from .common import * 24 | from .advans import * 25 | 26 | if __import__('tina').lazyguard: 27 | from .shield import * 28 | from .util import * 29 | from .matr import * 30 | from .core import * 31 | from .path import * 32 | from .assimp import * 33 | from .mesh import * 34 | from .pars import * 35 | from .voxl import * 36 | from .scene import * 37 | from .skybox import * 38 | from .random import * 39 | from .probe import * 40 | from .postp import * 41 | -------------------------------------------------------------------------------- /tina/__main__.py: -------------------------------------------------------------------------------- 1 | def main(*args): 2 | cmd, *args = args 3 | if cmd == 'mesh': 4 | from .cli.mesh import main 5 | return main(*args) 6 | if cmd == 'volume': 7 | from .cli.volume import main 8 | return main(*args) 9 | if cmd == 'particles': 10 | from .cli.particles import main 11 | return main(*args) 12 | else: 13 | print('bad command:', cmd) 14 | exit(1) 15 | 16 | 17 | if __name__ == '__main__': 18 | import sys 19 | main(*sys.argv[1:]) 20 | -------------------------------------------------------------------------------- /tina/advans.py: -------------------------------------------------------------------------------- 1 | from .common import * 2 | 3 | 4 | inf = 1e6 5 | eps = 1e-6 6 | 7 | 8 | def texture_as_field(filename): 9 | if isinstance(filename, str): 10 | img_np = ti.imread(filename) 11 | else: 12 | img_np = np.array(filename) 13 | if img_np.dtype == np.uint8: 14 | img_np = np.float32(img_np / 255) 15 | 16 | if len(img_np.shape) == 3: 17 | img = ti.Vector.field(img_np.shape[2], float, img_np.shape[:2]) 18 | img._dense_shape = img_np.shape[:2] 19 | else: 20 | assert len(img_np.shape) == 2 21 | img = ti.field(float, img_np.shape) 22 | img._dense_shape = img_np.shape 23 | 24 | @ti.materialize_callback 25 | def init_texture(): 26 | img.from_numpy(img_np) 27 | 28 | return img 29 | 30 | 31 | @ti.pyfunc 32 | def aces_tonemap(color): 33 | # https://zhuanlan.zhihu.com/p/21983679 34 | return color * (2.51 * color + 0.03) / (color * (2.43 * color + 0.59) + 0.14) 35 | 36 | 37 | @ti.pyfunc 38 | def _film(x): 39 | # https://zhuanlan.zhihu.com/p/21983679 40 | A = 0.22 41 | B = 0.30 42 | C = 0.10 43 | D = 0.20 44 | E = 0.01 45 | F = 0.30 46 | 47 | return (x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F) - E / F 48 | 49 | 50 | @ti.pyfunc 51 | def film_tonemap(color): 52 | return _film(1.6 * color) / _film(11.2) 53 | 54 | 55 | @ti.pyfunc 56 | def ce_tonemap(color): 57 | if ti.static(ti.inside_kernel()): 58 | return 1 - ti.exp(-color) 59 | else: 60 | return 1 - np.exp(-color) 61 | 62 | 63 | @ti.pyfunc 64 | def ce_untonemap(color): 65 | if ti.static(ti.inside_kernel()): 66 | # 255 / 256 ~= 0.996 67 | return -ti.log(1 - clamp(color * 0.996, 0, 1)) 68 | else: 69 | return -np.log(1 - np.clip(color * 0.996, 0, 1)) 70 | 71 | 72 | @eval('lambda x: x()') 73 | def get_tonemap_image(): 74 | @ti.kernel 75 | def _get_image(out: ti.ext_arr(), img: ti.template(), tonemap: ti.template()): 76 | for I in ti.grouped(img): 77 | val = tonemap(img[I]) 78 | if ti.static(isinstance(val, ti.Matrix)): 79 | for k in ti.static(range(val.n)): 80 | out[I, k] = val[k] 81 | else: 82 | out[I] = val 83 | 84 | 85 | def get_image(img, tonemap=lambda x: x): 86 | shape = img.shape 87 | if isinstance(img, ti.Matrix): 88 | shape = shape + (img.n,) 89 | out = np.empty(shape) 90 | _get_image(out, img, tonemap) 91 | return out 92 | 93 | return get_image 94 | 95 | 96 | @ti.func 97 | def tangentspace(nrm, up=V(233., 666., 512.)): 98 | bitan = nrm.cross(up).normalized() 99 | tan = bitan.cross(nrm) 100 | return ti.Matrix.cols([tan, bitan, nrm]) 101 | 102 | 103 | 104 | @ti.func 105 | def spherical(h, p): 106 | unit = V(ti.cos(p * ti.tau), ti.sin(p * ti.tau)) 107 | dir = V23(ti.sqrt(max(0, 1 - h**2)) * unit, h) 108 | return dir 109 | 110 | 111 | @ti.func 112 | def unspherical(dir): 113 | p = ti.atan2(dir.y, dir.x) / ti.tau 114 | return dir.z, p % 1 115 | -------------------------------------------------------------------------------- /tina/assimp/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .obj import * 3 | from .gltf import * 4 | from .tet import * 5 | from .pfm import * 6 | -------------------------------------------------------------------------------- /tina/assimp/pfm.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sys 3 | 4 | def pfmwrite(path, im): 5 | im = im.swapaxes(0, 1) 6 | scale = max(1e-10, -im.min(), im.max()) 7 | h, w = im.shape[:2] 8 | with open(path, 'wb') as f: 9 | f.write(b'PF\n' if len(im.shape) >= 3 else b'Pf\n') 10 | f.write(f'{w} {h}\n'.encode()) 11 | f.write(f'{scale if sys.byteorder == "big" else -scale}\n'.encode()) 12 | f.write((im / scale).astype(np.float32).tobytes()) 13 | -------------------------------------------------------------------------------- /tina/cli/__init__.py: -------------------------------------------------------------------------------- 1 | from . import mesh, volume, particles -------------------------------------------------------------------------------- /tina/cli/mesh.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import tina 3 | 4 | 5 | def main(filename): 6 | ti.init(ti.gpu) 7 | 8 | obj = tina.readobj(filename, scale='auto') 9 | scene = tina.Scene((1024, 768), maxfaces=len(obj['f']), smoothing=True) 10 | model = tina.MeshModel(obj) 11 | scene.add_object(model) 12 | 13 | gui = ti.GUI('mesh', scene.res, fast_gui=True) 14 | while gui.running: 15 | scene.input(gui) 16 | scene.render() 17 | gui.set_image(scene.img) 18 | gui.show() 19 | 20 | 21 | if __name__ == '__main__': 22 | import sys 23 | main(*sys.argv[1:]) 24 | -------------------------------------------------------------------------------- /tina/cli/particles.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | 6 | def main(filename, radius=0.05): 7 | ti.init(ti.gpu) 8 | 9 | pos = np.load(filename).astype(np.float32) 10 | scene = tina.Scene((1024, 768)) 11 | pars = tina.SimpleParticles(maxpars=len(pos)) 12 | scene.add_object(pars) 13 | 14 | gui = ti.GUI('particles', scene.res, fast_gui=True) 15 | pars.set_particles(pos) 16 | pars.set_particle_radii(np.ones(len(pos), dtype=np.float32) * radius) 17 | while gui.running: 18 | scene.input(gui) 19 | scene.render() 20 | gui.set_image(scene.img) 21 | gui.show() 22 | 23 | 24 | if __name__ == '__main__': 25 | import sys 26 | main(*sys.argv[1:]) 27 | -------------------------------------------------------------------------------- /tina/cli/volume.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import tina 4 | 5 | 6 | def main(filename, density=16): 7 | ti.init(ti.gpu) 8 | 9 | dens = np.load(filename).astype(np.float32) 10 | scene = tina.Scene((1024, 768), N=dens.shape[0], taa=True, density=density) 11 | volume = tina.SimpleVolume(N=dens.shape[0]) 12 | scene.add_object(volume) 13 | 14 | gui = ti.GUI('volume', scene.res, fast_gui=True) 15 | volume.set_volume_density(dens) 16 | while gui.running: 17 | scene.input(gui) 18 | scene.render() 19 | gui.set_image(scene.img) 20 | gui.show() 21 | 22 | 23 | 24 | if __name__ == '__main__': 25 | import sys 26 | main(*sys.argv[1:]) 27 | -------------------------------------------------------------------------------- /tina/core/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .engine import * 3 | from .triangle import * 4 | from .particle import * 5 | from .wireframe import * 6 | from .volume import * 7 | from .shader import * 8 | from .lighting import * 9 | -------------------------------------------------------------------------------- /tina/core/engine.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class Engine: 6 | def __init__(self, res_x=512, res_y=None): 7 | if res_y is None: 8 | res_y = res_x 9 | self.res = tovector((res_x, res_y) if isinstance(res_x, int) else res_x) 10 | 11 | self.depth = ti.field(int, self.res) 12 | self.maxdepth = 2**30 13 | 14 | self.W2V = ti.Matrix.field(4, 4, float, ()) 15 | self.V2W = ti.Matrix.field(4, 4, float, ()) 16 | 17 | self.bias = ti.Vector.field(2, float, ()) 18 | 19 | @ti.materialize_callback 20 | @ti.kernel 21 | def init_engine(): 22 | self.W2V[None] = ti.Matrix.identity(float, 4) 23 | self.W2V[None][2, 2] = -1 24 | self.V2W[None] = ti.Matrix.identity(float, 4) 25 | self.V2W[None][2, 2] = -1 26 | self.bias[None] = [0.5, 0.5] 27 | 28 | ti.materialize_callback(self.clear_depth) 29 | 30 | @ti.kernel 31 | def randomize_bias(self, center: ti.template()): 32 | if ti.static(center): 33 | self.bias[None] = [0.5, 0.5] 34 | else: 35 | #r = ti.sqrt(ti.random()) 36 | #a = ti.random() * ti.tau 37 | #x, y = r * ti.cos(a) * 0.5 + 0.5, r * ti.sin(a) * 0.5 + 0.5 38 | x, y = ti.random(), ti.random() 39 | self.bias[None] = [x, y] 40 | 41 | @ti.kernel 42 | def render_background(self, shader: ti.template()): 43 | for P in ti.grouped(ti.ndrange(*self.res)): 44 | if self.depth[P] >= self.maxdepth: 45 | uv = (float(P) + self.bias[None]) / self.res * 2 - 1 46 | ro = mapply_pos(self.V2W[None], V(uv.x, uv.y, -1.0)) 47 | ro1 = mapply_pos(self.V2W[None], V(uv.x, uv.y, +1.0)) 48 | rd = (ro1 - ro).normalized() 49 | shader.shade_background(P, rd) 50 | 51 | @ti.func 52 | def to_viewspace(self, p): 53 | return mapply_pos(self.W2V[None], p) 54 | 55 | @ti.func 56 | def from_viewspace(self, p): 57 | return mapply_pos(self.V2W[None], p) 58 | 59 | @ti.func 60 | def to_viewport(self, p): 61 | return (p.xy * 0.5 + 0.5) * self.res 62 | 63 | @ti.func 64 | def from_viewport(self, p): 65 | return p / self.res * 2 - 1 66 | 67 | @ti.kernel 68 | def clear_depth(self): 69 | for P in ti.grouped(self.depth): 70 | self.depth[P] = self.maxdepth 71 | 72 | def set_camera(self, view, proj): 73 | W2V = proj @ view 74 | V2W = np.linalg.inv(W2V) 75 | self.W2V.from_numpy(np.array(W2V, dtype=np.float32)) 76 | self.V2W.from_numpy(np.array(V2W, dtype=np.float32)) 77 | -------------------------------------------------------------------------------- /tina/core/lighting.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | 3 | 4 | @ti.data_oriented 5 | class SkyboxLighting: 6 | def __init__(self, skybox, ibltab=None): 7 | self.skybox = skybox 8 | if ibltab is None: 9 | precision = 32 10 | ibltab = {'env': skybox} 11 | tina.Lambert.cook_for_ibl(ibltab, precision) 12 | tina.CookTorrance.cook_for_ibl(ibltab, precision) 13 | self.ibltab = ibltab 14 | 15 | @ti.func 16 | def background(self, rd): 17 | return self.skybox.sample(rd) 18 | 19 | @ti.func 20 | def shade_color(self, material, pos, normal, viewdir): 21 | return material.sample_ibl(self.ibltab, viewdir, normal) + material.emission() 22 | 23 | 24 | @ti.data_oriented 25 | class Lighting: 26 | def __init__(self, maxlights=16): 27 | self.light_dirs = ti.Vector.field(4, float, maxlights) 28 | self.light_colors = ti.Vector.field(3, float, maxlights) 29 | self.ambient_color = ti.Vector.field(3, float, ()) 30 | self.nlights = ti.field(int, ()) 31 | 32 | @ti.materialize_callback 33 | @ti.kernel 34 | def init_lights(): 35 | self.nlights[None] = 0 36 | for i in self.light_dirs: 37 | self.light_dirs[i] = [0, 0, 1, 0] 38 | self.light_colors[i] = [1, 1, 1] 39 | 40 | def set_lights(self, light_dirs): 41 | self.nlights[None] = len(light_dirs) 42 | for i, (dir, color) in enumerate(light_dirs): 43 | self.light_dirs[i] = dir 44 | self.light_colors[i] = color 45 | 46 | def clear_lights(self): 47 | self.nlights[None] = 0 48 | 49 | def add_light(self, dir=(0, 0, 1), pos=None, color=(1, 1, 1)): 50 | i = self.nlights[None] 51 | self.nlights[None] = i + 1 52 | self.set_light(i, dir, pos, color) 53 | return i 54 | 55 | def set_light(self, i, dir=(0, 0, 1), pos=None, color=(1, 1, 1)): 56 | if pos is not None: 57 | dir = np.array(pos) 58 | dirw = 1 59 | else: 60 | dir = np.array(dir) 61 | dir = dir / np.linalg.norm(dir) 62 | dirw = 0 63 | color = np.array(color) 64 | self.light_dirs[i] = dir.tolist() + [dirw] 65 | self.light_colors[i] = color.tolist() 66 | 67 | def set_ambient_light(self, color): 68 | self.ambient_color[None] = np.array(color).tolist() 69 | 70 | @ti.func 71 | def get_lights_range(self): 72 | for i in range(self.nlights[None]): 73 | yield i 74 | 75 | @ti.func 76 | def get_light_data(self, l): 77 | return self.light_dirs[l], self.light_colors[l] 78 | 79 | @ti.func 80 | def get_ambient_light_color(self): 81 | return self.ambient_color[None] 82 | 83 | @ti.func 84 | def shade_color(self, material, pos, normal, viewdir): 85 | res = V(0.0, 0.0, 0.0) 86 | res += material.emission() 87 | res += self.get_ambient_light_color() * material.ambient() 88 | for l in ti.smart(self.get_lights_range()): 89 | light, lcolor = self.get_light_data(l) 90 | light_dir = light.xyz - pos * light.w 91 | light_distance = light_dir.norm() 92 | light_dir /= light_distance 93 | cos_i = normal.dot(light_dir) 94 | if cos_i > 0: 95 | lcolor /= light_distance**2 96 | mcolor = material.brdf(normal, light_dir, viewdir) 97 | res += cos_i * lcolor * mcolor 98 | return res 99 | -------------------------------------------------------------------------------- /tina/core/material.py: -------------------------------------------------------------------------------- 1 | '''mockup''' 2 | -------------------------------------------------------------------------------- /tina/core/volume.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from ..advans import * 3 | 4 | 5 | @ti.data_oriented 6 | class VolumeRaster: 7 | def __init__(self, engine, N=128, taa=False, density=32, 8 | radius=None, gaussian=None, **extra_options): 9 | self.engine = engine 10 | self.res = self.engine.res 11 | if radius is None: 12 | radius = 1 if taa else 8 13 | if gaussian is None: 14 | gaussian = not taa 15 | if not taa: 16 | density = density * 6 17 | self.density = density 18 | self.gaussian = gaussian 19 | self.radius = radius 20 | self.taa = taa 21 | self.N = N 22 | 23 | self.dens = ti.field(float, (N, N, N)) 24 | self.occup = ti.field(float, self.res) 25 | self.tmcup = ti.field(float, self.res) 26 | 27 | self.L2W = ti.Matrix.field(4, 4, float, ()) 28 | 29 | @ti.materialize_callback 30 | def init_dens(): 31 | self.dens.fill(1) 32 | 33 | @ti.materialize_callback 34 | @ti.kernel 35 | def init_L2W(): 36 | self.L2W[None] = ti.Matrix.identity(float, 4) 37 | 38 | if self.gaussian: 39 | self.wei = ti.field(float, self.radius + 1) 40 | 41 | @ti.materialize_callback 42 | @ti.kernel 43 | def init_wei(): 44 | total = -1.0 45 | for i in self.wei: 46 | x = 1.6 * i / (self.radius + 1) 47 | r = ti.exp(-x**2) 48 | self.wei[i] = r 49 | total += r * 2 50 | for i in self.wei: 51 | self.wei[i] /= total 52 | 53 | @ti.kernel 54 | def set_object(self, voxl: ti.template()): 55 | self.L2W[None] = voxl.get_transform() 56 | for I in ti.grouped(self.dens): 57 | self.dens[I] = voxl.sample_volume(I / self.N) 58 | 59 | def set_volume_density(self, dens): 60 | self.dens.from_numpy(dens) 61 | 62 | @ti.kernel 63 | def _render_occup(self): 64 | noise = ti.static(tina.WangHashRNG.noise) 65 | uniq = ti.Vector([0 for i in range(4)]).cast(ti.u32) 66 | if ti.static(self.taa): 67 | uniq = ti.Vector([ti.random(ti.u32) for i in range(4)]) 68 | for P in ti.grouped(self.occup): 69 | self.occup[P] = 0 70 | for I in ti.grouped(self.dens): 71 | bias = ti.Vector([noise(V34(I, uniq[i])) for i in range(3)]) 72 | Pll = (I + bias) / self.N * 2 - 1 73 | Pl = mapply_pos(self.L2W[None], Pll) 74 | Pv = self.engine.to_viewspace(Pl) 75 | if not all(-1 < Pv < 1): 76 | continue 77 | P = int(self.engine.to_viewport(Pv)) 78 | if not all(0 <= P <= self.res - 1): 79 | continue 80 | Rl = 2 / self.N 81 | 82 | depth_f = Pv.z 83 | if ti.static(self.taa): 84 | DZl = mapply_dir(self.engine.V2W[None], V(0., 0., 1.)).normalized() 85 | Rvz = self.engine.to_viewspace(Pl + DZl * Rl).z - Pv.z 86 | depth_f += Rvz * noise(V34(I, uniq[3])) 87 | 88 | depth = int(depth_f * self.engine.maxdepth) 89 | if self.engine.depth[P] >= depth: 90 | 91 | DXl = mapply_dir(self.engine.V2W[None], V(1., 0., 0.)).normalized() 92 | DYl = mapply_dir(self.engine.V2W[None], V(0., 1., 0.)).normalized() 93 | Rv = V(0., 0.) 94 | Rv.x = self.engine.to_viewspace(Pl + DXl * Rl).x - Pv.x 95 | Rv.y = self.engine.to_viewspace(Pl + DYl * Rl).y - Pv.y 96 | r = Rv * self.res 97 | 98 | rho = self.dens[I] / self.N * self.density 99 | self.occup[P] += rho * r.x * r.y / (2 * self.radius + 1) 100 | 101 | @ti.kernel 102 | def blur(self, src: ti.template(), dst: ti.template(), dir: ti.template()): 103 | for P in ti.grouped(src): 104 | res = src[P] * 0 105 | bot = min(self.radius, P[dir]) 106 | top = min(self.radius, self.res[dir] - 1 - P[dir]) 107 | for i in range(-bot, top + 1): 108 | Q = P + U2(dir) * i 109 | fac = 1 / (top + 1 + bot) 110 | if ti.static(self.gaussian): 111 | fac = self.wei[abs(i)] 112 | res += src[Q] * fac 113 | dst[P] = res 114 | 115 | @ti.kernel 116 | def render_color(self, shader: ti.template()): 117 | for P in ti.grouped(self.occup): 118 | rho = max(0, self.occup[P]) 119 | fac = 1 - ti.exp(-rho) 120 | color = V(1., 1., 1.) 121 | shader.blend_color(fac, P, P, fac, color) 122 | 123 | def render_occup(self): 124 | self._render_occup() 125 | if self.radius: 126 | self.blur(self.occup, self.tmcup, 0) 127 | self.blur(self.tmcup, self.occup, 1) 128 | -------------------------------------------------------------------------------- /tina/core/wireframe.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class WireframeRaster: 6 | def __init__(self, engine, maxwires=MAX, linewidth=1.5, 7 | linecolor=(.9, .6, 0), clipping=False, **extra_options): 8 | self.engine = engine 9 | self.res = self.engine.res 10 | self.maxfaces = maxwires 11 | self.linewidth = linewidth 12 | self.clipping = clipping 13 | 14 | self.nwires = ti.field(int, ()) 15 | self.verts = ti.Vector.field(3, float, (maxwires, 2)) 16 | self.linecolor = ti.Vector.field(3, float, ()) 17 | 18 | @ti.materialize_callback 19 | def init_linecolor(): 20 | self.linecolor[None] = linecolor 21 | 22 | @ti.func 23 | def get_wires_range(self): 24 | for i in range(self.nwires[None]): 25 | yield i 26 | 27 | @ti.func 28 | def get_wire_vertices(self, f): 29 | A, B = self.verts[f, 0], self.verts[f, 1] 30 | return A, B 31 | 32 | @ti.kernel 33 | def set_object(self, mesh: ti.template()): 34 | mesh.pre_compute() 35 | self.nwires[None] = mesh.get_nfaces() 36 | for i in range(self.nwires[None]): 37 | verts = mesh.get_face_verts(i) 38 | for k in ti.static(range(2)): 39 | self.verts[i, k] = verts[k] 40 | 41 | @ti.kernel 42 | def set_wire_verts(self, verts: ti.ext_arr()): 43 | self.nfaces[None] = min(verts.shape[0], self.verts.shape[0]) 44 | for i in range(self.nfaces[None]): 45 | for k in ti.static(range(2)): 46 | for l in ti.static(range(3)): 47 | self.verts[i, k][l] = verts[i, k, l] 48 | 49 | @ti.func 50 | def draw_line(self, src, dst): 51 | dlt = dst - src 52 | adlt = abs(dlt) 53 | k, siz = V(1.0, 1.0), 0 54 | if adlt.x >= adlt.y: 55 | k.x = 1.0 if dlt.x >= 0 else -1.0 56 | k.y = k.x * dlt.y / dlt.x 57 | siz = int(adlt.x) 58 | else: 59 | k.y = 1.0 if dlt.y >= 0 else -1.0 60 | k.x = k.y * dlt.x / dlt.y 61 | siz = int(adlt.y) 62 | for i in range(siz + 1): 63 | pos = src + k * i 64 | yield pos, i / siz 65 | 66 | def render_occup(self): 67 | pass 68 | 69 | @ti.kernel 70 | def render_color(self, shader: ti.template()): 71 | for f in ti.smart(self.get_wires_range()): 72 | Al, Bl = self.get_wire_vertices(f) 73 | Av, Bv = [self.engine.to_viewspace(p) for p in [Al, Bl]] 74 | 75 | if ti.static(self.clipping): 76 | if not all(-1 <= Av <= 1): 77 | if not all(-1 <= Bv <= 1): 78 | continue 79 | 80 | a, b = [self.engine.to_viewport(p) for p in [Av, Bv]] 81 | 82 | ban = (b - a).normalized() 83 | wscale = 1 / ti.Vector([mapply(self.engine.W2V[None], p, 1)[1] for p in [Al, Bl]]) 84 | for p, cor in ti.smart(self.draw_line(a, b)): 85 | pos = p + self.engine.bias[None] 86 | P = ifloor(pos) 87 | if all(0 <= P < self.res): 88 | wei = V(1 - cor, cor) * wscale 89 | wei /= wei.x + wei.y 90 | depth_f = wei.x * Av.z + wei.y * Bv.z 91 | depth = int(depth_f * self.engine.maxdepth) 92 | if ti.atomic_min(self.engine.depth[P], depth) > depth: 93 | if self.engine.depth[P] >= depth: 94 | color = self.linecolor[None] 95 | shader.blend_color(self.engine, P, pos, 1, color) 96 | -------------------------------------------------------------------------------- /tina/hacker.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | from taichi.lang.common_ops import TaichiOperations 3 | 4 | hasattr(ti, '_tinahacked') or setattr(ti, '_tinahacked', 1) or setattr(ti, 5 | 'static', lambda x, *xs: [x] + list(xs) if xs else x) or setattr( 6 | ti.Matrix, 'element_wise_writeback_binary', (lambda f: lambda x, y, z: 7 | (y.__name__ != 'assign' or not setattr(y, '__name__', '_assign')) 8 | and f(x, y, z))(ti.Matrix.element_wise_writeback_binary)) or setattr( 9 | ti.Matrix, 'is_global', (lambda f: lambda x: len(x) and f(x))( 10 | ti.Matrix.is_global)) or setattr(TaichiOperations, '__pos__', 11 | lambda x: x) or setattr(ti, 'pi', __import__('math').pi) or setattr(ti, 12 | 'tau', __import__('math').tau) or setattr(ti, 'materialize_callback', 13 | (lambda f: lambda x: [(x() if ti.get_runtime().materialized else f(x)), 14 | x][1])(ti.materialize_callback)) or setattr(ti, 'expr_init', (lambda f: 15 | lambda x: x if isinstance(x, dict) else f(x))(ti.expr_init)) or setattr( 16 | ti, 'expr_init_func', (lambda f: lambda x: x if isinstance(x, dict) 17 | else f(x))(ti.expr_init_func)) or print('[Tina] Taichi properties hacked') 18 | 19 | 20 | @eval('lambda x: x()') 21 | def _(): 22 | class GUI(ti.GUI): 23 | def __init__(self, name='Tina', res=512, **kwargs): 24 | if isinstance(res, ti.Matrix): 25 | res = res.entries 26 | if isinstance(res, list): 27 | res = tuple(res) 28 | super().__init__(name=name, res=res, **kwargs) 29 | self._post_show_cbs = [] 30 | 31 | def post_show(self, cb): 32 | self._post_show_cbs.append(cb) 33 | return cb 34 | 35 | def rects(self, topleft, bottomright, radius=1, color=0xffffff): 36 | import numpy as np 37 | topright = np.stack([topleft[:, 0], bottomright[:, 1]], axis=1) 38 | bottomleft = np.stack([bottomright[:, 0], topleft[:, 1]], axis=1) 39 | self.lines(topleft, topright, radius, color) 40 | self.lines(topright, bottomright, radius, color) 41 | self.lines(bottomright, bottomleft, radius, color) 42 | self.lines(bottomleft, topleft, radius, color) 43 | 44 | def show(self, *args, **kwargs): 45 | super().show(*args, **kwargs) 46 | for cb in self._post_show_cbs: 47 | cb(self) 48 | 49 | ti.GUI = GUI 50 | 51 | 52 | @eval('lambda x: x()') 53 | def _(): 54 | if hasattr(ti, 'smart'): 55 | return 56 | 57 | ti.smart = lambda x: x 58 | 59 | import copy, ast 60 | from taichi.lang.transformer import ASTTransformerBase, ASTTransformerPreprocess 61 | 62 | old_get_decorator = ASTTransformerBase.get_decorator 63 | 64 | @staticmethod 65 | def get_decorator(node): 66 | if not (isinstance(node, ast.Call) 67 | and isinstance(node.func, ast.Attribute) and isinstance( 68 | node.func.value, ast.Name) and node.func.value.id == 'ti' 69 | and node.func.attr in ['smart']): 70 | return old_get_decorator(node) 71 | return node.func.attr 72 | 73 | ASTTransformerBase.get_decorator = get_decorator 74 | 75 | old_visit_struct_for = ASTTransformerPreprocess.visit_struct_for 76 | 77 | def visit_struct_for(self, node, is_grouped): 78 | if not is_grouped: 79 | decorator = self.get_decorator(node.iter) 80 | if decorator == 'smart': # so smart! 81 | self.current_control_scope().append('smart') 82 | self.generic_visit(node, ['body']) 83 | t = self.parse_stmt('if 1: pass; del a') 84 | t.body[0] = node 85 | target = copy.deepcopy(node.target) 86 | target.ctx = ast.Del() 87 | if isinstance(target, ast.Tuple): 88 | for tar in target.elts: 89 | tar.ctx = ast.Del() 90 | t.body[-1].targets = [target] 91 | return t 92 | 93 | return old_visit_struct_for(self, node, is_grouped) 94 | 95 | ASTTransformerPreprocess.visit_struct_for = visit_struct_for 96 | 97 | 98 | __all__ = [] 99 | -------------------------------------------------------------------------------- /tina/inject.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import functools 3 | import atexit 4 | import time 5 | 6 | 7 | _cbs = [] 8 | 9 | 10 | @atexit.register 11 | def _exit_callback(): 12 | for func in _cbs: 13 | func() 14 | 15 | 16 | def _inject(module, name, enable=True): 17 | if not enable: 18 | return lambda x: x 19 | 20 | def decorator(hook): 21 | func = getattr(module, name) 22 | if hasattr(func, '_injected'): 23 | func = func._injected 24 | 25 | @functools.wraps(func) 26 | def wrapped(*args, **kwargs): 27 | _taichi_skip_traceback = 1 28 | clb = hook(*args, **kwargs) 29 | ret = func(*args, **kwargs) 30 | if clb is not None: 31 | clb(ret) 32 | return ret 33 | 34 | wrapped._injected = func 35 | setattr(module, name, wrapped) 36 | return hook 37 | 38 | return decorator 39 | 40 | 41 | @_inject(ti.Kernel, 'materialize') 42 | def _(self, key=None, args=None, arg_features=None): 43 | self._key = key 44 | 45 | if key is None: 46 | key = (self.func, 0) 47 | if not self.runtime.materialized: 48 | self.runtime.materialize() 49 | if key in self.compiled_functions: 50 | return 51 | grad_suffix = "" 52 | if self.is_grad: 53 | grad_suffix = "_grad" 54 | name = "{}_c{}_{}{}".format(self.func.__qualname__, 55 | self.kernel_counter, key[1], 56 | grad_suffix) 57 | self._kname[key] = name 58 | 59 | @_cbs.append 60 | def callback(): 61 | if self._profile.get(name): 62 | x = sorted(self._profile[name]) 63 | if len(x) % 2 == 0: 64 | dt = (x[len(x) // 2] + x[len(x) // 2 - 1]) / 2 65 | else: 66 | dt = x[len(x) // 2] 67 | print(f'[{max(x):8.05f} {dt:8.05f} {len(x):4d}] {name}') 68 | 69 | 70 | @_inject(ti.Kernel, '__call__') 71 | def _(self, *args, **kwargs): 72 | def callback(ret): 73 | t1 = time.time() 74 | dt = t1 - t0 75 | self._profile.setdefault(self._kname[self._key], []).append(dt) 76 | 77 | if not hasattr(self, '_profile'): 78 | self._profile = {} 79 | if not hasattr(self, '_kname'): 80 | self._kname = {} 81 | 82 | #ti.trace('calling [' + self.func.__qualname__ + ']') 83 | t0 = time.time() 84 | return callback 85 | 86 | 87 | print('[Tina] Taichi JIT injected!') 88 | 89 | __all__ = [] 90 | 91 | 92 | if __name__ == '__main__': 93 | @ti.data_oriented 94 | class ODOP: 95 | @ti.kernel 96 | def func(self): 97 | print(233) 98 | 99 | c = ODOP() 100 | c.func() 101 | exit(1) 102 | -------------------------------------------------------------------------------- /tina/matr/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .nodes import * 3 | from .material import * 4 | from .wavelen import * 5 | -------------------------------------------------------------------------------- /tina/matr/nodes.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | 3 | 4 | @ti.data_oriented 5 | class Node: 6 | arguments = [] 7 | defaults = [] 8 | 9 | def __init__(self, **kwargs): 10 | self.params = {} 11 | for dfl, key in zip(self.defaults, self.arguments): 12 | if key not in kwargs: 13 | if dfl is None: 14 | raise ValueError(f'`{key}` must specified for `{type(self)}`') 15 | value = dfl 16 | else: 17 | value = kwargs[key] 18 | del kwargs[key] 19 | 20 | if isinstance(value, (int, float, ti.Matrix)): 21 | value = Const(value) 22 | elif isinstance(value, (list, tuple)): 23 | value = Const(V(*value)) 24 | elif isinstance(value, str): 25 | if any(value.endswith(x) for x in ['.png', '.jpg', '.bmp']): 26 | value = Texture(value) 27 | else: 28 | value = Input(value) 29 | self.params[key] = value 30 | 31 | for key in kwargs.keys(): 32 | raise TypeError( 33 | f"{type(self).__name__}() got an unexpected keyword argument '{key}', supported keywords are: {self.arguments}") 34 | 35 | def __call__(self, *args, **kwargs): 36 | raise NotImplementedError(type(self)) 37 | 38 | def param(self, key, *args, **kwargs): 39 | return self.params[key](*args, **kwargs) 40 | 41 | 42 | class Const(Node): 43 | # noinspection PyMissingConstructor 44 | def __init__(self, value): 45 | self.value = value 46 | 47 | @ti.func 48 | def __call__(self): 49 | return self.value 50 | 51 | 52 | class Param(Node): 53 | # noinspection PyMissingConstructor 54 | def __init__(self, dtype=float, dim=None, initial=0): 55 | if dim is not None: 56 | self.value = ti.Vector.field(dim, dtype, ()) 57 | else: 58 | self.value = ti.field(dtype, ()) 59 | 60 | self.initial = initial 61 | if initial != 0: 62 | @ti.materialize_callback 63 | def init_value(): 64 | self.value[None] = self.initial 65 | 66 | @ti.func 67 | def __call__(self): 68 | return self.value[None] 69 | 70 | def make_slider(self, gui, title, min=0, max=1, step=0.01): 71 | self.slider = gui.slider(title, min, max, step) 72 | self.slider.value = self.initial 73 | 74 | @gui.post_show 75 | def post_show(gui): 76 | self.value[None] = self.slider.value 77 | 78 | 79 | class Input(Node): 80 | g_pars = [] 81 | 82 | @staticmethod 83 | def spec_g_pars(pars): 84 | Input.g_pars.insert(0, pars) 85 | 86 | @staticmethod 87 | def clear_g_pars(): 88 | Input.g_pars.pop(0) 89 | 90 | # noinspection PyMissingConstructor 91 | def __init__(self, name): 92 | self.name = name 93 | 94 | @ti.func 95 | def __call__(self): 96 | return Input.g_pars[0][self.name] 97 | 98 | 99 | class Texture(Node): 100 | arguments = ['texcoord'] 101 | defaults = ['texcoord'] 102 | 103 | def __init__(self, path, **kwargs): 104 | self.texture = texture_as_field(path) 105 | super().__init__(**kwargs) 106 | 107 | @ti.func 108 | def __call__(self): 109 | maxcoor = V(*self.texture.shape) - 1 110 | coor = self.param('texcoord') * maxcoor 111 | return bilerp(self.texture, coor) 112 | 113 | 114 | class ChessboardTexture(Node): 115 | arguments = ['texcoord', 'size', 'color0', 'color1'] 116 | defaults = ['texcoord', 0.1, 0.4, 0.9] 117 | 118 | @ti.func 119 | def __call__(self): 120 | size = self.param('size') 121 | color0 = self.param('color0') 122 | color1 = self.param('color1') 123 | texcoord = self.param('texcoord') 124 | return lerp((texcoord // size).sum() % 2, color0, color1) 125 | 126 | 127 | class LerpTexture(Node): 128 | arguments = ['x0', 'x1', 'y0', 'y1', 'texcoord'] 129 | defaults = [0.0, 1.0, 0.0, 0.0, 'texcoord'] 130 | 131 | @ti.func 132 | def __call__(self): 133 | x0, x1, y0, y1, uv = tuple(map(self.param, self.arguments)) 134 | return lerp(uv.x, x0, x1) + lerp(uv.y, x0, x1) 135 | 136 | 137 | class LambdaNode(Node): 138 | def __init__(self, func, **kwargs): 139 | self.func = func 140 | self.arguments = list(kwargs.keys()) 141 | self.defaults = [None for _ in self.arguments] 142 | super().__init__(**kwargs) 143 | 144 | def __call__(self): 145 | return self.func(self) 146 | 147 | 148 | def lambda_node(func): 149 | import functools 150 | 151 | @functools.wraps(func) 152 | def wrapped(**kwargs): 153 | return LambdaNode(func, **kwargs) 154 | 155 | return wrapped 156 | -------------------------------------------------------------------------------- /tina/matr/wavelen.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.func 5 | def blackbody(temp, wave): 6 | wave *= 1e-9 7 | HCC2 = 1.1910429723971884140794892e-29 8 | HKC = 1.438777085924334052222404423195819240925e-2 9 | return wave ** -5 * HCC2 / (ti.exp(HKC / (wave * temp)) - 1) 10 | 11 | 12 | @ti.func 13 | def wav_to_rgb(wav): 14 | # https://blog.csdn.net/tanmx219/article/details/91658415 15 | r = 0.0 16 | g = 0.0 17 | b = 0.0 18 | alpha = 0.0 19 | 20 | # 3, 3, 2.5 21 | if 380.0 <= wav < 440.0: 22 | # .5, 0, 1 23 | r = -1.0 * (wav - 440.0) / (440.0 - 380.0) 24 | g = 0.0 25 | b = 1.0 26 | elif 440.0 <= wav < 490.0: 27 | # 0, .5, 1 28 | r = 0.0 29 | g = (wav - 440.0) / (490.0 - 440.0) 30 | b = 1.0 31 | elif 490.0 <= wav < 510.0: 32 | # 0, 1, .5 33 | r = 0.0 34 | g = 1.0 35 | b = -1.0 * (wav - 510.0) / (510.0 - 490.0) 36 | elif 510.0 <= wav < 580.0: 37 | # .5, 1, 0 38 | r = (wav - 510.0) / (580.0 - 510.0) 39 | g = 1.0 40 | b = 0.0 41 | elif 580.0 <= wav < 645.0: 42 | # 1, .5, 0 43 | r = 1.0 44 | g = -1.0 * (wav - 645.0) / (645.0 - 580.0) 45 | b = 0.0 46 | elif 645.0 <= wav < 780.0: 47 | # 1, 0, 0 48 | r = 1.0 49 | g = 0.0 50 | b = 0.0 51 | else: 52 | r = 0.0 53 | g = 0.0 54 | b = 0.0 55 | 56 | # 在可见光谱的边缘处强度较低。 57 | if 380.0 <= wav < 420.0: 58 | alpha = 0.30 + 0.70 * (wav - 380.0) / (420.0 - 380.0) 59 | elif 420.0 <= wav < 701.0: 60 | alpha = 1.0 61 | elif 701.0 <= wav < 780.0: 62 | alpha = 0.30 + 0.70 * (780.0 - wav) / (780.0 - 700.0) 63 | else: 64 | alpha = 0.0 65 | 66 | r *= 1.0 / 0.43511572 67 | g *= 1.0 / 0.5000342 68 | b *= 3.0 / 2.5 / 0.45338875 69 | return V(r, g, b) * alpha 70 | 71 | 72 | @ti.func 73 | def rgb_at_wav(rgb, wav): 74 | if ti.static(not isinstance(rgb, ti.Matrix)): 75 | return rgb 76 | ret = 0.0 77 | r, g, b = rgb 78 | if 645 <= wav < 780: 79 | ret += r / (0.3041293 * 1.179588 * 1.0081447) 80 | if 500 <= wav < 540: 81 | ret += g / (0.3092271 * 1.0824629 * 0.9944099) 82 | if 420 <= wav < 460: 83 | ret += b / (0.3240771 * 1.1763208 * 1.0136887) 84 | return ret 85 | 86 | 87 | @ti.func 88 | def random_wav(cho): 89 | #cho = ti.random(int) % 6 90 | cho = cho % 6 91 | ret = ti.random() 92 | if cho == 0: 93 | ret = lerp(ret, 645, 780) 94 | elif cho == 1: 95 | ret = lerp(ret, 580, 645) 96 | elif cho == 2: 97 | ret = lerp(ret, 510, 580) 98 | elif cho == 3: 99 | ret = lerp(ret, 490, 510) 100 | elif cho == 4: 101 | ret = lerp(ret, 440, 490) 102 | elif cho == 5: 103 | ret = lerp(ret, 380, 440) 104 | return ret 105 | -------------------------------------------------------------------------------- /tina/mesh/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .grid import * 3 | from .cull import * 4 | from .norm import * 5 | from .wire import * 6 | from .trans import * 7 | from .model import * 8 | from .simple import * 9 | from .prim import * 10 | from .conn import * 11 | from .export import * 12 | -------------------------------------------------------------------------------- /tina/mesh/base.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class MeshEditBase: 6 | def __init__(self, mesh): 7 | self.mesh = mesh 8 | 9 | def __getattr__(self, attr): 10 | return getattr(self.mesh, attr) 11 | -------------------------------------------------------------------------------- /tina/mesh/conn.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class ConnectiveMesh: 6 | def __init__(self, maxfaces=MAX, maxverts=MAX, npolygon=3): 7 | self.faces = ti.Vector.field(npolygon, int, maxfaces) 8 | self.verts = ti.Vector.field(3, float, maxverts) 9 | self.coors = ti.Vector.field(2, float, maxverts) 10 | self.norms = ti.Vector.field(3, float, maxverts) 11 | self.nfaces = ti.field(int, ()) 12 | 13 | self.maxfaces = maxfaces 14 | self.maxverts = maxverts 15 | self.npolygon = npolygon 16 | 17 | def get_npolygon(self): 18 | return self.npolygon 19 | 20 | @ti.func 21 | def pre_compute(self): 22 | pass 23 | 24 | def get_max_vert_nindex(self): 25 | return self.maxverts 26 | 27 | @ti.func 28 | def get_indiced_vert(self, i): 29 | return self.verts[i] 30 | 31 | @ti.func 32 | def get_indiced_norm(self, i): 33 | return self.norms[i] 34 | 35 | @ti.func 36 | def get_indiced_coor(self, i): 37 | return self.coors[i] 38 | 39 | @ti.func 40 | def get_nfaces(self): 41 | return self.nfaces[None] 42 | 43 | @ti.func 44 | def get_face_vert_indices(self, n): 45 | i = self.faces[n][0] 46 | j = self.faces[n][1] 47 | k = self.faces[n][2] 48 | return i, j, k 49 | 50 | @ti.func 51 | def _get_face_props(self, prop, n): 52 | a = prop[self.faces[n][0]] 53 | b = prop[self.faces[n][1]] 54 | c = prop[self.faces[n][2]] 55 | return a, b, c 56 | 57 | @ti.func 58 | def get_face_verts(self, n): 59 | return self._get_face_props(self.verts, n) 60 | 61 | @ti.func 62 | def get_face_coors(self, n): 63 | return self._get_face_props(self.coors, n) 64 | 65 | @ti.func 66 | def get_face_norms(self, n): 67 | return self._get_face_props(self.norms, n) 68 | 69 | @ti.kernel 70 | def set_vertices(self, verts: ti.ext_arr()): 71 | nverts = min(verts.shape[0], self.verts.shape[0]) 72 | for i in range(nverts): 73 | for k in ti.static(range(3)): 74 | self.verts[i][k] = verts[i, k] 75 | 76 | @ti.kernel 77 | def set_vert_norms(self, norms: ti.ext_arr()): 78 | nverts = min(norms.shape[0], self.norms.shape[0]) 79 | for i in range(nverts): 80 | for k in ti.static(range(3)): 81 | self.norms[i][k] = norms[i, k] 82 | 83 | @ti.kernel 84 | def set_vert_coors(self, coors: ti.ext_arr()): 85 | nverts = min(coors.shape[0], self.coors.shape[0]) 86 | for i in range(nverts): 87 | for k in ti.static(range(3)): 88 | self.coors[i][k] = coors[i, k] 89 | 90 | @ti.kernel 91 | def set_faces(self, faces: ti.ext_arr()): 92 | self.nfaces[None] = min(faces.shape[0], self.faces.shape[0]) 93 | for i in range(self.nfaces[None]): 94 | for k in ti.static(range(self.npolygon)): 95 | self.faces[i][k] = faces[i, k] 96 | 97 | -------------------------------------------------------------------------------- /tina/mesh/cull.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from .base import MeshEditBase 3 | 4 | 5 | class MeshFlipCulling(MeshEditBase): 6 | @ti.func 7 | def _flip(self, props: ti.template()): 8 | props_rev = list(reversed(props)) 9 | for i, prop in ti.static(enumerate(props_rev)): 10 | props[i] = prop 11 | 12 | @ti.func 13 | def get_face_verts(self, n): 14 | verts = self.mesh.get_face_verts(n) 15 | self._flip(verts) 16 | return verts 17 | 18 | @ti.func 19 | def get_face_norms(self, n): 20 | norms = self.mesh.get_face_norms(n) 21 | self._flip(norms) 22 | return norms 23 | 24 | @ti.func 25 | def get_face_coors(self, n): 26 | coors = self.mesh.get_face_coors(n) 27 | self._flip(coors) 28 | return coors 29 | 30 | 31 | class MeshNoCulling(MeshFlipCulling): 32 | @ti.func 33 | def get_nfaces(self): 34 | return self.mesh.get_nfaces() * 2 35 | 36 | @ti.func 37 | def get_face_verts(self, n): 38 | verts = self.mesh.get_face_verts(n // 2) 39 | if n % 2 != 0: 40 | self._flip(verts) 41 | return verts 42 | 43 | @ti.func 44 | def get_face_norms(self, n): 45 | norms = self.mesh.get_face_norms(n // 2) 46 | if n % 2 != 0: 47 | self._flip(norms) 48 | for i, norm in ti.static(enumerate(norms)): 49 | norms[i] = -norm 50 | return norms 51 | 52 | @ti.func 53 | def get_face_coors(self, n): 54 | coors = self.mesh.get_face_coors(n // 2) 55 | if n % 2 != 0: 56 | self._flip(coors) 57 | return coors 58 | 59 | 60 | class MeshFlipNormal(MeshEditBase): 61 | @ti.func 62 | def get_face_norms(self, n): 63 | norms = self.mesh.get_face_norms(n) 64 | for i, norm in ti.static(enumerate(norms)): 65 | norms[i] = -norm 66 | return norms 67 | -------------------------------------------------------------------------------- /tina/mesh/export.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | 3 | 4 | @ti.kernel 5 | def _pre_compute_mesh_nfaces(mesh: ti.template()) -> int: 6 | mesh.pre_compute() 7 | return mesh.get_nfaces() 8 | 9 | 10 | @ti.kernel 11 | def _export_mesh_verts(mesh: ti.template(), out: ti.ext_arr()): 12 | for i in range(out.shape[0]): 13 | verts = mesh.get_face_verts(i) 14 | for j, vert in ti.static(enumerate(verts)): 15 | for k, v in ti.static(enumerate(vert)): 16 | out[i, j, k] = v 17 | 18 | @ti.kernel 19 | def _export_mesh_norms(mesh: ti.template(), out: ti.ext_arr()): 20 | for i in range(out.shape[0]): 21 | norms = mesh.get_face_norms(i) 22 | for j, norm in ti.static(enumerate(norms)): 23 | for k, n in ti.static(enumerate(norm)): 24 | out[i, j, k] = n 25 | 26 | @ti.kernel 27 | def _export_mesh_coors(mesh: ti.template(), out: ti.ext_arr()): 28 | for i in range(out.shape[0]): 29 | coors = mesh.get_face_coors(i) 30 | for j, coor in ti.static(enumerate(coors)): 31 | for k, c in ti.static(enumerate(coor)): 32 | out[i, j, k] = c 33 | 34 | 35 | @ti.kernel 36 | def _export_face_indices(mesh: ti.template(), out: ti.ext_arr()): 37 | for i in range(out.shape[0]): 38 | indices = mesh.get_face_indices(i) 39 | for j, v in ti.static(enumerate(indices)): 40 | out[i, j] = v 41 | 42 | 43 | @ti.kernel 44 | def _export_indiced_verts(mesh: ti.template(), out: ti.ext_arr()): 45 | for i in range(out.shape[0]): 46 | vert = mesh.get_indiced_vert(i) 47 | for j, v in ti.static(enumerate(vert)): 48 | out[i, j] = v 49 | 50 | 51 | @ti.kernel 52 | def _export_indiced_norms(mesh: ti.template(), out: ti.ext_arr()): 53 | for i in range(out.shape[0]): 54 | norm = mesh.get_indiced_norm(i) 55 | for j, v in ti.static(enumerate(norm)): 56 | out[i, j] = v 57 | 58 | 59 | @ti.kernel 60 | def _export_indiced_coors(mesh: ti.template(), out: ti.ext_arr()): 61 | for i in range(out.shape[0]): 62 | coor = mesh.get_indiced_coor(i) 63 | for j, v in ti.static(enumerate(coor)): 64 | out[i, j] = v 65 | 66 | 67 | def export_simple_mesh(mesh): 68 | nfaces = _pre_compute_mesh_nfaces(mesh) 69 | npolygon = 3 70 | ret = {} 71 | if hasattr(mesh, 'get_npolygon'): 72 | npolygon = mesh.get_npolygon() 73 | assert hasattr(mesh, 'get_face_verts') 74 | out = np.empty((nfaces, npolygon, 3), dtype=np.float32) 75 | _export_mesh_verts(mesh, out) 76 | ret['fv'] = out 77 | if hasattr(mesh, 'get_face_norms'): 78 | out = np.empty((nfaces, npolygon, 3), dtype=np.float32) 79 | _export_mesh_norms(mesh, out) 80 | ret['fn'] = out 81 | if hasattr(mesh, 'get_face_coors'): 82 | out = np.empty((nfaces, npolygon, 2), dtype=np.float32) 83 | _export_mesh_coors(mesh, out) 84 | ret['ft'] = out 85 | return ret 86 | 87 | 88 | def simple_mesh_to_connective(obj): 89 | nfaces = len(obj['fv']) 90 | npolygon = len(obj['fv'][0]) 91 | ret = {} 92 | ret['f'] = np.arange(nfaces * npolygon).reshape(nfaces, npolygon) 93 | ret['v'] = obj['fv'].reshape(nfaces * npolygon, 3) 94 | if 'fvn' in obj: 95 | ret['vn'] = obj['fvn'].reshape(nfaces * npolygon, 3) 96 | if 'fvt' in obj: 97 | ret['vt'] = obj['fvt'].reshape(nfaces * npolygon, 3) 98 | return ret 99 | 100 | 101 | def export_connective_mesh(mesh): 102 | if not hasattr(mesh, 'get_face_indices'): 103 | obj = export_simple_mesh(mesh) 104 | return simple_mesh_to_connective(obj) 105 | nfaces = _pre_compute_mesh(mesh) 106 | npolygon = 3 107 | ret = {} 108 | if hasattr(mesh, 'get_npolygon'): 109 | npolygon = mesh.get_npolygon() 110 | indices = np.empty((nfaces, npolygon), dtype=np.float32) 111 | _export_mesh_indices(indices) 112 | ret['f'] = indices 113 | nverts = np.max(indices) 114 | if hasattr(mesh, 'get_indiced_vert'): 115 | out = np.empty((nverts, npolygon, 3), dtype=np.float32) 116 | _export_mesh_indiced_verts(mesh, out) 117 | ret['v'] = out 118 | if hasattr(mesh, 'get_indiced_norm'): 119 | out = np.empty((nverts, npolygon, 3), dtype=np.float32) 120 | _export_mesh_indiced_norms(mesh, out) 121 | ret['vn'] = out 122 | if hasattr(mesh, 'get_indiced_coor'): 123 | out = np.empty((nverts, npolygon, 2), dtype=np.float32) 124 | _export_mesh_indiced_coors(mesh, out) 125 | ret['vt'] = out 126 | return ret 127 | -------------------------------------------------------------------------------- /tina/mesh/grid.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class MeshGrid: 6 | def __init__(self, res, as_quad=False): 7 | if isinstance(res, int): 8 | res = res, res 9 | self.res = V(*res) 10 | self.pos = ti.Vector.field(3, float, self.res) 11 | self.nrm = ti.Vector.field(3, float, self.res) 12 | self.tex = ti.Vector.field(2, float, self.res) 13 | self.as_quad = as_quad 14 | 15 | @ti.materialize_callback 16 | @ti.kernel 17 | def init_pos(): 18 | for I in ti.grouped(self.pos): 19 | u, v = I / (self.res - 1) 20 | self.tex[I] = V(u, v) 21 | self.pos[I] = V(u * 2 - 1, v * 2 - 1, 0) 22 | 23 | def get_npolygon(self): 24 | return 4 if self.as_quad else 3 25 | 26 | @ti.func 27 | def pre_compute(self): 28 | for i, j in self.pos: 29 | i2 = max(i - 1, 0) 30 | j2 = max(j - 1, 0) 31 | i1 = min(i + 1, self.res.x - 1) 32 | j1 = min(j + 1, self.res.y - 1) 33 | dy = self.pos[i, j1] - self.pos[i, j2] 34 | dx = self.pos[i1, j] - self.pos[i2, j] 35 | self.nrm[i, j] = dx.cross(dy).normalized() 36 | 37 | @ti.func 38 | def get_nfaces(self): 39 | n = (self.res.x - 1) * (self.res.y - 1) 40 | if ti.static(self.as_quad): 41 | return n 42 | else: 43 | return n * 2 44 | 45 | @ti.func 46 | def _get_face_props(self, prop, n): 47 | stride = self.res.x - 1 48 | m = n 49 | if ti.static(not self.as_quad): 50 | m = n // 2 51 | i, j = V(m // stride, m % stride) 52 | a, b = prop[i, j], prop[i + 1, j] 53 | c, d = prop[i + 1, j + 1], prop[i, j + 1] 54 | if ti.static(self.as_quad): 55 | return a, b, c, d 56 | if n % 2 != 0: 57 | a, b, c = a, c, d 58 | return a, b, c 59 | 60 | @ti.func 61 | def get_face_verts(self, n): 62 | return self._get_face_props(self.pos, n) 63 | 64 | @ti.func 65 | def get_face_norms(self, n): 66 | return self._get_face_props(self.nrm, n) 67 | 68 | @ti.func 69 | def get_face_coors(self, n): 70 | return self._get_face_props(self.tex, n) 71 | -------------------------------------------------------------------------------- /tina/mesh/model.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class MeshModel: 6 | def __init__(self, obj, *_, **__): 7 | ''' 8 | :param obj: (OBJ | str) the OBJ model as to be displayed, automatically invoke tina.readobj if a string is specified 9 | :return: (Mesh) the mesh object to add into scene 10 | ''' 11 | 12 | if isinstance(obj, str): 13 | obj = tina.readobj(obj, *_, **__) 14 | 15 | self.faces = ti.Matrix.field(3, 3, int, len(obj['f'])) 16 | self.verts = ti.Vector.field(3, float, len(obj['v'])) 17 | self.coors = ti.Vector.field(2, float, len(obj['vt'])) 18 | self.norms = ti.Vector.field(3, float, len(obj['vn'])) 19 | 20 | @ti.materialize_callback 21 | def init_mesh(): 22 | faces = obj['f'] 23 | if len(faces.shape) == 2: 24 | faces = np.stack([faces, faces, faces], axis=2) 25 | self.faces.from_numpy(faces.astype(np.uint32)) 26 | self.verts.from_numpy(obj['v']) 27 | self.coors.from_numpy(obj['vt']) 28 | self.norms.from_numpy(obj['vn']) 29 | 30 | self.maxfaces = len(obj['f']) 31 | self.maxverts = len(obj['v']) 32 | self.maxcoors = len(obj['vt']) 33 | self.maxnorms = len(obj['vn']) 34 | 35 | def get_npolygon(self): 36 | return 3 37 | 38 | @ti.func 39 | def pre_compute(self): 40 | pass 41 | 42 | def get_max_vert_nindex(self): 43 | return self.maxverts 44 | 45 | @ti.func 46 | def get_nfaces(self): 47 | return self.faces.shape[0] 48 | 49 | @ti.func 50 | def get_face_vert_indices(self, n): 51 | i = self.faces[n][0, 0] 52 | j = self.faces[n][1, 0] 53 | k = self.faces[n][2, 0] 54 | return i, j, k 55 | 56 | @ti.func 57 | def _get_face_props(self, prop, index: ti.template(), n): 58 | a = prop[self.faces[n][0, index]] 59 | b = prop[self.faces[n][1, index]] 60 | c = prop[self.faces[n][2, index]] 61 | return a, b, c 62 | 63 | @ti.func 64 | def get_face_verts(self, n): 65 | return self._get_face_props(self.verts, 0, n) 66 | 67 | @ti.func 68 | def get_face_coors(self, n): 69 | return self._get_face_props(self.coors, 1, n) 70 | 71 | @ti.func 72 | def get_face_norms(self, n): 73 | return self._get_face_props(self.norms, 2, n) 74 | -------------------------------------------------------------------------------- /tina/mesh/norm.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from .base import MeshEditBase 3 | 4 | 5 | class MeshFlatNormal(MeshEditBase): 6 | @ti.func 7 | def _calc_norm(self, a, b, c): 8 | return (b - a).cross(c - a).normalized() 9 | 10 | @ti.func 11 | def get_face_norms(self, n): 12 | verts = self.mesh.get_face_verts(n) 13 | norm = self._calc_norm(verts[0], verts[1], verts[2]) 14 | return [norm for vert in verts] 15 | 16 | 17 | class MeshSmoothNormal(MeshEditBase): 18 | def __init__(self, mesh, cached=True): 19 | super().__init__(mesh) 20 | 21 | N = self.mesh.get_max_vert_nindex() 22 | self.norm = ti.Vector.field(3, float, N) 23 | 24 | self.cached = cached 25 | if self.cached: 26 | ti.materialize_callback(self.update_normal) 27 | 28 | @ti.func 29 | def pre_compute(self): 30 | self.mesh.pre_compute() 31 | if ti.static(not self.cached): 32 | self._update_normal() 33 | 34 | @ti.func 35 | def _update_normal(self): 36 | for i in self.norm: 37 | self.norm[i] = 0 38 | for n in range(self.mesh.get_nfaces()): 39 | i, j, k = self.mesh.get_face_vert_indices(n) 40 | a, b, c = self.mesh.get_face_verts(n) 41 | nrm = (b - a).cross(c - a).normalized() 42 | self.norm[i] += nrm 43 | self.norm[j] += nrm 44 | self.norm[k] += nrm 45 | for i in self.norm: 46 | self.norm[i] = self.norm[i].normalized() 47 | 48 | @ti.kernel 49 | def update_normal(self): 50 | self._update_normal() 51 | 52 | @ti.func 53 | def get_face_norms(self, n): 54 | i, j, k = self.mesh.get_face_vert_indices(n) 55 | return self.norm[i], self.norm[j], self.norm[k] 56 | -------------------------------------------------------------------------------- /tina/mesh/prim.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from .simple import * 3 | 4 | 5 | @ti.data_oriented 6 | class PrimitiveMesh(SimpleMesh): 7 | def __init__(self, faces): 8 | if not isinstance(faces, np.ndarray): 9 | faces = np.array(faces, dtype=np.float32) 10 | verts = faces[:, :, 0] 11 | norms = faces[:, :, 1] 12 | coors = faces[:, :, 2] 13 | super().__init__(maxfaces=len(verts), npolygon=len(verts[0])) 14 | 15 | @ti.materialize_callback 16 | def init_mesh(): 17 | self.set_face_verts(verts) 18 | self.set_face_norms(norms) 19 | self.set_face_coors(coors) 20 | 21 | @classmethod 22 | def sphere(cls, lons=32, lats=24, rad=1): 23 | def at(lat, lon): 24 | lat /= lats 25 | lon /= lons 26 | coor = lon, lat, 0 27 | lat = (lat - 0.5) * np.pi 28 | lon = (lon * 2 - 1) * np.pi 29 | x = np.cos(lat) * np.cos(lon) 30 | y = np.cos(lat) * np.sin(lon) 31 | z = np.sin(lat) 32 | norm = x, y, z 33 | vert = x * rad, y * rad, z * rad 34 | return vert, norm, coor 35 | 36 | faces = [] 37 | for lat in range(lats): 38 | for lon in range(lons): 39 | v1 = at(lat, lon) 40 | v2 = at(lat, lon + 1) 41 | v3 = at(lat + 1, lon + 1) 42 | v4 = at(lat + 1, lon) 43 | if lat != 0: 44 | faces.append([v1, v2, v3]) 45 | if lat != lats - 1: 46 | faces.append([v3, v4, v1]) 47 | return cls(faces) 48 | 49 | @classmethod 50 | def cylinder(cls, lons=32, lats=4, rad=1, hei=2): 51 | def at(lat, lon, rad=rad): 52 | lat /= lats 53 | lon /= lons 54 | coor = lon, lat, 0 55 | lat = lat - 0.5 56 | lon = (lon * 2 - 1) * np.pi 57 | x = np.cos(lon) 58 | y = np.sin(lon) 59 | z = lat 60 | vert = x * rad, y * rad, z * hei 61 | norm = x, y, 0 62 | return vert, norm, coor 63 | 64 | faces = [] 65 | for lat in range(lats): 66 | for lon in range(lons): 67 | v1 = at(lat, lon) 68 | v2 = at(lat, lon + 1) 69 | v3 = at(lat + 1, lon + 1) 70 | v4 = at(lat + 1, lon) 71 | faces.append([v1, v2, v3]) 72 | faces.append([v3, v4, v1]) 73 | for lon in range(lons): 74 | norm = 0, 0, -1 75 | coor = 0, 0, -1 76 | v1 = (0, 0, -hei / 2), norm, coor 77 | v2 = at(0, lon)[0], norm, coor 78 | v3 = at(0, lon + 1)[0], norm, coor 79 | faces.append([v3, v2, v1]) 80 | for lon in range(lons): 81 | norm = 0, 0, 1 82 | coor = 0, 0, 1 83 | v1 = (0, 0, hei / 2), norm, coor 84 | v2 = at(lats, lon)[0], norm, coor 85 | v3 = at(lats, lon + 1)[0], norm, coor 86 | faces.append([v1, v2, v3]) 87 | return cls(faces) 88 | 89 | @classmethod 90 | def asset(cls, name): 91 | obj = tina.readobj('assets/' + name + '.obj', quadok=True) 92 | verts = obj['v'][obj['f'][:, :, 0]] 93 | coors = obj['vt'][obj['f'][:, :, 1]] 94 | norms = obj['vn'][obj['f'][:, :, 2]] 95 | faces = [] 96 | for vs, cs, ns in zip(verts, coors, norms): 97 | cs = list(cs) 98 | for i, c in enumerate(cs): 99 | cs[i] = list(c) + [0] 100 | faces.append(list(zip(vs, cs, ns))) 101 | return cls(faces) 102 | -------------------------------------------------------------------------------- /tina/mesh/simple.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class SimpleMesh: 6 | def __init__(self, maxfaces=MAX, npolygon=3): 7 | ''' 8 | :param maxfaces: (int) the maximum amount of faces to be supported 9 | :param npolygon: (int) number of polygon edges, 3 for triangles 10 | :return: (Mesh) the mesh object to add into scene 11 | ''' 12 | 13 | self.verts = ti.Vector.field(3, float, (maxfaces, npolygon)) 14 | self.coors = ti.Vector.field(2, float, (maxfaces, npolygon)) 15 | self.norms = ti.Vector.field(3, float, (maxfaces, npolygon)) 16 | self.mtlids = ti.field(int, maxfaces) 17 | self.nfaces = ti.field(int, ()) 18 | 19 | self.maxfaces = maxfaces 20 | self.npolygon = npolygon 21 | 22 | def get_npolygon(self): 23 | return self.npolygon 24 | 25 | @ti.func 26 | def pre_compute(self): 27 | pass 28 | 29 | @ti.func 30 | def get_nfaces(self): 31 | return min(self.nfaces[None], self.maxfaces) 32 | 33 | @ti.func 34 | def _get_face_props(self, prop, n): 35 | return [prop[n, i] for i in range(self.npolygon)] 36 | 37 | @ti.func 38 | def get_face_verts(self, n): 39 | return self._get_face_props(self.verts, n) 40 | 41 | @ti.func 42 | def get_face_coors(self, n): 43 | return self._get_face_props(self.coors, n) 44 | 45 | @ti.func 46 | def get_face_norms(self, n): 47 | return self._get_face_props(self.norms, n) 48 | 49 | @ti.func 50 | def get_face_mtlid(self, n): 51 | return self.mtlids[n] 52 | 53 | @ti.kernel 54 | def set_face_verts(self, verts: ti.ext_arr()): 55 | ''' 56 | :param verts: (np.array[nfaces, npolygon, 3]) the vertex positions of faces 57 | 58 | Specify the face vertex positions to be rendered 59 | 60 | :note: the number of faces is determined by the array's shape[0] 61 | ''' 62 | 63 | self.nfaces[None] = min(verts.shape[0], self.verts.shape[0]) 64 | for i in range(self.nfaces[None]): 65 | for k in ti.static(range(self.npolygon)): 66 | for l in ti.static(range(3)): 67 | self.verts[i, k][l] = verts[i, k, l] 68 | 69 | @ti.kernel 70 | def set_face_norms(self, norms: ti.ext_arr()): 71 | ''' 72 | :param norms: (np.array[nfaces, npolygon, 3]) the vertex normal vectors of faces 73 | 74 | Specify the face vertex normals to be rendered 75 | 76 | :note: the normals should be normalized to get desired result 77 | :note: this should be invoked only *after* set_face_verts for nfaces 78 | ''' 79 | 80 | for i in range(self.nfaces[None]): 81 | for k in ti.static(range(self.npolygon)): 82 | for l in ti.static(range(3)): 83 | self.norms[i, k][l] = norms[i, k, l] 84 | 85 | @ti.kernel 86 | def set_face_coors(self, coors: ti.ext_arr()): 87 | ''' 88 | :param norms: (np.array[nfaces, npolygon, 2]) the vertex texture coordinates of faces 89 | 90 | Specify the face vertex texcoords to be rendered 91 | 92 | :note: this should be invoked only *after* set_face_verts for nfaces 93 | ''' 94 | 95 | for i in range(self.nfaces[None]): 96 | for k in ti.static(range(self.npolygon)): 97 | for l in ti.static(range(2)): 98 | self.coors[i, k][l] = coors[i, k, l] 99 | 100 | @ti.kernel 101 | def set_face_mtlids(self, mtlids: ti.ext_arr()): 102 | ''' 103 | :param mtlids: (np.array[nfaces]) the material ids of faces 104 | 105 | Specify the face material ids to be rendered 106 | 107 | :note: this should be invoked only *after* set_face_verts for nfaces 108 | ''' 109 | for i in range(self.nfaces[None]): 110 | self.mtlids[i] = mtlids[i] 111 | 112 | @ti.kernel 113 | def set_material_id(self, mtlid: int): 114 | for i in range(self.nfaces[None]): 115 | self.mtlids[i] = mtlid 116 | -------------------------------------------------------------------------------- /tina/mesh/trans.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from .base import MeshEditBase 3 | 4 | 5 | class MeshTransform(MeshEditBase): 6 | def __init__(self, mesh, trans=None): 7 | super().__init__(mesh) 8 | 9 | self.trans = ti.Matrix.field(4, 4, float, ()) 10 | self.trans_normal = ti.Matrix.field(3, 3, float, ()) 11 | 12 | @ti.materialize_callback 13 | @ti.kernel 14 | def init_trans(): 15 | self.trans[None] = ti.Matrix.identity(float, 4) 16 | self.trans_normal[None] = ti.Matrix.identity(float, 3) 17 | 18 | if trans is not None: 19 | @ti.materialize_callback 20 | def init_trans_arg(): 21 | self.set_transform(trans) 22 | 23 | def set_transform(self, trans): 24 | trans_normal = np.transpose(np.linalg.inv(trans)) 25 | self.trans[None] = np.array(trans).tolist() 26 | self.trans_normal[None] = np.array(trans_normal).tolist() 27 | 28 | @ti.func 29 | def get_face_verts(self, n): 30 | verts = self.mesh.get_face_verts(n) 31 | for i, vert in ti.static(enumerate(verts)): 32 | verts[i] = mapply_pos(self.trans[None], vert) 33 | return verts 34 | 35 | @ti.func 36 | def get_face_norms(self, n): 37 | norms = self.mesh.get_face_norms(n) 38 | for i, norm in ti.static(enumerate(norms)): 39 | norms[i] = self.trans_normal[None] @ norm 40 | return norms 41 | -------------------------------------------------------------------------------- /tina/mesh/wire.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from .base import MeshEditBase 3 | 4 | 5 | class MeshToWire(MeshEditBase): 6 | def __init__(self, mesh): 7 | super().__init__(mesh) 8 | if hasattr(self.mesh, 'get_npolygon'): 9 | self.src_npolygon = self.mesh.get_npolygon() 10 | else: 11 | self.src_npolygon = 3 12 | 13 | def get_npolygon(self): 14 | return 2 15 | 16 | @ti.func 17 | def get_nfaces(self): 18 | return self.mesh.get_nfaces() * self.src_npolygon 19 | 20 | @ti.func 21 | def _get_face_props(self, src_getter: ti.template(), n): 22 | props = src_getter(n // self.src_npolygon) 23 | ep1 = n % self.src_npolygon 24 | ep2 = (n + 1) % self.src_npolygon 25 | p1 = list_subscript(props, ep1) 26 | p2 = list_subscript(props, ep2) 27 | return p1, p2 28 | 29 | @ti.func 30 | def get_face_verts(self, n): 31 | return self._get_face_props(self.mesh.get_face_verts, n) 32 | 33 | @ti.func 34 | def get_face_coors(self, n): 35 | return self._get_face_props(self.mesh.get_face_coors, n) 36 | 37 | @ti.func 38 | def get_face_norms(self, n): 39 | return self._get_face_props(self.mesh.get_face_norms, n) 40 | -------------------------------------------------------------------------------- /tina/pars/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .simple import * 3 | from .trans import * 4 | -------------------------------------------------------------------------------- /tina/pars/base.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class ParsEditBase: 6 | def __init__(self, pars): 7 | self.pars = pars 8 | 9 | def __getattr__(self, attr): 10 | return getattr(self.pars, attr) 11 | -------------------------------------------------------------------------------- /tina/pars/export.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | 3 | 4 | @ti.kernel 5 | def _pre_compute_pars_npars(pars: ti.template()) -> int: 6 | pars.pre_compute() 7 | return pars.get_npars() 8 | 9 | 10 | @ti.kernel 11 | def _export_pars_verts(pars: ti.template(), out: ti.ext_arr()): 12 | for i in range(out.shape[0]): 13 | vert = pars.get_particle_position(i) 14 | for j, v in ti.static(enumerate(vert)): 15 | out[i, j] = v 16 | 17 | @ti.kernel 18 | def _export_pars_sizes(pars: ti.template(), out: ti.ext_arr()): 19 | for i in range(out.shape[0]): 20 | size = pars.get_particle_radius(i) 21 | out[i] = size 22 | 23 | @ti.kernel 24 | def _export_pars_colors(pars: ti.template(), out: ti.ext_arr()): 25 | for i in range(out.shape[0]): 26 | color = pars.get_particle_color(i) 27 | for j, c in ti.static(enumerate(color)): 28 | out[i, j] = c 29 | 30 | 31 | def export_simple_pars(pars): 32 | npars = _pre_compute_pars_npars(pars) 33 | ret = {} 34 | assert hasattr(pars, 'get_particle_position') 35 | out = np.empty((npars, 3), dtype=np.float32) 36 | _export_pars_verts(pars, out) 37 | ret['v'] = out 38 | if hasattr(pars, 'get_particle_radius'): 39 | out = np.empty(npars, dtype=np.float32) 40 | _export_pars_sizes(pars, out) 41 | ret['vr'] = out 42 | if hasattr(pars, 'get_particle_color'): 43 | out = np.empty((npars, 3), dtype=np.float32) 44 | _export_pars_colors(pars, out) 45 | ret['vc'] = out 46 | return ret 47 | -------------------------------------------------------------------------------- /tina/pars/simple.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class SimpleParticles: 6 | def __init__(self, maxpars=65536, radius=0.02): 7 | self.verts = ti.Vector.field(3, float, maxpars) 8 | self.sizes = ti.field(float, maxpars) 9 | self.colors = ti.Vector.field(3, float, maxpars) 10 | self.npars = ti.field(int, ()) 11 | 12 | @ti.materialize_callback 13 | def init_pars(): 14 | self.sizes.fill(radius) 15 | self.colors.fill(1) 16 | 17 | self.maxpars = maxpars 18 | 19 | @ti.func 20 | def pre_compute(self): 21 | pass 22 | 23 | @ti.func 24 | def get_npars(self): 25 | return min(self.npars[None], self.maxpars) 26 | 27 | @ti.func 28 | def get_particle_position(self, n): 29 | return self.verts[n] 30 | 31 | @ti.func 32 | def get_particle_radius(self, n): 33 | return self.sizes[n] 34 | 35 | @ti.func 36 | def get_particle_color(self, n): 37 | return self.colors[n] 38 | 39 | @ti.kernel 40 | def set_particles(self, verts: ti.ext_arr()): 41 | self.npars[None] = min(verts.shape[0], self.verts.shape[0]) 42 | for i in range(self.npars[None]): 43 | for k in ti.static(range(3)): 44 | self.verts[i][k] = verts[i, k] 45 | 46 | @ti.kernel 47 | def set_particle_radii(self, sizes: ti.ext_arr()): 48 | for i in range(self.npars[None]): 49 | self.sizes[i] = sizes[i] 50 | 51 | @ti.kernel 52 | def set_particle_colors(self, colors: ti.ext_arr()): 53 | for i in range(self.npars[None]): 54 | for k in ti.static(range(3)): 55 | self.colors[i][k] = colors[i, k] 56 | -------------------------------------------------------------------------------- /tina/pars/trans.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from .base import ParsEditBase 3 | 4 | 5 | class ParsTransform(ParsEditBase): 6 | def __init__(self, pars): 7 | super().__init__(pars) 8 | 9 | self.trans = ti.Matrix.field(4, 4, float, ()) 10 | self.scale = ti.field(float, ()) 11 | 12 | @ti.materialize_callback 13 | @ti.kernel 14 | def init_trans(): 15 | self.trans[None] = ti.Matrix.identity(float, 4) 16 | self.scale[None] = 1 17 | 18 | def set_transform(self, trans, scale): 19 | self.trans[None] = np.array(trans).tolist() 20 | self.scale[None] = scale 21 | 22 | @ti.func 23 | def get_particle_position(self, n): 24 | vert = self.pars.get_particle_position(n) 25 | return mapply_pos(self.trans[None], vert) 26 | 27 | @ti.func 28 | def get_particle_radius(self, n): 29 | size = self.pars.get_particle_radius(n) 30 | return self.scale[None] * size 31 | -------------------------------------------------------------------------------- /tina/path/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .engine import * 3 | from .geometry import * 4 | from .particle import * 5 | from .triangle import * 6 | from .volume import * 7 | from .tree import * 8 | -------------------------------------------------------------------------------- /tina/path/geometry.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | 3 | 4 | @ti.func 5 | def ray_aabb_hit(bmin, bmax, ro, rd): 6 | near = -inf 7 | far = inf 8 | 9 | for i in ti.static(range(bmin.n)): 10 | if abs(rd[i]) < eps: 11 | if ro[i] < bmin[i] or ro[i] > bmax[i]: 12 | hit = 0 13 | else: 14 | i1 = (bmin[i] - ro[i]) / rd[i] 15 | i2 = (bmax[i] - ro[i]) / rd[i] 16 | 17 | far = min(far, max(i1, i2)) 18 | near = max(near, min(i1, i2)) 19 | 20 | return near, far 21 | 22 | ''' 23 | @ti.func 24 | def ray_triangle_hit(v0, v1, v2, ro, rd): 25 | e1 = v1 - v0 26 | e2 = v2 - v0 27 | p = rd.cross(e2) 28 | det = e1.dot(p) 29 | s = ro - v0 30 | 31 | t, u, v = inf * 2, 0.0, 0.0 32 | 33 | if det < 0: 34 | s = -s 35 | det = -det 36 | 37 | if det >= eps: 38 | u = s.dot(p) 39 | if 0 <= u <= det: 40 | q = s.cross(e1) 41 | v = rd.dot(q) 42 | if v >= 0 and u + v <= det: 43 | t = e2.dot(q) 44 | det = 1 / det 45 | t *= det 46 | u *= det 47 | v *= det 48 | 49 | return t, V(u, v) 50 | ''' 51 | 52 | #''' 53 | @ti.func 54 | def ray_triangle_hit(v0, v1, v2, ro, rd): 55 | u = v1 - v0 56 | v = v2 - v0 57 | norm = u.cross(v) 58 | depth = inf * 2 59 | s, t = 0., 0. 60 | hit = 0 61 | 62 | b = norm.dot(rd) 63 | if abs(b) >= eps: 64 | w0 = ro - v0 65 | a = -norm.dot(w0) 66 | r = a / b 67 | if r > 0: 68 | ip = ro + r * rd 69 | uu = u.dot(u) 70 | uv = u.dot(v) 71 | vv = v.dot(v) 72 | w = ip - v0 73 | wu = w.dot(u) 74 | wv = w.dot(v) 75 | D = uv * uv - uu * vv 76 | s = (uv * wv - vv * wu) / D 77 | t = (uv * wu - uu * wv) / D 78 | if 0 <= s <= 1: 79 | if 0 <= t and s + t <= 1: 80 | depth = r 81 | hit = 1 82 | return hit, depth, V(s, t) 83 | 84 | 85 | @ti.func 86 | def ray_triangle_cull_hit(v0, v1, v2, ro, rd): 87 | u = v1 - v0 88 | v = v2 - v0 89 | norm = u.cross(v) 90 | depth = inf * 2 91 | s, t = 0., 0. 92 | hit = 0 93 | 94 | b = -norm.dot(rd) 95 | if b >= eps: 96 | w0 = ro - v0 97 | a = norm.dot(w0) 98 | r = a / b 99 | if r > 0: 100 | ip = ro + r * rd 101 | uu = u.dot(u) 102 | uv = u.dot(v) 103 | vv = v.dot(v) 104 | w = ip - v0 105 | wu = w.dot(u) 106 | wv = w.dot(v) 107 | D = uv * uv - uu * vv 108 | s = (uv * wv - vv * wu) / D 109 | t = (uv * wu - uu * wv) / D 110 | if 0 <= s <= 1: 111 | if 0 <= t and s + t <= 1: 112 | depth = r 113 | hit = 1 114 | return hit, depth, V(s, t) 115 | #''' 116 | 117 | @ti.func 118 | def ray_sphere_hit(pos, rad, ro, rd): 119 | t = inf * 2 120 | op = pos - ro 121 | b = op.dot(rd) 122 | det = b**2 - op.norm_sqr() + rad**2 123 | if det >= 0: 124 | det = ti.sqrt(det) 125 | t = b - det 126 | if t <= eps: 127 | t = b + det 128 | if t <= eps: 129 | t = inf * 2 130 | return t < inf, t 131 | -------------------------------------------------------------------------------- /tina/path/particle.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | from .geometry import * 3 | 4 | 5 | @ti.data_oriented 6 | class ParticleTracer: 7 | @ti.func 8 | def calc_geometry(self, ind, uv, pos): 9 | nrm = (pos - self.verts[ind]).normalized() 10 | mtlid = self.mtlids[ind] 11 | return nrm, V(0., 0.), mtlid 12 | 13 | def __init__(self, maxpars=65536 * 16, coloring=True, multimtl=True, **extra_options): 14 | self.coloring = coloring 15 | self.multimtl = multimtl 16 | self.maxpars = maxpars 17 | 18 | self.verts = ti.Vector.field(3, float, maxpars) 19 | self.sizes = ti.field(float, maxpars) 20 | if self.coloring: 21 | self.colors = ti.Vector.field(3, float, maxpars) 22 | if self.multimtl: 23 | self.mtlids = ti.field(int, maxpars) 24 | self.npars = ti.field(int, ()) 25 | 26 | @ti.materialize_callback 27 | def init_pars(): 28 | self.sizes.fill(0.1) 29 | if self.coloring: 30 | self.colors.fill(1) 31 | 32 | self.tree = tina.BVHTree(self, self.maxpars * 4) 33 | 34 | self.eminds = ti.field(int, maxpars) 35 | self.neminds = ti.field(int, ()) 36 | 37 | @ti.kernel 38 | def _export_geometry(self, verts: ti.ext_arr(), sizes: ti.ext_arr()): 39 | for i in range(self.npars[None]): 40 | sizes[i] = self.sizes[i] 41 | for k in ti.static(range(3)): 42 | verts[i, k] = self.verts[i][k] 43 | 44 | @ti.kernel 45 | def update_emission(self, mtltab: ti.template()): 46 | self.neminds[None] = 0 47 | for i in range(self.npars[None]): 48 | mtlid = self.get_material_id(i) 49 | material = mtltab.get(mtlid) 50 | emission = material.estimate_emission() 51 | if Vany(emission > 0): 52 | j = ti.atomic_add(self.neminds[None], 1) 53 | self.eminds[j] = i 54 | 55 | def update(self): 56 | pos = np.empty((self.npars[None], 3), dtype=np.float32) 57 | rad = np.empty((self.npars[None]), dtype=np.float32) 58 | self._export_geometry(pos, rad) 59 | rad = np.stack([rad, rad, rad], axis=1) 60 | self.tree.build(pos - rad, pos + rad) 61 | 62 | def clear_objects(self): 63 | self.npars[None] = 0 64 | 65 | @ti.kernel 66 | def add_pars(self, world: ti.ext_arr(), verts: ti.ext_arr(), 67 | sizes: ti.ext_arr(), colors: ti.ext_arr(), mtlid: int): 68 | trans = ti.Matrix.zero(float, 4, 4) 69 | for i, j in ti.static(ti.ndrange(4, 4)): 70 | trans[i, j] = world[i, j] 71 | npars = verts.shape[0] 72 | base = self.npars[None] 73 | self.npars[None] += npars 74 | for i in range(npars): 75 | j = base + i 76 | self.mtlids[j] = mtlid 77 | for l in ti.static(range(3)): 78 | self.verts[j][l] = verts[i, l] 79 | self.verts[j] = mapply_pos(trans, self.verts[j]) 80 | self.sizes[j] = sizes[j] 81 | if ti.static(self.coloring): 82 | for l in ti.static(range(3)): 83 | self.colors[j][l] = colors[i, l] 84 | #self.colors[j] = trans @ self.colors[j] 85 | 86 | @ti.kernel 87 | def add_object(self, pars: ti.template(), mtlid: ti.template()): 88 | pars.pre_compute() 89 | npars = pars.get_npars() 90 | base = self.npars[None] 91 | self.npars[None] += npars 92 | for i in range(self.npars[None]): 93 | j = base + i 94 | if ti.static(self.multimtl): 95 | self.mtlids[j] = mtlid 96 | vert = pars.get_particle_position(i) 97 | self.verts[j] = vert 98 | size = pars.get_particle_radius(i) 99 | self.sizes[j] = size 100 | if ti.static(self.coloring): 101 | color = pars.get_particle_color(i) 102 | self.colors[j] = color 103 | 104 | @ti.func 105 | def element_hit(self, ind, ro, rd): 106 | pos = self.verts[ind] 107 | rad = self.sizes[ind] 108 | hit, depth = ray_sphere_hit(pos, rad, ro, rd) 109 | return hit, depth, V(0., 0.) 110 | 111 | @ti.func 112 | def hit(self, ro, rd): 113 | return self.tree.hit(ro, rd) 114 | 115 | @ti.func 116 | def get_material_id(self, ind): 117 | if ti.static(not self.multimtl): 118 | return 0 119 | return self.mtlids[ind] 120 | 121 | @ti.func 122 | def sample_light(self): 123 | pos, ind, wei = V3(0.), -1, 0. 124 | if self.neminds[None] != 0: 125 | ind = self.eminds[ti.random(int) % self.neminds[None]] 126 | nrm = spherical(ti.random(), ti.random()) 127 | pos = nrm * self.sizes[ind] + self.verts[ind] 128 | wei = 4 * ti.pi * self.sizes[ind]**2 129 | pos += nrm * eps * 8 130 | return pos, ind, V(0., 0.), wei * self.neminds[None] 131 | -------------------------------------------------------------------------------- /tina/path/tree.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | from .geometry import * 3 | 4 | 5 | @ti.data_oriented 6 | class BVHTree: 7 | def __init__(self, geom, N_tree=MAX, dim=3): 8 | self.geom = geom 9 | self.N_tree = N_tree 10 | self.dim = dim 11 | 12 | self.dir = ti.field(int) 13 | self.min = ti.Vector.field(self.dim, float) 14 | self.max = ti.Vector.field(self.dim, float) 15 | self.ind = ti.field(int) 16 | self.tree = ti.root.dense(ti.i, self.N_tree) 17 | self.tree.place(self.dir, self.min, self.max, self.ind) 18 | 19 | def build(self, pmin, pmax): 20 | assert len(pmin) == len(pmax) 21 | assert np.all(pmax >= pmin) 22 | data = lambda: None 23 | data.dir = self.dir.to_numpy() 24 | data.dir[:] = -1 25 | data.min = self.min.to_numpy() 26 | data.max = self.max.to_numpy() 27 | data.ind = self.ind.to_numpy() 28 | print('[Tina] building tree...') 29 | self._build(data, pmin, pmax, np.arange(len(pmin)), 1) 30 | self._build_from_data(data.dir, data.min, data.max, data.ind) 31 | print('[Tina] building tree done') 32 | 33 | @ti.kernel 34 | def _build_from_data(self, 35 | data_dir: ti.ext_arr(), 36 | data_min: ti.ext_arr(), 37 | data_max: ti.ext_arr(), 38 | data_ind: ti.ext_arr()): 39 | for i in range(self.dir.shape[0]): 40 | if data_dir[i] == -1: 41 | continue 42 | self.dir[i] = data_dir[i] 43 | for k in ti.static(range(self.dim)): 44 | self.min[i][k] = data_min[i, k] 45 | self.max[i][k] = data_max[i, k] 46 | self.ind[i] = data_ind[i] 47 | 48 | def _build(self, data, pmin, pmax, pind, curr): 49 | assert curr < self.N_tree, curr 50 | if not len(pind): 51 | return 52 | 53 | elif len(pind) <= 1: 54 | data.dir[curr] = 0 55 | data.ind[curr] = pind[0] 56 | data.min[curr] = pmin[0] 57 | data.max[curr] = pmax[0] 58 | return 59 | 60 | bmax = np.max(pmax, axis=0) 61 | bmin = np.min(pmin, axis=0) 62 | dir = np.argmax(bmax - bmin) 63 | sort = np.argsort(pmax[:, dir] + pmin[:, dir]) 64 | mid = len(sort) // 2 65 | lsort = sort[:mid] 66 | rsort = sort[mid:] 67 | 68 | lmin, rmin = pmin[lsort], pmin[rsort] 69 | lmax, rmax = pmax[lsort], pmax[rsort] 70 | lind, rind = pind[lsort], pind[rsort] 71 | data.dir[curr] = 1 + dir 72 | data.ind[curr] = 0 73 | data.min[curr] = bmin 74 | data.max[curr] = bmax 75 | self._build(data, lmin, lmax, lind, curr * 2) 76 | self._build(data, rmin, rmax, rind, curr * 2 + 1) 77 | 78 | @ti.kernel 79 | def _active_indices(self, out: ti.ext_arr()): 80 | for curr in self.dir: 81 | if self.dir[curr] != 0: 82 | out[curr] = 1 83 | 84 | def active_indices(self): 85 | ind = np.zeros(self.N_tree, dtype=np.int32) 86 | self._active_indices(ind) 87 | return np.bool_(ind) 88 | 89 | def visualize(self, gui): 90 | assert self.dim == 2 91 | bmin = self.min.to_numpy() 92 | bmax = self.max.to_numpy() 93 | ind = self.active_indices() 94 | bmin, bmax = bmin[ind], bmax[ind] 95 | delta = bmax - bmin 96 | ind = np.any(delta >= 0.02, axis=1) 97 | bmin, bmax = bmin[ind], bmax[ind] 98 | bmin = bmin * 0.5 + 0.5 99 | bmax = bmax * 0.5 + 0.5 100 | gui.rects(bmin, bmax, color=0xff0000) 101 | 102 | @ti.func 103 | def hit(self, ro, rd): 104 | stack = tina.Stack.instance() 105 | near = inf 106 | ntimes = 0 107 | stack.clear() 108 | stack.push(1) 109 | hitind = -1 110 | hituv = V(0., 0.) 111 | while ntimes < self.N_tree and stack.size() != 0: 112 | curr = stack.pop() 113 | 114 | if self.dir[curr] == 0: 115 | ind = self.ind[curr] 116 | hit, depth, uv = self.geom.element_hit(ind, ro, rd) 117 | if hit != 0 and depth < near: 118 | near = depth 119 | hitind = ind 120 | hituv = uv 121 | continue 122 | 123 | bmin, bmax = self.min[curr], self.max[curr] 124 | bnear, bfar = ray_aabb_hit(bmin, bmax, ro, rd) 125 | if bnear > bfar: 126 | continue 127 | 128 | ntimes += 1 129 | stack.push(curr * 2) 130 | stack.push(curr * 2 + 1) 131 | return near, hitind, hituv 132 | -------------------------------------------------------------------------------- /tina/path/volume.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | from .geometry import * 3 | 4 | 5 | @ti.data_oriented 6 | class VolumeTracer: 7 | is_dedicated_tracer = True 8 | 9 | def __init__(self, **extra_options): 10 | pass 11 | 12 | def clear_objects(self): 13 | pass 14 | 15 | def add_object(self, voxl, mtlid): 16 | self.voxl = voxl 17 | 18 | @ti.func 19 | def sample_density(self, pos): 20 | return self.voxl.sample_volume(pos) 21 | #return 2.6 if (pos // 0.5).sum() % 2 == 0 else 0.0 22 | 23 | @ti.func 24 | def hit(self, ro, rd, maxfar=inf): 25 | # travel volume 26 | bmin, bmax = self.voxl.get_bounding_box() 27 | near, far = tina.ray_aabb_hit(bmin, bmax, ro, rd) 28 | 29 | hitind = -1 30 | depth = inf 31 | if near <= far: 32 | near = max(near, 0) 33 | far = min(far, maxfar) 34 | 35 | t = near 36 | int_rho = 0.0 37 | ran = -ti.log(ti.random()) 38 | step = 0.01 39 | t += step * (.5 + .5 * ti.random()) 40 | while t < far: 41 | dt = min(step, far - t) 42 | pos = ro + t * rd 43 | rho = self.sample_density(pos) 44 | int_rho += rho * dt 45 | if ran < int_rho: # ti.random() > ti.exp(-Integrate rho*dt) 46 | depth = t 47 | hitind = 0 48 | break 49 | t += dt 50 | 51 | return depth, hitind, V(0., 0.) 52 | 53 | @ti.func 54 | def calc_geometry(self, near, ind, uv, ro, rd): 55 | nrm = V(0., 0., 0.) 56 | return nrm, V(0., 0.) 57 | 58 | @ti.func 59 | def sample_light_pos(self, org): 60 | return V(0., 0., 0.), 0, 0.0 61 | 62 | @ti.kernel 63 | def update_emission(self, mtltab: ti.template()): 64 | pass 65 | 66 | def update(self): 67 | pass 68 | 69 | @ti.func 70 | def get_material_id(self, ind): 71 | return 1 72 | -------------------------------------------------------------------------------- /tina/postp/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .tonemap import * 3 | from .denoise import * 4 | from .blooming import * 5 | from .fxaa import * 6 | from .ssao import * 7 | from .ssr import * 8 | -------------------------------------------------------------------------------- /tina/postp/blooming.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | 3 | 4 | @ti.data_oriented 5 | class Blooming: 6 | def __init__(self, res): 7 | self.res = tovector(res) 8 | self.img = ti.Vector.field(3, float, self.res // 2) 9 | self.tmp = ti.Vector.field(3, float, self.res // 2) 10 | self.gwei = ti.field(float, 128) 11 | self.thresh = ti.field(float, ()) 12 | self.factor = ti.field(float, ()) 13 | self.scale = ti.field(float, ()) 14 | self.sigma = ti.field(float, ()) 15 | self.radius = ti.field(int, ()) 16 | 17 | @ti.materialize_callback 18 | def init_params(): 19 | self.thresh[None] = 1 20 | self.factor[None] = 1 21 | self.radius[None] = min(*self.res) // 16 22 | self.sigma[None] = 1 23 | self.scale[None] = 0.25 24 | self.init_gwei() 25 | 26 | @ti.kernel 27 | def init_gwei(self): 28 | sum = -1.0 29 | radius = self.radius[None] 30 | sigma = self.sigma[None] 31 | for i in range(radius + 1): 32 | x = sigma * i / radius 33 | y = ti.exp(-x**2) 34 | self.gwei[i] = y 35 | sum += y * 2 36 | for i in range(radius + 1): 37 | self.gwei[i] /= sum 38 | 39 | @ti.func 40 | def filter(self, x): 41 | t = max(0, x - self.thresh[None]) 42 | t = 1 - 1 / (1 + self.scale[None] * t) 43 | return self.factor[None] * t 44 | 45 | @ti.kernel 46 | def apply(self, image: ti.template()): 47 | for I in ti.grouped(self.img): 48 | res = V(0., 0., 0.) 49 | for J in ti.grouped(ti.ndrange(2, 2)): 50 | res += self.filter(image[I * 2 + J]) 51 | self.img[I] = res / 4 52 | for I in ti.grouped(self.img): 53 | res = self.img[I] * self.gwei[0] 54 | for i in range(1, self.radius[None] + 1): 55 | val = self.img[max(0, I.x - i), I.y] 56 | val += self.img[min(self.img.shape[0] - 1, I.x + i), I.y] 57 | res += val * self.gwei[i] 58 | self.tmp[I] = res 59 | for I in ti.grouped(self.img): 60 | res = self.tmp[I] * self.gwei[0] 61 | for i in range(1, self.radius[None] + 1): 62 | dir = V(0, 1) 63 | val = self.tmp[I.x, max(0, I.y - i)] 64 | val += self.tmp[I.x, min(self.img.shape[1] - 1, I.y + i)] 65 | res += val * self.gwei[i] 66 | self.img[I] = res 67 | for I in ti.grouped(image): 68 | image[I] += bilerp(self.img, I / 2) 69 | -------------------------------------------------------------------------------- /tina/postp/denoise.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class Denoise: 6 | def __init__(self, res): 7 | self.res = tovector(res) 8 | 9 | self.src = ti.Vector.field(3, float, self.res) 10 | self.dst = ti.Vector.field(3, float, self.res) 11 | 12 | def knn(self, radius=3, noiseness=0.32, wei_thres=0.02, 13 | lerp_thres=0.79, lerp_factor=0.2): 14 | self._knn(radius, noiseness, wei_thres, 15 | lerp_thres, lerp_factor) 16 | 17 | def nlm(self, radius=3, noiseness=1.45, wei_thres=0.1, 18 | lerp_thres=0.1, lerp_factor=0.2): 19 | self._nlm(radius, noiseness, wei_thres, 20 | lerp_thres, lerp_factor) 21 | 22 | @ti.kernel 23 | def _knn(self, radius: int, noiseness: float, wei_thres: float, 24 | lerp_thres: float, lerp_factor: float): 25 | for x, y in self.src: 26 | cnt = 0.0 27 | wei = 0.0 28 | clr = ti.zero(self.src[x, y]) 29 | 30 | noise = 1 / max(1e-5, noiseness**2) 31 | inv_area = 1 / (2 * radius + 1)**2 32 | 33 | clr00 = self.src[x, y] 34 | for i, j in ti.ndrange((-radius, radius + 1), (-radius, radius + 1)): 35 | clrij = self.src[x + i, y + j] 36 | disij = Vlen2(clr00 - clrij) 37 | 38 | wij = ti.exp(-(disij * noise + (i**2 + j**2) * inv_area)) 39 | cnt += inv_area if wij > wei_thres else 0 40 | clr += clrij * wij 41 | wei += wij 42 | 43 | clr /= wei 44 | lerp_q = lerp_factor if cnt > lerp_thres else 1 - lerp_factor 45 | self.dst[x, y] = clr * (1 - lerp_q) + clr00 * lerp_q 46 | 47 | @ti.kernel 48 | def _nlm(self, radius: int, noiseness: float, wei_thres: float, 49 | lerp_thres: float, lerp_factor: float): 50 | for x, y in self.src: 51 | cnt = 0.0 52 | wei = 0.0 53 | clr = ti.zero(self.src[x, y]) 54 | 55 | noise = 1 / max(1e-5, noiseness**2) 56 | inv_area = 1 / (2 * radius + 1)**2 57 | 58 | for i, j in ti.ndrange((-radius, radius + 1), (-radius, radius + 1)): 59 | wij = 0.0 60 | for m, n in ti.ndrange((-radius, radius + 1), (-radius, radius + 1)): 61 | clr00 = self.src[x + m, y + n] 62 | clrij = self.src[x + i + m, y + j + n] 63 | wij += Vlen2(clr00 - clrij) 64 | 65 | wij = ti.exp(-(wij * noise + (i**2 + j**2) * inv_area)) 66 | cnt += inv_area if wij > wei_thres else 0 67 | clr += self.src[x + i, y + j] * wij 68 | wei += wij 69 | 70 | clr /= wei 71 | lerp_q = lerp_factor if cnt > lerp_thres else 1 - lerp_factor 72 | self.dst[x, y] = clr * (1 - lerp_q) + self.src[x, y] * lerp_q 73 | 74 | 75 | if __name__ == '__main__': 76 | ti.init(ti.gpu) 77 | 78 | img = ti.imread('noise.png') 79 | #img = ti.imread('cornell.png') 80 | #img = ti.imread('/opt/cuda/samples/3_Imaging/imageDenoising/data/portrait_noise.bmp') 81 | img = np.float32(img / 255) 82 | w, h, _ = img.shape 83 | 84 | denoise = Denoise((w, h)) 85 | denoise.src.from_numpy(img) 86 | 87 | gui = ti.GUI('denoise', (w, h)) 88 | 89 | knn = 0 90 | if knn: 91 | radius = gui.slider('radius', 0, 6, 1) 92 | noiseness = gui.slider('noiseness', 0, 3, 0.01) 93 | wei_thres = gui.slider('wei_thres', 0, 1, 0.01) 94 | lerp_thres = gui.slider('lerp_thres', 0, 3, 0.01) 95 | lerp_factor = gui.slider('lerp_factor', 0, 1, 0.01) 96 | radius.value = 3 97 | noiseness.value = 0.32 98 | wei_thres.value = 0.02 99 | lerp_thres.value = 0.79 100 | lerp_factor.value = 0.2 101 | else: 102 | radius = gui.slider('radius', 0, 6, 1) 103 | noiseness = gui.slider('noiseness', 0, 5, 0.01) 104 | wei_thres = gui.slider('wei_thres', 0, 1, 0.01) 105 | lerp_thres = gui.slider('lerp_thres', 0, 1, 0.01) 106 | lerp_factor = gui.slider('lerp_factor', 0, 1, 0.01) 107 | radius.value = 3 108 | noiseness.value = 1.45 109 | wei_thres.value = 0.1 110 | lerp_thres.value = 0.1 111 | lerp_factor.value = 0.2 112 | 113 | denoise.nlm() 114 | while gui.running: 115 | gui.running = not gui.get_event(gui.ESCAPE) 116 | if knn: 117 | denoise.knn(int(radius.value), noiseness.value, wei_thres.value, lerp_thres.value, lerp_factor.value) 118 | else: 119 | denoise.nlm(int(radius.value), noiseness.value, wei_thres.value, lerp_thres.value, lerp_factor.value) 120 | gui.set_image(denoise.dst) 121 | gui.show() 122 | -------------------------------------------------------------------------------- /tina/postp/fxaa.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | 3 | 4 | @ti.func 5 | def luminance(c): 6 | return V(0.2989, 0.587, 0.114).dot(c) 7 | 8 | 9 | # https://catlikecoding.com/unity/tutorials/advanced-rendering/fxaa/ 10 | @ti.data_oriented 11 | class FXAA: 12 | def __init__(self, res): 13 | self.res = res 14 | self.lumi = ti.field(float, self.res) 15 | self.img = ti.Vector.field(3, float, self.res) 16 | self.abs_thresh = ti.field(float, ()) 17 | self.rel_thresh = ti.field(float, ()) 18 | self.factor = ti.field(float, ()) 19 | 20 | @ti.materialize_callback 21 | def init_params(): 22 | self.abs_thresh[None] = 0.0625 23 | self.rel_thresh[None] = 0.063 24 | self.factor[None] = 1 25 | 26 | @ti.kernel 27 | def apply(self, image: ti.template()): 28 | for I in ti.grouped(image): 29 | self.lumi[I] = clamp(luminance(image[I]), 0, 1) 30 | self.img[I] = image[I] 31 | for I in ti.grouped(image): 32 | m = self.lumi[I] 33 | n = self.lumi[I + V(0, 1)] 34 | e = self.lumi[I + V(1, 0)] 35 | s = self.lumi[I + V(0, -1)] 36 | w = self.lumi[I + V(-1, 0)] 37 | ne = self.lumi[I + V(1, 1)] 38 | nw = self.lumi[I + V(-1, 1)] 39 | se = self.lumi[I + V(1, -1)] 40 | sw = self.lumi[I + V(-1, -1)] 41 | hi = max(m, n, e, s, w) 42 | lo = min(m, n, e, s, w) 43 | c = hi - lo 44 | if c < self.abs_thresh[None] or c < self.rel_thresh[None] * hi: 45 | continue 46 | filt = 2 * (n + e + s + w) 47 | filt += ne + nw + se + sw 48 | filt = abs(filt / 12 - m) 49 | filt = clamp(filt / c, 0, 1) 50 | blend = smoothstep(filt, 0, 1)**2 * self.factor[None] 51 | hori = abs(n + s - 2 * m) * 2 52 | hori += abs(ne + se - 2 * e) 53 | hori += abs(nw + sw - 2 * w) 54 | vert = abs(e + w - 2 * m) * 2 55 | vert += abs(ne + nw - 2 * n) 56 | vert += abs(se + sw - 2 * s) 57 | is_hori = hori >= vert 58 | 59 | plumi = n if is_hori else e 60 | nlumi = s if is_hori else w 61 | pgrad = abs(plumi - m) 62 | ngrad = abs(nlumi - m) 63 | 64 | if pgrad < ngrad: 65 | blend = -blend 66 | dir = V(0, 1) if is_hori else V(1, 0) 67 | 68 | image[I] = bilerp(self.img, I + blend * dir) 69 | -------------------------------------------------------------------------------- /tina/postp/ssao.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | from ..core.shader import calc_viewdir 3 | 4 | 5 | @ti.data_oriented 6 | class SSAO: 7 | def __init__(self, res, norm, nsamples=64, thresh=0.0, 8 | radius=0.2, factor=1.0, noise_size=4, taa=False): 9 | self.res = tovector(res) 10 | self.img = ti.field(float, self.res) 11 | self.radius = ti.field(float, ()) 12 | self.thresh = ti.field(float, ()) 13 | self.factor = ti.field(float, ()) 14 | self.nsamples = ti.field(int, ()) 15 | self.taa = taa 16 | self.norm = norm 17 | 18 | @ti.materialize_callback 19 | def init_params(): 20 | self.radius[None] = radius 21 | self.thresh[None] = thresh 22 | self.factor[None] = factor 23 | self.nsamples[None] = nsamples if not self.taa else nsamples // 4 24 | 25 | if not self.taa: 26 | self.samples = ti.Vector.field(3, float, nsamples) 27 | self.rotations = ti.Vector.field(2, float, (noise_size, noise_size)) 28 | ti.materialize_callback(self.seed_samples) 29 | 30 | @ti.kernel 31 | def seed_samples(self): 32 | for i in self.samples: 33 | self.samples[i] = self.make_sample() 34 | for I in ti.grouped(self.rotations): 35 | t = ti.tau * ti.random() 36 | self.rotations[I] = V(ti.cos(t), ti.sin(t)) 37 | 38 | @ti.kernel 39 | def apply(self, out: ti.template()): 40 | for i, j in self.img: 41 | if ti.static(self.taa): 42 | out[i, j] *= 1 - self.img[i, j] 43 | else: 44 | r = 0.0 45 | rad = self.rotations.shape[0] 46 | offs = rad // 2 47 | for k, l in ti.ndrange(rad, rad): 48 | r += self.img[i + k - offs, j + l - offs] 49 | out[i, j] *= 1 - r / rad**2 50 | 51 | @ti.func 52 | def make_sample(self): 53 | u, v = ti.random(), ti.random() 54 | r = lerp(ti.random()**1.5, 0.01, 1.0) 55 | u = lerp(u, 0.01, 1.0) 56 | return spherical(u, v) * r 57 | 58 | @ti.kernel 59 | def render(self, engine: ti.template()): 60 | for P in ti.grouped(engine.depth): 61 | self.render_at(engine, P) 62 | 63 | @ti.func 64 | def render_at(self, engine, P): 65 | normal = self.norm[P] 66 | p = P + engine.bias[None] 67 | vpos = V23(engine.from_viewport(p), engine.depth[P] / engine.maxdepth) 68 | pos = mapply_pos(engine.V2W[None], vpos) 69 | viewdir = calc_viewdir(engine, p) 70 | 71 | occ = 0.0 72 | radius = self.radius[None] 73 | vradius = engine.to_viewspace(pos - radius * viewdir).z - vpos.z 74 | for i in range(self.nsamples[None]): 75 | samp = V(0., 0., 0.) 76 | if ti.static(self.taa): 77 | samp = self.make_sample() 78 | else: 79 | samp = self.samples[i] 80 | rot = self.rotations[P % self.rotations.shape[0]] 81 | rotmat = ti.Matrix([[rot.x, rot.y], [-rot.x, rot.y]]) 82 | samp.x, samp.y = rotmat @ samp.xy 83 | sample = tangentspace(normal) @ samp 84 | sample = pos + sample * radius 85 | sample = engine.to_viewspace(sample) 86 | D = engine.to_viewport(sample) 87 | if all(0 <= D < engine.res): 88 | depth = engine.depth[int(D)] / engine.maxdepth 89 | if depth < sample.z: 90 | rc = vradius / (vpos.z - depth) 91 | rc = smoothstep(abs(rc), 0, 1) 92 | occ += rc 93 | 94 | ao = occ / self.nsamples[None] 95 | ao = self.factor[None] * (ao - self.thresh[None]) 96 | self.img[P] = clamp(ao, 0, 1) 97 | 98 | 99 | -------------------------------------------------------------------------------- /tina/postp/ssr.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | from ..core.shader import calc_viewdir 3 | 4 | 5 | @ti.data_oriented 6 | class SSR: 7 | def __init__(self, res, norm, coor, mtlid, mtltab, taa=False): 8 | self.res = tovector(res) 9 | self.img = ti.Vector.field(4, float, self.res) 10 | self.nsamples = ti.field(int, ()) 11 | self.nsteps = ti.field(int, ()) 12 | self.stepsize = ti.field(float, ()) 13 | self.tolerance = ti.field(float, ()) 14 | self.blurring = ti.field(int, ()) 15 | self.norm = norm 16 | self.coor = coor 17 | self.mtlid = mtlid 18 | self.mtltab = mtltab 19 | self.taa = taa 20 | 21 | @ti.materialize_callback 22 | def init_params(): 23 | self.nsamples[None] = 32 if not taa else 12 24 | self.nsteps[None] = 32 if not taa else 64 25 | self.stepsize[None] = 2 26 | self.tolerance[None] = 15 27 | self.blurring[None] = 4 28 | 29 | @ti.kernel 30 | def apply(self, image: ti.template()): 31 | for i, j in self.img: 32 | res = V(0., 0., 0., 0.) 33 | if ti.static(self.taa): 34 | res = self.img[i, j] 35 | else: 36 | rad = self.blurring[None] 37 | offs = rad // 2 38 | for k, l in ti.ndrange(rad, rad): 39 | res += self.img[i + k - offs, j + l - offs] 40 | res /= rad**2 41 | image[i, j] *= 1 - res.w 42 | image[i, j] += res.xyz 43 | 44 | @ti.kernel 45 | def render(self, engine: ti.template(), image: ti.template()): 46 | for P in ti.grouped(image): 47 | if self.norm[P].norm_sqr() < eps: 48 | self.img[P] = 0 49 | else: 50 | self.render_at(engine, image, P) 51 | 52 | @ti.func 53 | def render_at(self, engine, image: ti.template(), P): 54 | normal = self.norm[P] 55 | texcoord = self.coor[P] 56 | mtlid = self.mtlid[P] 57 | 58 | p = P + engine.bias[None] 59 | vpos = V23(engine.from_viewport(p), engine.depth[P] / engine.maxdepth) 60 | pos = mapply_pos(engine.V2W[None], vpos) 61 | viewdir = calc_viewdir(engine, p) 62 | material = self.mtltab.get(mtlid) 63 | res = V(0., 0., 0., 0.) 64 | 65 | tina.Input.spec_g_pars({ 66 | 'pos': pos, 67 | 'color': V(1., 1., 1.), 68 | 'normal': normal, 69 | 'texcoord': texcoord, 70 | }) 71 | 72 | rng = tina.TaichiRNG() 73 | if ti.static(not self.taa): 74 | pid = P % self.blurring[None] 75 | rng = ti.static(tina.WangHashRNG(pid)) 76 | 77 | nsamples = self.nsamples[None] 78 | nsteps = self.nsteps[None] 79 | for i in range(nsamples): 80 | odir, wei, rough = material.sample(viewdir, normal, 1, rng) 81 | 82 | step = self.stepsize[None] / ( 83 | ti.sqrt(1 - odir.dot(viewdir)**2) * nsteps) 84 | vtol = self.tolerance[None] * ( 85 | mapply_pos(engine.W2V[None], pos - viewdir / nsteps 86 | ).z - mapply_pos(engine.W2V[None], pos).z) 87 | 88 | ro = pos + odir * rng.random() * step 89 | for j in range(nsteps): 90 | ro += odir * step 91 | vro = mapply_pos(engine.W2V[None], ro) 92 | if not all(-1 <= vro <= 1): 93 | break 94 | D = engine.to_viewport(vro) 95 | depth = engine.depth[int(D)] / engine.maxdepth 96 | if vro.z - vtol < depth < vro.z: 97 | clr = bilerp(image, D) * wei 98 | res += V34(clr, 1.0) 99 | break 100 | 101 | tina.Input.clear_g_pars() 102 | 103 | self.img[P] = res / nsamples 104 | -------------------------------------------------------------------------------- /tina/postp/tonemap.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | 3 | 4 | @ti.data_oriented 5 | class ToneMapping: 6 | def __init__(self, res): 7 | self.res = res 8 | 9 | @ti.kernel 10 | def apply(self, image: ti.template()): 11 | for I in ti.grouped(image): 12 | image[I] = aces_tonemap(image[I]) 13 | -------------------------------------------------------------------------------- /tina/probe.py: -------------------------------------------------------------------------------- 1 | from .advans import * 2 | 3 | 4 | @ti.data_oriented 5 | class ProbeShader: 6 | def __init__(self, res): 7 | self.res = res 8 | self.elmid = ti.field(int, res) 9 | self.texcoord = ti.Vector.field(2, float, res) 10 | 11 | @ti.kernel 12 | def clear_buffer(self): 13 | for I in ti.grouped(self.elmid): 14 | self.elmid[I] = -1 15 | self.texcoord[I] *= 0 16 | 17 | @ti.func 18 | def shade_color(self, engine, P, p, f, pos, normal, texcoord, color): 19 | self.elmid[P] = f 20 | self.texcoord[P] = texcoord 21 | 22 | @ti.kernel 23 | def touch(self, callback: ti.template(), mx: float, my: float, rad: float): 24 | p = V(mx, my) * self.res 25 | bot, top = ifloor(p - rad), iceil(p + rad) 26 | for I in ti.grouped(ti.ndrange((bot.x, top.x + 1), (bot.y, top.y + 1))): 27 | r = (I - p).norm() 28 | if r > rad: 29 | continue 30 | if self.elmid[I] == -1: 31 | continue 32 | callback(self, I, r) 33 | -------------------------------------------------------------------------------- /tina/random.py: -------------------------------------------------------------------------------- 1 | from .advans import * 2 | 3 | 4 | @ti.data_oriented 5 | class TaichiRNG: 6 | def __init__(self): 7 | pass 8 | 9 | def random(self): 10 | return ti.random() 11 | 12 | def random_int(self): 13 | return ti.random(int) 14 | 15 | 16 | @ti.data_oriented 17 | class WangHashRNG: 18 | def __init__(self, seed): 19 | seed = self.noise_int(seed) 20 | self.seed = ti.expr_init(seed) 21 | 22 | def __del__(self): 23 | if hasattr(self, 'seed'): 24 | del self.seed 25 | 26 | @staticmethod 27 | @ti.func 28 | def _noise(x): 29 | value = ti.cast(x, ti.u32) 30 | value = (value ^ 61) ^ (value >> 16) 31 | value *= 9 32 | value ^= value << 4 33 | value *= 0x27d4eb2d 34 | value ^= value >> 15 35 | return value 36 | 37 | @classmethod 38 | @ti.func 39 | def noise(cls, x): 40 | u = cls.noise_int(x) >> 1 41 | return u * (2 / 4294967296) 42 | 43 | @classmethod 44 | @ti.func 45 | def noise_int(cls, x): 46 | if ti.static(not isinstance(x, ti.Matrix)): 47 | return cls._noise(x) 48 | index = ti.cast(x, ti.u32) 49 | value = cls._noise(index.entries[0]) 50 | for i in ti.static(index.entries[1:]): 51 | value = cls._noise(i ^ value) 52 | return value 53 | 54 | @ti.func 55 | def random(self): 56 | ret = self.noise(self.seed) 57 | self.seed = self.seed + 1 58 | return ret 59 | 60 | @ti.func 61 | def random_int(self): 62 | ret = self.noise_int(self.seed) 63 | self.seed = self.seed + 1 64 | return ret 65 | 66 | 67 | @ti.data_oriented 68 | class UnixFastRNG(WangHashRNG): 69 | @staticmethod 70 | @ti.func 71 | def _noise(x): 72 | value = ti.cast(x, ti.u32) 73 | value = (value * 7**5) % (2**31 - 1) 74 | return value 75 | 76 | 77 | @ti.data_oriented 78 | class HammersleyRNG: 79 | def __init__(self, seed, time): 80 | seed = WangHashRNG.noise_int(seed) 81 | self.seed = ti.expr_init(seed) 82 | self.time = ti.expr_init(time) 83 | 84 | def __del__(self): 85 | if hasattr(self, 'seed'): 86 | del self.seed 87 | 88 | @staticmethod 89 | @ti.func 90 | def noise(x): 91 | i = int(x) & 0x1fffffff 92 | j = 0 93 | k = 1 94 | while i != 0: 95 | k <<= 1 96 | j <<= 1 97 | j |= i & 1 98 | i >>= 1 99 | return j / k 100 | 101 | @ti.func 102 | def random(self): 103 | ret = self.noise(self.seed + self.time) 104 | self.seed = WangHashRNG.noise_int(self.seed) 105 | return ret 106 | 107 | @ti.func 108 | def random_int(self): 109 | return int(self.random() * 2**20) 110 | 111 | 112 | @ti.data_oriented 113 | class SobolSequence: 114 | def __init__(self, m=32, n=512, skip=8): 115 | self.m = m 116 | self.n = n 117 | self.skip = self.skip0 = skip 118 | self.data = ti.field(float, (m, n)) 119 | 120 | ti.materialize_callback(self.reseed) 121 | 122 | def clear(self): 123 | self.skip = self.skip0 124 | self.reseed() 125 | 126 | def reseed(self): 127 | import sobol 128 | arr = sobol.i4_sobol_generate(self.m, self.n, self.skip) 129 | self.data.from_numpy(arr) 130 | self.skip += 8 131 | 132 | 133 | @ti.data_oriented 134 | class SobolRNG: 135 | def __init__(self, seq, index): 136 | self.seq = seq 137 | index = WangHashRNG.noise_int(index) 138 | self.index = ti.expr_init(index) 139 | self.time = ti.expr_init(0) 140 | 141 | @ti.func 142 | def random(self): 143 | ret = self.seq.data[self.index % self.seq.m, self.time % self.seq.n] 144 | self.time += 1 145 | return ret 146 | 147 | @ti.func 148 | def random_int(self): 149 | return int(self.random() * 2**20) 150 | 151 | 152 | ''' 153 | def binrev(i): 154 | j = 0 155 | k = 1 156 | while i != 0: 157 | k <<= 1 158 | j <<= 1 159 | j |= i & 1 160 | i >>= 1 161 | return j / k 162 | 163 | def graycode(x): 164 | return x ^ (x >> 1) 165 | ''' 166 | -------------------------------------------------------------------------------- /tina/scene/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .raster import * 3 | from .tracer import * 4 | -------------------------------------------------------------------------------- /tina/scene/tracer.py: -------------------------------------------------------------------------------- 1 | from ..advans import * 2 | from .raster import Scene 3 | 4 | 5 | class MixedGeometryTracer: 6 | def __init__(self): 7 | self.tracers = [] 8 | 9 | @ti.func 10 | def sample_light(self): 11 | if ti.static(len(self.tracers) == 1): 12 | pos, ind, uv, wei = self.tracers[0].sample_light() 13 | return pos, ind, uv, 0, wei 14 | 15 | gid = ti.random(int) % len(self.tracers) 16 | pos, ind, uv, wei = V(0., 0., 0.), -1, V(0., 0.), 0. 17 | for i, tracer in ti.static(enumerate(self.tracers)): 18 | if i == gid: 19 | pos, ind, uv, wei = tracer.sample_light() 20 | return pos, ind, uv, gid, wei 21 | 22 | @ti.func 23 | def hit(self, ro, rd): 24 | if ti.static(len(self.tracers) == 1): 25 | near, ind, uv = self.tracers[0].hit(ro, rd) 26 | gid = -1 if ind == -1 else 0 27 | return near, ind, gid, uv 28 | 29 | ret_near, ret_ind, ret_gid, ret_uv = inf, -1, -1, V(0., 0.) 30 | for gid, tracer in ti.static(enumerate(self.tracers)): 31 | near, ind, uv = tracer.hit(ro, rd) 32 | if near < ret_near: 33 | ret_near, ret_ind, ret_gid, ret_uv = near, ind, gid, uv 34 | return ret_near, ret_ind, ret_gid, ret_uv 35 | 36 | @ti.func 37 | def calc_geometry(self, gid, ind, uv, pos): 38 | if ti.static(len(self.tracers) == 1): 39 | return self.tracers[0].calc_geometry(ind, uv, pos) 40 | 41 | nrm, tex, mtlid = V(0., 0., 0.), V(0., 0.), -1 42 | for i, tracer in ti.static(enumerate(self.tracers)): 43 | if i == gid: 44 | nrm, tex, mtlid = tracer.calc_geometry(ind, uv, pos) 45 | return nrm, tex, mtlid 46 | 47 | 48 | # noinspection PyMissingConstructor 49 | class PTScene(Scene): 50 | def __init__(self, res=512, **options): 51 | self.mtltab = tina.MaterialTable() 52 | self.geom = MixedGeometryTracer() 53 | self.engine = tina.PathEngine(self.geom, self.mtltab, res) 54 | self.res = self.engine.res 55 | self.options = options 56 | 57 | self.materials = [tina.Lambert()] 58 | 59 | self.geom.tracers.append(tina.TriangleTracer(**self.options)) 60 | self.geom.tracers.append(tina.ParticleTracer(**self.options)) 61 | 62 | @ti.materialize_callback 63 | def init_mtltab(): 64 | self.mtltab.clear_materials() 65 | for material in self.materials: 66 | self.mtltab.add_material(material) 67 | 68 | def clear_objects(self): 69 | for tracer in self.geom.tracers: 70 | tracer.clear_objects() 71 | 72 | def add_mesh(self, world, verts, norms, coors, mtlid): 73 | self.geom.tracers[0].add_mesh(np.float32(world), verts, norms, coors, mtlid) 74 | 75 | def add_pars(self, world, verts, sizes, colors, mtlid): 76 | self.geom.tracers[1].add_pars(np.float32(world), verts, sizes, colors, mtlid) 77 | 78 | def add_object(self, object, material=None): 79 | if material is None: 80 | material = self.materials[0] 81 | if material not in self.materials: 82 | self.materials.append(material) 83 | mtlid = self.materials.index(material) 84 | 85 | if hasattr(object, 'get_nfaces'): 86 | @ti.materialize_callback 87 | def add_mesh(): 88 | obj = tina.export_simple_mesh(object) 89 | self.add_mesh(np.eye(4), obj['fv'], obj['fn'], obj['ft'], mtlid) 90 | 91 | elif hasattr(object, 'get_npars'): 92 | @ti.materialize_callback 93 | def add_pars(): 94 | obj = tina.export_simple_pars(object) 95 | self.add_pars(np.eye(4), obj['v'], obj['vr'], obj['vc'], mtlid) 96 | 97 | else: 98 | raise RuntimeError(f'cannot determine type of object: {object!r}') 99 | 100 | def clear(self): 101 | self.engine.clear_image() 102 | 103 | def update(self): 104 | self.engine.clear_image() 105 | for tracer in self.geom.tracers: 106 | tracer.update() 107 | for tracer in self.geom.tracers: 108 | tracer.update_emission(self.mtltab) 109 | 110 | def render(self, nsteps=10, russian=2, blocksize=0): 111 | self.engine.trace(nsteps, russian, blocksize) 112 | 113 | def render_light(self, nsteps=10, russian=2): 114 | self.engine.trace_light(nsteps, russian) 115 | 116 | @property 117 | def img(self): 118 | return self.engine.get_image() 119 | 120 | @property 121 | def raw_img(self): 122 | return self.engine.get_image(raw=True) 123 | 124 | def _fast_export_image(self, out, blocksize=0): 125 | self.engine._fast_export_image(out, blocksize) 126 | -------------------------------------------------------------------------------- /tina/shield.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | import functools 4 | import pickle 5 | 6 | 7 | def _getstate(self): 8 | return self._pickable_decl, self.to_numpy() 9 | 10 | 11 | def _Expr_setstate(self, state): 12 | (a, b), data = state 13 | 14 | field = ti.field(*a, **b) 15 | op = ti.impl.pytaichi.global_vars.pop() 16 | assert op is field, (op, field) 17 | self.__init__(field.ptr) 18 | ti.impl.pytaichi.global_vars.append(self) 19 | 20 | @ti.materialize_callback 21 | def restore_state(): 22 | self.from_numpy(data) 23 | 24 | 25 | def _Matrix_setstate(self, state): 26 | (a, b), data = state 27 | 28 | olen = len(ti.impl.pytaichi.global_vars) 29 | field = ti.Matrix.field(*a, **b) 30 | self.__init__([[field(i, j) for j in range(field.m)] for i in range(field.n)]) 31 | 32 | @ti.materialize_callback 33 | def restore_state(): 34 | self.from_numpy(data) 35 | 36 | 37 | ti.Expr.__getstate__ = _getstate 38 | ti.Matrix.__getstate__ = _getstate 39 | ti.Expr.__setstate__ = _Expr_setstate 40 | ti.Matrix.__setstate__ = _Matrix_setstate 41 | 42 | 43 | def _mock(foo): 44 | @functools.wraps(foo) 45 | def wrapped(*a, **b): 46 | ret = foo(*a, **b) 47 | if foo == ti.Matrix._Vector_field: 48 | a = (a[0], 1) + a[1:] 49 | ret._pickable_decl = a, b 50 | return ret 51 | 52 | return wrapped 53 | 54 | 55 | ti.field = _mock(ti.field) 56 | ti.Vector.field = _mock(ti.Vector.field) 57 | ti.Matrix.field = _mock(ti.Matrix.field) 58 | 59 | 60 | print('[Tina] Taichi fields pickle hacked') 61 | 62 | 63 | __all__ = [] 64 | -------------------------------------------------------------------------------- /tina/util/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .accumator import * 3 | from .control import * 4 | from .matrix import * 5 | from .mciso import * 6 | from .stack import * 7 | -------------------------------------------------------------------------------- /tina/util/_mciso_data.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import base64 3 | 4 | _et2 = np.array([ 5 | [[-1, -1], [-1, -1]], # 6 | [[0, 1], [-1, -1]], #a 7 | [[0, 2], [-1, -1]], #b 8 | [[1, 2], [-1, -1]], #ab 9 | [[1, 3], [-1, -1]], #c 10 | [[0, 3], [-1, -1]], #ca 11 | [[2, 3], [0, 1]], #cb 12 | [[2, 3], [-1, -1]], #cab 13 | [[2, 3], [-1, -1]], #d 14 | [[2, 3], [0, 1]], #da 15 | [[0, 3], [-1, -1]], #db 16 | [[1, 3], [-1, -1]], #dab 17 | [[1, 2], [-1, -1]], #dc 18 | [[0, 2], [-1, -1]], #dca 19 | [[0, 1], [-1, -1]], #dcb 20 | [[-1, -1], [-1, -1]], #dcab 21 | ], np.int32) 22 | 23 | # {{{ https://www.cnblogs.com/shushen/p/5542131.html 24 | _et3b = b'|NsC0|NsC0|NsC0|Ns902m}BB|NsC0|NsC0|Nj613IG59|NsC0|NsC0{{aXC2?zoI|NsC0|NsC00RjsD|NsC0|NsC0|Ns902m=8E3jhEA|NsC0|NjXB3IGBL|NsC0|NsC0{{jdD0tyHU2?+oH|NsC00}BHG|NsC0|NsC0|Ns903jzoW0RR90|NsC0|Nj9A00ILG|NsC0|NsC0{{agE0SOBU2n+xJ|NsC00}25P3IqTD|NsC0|Ns903IPBJ3J41d|NsC0|NjFC00RpN3knJU|NsC0{|N{R3J44T|NsC0|NsC01P2KJ|NsC0|NsC0|Ns940{{mD1poj4|NsC0|Nj612?zuS|NsC0|NsC0{{#UE1P1{J0|Ed4|NsC00RjpL1PA~B|NsC0|Ns931P22E1OWmH|NsC0|NjXB3JCxL2m}ZJ|NsC0{{jjL0tp8K2LlHQ1poj42m}WM3j+WD|NsC0|Ns9B1P2QO1OfmA|NsC0|NjX90SE*K0s{;G|NsC0{{#mM2?PrX3jzrO0ssI10}25H3knAa1poj4|Ns913km@Q3jqKG2MYxM|NjIB2nhfS2@47f00aO3{{#mM1PciX3kv`L|NsC02?YfI|NsC0|NsC0|Ns991q1*H1ONa3|NsC0|Nj651OWvA|NsC0|NsC0{|E&H2m=KJ0R{j6|NsC00RjpM1qA>9|NsC0|Ns9300;pB3IquS|NsC0|NjL73IzlL1ONj6|NsC0{{jjH0|EsD1q1^G2><{82?YcK0}KEE|NsC0|Ns903jzQL3j_%T|NsC0|Nj651ONdA0s{;G|NsC0{{jI80tE;H2nz%V1^@s53IhuY0RssI1poj4|Ns942?YQM0SF2K2n!1T|NjL900jUG1q%ub00aO3{{;jH1qccX2n+xJ|NsC02?q!T2MPcG|NsC0|Ns990{{sH0|f^I|NsC0|Nj672mk>G0R;#D|NsC0{{aO90|f{F|NsC0|NsC02?q!X1qTWN0{{R2|Ns9A0RjmH00jd81qTEF|NjU80tf;H2n7cU1p@#7{{jjH0tEvD1qc8C|NsC02MGlS2nhoV0{{R2|Ns991qTTS0to^D0tXBK|NjC53jhHK0S5>H1qc8C{|f>E3jqfQ0R{j6|NsC02?YoU1qTWN0}2BR|Ns952LJ^C2?q-R0RRdM3IGcV01E&E3IzZN00#vJ01FBQ2MY!N|NsC0|NsC03I+xL|NsC0|NsC0|Ns902m=KQ2LJ#6|NsC0|NjX90R;*M|NsC0|NsC0{{aXC0SO2N3I_lG|NsC00R{yE1_A&7|NsC0|Ns911_c2E1_J;H|NsC0|NjXF1qlEK00IX8|NsC0{{;yM1qcEK0tN#D2><{80s{*Q1_l5B|NsC0|Ns9B00;{L015^L|NsC0|Nj612?7HP1qufL|NsC0{{;#L0SN*L3jzrU3;+NB1_KKQ1p@^E1ONa3|Ns902nzrU1poyB1q%lM|NjFE1^@#F00spB1quKE{{{sK1_=ub2?+oH|NsC01qucP2MGWF|NsC0|Ns940{{dE0|o^O|NsC0|Nj9A00jyL2m}ZJ|NsC0{|W{L0SN~I2LlHQ1poj41_1&F1px#H2><{8|Ns910tE#E1_J;D0|W>E|NjUC2MGWL00spB0tWy8{|5sJ2MGiN0tp2P1_A~N0}BEN2m}fS1^@s5|Ns953I+rR0t5m80tXBK|Nj612?PfS0s{*L3I_lG{|N#C2@3)V1PccX1O*BP2m}WM3k3rO0R;;N|Ns950Sg5S1_1yI2MYuM1PcHK2>=EK00RaK1_KBL2L=TR1_=uU2MGrY3IG593IquT1PcHE|NsC0|Ns943I+rT3IGTL|NsC0|NjaA0SX2H1_S{A|NsC0{|EyC2muBN1_TBH3jhEA0R#yF0t5mE1poj4|Ns9300;pB2?7KO0tN*C|Nj621Ox&G|NsC0|NsC0{|EyD2m%BI0tWy8|NsC03IquX1_TQN1ONa3|Ns902m%5K3j_%Y1PTWK|NjFE0ssL900smG0Sf>B{{{pB1_25L2mt~C3kVAV2?hiS0|p5J0}BQN|Ns983jqiL01E~I2>}EK1OWpJ1_K5F00spA|NsC0{{{pI3kC@P|NsC0|NsC02MPuU2nq-Z3jhEA|Ns902Lk{K2LK5Q1_uiN|NjaG2LTEP0S5>H2mt^8{|W{N3I_oJ2Lu2A|NsC00RjdA1_%KN2?z!U|Ns921_=TQ0R{&N00{#J0|^HR00#gD1^@#8|NsC0{|5sC1_uKF|NsC0|NsC00s{*Q1_%lW2?z!U|Ns9200#mG3jhfR1_ufW3I_oQ009RG0SX5O2MPiM3kw1P3jqfT1_1^K0ssI12nhxV1_ucN1`7rQ0RsjA2>}ZR2mk;7|NsC0|NjRF00#gD0}B8P1_1y6{|5^O|NsC0|NsC0|NsC02L=oO|NsC0|NsC0|Ns9300;{Q2LJ#6|NsC0|Nj612@3}X|NsC0|NsC0{|EsI2m=8N2L}KD|NsC03IPHJ3kU!I|NsC0|Ns910ty2F2nGuW|NsC0|NjCB00IgL1`7xO|NsC0{{{;O0ty2P2m=ZU2><{82Lb~I0tf&9|NsC0|Ns9700;*L00sg8|NsC0|NjC91_A>I009aA|NsC0{{aR90SE>G2?z)W2LJ#63I_%X0S5sC2mk;7|Ns9A2L=HL3IPZQ0RRa9|Nj632LJ~O0162P3J3rH{|5#N2MP!X3JL%J|NsC01_%TT2nPTE|NsC0|Ns931`7iK1^@&G|NsC0|NjUE3kU=T2>=2A|NsC0{|N*J2?hfR0|5&I2LJ#61_%TO3kU)V0ssI1|Ns910ty2F3jhWS00ajA|NjIF2m}TT00IdD3JL%J{|X5M3IhTO1Op2L1_TBJ2m%8L1OfyG0{{R2|Ns901OfyG0{{R2|NsC0|Nj9A00IL90t5yG0|@{B{{aaE0R#d91P1^A|NsC02mu2K1_1~J1_lZN|Ns9A0RRdB1_l5G|NsC0|NjIA0|WyI1_}cJ0|^QV0}2TQ1_}iK|NsC0|NsC01PKKP1`GfH|NsC0|Ns902m=HO1q%lT|NsC0|NjL50R;pA2L=oO|NsC0{|g5O2m=HI1q1^D1^@s52?YcS0RjgG3;+NB|Ns963kLxL3IGTL1PKNI|NjRD3k3uU1Of^K00RI2{{sXF0|f*F0tE^M0t*KQ2Lb~J1_A{H3IG59|Ns991q1*H1^@;E1_%fL|NjF90s{vI0R;dB1OWg4{{{jG1_%cN0SE*L1px&J2?YcS0R{mF1_1*H|Ns911_}WO1_1yE2nPTO1q1{D3IqxT00RmJ3I_uR3I_%X2MP!U1PTNQ3jhEA1_=cQ3keGd3IG59|Ns931`7ZN0{{gE00{;E|Nj6B2ml2O009LB1`GfH{{{;K1_K2J0|Ed4|NsC00RjpM1q%ra2nz)U|Ns903j+WK3jhfQ1qKNL0tyQV1q%fR2ml2N1p)v91qKTP1_K2G3IhrS1ONa31qcZR0tf{L0s{yF|Ns991qKNQ000I8|NsC0|Nj962muHH1qKKM2m%HI2mu8K0s#j9|NsC0|NsC00RsjB1_}cR1_cHQ2nhxX0RRdB1_=cK1qJ~B|Nj632n7ZT|NsC0|NsC0{|W^L|NsC0|NsC0|NsC03k3=X1q=WG|NsC0|Ns9B1qurX1qcHG|NsC0|NjLG2L%cX0SN&A|NsC0{|W~M3JV7b2muHK0ssI13jqQP2LT5K0ssI1|Ns902m=8E2LT5K2LcQK|NjXG1qlKN2>=2D3kU!I{|5yE2LcNP2?7HG2nh%R0tE^J0|f&I1^@s5|Ns980ssgF0tg2M3IYZH|NjX90R;*J1p@~I3IhNC{|N{J2?7BK2LcKL1qTHJ0RsgC2L=EC|NsC0|Ns902nPTM0RaaE|NsC0|NjX90|^5K1p^2F|NsC0{|N{O1qlcL|NsC0|NsC01qcKM3J3}d2><{8|Ns9500adK00jyQ3j+ZE|Nj612?zuV2nq`d1O@;9{|XBP3IqiU0|W^K0RsU90tEpA2n7NQ2m}QP|Ns901PcHQ0|W&N0t*2J0Sf>E1poyJ0t*EM1qcfW1qlQN0t*BG|NsC0|NsC00tE^K1p)&E1p^2K|Ns953IYWJ1Ox&A|NsC0|NjFD0s{pK0|*5K1qc8E2?YuQ1p)*C2?7ZO0{{R22m}QP1p@;G0ssI1|Ns901O)*A1^@s5|NsC0|NjUC1qcNL2>=BE0|o#8{|N*I|NsC0|NsC0|NsC01PccQ2@44d3;+NB|Ns902m=HO2MG%Y2?`7U|Nj9B3jqrR0R#XC1PlNF{{sO80|W>G3IqoP3knMa1PccV3j_%Q3kd-N|Ns992LuTV2MGZS0t*2E2m=cT1PcTL0t5j6|NsC0{|g5M3j_iP0|WyC1poj40tpHN2MGcL2L}WR|Ns993I_=X1PTHN2nPTH00#pH3IhrP2LuWM3IGHE3IPfN2nPiJ|NsC0|NsC01PK8I0S5;G1ONa3|Ns942>}EF2LK2G2nPZG|NjI40|x{H|NsC0|NsC0{{#pJ|NsC0|NsC0|NsC02?_`b3kd)J|NsC0|Ns9300{#L3kwMf|NsC0|Nj613IGZS2nq}T|NsC0{{sOE3j+%O|NsC0|NsC00RjsF3keAe2><{8|Ns9300{#L3jqQN0t*TM|Nj623kU!U|NsC0|NsC0{{sRG|NsC0|NsC0|NsC00s{yF2nq@a3IG59|Ns993IYHL0{{R2|NsC0|NjC52m%NS009UA3JCxI{{adD|NsC0|NsC0|NsC00RspL0SN#9|NsC0|Ns902?78A|NsC0|NsC0|Nj632><{8|NsC0|NsC0|NsC0|NsC0|NsC0|NsC0' 25 | # }}} 26 | _et3 = np.frombuffer(base64.b85decode(_et3b), dtype=np.int8).reshape(256, 5, 3).astype(np.int32) 27 | -------------------------------------------------------------------------------- /tina/util/accumator.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class Accumator: 6 | def __init__(self, res): 7 | self.img = ti.Vector.field(3, float, res) 8 | self.count = ti.field(int, ()) 9 | 10 | @ti.kernel 11 | def clear(self): 12 | self.count[None] = 0 13 | for I in ti.grouped(self.img): 14 | self.img[I] *= 0 15 | 16 | @ti.kernel 17 | def update(self, src: ti.template()): 18 | self.count[None] += 1 19 | inv_count = 1 / self.count[None] 20 | for I in ti.grouped(self.img): 21 | color = src[I] 22 | self.img[I] *= 1 - inv_count 23 | self.img[I] += color * inv_count 24 | -------------------------------------------------------------------------------- /tina/util/matrix.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from transformations import quaternion_matrix, quaternion_multiply, quaternion_from_matrix 3 | 4 | def identity(): 5 | return np.eye(4) 6 | 7 | 8 | def affine(lin, pos): 9 | lin = np.concatenate([lin, np.zeros((1, 3))], axis=0) 10 | pos = np.concatenate([pos, np.ones(1)]) 11 | lin = np.concatenate([lin, pos[:, None]], axis=1) 12 | return lin 13 | 14 | def RotationStep(R, wx, wy, wz): 15 | q = quaternion_from_matrix(R) 16 | qw = 0.5*np.array([0, wx, wy, wz]) 17 | q += quaternion_multiply(q, qw) 18 | q /= np.linalg.norm(q) 19 | return quaternion_matrix(q) 20 | 21 | 22 | def lookat(pos=(0, 0, 0), back=(0, 0, 3), up=(0, 1, 1e-12)): 23 | pos = np.array(pos, dtype=float) 24 | back = np.array(back, dtype=float) 25 | up = np.array(up, dtype=float) 26 | 27 | fwd = -back 28 | fwd /= np.linalg.norm(fwd) 29 | right = np.cross(fwd, up) 30 | right /= np.linalg.norm(right) 31 | up = np.cross(right, fwd) 32 | 33 | lin = np.transpose(np.stack([right, up, -fwd])) 34 | return np.linalg.inv(affine(lin, (pos + back))) 35 | 36 | 37 | def ortho(left=-1, right=1, bottom=-1, top=1, near=-100, far=100): 38 | lin = np.eye(4) 39 | lin[0, 0] = 2 / (right - left) 40 | lin[1, 1] = 2 / (top - bottom) 41 | lin[2, 2] = -2 / (far - near) 42 | lin[0, 3] = -(right + left) / (right - left) 43 | lin[1, 3] = -(top + bottom) / (top - bottom) 44 | lin[2, 3] = -(far + near) / (far - near) 45 | return lin 46 | 47 | 48 | def frustum(left=-1, right=1, bottom=-1, top=1, near=1, far=100): 49 | lin = np.eye(4) 50 | lin[0, 0] = 2 * near / (right - left) 51 | lin[1, 1] = 2 * near / (top - bottom) 52 | lin[0, 2] = (right + left) / (right - left) 53 | lin[1, 2] = (top + bottom) / (top - bottom) 54 | lin[2, 2] = -(far + near) / (far - near) 55 | lin[2, 3] = -2 * far * near / (far - near) 56 | lin[3, 2] = -1 57 | lin[3, 3] = 0 58 | return lin 59 | 60 | 61 | def orthogonal(size=1, aspect=1, near=-100, far=100): 62 | ax, ay = size * aspect, size 63 | return ortho(-ax, ax, -ay, ay, near, far) 64 | 65 | 66 | def perspective(fov=60, aspect=1, near=0.05, far=500): 67 | fov = np.tan(np.radians(fov) / 2) 68 | ax, ay = fov * aspect, fov 69 | return frustum(-near * ax, near * ax, -near * ay, near * ay, near, far) 70 | 71 | 72 | def scale(factor): 73 | return affine(np.eye(3) * np.array(factor), np.zeros(3)) 74 | 75 | 76 | def translate(offset): 77 | return affine(np.eye(3), np.array(offset) * np.ones(3)) 78 | 79 | 80 | # https://zhuanlan.zhihu.com/p/259999988 81 | def quaternion(q): 82 | R = np.array([ 83 | [1.0 - 2 * (q[1] * q[1] + q[2] * q[2]), 84 | 2 * (q[0] * q[1] - q[3] * q[2]), 85 | 2 * (q[3] * q[1] + q[0] * q[2])], 86 | [2 * (q[0] * q[1] + q[3] * q[2]), 87 | 1.0 - 2 * (q[0] * q[0] + q[2] * q[2]), 88 | 2 * (q[1] * q[2] - q[3] * q[0])], 89 | [2 * (q[0] * q[2] - q[3] * q[1]), 90 | 2 * (q[1] * q[2] + q[3] * q[0]), 91 | 1.0 - 2 * (q[0] * q[0] + q[1] * q[1])]]) 92 | return affine(R, np.zeros(3)) 93 | 94 | 95 | # https://zhuanlan.zhihu.com/p/259999988 96 | def eularXYZ(theta): 97 | R_x = np.array([[1, 0, 0], 98 | [0, np.cos(theta[0]), -np.sin(theta[0])], 99 | [0, np.sin(theta[0]), np.cos(theta[0])]]) 100 | R_y = np.array([[np.cos(theta[1]), 0, np.sin(theta[1])], 101 | [0, 1, 0], [-np.sin(theta[1]), 0, np.cos(theta[1])]]) 102 | R_z = np.array([[np.cos(theta[2]), -np.sin(theta[2]), 0], 103 | [np.sin(theta[2]), np.cos(theta[2]), 0], [0, 0, 1]]) 104 | return affine(R_z @ R_y @ R_x, np.zeros(3)) 105 | -------------------------------------------------------------------------------- /tina/util/stack.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class Stack: 6 | def __init__(self, N_mt=512 * 512, N_len=32, field=None): 7 | if ti.cfg.arch == ti.cpu and ti.cfg.cpu_max_num_threads == 1 or ti.cfg.arch == ti.cc: 8 | N_mt = 1 9 | print('[Tina] Using', N_mt, 'threads') 10 | self.N_mt = N_mt 11 | self.N_len = N_len 12 | self.val = ti.field(int) if field is None else field 13 | self.blk1 = ti.root.dense(ti.i, N_mt) 14 | self.blk2 = self.blk1.dense(ti.j, N_len) 15 | self.blk2.place(self.val) 16 | self.len = ti.field(int, N_mt) 17 | 18 | @staticmethod 19 | def instance(): 20 | return Stack.g_stack 21 | 22 | @ti.func 23 | def __iter__(self): 24 | for i in range(self.N_mt): 25 | stack = self.Proxy(self, i) 26 | setattr(Stack, 'g_stack', stack) 27 | yield i 28 | delattr(Stack, 'g_stack') 29 | 30 | @ti.data_oriented 31 | class Proxy: 32 | def __init__(self, stack, mtid): 33 | self.stack = stack 34 | self.mtid = mtid 35 | 36 | def __getattr__(self, attr): 37 | return getattr(self.stack, attr) 38 | 39 | @ti.func 40 | def size(self): 41 | return self.len[self.mtid] 42 | 43 | @ti.func 44 | def clear(self): 45 | self.len[self.mtid] = 0 46 | 47 | @ti.func 48 | def push(self, val): 49 | l = self.len[self.mtid] 50 | self.val[self.mtid, l] = val 51 | self.len[self.mtid] = l + 1 52 | 53 | @ti.func 54 | def pop(self): 55 | l = self.len[self.mtid] 56 | val = self.val[self.mtid, l - 1] 57 | self.len[self.mtid] = l - 1 58 | return val 59 | -------------------------------------------------------------------------------- /tina/voxl/__init__.py: -------------------------------------------------------------------------------- 1 | if __import__('tina').lazyguard: 2 | from .simple import * 3 | from .trans import * 4 | from .scale import * 5 | -------------------------------------------------------------------------------- /tina/voxl/base.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class VoxlEditBase: 6 | def __init__(self, voxl): 7 | self.voxl = voxl 8 | 9 | def __getattr__(self, attr): 10 | return getattr(self.voxl, attr) 11 | -------------------------------------------------------------------------------- /tina/voxl/scale.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from .base import VoxlEditBase 3 | 4 | 5 | class VolumeScale(VoxlEditBase): 6 | def __init__(self, voxl, scale=1): 7 | super().__init__(voxl) 8 | 9 | self.scale = ti.field(float, ()) 10 | 11 | @ti.materialize_callback 12 | def init_scale(): 13 | self.scale[None] = scale 14 | 15 | def set_scale(self, scale): 16 | self.scale[None] = scale 17 | 18 | @ti.func 19 | def sample_volume(self, pos): 20 | return self.voxl.sample_volume(pos) * self.scale[None] 21 | -------------------------------------------------------------------------------- /tina/voxl/simple.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | 3 | 4 | @ti.data_oriented 5 | class SimpleVolume: 6 | def __init__(self, N): 7 | self.N = N 8 | self.dens = ti.field(float, (N, N, N)) 9 | 10 | @ti.materialize_callback 11 | def init_pars(): 12 | self.dens.fill(1) 13 | 14 | def set_volume_density(self, dens): 15 | self.dens.from_numpy(dens) 16 | 17 | @ti.func 18 | def pre_compute(self): 19 | pass 20 | 21 | @ti.func 22 | def sample_volume(self, pos): 23 | ret = 0.0 24 | if all(-1 <= pos <= 1): 25 | ret = trilerp(self.dens, (pos * 0.5 + 0.5) * self.N) 26 | return ret 27 | 28 | @ti.func 29 | def sample_gradient(self, pos): 30 | ret = ti.Vector.zero(float, 3) 31 | for i in ti.static(range(3)): 32 | dir = U3(i) * 0.5 / self.N 33 | hi = self.sample_volume(pos + dir) 34 | lo = self.sample_volume(pos - dir) 35 | ret[i] = (hi - lo) / 2 36 | return ret 37 | 38 | @ti.func 39 | def get_transform(self): 40 | return ti.Matrix.identity(float, 4) 41 | 42 | def get_bounding_box(self): 43 | return V3(-1.), V3(1.) 44 | -------------------------------------------------------------------------------- /tina/voxl/trans.py: -------------------------------------------------------------------------------- 1 | from ..common import * 2 | from .base import VoxlEditBase 3 | 4 | 5 | class VolumeTransform(VoxlEditBase): 6 | def __init__(self, voxl): 7 | super().__init__(voxl) 8 | 9 | self.trans = ti.Matrix.field(4, 4, float, ()) 10 | self.inv_trans = ti.Matrix.field(4, 4, float, ()) 11 | 12 | @ti.materialize_callback 13 | @ti.kernel 14 | def init_trans(): 15 | self.trans[None] = ti.Matrix.identity(float, 4) 16 | self.inv_trans[None] = ti.Matrix.identity(float, 4) 17 | 18 | @ti.func 19 | def get_transform(self): 20 | return self.trans[None] @ self.voxl.get_transform() 21 | 22 | def set_transform(self, trans): 23 | self.trans[None] = np.array(trans).tolist() 24 | self.inv_trans[None] = np.linalg.inv(trans).tolist() 25 | -------------------------------------------------------------------------------- /view.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | from matplotlib import cm 4 | import os 5 | 6 | cmap = cm.get_cmap('magma') 7 | 8 | 9 | res = 256, 64, 64 10 | #res = 1024, 256, 1 11 | rho = ti.field(float, res) 12 | vel = ti.Vector.field(3, float, res) 13 | img = ti.field(float, (res[0], res[1])) 14 | 15 | 16 | def load(frame): 17 | path = f'/tmp/{frame:06d}.npz' 18 | if not os.path.exists(path): 19 | return False 20 | with np.load(path) as data: 21 | rho.from_numpy(data['rho']) 22 | vel.from_numpy(data['vel']) 23 | return True 24 | 25 | 26 | @ti.func 27 | def color(x, y, z): 28 | return vel[x, y, z].norm() * 5 29 | 30 | 31 | @ti.kernel 32 | def render(): 33 | for x, y in img: 34 | ret = 0.0 35 | cnt = 0 36 | for z in range(res[2] // 4, max(1, res[2] * 3 // 4)): 37 | ret += color(x, y, z) 38 | cnt += 1 39 | img[x, y] = ret / cnt 40 | 41 | 42 | frame = 0 43 | while load(frame): 44 | print('render for', frame) 45 | render() 46 | im = cmap(img.to_numpy()) 47 | ti.imshow(im) 48 | ti.imwrite(im, f'/tmp/{frame:06d}.png') 49 | frame += 1 50 | --------------------------------------------------------------------------------