Show / Hide Table of Contents
    ///////////////////////////////////////////////////////////////////////////
    //
    //  System:    Simplygon
    //  File:      ReductionPipelineExample.cpp
    //  Language:  C++
    //
    //  Copyright (c) 2015 Donya Labs AB. 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# An extensive reduction example using pipelines
    //
    //  In this example we demonstrate 2 common usages for the Simplygon reducer
    //  and explain good starting settings for the different usages. We also discuss
    //  how to use the stop conditions to setup complex reduction targets.
    //
    //  First, we do a high-quality reduction using symmetry awareness and 
    //  high-quality normal handling, explaining most of the commonly 
    //  used settings along the way
    //
    //  Secondly, the mesh is again reduced and the 9 input materials
    //  of the original are cast into a single output material, retaining all 
    //  channels from the original material
    //
    ///////////////////////////////////////////////////////////////////////////
    
    #include "../Common/Example.h"
    
    #include <chrono>
    
    #ifdef __cpp_lib_filesystem // Check for C++17 filesystem
    #include <filesystem>
    namespace fs = std::filesystem;
    #else
    #ifndef _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
    #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
    #endif
    #include <experimental/filesystem>
    namespace fs = std::experimental::filesystem::v1;
    #endif
    
    void RunHighQualityReduction( const std::string& readFrom, const std::string& writeTo );
    void RunReductionWithTextureCasting( const std::string& readFrom, const std::string& writeTo );
    
    class ProgressObserver : public robserver
        {
            public:
            virtual void Execute(
                IObject * subject,
                rid EventId,
                void * EventParameterBlock,
                unsigned int EventParameterBlockSize )
                {
                // only care for progress events
                if( EventId == SG_EVENT_PROGRESS )
                    {
                    // get the progress in percent
                    int val = *((int *)EventParameterBlock);
                    // tell the process to continue
                    // this is required by the progress event
                    *((int *)EventParameterBlock) = 1;
                    // output the progress update
                    PrintProgressBar( val );
                    }
                }
        } progressObserver;
    
    int main( int argc, char* argv[] )
        {
        InitExample();
    
        // Before any specific processing starts, set global variables. 
        // Using Orthonormal method for calculating tangentspace.
        sg->SetGlobalSetting("DefaultTBNType", SG_TANGENTSPACEMETHOD_ORTHONORMAL);
    
        std::string assetPath = GetAssetPath();
    
        std::chrono::milliseconds elapsed(0);
        int numRuns = 1;
        double timeMultiplier = 1.0 / ((double)(numRuns > 1 ? (numRuns - 1) : 1) * 1000.0);
    
        // Run HQ reduction example, reducing a single geometry to a single LOD
        printf("Running HQ reduction... \n");
        elapsed = std::chrono::milliseconds::zero();
        for (int run = 0; run < numRuns; ++run)
        {
            PrintProgressBar(0);
            auto start = std::chrono::high_resolution_clock::now();
            RunHighQualityReduction(assetPath + "SimplygonMan/SimplygonMan.obj", "SimplygonMan_HQ_LOD");
            if (run || numRuns == 1)
                elapsed += std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start);
        }
        printf("\nDone, %.2f seconds\n\n", (double)elapsed.count() * timeMultiplier);
    
        // Run reduction example that bakes all input materials into a single output material
        printf("Running reduction with material baking... \n");
        elapsed = std::chrono::milliseconds::zero();
        for (int run = 0; run < numRuns; ++run)
        {
            PrintProgressBar(0);
            auto start = std::chrono::high_resolution_clock::now();
            RunReductionWithTextureCasting(assetPath + "SimplygonMan/SimplygonMan.obj", "SimplygonMan_Rebaked_Materials_LOD");
            if (run || numRuns == 1)
                elapsed += std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start);
        }
        printf("\nDone, %.2f seconds\n\n", (double)elapsed.count() * timeMultiplier);
    
        // Done!
        printf("All LODs complete, shutting down...");
        DeinitExample();
    
        return 0;
        }
    
    void RunHighQualityReduction( const std::string & readFrom, const std::string & writeTo )
        {
        //Setup output paths
        fs::path outputBasePath = fs::path( GetExecutablePath() ) / writeTo;
        fs::remove_all( outputBasePath );
        fs::create_directories( outputBasePath );
    
        std::string outputGeomPath = ( outputBasePath / ( writeTo + ".obj" ) ).generic_u8string();
    
        //Create a remeshing pipeline
        spReductionPipeline reductionPipeline = sg->CreateReductionPipeline();
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // SETTINGS - Most of these are set to the same value by default, but are set anyway for clarity
    
        spPipelineSettings pipelineSettings = reductionPipeline->GetPipelineSettings();
        pipelineSettings->SetTextureOutputPath( ( outputBasePath / "Textures" ).generic_u8string().c_str() );
    
        // The reduction settings object contains settings pertaining to the actual decimation
        spReductionSettings reductionSettings = reductionPipeline->GetReductionSettings();
        reductionSettings->SetKeepSymmetry( true ); //Try, when possible to reduce symmetrically
        reductionSettings->SetUseAutomaticSymmetryDetection( true ); //Auto-detect the symmetry plane, if one exists. Can, if required, be set manually instead.
        reductionSettings->SetUseHighQualityNormalCalculation( true ); //Drastically increases the quality of the LODs normals, at the cost of extra processing time.
        reductionSettings->SetReductionHeuristics( SG_REDUCTIONHEURISTICS_CONSISTENT ); //Choose between "fast" and "consistent" processing. Fast will look as good, but may cause inconsistent 
                                                                                      //triangle counts when comparing MaxDeviation targets to the corresponding percentage targets.
    
                                                                                      // The reducer uses importance weights for all features to decide where and how to reduce.
                                                                                      // These are advanced settings and should only be changed if you have some specific reduction requirement
                                                                                      /*reductionSettings->SetShadingImportance(2.f); //This would make the shading twice as important to the reducer as the other features.*/
    
                                                                                      // The actual reduction triangle target are controlled by these settings
        reductionSettings->SetStopCondition( SG_STOPCONDITION_ANY );//The reduction stops when any of the targets below is reached
        reductionSettings->SetReductionTargets( SG_REDUCTIONTARGET_ALL );//Selects which targets should be considered when reducing
        reductionSettings->SetTriangleRatio( 0.5 ); //Targets at 50% of the original triangle count
        reductionSettings->SetTriangleCount( 10 ); //Targets when only 10 triangle remains
        reductionSettings->SetMaxDeviation( REAL_MAX ); //Targets when an error of the specified size has been reached. As set here it never happens.
        reductionSettings->SetOnScreenSize( 50 ); //Targets when the LOD is optimized for the selected on screen pixel size
    
        // The repair settings object contains settings to fix the geometries
        spRepairSettings repairSettings = reductionPipeline->GetRepairSettings();
        repairSettings->SetTjuncDist( 0.0f ); //Removes t-junctions with distance 0.0f
        repairSettings->SetWeldDist( 0.0f ); //Welds overlapping vertices
    
        // The normal calculation settings deal with the normal-specific reduction settings
        spNormalCalculationSettings normalSettings = reductionPipeline->GetNormalCalculationSettings();
        normalSettings->SetReplaceNormals( false ); //If true, this will turn off normal handling in the reducer and recalculate them all afterwards instead.
                                                  //If false, the reducer will try to preserve the original normals as well as possible
                                                  /*normalSettings->SetHardEdgeAngleInRadians( 3.14159f*60.0f/180.0f ); //If the normals are recalculated, this sets the hard-edge angle.*/
    
        //END SETTINGS 
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
        // Add progress observer
        reductionPipeline->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    
        // Run the actual processing. After this, the set geometry will have been reduced according to the settings
        reductionPipeline->RunSceneFromFile( readFrom.c_str(), outputGeomPath.c_str() );
    
        spPipelineSerializer serializer = sg->CreatePipelineSerializer();
        serializer->SavePipelineToFile( (GetExecutablePath() + writeTo + "/pipeline.json").c_str(), reductionPipeline );
    
        //Done! LOD created.
        }
    
    void RunReductionWithTextureCasting( const std::string & readFrom, const std::string & writeTo )
        {
        //Setup output paths
        fs::path outputBasePath = fs::path( GetExecutablePath() ) / writeTo;
        fs::remove_all( outputBasePath );
        fs::create_directories( outputBasePath );
    
        std::string outputGeomPath = ( outputBasePath / ( writeTo + ".obj" ) ).generic_u8string();
    
        //Create a remeshing pipeline
        spReductionPipeline reductionPipeline = sg->CreateReductionPipeline();
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // SETTINGS - Most of these are set to the same value by default, but are set anyway for clarity
    
        spPipelineSettings pipelineSettings = reductionPipeline->GetPipelineSettings();
        pipelineSettings->SetTextureOutputPath( ( outputBasePath / "Textures" ).generic_u8string().c_str() );
    
        // The reduction settings object contains settings pertaining to the actual decimation
        spReductionSettings reductionSettings = reductionPipeline->GetReductionSettings();
        reductionSettings->SetReductionHeuristics( SG_REDUCTIONHEURISTICS_FAST ); //Choose between "fast" and "consistent" processing.
    
        // The actual reduction triangle target are controlled by these three settings
        reductionSettings->SetStopCondition( SG_STOPCONDITION_ANY );//The reduction stops when any of the targets below is reached
        reductionSettings->SetReductionTargets( SG_REDUCTIONTARGET_ALL );//Selects which targets should be considered when reducing
        reductionSettings->SetTriangleRatio( 0.5 ); //Targets at 50% of the original triangle count
        reductionSettings->SetTriangleCount( 10 ); //Targets when only 10 triangle remains
        reductionSettings->SetMaxDeviation( REAL_MAX ); //Targets when an error of the specified size has been reached. As set here it never happens.
        reductionSettings->SetOnScreenSize( 50 ); //Targets when the LOD is optimized for the selected on screen pixel size
    
        // The normal calculation settings deal with the normal-specific reduction settings
        spNormalCalculationSettings normalSettings = reductionPipeline->GetNormalCalculationSettings();
        normalSettings->SetReplaceNormals( true ); //If true, this will turn off normal handling in the reducer and recalculate them all afterwards instead.
        normalSettings->SetHardEdgeAngleInRadians( 3.14f ); //If the normals are recalculated, this sets the hard-edge angle. Large here, since we're baking a new normal map, and want large charts.
    
        // The Image Mapping Settings, specifically needed for the texture baking we are doing later
        spMappingImageSettings mappingSettings = reductionPipeline->GetMappingImageSettings();
        mappingSettings->SetUseFullRetexturing( false ); //Keep the input texture coordinates
        mappingSettings->SetGenerateMappingImage( true ); //Without this we cannot fetch data from the original geometry, and thus not generate diffuse and normal-maps later on.
        mappingSettings->SetGenerateTexCoords( true );//Set to generate new texture coordinates.
        mappingSettings->SetGenerateTangents( true );//Set to generate new texture coordinates.
        mappingSettings->SetTexCoordLevelName( "remeshing_generated" ); //Set texcoord level name to generate a new UV set and keep the old instead of overwriting level 0
        mappingSettings->SetParameterizerMaxStretch( 0.8f ); //The higher the number, the fewer texture-borders.
        mappingSettings->SetGutterSpace( 2 ); //Buffer space for when texture is mip-mapped, so color values don't blend over. Greatly influences packing efficiency
        mappingSettings->SetTexCoordLevel( 0 ); //Sets the output texcoord level. For this asset, this will overwrite the original coords
        mappingSettings->SetWidth( 1024 );
        mappingSettings->SetHeight( 1024 );
        mappingSettings->SetMultisamplingLevel( 2 );
    
        // Cast diffuse and specular texture data with a color caster
        spColorCaster diffuseCaster = sg->CreateColorCaster();
        spColorCasterSettings diffuseCasterSettings = diffuseCaster->GetColorCasterSettings();
        diffuseCasterSettings->SetOutputChannelBitDepth( 8 ); //8 bits per channel. So in this case we will have 24bit colors RGB.
        diffuseCasterSettings->SetDilation( 10 ); //To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
        diffuseCasterSettings->SetOutputChannels( 3 ); //RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
        diffuseCasterSettings->SetMaterialChannel( SG_MATERIAL_CHANNEL_DIFFUSE );
    
        spColorCaster specularCaster = sg->CreateColorCaster();
        spColorCasterSettings specularCasterSettings = specularCaster->GetColorCasterSettings();
        specularCasterSettings->SetOutputChannelBitDepth( 8 ); //8 bits per channel. So in this case we will have 24bit colors RGB.
        specularCasterSettings->SetDilation( 10 ); //To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
        specularCasterSettings->SetOutputChannels( 4 ); //RGBA, 4 channels! Stores spec power in A
        specularCasterSettings->SetMaterialChannel( SG_MATERIAL_CHANNEL_SPECULAR );
    
        // Cast normal map texture data with the normal caster. This also compensates for any geometric errors that have appeared in the reduction process.
        spNormalCaster normalCaster = sg->CreateNormalCaster();
        spNormalCasterSettings normalCasterSettings = normalCaster->GetNormalCasterSettings();
        normalCasterSettings->SetOutputChannels( 3 ); // RGB, 3 channels! (But really the x, y and z values for the normal)
        normalCasterSettings->SetOutputChannelBitDepth( 8 );
        normalCasterSettings->SetDilation( 10 );
        normalCasterSettings->SetFlipBackfacingNormals( false );
        normalCasterSettings->SetGenerateTangentSpaceNormals( true );
        normalCasterSettings->SetMaterialChannel( SG_MATERIAL_CHANNEL_NORMALS );
    
        reductionPipeline->AddMaterialCaster( diffuseCaster, 0 );
        reductionPipeline->AddMaterialCaster( specularCaster, 0 );
        reductionPipeline->AddMaterialCaster( normalCaster, 0 );
    
        //END SETTINGS 
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
        // Add progress observer
        reductionPipeline->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    
        // Run the actual processing. After this, the set geometry will have been reduced according to the settings
        reductionPipeline->RunSceneFromFile( readFrom.c_str(), outputGeomPath.c_str() );
    
        spPipelineSerializer serializer = sg->CreatePipelineSerializer();
        serializer->SavePipelineToFile( (GetExecutablePath() + writeTo + "/pipeline.json").c_str(), reductionPipeline );
    
        //Done! LOD and material created.
        }
    
    Back to top Terms of Use | Privacy and cookies | Trademarks | Copyright © 2019 Microsoft