SurfaceMappingExample.cpp

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