{"id":469,"date":"2024-08-02T15:28:38","date_gmt":"2024-08-02T15:28:38","guid":{"rendered":"https:\/\/summergeometry.org\/sgi2024\/?p=469"},"modified":"2024-08-02T15:28:40","modified_gmt":"2024-08-02T15:28:40","slug":"an-introduction-to-rxmesh","status":"publish","type":"post","link":"https:\/\/summergeometry.org\/sgi2024\/an-introduction-to-rxmesh\/","title":{"rendered":"An Introduction to RXMesh"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This post serves as a tutorial for using <a href=\"https:\/\/github.com\/owensgroup\/RXMesh\">RXMesh<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">RXMesh is a framework for processing triangle mesh on the GPU. It allows developers to easily use the GPU&#8217;s massive parallelism to perform common geometry processing tasks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This tutorial will cover the following topics:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Setting up RXMesh<\/li>\n\n\n\n<li>RXMesh basic workflow<\/li>\n\n\n\n<li>Writing kernels for queries<\/li>\n\n\n\n<li>Example: Visualizing face normals<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up RXMesh<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For the sake of this tutorial, you can set up a fork of RXMesh and then create your own small section to implement your own tutorial examples in the fork. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Go to <a href=\"https:\/\/github.com\/owensgroup\/RXMesh\">https:\/\/github.com\/owensgroup\/RXMesh<\/a> and fork the repository. You can create your own directory with a simple CUDA script to start using RXMesh. To do so, go to <a href=\"https:\/\/github.com\/owensgroup\/RXMesh\/blob\/main\/apps\/CMakeLists.txt\">https:\/\/github.com\/owensgroup\/RXMesh\/blob\/main\/apps\/CMakeLists.txt<\/a> in your fork and add a line:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>add_subdirectory(introduction)<\/code><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In the <a href=\"https:\/\/github.com\/owensgroup\/RXMesh\/blob\/main\/apps\/\">apps<\/a> directory on your local fork, create a folder called &#8220;introduction&#8221;. There should be two files in this folder.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A <em>.cu<\/em> file to run the code. You can call this<em> introduction.cu<\/em>. To start, it can have the following contents:<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>#include \"rxmesh\/query.cuh\"\n#include \"rxmesh\/rxmesh_static.h\"\n\n#include \"rxmesh\/matrix\/sparse_matrix.cuh\"\n\nusing namespace rxmesh;\n\nint main(int argc, char** argv)\n{\n    Log::init();\n\n    const uint32_t device_id = 0;\n    cuda_query(device_id);\n\n    RXMeshStatic rx(STRINGIFY(INPUT_DIR) \"sphere3.obj\");\n\n#if USE_POLYSCOPE\n    polyscope::show();\n#endif\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">2. A CMake file called <em>CMakeLists.txt<\/em>. It must have the following contents:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>add_executable(introduction)\n\nset(SOURCE_LIST\n    introduction.cu\t\n)\n\ntarget_sources(introduction\n    PRIVATE\n    ${SOURCE_LIST}\n)\n\nset_target_properties(introduction PROPERTIES FOLDER \"apps\")\n\nset_property(TARGET ARAP PROPERTY CUDA_SEPARABLE_COMPILATION ON)\n\nsource_group(TREE ${CMAKE_CURRENT_LIST_DIR} PREFIX \"introduction\" FILES ${SOURCE_LIST})\n\ntarget_link_libraries(introduction\n    PRIVATE RXMesh\n)\n\n#gtest_discover_tests( introduction )<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once you have done the above steps, building the project with CMake is simple. Refer to the README in <a href=\"https:\/\/github.com\/owensgroup\/RXMesh\">https:\/\/github.com\/owensgroup\/RXMesh<\/a> to know the requirements and steps to build the project for your device.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once the project has been set up, we can begin to test and run our <em>introduction.cu<\/em> file. If you were to run it, you should get an output of a 3D sphere.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"536\" src=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-20-1024x536.png\" alt=\"\" class=\"wp-image-784\" srcset=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-20-1024x536.png 1024w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-20-300x157.png 300w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-20-768x402.png 768w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-20-1536x804.png 1536w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-20-1200x628.png 1200w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-20.png 1919w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This means the project set up has been successful, we can now begin learning RXMesh.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">RXMesh basic workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">RXMesh provides custom data structures and functions to handle various low-level computational tasks that would otherwise require low-level implementation. Let&#8217;s explore the fundamentals of how this workflow operates.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To start, look at this piece of code.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:100%\">\n<pre class=\"wp-block-code\"><code>int main(int argc, char** argv)\n{\n    Log::init();\n    const uint32_t device_id = 0;\n    rxmesh::cuda_query(device_id);\n\n    rxmesh::RXMeshStatic rx(STRINGIFY(INPUT_DIR) \"dragon.obj\");\n    auto polyscope_mesh = rx.get_polyscope_mesh();\n\n    auto vertex_pos   = *rx.get_input_vertex_coordinates();\n    auto vertex_color = *rx.add_vertex_attribute&lt;float&gt;(\"vColor\", 3);\n\n    rx.for_each_vertex(rxmesh::DEVICE,\n    &#091;vertex_color, vertex_pos] __device__(const rxmesh::VertexHandle vh)\n    {\n            vertex_color(vh, 0) = 0.9;\n            vertex_color(vh, 1) = vertex_pos(vh, 1);\n            vertex_color(vh, 2) = 0.9;\n    });\n\n    vertex_color.move(rxmesh::DEVICE, rxmesh::HOST);\n\n    polyscope_mesh-&gt;addVertexColorQuantity(\"vColor\", vertex_color);\n    polyscope::show();\n\n    return 0;\n}<\/code><\/pre>\n<\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The above is a slightly simplified version of what a <code>for_each<\/code> computation program would look like using RXMesh. <code>for_each<\/code> involves accessing every <em>mesh element<\/em> of a specific type (vertex, edge, or face) and performing some process on\/with it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the code example above, the computation adjusts the green component of the vertex&#8217;s color based on the vertex&#8217;s Y coordinate in 3D space.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But how do we comprehend this code? We will look at it line by line:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    rxmesh::RXMeshStatic rx(STRINGIFY(INPUT_DIR) \"dragon.obj\");<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The above line declares the <code>RXMeshStatic<\/code> object. Static here stands for a static mesh (one whose connectivity remains constant for the duration of the program&#8217;s runtime). Using the RXMesh&#8217;s Input directory, we can use some meshes that come with it. In this case, we pass in the dragon.obj file, which holds the mesh data for a dragon.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    auto polyscope_mesh = rx.get_polyscope_mesh();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This line returns the data needed for <a href=\"https:\/\/github.com\/nmwsharp\/polyscope\">Polyscope <\/a>to display the mesh along with any attribute content we add to it for visualization.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    auto vertex_pos = *rx.get_input_vertex_coordinates();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This line is a special function that directly gives us the coordinates of the input mesh.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>auto vertex_color = *rx.add_vertex_attribute&lt;float&gt;(\"vColor\", 3);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This line gives vertex attributes called &#8220;vColor&#8221;. Attributes are simply data that lives on top of vertices, edges, or faces. To learn more about how to handle attributes in RXMesh, check out <a href=\"https:\/\/github.com\/owensgroup\/RXMesh?tab=readme-ov-file#structures\" data-type=\"link\" data-id=\"https:\/\/github.com\/owensgroup\/RXMesh?tab=readme-ov-file#structures\">this<\/a>. In this case, we associate three float-point numbers to each vertex in the mesh.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rx.for_each_vertex(rxmesh::DEVICE,\n    &#091;vertex_color, vertex_pos] __device__(const rxmesh::VertexHandle vh)\n    {\n            vertex_color(vh, 0) = 0.9;\n            vertex_color(vh, 1) = vertex_pos(vh, 1);\n            vertex_color(vh, 2) = 0.9;\n    });<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The above lines represent a lambda function that is utilized by the <code>for_each<\/code> computation. In this case, <code>for_each_vertex<\/code> accesses each of the vertices of the mesh associated with our dragon. We pass in the arguments:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><code>rxmesh::DEVICE<\/code> &#8211; to let RXMesh know this is happening on the device i.e., the GPU.<\/li>\n\n\n\n<li><code>[vertex_color, vertex_pos]<\/code> &#8211; represents the data we are passing to the function.<\/li>\n\n\n\n<li><code>(const rxmesh::VertexHandle vh)<\/code> &#8211; is the handle that is used in any RXMesh computation. A handle allows us to access data associated to individual geometric elements. In this case, we have a handle that allows us to access each vertex in the mesh.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">What do these lines mean? <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>            vertex_color(vh, 0) = 0.9;\n            vertex_color(vh, 1) = vertex_pos(vh, 1);\n            vertex_color(vh, 2) = 0.9;<\/code><\/pre>\n\n\n\n<p class=\"has-normal-font-size wp-block-paragraph\">These 3 lines bring together a lot of the declarations made earlier. Through the kernel, we are accessing the attribute data we defined before. Since we need to know which vertex we are accessing its color, we pass in <code>vh<\/code> as the first argument. Since a vertex has three components (standing for RGB), we also need to pass which index in the attribute&#8217;s vector (you can think of it as a 1D array too) we are accessing. Hence <code>vertex_color(vh, 0) = 0.9;<\/code> which stands for &#8220;in the vertex color associated with the handle vh (which for the kernel represents a specific vertex on the mesh), the value of the first component is 0.9&#8221;. Note that this &#8220;first component&#8221; represents red for us.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"508\" height=\"226\" src=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-19.png\" alt=\"\" class=\"wp-image-480\" srcset=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-19.png 508w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-19-300x133.png 300w\" sizes=\"auto, (max-width: 508px) 100vw, 508px\" \/><\/figure>\n\n\n\n<p class=\"has-normal-font-size wp-block-paragraph\">What about <code>vertex_color(vh, 1) = vertex_pos(vh, 1)<\/code>? This line, similar to the previous one, is accessing the second component associated with the color, in the vertex the handle is associated with.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But what is on the right-hand side? We are accessing vertex_pos (our coordinates of each vertex in the mesh) and we are accessing it the same way we access our color. In this case, the line is telling us that we are accessing the 2nd positional coordinate (y coordinate) associated with our vertex (that our handle gives to the kernel).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    vertex_color.move(rxmesh::DEVICE, rxmesh::HOST);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This line moves the attribute data from the GPU to the CPU.  <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>polyscope_mesh-&gt;addVertexColorQuantity(\"vColor\", vertex_color);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This line uses Polyscope to add a &#8220;quantity&#8221; to Polyscope&#8217;s visualization of the mesh. In this case, when we add the VertexColorQuantity and pass vertex_color, Polyscope will now visualize the per-vertex color information we calculated in the lambda function.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    polyscope::show();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">We finally render the entire mesh with our added quantities using the <code>show()<\/code> function from Polyscope.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Throughout this basic workflow, you may have noticed it works similarly to any other program where we receive some input data, perform some processing, and then render\/output it in some form.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">More specifically, we use RXMesh to read that data, set up our attributes, and then pass that into a kernel to perform some processing. Once we move that data from the device to the host, we can either perform further processing or render it using Polyscope. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is important to look at the kernel as computing <em>per geometric element.<\/em> This means we <em>only <\/em>need to think of computation on a single mesh element of a certain type (i.e., vertex, edge, or face) since RXMesh then takes this computation and runs it in parallel on all mesh elements of that type.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Writing kernels for queries<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">While our basic workflow covers how to perform <code>for_each<\/code> operation using the GPU, we may often require geometric connectivity information for different geometry processing tasks. To do that, RXMesh implements various query operations. To understand the different types of queries, check out <a href=\"https:\/\/github.com\/owensgroup\/RXMesh?tab=readme-ov-file#computation\" data-type=\"link\" data-id=\"https:\/\/github.com\/owensgroup\/RXMesh?tab=readme-ov-file#computation\">this<\/a> part of the README.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can say there are two parts to run a query.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first part consists of creating the <code>launchbox <\/code>for the query, which defines the threads and (shared) memory allocated for the process along with calling the function from the host to run on the device.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The second part consists of actually defining what the <em>kernel <\/em>looks like.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will look at these one by one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s an example of how a <code>launchbox <\/code>is created and used for a process where we want to find all the vertex normals:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Vertex Normal\nauto vertex_normals = rx.add_vertex_attribute(\"vNormals\", 3);\nconstexpr uint32_t CUDABlockSize = 256;\n\nrxmesh::LaunchBox&lt;CUDABlockSize&gt; launch_box;\n\nrx.prepare_launch_box(\n  {rxmesh::Op::FV},\n  launch_box,\n  (void*)compute_vertex_normal&lt;float,CUDABlockSize&gt;);\n\ncompute_vertex_normal&lt;float, CUDABlockSize&gt;\n&lt;&lt;&lt;launch_box.blocks, \n   launch_box.num_threads, \n   launch_box.smem_bytes_dyn&gt;&gt;&gt;\n(rx.get_context(), vertex_pos, *vertex_normals);\n\nvertex_normals-&gt;move(rxmesh::DEVICE, rxmesh::HOST);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Notice how, in replacing the for_each part of our basic workflow, we instead declare the launchbox and call our function <code>compute_vertex_normal <\/code>(which we will look at next) from our main function.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We must also define the kernel which will run on the device.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>template &lt;typename T, uint32_t blockThreads&gt;\n__global__ static void compute_vertex_normal(const rxmesh::Context      context,\nrxmesh::VertexAttribute&lt;T&gt; coords,\nrxmesh::VertexAttribute&lt;T&gt; normals)\n{\n\n\n    auto vn_lambda = &#091;&amp;](FaceHandle face_id, VertexIterator&amp; fv){\n        \/\/ get the face's three vertices coordinates\n        glm::fvec3 c0(coords(fv&#091;0], 0), coords(fv&#091;0], 1), coords(fv&#091;0], 2));\n        glm::fvec3 c1(coords(fv&#091;1], 0), coords(fv&#091;1], 1), coords(fv&#091;1], 2));\n        glm::fvec3 c2(coords(fv&#091;2], 0), coords(fv&#091;2], 1), coords(fv&#091;2], 2));\n\n        \/\/ compute the face normal\n        glm::fvec3 n = cross(c1 - c0, c2 - c0);\n\n        \/\/ the three edges length\n        glm::fvec3 l(glm::distance2(c0, c1),\n                     glm::distance2(c1, c2),\n                     glm::distance2(c2, c0));\n\n        \/\/ add the face's normal to its vertices\n        for (uint32_t v = 0; v &lt; 3; ++v) {      \n\/\/ for every vertex in this face\n            for (uint32_t i = 0; i &lt; 3; ++i) {  \n\/\/ for the vertex 3 coordinates\n                atomicAdd(&amp;normals(fv&#091;v], i), n&#091;i] \/ (l&#091;v] + l&#091;(v + 2) % 3]));\n            }\n        }\n    };\n\n    auto block = cooperative_groups::this_thread_block();\n\n    Query&lt;blockThreads&gt; query(context);\n    ShmemAllocator      shrd_alloc;\n    query.dispatch&lt;Op::FV&gt;(block, shrd_alloc, vn_lambda);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A few things to note about our kernel<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>We pass in a few arguments to our kernel. \n<ul class=\"wp-block-list\">\n<li>We pass the <code>context<\/code> which allows RXMesh to access the data structures on the device.<\/li>\n\n\n\n<li>We pass in the coordinates of each vertex as a <code>VertexAttribute<\/code><\/li>\n\n\n\n<li>We pass in the normals of each vertex as an attribute. Here, we accumulate the face&#8217;s normal on its three vertices.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>The function that performs the processing is a lambda function. It takes in the handles and iterators as arguments. The type of the argument will depend on what type of query is used. In this case, since it is an FV query, we have access to the current face(<code>face_id<\/code>) the thread is acting on as well as the vertices of that face using <code>fv<\/code>.<\/li>\n\n\n\n<li>Notice how within our lambda function, we do the same as before with our <code>for_each<\/code> operation, i.e., accessing the data we need using the attribute handle and processing it for some output for our attribute.<\/li>\n\n\n\n<li>Outside our lambda function, notice how we need to set some things up with regards to memory and the type of query. After that, we call the lambda function to make it run using <code>query.dispatch<\/code><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Visualizing face normals<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now that we&#8217;ve learnt all the pieces required to do some interesting calculations using RXMesh, let&#8217;s try a new one out. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Try to visualize the face normals of a given mesh. This would mean obtaining an output like this for the dragon mesh given in the repository:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"683\" height=\"632\" src=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/08\/Screenshot-2024-08-01-214425.png\" alt=\"\" class=\"wp-image-819\" srcset=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/08\/Screenshot-2024-08-01-214425.png 683w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/08\/Screenshot-2024-08-01-214425-300x278.png 300w\" sizes=\"auto, (max-width: 683px) 100vw, 683px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"640\" src=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/08\/image-1024x640.png\" alt=\"\" class=\"wp-image-817\" srcset=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/08\/image-1024x640.png 1024w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/08\/image-300x187.png 300w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/08\/image-768x480.png 768w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/08\/image-1200x750.png 1200w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/08\/image.png 1306w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s how to calculate the face normals of a mesh:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"360\" height=\"445\" src=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-21.png\" alt=\"\" class=\"wp-image-786\" style=\"width:329px;height:auto\" srcset=\"https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-21.png 360w, https:\/\/summergeometry.org\/sgi2024\/wp-content\/uploads\/2024\/07\/image-21-243x300.png 243w\" sizes=\"auto, (max-width: 360px) 100vw, 360px\" \/><\/figure>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Take a vertex. Calculate two vectors that are formed by subtracting the selected vertex from the other two vertices.<\/li>\n\n\n\n<li>Take the cross product of the two vectors. <\/li>\n\n\n\n<li>The vector obtained from the cross product is the normal of the face.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Now try to perform the calculation above. We can do this for each face using the vertices it is connected to.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">All the information above should give you the ability to implement this yourself. If required, the solution is given below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The kernel that runs on the device:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>template &lt;typename T, uint32_t blockThreads&gt;\nglobal static void compute_face_normal(\nconst rxmesh::Context context,\nrxmesh::VertexAttribute coords, \/\/ input\nrxmesh::FaceAttribute normals) \/\/ output\n{\nauto vn_lambda = &#091;&amp;](FaceHandle face_id, VertexIterator&amp; fv) {\n    \/\/ get the face's three vertices coordinates\n\n    glm::fvec3 c0(coords(fv&#091;0], 0), coords(fv&#091;0], 1), coords(fv&#091;0], 2));\n    glm::fvec3 c1(coords(fv&#091;1], 0), coords(fv&#091;1], 1), coords(fv&#091;1], 2));\n    glm::fvec3 c2(coords(fv&#091;2], 0), coords(fv&#091;2], 1), coords(fv&#091;2], 2));\n\n    \/\/ compute the face normal\n    glm::fvec3 n = cross(c1 - c0, c2 - c0);\n    normals(face_id, 0) = n&#091;0];\n    normals(face_id, 1) = n&#091;1];\n    normals(face_id, 2) = n&#091;2];\n};\n\nauto block = cooperative_groups::this_thread_block();\nQuery&lt;blockThreads&gt; query(context);\nShmemAllocator shrd_alloc;\nquery.dispatch&lt;Op::FV&gt;(block, shrd_alloc, vn_lambda);\n\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The function call from main on HOST:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>int main(int argc, char** argv)\n{\n    Log::init();\n\n    const uint32_t device_id = 0;\n    cuda_query(device_id);\n\n    rxmesh::RXMeshStatic rx(STRINGIFY(INPUT_DIR) \"sphere3.obj\");\n\n    auto vertex_pos = *rx.get_input_vertex_coordinates();\n    auto face_normals = rx.add_face_attribute&lt;float&gt;(\"fNorm\", 3);\n\n    constexpr uint32_t CUDABlockSize = 256;\n    LaunchBox&lt;CUDABlockSize&gt; launch_box;\n  \n    compute_face_normal&lt;float, CUDABlockSize&gt;\n    &lt;&lt;&lt;launch_box.blocks,\n    launch_box.num_threads,\n    launch_box.smem_bytes_dyn&gt;&gt;&gt;\n    (rx.get_context(), vertex_pos, *face_normals);\n    \n    face_normals-&gt;move(DEVICE, HOST);\n\n    rx.get_polyscope_mesh()-&gt;addFaceVectorQuantity(\"fNorm\", *face_normals);\n\n#if USE_POLYSCOPE\n    polyscope::show();\n#endif\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">As an extra example, try the following:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Color a vertex depending on how many vertices it is connected to. <\/li>\n\n\n\n<li>Test the above on different meshes and see the results. You can access different kinds of mesh files from the &#8220;Input&#8221; folder in the RXMesh repo and add your own meshes if you&#8217;d like.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">By the end of this, you should be able to do the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Know how to create new subdirectories in the RXMesh Fork<\/li>\n\n\n\n<li>Know how to create your own <code>for_each <\/code>computations on the mesh<\/li>\n\n\n\n<li>Know how to create your own queries <\/li>\n\n\n\n<li>Be able to perform some basic geometric analyses&#8217; using RXMesh queries<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">References<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">More information can be found in <a href=\"https:\/\/github.com\/owensgroup\/RXMesh\">RXMesh GitHub Repository<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This blog was written by Sachin Kishan during the SGI 2024 Fellowship as one of the outcomes of a two week project under the mentorship of Ahmed Mahmoud and support of Supriya Gadi Patil as teaching assistant.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>An introduction to the RXMesh Library for GPU Geometry Processing<\/p>\n","protected":false},"author":1,"featured_media":856,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[37,39],"tags":[54,53,55,6],"ppma_author":[26],"class_list":["post-469","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-research","category-tutorials","tag-gpu","tag-rxmesh","tag-sgi","tag-sgi2024"],"authors":[{"term_id":26,"user_id":0,"is_guest":1,"slug":"cap-sachinkishan02","display_name":"sachinkishan02","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g","author_category":"","first_name":"","last_name":"","user_url":"","job_title":"","description":""}],"_links":{"self":[{"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/posts\/469","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/comments?post=469"}],"version-history":[{"count":9,"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/posts\/469\/revisions"}],"predecessor-version":[{"id":857,"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/posts\/469\/revisions\/857"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/media\/856"}],"wp:attachment":[{"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/media?parent=469"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/categories?post=469"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/tags?post=469"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/summergeometry.org\/sgi2024\/wp-json\/wp\/v2\/ppma_author?post=469"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}