Iray Programmer's Manual

Creating subdivision surfaces

This topic introduces:

  1. Core concepts about the control mesh of a subdivision surface and the instancing of objects:
  2. 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 }