neuray API Programmer's Manual

Example for Subdivision surfaces

[Previous] [Next] [Up]

This example imports a partial scene containing definitions for the camera, a light, and some geometry (a ground plane and a yellow cube). It then creates via the API a subdivision surface and three instances of it with different approximation levels.

New Topics

  • Creation of subdivision surfaces

  • Instancing of objects

Detailed Description

Creation of subdivision surfaces

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 previous example for polygon meshes). In this example we create a simple cube. In addition the crease values of the edges of two opposing faces are set to 1.0. This will result in sharp edges in the limit surface which is a cylinder (without the crease values the limit surface would be a sphere) .

Instancing of objects

The subdivision is included in the scene similar as the tetrahedron and the cylinder in the previous examples for triangle and polygon meshes. But in contrast, not only a single instance, but three instances are used in this example.

Instances allow to share the geometry of multiple identical objects. Each instance has its own transformation matrix. Furthermore, each instance can have its own attributes. In our example, we use different approximation levels for each instance to show the subdivision process.

Example Source

Source Code Location: examples/example_subdivision_surface.cpp

‎/******************************************************************************
 * Copyright 1986, 2011 NVIDIA Corporation. All rights reserved.
 *****************************************************************************/

// examples/example_subdivision_surface.cpp
//
// Creates a three instances of a subdivision surface with different approximation levels.

#include <mi/neuraylib.h>

// Include code shared by all examples.
#include "example_shared.h"
// Include an implementation of ITile, ICanvas, and IRender_target.
#include "example_render_target.h"

#include <iostream>

// Create a subdivision surface with a cube as control mesh.
INVALID_DOXYREFmi::ISubdivision_surface* create_cube( mi::neuraylib::ITransaction* transaction)
{
    const mi::Size n_points = 8;
    const mi::Size n_quads = 6;

    mi::Float32_3 cube_points[n_points] = {
        mi::Float32_3( -0.5, -0.5, -0.5),
        mi::Float32_3(  0.5, -0.5, -0.5),
        mi::Float32_3(  0.5,  0.5, -0.5),
        mi::Float32_3( -0.5,  0.5, -0.5),
        mi::Float32_3( -0.5, -0.5,  0.5),
        mi::Float32_3(  0.5, -0.5,  0.5),
        mi::Float32_3(  0.5,  0.5,  0.5),
        mi::Float32_3( -0.5,  0.5,  0.5) };

    mi::Uint32 cube_quads[n_quads][4] = {
        { 0, 1, 5, 4 },
        { 4, 5, 6, 7 },
        { 7, 6, 2, 3 },
        { 3, 2, 1, 0 },
        { 1, 2, 6, 5 },
        { 3, 0, 4, 7 } };

    // Create an empty subdivision surface
    INVALID_DOXYREFmi::ISubdivision_surface* mesh
        = transaction->create<INVALID_DOXYREFmi::ISubdivision_surface>( "Subdivision_surface");

    // Create a cube (points and polygons)
    mesh->INVALID_DOXYREF( n_points);
    for( mi::Uint32 i = 0; i < n_points; ++i)
        mesh->INVALID_DOXYREF( cube_points[i]);
    for( mi::Uint32 i = 0; i < n_quads; ++i)
        mesh->INVALID_DOXYREF( 4);

    // Map vertices of the polygons to points
    mi::base::Handle< mi::IPolygon_connectivity> mesh_connectivity( mesh->INVALID_DOXYREF());
    for( mi::Uint32 i = 0; i < n_quads; ++i)
        mesh_connectivity->set_polygon_indices( INVALID_DOXYREFmi::Polygon_handle( i), cube_quads[i]);
    check_success( mesh->INVALID_DOXYREF( mesh_connectivity.get()) == 0);

    // Set crease values to 1.0f on two opposing faces to end up with a cylinder
    mi::Float32 crease_values[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
    mesh->INVALID_DOXYREF( INVALID_DOXYREFmi::Polygon_handle( 1), crease_values);
    mesh->INVALID_DOXYREF( INVALID_DOXYREFmi::Polygon_handle( 3), crease_values);

    return mesh;
}

// Add three instances of a subdivision surface with different approximation levels
void setup_scene( mi::neuraylib::ITransaction* transaction, const char* rootgroup)
{
    // Remove the existing cube from the scene. The unrefined control mesh (in red) will take its
    // place.
    mi::base::Handle< mi::IGroup> group( transaction->edit<INVALID_DOXYREFmi::IGroup>( rootgroup));
    group->detach( "cube1_inst");
    group = 0;

    // Create the subdivision surface
    mi::base::Handle< mi::ISubdivision_surface> mesh( create_cube(transaction));
    transaction->store( mesh.get(), "mesh");

    // Create three instances of the subdivision surface with different materials, approximation
    // levels and world-to-object transformation matrices.
    const char* names[3]               = { "instance0", "instance1", "instance2" };
    const char* materials[3]           = { "redSG", "greenSG", "blueSG" };
    mi::Float32 approximation_level[3] = { 0.0f, 1.0f, 2.0f };
    const mi::Float32_3 translation[3] = {
        mi::Float32_3(  0.5f, 0.0f,  1.5f),
        mi::Float32_3( -1.5f, 0.0f,  1.5f),
        mi::Float32_3( -1.5f, 0.0f, -0.5f)
    };

    for( mi::Size i = 0; i < 3; ++i) {

        mi::base::Handle< mi::IInstance> instance( transaction->create<INVALID_DOXYREFmi::IInstance>( "Instance"));
        instance->attach( "mesh");

        mi::Float64_4_4 matrix( 1.0);
        matrix.translate( translation[i]);
        matrix.rotate( 0.0, 0.25 * MI_PI_2, 0.0);
        instance->set_world_to_obj( matrix);

        mi::base::Handle< mi::IBoolean> visible(
            instance->create_attribute<mi::IBoolean>( "visible", "Boolean"));
        visible->set_value( true);

        mi::base::Handle< mi::IRef> material(
            instance->create_attribute<mi::IRef>( "material", "Ref"));
        material->set_reference( materials[i]);

        mi::base::Handle< mi::IStructure> approx(
            instance->create_attribute<mi::IStructure>( "approx", "Approx"));
        mi::base::Handle< mi::ISint8> method( approx->get_value<mi::ISint8>( "method"));
        method->set_value( 0);
        mi::base::Handle< mi::IFloat32> const_u( approx->get_value<mi::IFloat32>( "const_u"));
        const_u->set_value( approximation_level[i]);

        transaction->store( instance.get(), names[i]);

        mi::base::Handle< mi::IGroup> group( transaction->edit<INVALID_DOXYREFmi::IGroup>( rootgroup));
        group->attach( names[i]);
    }
}

void configuration( mi::base::Handle< mi::neuraylib::INeuray> neuray, const char* shader_path)
{
    // Configure the neuray library. Here we set some paths needed by the renderer.
    mi::base::Handle< mi::neuraylib::IRendering_configuration> rendering_configuration(
        neuray->get_api_component<mi::neuraylib::IRendering_configuration>());
    check_success( rendering_configuration.is_valid_interface());
    check_success( rendering_configuration->add_shader_path( shader_path) == 0);

    // Load the FreeImage image plugin and the LLVM backend for MetaSL.
    mi::base::Handle< mi::neuraylib::IPlugin_configuration> plugin_configuration(
        neuray->get_api_component<mi::neuraylib::IPlugin_configuration>());
#ifndef MI_PLATFORM_WINDOWS
    check_success( plugin_configuration->load_plugin_library( "freeimage.so") == 0);
    check_success( plugin_configuration->load_plugin_library( "gen_llvm.so") == 0);
#else
    check_success( plugin_configuration->load_plugin_library( "freeimage.dll") == 0);
    check_success( plugin_configuration->load_plugin_library( "gen_llvm.dll") == 0);
#endif
}

void rendering( mi::base::Handle< mi::neuraylib::INeuray> neuray)
{
    // Get the database, the global scope, which is the root for all transactions,
    // and create a transaction for importing the scene file and storing the scene.
    mi::base::Handle< mi::neuraylib::IDatabase> database(
        neuray->get_api_component<mi::neuraylib::IDatabase>());
    check_success( database.is_valid_interface());
    mi::base::Handle< mi::neuraylib::IScope> scope(
        database->get_global_scope());
    mi::base::Handle< mi::neuraylib::ITransaction> transaction(
        scope->create_transaction());
    check_success( transaction.is_valid_interface());

    // Import the scene file
    mi::base::Handle< mi::neuraylib::IImport_api> import_api(
        neuray->get_api_component<mi::neuraylib::IImport_api>());
    check_success( import_api.is_valid_interface());
    mi::base::Handle< const mi::IImport_result> import_result(
        import_api->import_elements( transaction.get(), "main.mi"));
    check_success( import_result->get_error_number() == 0);

    // Add three instances of a subdivision surface with different approximation levels
    setup_scene( transaction.get(), import_result->get_rootgroup());

    // Create the scene object
    mi::base::Handle< mi::neuraylib::IScene> scene(
        transaction->create<mi::neuraylib::IScene>( "Scene"));
    scene->set_rootgroup(       import_result->get_rootgroup());
    scene->set_options(         import_result->get_options());
    scene->set_camera_instance( import_result->get_camera_inst());
    transaction->store( scene.get(), "the_scene");

    // Create the render context using the default renderer
    scene = transaction->edit<mi::neuraylib::IScene>( "the_scene");
    mi::base::Handle< mi::neuraylib::IRender_context> render_context(
        scene->get_render_context( transaction.get(), "rt_bsp"));
    scene = 0;

    // Create the render target and render the scene
    Render_target render_target( 512, 384);
    check_success( render_context->render( transaction.get(), &render_target, NULL) == 0);

    // Write the image to disk
    mi::base::Handle< mi::neuraylib::IExport_api> export_api(
        neuray->get_api_component<mi::neuraylib::IExport_api>());
    check_success( export_api.is_valid_interface());
    mi::base::Handle< mi::neuraylib::ICanvas> canvas( render_target.get_canvas( 0));
    export_api->export_canvas( "example_subdivision_surface.png", canvas.get(), 100);

    // All transactions need to get committed or aborted, not really important in this example.
    transaction->commit();
}

// The example takes the following command line arguments:
//
//   example_subdivision_surface <shader_path>
//
// shader_path      path to the shaders, e.g., neuray-<version>/shaders
//
// The rendered image is written to a file named "example_subdivision_surface.png".
//
int main( int argc, char* argv[])
{
    // Collect command line parameters
    if( argc != 2) {
        std::cerr << "Usage: example_subdivision_surface <shader_path>" << std::endl;
        return EXIT_FAILURE;
    }
    const char* shader_path = argv[1];

    // Access the neuray library
    mi::base::Handle< mi::neuraylib::INeuray> neuray( load_and_get_ineuray());
    check_success( neuray.is_valid_interface());

    // Configure the neuray library
    configuration ( neuray, shader_path);

    // Start the neuray library
    check_success( neuray->start( true) == 0);

    // Do the actual rendering
    rendering( neuray);

    // Shut down the neuray library
    check_success( neuray->shutdown() == 0);
    neuray = 0;

    // Unload the neuray library
    check_success( unload());

    return EXIT_SUCCESS;
}

[Previous] [Next] [Up]