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