MaterialCastingExample.cpp

<< Click to Display Table of Contents >>

Navigation:  Simplygon 7.1 examples >

MaterialCastingExample.cpp

///////////////////////////////////////////////////////////////////////////
//
//  System:    Simplygon
//  File:      MaterialCastingExample.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#
// 
//  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[] )
    {
    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();
    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() )
        return;
    //Get the scene
    spScene scene = objReader->GetScene();
    //Get material table and add a custom user channel
    spMaterialTable original_materials = scene->GetMaterialTable();
    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);
    original_materials->GetMaterial(0)->SetTextureImage("user_channel", imgUserChannelGradient);
    original_materials->GetMaterial(1)->SetTextureImage("user_channel", imgUserChannelGradient);
#if USE_SHADING_NETWORKS == 1
    //Convert legacy materials to node based
    spTextureTable texTable = sg->CreateTextureTable();
    const char* channels[4] = {SG_MATERIAL_CHANNEL_DIFFUSE, SG_MATERIAL_CHANNEL_SPECULAR, SG_MATERIAL_CHANNEL_NORMALS, "user_channel"};
    for(uint i = 0; i < original_materials->GetItemsCount(); ++i)
        {
        //This'll make a new shading node for each channel and assign it, and a texture will be added to the node and texture table
        for(uint c = 0; c < 4; ++c)
            {
            //Either the path or the imagedata should be set for each material channel.
            rstring texPath = original_materials->GetMaterial(i)->GetTexture(channels[c]);
            spImageData texImg = original_materials->GetMaterial(i)->GetTextureImage(channels[c]);
            spTexture tex = sg->CreateTexture();
            //This will be the unique string identifier we use to reference a texture in the texture table from the texture node
            char texName[MAX_PATH];
            sprintf(texName, "%s%i_%s","Material",i,channels[c]);
            tex->SetName(texName);
            //As noted above, a texture is either defined by a path or an image in memory
            if(texImg != NULL)
                tex->SetImageData(texImg);
            else
                tex->SetFilePath(texPath);
            //Add to texture table
            texTable->AddTexture(tex);
            //Now we construct the node that will replace the current texture reference
            spShadingTextureNode texNode = sg->CreateShadingTextureNode();
            texNode->SetTextureName(texName);
            texNode->SetTextureLevelName(original_materials->GetMaterial(i)->GetTextureLevelName(channels[c]));
            //Normal maps get all kinds of jumbled if you try to use sRGB on them
            if(strcmp(channels[c], SG_MATERIAL_CHANNEL_NORMALS) == 0)
                texNode->SetUseSRGB(false);
            //Add node, and remove the image and/or path from the material
            original_materials->GetMaterial(i)->SetShadingNetwork(channels[c], texNode);
            //original_materials->GetMaterial(i)->SetTexture(channels[c], ""); //Remove the texture reference, we have a node instead
            //original_materials->GetMaterial(i)->SetTextureImage(channels[c], NULL); //Remove the texture image, we have a node instead
            }
        }
#endif
    //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();
    //  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 );
#if USE_SHADING_NETWORKS == 1
    ColorCaster->SetSourceTextures(texTable); //If we are casting materials defined by shading networks, a source texture table also needs to be set
#endif
    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->CastMaterials();
    // set the material properties
    // Set material to point to created texture filename.
    output_material->SetTexture( 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->CastMaterials();
    // set the material properties
    // Set material to point to created texture filename.
    output_material->SetTexture( 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->CastMaterials();
    // set the material properties
    // Set material to point to created texture filename.
    output_material->SetTexture( "user_channel" , output_userchannel_filename.c_str() );
    // NORMAL MAP
    // cast the normal map texture data
    spNormalCaster NormalCaster = sg->CreateNormalCaster();
    NormalCaster->SetSourceMaterials( original_materials );
#if USE_SHADING_NETWORKS == 1
    NormalCaster->SetSourceTextures(texTable); //If we are casting materials defined by shading networks, a source texture table also needs to be set
#endif
    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->CastMaterials();
    // Set material to point to created texture filename.
    output_material->SetTexture( 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);
    //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 );
    }