///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: Impostor.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 use the ImpostorProcessor to generate a two-triangle
// billboard impostor geometry of an input geometry from a specific viewing
// angle, and cast textures and normals from the original geometry to the
// impostor.
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
//Observer for monitoring progress
class ProgressObserver : public robserver
{
public:
virtual void Execute(
IObject * subject,
rid EventId,
void * EventParameterBlock,
unsigned int EventParameterBlockSize )
{
// only care for progress events
if( EventId == SG_EVENT_PROGRESS )
{
// get the progress in percent
int val = *((int *)EventParameterBlock);
// tell the process to continue
// this is required by the progress event
*((int *)EventParameterBlock) = 1;
// output the progress update
PrintProgressBar( val );
}
}
} progressObserver;
void RunImpostorGenerationWithTextureCasting( const std::string& readFrom, 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 HQ reduction example, reducing a single geometry to a single LOD
printf("Running impostor generation and texture casting... \n");
PrintProgressBar(0); //Initial progress bar
RunImpostorGenerationWithTextureCasting(assetPath + "wall.obj", "wall_impostor.obj");
printf("\nDone.\n\n");
// Done!
printf("LOD complete, shutting down...");
DeinitExample();
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
return 0;
}
void RunImpostorGenerationWithTextureCasting( const std::string& readFrom, const std::string& writeTo )
{
// Load input geometry from file
spWavefrontImporter objReader = sg->CreateWavefrontImporter();
objReader->SetExtractGroups( false ); //This makes the .obj reader import into a single geometry object instead of multiple
objReader->SetImportFilePath( readFrom.c_str() );
if( !objReader->RunImport() )
throw std::exception("Failed to load input file!");
// Get the scene from the importer
// Will only contain a single geom
spScene scene = objReader->GetScene();
spMaterialTable originalMaterialTable = scene->GetMaterialTable();
spTextureTable originalTextureTable = scene->GetTextureTable();
spSelectionSet selectSceneMeshes = scene->GetSelectionSetTable()->GetSelectionSet( scene->SelectNodes( "ISceneMesh" ) );
spSceneMesh sceneMesh = Cast<ISceneMesh>( scene->GetNodeByGUID( selectSceneMeshes->GetItem( 0 ) ) );
spGeometryData originalGeom = sceneMesh->GetGeometry();
// Make sure geometry has normals and tangents, since it will cast tangent space normals wrong if either are not present and an input normal map exists.
spNormalRepairer normRep = sg->CreateNormalRepairer();
normRep->SetGeometry( originalGeom );
normRep->RunProcessing();
spTangentCalculator tanCalc = sg->CreateTangentCalculator();
tanCalc->CalculateTangents( originalGeom );
// Create the impostor processor, and set the geometry
spImpostorProcessor imp = sg->CreateImpostorProcessor();
imp->SetGeometry( originalGeom );
///////////////////////////////////////////////////////////////////////////////////////////////
// SETTINGS
spImpostorSettings impostorSettings = imp->GetImpostorSettings();
//The viewdir determines from what direction the billboard will be visible.
//If making a billboard replacement for something like a wall segment, this vector should point
//directly towards the wall, perfectly orthogonal to the wall plane.
//The impostor processor always uses positive Y as up, meaning that the quads are aligned with the Y axis.
real viewDir[3] = { 0.f,0.f,-1.0f };
impostorSettings->SetViewDirection( viewDir );
//This setting controls the extents of the generated billboard geometry. If true, the impostor geometry will EXACTLY
//encapsulate the original geometry from the set viewing angle. If false, the billboards generated are always square,
//with sides as wide as the diameter of the object.
//It is worth noting that if this is true, the pixel density in X and Y will in most cases become non-uniform,
//since we do not yet know the aspect ratio of the impostor when setting texture size.
//You can fix this by calculating the aspect ratio beforehand with the function below.
impostorSettings->SetUseTightFitting( true );
//The depth offset is the offset in the impostor-space z direction of the resulting plane.
//0 puts the impostor in the center of the input geometry.
//1 puts the impostor at the "front" of the input geometry
//-1 puts the impostor at the "back" of the input geometry.
impostorSettings->SetTightFittingDepthOffset( 1.f );
//This makes the texcoords generated in the range 0.01 to 0.99 instead of 0 to 1, fixing some potential sub-pixel
//rendering errors near the edge of the impostor if you're using tiled textures in your engine.
impostorSettings->SetTexCoordPadding( 0.01f );
//Once geometry and settings are set, you can calculate the aspect ratio for your textures.
real aspect = imp->CalculateImpostorAspectRatio();
int xDim = int( aspect * 512.f );
int yDim = int( 1.f * 512.f );
//These settings determine the dimensions of the output mapping image, and hence baked textures.
//MappingImageSettings contains a lot of further settings, but none of those are relevant to the impostor processor.
spMappingImageSettings mappingSettings = imp->GetMappingImageSettings();
mappingSettings->SetWidth( xDim );
mappingSettings->SetHeight( yDim );
mappingSettings->SetMultisamplingLevel( 2 );
//END SETTINGS
///////////////////////////////////////////////////////////////////////////////////////////////
// Add observer
imp->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
// Run the actual processing.
// In contrast to the reduction and remeshing processors, the input geometry is never replaced by the lod.
// Instead, the lod can be specifically fetched using imp->GetImpostorGeometry();
imp->RunProcessing();
// The processing is done. Fetch the generated impostor, and the output mapping image
spGeometryData lodGeometry = imp->GetImpostorGeometry();
spMappingImage mappingImage = imp->GetMappingImage();
///////////////////////////////////////////////////////////////////////////////////////////////
// CASTING
// Now, we want to cast the materials and normals from our original geometry onto the billboard geometry.
// Note the OPACITY casting, as it might be important depending on the type of impostor you want
// First, create a new material table.
spMaterialTable lodMaterialTable = sg->CreateMaterialTable();
spTextureTable lodTextureTable = sg->CreateTextureTable();
// Create new material for the table.
spMaterial lodMaterial = sg->CreateMaterial();
lodMaterial->SetName( "SimplygonBakedMaterial" );
lodMaterialTable->AddMaterial( lodMaterial );
// Cast diffuse and specular texture data with a color caster
if( true )
{
// Cast the data using a color caster
spColorCaster colorCaster = sg->CreateColorCaster();
colorCaster->SetSourceMaterials( originalMaterialTable );
colorCaster->SetSourceTextures( originalTextureTable );
colorCaster->SetMappingImage( mappingImage ); //The mapping image we got from the impostor processor.
colorCaster->SetOutputChannelBitDepth( 8 ); //8 bits per channel. So in this case we will have 24bit colors RGB.
colorCaster->SetBakeOpacityInAlpha( false );
colorCaster->SetDilation( 10 ); //To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
colorCaster->SetFillMode( SG_ATLASFILLMODE_INTERPOLATE ); //This determines what to do with the empty space left on the atlas after dilation
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, lodTextureTable, SG_MATERIAL_CHANNEL_DIFFUSE, "combinedDiffuseMap.png" );
AddSimplygonTexture( lodMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_SPECULAR, "combinedSpecularMap.png" );
}
// Cast normal map texture data with the normal caster.
if( true )
{
spNormalCaster normalCaster = sg->CreateNormalCaster();
normalCaster->SetSourceMaterials( originalMaterialTable );
normalCaster->SetSourceTextures( originalTextureTable );
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( 10 );
normalCaster->SetOutputFilePath( "combinedNormalMap.png" );
normalCaster->SetFlipBackfacingNormals( true );
normalCaster->SetGenerateTangentSpaceNormals( true );
normalCaster->SetFillMode( SG_ATLASFILLMODE_INTERPOLATE );
normalCaster->RunProcessing();
// Set normal map of the created material to point to the combined normal map
AddSimplygonTexture( lodMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_NORMALS, "combinedNormalMap.png" );
lodMaterial->SetUseTangentSpaceNormals( true );
}
// Cast an opacity map
// This step is important if you want an impostor with alpha transparency that works correctly.
// There are some specific settings that needs to be used, otherwise the empty space will be filled
// by the dilation and fill steps, rather than contain transparency.
// The same settings will also apply if you bake the alpha into the diffuse map, rather than into a specific map.
if( true )
{
// cast the data using an opacity caster
spOpacityCaster opacityCaster = sg->CreateOpacityCaster();
opacityCaster->SetSourceMaterials( originalMaterialTable );
opacityCaster->SetMappingImage( mappingImage );
opacityCaster->SetOutputChannels( 1 ); // L, 1 channel
opacityCaster->SetOutputChannelBitDepth( 8 );
opacityCaster->SetDilation( 0 ); // If this is not 0, the opaque pixels will dilate into the "empty area"
opacityCaster->SetOutputFilePath( "combinedOpacityMap.png" );
opacityCaster->SetFillMode( SG_ATLASFILLMODE_NONE );// IMPORTANT! If this is not set to NONE, it will fill the areas that should be transparant with potentially opaque values.
opacityCaster->RunProcessing();
// Set opacity map of the created material to point to the combined normal map
AddSimplygonTexture( lodMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_OPACITY, "combinedOpacityMap.png" );
}
// END CASTING
///////////////////////////////////////////////////////////////////////////////////////////////
spScene ImpostorScene = sg->CreateScene();
ImpostorScene->GetRootNode()->CreateChildMesh( lodGeometry );
ImpostorScene->GetMaterialTable()->Copy( lodMaterialTable );
ImpostorScene->GetTextureTable()->Copy( lodTextureTable );
//Create an .obj exporter to save our result
spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
std::string outputGeomFilename = GetExecutablePath() + writeTo;
// Do the actual exporting
objExporter->SetExportFilePath( outputGeomFilename.c_str() );
objExporter->SetScene( ImpostorScene );
objExporter->RunExport();
//Done! LOD and material created.
}