Show / Hide Table of Contents
    ///////////////////////////////////////////////////////////////////////////
    //
    //  System:    Simplygon
    //  File:      MaterialCastingExample.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# 
    //  
    //  A scene is loaded from a wavefront file. It has multiple materials with
    //  diffuse and specular channels. A custom user channel is added.
    //
    //  The geometries are reduced and a mapping image is created.
    //
    //  Using the mapping image, the original materials are casted to a single new 
    //  material, reducing the number of materials for the processed scene. 
    //  A tangent space normal map is also created to mimic the removed geometry.
    //  
    //  A custom user channel texture is created for the original scene and casted
    //  to the processed scene.
    //
    //  The processed scene and textures are saved to file.
    //
    ///////////////////////////////////////////////////////////////////////////
    
    //For demonstration purposes, enabling this flag converts the materials supplied by the .obj reader, 
    //(which are defined using the legacy material system) to the new, node-based system. 
    //Texture path references are replaced by single texture nodes, and a texture table is generated for casting.
    #define USE_SHADING_NETWORKS 0
    
    #include "../Common/Example.h"
    
    void RunExampleCasting( const std::string& readFrom, const std::string& writeTo );
    
    spImageData GenerateGradientImage( unsigned int image_width, unsigned int image_height );
    float clamp( float value, float min, float max );
    int clamp( int value, int min, int max );
    
    int main( int argc, char* argv[] )
        {
        try
        {
            InitExample();
    
            // Set global variable. Using Orthonormal method for calculating
            // tangentspace.
            sg->SetGlobalSetting("DefaultTBNType", SG_TANGENTSPACEMETHOD_ORTHONORMAL);
    
            std::string assetPath = GetAssetPath();
    
            // Run the example code
            RunExampleCasting(assetPath + "Helmet/helmet.obj", "helmetLOD");
    
            DeinitExample();
        }
        catch (const std::exception& ex)
        {
            std::cerr << ex.what() << std::endl;
            return -1;
        }
    
        return 0;
        }
    
    void RunExampleCasting( const std::string& readFrom, const std::string& writeTo )
        {
        // Import scene from a file and optimize it. Generate a mapping image from
        // the original geometries and use it to cast diffuse, specular and normal maps
        // to the processed scene.
    
        std::string exePath = GetExecutablePath();
    
        std::string output_geometry_filename = exePath + writeTo + ".obj";
        std::string output_diffuse_filename = exePath + writeTo + "_diffuse.png";
        std::string output_specular_filename = exePath + writeTo + "_specular.png";
        std::string output_normals_filename = exePath + writeTo + "_normals.png";
        std::string output_userchannel_filename = exePath + writeTo + "_userchannel.png";
    
        //Load object from file
        spWavefrontImporter objReader = sg->CreateWavefrontImporter();
        objReader->SetImportFilePath( readFrom.c_str() );
        if( !objReader->RunImport() )
            throw std::exception("Failed to load input file!");
    
        //Get the scene
        spScene scene = objReader->GetScene();
    
        //Get material table and add a custom user channel
        spMaterialTable original_materials = scene->GetMaterialTable();
        spTextureTable original_textures = scene->GetTextureTable();
        original_materials->GetMaterial( 0 )->AddUserChannel( "user_channel" );
        original_materials->GetMaterial( 1 )->AddUserChannel( "user_channel" );
    
    
        //Create an ImageData with the user_channel texture
        spImageData imgUserChannelGradient = GenerateGradientImage( 512, 512 );
        spTexture userChannelTexture = sg->CreateTexture();
        userChannelTexture->SetName( "user_channel" );
        userChannelTexture->SetImageData( imgUserChannelGradient );
    
        original_textures->AddTexture( userChannelTexture );
    
        //Make a texture node and set as input in the material
        spShadingTextureNode userNode = sg->CreateShadingTextureNode();
        userNode->SetTextureName( "user_channel" );
        original_materials->GetMaterial( 0 )->SetShadingNetwork( "user_channel", userNode );
        original_materials->GetMaterial( 1 )->SetShadingNetwork( "user_channel", userNode );
    
        //Reducer
        spReductionProcessor red = sg->CreateReductionProcessor();
        red->SetScene( scene );
    
        // Set the Repair Settings.
        spRepairSettings repair_settings = red->GetRepairSettings();
        repair_settings->SetWeldDist( 0.0f );
        repair_settings->SetTjuncDist( 0.0f );
    
        // Set the Reduction Settings.
        spReductionSettings reduction_settings = red->GetReductionSettings();
    
        //Reduce the triangle count
        reduction_settings->SetTriangleRatio( 0.3f );
    
        //Keep symmetry
        reduction_settings->SetKeepSymmetry( true );
        reduction_settings->SetUseAutomaticSymmetryDetection( true );
    
        // Set the Normal Calculation Settings.
        spNormalCalculationSettings normal_settings = red->GetNormalCalculationSettings();
        normal_settings->SetReplaceNormals( true );
        normal_settings->SetHardEdgeAngleInRadians( 3.14159f*70.0f / 180.0f );
    
    
        // Set the Image Mapping Settings.
        spMappingImageSettings mapping_settings = red->GetMappingImageSettings();
        // Without this we cannot fetch data from the original geometry, and thus not
        // generate diffuse, specular, normal maps and custom channel later.
        mapping_settings->SetGenerateMappingImage( true );
        // Set to generate new texture coordinates.
        mapping_settings->SetGenerateTexCoords( true );
        // The higher the number, the fewer texture-borders.
        mapping_settings->SetParameterizerMaxStretch( 0.3f );
        // Buffer space for when texture is mip-mapped, so color values dont blend over.
        mapping_settings->SetGutterSpace( 1 );
    
        mapping_settings->SetWidth( 1024 );
        mapping_settings->SetHeight( 1024 );
        mapping_settings->SetMultisamplingLevel( 2 );
    
        red->RunProcessing();
    
        // Mapping image is needed later on for texture casting.
        spMappingImage mapping_image = red->GetMappingImage();
    
        // Create new material table.
        spMaterialTable output_materials = sg->CreateMaterialTable();
        spTextureTable output_textures = sg->CreateTextureTable();
    
        //  Create new material for the table.
        spMaterial output_material = sg->CreateMaterial();
        output_material->SetName( "output_material" );
        output_material->AddUserChannel( "user_channel" );
    
        //Add the new material to the table
        output_materials->AddMaterial( output_material );
    
        // DIFFUSE
        // Create a color caster to cast the diffuse texture data
        spColorCaster ColorCaster = sg->CreateColorCaster();
        ColorCaster->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE ); //Select the diffuse channel from the original material 
        ColorCaster->SetSourceMaterials( original_materials );
        ColorCaster->SetSourceTextures( original_textures ); //If we are casting materials defined by shading networks, a source texture table also needs to be set
        ColorCaster->SetMappingImage( mapping_image ); // The mapping image we got from the reduction process, reduced to half-width/height, just for testing purposes  
        ColorCaster->SetOutputChannels( 3 ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
        ColorCaster->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB.
        ColorCaster->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
        ColorCaster->SetOutputFilePath( output_diffuse_filename.c_str() ); // Where the texture map will be saved to file.
        ColorCaster->RunProcessing();
    
        // set the material properties 
        // Set material to point to created texture filename.
        AddSimplygonTexture( output_material, output_textures, SG_MATERIAL_CHANNEL_DIFFUSE, output_diffuse_filename.c_str() );
    
        // SPECULAR
        // Modify the color caster to cast specular texture data
        ColorCaster->SetColorType( SG_MATERIAL_CHANNEL_SPECULAR ); //Select the specular channel from the original material 
        ColorCaster->SetOutputFilePath( output_specular_filename.c_str() ); // Where the texture map will be saved to file.
        ColorCaster->RunProcessing();
    
        // set the material properties 
        // Set material to point to created texture filename.
        AddSimplygonTexture( output_material, output_textures, SG_MATERIAL_CHANNEL_SPECULAR, output_specular_filename.c_str() );
    
    
        // USER CHANNEL
        ColorCaster->SetColorType( "user_channel" ); //Select the diffuse channel from the original material
        ColorCaster->SetOutputFilePath( output_userchannel_filename.c_str() ); // Where the texture map will be saved to file.
        ColorCaster->RunProcessing();
    
        // set the material properties 
        // Set material to point to created texture filename.
        AddSimplygonTexture( output_material, output_textures, "user_channel", output_userchannel_filename.c_str() );
    
        // NORMAL MAP
        // cast the normal map texture data
        spNormalCaster NormalCaster = sg->CreateNormalCaster();
        NormalCaster->SetSourceMaterials( original_materials );
        NormalCaster->SetSourceTextures( original_textures ); //If we are casting materials defined by shading networks, a source texture table also needs to be set
        NormalCaster->SetMappingImage( mapping_image );
        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( output_normals_filename.c_str() );
        NormalCaster->SetFlipBackfacingNormals( true );
        NormalCaster->SetGenerateTangentSpaceNormals( true );
        NormalCaster->RunProcessing();
    
        // Set material to point to created texture filename.
        AddSimplygonTexture( output_material, output_textures, SG_MATERIAL_CHANNEL_NORMALS, output_normals_filename.c_str() );
    
        //Overwrite the scene's material table with the casted materials
        scene->GetMaterialTable()->Clear();
        scene->GetMaterialTable()->Copy( output_materials );
        scene->GetTextureTable()->Clear();
        scene->GetTextureTable()->Copy( output_textures );
    
        //Store the finished processed scene
        spWavefrontExporter objexp = sg->CreateWavefrontExporter();
        objexp->SetExportFilePath( output_geometry_filename.c_str() );
        objexp->SetScene( scene );
        objexp->RunExport();
        }
    
    spImageData GenerateGradientImage( unsigned int image_width, unsigned int image_height )
        {
        spImageData img = sg->CreateImageData();
    
        //Set imagedata dimensions
        img->Set2DSize( image_width, image_height );
    
        //Add rgb colors to imagedata
        img->AddColors( TYPES_ID_UCHAR, "RGB" );
    
        //Get the reference to the colors to array with tuple size 3, tuple count (image_width x image_height)
        spUnsignedCharArray colors = SafeCast<IUnsignedCharArray>( img->GetColors() );
    
        //Gradient texture parameters
        int gradient_RL = 255;
        int gradient_GL = 255;
        int gradient_BL = 255;
    
        int gradient_RR = 0;
        int gradient_GR = 0;
        int gradient_BR = 0;
    
        //Iterate over the image pixels and set the array values
        for( unsigned int y = 0; y < image_height; y++ )
            {
            for( unsigned int x = 0; x < image_width; x++ )
                {
                //One dimensional index
                int i = image_width * y + x;
    
                //Gradient colors to array
                colors->SetItem( i * 3 + 0, clamp( (int)(((image_width - x) * gradient_RL + x * gradient_RR) / image_width), 0, 255 ) );
                colors->SetItem( i * 3 + 1, clamp( (int)(((image_width - x) * gradient_GL + x * gradient_GR) / image_width), 0, 255 ) );
                colors->SetItem( i * 3 + 2, clamp( (int)(((image_width - x) * gradient_BL + x * gradient_BR) / image_width), 0, 255 ) );
    
                colors->SetItem( i * 3 + 0, (x * 255) / image_width );
                colors->SetItem( i * 3 + 1, (x * 255) / image_width );
                colors->SetItem( i * 3 + 2, (x * 255) / image_width );
                }
            }
    
        //Save generated image to file for demo purposes, the caster will just use the image data that's already in memory
        spImageDataExporter imExp = sg->CreateImageDataExporter();
        imExp->SetImage( img );
        imExp->SetExportFilePath( "gradient_texture.png" );
        imExp->RunExport();
    
        return img;
        }
    
    float clamp( float value, float min, float max )
        {
        return value < min ? min : (value > max ? max : value);
        }
    
    int clamp( int value, int min, int max )
        {
        return value < min ? min : (value > max ? max : value);
        }
    
    Back to top Terms of Use | Privacy and cookies | Trademarks | Copyright © 2019 Microsoft