Show / Hide Table of Contents
    ///////////////////////////////////////////////////////////////////////////
    //
    //  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.
        }
    
    Back to top Terms of Use | Privacy and cookies | Trademarks | Copyright © 2019 Microsoft