///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: RemeshingExample.cpp
// Language: C++
//
// Copyright (c) 2019 Microsoft. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////
//
// #Description# A remeshing example
//
// This example shows how to use the remeshing API in Simplygon.
//
// First, an asset with transparencies and internal geometry is remeshed
// to demonstrate the remesher's ability to remove internal geometry and
// project the detail of said geometry onto transparent surfaces in the output
// textures.
//
// Secondly, a brick wall asset is processed with a high merge distance,
// demoing the remesher's ability to fill cavities in meshes and generate
// nice, two-manifold outputs.
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
//Functions containing the Simplygon remeshing calls
void RunRemeshingProcessing( const std::string& readFrom, const std::string& writeTo, int onScreenSize, int mergeDistance, int textureSize );
void RunMaterialSeparatedRemeshingProcessing( const std::string& readFrom, const std::string& writeTo, int onScreenSize, int textureSize );
//Progress observer
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;
/////////////////////////////////////////////////////////////////////
// Main function with startup and shutdown code
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 console mesh processing, will project interior onto transparent window
printf("Running console asset, projecting interior...\n");
PrintProgressBar(0); //Initial progress bar
RunRemeshingProcessing(assetPath + "Console/console.obj", "console_remeshed", 300, 0, 512);
printf("\nDone.\n\n");
printf("Running console asset, meshing materials separately...\n");
PrintProgressBar(0); //Initial progress bar
RunMaterialSeparatedRemeshingProcessing(assetPath + "Console/console.obj", "console_separated_remeshed", 300, 512);
printf("\nDone.\n\n");
// Run brick wall mesh processing, merge distance 16
printf("Running brick wall asset, high merge distance to fuse the individual bricks...\n ");
PrintProgressBar(0); //Initial progress bar
RunRemeshingProcessing(assetPath + "wall.obj", "wall_remeshed", 300, 16, 512);
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;
}
//////////////////////////////////////////////////////////////////////
//Takes a .obj path and remeshes the asset with the parameters below
void RunRemeshingProcessing( const std::string& readFrom, const std::string& writeTo, int onScreenSize, int mergeDistance, int textureSize )
{
//Setup output paths
std::string outputGeomPath = GetExecutablePath() + writeTo + ".obj";
std::string outputDiffPath = GetExecutablePath() + writeTo + "_d.png";
std::string outputSpecPath = GetExecutablePath() + writeTo + "_s.png";
std::string outputNormPath = GetExecutablePath() + writeTo + "_n.png";
//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 scene = objReader->GetScene();
spMaterialTable materials = scene->GetMaterialTable();
spTextureTable textures = scene->GetTextureTable();
//Create a remeshing processor
spRemeshingProcessor remeshingProcessor = sg->CreateRemeshingProcessor();
///////////////////////////////////////////////////////////////////////////////////////////////
// SETTINGS
remeshingProcessor->SetScene( scene ); //Defines the scene on which to run the remesher
//Geometry related settings:
spRemeshingSettings remeshingSettings = remeshingProcessor->GetRemeshingSettings();
//remeshingSettings->SetProcessSelectionSetID(-1); //Can be used to remesh only a specific selection set defined in the scene
remeshingSettings->SetOnScreenSize( onScreenSize ); //The most important setting, defines the "resolution" of the remeshing, i.e. tri-count
remeshingSettings->SetMergeDistance( mergeDistance ); //Defines how large gaps to fill in, in pixels. Relative to the setting above.
remeshingSettings->SetSurfaceTransferMode( SG_SURFACETRANSFER_ACCURATE ); //This toggles between the two available surface mapping modes
//remeshingSettings->SetTransferColors(false); //Sets if the remesher should transfer the old vertex colors to the new geometry, if availible
//remeshingSettings->SetTransferNormals(false) //Sets if the remesher should transfer the old normals to the new geometry (generally a bad idea since the geometries don't match exactly)
remeshingSettings->SetHardEdgeAngleInRadians( 3.141f / 2.1f ); //Sets the normal hard edge angle, used for normal recalc if TransferNormals is off. Here, slightly lower than 90 degrees.
//remeshingSettings->SetUseCuttingPlanes( false ); //Defines whether to use cutting planes or not. Planes are defined in the scene object.
//remeshingSettings->SetCuttingPlaneSelectionSetID( -1 ); //Sets a selection set for cutting planes, if you don't want to use all of the planes in the scene.
remeshingSettings->SetUseEmptySpaceOverride( false ); //Overrides what the remesher considers to be "outside", so you can do interiors. Set coord with SetEmptySpaceOverride.
//remeshingSettings->SetMaxTriangleSize( 10 ); //Can be used to limit the triangle size of the output mesh, producing more triangles. Can be useful for exotic use cases.
//Parameterization and material casting related settings
spMappingImageSettings mappingSettings = remeshingProcessor->GetMappingImageSettings();
//mappingSettings->SetUseFullRetexturing( true ); //Kind of irrelevant for the remesher, since it always is a "full retexturing"
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 tangents and bitangents.
mappingSettings->SetParameterizerMaxStretch( 0.8f ); //The higher the number, the fewer texture-borders. Also introduces more stretch, obviously.
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.
mappingSettings->SetWidth( textureSize );
mappingSettings->SetHeight( textureSize );
mappingSettings->SetMultisamplingLevel( 2 );
mappingSettings->SetMaximumLayers( 3 ); //IMPORTANT! This setting defines how many transparant layers the remesher will project onto the outermost surface of the remeshed geom,
//and hence, how many layers will be in the generated mapping image
//END SETTINGS
///////////////////////////////////////////////////////////////////////////////////////////////
remeshingProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
//Run the remeshing
remeshingProcessor->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 = remeshingProcessor->GetMappingImage();
// Now, for each channel, we want to cast the input materials into a single output material, with one texture per channel.
// Create new material for the table.
spMaterial lodMaterial = sg->CreateMaterial();
lodMaterial->SetName( "SimplygonBakedMaterial" );
// Make a new tex table
spTextureTable lodTextures = sg->CreateTextureTable();
// Cast diffuse and specular texture data with a color caster
if( true )
{
// Cast the data using a color caster
spColorCaster colorCaster = sg->CreateColorCaster();
colorCaster->SetSourceMaterials( materials );
colorCaster->SetSourceTextures( textures );
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( outputDiffPath.c_str() ); //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( outputSpecPath.c_str() ); //Where the texture map will be saved to file.
colorCaster->RunProcessing(); //Do the actual casting and write to texture.
AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_DIFFUSE, outputDiffPath.c_str() );
AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_SPECULAR, outputSpecPath.c_str() );
}
// Cast normal map texture data with the normal caster. This also compensates for any geometric errors that have appeared in the reduction process.
if( true )
{
// cast the data using a normal caster
spNormalCaster normalCaster = sg->CreateNormalCaster();
normalCaster->SetSourceMaterials( materials );
normalCaster->SetSourceTextures( textures );
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( outputNormPath.c_str() );
normalCaster->SetFlipBackfacingNormals( false );
normalCaster->SetGenerateTangentSpaceNormals( true );
normalCaster->RunProcessing();
// Set normal map of the created material to point to the combined normal map
AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_NORMALS, outputNormPath.c_str() );
}
//Now, we can clear the original material table in the scene, and replace its contents with our new lodMaterial
materials->Clear();
materials->AddMaterial( lodMaterial ); //This will be added at matId 0, which will match the remeshed geometry
//Also, replace the texture list from the original with the new one
textures->Copy( lodTextures );
// END CASTING
///////////////////////////////////////////////////////////////////////////////////////////////
//Create an .obj exporter to save our result
spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
// Do the exporting
objExporter->SetExportFilePath( outputGeomPath.c_str() );
objExporter->SetScene( scene ); //scene now contains the remeshed geometry and the material table we modified above
objExporter->RunExport();
//Done! LOD and material created.
}
//Takes a .obj path and remeshes the asset with the parameters below. Generates separate geometry for each input material in the
//original, which means the sub-meshes that together make up a single input material are shrink-wrapped separately from the rest of the mesh.
void RunMaterialSeparatedRemeshingProcessing( const std::string& readFrom, const std::string& writeTo, int onScreenSize, int textureSize )
{
std::string outputGeomPath = GetExecutablePath() + writeTo + ".obj";
//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 scene = objReader->GetScene();
spMaterialTable materials = scene->GetMaterialTable();
spTextureTable textures = scene->GetTextureTable();
//Create a remeshing processor
spRemeshingProcessor remeshingProcessor = sg->CreateRemeshingProcessor();
///////////////////////////////////////////////////////////////////////////////////////////////
// SETTINGS
remeshingProcessor->SetScene( scene ); //Defines the scene on which to run the remesher
//Geometry related settings:
spRemeshingSettings remeshingSettings = remeshingProcessor->GetRemeshingSettings();
remeshingSettings->SetOnScreenSize( onScreenSize ); //The most important setting, defines the "resolution" of the remeshing, i.e. tri-count
spMappingImageSettings mappingSettings = remeshingProcessor->GetMappingImageSettings();
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 tangents and bitangents.
mappingSettings->SetParameterizerMaxStretch( 0.8f ); //The higher the number, the fewer texture-borders. Also introduces more stretch, obviously.
mappingSettings->SetTexCoordLevel( 0 ); //Sets the output texcoord level.
mappingSettings->SetMaximumLayers( 3 );
//------------------MATERIAL MAPPING SETTINGS
//--Each output material will be meshed separately and assigned its own mapping image, enabling you to handle
//--transparency and other special case materials. Since the geometries are shrink-wrapped separately, you may get
//--double-sided results depending on if your per-material submeshes are watertight.
//--Enabling visibility culling can get rid of some of this.
mappingSettings->SetInputMaterialCount( materials->GetItemsCount() );
mappingSettings->SetOutputMaterialCount( materials->GetItemsCount() );
for( uint m = 0; m < materials->GetItemsCount(); ++m )
{
mappingSettings->SetInputOutputMaterialMapping( m, m ); //This just means we want the same number of output materials as input materials.
}
//Set settings for our output materials
for( uint newM = 0; newM < materials->GetItemsCount(); ++newM )
{
mappingSettings->SetGutterSpace( newM, 2 );
mappingSettings->SetMultisamplingLevel( newM, 2 );
mappingSettings->SetWidth( newM, textureSize );
mappingSettings->SetHeight( newM, textureSize );
}
////Example usage:
////This would generate an output where materials 0-3 ends up in output material 0, and 4 ends up in output material 2.
//mappingSettings->SetInputMaterialCount( 5 );
//mappingSettings->SetInputMaterialCount( 2 );
//mappingSettings->SetInputOutputMaterialMapping( 0, 0 );
//mappingSettings->SetInputOutputMaterialMapping( 1, 0 );
//mappingSettings->SetInputOutputMaterialMapping( 2, 0 );
//mappingSettings->SetInputOutputMaterialMapping( 3, 0 );
//mappingSettings->SetInputOutputMaterialMapping( 4, 1 );
//------------------END MATERIAL MAPPING SETTINGS
//END SETTINGS
///////////////////////////////////////////////////////////////////////////////////////////////
remeshingProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
//Run the remeshing
remeshingProcessor->RunProcessing();
///////////////////////////////////////////////////////////////////////////////////////////////
// CASTING
// Make a new tex table
spTextureTable lodTextures = sg->CreateTextureTable();
spMaterialTable lodMaterials = sg->CreateMaterialTable();
//Now we need to do the casting per output material.
//Some output materials might have ended up with no triangles, in these cases the mapping image returned will be null
//Material Ids in the output geometry will correspond to the indices of the mapping images
for( uint matId = 0; matId < materials->GetItemsCount(); ++matId )
{
spMaterial lodMaterial = sg->CreateMaterial();
lodMaterial->SetName( std::to_string( matId ).c_str() );
// Cast diffuse and specular texture data with a color caster
if( !remeshingProcessor->GetMappingImage( matId ).IsNull() )
{
// Cast the data using a color caster
spColorCaster colorCaster = sg->CreateColorCaster();
colorCaster->SetSourceMaterials( materials );
colorCaster->SetSourceTextures( textures );
colorCaster->SetMappingImage( remeshingProcessor->GetMappingImage( matId ) );
colorCaster->SetOutputChannelBitDepth( 8 );
colorCaster->SetDilation( 10 );
colorCaster->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE );
colorCaster->SetOutputChannels( 3 );
colorCaster->SetOutputFilePath( ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_d.png" ).c_str() ); //Some ugly string wrangling to make sure we get unique texture paths
colorCaster->RunProcessing();
colorCaster->SetColorType( SG_MATERIAL_CHANNEL_SPECULAR );
colorCaster->SetOutputChannels( 3 );
colorCaster->SetOutputFilePath( ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_s.png" ).c_str() );
colorCaster->RunProcessing();
//These are just convenience functions to add the generated textures to our material structures
AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_DIFFUSE, ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_d.png" ).c_str(), std::to_string( matId ).c_str() );
AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_SPECULAR, ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_s.png" ).c_str(), std::to_string( matId ).c_str() );
}
// Cast normal map texture data with the normal caster. This also compensates for any geometric errors that have appeared in the reduction process.
if( !remeshingProcessor->GetMappingImage( matId ).IsNull() )
{
// cast the data using a normal caster
spNormalCaster normalCaster = sg->CreateNormalCaster();
normalCaster->SetSourceMaterials( materials );
normalCaster->SetSourceTextures( textures );
normalCaster->SetMappingImage( remeshingProcessor->GetMappingImage( matId ) );
normalCaster->SetOutputChannels( 3 );
normalCaster->SetOutputChannelBitDepth( 8 );
normalCaster->SetDilation( 10 );
normalCaster->SetOutputFilePath( ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_n.png" ).c_str() );
normalCaster->SetFlipBackfacingNormals( false );
normalCaster->SetGenerateTangentSpaceNormals( true );
normalCaster->RunProcessing();
//These are just convenience functions to add the generated textures to our material structures
AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_NORMALS, ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_n.png" ).c_str(), std::to_string( matId ).c_str() );
}
if( !remeshingProcessor->GetMappingImage( matId ).IsNull() )
{
// cast the data using a normal caster
spOpacityCaster opCaster = sg->CreateOpacityCaster();
opCaster->SetSourceMaterials( materials );
opCaster->SetSourceTextures( textures );
opCaster->SetMappingImage( remeshingProcessor->GetMappingImage( matId ) );
opCaster->SetOutputChannels( 1 );
opCaster->SetOutputChannelBitDepth( 8 );
opCaster->SetDilation( 10 );
opCaster->SetOutputFilePath( ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_o.png" ).c_str() );
opCaster->RunProcessing();
// Set normal map of the created material to point to the combined normal map
AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_OPACITY, ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_o.png" ).c_str(), std::to_string( matId ).c_str() );
}
//Add one new material per output mapping image
lodMaterials->AddMaterial( lodMaterial );
}
//Also, replace the texture and material lists from the original with the new one
textures->Copy( lodTextures );
materials->Copy( lodMaterials );
// END CASTING
///////////////////////////////////////////////////////////////////////////////////////////////
//Create an .obj exporter to save our result
spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
// Do the exporting
objExporter->SetExportFilePath( outputGeomPath.c_str() );
objExporter->SetScene( scene ); //scene now contains the remeshed geometry and the material table we modified above
objExporter->RunExport();
//Done! LOD and material created.
}