Show / Hide Table of Contents
    ///////////////////////////////////////////////////////////////////////////
    //
    //  System:    Simplygon
    //  File:      ReductionExample.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# An extensive reduction example
    //
    //  In this example we demonstrate 3 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
    //
    //  Thirdly, a cascaded reduction chain is run using faster settings
    //  to show how to setup a cascaded LOD chain.
    //
    ///////////////////////////////////////////////////////////////////////////
    
    #include "../Common/Example.h"
    
    void RunHighQualityReduction( const std::string& readFrom, const std::string& writeTo );
    void RunReductionWithTextureCasting( const std::string& readFrom, const std::string& writeTo );
    void RunCascadedLodChainReduction( const std::string& readFrom, const std::string& writeToLod1, const std::string& writeToLod2, const std::string& writeToLod3 );
    
    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[] )
        {
        try
        {
            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();
    
            // Run HQ reduction example, reducing a single geometry to a single LOD
            printf("Running HQ reduction... \n");
            PrintProgressBar(0);
            RunHighQualityReduction(assetPath + "SimplygonMan/SimplygonMan.obj", "SimplygonMan_HQ_LOD");
            printf("\nDone.\n\n");
    
            // Run reduction example that bakes all input materials into a single output material
            printf("Running reduction with material baking... \n");
            PrintProgressBar(0);
            RunReductionWithTextureCasting(assetPath + "SimplygonMan/SimplygonMan.obj", "SimplygonMan_Rebaked_Materials_LOD");
            printf("\nDone.\n\n");
    
            //// Run a cascaded LOD chain generation
            printf("Running cascaded LOD chain reduction... \n");
            RunCascadedLodChainReduction(assetPath + "SimplygonMan/SimplygonMan.obj", "SimplygonMan_Cascade_LOD1", "SimplygonMan_Cascade_LOD2", "SimplygonMan_Cascade_LOD3");
            printf("\nDone.\n\n");
    
            // Done!
            printf("All LODs complete, shutting down...");
            DeinitExample();
        }
        catch (const std::exception& ex)
        {
            std::cerr << ex.what() << std::endl;
            return -1;
        }
    
        return 0;
        }
    
    void RunHighQualityReduction( const std::string& readFrom, const std::string& writeTo )
        {
        // Load input geometry from file
        spWavefrontImporter objReader = sg->CreateWavefrontImporter();
        objReader->SetExtractGroups( false ); //This makes the .obj reader import into a single geometry object instead of multiple
        objReader->SetImportFilePath( readFrom.c_str() );
        if( !objReader->RunImport() )
            throw std::exception("Failed to load input file!");
    
        // Get geometry and materials from importer
        spScene originalScene = objReader->GetScene();
    
        //Create a copy of the original scene on which we will run the reduction
        spScene lodScene = originalScene->NewCopy();
    
        // Create the reduction-processor, and set which scene to reduce
        spReductionProcessor reductionProcessor = sg->CreateReductionProcessor();
        reductionProcessor->SetScene( lodScene );
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // SETTINGS - Most of these are set to the same value by default, but are set anyway for clarity
    
        // The reduction settings object contains settings pertaining to the actual decimation
        spReductionSettings reductionSettings = reductionProcessor->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 = reductionProcessor->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 = reductionProcessor->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
        reductionProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    
        // Run the actual processing. After this, the set geometry will have been reduced according to the settings
        reductionProcessor->RunProcessing();
    
        // For this reduction, the LOD will use the same material set as the original, and hence no further processing is required
    
        //Create an .obj exporter to save our result
        spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
    
        // Generate the output filenames
        std::string outputGeomFilename = GetExecutablePath() + writeTo + ".obj";
    
        // Do the actual exporting
        objExporter->SetExportFilePath( outputGeomFilename.c_str() );
        objExporter->SetScene( lodScene ); //This is the geometry we set as the processing geom of the reducer, retaining the materials in the original scene
        objExporter->RunExport();
    
        //Done! LOD created.
        }
    
    void RunReductionWithTextureCasting( const std::string& readFrom, const std::string& writeTo )
        {
        // Load input geometry from file
        spWavefrontImporter objReader = sg->CreateWavefrontImporter();
        objReader->SetExtractGroups( false ); //This makes the .obj reader import into a single geometry object instead of multiple
        objReader->SetImportFilePath( readFrom.c_str() );
        if( !objReader->RunImport() )
            throw std::exception("Failed to load input file!");
    
        // Get the scene from the importer
        spScene originalScene = objReader->GetScene();
        spMaterialTable originalMaterialTable = originalScene->GetMaterialTable();
        spTextureTable originalTextures = originalScene->GetTextureTable();
    
        // Create a copy of the original scene on which we will run the reduction
        spScene lodScene = originalScene->NewCopy();
    
        // Create the reduction-processor, and set the scene to reduce
        spReductionProcessor reductionProcessor = sg->CreateReductionProcessor();
        reductionProcessor->SetScene( lodScene );
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // SETTINGS - Most of these are set to the same value by default, but are set anyway for clarity
    
        // The reduction settings object contains settings pertaining to the actual decimation
        spReductionSettings reductionSettings = reductionProcessor->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 = reductionProcessor->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 = reductionProcessor->GetMappingImageSettings();
        mappingSettings->SetUseFullRetexturing( true ); //This clears all existing UV sets and replaces the material IDs 
        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->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 );
    
        //END SETTINGS 
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
        // Add progress observer
        reductionProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    
        // Run the actual processing. After this, the set geometry will have been reduced according to the settings
        reductionProcessor->RunProcessing();
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // CASTING
    
        // Now, we need to retrieve the generated mapping image and use it to cast the old materials into a new one, for each channel.
        spMappingImage mappingImage = reductionProcessor->GetMappingImage();
    
        // Now, for each channel, we want to cast the 9 input materials into a single output material, with one texture per channel. 
        // First, clear the lod materials (as they are copies from the original initially)
        spMaterialTable lodMaterialTable = lodScene->GetMaterialTable();
        spTextureTable lodTextureTable = lodScene->GetTextureTable();
        lodMaterialTable->Clear();
        lodTextureTable->Clear();
        // Create new material for the table.
        spMaterial lodMaterial = sg->CreateMaterial();
        lodMaterial->SetName( "SimplygonBakedMaterial" );
        lodMaterialTable->AddMaterial( lodMaterial );
        // Cast diffuse and specular texture data with a color caster
        {
        // Cast the data using a color caster
        spColorCaster colorCaster = sg->CreateColorCaster();
        colorCaster->SetSourceMaterials( originalMaterialTable );
        colorCaster->SetSourceTextures( originalTextures );
        //It means the internal shininess is multiplied by 128 before baking to texture.
        colorCaster->SetMappingImage( mappingImage ); //The mapping image we got from the reduction process.
        colorCaster->SetOutputChannelBitDepth( 8 ); //8 bits per channel. So in this case we will have 24bit colors RGB.
        colorCaster->SetDilation( 10 ); //To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
    
        colorCaster->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE );
        colorCaster->SetOutputChannels( 3 ); //RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
        colorCaster->SetOutputFilePath( "combinedDiffuseMap.png" ); //Where the texture map will be saved to file.
        colorCaster->RunProcessing(); //Do the actual casting and write to texture.
    
        colorCaster->SetColorType( SG_MATERIAL_CHANNEL_SPECULAR );
        colorCaster->SetOutputChannels( 4 ); //RGBA, 4 channels! Stores spec power in A
        colorCaster->SetOutputFilePath( "combinedSpecularMap.png" ); //Where the texture map will be saved to file.
        colorCaster->RunProcessing(); //Do the actual casting and write to texture.
    
    
        AddSimplygonTexture( lodMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_DIFFUSE, "combinedDiffuseMap.png" );
        AddSimplygonTexture( lodMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_SPECULAR, "combinedSpecularMap.png" );
        }
    
        // Cast normal map texture data with the normal caster. This also compensates for any geometric errors that have appeared in the reduction process.
        {
        // cast the data using a normal caster
        spNormalCaster normalCaster = sg->CreateNormalCaster();
        normalCaster->SetSourceMaterials( originalMaterialTable );
        normalCaster->SetSourceTextures( originalTextures );
        normalCaster->SetMappingImage( mappingImage );
        normalCaster->SetOutputChannels( 3 ); // RGB, 3 channels! (But really the x, y and z values for the normal)
        normalCaster->SetOutputChannelBitDepth( 8 );
        normalCaster->SetDilation( 10 );
        normalCaster->SetOutputFilePath( "combinedNormalMap.png" );
        normalCaster->SetFlipBackfacingNormals( false );
        normalCaster->SetGenerateTangentSpaceNormals( true );
        normalCaster->RunProcessing();
    
        // Set normal map of the created material to point to the combined normal map
        AddSimplygonTexture( lodMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_NORMALS, "combinedNormalMap.png" );
        }
    
        // END CASTING
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
        //Create an .obj exporter to save our result
        spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
    
        // Generate the output filenames
        std::string outputGeomFilename = GetExecutablePath() + writeTo + ".obj";
    
        // Do the actual exporting
        objExporter->SetExportFilePath( outputGeomFilename.c_str() );
        objExporter->SetScene( lodScene ); //This is the geometry we set as the processing geom of the reducer
        objExporter->RunExport();
    
        //Done! LOD and material created.
        }
    
    void RunCascadedLodChainReduction( const std::string& readFrom, const std::string& writeToLod1, const std::string& writeToLod2, const std::string& writeToLod3 )
        {
        // Load input geometry from file
        spWavefrontImporter objReader = sg->CreateWavefrontImporter();
        objReader->SetExtractGroups( false ); //This makes the .obj reader import into a single geometry object instead of multiple
        objReader->SetImportFilePath( readFrom.c_str() );
        if( !objReader->RunImport() )
            throw std::exception("Failed to load input file!");
    
        // Get the scene from the importer
        spScene originalScene = objReader->GetScene();
    
        // Create a copy of the original scene on which we will run the reduction
        spScene lodScene = originalScene->NewCopy();
    
        // Create the reduction-processor, and set the scene to reduce
        spReductionProcessor reductionProcessor = sg->CreateReductionProcessor();
        reductionProcessor->SetScene( lodScene );
    
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // SETTINGS 
    
        // The reduction settings object contains settings pertaining to the actual decimation
        spReductionSettings reductionSettings = reductionProcessor->GetReductionSettings();
        reductionSettings->SetReductionHeuristics( SG_REDUCTIONHEURISTICS_FAST ); //Choose between "fast" and "consistent" processing.
    
        // The normal calculation settings deal with the normal-specific reduction settings
        spNormalCalculationSettings normalSettings = reductionProcessor->GetNormalCalculationSettings();
        normalSettings->SetReplaceNormals( false ); //If true, this will turn off normal handling in the reducer and recalculate them all afterwards instead.
        //normalSettings->SetHardEdgeAngleInRadians( 3.14159f*70.0f/180.0f ); //If the normals are recalculated, this sets the hard-edge angle.
    
        // The actual reduction triangle target are controlled by these settings
        reductionSettings->SetStopCondition( SG_STOPCONDITION_ANY ); //The reduction stops when either of the targets is reached
        reductionSettings->SetReductionTargets( SG_REDUCTIONTARGET_ONSCREENSIZE );//The max deviation target determines when to stop the reduction. It is set in the loop below.
    
        //END SETTINGS 
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
        // Add progress observer
        reductionProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    
        //Create an .obj exporter to save our result
        spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
        objExporter->SetScene( lodScene ); //This is the geometry we set as the processing geom of the reducer
    
        //Set reduction targets using on-screen size
        uint onScreenSizeTargets[3];
        onScreenSizeTargets[0] = 500; //Gives a deviation of max 1 pixel at ~500 pixels on-screen
        onScreenSizeTargets[1] = 100; //Gives a deviation of max 1 pixel at ~100 pixels on-screen
        onScreenSizeTargets[2] = 50; //Gives a deviation of max 1 pixel at ~50 pixels on-screen
    
        //Generate the output filenames
        std::string outputGeomFilename[3];
        outputGeomFilename[0] = GetExecutablePath() + writeToLod1 + ".obj";
        outputGeomFilename[1] = GetExecutablePath() + writeToLod2 + ".obj";
        outputGeomFilename[2] = GetExecutablePath() + writeToLod3 + ".obj";
    
        // Run the iterative processing, saving the output geometry after every process
        for( int reductionIteration = 0; reductionIteration < 3; ++reductionIteration )
            {
            PrintProgressBar( 0 ); //Initial progress bar
    
            // The geometry still uses the same pointer, so it does not need to be re-set for the exporter or reducer after each pass.
            reductionSettings->SetOnScreenSize( onScreenSizeTargets[reductionIteration] ); //Stops when an error of the specified size has been reached. 
            reductionProcessor->RunProcessing();
    
            // Do the exporting
            objExporter->SetMaterialFilePath( NULL ); //Reset the material file path so it's set by ExportFilePath
            objExporter->SetExportFilePath( outputGeomFilename[reductionIteration].c_str() );
            objExporter->RunExport();
            }
    
        //Done! 3 cascaded LODs created.
        }
    
    Back to top Terms of Use | Privacy and cookies | Trademarks | Copyright © 2019 Microsoft