Show / Hide Table of Contents
    ///////////////////////////////////////////////////////////////////////////
    //
    //  System:    Simplygon
    //  File:      MaterialNodeExample.cpp
    //  Language:  C++
    //
    //  Copyright (c) 2019 Microsoft. All rights reserved.
    //
    //  This is private property, and it is illegal to copy or distribute in
    //  any form, without written authorization by the copyright owner(s).
    //
    ///////////////////////////////////////////////////////////////////////////
    //
    //  #Description# 
    //
    //  This example shows how to use the material node network.
    //
    //  The RunExample() function will load a scene and create a new 
    //  material. A material node network is created and added to the diffuse
    //  channel of the new material.
    //
    //  The example will go through different usages of the material nodes.
    //
    //  Material Node usage example:
    //
    //  1)
    //  Export GLSL/HLSL shader code from a material with a material node 
    //  network.
    //
    //  2)
    //  Preview the model with the material node network.
    //
    //  3)
    //  Reduce the geometry and create a mapping image to perform material 
    //  casting, baking the materials to a single material.
    //
    ///////////////////////////////////////////////////////////////////////////
    
    #include "../Common/Example.h"
    
    void RunExample( const std::string& readFrom );
    
    std::string assetPath = GetAssetPath() + "MaterialNodeExampleAssets/";
    
    int main( int argc, char* argv[] )
        {
        try
        {
            InitExample();
    
            // Set global variable. Using Orthonormal method for calculating
            // tangentspace.
            sg->SetGlobalSetting("DefaultTBNType", SG_TANGENTSPACEMETHOD_ORTHONORMAL);
    
            // Run the example code
            RunExample(assetPath + "island.obj");
    
            DeinitExample();
        }
        catch (const std::exception& ex)
        {
            std::cerr << ex.what() << std::endl;
            return -1;
        }
    
        return 0;
        }
    
    // Takes the existing texture coordinates level and copies it into a new texture coordinates level
    // the second UV level is multiplied to get a higher frequency ( for tiling textures )
    void SetupTexcoordLevels( spGeometryData newGeom )
        {
        // Get the tex_coords
        spRealArray tex_coords_0 = newGeom->GetTexCoords( 0 );
        tex_coords_0->SetAlternativeName( "UVzero" );
    
        if( !newGeom->GetTexCoords( 1 ) )
            {
            newGeom->AddTexCoords( 1 );
            spRealArray tex_coords_1 = newGeom->GetTexCoords( 1 );
    
            // Copy texcoords in level 0 to texcoords level 1 but multiply them to change frequency
            for( uint i = 0; i < tex_coords_0->GetItemCount(); ++i )
                {
                tex_coords_1->SetItem( i, tex_coords_0->GetItem( i ) * 4.0f );
                }
            tex_coords_1->SetAlternativeName( "UVone" );
            }
        }
    
    // Compute vertex colors from the model that will be used to interpolate between textures
    // and other nodes
    void ComputeVertexColors( spGeometryData newGeom )
        {
        float threshold_water = 0.01f; // At what height the water should end
        float threshold_grass = 0.3f; // At what height the grass should end
        float threshold_dirt = 0.5f; // At what height the dirt should end
    
        spRealArray coords = newGeom->GetCoords();
        spRidArray vertex_ids = newGeom->GetVertexIds();
    
        real max_height = -REAL_MAX;
        real min_height = REAL_MAX;
    
        //Find the max and min y-value of the geometry
        for( uint i = 0; i < coords->GetTupleCount(); ++i )
            {
            real current_height = coords->GetItem( i * 3 + 1 );
    
            if( current_height > max_height )
                max_height = current_height;
    
            if( current_height < min_height )
                min_height = current_height;
            }
    
        if( !newGeom->GetColors( 0 ) )
            {
            newGeom->AddColors( 0 );
            }
        spRealArray relative_height_vertex_color = newGeom->GetColors( 0 );
        relative_height_vertex_color->SetAlternativeName( "colorA" );
    
        if( !newGeom->GetColors( 1 ) )
            {
            newGeom->AddColors( 1 );
            }
        spRealArray weight_water_vertex_color = newGeom->GetColors( 1 );
        weight_water_vertex_color->SetAlternativeName( "colorB" );
    
        if( !newGeom->GetColors( 2 ) )
            {
            newGeom->AddColors( 2 );
            }
        spRealArray weight_grass_vertex_color = newGeom->GetColors( 2 );
        weight_grass_vertex_color->SetAlternativeName( "colorC" );
    
        if( !newGeom->GetColors( 3 ) )
            {
            newGeom->AddColors( 3 );
            }
        spRealArray weight_dirt_vertex_color = newGeom->GetColors( 3 );
        weight_dirt_vertex_color->SetAlternativeName( "colorD" );
    
        for( uint corner_id = 0; corner_id < newGeom->GetTriangleCount() * 3; ++corner_id )
            {
            rid v_id = vertex_ids->GetItem( corner_id );
    
            // Get the y component from the coordinates
            real absolute_height = coords->GetItem( v_id * 3 + 1 );
    
            // Compute the relative height where the lowest model coordinate will be zero, and the highest will be one
            real relative_height = (absolute_height - min_height) / (max_height - min_height);
    
            // Compute the blend values between textures
            // For instance, the interpolate_water_grass vertex colors will be:
            // [0 -> 1] between [zero height -> threshold_water height]
            // and then 1 for all heights above the threshold_water height
            real interpolate_water_grass = clamp( relative_height / threshold_water, 0.0f, 1.0f );
            real interpolate_grass_dirt = clamp( (relative_height - threshold_water) / (threshold_grass - threshold_water), 0.0f, 1.0f );
            real interpolate_dirt_rock = clamp( (relative_height - threshold_grass) / (threshold_dirt - threshold_grass), 0.0f, 1.0f );
    
            real relative_height_color[4] = { relative_height, relative_height, relative_height, 1.0 };
            real blend_water_color[4] = { interpolate_water_grass, interpolate_water_grass, interpolate_water_grass, 1.0 };
            real blend_grass_color[4] = { interpolate_grass_dirt, interpolate_grass_dirt, interpolate_grass_dirt, 1.0 };
            real blend_dirt_color[4] = { interpolate_dirt_rock, interpolate_dirt_rock, interpolate_dirt_rock, 1.0 };
    
            // Add the blend values to the vertex color fields
            relative_height_vertex_color->SetTuple( corner_id, relative_height_color );
            weight_water_vertex_color->SetTuple( corner_id, blend_water_color );
            weight_grass_vertex_color->SetTuple( corner_id, blend_grass_color );
            weight_dirt_vertex_color->SetTuple( corner_id, blend_dirt_color );
            }
        }
    
    // This function will use a few nodes to create a network of nodes that will
    // resemble a mountain material
    spShadingNode CreateMountainNode()
        {
        // Begin by creating texture nodes, setting which UV sets they will use
        // and the paths to their respective textures
        spShadingTextureNode node_texture_icemask = sg->CreateShadingTextureNode();
        spShadingTextureNode node_texture_ice = sg->CreateShadingTextureNode();
        spShadingTextureNode node_texture_rock = sg->CreateShadingTextureNode();
    
        const char *level_0 = "UVzero";
        const char *level_1 = "UVone";
    
        node_texture_icemask->SetTextureLevelName( level_0 );
        node_texture_ice->SetTextureLevelName( level_1 );
        node_texture_rock->SetTextureLevelName( level_1 );
    
        node_texture_icemask->SetTextureName( (assetPath + "icemask.png").c_str() );
        node_texture_ice->SetTextureName( (assetPath + "ice.png").c_str() );
        node_texture_rock->SetTextureName( (assetPath + "rock.jpg").c_str() );
    
        // Create a vertex color node that will link to the vertex color: "colorA"
        spShadingVertexColorNode node_weights_relative_height = sg->CreateShadingVertexColorNode();
        node_weights_relative_height->SetVertexColorSet( "colorA" );
    
        // Blend between ice and rock using the ice mask
        spShadingInterpolateNode node_masked_ice = sg->CreateShadingInterpolateNode();
        node_masked_ice->SetInput( 0, node_texture_rock );
        node_masked_ice->SetInput( 1, node_texture_ice );
        node_masked_ice->SetInput( 2, node_texture_icemask );
    
        // Offset all the weights of the height slightly using a subtraction
        spShadingSubtractNode node_icyness = sg->CreateShadingSubtractNode();
        node_icyness->SetInput( 0, node_weights_relative_height );
        node_icyness->SetDefaultParameter( 1, 0.1f, 0.1f, 0.1f, 0.0f );
    
        // Clamp the weights to remain between zero and one after the subtraction
        spShadingClampNode node_icyness_clamp = sg->CreateShadingClampNode();
        node_icyness_clamp->SetInput( 0, node_icyness );
        node_icyness_clamp->SetDefaultParameter( 1, 0.0f, 0.0f, 0.0f, 1.0f );
        node_icyness_clamp->SetDefaultParameter( 2, 1.0f, 1.0f, 1.0f, 1.0f );
    
        // Blend between the rock texture and the already masked ice
        // based on the height of the mountains
        spShadingInterpolateNode node_interpolate_mountain = sg->CreateShadingInterpolateNode();
        node_interpolate_mountain->SetInput( 0, node_texture_rock );
        node_interpolate_mountain->SetInput( 1, node_masked_ice );
        node_interpolate_mountain->SetInput( 2, node_icyness_clamp );
    
        return spShadingNode( node_interpolate_mountain );
        }
    
    // This function will use a few nodes to create a network of nodes that will
    // resemble a water material
    spShadingNode CreateWaterNode()
        {
        // Begin by creating texture nodes, setting which UV sets they will use
        // and the paths to their respective textures
        spShadingTextureNode node_texture_water = sg->CreateShadingTextureNode();
        spShadingTextureNode node_texture_noise = sg->CreateShadingTextureNode();
    
        const char *level_0 = "UVzero";
        const char *level_1 = "UVone";
    
        node_texture_water->SetTextureLevelName( level_1 );
        node_texture_noise->SetTextureLevelName( level_1 );
    
        node_texture_water->SetTextureName( (assetPath + "water.png").c_str() );
        node_texture_noise->SetTextureName( (assetPath + "noise.png").c_str() );
    
        // Create a color node which will simply be a static color
        spShadingColorNode node_color_water = sg->CreateShadingColorNode();
        node_color_water->SetColor( 0.075f, 0.368f, 0.347f, 1.0f );
    
        // Interpolate between the water texture and the water color
        // using the noise texture
        spShadingInterpolateNode node_interpolate_water_color = sg->CreateShadingInterpolateNode();
        node_interpolate_water_color->SetInput( 0, node_texture_water );
        node_interpolate_water_color->SetInput( 1, node_color_water );
        node_interpolate_water_color->SetInput( 2, node_texture_noise );
    
        return spShadingNode( node_interpolate_water_color );
        }
    
    // Creates a material node network that uses different kinds of nodes
    // to create a material for an island model
    // It uses multiple textures with different uv sets
    // and blends nodes using vertex colors and textures
    spShadingNode CreateIslandMaterialNodeNetwork()
        {
        // Create the mountain node
        spShadingNode node_mountain = CreateMountainNode();
    
        // Create the water node
        spShadingNode node_water = CreateWaterNode();
    
        // Define texture nodes, setting which UV sets they will use
        // and the paths to their respective textures
        spShadingTextureNode node_texture_grass = sg->CreateShadingTextureNode();
        spShadingTextureNode node_texture_dirt = sg->CreateShadingTextureNode();
    
        const char *level_0 = "UVzero";
        const char *level_1 = "UVone";
    
        node_texture_dirt->SetTextureLevelName( level_1 );
        node_texture_grass->SetTextureLevelName( level_1 );
    
        node_texture_dirt->SetTextureName( (assetPath + "stonedirt.jpg").c_str() );
        node_texture_grass->SetTextureName( (assetPath + "greengrass.jpg").c_str() );
    
        // Create interpolation nodes 
        spShadingInterpolateNode node_interpolate_water_land = sg->CreateShadingInterpolateNode();
        spShadingInterpolateNode node_interpolate_land_0 = sg->CreateShadingInterpolateNode();
        spShadingInterpolateNode node_interpolate_land_1 = sg->CreateShadingInterpolateNode();
    
        spShadingVertexColorNode node_weights_water_land = sg->CreateShadingVertexColorNode();
        node_weights_water_land->SetVertexColorSet( "colorB" );
        spShadingVertexColorNode node_weights_interpolate_land_0 = sg->CreateShadingVertexColorNode();
        node_weights_interpolate_land_0->SetVertexColorSet( "colorC" );
        spShadingVertexColorNode node_weights_interpolate_land_1 = sg->CreateShadingVertexColorNode();
        node_weights_interpolate_land_1->SetVertexColorSet( "colorD" );
    
        node_interpolate_land_1->SetInput( 0, node_texture_dirt );
        node_interpolate_land_1->SetInput( 1, node_mountain );
        node_interpolate_land_1->SetInput( 2, node_weights_interpolate_land_1 );
    
        node_interpolate_land_0->SetInput( 0, node_texture_grass );
        node_interpolate_land_0->SetInput( 1, node_interpolate_land_1 );
        node_interpolate_land_0->SetInput( 2, node_weights_interpolate_land_0 );
    
        node_interpolate_water_land->SetInput( 0, node_water );
        node_interpolate_water_land->SetInput( 1, node_interpolate_land_0 );
        node_interpolate_water_land->SetInput( 2, node_weights_water_land );
    
        return spShadingNode( node_interpolate_water_land );
        }
    
    void RunExample( const std::string& readFrom )
        {
        const std::string writeTo = "output";
    
        std::string output_geometry_filename = GetExecutablePath() + writeTo + ".obj";
        std::string output_diffuse_filename = GetExecutablePath() + writeTo + "_diffuse.png";
    
        // Load object from file
        spWavefrontImporter objReader = sg->CreateWavefrontImporter();
        objReader->SetImportFilePath( readFrom.c_str() );
        if( !objReader->RunImport() )
            throw std::exception("Failed to load input file!");
    
        //Get the loaded scene
        spScene scene = objReader->GetScene();
    
        //Find all ISceneMeshes in the scene and compute some vertex colors and texcoords for the geometries
        int selectionSetAllSceneMeshesID = scene->SelectNodes( "ISceneMesh" );
        spSelectionSet selectionSetAllSceneMeshes = scene->GetSelectionSetTable()->GetSelectionSet( selectionSetAllSceneMeshesID );
    
        for( uint i = 0; i < selectionSetAllSceneMeshes->GetItemCount(); ++i )
            {
            //Get the node from the selection set and cast it to an ISceneMesh
            spSceneMesh sceneMesh = Cast<ISceneMesh>( scene->GetNodeByGUID( selectionSetAllSceneMeshes->GetItem( i ) ) );
    
            //Get the goemetry from the scene mesh
            spGeometryData geom = sceneMesh->GetGeometry();
    
            // Compute a few sets of vertex colors based on the height of the model
            // the colors will be used to blend between textures
            ComputeVertexColors( geom );
    
            // Setup UV sets
            SetupTexcoordLevels( geom );
            }
    
        //Get the scene's material table and clear it
        spMaterialTable material_table = scene->GetMaterialTable();
        material_table->Clear();
    
        spMaterial island_material = sg->CreateMaterial();
        material_table->AddMaterial( island_material );
    
        spTextureTable texture_table = scene->GetTextureTable();
    
        std::string Texs[7];
        Texs[0] = assetPath + "stonedirt.jpg";
        Texs[1] = assetPath + "greengrass.jpg";
        Texs[2] = assetPath + "water.png";
        Texs[3] = assetPath + "noise.png";
        Texs[4] = assetPath + "icemask.png";
        Texs[5] = assetPath + "ice.png";
        Texs[6] = assetPath + "rock.jpg";
    
        for( uint i = 0; i < 7; ++i )
            {
            spTexture texture = sg->CreateTexture();
            texture->SetName( Texs[i].c_str() );
            texture->SetFilePath( Texs[i].c_str() );
            texture_table->AddTexture( texture );
            }
    
        // Create a material node network consisting of an arbitrary amount of shading nodes
        spShadingNode exit_node = CreateIslandMaterialNodeNetwork();
    
        // Assign the exit node of the material node network to the diffuse channel in the island material
        island_material->SetShadingNetwork( SG_MATERIAL_CHANNEL_DIFFUSE, exit_node );
    
        ///////////////////////////////////
        // Material Node usage example 1 
        // 
        // Export GLSL/HLSL shader code from a material with a material node 
        // network
        //
        // Once the material has node networks assigned to it,
        // we can generate a shader for it both in GLSL and HLSL.
        //
    
        // We create a shader generator object
        spShaderGenerator island_shader = sg->CreateShaderGenerator();
    
        // We add the material to it
        island_shader->SetMaterial( island_material );
    
        // We tell the shader generator object to traverse the material node tree 
        // and gather the information needed to create a shader from the nodes 
        island_shader->GenerateShaderData();
    
        // Once the GenerateShaderData() has been called, the shader generator object
        // will contain the information needed to setup the renderer for the 
        // GLSL or HLSL shader
    
        // Get all the texture paths that should be loaded and sent to the shader
        // the textures should be uploaded to the shader at the texture slot 
        // corresponding to its position in the collection
        std::cout << "Shader Input Texture Paths:" << std::endl;
        for (uint index = 0; index < island_shader->GetShaderInputTexturePathsCount(); index++)
            {
            rstring str = island_shader->GetShaderInputTexturePath(index);
            std::cout << "\t" << str.GetText() << std::endl;
            }
    
        // Get the UV sets that should be uploaded to the shader
        // the slot in the array corresponds to the slot in the shader
        // the values in the array corresponds to the tex coord level being used
        std::cout << "Shader Input UV Sets:" << std::endl;
        for (uint index = 0; index < island_shader->GetShaderInputUVSetsCount(); index++)
            {
            rstring str = island_shader->GetShaderInputUVSet(index);
            std::cout << "\t" << str.GetText() << std::endl;
            }
    
        // Get the vertex colors that should be uploaded to the shader
        // the slot in the array corresponds to the slot in the shader
        // the values in the array corresponds to the vertex color field being used
        std::cout << "Shader Input Vertex Color Sets:" << std::endl;
        for (uint index = 0; index < island_shader->GetShaderInputVertexColorsCount(); index++)
            {
            rstring str = island_shader->GetShaderInputVertexColor(index);
            std::cout << "\t" << str.GetText() << std::endl;
            }
    
        // Get the entire shader code for HLSL and GLSL
        rstring hlsl_code = island_shader->GetHLSLCode();
        rstring glsl_vertex_code = island_shader->GetGLSLVertexCode();
        rstring glsl_fragment_code = island_shader->GetGLSLFragmentCode();
    
        //Store the shaders to file in the current folder 
        FILE * file_hlsl = fopen( "shader_hlsl.txt", "wt" );
        fprintf( file_hlsl, "%s", hlsl_code.GetText() );
        fclose( file_hlsl );
        FILE * file_glsl_vertex = fopen( "shader_glsl_vertex.txt", "wt" );
        fprintf( file_glsl_vertex, "%s", glsl_vertex_code.GetText() );
        fclose( file_glsl_vertex );
        FILE * file_glsl_fragment = fopen( "shader_glsl_fragment.txt", "wt" );
        fprintf( file_glsl_fragment, "%s", glsl_fragment_code.GetText() );
        fclose( file_glsl_fragment );
    
        //
        // End material node usage example 1
        ///////////////////////////////////
    
    #ifdef _WIN32
        ///////////////////////////////////
        // Material Node usage example 2 
        // 
        // Preview the model with the material node network 
        //
        // This example will render the model using DirectX 
        // A shader will be generated like above (but automatically)
        // And the renderer will automatically upload the textures, uv sets and
        // vertex colors channels to a shader at the proper slots.
        // 
        // Then we will render the model with the shader and store the frames
        // to file.
        //
    #ifndef _HEADLESS
        // Create a DirectX renderer
        spDirectXRenderer renderer = sg->CreateDirectXRenderer();
    
        // Setup the previewer with the window dimensions
        renderer->CreatePreviewer( 2048, 2048 );
    
        //Load the scene into the renderer
        renderer->LoadGeometryDataWithMaterialShadingNetwork( scene );
    
        // Create a camera for the renderer
        spSceneCamera sceneCamera = sg->CreateSceneCamera();
        //Generate a few camera views above the scene 
        sceneCamera->SetCustomSphereCameraPath(
            1, //Fidelity
            0.0f, //pitch
            0.0f, //yaw
            100.0f //coverage
            );
        scene->GetRootNode()->AddChild( sceneCamera );
    
        //Create a selection set containing the camera views
        spSelectionSet cameraSelectionSet = sg->CreateSelectionSet();
        cameraSelectionSet->AddItem( sceneCamera->GetNodeGUID() );
    
        //Add the selection set to the scene's selection set table
        rid cameraSelectionSetID = scene->GetSelectionSetTable()->AddSelectionSet( cameraSelectionSet );
    
        // Render the model with the material node network and store the frames
        renderer->RenderAlongCameraPathAndStorePics( cameraSelectionSetID, "screenshot", "png" );
    #endif
    
        //
        // End material node usage example 2
        ///////////////////////////////////
    #endif
    
        ///////////////////////////////////
        // Material Node usage example 3
        // 
        // Reduce the geometry and create a mapping image for it to be able to 
        // perform material casting.
        // 
        // Cast the diffuse channel from the material node network to the new 
        // material.
        spReductionProcessor red = sg->CreateReductionProcessor();
        red->SetScene( scene );
    
        // Set the Repair Settings.
        spRepairSettings repair_settings = red->GetRepairSettings();
    
        // Will take care of holes that are of size 0.2 or less, so small gaps etc are removed.
        repair_settings->SetWeldDist( 0.2f );
        repair_settings->SetTjuncDist( 0.2f );
    
        // Set the Reduction Settings.
        spReductionSettings reduction_settings = red->GetReductionSettings();
    
        // Set the reduction ratios to 0.9, meaning that we keep 90% of the triangles
        reduction_settings->SetTriangleRatio( 0.9f );
    
        // Set the Mapping Image Settings.
        spMappingImageSettings mapping_settings = red->GetMappingImageSettings();
        // Without this we cannot fetch data from the original geometry
        mapping_settings->SetGenerateMappingImage( true );
        // Set to generate new texture coordinates.
        mapping_settings->SetGenerateTexCoords( true );
        // The higher the number, the fewer texture-borders.
        mapping_settings->SetParameterizerMaxStretch( 0.5f );
        // Buffer space for when texture is mip-mapped, so color values dont blend over.
        mapping_settings->SetGutterSpace( 4 );
    
        // Set the new material texture dimensions
        mapping_settings->SetUseAutomaticTextureSize( false );
        mapping_settings->SetWidth( 2048 );
        mapping_settings->SetHeight( 2048 );
        mapping_settings->SetMultisamplingLevel( 2 );
    
        // Run the reduction
        red->RunProcessing();
    
        // Mapping image is needed for texture casting.
        spMappingImage mapping_image = red->GetMappingImage();
    
        // Create new material table.
        spMaterialTable output_materials = sg->CreateMaterialTable();
        spTextureTable output_textures = sg->CreateTextureTable();
    
        //Create new material for the table.
        spMaterial output_material = sg->CreateMaterial();
        output_material->SetName( "output_material" );
    
        //Add the new material to the table
        output_materials->AddMaterial( output_material );
    
        // Cast the diffuse texture data using a color caster
        spColorCaster cast = sg->CreateColorCaster();
        cast->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE ); //Select the diffuse channel from the original material 
        cast->SetSourceMaterials( scene->GetMaterialTable() );
        cast->SetSourceTextures( scene->GetTextureTable() );
        cast->SetMappingImage( mapping_image ); // The mapping image we got from the reduction process.
        cast->SetOutputChannels( 3 ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
        cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB.
        cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
        cast->SetOutputFilePath( output_diffuse_filename.c_str() ); // Where the texture map will be saved to file.
        cast->RunProcessing(); // Fetch!
    
        // Set the material properties 
        // Set material to point to the created texture filename.
        AddSimplygonTexture( output_material, output_textures, SG_MATERIAL_CHANNEL_DIFFUSE, output_diffuse_filename.c_str() );
    
        spWavefrontExporter objexp = sg->CreateWavefrontExporter();
    
        //Copy the output material table to the scene's materials
        material_table->Clear();
        material_table->Copy( output_materials );
        texture_table->Clear();
        texture_table->Copy( output_textures );
        objexp->SetExportFilePath( output_geometry_filename.c_str() );
        objexp->SetScene( scene );
        objexp->RunExport();
    
        //
        // End material node usage example 3
        ///////////////////////////////////
    
    
        }
    
    Back to top Terms of Use | Privacy and cookies | Trademarks | Copyright © 2019 Microsoft