ReductionExample.cpp

<< Click to Display Table of Contents >>

Navigation:  Simplygon 7.1 examples >

ReductionExample.cpp

///////////////////////////////////////////////////////////////////////////
//
//  System:    Simplygon
//  File:      ReductionExample.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
//
//  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[] )
    {
    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();
    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() )
        return;
    // 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() )
        return;
    // Get the scene from the importer
    spScene originalScene = objReader->GetScene();
    spMaterialTable originalMaterialTable = originalScene->GetMaterialTable();
    // 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();
    lodMaterialTable->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
        {
        // Set the material properties
        lodMaterial->SetColor( SG_MATERIAL_CHANNEL_AMBIENT , 0 , 0 , 0 , 0 );
        lodMaterial->SetColor( SG_MATERIAL_CHANNEL_DIFFUSE , 1 , 1 , 1 , 1 );
        lodMaterial->SetColor( SG_MATERIAL_CHANNEL_SPECULAR , 1 , 1 , 1 , 128 );
        //Note the 128 on the specular channels alpha. Simplygon bakes shininess
        //to the alpha channel of the specular map if the caster is set to 4 channel
        //output, and it is scaled between 0 and 1 internally. To get the correct
        //scale on the output, it should be multiplied by 128.
        // Cast the data using a color caster
        spColorCaster colorCaster = sg->CreateColorCaster();
        colorCaster->SetSourceMaterials( originalMaterialTable );
        colorCaster->SetDestMaterial( lodMaterial ); //This modulates the cast color with the base colors set for the dest material above.
        //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->CastMaterials(); //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->CastMaterials(); //Do the actual casting and write to texture.
        lodMaterial->SetTexture( SG_MATERIAL_CHANNEL_DIFFUSE , "combinedDiffuseMap.png" ); //Set material to point to the texture we cast to above
        lodMaterial->SetTexture( SG_MATERIAL_CHANNEL_SPECULAR , "combinedSpecularMap.png" ); //Set material to point to the texture we cast to above
        }
        // 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->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->CastMaterials();
        // Set normal map of the created material to point to the combined normal map
        lodMaterial->SetTexture( 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() )
        return;
    // 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.
    }