Creating subdivision surfaces
This topic introduces:
- Core concepts about the control mesh of a subdivision surface and the instancing of objects:
- The program example_subdivision_surface.cpp, which serves as an example implementation. The program imports a partial scene containing definitions for the camera, a light, and some geometry (a ground plane and a yellow cube). Using the API, it then creates a subdivision surface and three instances of it with different approximation levels.
The control mesh
The control mesh of the subdivision surface is exposed as a polygon mesh with the limitation that only triangles and quads are supported (see the example for polygon meshes). In example_subdivision_surface.cpp, a simple cube is created. Additionally, the crease values of the edges of two opposing faces are set to 1.0, which creates sharp edges in the limit surface (which is a cylinder). Without the crease values the limit surface would be a sphere.
Instancing objects
The procedure to add the subdivision element to the scene database and the scene graph is similar to the procedure used for the tetrahedron and the cylinder in previous examples.
In the earlier examples, one instance of the element was created. In example_subdivision_surface.cpp, three instances of the subdivision element are created. Multiple identical instances share the same geometry. Each instance has its own transformation matrix and each instance can have its own attributes. To show the subdivision process in example_subdivision_surface.cpp, different approximation levels are used for each instance.
example_subdivision_surface.cpp
001 /****************************************************************************** 002 * © 1986, 2016 NVIDIA Corporation. All rights reserved. 003 *****************************************************************************/ 004 005 // examples/example_subdivision_surface.cpp 006 // 007 // Creates three instances of a subdivision surface with different approximation levels. 008 // 009 // The example expects the following command line arguments: 010 // 011 // example_subdivision_surface <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_subdivision_surface.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 // Create a subdivision surface with a cube as control mesh. 027 mi::neuraylib::ISubdivision_surface* create_cube( mi::neuraylib::ITransaction* transaction) 028 { 029 const mi::Size n_points = 8; 030 const mi::Size n_quads = 6; 031 032 mi::Float32_3 cube_points[n_points] = { 033 mi::Float32_3( -0.5, -0.5, -0.5), 034 mi::Float32_3( 0.5, -0.5, -0.5), 035 mi::Float32_3( 0.5, 0.5, -0.5), 036 mi::Float32_3( -0.5, 0.5, -0.5), 037 mi::Float32_3( -0.5, -0.5, 0.5), 038 mi::Float32_3( 0.5, -0.5, 0.5), 039 mi::Float32_3( 0.5, 0.5, 0.5), 040 mi::Float32_3( -0.5, 0.5, 0.5) }; 041 042 mi::Uint32 cube_quads[n_quads][4] = { 043 { 0, 1, 5, 4 }, 044 { 4, 5, 6, 7 }, 045 { 7, 6, 2, 3 }, 046 { 3, 2, 1, 0 }, 047 { 1, 2, 6, 5 }, 048 { 3, 0, 4, 7 } }; 049 050 // Create an empty subdivision surface 051 mi::neuraylib::ISubdivision_surface* mesh 052 = transaction->create<mi::neuraylib::ISubdivision_surface>( "Subdivision_surface"); 053 054 // Create a cube (points and polygons) 055 mesh->reserve_points( n_points); 056 for( mi::Uint32 i = 0; i < n_points; ++i) 057 mesh->append_point( cube_points[i]); 058 for( mi::Uint32 i = 0; i < n_quads; ++i) 059 mesh->add_polygon( 4); 060 061 // Map vertices of the polygons to points 062 mi::base::Handle<mi::neuraylib::IPolygon_connectivity> mesh_connectivity( 063 mesh->edit_mesh_connectivity()); 064 for( mi::Uint32 i = 0; i < n_quads; ++i) 065 mesh_connectivity->set_polygon_indices( mi::neuraylib::Polygon_handle( i), cube_quads[i]); 066 check_success( mesh->attach_mesh_connectivity( mesh_connectivity.get()) == 0); 067 068 // Set crease values to 1.0f on two opposing faces to end up with a cylinder 069 mi::Float32 crease_values[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; 070 mesh->set_crease_values( mi::neuraylib::Polygon_handle( 1), crease_values); 071 mesh->set_crease_values( mi::neuraylib::Polygon_handle( 3), crease_values); 072 073 return mesh; 074 } 075 076 // Add three instances of a subdivision surface with different approximation levels 077 void setup_scene( mi::neuraylib::ITransaction* transaction, const char* rootgroup) 078 { 079 // Remove the existing cube from the scene. The unrefined control mesh (in red) will take its 080 // place. 081 mi::base::Handle<mi::neuraylib::IGroup> group( 082 transaction->edit<mi::neuraylib::IGroup>( rootgroup)); 083 group->detach( "cube_instance"); 084 group = 0; 085 086 // Create the subdivision surface 087 mi::base::Handle<mi::neuraylib::ISubdivision_surface> mesh( create_cube( transaction)); 088 transaction->store( mesh.get(), "mesh"); 089 090 // Create three instances of the subdivision surface with different materials, approximation 091 // levels and world-to-object transformation matrices. 092 const char* names[3] = { "instance0", "instance1", "instance2" }; 093 const char* materials[3] = { "red_material", "green_material", "blue_material" }; 094 mi::Float32 approximation_level[3] = { 0.0f, 1.0f, 2.0f }; 095 const mi::Float32_3 translation[3] = { 096 mi::Float32_3( 1.1f, -0.5f, 0.9f), 097 mi::Float32_3( -0.9f, -0.5f, 0.9f), 098 mi::Float32_3( -0.9f, -0.5f, -1.1f) 099 }; 100 101 for( mi::Size i = 0; i < 3; ++i) { 102 103 mi::base::Handle<mi::neuraylib::IInstance> instance( 104 transaction->create<mi::neuraylib::IInstance>( "Instance")); 105 instance->attach( "mesh"); 106 107 mi::Float64_4_4 matrix( 1.0); 108 matrix.translate( translation[i]); 109 matrix.rotate( 0.0, 0.25 * MI_PI_2, 0.0); 110 instance->set_matrix( matrix); 111 112 mi::base::Handle<mi::IBoolean> visible( 113 instance->create_attribute<mi::IBoolean>( "visible", "Boolean")); 114 visible->set_value( true); 115 116 mi::base::Handle<mi::IRef> material( 117 instance->create_attribute<mi::IRef>( "material", "Ref")); 118 material->set_reference( materials[i]); 119 120 mi::base::Handle<mi::IStructure> approx( 121 instance->create_attribute<mi::IStructure>( "approx", "Approx")); 122 mi::base::Handle<mi::ISint8> method( approx->get_value<mi::ISint8>( "method")); 123 method->set_value( 0); 124 mi::base::Handle<mi::IFloat32> const_u( approx->get_value<mi::IFloat32>( "const_u")); 125 const_u->set_value( approximation_level[i]); 126 127 transaction->store( instance.get(), names[i]); 128 129 mi::base::Handle<mi::neuraylib::IGroup> group( 130 transaction->edit<mi::neuraylib::IGroup>( rootgroup)); 131 group->attach( names[i]); 132 } 133 } 134 135 void configuration( mi::base::Handle<mi::neuraylib::INeuray> neuray, const char* mdl_path) 136 { 137 // Configure the neuray library. Here we set the search path for .mdl files. 138 mi::base::Handle<mi::neuraylib::IRendering_configuration> rendering_configuration( 139 neuray->get_api_component<mi::neuraylib::IRendering_configuration>()); 140 check_success( rendering_configuration.is_valid_interface()); 141 check_success( rendering_configuration->add_mdl_path( mdl_path) == 0); 142 143 // Load the FreeImage, Iray Photoreal, and .mi importer plugins. 144 mi::base::Handle<mi::neuraylib::IPlugin_configuration> plugin_configuration( 145 neuray->get_api_component<mi::neuraylib::IPlugin_configuration>()); 146 #ifndef MI_PLATFORM_WINDOWS 147 check_success( plugin_configuration->load_plugin_library( "freeimage.so") == 0); 148 check_success( plugin_configuration->load_plugin_library( "libiray.so") == 0); 149 check_success( plugin_configuration->load_plugin_library( "mi_importer.so") == 0); 150 #else 151 check_success( plugin_configuration->load_plugin_library( "freeimage.dll") == 0); 152 check_success( plugin_configuration->load_plugin_library( "libiray.dll") == 0); 153 check_success( plugin_configuration->load_plugin_library( "mi_importer.dll") == 0); 154 #endif 155 } 156 157 void rendering( mi::base::Handle<mi::neuraylib::INeuray> neuray) 158 { 159 // Get the database, the global scope of the database, and create a transaction in the global 160 // scope for importing the scene file and storing the scene. 161 mi::base::Handle<mi::neuraylib::IDatabase> database( 162 neuray->get_api_component<mi::neuraylib::IDatabase>()); 163 check_success( database.is_valid_interface()); 164 mi::base::Handle<mi::neuraylib::IScope> scope( 165 database->get_global_scope()); 166 mi::base::Handle<mi::neuraylib::ITransaction> transaction( 167 scope->create_transaction()); 168 check_success( transaction.is_valid_interface()); 169 170 // Import the scene file 171 mi::base::Handle<mi::neuraylib::IImport_api> import_api( 172 neuray->get_api_component<mi::neuraylib::IImport_api>()); 173 check_success( import_api.is_valid_interface()); 174 mi::base::Handle<const mi::neuraylib::IImport_result> import_result( 175 import_api->import_elements( transaction.get(), "file:main.mi")); 176 check_success( import_result->get_error_number() == 0); 177 178 // Add three instances of a subdivision surface with different approximation levels 179 setup_scene( transaction.get(), import_result->get_rootgroup()); 180 181 // Create the scene object 182 mi::base::Handle<mi::neuraylib::IScene> scene( 183 transaction->create<mi::neuraylib::IScene>( "Scene")); 184 scene->set_rootgroup( import_result->get_rootgroup()); 185 scene->set_options( import_result->get_options()); 186 scene->set_camera_instance( import_result->get_camera_inst()); 187 transaction->store( scene.get(), "the_scene"); 188 189 // Create the render context using the Iray Photoreal render mode 190 scene = transaction->edit<mi::neuraylib::IScene>( "the_scene"); 191 mi::base::Handle<mi::neuraylib::IRender_context> render_context( 192 scene->create_render_context( transaction.get(), "iray")); 193 check_success( render_context.is_valid_interface()); 194 mi::base::Handle<mi::IString> scheduler_mode( transaction->create<mi::IString>()); 195 scheduler_mode->set_c_str( "batch"); 196 render_context->set_option( "scheduler_mode", scheduler_mode.get()); 197 scene = 0; 198 199 // Create the render target and render the scene 200 mi::base::Handle<mi::neuraylib::IImage_api> image_api( 201 neuray->get_api_component<mi::neuraylib::IImage_api>()); 202 mi::base::Handle<mi::neuraylib::IRender_target> render_target( 203 new Render_target( image_api.get(), "Color", 512, 384)); 204 check_success( render_context->render( transaction.get(), render_target.get(), 0) >= 0); 205 206 // Write the image to disk 207 mi::base::Handle<mi::neuraylib::IExport_api> export_api( 208 neuray->get_api_component<mi::neuraylib::IExport_api>()); 209 check_success( export_api.is_valid_interface()); 210 mi::base::Handle<mi::neuraylib::ICanvas> canvas( render_target->get_canvas( 0)); 211 export_api->export_canvas( "file:example_subdivision_surface.png", canvas.get()); 212 213 transaction->commit(); 214 } 215 216 int main( int argc, char* argv[]) 217 { 218 // Collect command line parameters 219 if( argc != 2) { 220 std::cerr << "Usage: example_subdivision_surface <mdl_path>" << std::endl; 221 keep_console_open(); 222 return EXIT_FAILURE; 223 } 224 const char* mdl_path = argv[1]; 225 226 // Access the neuray library 227 mi::base::Handle<mi::neuraylib::INeuray> neuray( load_and_get_ineuray()); 228 check_success( neuray.is_valid_interface()); 229 230 // Configure the neuray library 231 configuration( neuray, mdl_path); 232 233 // Start the neuray library 234 mi::Sint32 result = neuray->start(); 235 check_start_success( result); 236 237 // Do the actual rendering 238 rendering( neuray); 239 240 // Shut down the neuray library 241 check_success( neuray->shutdown() == 0); 242 neuray = 0; 243 244 // Unload the neuray library 245 check_success( unload()); 246 247 keep_console_open(); 248 return EXIT_SUCCESS; 249 }