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).

Leafs, original at 22 triangles, impostor at 608 triangles and reduces impostor at 129 triangles

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.

Cube with opacity map

Cube with opacity map

Rendered with alpha masking on for 2nd and 3rd impostor

Rendered with alpha masking on for 2nd and 3rd impostor

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.

All impostors rendered without alpha masking

All impostors rendered without 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.

Reduced billboard cloud

Reduced billboard cloud


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


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

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

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

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

		spSceneExporter exporter = sg->CreateSceneExporter();
		exporter->SetExportFilePath( outputGeometryFilename.c_str() );
		exporter->SetScene( lodScene );

⇐ Back to blog post list