///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: SurfaceMappingExample.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#
//
// In this example, we demonstrate how to use the surface mapper to
// generate a mapping image between two user-defined geometries, which can
// then be used to cast materials and normals from the source to the destination.
// The surface mapper searches for the source geometry along the normal direction of
// the destination geometry, from the outside going in, and maps the geometry accordingly.
//
// In this example, materials and normals from an open box containing a teapot
// are transfered to a cube of matching dimensions.
//
// This is useful if you have some very specific geometric requirements for your LODs,
// and hence need to create the geometry manually, but still want to easily cast materials.
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
void RunSurfaceTransferAndCasting( const std::string& readFromSource, const std::string& readFromDest, const std::string& writeTo );
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 surface mapping, and use mapping to cast textures and normals
printf("\nRunning surface mapping and casting... ");
RunSurfaceTransferAndCasting(assetPath + "ObscuredTeapot/ObscuredTeapot.obj", assetPath + "ObscuredTeapot/MatchingBox.obj", "ObscuredTeapotTransfered.obj");
printf("Done.\n");
// Done!
printf("\nAll LODs complete, shutting down...");
DeinitExample();
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
return 0;
}
void RunSurfaceTransferAndCasting( const std::string& readFromSource, const std::string& readFromDest, const std::string& writeTo )
{
float PI = 3.14159f;
// Load input geometries from file
spWavefrontImporter objReader = sg->CreateWavefrontImporter();
objReader->SetExtractGroups( false ); //This makes the .obj reader import into a single geometry object instead of multiple
// Get geometry and materials from importer. This is the "source" geometry, ie. LOD0
objReader->SetImportFilePath( readFromSource.c_str() );
if( !objReader->RunImport() )
throw std::exception("Failed to load input file!");
spGeometryData srcGeom = objReader->GetScene()->NewCombinedGeometry(); //Copies all geometry in the scene into a combined copy
spMaterialTable srcMaterials = objReader->GetScene()->GetMaterialTable();
spTextureTable srcTextures = objReader->GetScene()->GetTextureTable();
// Get geometry and materials from importer. This is the "destination" geometry, ie. LOD1
objReader->SetImportFilePath( readFromDest.c_str() );
if( !objReader->RunImport() )
throw std::exception("Failed to load input file!");
spGeometryData destGeom = objReader->GetScene()->NewCombinedGeometry(); //Copies all geometry in the scene into a combined copy
spMaterialTable destMaterials = objReader->GetScene()->GetMaterialTable(); //This material is actually overwritten later, so loading it is not really important.
spTextureTable destTextures = objReader->GetScene()->GetTextureTable();
// Create a unique parameterization for the destination geometry, ie. a parameterization where each point on the mesh has a unique UV. No tiling.
spParameterizer par = sg->CreateParameterizer();
par->SetTextureWidth( 1024 ); //This just makes sure the pixel density is consistent with the output images cast, any parameterization will work.
par->SetTextureHeight( 512 );
par->Parameterize( destGeom, destGeom->GetTexCoords( 0 ) );
// Make sure normals and tangents exist on the geometries, to make sure normal maps get cast correctly
spNormalRepairer normRep = sg->CreateNormalRepairer();
normRep->SetGeometry( destGeom );
normRep->RunProcessing();
normRep->SetGeometry( srcGeom );
normRep->RunProcessing();
spTangentCalculator tanCalc = sg->CreateTangentCalculator();
tanCalc->CalculateTangents( destGeom );
tanCalc->CalculateTangents( srcGeom );
//Do the surface mapping!
spSurfaceMapper surfMap = sg->CreateSurfaceMapper();
surfMap->SetSourceGeometry( srcGeom );
surfMap->SetDestinationGeometry( destGeom );
surfMap->SetDestinationTexCoordSet( 0 );
//The surface mapper searches for the source geometry along the normal direction of the destination geometry, from the outside going in.
//SearchOffset determines how far outside the destination surface the search will start, and SearchDistance determines at what point it will stop looking.
//These two will be set internally to sane defaults based on the geometry size if they are set to values < 0.
surfMap->SetSearchOffset( -1.f );
surfMap->SetSearchDistance( -1.f );
//If RecalculateSearchDirection is off, the existing normals of the destination geometry will determine the search direction, and if it is on, new search directions
//will be calculated internally, with the hard edge angle set below.
//This is very important to how the end mapping will turn out, since it basically determines the projection of the source geometry onto the destination geometry.
//"Smooth" normals will, in the case of a cube, create a strange spherical projection, while hard normals will create orthogonal projection. Best use will depend on case.
surfMap->SetRecalculateSearchDirection( true );
surfMap->SetSearchDirectionHardEdgeAngleInRadians( (real)PI / 3.f ); //Since our destination is a cube, this will make the search direction "orthogonal" for all faces.
//The surface mappers mapping image settings object contains the output texture size, as well as supersampling settings.
//Since the mapping image settings are used for other processors, there are other settings in there, but none relevant to surface mapping.
surfMap->GetMappingImageSettings()->SetWidth( 1024 );
surfMap->GetMappingImageSettings()->SetHeight( 512 );
surfMap->GetMappingImageSettings()->SetMultisamplingLevel( 2 );
//This call runs the actual processing
surfMap->RunSurfaceMapping();
///////////////////////////////////////////////////////////////////////////////////////////////
// CASTING
//Something to keep in mind for this part is that some parts of the geometry might have failed to map.
//However, in most cases we can fix this by just increasing the dilation of the texture casting, since that will fill
//the empty space with "appropriate" pixel values!
spMappingImage mappingImage = surfMap->GetMappingImage();
// First, clear the old destination material table
destMaterials->Clear();
destTextures->Clear();
// Create new material for the table.
spMaterial lodMaterial = sg->CreateMaterial();
lodMaterial->SetName( "SimplygonBakedMaterial" );
destMaterials->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( srcMaterials );
colorCaster->SetSourceTextures( srcTextures );
//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( 100 ); //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_AMBIENT );
colorCaster->SetOutputChannels( 3 ); //RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
colorCaster->SetOutputFilePath( "combinedAmbientMap.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_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, destTextures, SG_MATERIAL_CHANNEL_AMBIENT, "combinedAmbientMap.png" );
AddSimplygonTexture( lodMaterial, destTextures, SG_MATERIAL_CHANNEL_DIFFUSE, "combinedDiffuseMap.png" );
AddSimplygonTexture( lodMaterial, destTextures, 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( srcMaterials );
normalCaster->SetSourceTextures( srcTextures );
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( 100 );
normalCaster->SetOutputFilePath( "combinedNormalMap.png" );
normalCaster->SetFlipBackfacingNormals( true );
normalCaster->SetGenerateTangentSpaceNormals( true );
normalCaster->RunProcessing();
// Set normal map of the created material to point to the combined normal map
AddSimplygonTexture( lodMaterial, destTextures, SG_MATERIAL_CHANNEL_NORMALS, "combinedNormalMap.png" );
}
// END CASTING
///////////////////////////////////////////////////////////////////////////////////////////////
// Set all material IDs to 0 in the destination geom, just in case they weren't already
spRidArray materialIdsArray = destGeom->GetMaterialIds();
for( unsigned int i = 0; i < destGeom->GetTriangleCount(); i++ )
{
materialIdsArray->SetItem( i, 0 );
}
//Create an .obj exporter to save our result
spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
// Generate the output filenames
std::string outputGeomFilename = GetExecutablePath() + writeTo;
spScene outScene = sg->CreateScene();
outScene->GetRootNode()->CreateChildMesh( destGeom );
outScene->GetMaterialTable()->Copy( destMaterials );
outScene->GetTextureTable()->Copy( destTextures );
// Do the actual exporting
objExporter->SetExportFilePath( outputGeomFilename.c_str() );
objExporter->SetScene( outScene );
objExporter->RunExport();
//Done! LOD and material created.
}