///////////////////////////////////////////////////////////////////////////
//
// 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);
}