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, 2014 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::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::ISubdivision_surface* mesh 052 = transaction->create<mi::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::IPolygon_connectivity> mesh_connectivity( mesh->edit_mesh_connectivity()); 063 for( mi::Uint32 i = 0; i < n_quads; ++i) 064 mesh_connectivity->set_polygon_indices( mi::Polygon_handle( i), cube_quads[i]); 065 check_success( mesh->attach_mesh_connectivity( mesh_connectivity.get()) == 0); 066 067 // Set crease values to 1.0f on two opposing faces to end up with a cylinder 068 mi::Float32 crease_values[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; 069 mesh->set_crease_values( mi::Polygon_handle( 1), crease_values); 070 mesh->set_crease_values( mi::Polygon_handle( 3), crease_values); 071 072 return mesh; 073 } 074 075 // Add three instances of a subdivision surface with different approximation levels 076 void setup_scene( mi::neuraylib::ITransaction* transaction, const char* rootgroup) 077 { 078 // Remove the existing cube from the scene. The unrefined control mesh (in red) will take its 079 // place. 080 mi::base::Handle<mi::IGroup> group( transaction->edit<mi::IGroup>( rootgroup)); 081 group->detach( "cube_instance"); 082 group = 0; 083 084 // Create the subdivision surface 085 mi::base::Handle<mi::ISubdivision_surface> mesh( create_cube( transaction)); 086 transaction->store( mesh.get(), "mesh"); 087 088 // Create three instances of the subdivision surface with different materials, approximation 089 // levels and world-to-object transformation matrices. 090 const char* names[3] = { "instance0", "instance1", "instance2" }; 091 const char* materials[3] = { "red_material", "green_material", "blue_material" }; 092 mi::Float32 approximation_level[3] = { 0.0f, 1.0f, 2.0f }; 093 const mi::Float32_3 translation[3] = { 094 mi::Float32_3( 1.1f, -0.5f, 0.9f), 095 mi::Float32_3( -0.9f, -0.5f, 0.9f), 096 mi::Float32_3( -0.9f, -0.5f, -1.1f) 097 }; 098 099 for( mi::Size i = 0; i < 3; ++i) { 100 101 mi::base::Handle<mi::IInstance> instance( transaction->create<mi::IInstance>( "Instance")); 102 instance->attach( "mesh"); 103 104 mi::Float64_4_4 matrix( 1.0); 105 matrix.translate( translation[i]); 106 matrix.rotate( 0.0, 0.25 * MI_PI_2, 0.0); 107 instance->set_matrix( matrix); 108 109 mi::base::Handle<mi::IBoolean> visible( 110 instance->create_attribute<mi::IBoolean>( "visible", "Boolean")); 111 visible->set_value( true); 112 113 mi::base::Handle<mi::IRef> material( 114 instance->create_attribute<mi::IRef>( "material", "Ref")); 115 material->set_reference( materials[i]); 116 117 mi::base::Handle<mi::IStructure> approx( 118 instance->create_attribute<mi::IStructure>( "approx", "Approx")); 119 mi::base::Handle<mi::ISint8> method( approx->get_value<mi::ISint8>( "method")); 120 method->set_value( 0); 121 mi::base::Handle<mi::IFloat32> const_u( approx->get_value<mi::IFloat32>( "const_u")); 122 const_u->set_value( approximation_level[i]); 123 124 transaction->store( instance.get(), names[i]); 125 126 mi::base::Handle<mi::IGroup> group( transaction->edit<mi::IGroup>( rootgroup)); 127 group->attach( names[i]); 128 } 129 } 130 131 void configuration( mi::base::Handle<mi::neuraylib::INeuray> neuray, const char* mdl_path) 132 { 133 // Configure the neuray library. Here we set the search path for .mdl files. 134 mi::base::Handle<mi::neuraylib::IRendering_configuration> rendering_configuration( 135 neuray->get_api_component<mi::neuraylib::IRendering_configuration>()); 136 check_success( rendering_configuration.is_valid_interface()); 137 check_success( rendering_configuration->add_mdl_path( mdl_path) == 0); 138 139 // Load the FreeImage, Iray Photoreal, and .mi importer plugins. 140 mi::base::Handle<mi::neuraylib::IPlugin_configuration> plugin_configuration( 141 neuray->get_api_component<mi::neuraylib::IPlugin_configuration>()); 142 #ifndef MI_PLATFORM_WINDOWS 143 check_success( plugin_configuration->load_plugin_library( "freeimage.so") == 0); 144 check_success( plugin_configuration->load_plugin_library( "libiray.so") == 0); 145 check_success( plugin_configuration->load_plugin_library( "mi_importer.so") == 0); 146 #else 147 check_success( plugin_configuration->load_plugin_library( "freeimage.dll") == 0); 148 check_success( plugin_configuration->load_plugin_library( "libiray.dll") == 0); 149 check_success( plugin_configuration->load_plugin_library( "mi_importer.dll") == 0); 150 #endif 151 } 152 153 void rendering( mi::base::Handle<mi::neuraylib::INeuray> neuray) 154 { 155 // Get the database, the global scope, which is the root for all transactions, 156 // and create a transaction for importing the scene file and storing the scene. 157 mi::base::Handle<mi::neuraylib::IDatabase> database( 158 neuray->get_api_component<mi::neuraylib::IDatabase>()); 159 check_success( database.is_valid_interface()); 160 mi::base::Handle<mi::neuraylib::IScope> scope( 161 database->get_global_scope()); 162 mi::base::Handle<mi::neuraylib::ITransaction> transaction( 163 scope->create_transaction()); 164 check_success( transaction.is_valid_interface()); 165 166 // Import the scene file 167 mi::base::Handle<mi::neuraylib::IImport_api> import_api( 168 neuray->get_api_component<mi::neuraylib::IImport_api>()); 169 check_success( import_api.is_valid_interface()); 170 mi::base::Handle<const mi::IImport_result> import_result( 171 import_api->import_elements( transaction.get(), "file:main.mi")); 172 check_success( import_result->get_error_number() == 0); 173 174 // Add three instances of a subdivision surface with different approximation levels 175 setup_scene( transaction.get(), import_result->get_rootgroup()); 176 177 // Create the scene object 178 mi::base::Handle<mi::neuraylib::IScene> scene( 179 transaction->create<mi::neuraylib::IScene>( "Scene")); 180 scene->set_rootgroup( import_result->get_rootgroup()); 181 scene->set_options( import_result->get_options()); 182 scene->set_camera_instance( import_result->get_camera_inst()); 183 transaction->store( scene.get(), "the_scene"); 184 185 // Create the render context using the Iray Photoreal render mode 186 scene = transaction->edit<mi::neuraylib::IScene>( "the_scene"); 187 mi::base::Handle<mi::neuraylib::IRender_context> render_context( 188 scene->create_render_context( transaction.get(), "iray")); 189 check_success( render_context.is_valid_interface()); 190 mi::base::Handle<mi::IString> scheduler_mode( transaction->create<mi::IString>()); 191 scheduler_mode->set_c_str( "batch"); 192 render_context->set_option( "scheduler_mode", scheduler_mode.get()); 193 scene = 0; 194 195 // Create the render target and render the scene 196 mi::base::Handle<mi::neuraylib::IImage_api> image_api( 197 neuray->get_api_component<mi::neuraylib::IImage_api>()); 198 mi::base::Handle<mi::neuraylib::IRender_target> render_target( 199 new Render_target( image_api.get(), "Color", 512, 384)); 200 check_success( render_context->render( transaction.get(), render_target.get(), 0) >= 0); 201 202 // Write the image to disk 203 mi::base::Handle<mi::neuraylib::IExport_api> export_api( 204 neuray->get_api_component<mi::neuraylib::IExport_api>()); 205 check_success( export_api.is_valid_interface()); 206 mi::base::Handle<mi::neuraylib::ICanvas> canvas( render_target->get_canvas( 0)); 207 export_api->export_canvas( "file:example_subdivision_surface.png", canvas.get()); 208 209 transaction->commit(); 210 } 211 212 int main( int argc, char* argv[]) 213 { 214 // Collect command line parameters 215 if( argc != 2) { 216 std::cerr << "Usage: example_subdivision_surface <mdl_path>" << std::endl; 217 keep_console_open(); 218 return EXIT_FAILURE; 219 } 220 const char* mdl_path = argv[1]; 221 222 // Access the neuray library 223 mi::base::Handle<mi::neuraylib::INeuray> neuray( load_and_get_ineuray()); 224 check_success( neuray.is_valid_interface()); 225 226 // Configure the neuray library 227 configuration( neuray, mdl_path); 228 229 // Start the neuray library 230 check_success( neuray->start() == 0); 231 232 // Do the actual rendering 233 rendering( neuray); 234 235 // Shut down the neuray library 236 check_success( neuray->shutdown() == 0); 237 neuray = 0; 238 239 // Unload the neuray library 240 check_success( unload()); 241 242 keep_console_open(); 243 return EXIT_SUCCESS; 244 }