diff --git a/docs/lang/articles/visualization/ggui.md b/docs/lang/articles/visualization/ggui.md index 39723a0b69f3d..efa093e91d557 100644 --- a/docs/lang/articles/visualization/ggui.md +++ b/docs/lang/articles/visualization/ggui.md @@ -94,6 +94,7 @@ Note that you need to call `point_light()` for every frame. Similar to the `canv ### 3D Geometries ```python +scene.lines(vertices, width, indices, color, per_vertex_color) scene.mesh(vertices, indices, normals, color, per_vertex_color) scene.particles(vertices, radius, color, per_vertex_color) ``` @@ -108,6 +109,220 @@ If a mesh has `num` triangles, the `indices` should be a 1D scalar field with a `normals` is an optional parameter for `scene.mesh()`. +:::example + +1. An example of drawing 3d-lines + +```python +import taichi as ti + +ti.init(arch=ti.cuda) + +N = 10 + +particles_pos = ti.Vector.field(3, dtype=ti.f32, shape = N) +points_pos = ti.Vector.field(3, dtype=ti.f32, shape = N) + +@ti.kernel +def init_points_pos(points : ti.template()): + for i in range(points.shape[0]): + points[i] = [i for j in ti.static(range(3))] + +init_points_pos(particles_pos) +init_points_pos(points_pos) + +window = ti.ui.Window("Test for Drawing 3d-lines", (768, 768)) +canvas = window.get_canvas() +scene = ti.ui.Scene() +camera = ti.ui.make_camera() +camera.position(5, 2, 2) + +while window.running: + camera.track_user_inputs(window, movement_speed=0.03, hold_key=ti.ui.RMB) + scene.set_camera(camera) + scene.ambient_light((0.8, 0.8, 0.8)) + scene.point_light(pos=(0.5, 1.5, 1.5), color=(1, 1, 1)) + + scene.particles(particles_pos, color = (0.68, 0.26, 0.19), radius = 0.1) + # Draw 3d-lines in the scene + scene.lines(points_pos, color = (0.28, 0.68, 0.99), width = 5.0) + canvas.scene(scene) + window.show() +``` + +### Advanced 3d Geometries + +```python +scene.lines(vertices, width, indices, color, per_vertex_color, vertex_offset, vertex_count, index_offset, index_count) + +scene.mesh(vertices, indices, normals, color, per_vertex_color, vertex_offset, vertex_count, index_offset, index_count, show_wireframe) + +scene.particles(vertices, radius, color, per_vertex_color, index_offset, index_count) + +scene.mesh_instance(vertices, indices, normals, color, per_vertex_color, vertex_offset, vertex_count, index_offset, index_count, show_wireframe) +``` + +The additional arguments `vertex_offset`, `vertex_count`, `index_offset` and `index_count` control the visible part of the particles and mesh. For the `mesh()` and `mesh_instance()` methods, set whether to show wireframe mode through setting `show_wireframe`. + +:::example + +1. Example of drawing a part of the mesh/particles + +```python +# For particles +# draw the 2-th to 7-th particles +scene.particles(center, radius, +index_offset = 1, +index_count = 6) + +# For mesh +# 1. with indices +scene.mesh(vertices, indices, +index_offset = user_defined_first_indices_index, +index_count = user_defined_index_count, +# vertex_offset is set to 0 by default, and it is not necessary +# to assign vertex_offset a value that otherwise you must. +vertex_offset = user_defined_vertex_offset) + +# usually used as below: +# draw the 11-th to 111-th mesh vertexes +scene.mesh(vertices, indices, +index_offset = 10, +index_count = 100) + +# 2. without indices (similar to the particles' example above) +scene.mesh(vertices, +vertex_offset = user_defined_first_vertex_index, +vertex_count = user_defined_vertex_count) +``` +2. An example of drawing part of lines +```python +import taichi as ti + +ti.init(arch=ti.cuda) + +N = 10 + +particles_pos = ti.Vector.field(3, dtype=ti.f32, shape = N) +points_pos = ti.Vector.field(3, dtype=ti.f32, shape = N) +points_indices = ti.Vector.field(1, dtype=ti.i32, shape = N) + +@ti.kernel +def init_points_pos(points : ti.template()): + for i in range(points.shape[0]): + points[i] = [i for j in range(3)] + # points[i] = [ti.sin(i * 1.0), i * 0.2, ti.cos(i * 1.0)] + +@ti.kernel +def init_points_indices(points_indices : ti.template()): + for i in range(N): + points_indices[i][0] = i // 2 + i % 2 + +init_points_pos(particles_pos) +init_points_pos(points_pos) +init_points_indices(points_indices) + +window = ti.ui.Window("Test for Drawing 3d-lines", (768, 768)) +canvas = window.get_canvas() +scene = ti.ui.Scene() +camera = ti.ui.make_camera() +camera.position(5, 2, 2) + +while window.running: + camera.track_user_inputs(window, movement_speed=0.03, hold_key=ti.ui.RMB) + scene.set_camera(camera) + scene.ambient_light((0.8, 0.8, 0.8)) + scene.point_light(pos=(0.5, 1.5, 1.5), color=(1, 1, 1)) + + scene.particles(particles_pos, color = (0.68, 0.26, 0.19), radius = 0.1) + # Here you will get visible part from the 3rd point with (N - 4) points. + scene.lines(points_pos, color = (0.28, 0.68, 0.99), width = 5.0, vertex_count = N - 4, vertex_offset = 2) + # Using indices to indicate which vertex to use + # scene.lines(points_pos, color = (0.28, 0.68, 0.99), width = 5.0, indices = points_indices) + # Case 1, vertex_count will be changed to N - 2 when drawing. + # scene.lines(points_pos, color = (0.28, 0.68, 0.99), width = 5.0, vertex_count = N - 1, vertex_offset = 0) + # Case 2, vertex_count will be changed to N - 2 when drawing. + # scene.lines(points_pos, color = (0.28, 0.68, 0.99), width = 5.0, vertex_count = N, vertex_offset = 2) + canvas.scene(scene) + window.show() +``` + +3. Details of mesh instancing +```python +num_instance = 100 +m_transforms = ti.Matrix.field(4, 4, dtype = ti.f32, shape = num_instance) + + +# For example: An object is scaled by 2, rotated by rotMat, and translated by t = [1, 2, 3], then +# +# The ScaleMatrix is: +# 2, 0, 0, 0 +# 0, 2, 0, 0 +# 0, 0, 2, 0 +# 0, 0, 0, 1 +# +# The RotationMatrix is: +# https://en.wikipedia.org/wiki/Rotation_matrix#General_rotations +# +# The TranslationMatrix is: +# 1, 0, 0, 1 +# 0, 1, 0, 2 +# 0, 0, 1, 3 +# 0, 0, 0, 1 +# +# Let TransformMatrix = TranslationMatrix @ RotationMatrix @ ScaleMatrix, then the final TransformMatrix is: +# 2 * rotMat00, rotMat01, rotMat02, 1 +# rotMat10, 2 * rotMat11, rotMat12, 2 +# rotMat20, rotMat21, 2 * rotMat22, 3 +# 0, 0, 0, 1 +... + +# Draw mesh instances (from the 1st instance) +scene.mesh_instance(vertices, indices, transforms = m_transforms, instance_offset = 1) +``` +4. Example of setting wireframe mode +```python + +window = ti.ui.Window("Display Mesh", (1024, 1024), vsync=True) +canvas = window.get_canvas() +scene = ti.ui.Scene() +camera = ti.ui.make_camera() + +# slider_int usage +some_int_type_value = 0 +def show_options(): + global some_int_type_value + + window.GUI.begin("Display Panel", 0.05, 0.1, 0.2, 0.15) + display_mode = window.GUI.slider_int("Value Range", some_int_type_value, 0, 5) + window.GUI.end() + +while window.running: + + ... + # if to show wireframe + scene.mesh_instance(vertices, indices, instance_count = 100 , show_wireframe = True) + + canvas.scene(scene) + show_options() + window.show() +``` + + + +:::note + +If `indices` is not provided, consider using like this: +```python +scene.mesh(vertices, normals, color, per_vertex_color, vertex_offset, vertex_count, wireframe) +``` +If `indices` is provided, consider using like this: +```python +scene.mesh(vertices, indices, normals, color, per_vertex_color, vertex_offset, index_offset, index_count, wireframe) +``` + + + ::: ### Rendering the scene @@ -118,6 +333,55 @@ You can render a scene on a canvas. canvas.scene(scene) ``` +### Fetching Color/Depth information + +```python +img = window.get_image_buffer() +window.get_depth_buffer(scene_depth) +depth = window.get_depth_buffer_as_numpy() +``` + +After rendering the current scene, you can fetch the color and depth information of the current scene using `get_image_buffer()` and `get_depth_buffer_as_numpy()`, which copy the gpu data to a NumPy array(cpu). +`get_depth_buffer()` copies the GPU data to a Taichi field (depend on the `arch` you choose) or copies data from GPU to GPU. + +:::example + +1. Example of fetching color information +```python +window = ti.ui.Window("Test for getting image buffer from ggui", (768, 768), vsync=True) +video_manager = ti.tools.VideoManager("OutputDir") + +while window.running: + render_scene() + img = window.get_image_buffer() + video_manager.write_frame(img) + window.show() + +video_manager.make_video(gif=True, mp4=True) +``` + +2. An example of fetching the depth data +```python +window_shape = (720, 1080) +window = ti.ui.Window("Test for copy depth data", window_shape) +canvas = window.get_canvas() +scene = ti.ui.Scene() +camera = ti.ui.make_camera() + +# Get the shape of the window +w, h = window.get_window_shape() +# The field/ndarray stores the depth information, and must be of the ti.f32 data type and have a 2d shape. +# or, in other words, the shape must equal the window's shape +scene_depth = ti.ndarray(ti.f32, shape = (w, h)) +# scene_depth = ti.field(ti.f32, shape = (w, h)) + +while window.running: + render() + canvas.scene(scene) + window.get_depth_buffer(scene_depth) + window.show() +``` + ## GUI components The design of GGUI's GUI components follows the [Dear ImGui](https://github.com/ocornut/imgui) APIs. diff --git a/python/taichi/ui/scene.py b/python/taichi/ui/scene.py index 344f2a72900e2..e095ffc009486 100644 --- a/python/taichi/ui/scene.py +++ b/python/taichi/ui/scene.py @@ -171,7 +171,7 @@ def mesh(self, vertex_count: int = None, index_offset: int = 0, index_count: int = None, - show_wareframe: bool = False): + show_wireframe: bool = False): """Declare a mesh inside the scene. if you indicate the index_offset and index_count, the normals will also @@ -206,7 +206,7 @@ def mesh(self, index_count (int, optional): only available when `indices` is provided, which is the the number of vertices to draw. - show_wareframe (bool, optional): + show_wireframe (bool, optional): turn on/off WareFrame mode. """ vbo = get_vbo_field(vertices) @@ -229,7 +229,7 @@ def mesh(self, self.scene.mesh(vbo_info, has_per_vertex_color, indices_info, color, two_sided, index_count, index_offset, vertex_count, - vertex_offset, show_wareframe) + vertex_offset, show_wireframe) def mesh_instance(self, vertices, @@ -245,7 +245,7 @@ def mesh_instance(self, vertex_count: int = None, index_offset: int = 0, index_count: int = None, - show_wareframe: bool = False): + show_wireframe: bool = False): """Declare mesh instances inside the scene. If transforms is given, then according to the shape of transforms, it will @@ -290,7 +290,7 @@ def mesh_instance(self, index_count (int, optional): only available when `indices` is provided, which is the the number of indices to draw. - show_wareframe (bool, optional): + show_wireframe (bool, optional): turn on/off WareFrame mode. """ vbo = get_vbo_field(vertices) @@ -319,7 +319,7 @@ def mesh_instance(self, color, two_sided, transform_info, instance_count, instance_offset, index_count, index_offset, vertex_count, vertex_offset, - show_wareframe) + show_wireframe) def particles(self, centers, diff --git a/python/taichi/ui/window.py b/python/taichi/ui/window.py index b2fb8bfafd5fc..0fdb7a945230b 100644 --- a/python/taichi/ui/window.py +++ b/python/taichi/ui/window.py @@ -168,13 +168,13 @@ def get_depth_buffer_as_numpy(self): arr_vulkan_layout_to_arr_normal_layout(tmp_depth, depth_numpy_arr) return depth_numpy_arr - def get_image_buffer(self): + def get_image_buffer_as_numpy(self): """Get the window content to numpy array. Returns: 3d numpy array: [width, height, channels] with (0.0~1.0) float-format color. """ - return self.window.get_image_buffer() + return self.window.get_image_buffer_as_numpy() def destroy(self): """Destroy this window. The window will be unavailable then. diff --git a/taichi/python/export_ggui.cpp b/taichi/python/export_ggui.cpp index 7c5be1a917b24..bb3feec085ba7 100644 --- a/taichi/python/export_ggui.cpp +++ b/taichi/python/export_ggui.cpp @@ -176,7 +176,7 @@ struct PyScene { float draw_first_index, float draw_vertex_count, float draw_first_vertex, - bool show_wareframe) { + bool show_wireframe) { RenderableInfo renderable_info; renderable_info.vbo = vbo; renderable_info.has_per_vertex_color = has_per_vertex_color; @@ -186,7 +186,7 @@ struct PyScene { renderable_info.draw_first_index = (int)draw_first_index; renderable_info.draw_vertex_count = (int)draw_vertex_count; renderable_info.draw_first_vertex = (int)draw_first_vertex; - renderable_info.display_mode = show_wareframe + renderable_info.display_mode = show_wireframe ? taichi::lang::PolygonMode::Line : taichi::lang::PolygonMode::Fill; @@ -231,7 +231,7 @@ struct PyScene { float draw_first_index, float draw_vertex_count, float draw_first_vertex, - bool show_wareframe) { + bool show_wireframe) { RenderableInfo renderable_info; renderable_info.vbo = vbo; renderable_info.has_per_vertex_color = has_per_vertex_color; @@ -241,7 +241,7 @@ struct PyScene { renderable_info.draw_first_index = (int)draw_first_index; renderable_info.draw_vertex_count = (int)draw_vertex_count; renderable_info.draw_first_vertex = (int)draw_first_vertex; - renderable_info.display_mode = show_wareframe + renderable_info.display_mode = show_wireframe ? taichi::lang::PolygonMode::Line : taichi::lang::PolygonMode::Fill; @@ -484,7 +484,7 @@ void export_ggui(py::module &m) { .def("write_image", &PyWindow::write_image) .def("copy_depth_buffer_to_ndarray", &PyWindow::copy_depth_buffer_to_ndarray) - .def("get_image_buffer", &PyWindow::get_image_buffer) + .def("get_image_buffer_as_numpy", &PyWindow::get_image_buffer) .def("is_pressed", &PyWindow::is_pressed) .def("get_cursor_pos", &PyWindow::py_get_cursor_pos) .def("is_running", &PyWindow::is_running)