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