<< Click to Display Table of Contents >> Navigation: Simplygon 7.1 examples > SurfaceMappingExample.cpp |
///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: SurfaceMappingExample.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#
//
// 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[] )
{
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();
return 0;
}
void RunSurfaceTransferAndCasting(const std::string& readFromSource, const std::string& readFromDest, const std::string& writeTo)
{
// 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() )
return;
spGeometryData srcGeom = objReader->GetScene()->GetCombinedGeometry(); //Copies all geometry in the scene into a combined copy
spMaterialTable srcMaterials = objReader->GetScene()->GetMaterialTable();
// Get geometry and materials from importer. This is the "destination" geometry, ie. LOD1
objReader->SetImportFilePath(readFromDest.c_str());
if( !objReader->RunImport() )
return;
spGeometryData destGeom = objReader->GetScene()->GetCombinedGeometry(); //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.
// 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();
// 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
{
// Set the material properties
lodMaterial->SetColor( SG_MATERIAL_CHANNEL_AMBIENT , 1 , 1 , 1 , 1 );
lodMaterial->SetColor( SG_MATERIAL_CHANNEL_DIFFUSE , 1 , 1 , 1 , 1 );
lodMaterial->SetColor( SG_MATERIAL_CHANNEL_SPECULAR , 1 , 1 , 1 , 128 );
// Cast the data using a color caster
spColorCaster colorCaster = sg->CreateColorCaster();
colorCaster->SetSourceMaterials( srcMaterials );
colorCaster->SetDestMaterial( lodMaterial ); //This modulates the cast color with the base colors set for the dest material above.
//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->CastMaterials(); //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->CastMaterials(); //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->CastMaterials(); //Do the actual casting and write to texture.
lodMaterial->SetTexture( SG_MATERIAL_CHANNEL_DIFFUSE , "combinedAmbientMap.png" ); //Set material to point to the texture we cast to above
lodMaterial->SetTexture( SG_MATERIAL_CHANNEL_DIFFUSE , "combinedDiffuseMap.png" ); //Set material to point to the texture we cast to above
lodMaterial->SetTexture( SG_MATERIAL_CHANNEL_SPECULAR , "combinedSpecularMap.png" ); //Set material to point to the texture we cast to above
}
// 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->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->CastMaterials();
// Set normal map of the created material to point to the combined normal map
lodMaterial->SetTexture( 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);
// Do the actual exporting
objExporter->SetExportFilePath( outputGeomFilename.c_str() );
objExporter->SetScene(outScene);
objExporter->RunExport();
//Done! LOD and material created.
}