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, 2014 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( -m_height/2.0f, m_radius * std::sin( angle), m_radius * std::cos( angle)); 096 else 097 return mi::Float32_3( m_height/2.0f, m_radius * std::sin( angle), m_radius * std::cos( angle)); 098 } 099 100 mi::Float32_3 normal( mi::Uint32 index) 101 { 102 if( index == 0) return mi::Float32_3( -1.0f, 0.0f, 0.0f); 103 if( index == 1) return mi::Float32_3( 1.0f, 0.0f, 0.0f); 104 105 mi::Float32 angle = static_cast<mi::Float32>( 2.0f * MI_PI * (index-2) / m_n); 106 return mi::Float32_3( 0.0f, std::sin( angle), std::cos( angle)); 107 } 108 109 private: 110 mi::Uint32 m_n; 111 mi::Float32 m_radius, m_height; 112 mi::Uint32* m_mesh_connectivity; 113 mi::Uint32* m_normal_connectivity; 114 mi::Uint32* m_offsets; 115 }; 116 117 // Create a cylinder with a regular N-gon as base and normal vectors. 118 mi::IPolygon_mesh* create_cylinder( mi::neuraylib::ITransaction* transaction, 119 mi::Uint32 n, mi::Float32 radius, mi::Float32 height) 120 { 121 Cylinder cylinder( n, radius, height); 122 123 // Create an empty polygon mesh 124 mi::IPolygon_mesh* mesh = transaction->create<mi::IPolygon_mesh>( "Polygon_mesh"); 125 check_success( mesh); 126 127 // Create a cylinder (points and polygons) 128 mesh->reserve_points( cylinder.num_points()); 129 for( mi::Uint32 i = 0; i < cylinder.num_points(); ++i) 130 mesh->append_point( cylinder.point( i)); 131 for( mi::Uint32 i = 0; i < cylinder.num_polys(); ++i) 132 mesh->add_polygon( cylinder.polygon_size( i)); 133 134 // Map vertices of the polygons to points 135 mi::base::Handle<mi::IPolygon_connectivity> mesh_connectivity( mesh->edit_mesh_connectivity()); 136 for( mi::Uint32 i = 0; i < cylinder.num_polys(); ++i) 137 mesh_connectivity->set_polygon_indices( 138 mi::Polygon_handle( i), cylinder.polygon_indices( i)); 139 check_success( mesh->attach_mesh_connectivity( mesh_connectivity.get()) == 0); 140 141 // Create a custom connectivity for normal vectors and map vertices to entries in the 142 // attribute vector 143 mi::base::Handle<mi::IPolygon_connectivity> normal_connectivity( 144 mesh->create_connectivity()); 145 for( mi::Uint32 i = 0; i < cylinder.num_polys(); ++i) 146 normal_connectivity->set_polygon_indices( 147 mi::Polygon_handle( i), cylinder.normal_indices( i)); 148 149 // Create an attribute vector for the normals 150 mi::base::Handle<mi::IAttribute_vector> normals( 151 normal_connectivity->create_attribute_vector( mi::ATTR_NORMAL)); 152 for( mi::Uint32 i = 0; i < cylinder.num_normals(); ++i) 153 normals->append_vector3( cylinder.normal( i)); 154 check_success( normals->is_valid_attribute()); 155 check_success( normal_connectivity->attach_attribute_vector( normals.get()) == 0); 156 check_success( !normals->is_valid_attribute()); 157 158 check_success( mesh->attach_connectivity( normal_connectivity.get()) == 0); 159 160 return mesh; 161 } 162 163 // Add a red cylinder as polygon mesh and a blue cylinder as tessellation of the red cylinder 164 void setup_scene( mi::neuraylib::ITransaction* transaction, const char* rootgroup) 165 { 166 mi::Float32 cylinder_radius = 0.6f; 167 mi::Float32 cylinder_height = 1.2f; 168 169 // Create the red cylinder 170 mi::base::Handle<mi::IPolygon_mesh> mesh_red( create_cylinder( 171 transaction, 23, cylinder_radius, cylinder_height)); 172 transaction->store( mesh_red.get(), "mesh_red"); 173 174 // Create the instance for the red cylinder 175 mi::base::Handle<mi::IInstance> instance( transaction->create<mi::IInstance>( "Instance")); 176 instance->attach( "mesh_red"); 177 178 // Set the transformation matrix, the visible attribute, and the material 179 mi::Float64_4_4 matrix( 1.0); 180 matrix.translate( -0.1, -cylinder_radius, 0.2); 181 matrix.rotate( 0.0, -0.5 * MI_PI_2, 0.0); 182 instance->set_matrix( matrix); 183 184 mi::base::Handle<mi::IBoolean> visible( 185 instance->create_attribute<mi::IBoolean>( "visible", "Boolean")); 186 visible->set_value( true); 187 188 mi::base::Handle<mi::IRef> material( instance->create_attribute<mi::IRef>( "material", "Ref")); 189 material->set_reference( "red_material"); 190 191 transaction->store( instance.get(), "instance_red"); 192 193 // And attach the instance to the root group 194 mi::base::Handle<mi::IGroup> group( transaction->edit<mi::IGroup>( rootgroup)); 195 group->attach( "instance_red"); 196 197 // Tessellate the polygon mesh of the red cylinder. 198 mi::base::Handle<mi::ITessellator> tessellator( 199 transaction->create<mi::ITessellator>( "Tessellator")); 200 mi::base::Handle<const mi::IPolygon_mesh> c_mesh_red( 201 transaction->access<mi::IPolygon_mesh>( "mesh_red")); 202 mi::base::Handle<mi::ITriangle_mesh> m_mesh_blue( tessellator->run( c_mesh_red.get())); 203 transaction->store( m_mesh_blue.get(), "mesh_blue"); 204 205 // Create the instance for the blue cylinder 206 instance = transaction->create<mi::IInstance>( "Instance"); 207 instance->attach( "mesh_blue"); 208 209 // Set the transformation matrix, the visible attribute, and the material 210 matrix = mi::Float64_4_4( 1.0); 211 matrix.translate( -1.1, -cylinder_radius, -1.1); 212 instance->set_matrix( matrix); 213 214 visible = instance->create_attribute<mi::IBoolean>( "visible", "Boolean"); 215 visible->set_value( true); 216 217 mi::base::Handle<mi::IRef> ref( instance->create_attribute<mi::IRef>( "material", "Ref")); 218 ref->set_reference( "blue_material"); 219 220 transaction->store( instance.get(), "instance_blue"); 221 222 // And attach the instance to the root group 223 group->attach( "instance_blue"); 224 } 225 226 void configuration( mi::base::Handle<mi::neuraylib::INeuray> neuray, const char* mdl_path) 227 { 228 // Configure the neuray library. Here we set the search path for .mdl files. 229 mi::base::Handle<mi::neuraylib::IRendering_configuration> rendering_configuration( 230 neuray->get_api_component<mi::neuraylib::IRendering_configuration>()); 231 check_success( rendering_configuration.is_valid_interface()); 232 check_success( rendering_configuration->add_mdl_path( mdl_path) == 0); 233 234 // Load the FreeImage, Iray Photoreal, and .mi importer plugins. 235 mi::base::Handle<mi::neuraylib::IPlugin_configuration> plugin_configuration( 236 neuray->get_api_component<mi::neuraylib::IPlugin_configuration>()); 237 #ifndef MI_PLATFORM_WINDOWS 238 check_success( plugin_configuration->load_plugin_library( "freeimage.so") == 0); 239 check_success( plugin_configuration->load_plugin_library( "libiray.so") == 0); 240 check_success( plugin_configuration->load_plugin_library( "mi_importer.so") == 0); 241 #else 242 check_success( plugin_configuration->load_plugin_library( "freeimage.dll") == 0); 243 check_success( plugin_configuration->load_plugin_library( "libiray.dll") == 0); 244 check_success( plugin_configuration->load_plugin_library( "mi_importer.dll") == 0); 245 #endif 246 } 247 248 void rendering( mi::base::Handle<mi::neuraylib::INeuray> neuray) 249 { 250 // Get the database, the global scope, which is the root for all transactions, 251 // and create a transaction for importing the scene file and storing the scene. 252 mi::base::Handle<mi::neuraylib::IDatabase> database( 253 neuray->get_api_component<mi::neuraylib::IDatabase>()); 254 check_success( database.is_valid_interface()); 255 mi::base::Handle<mi::neuraylib::IScope> scope( 256 database->get_global_scope()); 257 mi::base::Handle<mi::neuraylib::ITransaction> transaction( 258 scope->create_transaction()); 259 check_success( transaction.is_valid_interface()); 260 261 // Import the scene file 262 mi::base::Handle<mi::neuraylib::IImport_api> import_api( 263 neuray->get_api_component<mi::neuraylib::IImport_api>()); 264 check_success( import_api.is_valid_interface()); 265 mi::base::Handle<const mi::IImport_result> import_result( 266 import_api->import_elements( transaction.get(), "file:main.mi")); 267 check_success( import_result->get_error_number() == 0); 268 269 // Add a polygon mesh and a tessellated blue mesh to the scene 270 setup_scene( transaction.get(), import_result->get_rootgroup()); 271 272 // Create the scene object 273 mi::base::Handle<mi::neuraylib::IScene> scene( 274 transaction->create<mi::neuraylib::IScene>( "Scene")); 275 scene->set_rootgroup( import_result->get_rootgroup()); 276 scene->set_options( import_result->get_options()); 277 scene->set_camera_instance( import_result->get_camera_inst()); 278 transaction->store( scene.get(), "the_scene"); 279 280 // Create the render context using the Iray Photoreal render mode 281 scene = transaction->edit<mi::neuraylib::IScene>( "the_scene"); 282 mi::base::Handle<mi::neuraylib::IRender_context> render_context( 283 scene->create_render_context( transaction.get(), "iray")); 284 check_success( render_context.is_valid_interface()); 285 mi::base::Handle<mi::IString> scheduler_mode( transaction->create<mi::IString>()); 286 scheduler_mode->set_c_str( "batch"); 287 render_context->set_option( "scheduler_mode", scheduler_mode.get()); 288 scene = 0; 289 290 // Create the render target and render the scene 291 mi::base::Handle<mi::neuraylib::IImage_api> image_api( 292 neuray->get_api_component<mi::neuraylib::IImage_api>()); 293 mi::base::Handle<mi::neuraylib::IRender_target> render_target( 294 new Render_target( image_api.get(), "Color", 512, 384)); 295 check_success( render_context->render( transaction.get(), render_target.get(), 0) >= 0); 296 297 // Write the image to disk 298 mi::base::Handle<mi::neuraylib::IExport_api> export_api( 299 neuray->get_api_component<mi::neuraylib::IExport_api>()); 300 check_success( export_api.is_valid_interface()); 301 mi::base::Handle<mi::neuraylib::ICanvas> canvas( render_target->get_canvas( 0)); 302 export_api->export_canvas( "file:example_polygon_mesh.png", canvas.get()); 303 304 transaction->commit(); 305 } 306 307 int main( int argc, char* argv[]) 308 { 309 // Collect command line parameters 310 if( argc != 2) { 311 std::cerr << "Usage: example_polygon_mesh <mdl_path>" << std::endl; 312 keep_console_open(); 313 return EXIT_FAILURE; 314 } 315 const char* mdl_path = argv[1]; 316 317 // Access the neuray library 318 mi::base::Handle<mi::neuraylib::INeuray> neuray( load_and_get_ineuray()); 319 check_success( neuray.is_valid_interface()); 320 321 // Configure the neuray library 322 configuration( neuray, mdl_path); 323 324 // Start the neuray library 325 check_success( neuray->start() == 0); 326 327 // Do the actual rendering 328 rendering( neuray); 329 330 // Shut down the neuray library 331 check_success( neuray->shutdown() == 0); 332 neuray = 0; 333 334 // Unload the neuray library 335 check_success( unload()); 336 337 keep_console_open(); 338 return EXIT_SUCCESS; 339 }