Creating polygon meshes
This topic introduces:
- Core concepts about creating and tessellating polygon meshes:
- The program example_polygon_mesh.cpp, which serves as an example implementation of these concepts. This program imports a partial scene containing definitions for the camera, a light, and some geometry (a ground plane and a yellow cube). Using calls to the API, it creates a red cylinder as a polygon mesh and tessellates a copy of it.
Creating a polygon mesh
To create a polygon mesh, you need to specify at least the following:- Specify the points (the position of the vertices) of the polygon mesh
- Polygons (as point indices)
In example_polygon_mesh.cpp, create_cylinder() is used to create a cylinder with a regular N-gon as base. The actual data such as point coordinates and normal directions is not hardcoded, but computed by the helper class Cylinder and based on a few input parameters: radius, height, and parameter n of the N-gon.
To get a sharp edge for the top and base face, vertex normals are not specified per point, but per vertex. Hence, the mesh connectivity cannot be used. A custom connectivity must be used instead.
The vertices are mapped to entries in the attribute vector as follows:
- All vertices of the top face are mapped to the same normal
- All vertices of the base face are mapped to the same normal
- Each two vertices on the edge between two adjacent side faces share one normal
Tessellating a polygon mesh
The tessellator is a functor which tessellates a polygon mesh and returns the tessellated mesh as a triangle mesh. In example_polygon_mesh.cpp, the red cylinder is tessellated and the result is added to the scene (with a blue material). Currently, the tessellator does not support any options to control its behavior.
Adding a polygon mesh to a scene
The program example_polygon_mesh.cpp demonstrates how to add a polygon mesh to a scene graph. For more information, see Adding geometry to a scene.
example_polygon_mesh.cpp
001 /****************************************************************************** 002 * © 1986, 2016 NVIDIA Corporation. All rights reserved. 003 *****************************************************************************/ 004 005 // examples/example_polygon_mesh.cpp 006 // 007 // Creates and tessellates a polygon mesh. 008 // 009 // The example expects the following command line arguments: 010 // 011 // example_polygon_mesh <mdl_path> 012 // 013 // mdl_path path to the MDL modules, e.g., iray-<version>/mdl 014 // 015 // The rendered image is written to a file named "example_polygon_mesh.png". 016 017 #include <mi/neuraylib.h> 018 019 // Include code shared by all examples. 020 #include "example_shared.h" 021 // Include an implementation of IRender_target. 022 #include "example_render_target_simple.h" 023 024 #include <iostream> 025 026 // Helper class that represents a cylinder as a polygon mesh with a regular N-gon as base 027 class Cylinder 028 { 029 public: 030 Cylinder( mi::Uint32 n, mi::Float32 radius, mi::Float32 height) 031 : m_n( n), m_radius( radius), m_height( height) 032 { 033 m_mesh_connectivity = new mi::Uint32[ 2*n + 4*n]; 034 m_normal_connectivity = new mi::Uint32[ 2*n + 4*n]; 035 m_offsets = new mi::Uint32[ n + 2 + 1]; 036 037 // offsets to the first vertex index of each polygon 038 m_offsets[0] = 0; // base (n vertices) 039 m_offsets[1] = n; // top (n vertices) 040 for( mi::Uint32 i = 0; i <= n; ++i) 041 m_offsets[2+i] = 2*n + 4*i; // sides (4 vertices each) 042 043 // the mesh connectivity 044 mi::Uint32 index = 0; 045 for( mi::Uint32 i = 0; i < n; ++i) // base (first n even indices) 046 m_mesh_connectivity[index++] = 2*i; 047 for( mi::Uint32 i = 0; i < n; ++i) // top (first n odd indices) 048 m_mesh_connectivity[index++] = 2*i+1; 049 for( mi::Uint32 i = 0; i < n; ++i) { // sides (four subsequent indices) 050 m_mesh_connectivity[index++] = 2*i; 051 m_mesh_connectivity[index++] = (2*i + 2) % (2*n); 052 m_mesh_connectivity[index++] = (2*i + 3) % (2*n); 053 m_mesh_connectivity[index++] = (2*i + 1) % (2*n); 054 } 055 056 // the custom connectivity for normals 057 index = 0; 058 for( mi::Uint32 i = 0; i < n; ++i) // base (one constant normal) 059 m_normal_connectivity[index++] = 0; 060 for( mi::Uint32 i = 0; i < n; ++i) // top (one constant normal) 061 m_normal_connectivity[index++] = 1; 062 for( mi::Uint32 i = 0; i < n; ++i) { // sides (two normals each, shared 063 m_normal_connectivity[index++] = 2 + i; // with adjacent side face) 064 m_normal_connectivity[index++] = 2 + (i+1) % n; 065 m_normal_connectivity[index++] = 2 + (i+1) % n; 066 m_normal_connectivity[index++] = 2 + i; 067 } 068 } 069 070 ~Cylinder() 071 { 072 delete[] m_mesh_connectivity; 073 delete[] m_normal_connectivity; 074 delete[] m_offsets; 075 } 076 077 mi::Uint32 num_points() const { return m_n * 2; } 078 079 mi::Uint32 num_normals() const { return m_n + 2; } 080 081 mi::Uint32 num_polys() const { return m_n + 2; } 082 083 mi::Uint32 polygon_size( mi::Uint32 p) const { return m_offsets[p+1] - m_offsets[p]; } 084 085 mi::Uint32* polygon_indices( mi::Uint32 p) const { return &m_mesh_connectivity[m_offsets[p]]; } 086 087 mi::Uint32* normal_indices( mi::Uint32 p) const { return &m_normal_connectivity[m_offsets[p]]; } 088 089 mi::Float32_3 point( mi::Uint32 index) 090 { 091 mi::Uint32 i = index / 2; 092 mi::Float32 angle = static_cast<mi::Float32>( 2.0f * MI_PI * i / m_n); 093 094 if( index % 2 == 0) 095 return mi::Float32_3( 096 -m_height/2.0f, m_radius * std::sin( angle), m_radius * std::cos( angle)); 097 else 098 return mi::Float32_3( 099 m_height/2.0f, m_radius * std::sin( angle), m_radius * std::cos( angle)); 100 } 101 102 mi::Float32_3 normal( mi::Uint32 index) 103 { 104 if( index == 0) return mi::Float32_3( -1.0f, 0.0f, 0.0f); 105 if( index == 1) return mi::Float32_3( 1.0f, 0.0f, 0.0f); 106 107 mi::Float32 angle = static_cast<mi::Float32>( 2.0f * MI_PI * (index-2) / m_n); 108 return mi::Float32_3( 0.0f, std::sin( angle), std::cos( angle)); 109 } 110 111 private: 112 mi::Uint32 m_n; 113 mi::Float32 m_radius, m_height; 114 mi::Uint32* m_mesh_connectivity; 115 mi::Uint32* m_normal_connectivity; 116 mi::Uint32* m_offsets; 117 }; 118 119 // Create a cylinder with a regular N-gon as base and normal vectors. 120 mi::neuraylib::IPolygon_mesh* create_cylinder( mi::neuraylib::ITransaction* transaction, 121 mi::Uint32 n, mi::Float32 radius, mi::Float32 height) 122 { 123 Cylinder cylinder( n, radius, height); 124 125 // Create an empty polygon mesh 126 mi::neuraylib::IPolygon_mesh* mesh 127 = transaction->create<mi::neuraylib::IPolygon_mesh>( "Polygon_mesh"); 128 check_success( mesh); 129 130 // Create a cylinder (points and polygons) 131 mesh->reserve_points( cylinder.num_points()); 132 for( mi::Uint32 i = 0; i < cylinder.num_points(); ++i) 133 mesh->append_point( cylinder.point( i)); 134 for( mi::Uint32 i = 0; i < cylinder.num_polys(); ++i) 135 mesh->add_polygon( cylinder.polygon_size( i)); 136 137 // Map vertices of the polygons to points 138 mi::base::Handle<mi::neuraylib::IPolygon_connectivity> mesh_connectivity( 139 mesh->edit_mesh_connectivity()); 140 for( mi::Uint32 i = 0; i < cylinder.num_polys(); ++i) 141 mesh_connectivity->set_polygon_indices( 142 mi::neuraylib::Polygon_handle( i), cylinder.polygon_indices( i)); 143 check_success( mesh->attach_mesh_connectivity( mesh_connectivity.get()) == 0); 144 145 // Create a custom connectivity for normal vectors and map vertices to entries in the 146 // attribute vector 147 mi::base::Handle<mi::neuraylib::IPolygon_connectivity> normal_connectivity( 148 mesh->create_connectivity()); 149 for( mi::Uint32 i = 0; i < cylinder.num_polys(); ++i) 150 normal_connectivity->set_polygon_indices( 151 mi::neuraylib::Polygon_handle( i), cylinder.normal_indices( i)); 152 153 // Create an attribute vector for the normals 154 mi::base::Handle<mi::neuraylib::IAttribute_vector> normals( 155 normal_connectivity->create_attribute_vector( mi::neuraylib::ATTR_NORMAL)); 156 for( mi::Uint32 i = 0; i < cylinder.num_normals(); ++i) 157 normals->append_vector3( cylinder.normal( i)); 158 check_success( normals->is_valid_attribute()); 159 check_success( normal_connectivity->attach_attribute_vector( normals.get()) == 0); 160 check_success( !normals->is_valid_attribute()); 161 162 check_success( mesh->attach_connectivity( normal_connectivity.get()) == 0); 163 164 return mesh; 165 } 166 167 // Add a red cylinder as polygon mesh and a blue cylinder as tessellation of the red cylinder 168 void setup_scene( mi::neuraylib::ITransaction* transaction, const char* rootgroup) 169 { 170 mi::Float32 cylinder_radius = 0.6f; 171 mi::Float32 cylinder_height = 1.2f; 172 173 // Create the red cylinder 174 mi::base::Handle<mi::neuraylib::IPolygon_mesh> mesh_red( create_cylinder( 175 transaction, 23, cylinder_radius, cylinder_height)); 176 transaction->store( mesh_red.get(), "mesh_red"); 177 178 // Create the instance for the red cylinder 179 mi::base::Handle<mi::neuraylib::IInstance> instance( 180 transaction->create<mi::neuraylib::IInstance>( "Instance")); 181 instance->attach( "mesh_red"); 182 183 // Set the transformation matrix, the visible attribute, and the material 184 mi::Float64_4_4 matrix( 1.0); 185 matrix.translate( -0.1, -cylinder_radius, 0.2); 186 matrix.rotate( 0.0, -0.5 * MI_PI_2, 0.0); 187 instance->set_matrix( matrix); 188 189 mi::base::Handle<mi::IBoolean> visible( 190 instance->create_attribute<mi::IBoolean>( "visible", "Boolean")); 191 visible->set_value( true); 192 193 mi::base::Handle<mi::IRef> material( instance->create_attribute<mi::IRef>( "material", "Ref")); 194 material->set_reference( "red_material"); 195 196 transaction->store( instance.get(), "instance_red"); 197 198 // And attach the instance to the root group 199 mi::base::Handle<mi::neuraylib::IGroup> group( 200 transaction->edit<mi::neuraylib::IGroup>( rootgroup)); 201 group->attach( "instance_red"); 202 203 // Tessellate the polygon mesh of the red cylinder. 204 mi::base::Handle<mi::neuraylib::ITessellator> tessellator( 205 transaction->create<mi::neuraylib::ITessellator>( "Tessellator")); 206 mi::base::Handle<const mi::neuraylib::IPolygon_mesh> c_mesh_red( 207 transaction->access<mi::neuraylib::IPolygon_mesh>( "mesh_red")); 208 mi::base::Handle<mi::neuraylib::ITriangle_mesh> m_mesh_blue( 209 tessellator->run( c_mesh_red.get())); 210 transaction->store( m_mesh_blue.get(), "mesh_blue"); 211 212 // Create the instance for the blue cylinder 213 instance = transaction->create<mi::neuraylib::IInstance>( "Instance"); 214 instance->attach( "mesh_blue"); 215 216 // Set the transformation matrix, the visible attribute, and the material 217 matrix = mi::Float64_4_4( 1.0); 218 matrix.translate( -1.1, -cylinder_radius, -1.1); 219 instance->set_matrix( matrix); 220 221 visible = instance->create_attribute<mi::IBoolean>( "visible", "Boolean"); 222 visible->set_value( true); 223 224 mi::base::Handle<mi::IRef> ref( instance->create_attribute<mi::IRef>( "material", "Ref")); 225 ref->set_reference( "blue_material"); 226 227 transaction->store( instance.get(), "instance_blue"); 228 229 // And attach the instance to the root group 230 group->attach( "instance_blue"); 231 } 232 233 void configuration( mi::base::Handle<mi::neuraylib::INeuray> neuray, const char* mdl_path) 234 { 235 // Configure the neuray library. Here we set the search path for .mdl files. 236 mi::base::Handle<mi::neuraylib::IRendering_configuration> rendering_configuration( 237 neuray->get_api_component<mi::neuraylib::IRendering_configuration>()); 238 check_success( rendering_configuration.is_valid_interface()); 239 check_success( rendering_configuration->add_mdl_path( mdl_path) == 0); 240 241 // Load the FreeImage, Iray Photoreal, and .mi importer plugins. 242 mi::base::Handle<mi::neuraylib::IPlugin_configuration> plugin_configuration( 243 neuray->get_api_component<mi::neuraylib::IPlugin_configuration>()); 244 #ifndef MI_PLATFORM_WINDOWS 245 check_success( plugin_configuration->load_plugin_library( "freeimage.so") == 0); 246 check_success( plugin_configuration->load_plugin_library( "libiray.so") == 0); 247 check_success( plugin_configuration->load_plugin_library( "mi_importer.so") == 0); 248 #else 249 check_success( plugin_configuration->load_plugin_library( "freeimage.dll") == 0); 250 check_success( plugin_configuration->load_plugin_library( "libiray.dll") == 0); 251 check_success( plugin_configuration->load_plugin_library( "mi_importer.dll") == 0); 252 #endif 253 } 254 255 void rendering( mi::base::Handle<mi::neuraylib::INeuray> neuray) 256 { 257 // Get the database, the global scope of the database, and create a transaction in the global 258 // scope for importing the scene file and storing the scene. 259 mi::base::Handle<mi::neuraylib::IDatabase> database( 260 neuray->get_api_component<mi::neuraylib::IDatabase>()); 261 check_success( database.is_valid_interface()); 262 mi::base::Handle<mi::neuraylib::IScope> scope( 263 database->get_global_scope()); 264 mi::base::Handle<mi::neuraylib::ITransaction> transaction( 265 scope->create_transaction()); 266 check_success( transaction.is_valid_interface()); 267 268 // Import the scene file 269 mi::base::Handle<mi::neuraylib::IImport_api> import_api( 270 neuray->get_api_component<mi::neuraylib::IImport_api>()); 271 check_success( import_api.is_valid_interface()); 272 mi::base::Handle<const mi::neuraylib::IImport_result> import_result( 273 import_api->import_elements( transaction.get(), "file:main.mi")); 274 check_success( import_result->get_error_number() == 0); 275 276 // Add a polygon mesh and a tessellated blue mesh to the scene 277 setup_scene( transaction.get(), import_result->get_rootgroup()); 278 279 // Create the scene object 280 mi::base::Handle<mi::neuraylib::IScene> scene( 281 transaction->create<mi::neuraylib::IScene>( "Scene")); 282 scene->set_rootgroup( import_result->get_rootgroup()); 283 scene->set_options( import_result->get_options()); 284 scene->set_camera_instance( import_result->get_camera_inst()); 285 transaction->store( scene.get(), "the_scene"); 286 287 // Create the render context using the Iray Photoreal render mode 288 scene = transaction->edit<mi::neuraylib::IScene>( "the_scene"); 289 mi::base::Handle<mi::neuraylib::IRender_context> render_context( 290 scene->create_render_context( transaction.get(), "iray")); 291 check_success( render_context.is_valid_interface()); 292 mi::base::Handle<mi::IString> scheduler_mode( transaction->create<mi::IString>()); 293 scheduler_mode->set_c_str( "batch"); 294 render_context->set_option( "scheduler_mode", scheduler_mode.get()); 295 scene = 0; 296 297 // Create the render target and render the scene 298 mi::base::Handle<mi::neuraylib::IImage_api> image_api( 299 neuray->get_api_component<mi::neuraylib::IImage_api>()); 300 mi::base::Handle<mi::neuraylib::IRender_target> render_target( 301 new Render_target( image_api.get(), "Color", 512, 384)); 302 check_success( render_context->render( transaction.get(), render_target.get(), 0) >= 0); 303 304 // Write the image to disk 305 mi::base::Handle<mi::neuraylib::IExport_api> export_api( 306 neuray->get_api_component<mi::neuraylib::IExport_api>()); 307 check_success( export_api.is_valid_interface()); 308 mi::base::Handle<mi::neuraylib::ICanvas> canvas( render_target->get_canvas( 0)); 309 export_api->export_canvas( "file:example_polygon_mesh.png", canvas.get()); 310 311 transaction->commit(); 312 } 313 314 int main( int argc, char* argv[]) 315 { 316 // Collect command line parameters 317 if( argc != 2) { 318 std::cerr << "Usage: example_polygon_mesh <mdl_path>" << std::endl; 319 keep_console_open(); 320 return EXIT_FAILURE; 321 } 322 const char* mdl_path = argv[1]; 323 324 // Access the neuray library 325 mi::base::Handle<mi::neuraylib::INeuray> neuray( load_and_get_ineuray()); 326 check_success( neuray.is_valid_interface()); 327 328 // Configure the neuray library 329 configuration( neuray, mdl_path); 330 331 // Start the neuray library 332 mi::Sint32 result = neuray->start(); 333 check_start_success( result); 334 335 // Do the actual rendering 336 rendering( neuray); 337 338 // Shut down the neuray library 339 check_success( neuray->shutdown() == 0); 340 neuray = 0; 341 342 // Unload the neuray library 343 check_success( unload()); 344 345 keep_console_open(); 346 return EXIT_SUCCESS; 347 }