///////////////////////////////////////////////////////////////////////////
//
// 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.
}