Tight fitting impostors with minimal overdraw
Disclaimer: The code in this post is written using version 10.2.8400.0 of Simplygon. If you encounter this post at a later stage, some of the API calls might have changed. However, the core concepts should still remain valid.
Tight fitting impostors
For some applications keeping the overdraw to a minimum can be beneficial even if the amount of geometry is increased. If the geometry only contains opaque parts then it doesn't need to be rendered with transparency.
The billboard cloud processor has been upgraded to be able to generate impostors with minimal overdraw. It now allows holes inside of the impostors. It also reads the input opacity map. By reading the opacity map it's able to create a tight fitting geometry around only the opaque parts of the original asset. The end result will have more geometry, but can eliminate having to use an opacity map and the entire object can be rendered as opaque (depending on how closely the impostor is set to track the visible pixels, and if the opacity map is simply meant to either be opaque or transparent).
Using the input opacity map
Just like with the Material Casters, the user needs to specify where the opacity is located. Usually the alpha is either in the 4th component of the BaseColor/Diffuse/Opacity channel or in the 1st component of the Opacity channel.
billboardCloudSettings->SetOpacityChannel( SG_MATERIAL_CHANNEL_OPACITY );
billboardCloudSettings->SetOpacityChannelComponent( EColorComponent::Alpha );
And the material needs to be either set to Mask or Blend for the opacity map to even be considered.
materialTable->GetMaterial( m )->SetBlendMode( EMaterialBlendMode::Blend );
The opacity mode determines whether the value specifies transparency or opacity.
materialTable->GetMaterial( m )->SetOpacityType(EOpacityType::Opacity);
We can also set an Opacity cutoff value that determines at which threshold opacity values should be considered opaque.
billboardCloudSettings->SetOpacityCutoff( 0.5f );
That is it for opacity settings. To control how much geometry will be created is a mix of "Billboard density", "Geometric complexity" and "Max plane count". Billboard density controls how close the planes should be to the underlying geometry that they represent - like a maximum projection distance. But they can be clamped to a max plane count. Then geometric complexity is a value between 0 and 1. Having geometric complexity set to approximately 1 will generate a very tight fitting impostor that tracks almost per pixel in the opacity map.
billboardCloudSettings->SetBillboardDensity( 0.9f );
billboardCloudSettings->SetMaxPlaneCount( 10 );
billboardCloudSettings->SetGeometricComplexity( 0.95f );
Cube example
Here's a simple example with a cube that has a texture for the color and its grayscale determines the opacity. The geometry consists of 12 triangles.
We're generating 3 impostors that only vary the geometric complexity.
The results look pretty much identical to the original but the amount of geometry and overdraw differs a lot. The first Impostor was generated with the highest geometric complexity which has generated tight fitting triangles that follow the opacity per pixel. It does not need to be rendered with alpha masking.
The other two impostors have progressively decreasing complexity and increasing overdraw. There is a visible difference when rendering with alpha masking ON or OFF.
We can see how tight the geometries are by looking at the UVs and the generated opacity maps.
Impostor #1 opacity map | Impostor #2 opacity map | Impostor #3 opacity map |
---|---|---|
Leaves example
And here's a simple scene with impostors for a mesh with leaves.
Leaves. Front row has all alpha masking OFF for all impostors, and the back row has enabled alpha masking for all but the first impostor. |
---|
Tree example
Below is a billboard cloud for a tree. The geometric complexity is set to track closely at 0.95. The code to generate this billboard cloud is in the code section below.
Rendered without alpha masking | Opacity map |
---|---|
The opacity map shows some darker parts but mostly the geometry consists of opaque parts.
The impostor can then be optionally reduced afterwards to reduce excessive triangles.
Code
void AddSimplygonTexture( spMaterial mat, spTextureTable texTable, const char* channelName, const char* texFilePath, const char* namePrefix )
{
char texName[ 256 ];
sprintf_s( texName, "%s%s", namePrefix, channelName );
spTexture newTex = sg->CreateTexture();
newTex->SetFilePath( texFilePath );
newTex->SetName( texName );
texTable->AddTexture( newTex );
spShadingTextureNode texNode = sg->CreateShadingTextureNode();
texNode->SetTextureName( texName );
texNode->SetTexCoordLevel( 0 );
mat->AddMaterialChannel( channelName );
mat->SetShadingNetwork( channelName, texNode );
}
void RunTightFittingImpostor()
{
spSceneImporter sceneImporter = sg->CreateSceneImporter();
sceneImporter->SetImportFilePath( readFrom.c_str() );
auto readerResult = sceneImporter->Run();
if( Simplygon::Failed( readerResult ) )
throw std::exception( "Failed to load input file!" );
// Get the scene
spScene originalScene = sceneImporter->GetScene();
// Parameters
float density = 0.9f;
float geometricComplexity = 0.95f;
uint maxPlaneCount = 10;
uint reductionOnScreenSize = 120;
bool reduceImpostor = false;
// Set all materials' blend mode
spMaterialTable materialTable = originalScene->GetMaterialTable();
uint mCount = materialTable->GetMaterialsCount();
for( uint m = 0; m < mCount; ++m )
{
materialTable->GetMaterial( m )->SetBlendMode( EMaterialBlendMode::Blend );
materialTable->GetMaterial( m )->SetOpacityType( EOpacityType::Opacity );
}
spScene lodScene = sg->CreateScene();
lodScene->DeepCopy( originalScene );
spImpostorProcessor impostorProcessor = sg->CreateImpostorProcessor();
impostorProcessor->GetImpostorSettings()->SetImpostorType( EImpostorType::BillboardCloud );
impostorProcessor->SetScene( lodScene );
spMappingImageSettings mSettings = impostorProcessor->GetMappingImageSettings();
mSettings->GetOutputMaterialSettings( 0 )->SetTextureWidth( 1024 );
mSettings->GetOutputMaterialSettings( 0 )->SetTextureHeight( 1024 );
mSettings->SetMaximumLayers( 10 );
mSettings->GetOutputMaterialSettings( 0 )->SetMultisamplingLevel( 2 );
spBillboardCloudSettings billboardCloudSettings = impostorProcessor->GetImpostorSettings()->GetBillboardCloudSettings();
billboardCloudSettings->SetBillboardMode( EBillboardMode::Foliage );
billboardCloudSettings->SetBillboardDensity( density );
billboardCloudSettings->SetGeometricComplexity( geometricComplexity );
billboardCloudSettings->SetMaxPlaneCount( maxPlaneCount );
spFoliageSettings foliageSettings = impostorProcessor->GetImpostorSettings()->GetBillboardCloudSettings()->GetFoliageSettings();
foliageSettings->SetSeparateTrunkAndFoliage( true );
foliageSettings->SetSeparateFoliageMaterials( "[\"m_leafs\"]" );
foliageSettings->SetSeparateFoliageTriangleRatio( 0.01f );
foliageSettings->SetSeparateFoliageTriangleThreshold( 10 );
foliageSettings->SetSeparateFoliageAreaThreshold( 0.1f );
foliageSettings->SetSeparateFoliageSizeThreshold( 0.1f );
foliageSettings->SetTrunkReductionRatio( 0.5f );
foliageSettings->SetMaintainLeafConnections( true );
// Tight fitting opacity settings
billboardCloudSettings->SetOpacityChannel( SG_MATERIAL_CHANNEL_OPACITY );
billboardCloudSettings->SetOpacityChannelComponent( EColorComponent::Alpha );
billboardCloudSettings->SetOpacityCutoff( 0.2f );
impostorProcessor->RunProcessing();
spMappingImage foliageMappingImage = impostorProcessor->GetMappingImage();
if( foliageMappingImage )
{
// Cast materials
spMaterialTable originalMaterialTable = originalScene->GetMaterialTable();
spTextureTable originalTextures = originalScene->GetTextureTable();
spMaterialTable lodMaterialTable = lodScene->GetMaterialTable();
spTextureTable lodTextureTable = lodScene->GetTextureTable();
// Get the newly created foliage material
spMaterial foliageMaterial = lodMaterialTable->GetMaterial( impostorProcessor->GetBillboardCloudMaterialId() );
spColorCaster colorCaster = sg->CreateColorCaster();
colorCaster->GetColorCasterSettings()->SetOpacityChannel( SG_MATERIAL_CHANNEL_OPACITY );
colorCaster->GetColorCasterSettings()->SetOpacityChannelComponent( EColorComponent::Alpha );
colorCaster->SetSourceMaterials( originalMaterialTable );
colorCaster->SetSourceTextures( originalTextures );
colorCaster->SetMappingImage( foliageMappingImage );
colorCaster->GetColorCasterSettings()->SetDilation( 10 );
colorCaster->GetColorCasterSettings()->SetBakeOpacityInAlpha( false );
colorCaster->GetColorCasterSettings()->SetUseMultisampling( true );
colorCaster->GetColorCasterSettings()->SetFillMode( EAtlasFillMode::Interpolate );
colorCaster->GetColorCasterSettings()->SetOutputSRGB( true );
colorCaster->GetColorCasterSettings()->SetMaterialChannel( SG_MATERIAL_CHANNEL_DIFFUSE );
colorCaster->GetColorCasterSettings()->SetOutputPixelFormat( EPixelFormat::R8G8B8 );
colorCaster->SetOutputFilePath( outputDiffuseFileName.c_str() );
colorCaster->RunProcessing();
AddSimplygonTexture( foliageMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_DIFFUSE, outputDiffuseFileName.c_str() );
spNormalCaster normalCaster = sg->CreateNormalCaster();
normalCaster->GetNormalCasterSettings()->SetOpacityChannel( SG_MATERIAL_CHANNEL_OPACITY );
normalCaster->GetNormalCasterSettings()->SetOpacityChannelComponent( EColorComponent::Alpha );
normalCaster->SetSourceMaterials( originalMaterialTable );
normalCaster->SetSourceTextures( originalTextures );
normalCaster->SetMappingImage( foliageMappingImage );
normalCaster->GetNormalCasterSettings()->SetOutputPixelFormat( EPixelFormat::R8G8B8 );
normalCaster->GetNormalCasterSettings()->SetDilation( 10 );
normalCaster->SetOutputFilePath( outputNormalFileName.c_str() );
normalCaster->GetNormalCasterSettings()->SetFlipBackfacingNormals( true );
normalCaster->GetNormalCasterSettings()->SetGenerateTangentSpaceNormals( true );
normalCaster->RunProcessing();
AddSimplygonTexture( foliageMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_NORMALS, outputNormalFileName.c_str() );
spOpacityCaster opacityCaster = sg->CreateOpacityCaster();
opacityCaster->GetOpacityCasterSettings()->SetOpacityChannel( SG_MATERIAL_CHANNEL_OPACITY );
opacityCaster->GetOpacityCasterSettings()->SetOpacityChannelComponent( EColorComponent::Alpha );
opacityCaster->SetSourceMaterials( originalMaterialTable );
opacityCaster->SetSourceTextures( originalTextures );
opacityCaster->SetMappingImage( foliageMappingImage );
opacityCaster->GetOpacityCasterSettings()->SetOutputPixelFormat( EPixelFormat::R8 );
opacityCaster->SetOutputFilePath( outputOpacityFileName.c_str() );
opacityCaster->GetOpacityCasterSettings()->SetDilation( 0 );
opacityCaster->GetOpacityCasterSettings()->SetFillMode( EAtlasFillMode::NoFill );
opacityCaster->RunProcessing();
AddSimplygonTexture( foliageMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_OPACITY, outputOpacityFileName.c_str() );
}
if( reduceImpostor )
{
// Optionally use the reducer on the output to remove excessive triangles
spReductionProcessor red = sg->CreateReductionProcessor();
red->SetScene( lodScene );
red->GetReductionSettings()->SetReductionTargetOnScreenSize( reductionOnScreenSize );
red->GetReductionSettings()->SetReductionTargets( EStopCondition::All, false, false, false, true );
red->RunProcessing();
}
spSceneExporter exporter = sg->CreateSceneExporter();
exporter->SetExportFilePath( outputGeometryFilename.c_str() );
exporter->SetScene( lodScene );
exporter->Run();