///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: RemeshingV2Example.cpp
// Language: C++
//
// Copyright (c) 2019 Microsoft. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////
//
// #Description# A remeshing example
//
// This example shows how to use the updated remeshing processor and its features
//
// The naming of the processor is meant to indicate that this processor will
// eventually replace the current RemeshingProcessor when it is feature complete,
// and is now supplied as a preview to show the new features.
//
// What differentiates the new remeshing processor from the old one is
// currently massively increased maximum OnScreenSize for generating very
// high quality meshes and intelligent hole filling and cavity removal.
//
// This example will demonstrate the functionality of the hole filling.
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
//Functions containing the Simplygon remeshing calls
void RunRemeshingProcessing(const std::string& readFrom, const std::string& writeTo, int onScreenSize, int mergeDistance, float mergeBoldness, int textureSize, bool bakeMaterials = true);
//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 remeshing processing on a character model with removed components and
// large gaps in its armor. The merge settings used will fill the holes
// in a smooth way and create a solid two-manifold geometry while
// using the old remesher or using 0 merge distance will generate
// a twosided mesh, wasting geometry and texture space.
printf("Running broken SimplygonMan...\n");
PrintProgressBar(0); //Initial progress bar
RunRemeshingProcessing(assetPath + "SimplygonMan/BrokenSimplygonManTorsoHalf.obj", "TorsoHalfHoleFilled", 500, 100, 0.7f, 1024);
printf("\nDone.\n\n");
// Run multiple remeshings on a test mesh with increasing mergeboldness
// without casting materials to demonstrate the effect on the output geometry
printf("Running sphere hole asset with increasing mergeboldness...\n");
PrintProgressBar(0);
RunRemeshingProcessing(assetPath + "spherehole.obj", "sphereholefilled_nomerge", 200, 0, 0, 1024, false);
PrintProgressBar(0);
RunRemeshingProcessing(assetPath + "spherehole.obj", "sphereholefilled_b" + std::to_string(0.0), 200, 100, 0.0, 1024, false);
PrintProgressBar(0);
RunRemeshingProcessing(assetPath + "spherehole.obj", "sphereholefilled_b" + std::to_string(0.5), 200, 100, 0.5, 1024, false);
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, float mergeBoldness, int textureSize, bool bakeMaterials)
{
//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
spRemeshingProcessorV2 remeshingProcessor = sg->CreateRemeshingProcessorV2();
remeshingProcessor->SetScene(scene);
///////////////////////////////////////////////////////////////////////////////////////////////
// SETTINGS
spRemeshingSettingsV2 remeshingSettings = remeshingProcessor->GetRemeshingSettingsV2();
//The most important setting, defines the "resolution" of the remeshing, i.e. tri-count.
//Works the same way as the setting in the old remesher
remeshingSettings->SetOnScreenSize(onScreenSize);
//Merge distance and merge boldness are used to control the holefilling and cavity removal.
//These settings exist in the old remesher, but what they actually do is quite different.
//In the previous remesher, these settings controlled what was essentially a morphologic close operation,
//often leaving the results "blobby", undefined, and lacking detail.
//In V2, they control an intelligent localized cavity removal algorithm that selectively fills
//holes and cavities with setting-dependent opening sizes and depths, and will not merge things that
//should not be merged.
//Defines how large the openings of the holes and cavities the processor will attempt to fill will be, in pixels.
remeshingSettings->SetMergeDistance(mergeDistance);
//Defines how deep cavities that should be filled are allowed to be.
//0 means almost nothing will be filled, 1 will fill more or less everything obviously concave.
remeshingSettings->SetMergeBoldness(mergeBoldness);
//These settings are optional algorithm switches which can be used to cut down processing time
//slightly at the cost of output consistency. Controlled with SG_MODE_ACCURATE and SG_MODE_FAST flags.
remeshingSettings->SetMeshGenerationMode(SG_MODE_ACCURATE);
remeshingSettings->SetSurfaceTransferMode(SG_MODE_ACCURATE); //SG_MODE_ACCURATE == SG_SURFACETRANSFER_ACCURATE from old processor
remeshingSettings->SetHardEdgeAngleInRadians(3.141f / 4.1f);
spMappingImageSettings mappingSettings = remeshingProcessor->GetMappingImageSettings();
mappingSettings->SetGenerateMappingImage(bakeMaterials);
mappingSettings->SetGenerateTexCoords(bakeMaterials);
mappingSettings->SetGenerateTangents(bakeMaterials);
mappingSettings->SetParameterizerMaxStretch(0.5f);
mappingSettings->SetGutterSpace(2);
mappingSettings->SetTexCoordLevel(0);
mappingSettings->SetWidth(textureSize);
mappingSettings->SetHeight(textureSize);
mappingSettings->SetMultisamplingLevel(2);
mappingSettings->SetMaximumLayers(3);
//END SETTINGS
///////////////////////////////////////////////////////////////////////////////////////////////
remeshingProcessor->AddObserver(&progressObserver, SG_EVENT_PROGRESS);
//Run the remeshing
remeshingProcessor->RunProcessing();
///////////////////////////////////////////////////////////////////////////////////////////////
// CASTING
spMappingImage mappingImage = remeshingProcessor->GetMappingImage();
spMaterial lodMaterial = sg->CreateMaterial();
lodMaterial->SetName("SimplygonBakedMaterial");
spTextureTable lodTextures = sg->CreateTextureTable();
// Cast diffuse and specular texture data with a color caster
if (bakeMaterials)
{
spColorCaster colorCaster = sg->CreateColorCaster();
colorCaster->SetSourceMaterials(materials);
colorCaster->SetSourceTextures(textures);
colorCaster->SetMappingImage(mappingImage);
colorCaster->SetOutputChannelBitDepth(8);
colorCaster->SetDilation(10);
colorCaster->SetColorType(SG_MATERIAL_CHANNEL_DIFFUSE);
colorCaster->SetOutputChannels(3);
colorCaster->SetOutputFilePath(outputDiffPath.c_str());
colorCaster->RunProcessing();
colorCaster->SetColorType(SG_MATERIAL_CHANNEL_SPECULAR);
colorCaster->SetOutputChannels(4); //RGBA, 4 channels, stores spec power in A
colorCaster->SetOutputFilePath(outputSpecPath.c_str());
colorCaster->RunProcessing();
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 (bakeMaterials)
{
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();
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.
}